feat: Phase 2B — staff payroll / hourly rate
- Users: add hourly_rate (Float, nullable) — manager-only, not shown to waiter - WaiterShift: add hourly_rate_snapshot — copied from user.hourly_rate at shift open, never updated - Shift start (both /start and /manager/start): snapshot rate at shift creation - compute_shift_pay(): calculates worked hours (minus breaks) × rate_snapshot; returns None if unset - _enrich_shift(): now includes hourly_rate_snapshot, duration_hours, shift_pay - shifts_report in reports.py: delegates to _enrich_shift so all shift endpoints are consistent - Shifts CSV export: adds duration_hours, hourly_rate, shift_pay columns - StaffTab: hourly_rate field in waiter edit modal (Μισθοδοσία section, col 3) - ShiftsOverview report: Αμοιβή/ώρα + Αποδοχές columns; untracked shows 'Δεν παρακολουθείται' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -277,7 +277,7 @@ export default function WaitersPage() {
|
||||
const [newAvatarFile, setNewAvatarFile] = useState(null)
|
||||
const [newAvatarPreview, setNewAvatarPreview] = useState(null)
|
||||
const [editModal, setEditModal] = useState(null)
|
||||
const [editForm, setEditForm] = useState({ username: '', full_name: '', nickname: '', mobile_phone: '', email: '', note: '', role: 'waiter' })
|
||||
const [editForm, setEditForm] = useState({ username: '', full_name: '', nickname: '', mobile_phone: '', email: '', note: '', role: 'waiter', hourly_rate: '' })
|
||||
const avatarInputRef = useRef(null)
|
||||
const newAvatarInputRef = useRef(null)
|
||||
|
||||
@@ -353,7 +353,7 @@ export default function WaitersPage() {
|
||||
|
||||
function openEdit(w) {
|
||||
setEditModal(w)
|
||||
setEditForm({ username: w.username || '', full_name: w.full_name || '', nickname: w.nickname || '', mobile_phone: w.mobile_phone || '', email: w.email || '', note: w.note || '', role: w.role || 'waiter' })
|
||||
setEditForm({ username: w.username || '', full_name: w.full_name || '', nickname: w.nickname || '', mobile_phone: w.mobile_phone || '', email: w.email || '', note: w.note || '', role: w.role || 'waiter', hourly_rate: w.hourly_rate ?? '' })
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -443,6 +443,7 @@ export default function WaitersPage() {
|
||||
full_name: editForm.full_name || null, nickname: editForm.nickname || null,
|
||||
mobile_phone: editForm.mobile_phone || null, email: editForm.email || null,
|
||||
note: editForm.note || null, role: editForm.role,
|
||||
hourly_rate: editForm.hourly_rate !== '' ? parseFloat(editForm.hourly_rate) : null,
|
||||
})}
|
||||
onUploadAvatar={file => uploadAvatar.mutate({ id: editModal.id, file })}
|
||||
onDeleteAvatar={() => deleteAvatar.mutate(editModal.id)}
|
||||
@@ -719,7 +720,7 @@ function EditWaiterModal({ waiter, form, setForm, avatarInputRef, isPending, isU
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 3: Info + PIN note */}
|
||||
{/* Column 3: Info + Payroll + PIN note */}
|
||||
<div style={{ padding: '20px 22px', display: 'flex', flexDirection: 'column', gap: 16, background: '#fafafa' }}>
|
||||
<div>
|
||||
<SectionLabel>Πληροφορίες</SectionLabel>
|
||||
@@ -737,7 +738,31 @@ function EditWaiterModal({ waiter, form, setForm, avatarInputRef, isPending, isU
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 8, padding: 14, background: '#f3f4f6', borderRadius: 10 }}>
|
||||
{/* Phase 2B — Payroll */}
|
||||
<div>
|
||||
<SectionLabel>Μισθοδοσία</SectionLabel>
|
||||
<FormField label="Ωριαία αμοιβή (€/ώρα)" desc="Μόνο για διαχειριστές. Δεν εμφανίζεται στον εργαζόμενο.">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<input
|
||||
type="number" step="0.50" min="0"
|
||||
style={{ ...inputStyle, width: 110 }}
|
||||
placeholder="π.χ. 5.50"
|
||||
value={form.hourly_rate}
|
||||
onChange={e => f('hourly_rate', e.target.value)}
|
||||
/>
|
||||
{form.hourly_rate !== '' && parseFloat(form.hourly_rate) > 0 && (
|
||||
<span style={{ fontSize: 11.5, color: '#16a34a', fontWeight: 500 }}>
|
||||
€{parseFloat(form.hourly_rate).toFixed(2)}/ώρα
|
||||
</span>
|
||||
)}
|
||||
{form.hourly_rate === '' && (
|
||||
<span style={{ fontSize: 11, color: '#9ca3af' }}>Δεν παρακολουθείται</span>
|
||||
)}
|
||||
</div>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 4, padding: 14, background: '#f3f4f6', borderRadius: 10 }}>
|
||||
<p style={{ margin: '0 0 4px', fontSize: 11.5, fontWeight: 600, color: '#374151' }}>Κωδικός PIN</p>
|
||||
<p style={{ margin: 0, fontSize: 11.5, color: '#6b7280', lineHeight: 1.5 }}>
|
||||
Για αλλαγή PIN χρησιμοποιήστε την επιλογή <strong style={{ color: '#374151' }}>Επαναφορά PIN</strong> από το μενού Ενεργειών.
|
||||
|
||||
Reference in New Issue
Block a user