Add shift planning, month closure + payroll, and a dark modern redesign
Ports two modules from the friend's Google Apps Script build (kept as reference in gscript/) onto the TypeScript stack, rewritten cleanly against this app's own data model rather than copied 1:1: - Shift planning: weekly template (Sun-Thu evening, Fri/Sat two slots), lazy idempotent generation per period (no cron needed), employee signup/cancel with collision + capacity checks, admin calendar view with slot editing and manual assignment. - Month closure + payroll: employee confirms the month (blocked while a shift is still open), admin locks and finalizes pay (base hours * rate + tips/bonus/other), reopen to undo a premature lock, mark paid. Pay rates are versioned by date, defaulting the first-ever rate to apply retroactively to the employee's whole history. - A shift left open more than 12h (forgotten clock-out) is auto-closed at clock_in + 12h, checked lazily on read instead of a background job. - Full dark, sharp-edged modern restyle (theme.css replaces tui.css) with an amber accent, keeping every existing class name so no component logic needed to change. Backend test coverage (jest) for all three workflows: shift planning, closure/payroll, and the forgotten-clock-out auto-close.
This commit is contained in:
295
gscript/Config.js
Normal file
295
gscript/Config.js
Normal file
@@ -0,0 +1,295 @@
|
||||
const CFG = Object.freeze({
|
||||
APP_NAME: 'EatMe Portál',
|
||||
TZ: 'Europe/Prague',
|
||||
SESSION_DAYS: 7,
|
||||
LOGIN_CODE_MINUTES: 15,
|
||||
PASSWORD_ROUNDS: 12000,
|
||||
|
||||
SHEETS: {
|
||||
USERS: 'USERS',
|
||||
EMPLOYEES: 'EMPLOYEES',
|
||||
COMPANIES: 'COMPANIES',
|
||||
LOCATIONS: 'LOCATIONS',
|
||||
USER_COMPANIES: 'USER_COMPANIES',
|
||||
PAY_RATES: 'PAY_RATES',
|
||||
ATTENDANCE_EVENTS: 'ATTENDANCE_EVENTS',
|
||||
SHIFTS: 'SHIFTS',
|
||||
SHIFT_CHANGE_REQUESTS: 'SHIFT_CHANGE_REQUESTS',
|
||||
SCHEDULES: 'SCHEDULES',
|
||||
REQUESTS: 'REQUESTS',
|
||||
MONTH_CLOSURES: 'MONTH_CLOSURES',
|
||||
PAYROLL: 'PAYROLL',
|
||||
PAYROLL_ITEMS: 'PAYROLL_ITEMS',
|
||||
PAYSLIPS: 'PAYSLIPS',
|
||||
NEWS: 'NEWS',
|
||||
NEWS_READS: 'NEWS_READS',
|
||||
DOCUMENTS: 'DOCUMENTS',
|
||||
NOTIFICATIONS: 'NOTIFICATIONS',
|
||||
SESSIONS: 'SESSIONS',
|
||||
LOGIN_CODES: 'LOGIN_CODES',
|
||||
AUDIT_LOG: 'AUDIT_LOG',
|
||||
SETTINGS: 'SETTINGS'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const HEADERS = Object.freeze({
|
||||
|
||||
USERS: [
|
||||
'user_id',
|
||||
'email',
|
||||
'role',
|
||||
'employee_id',
|
||||
'password_salt',
|
||||
'password_hash',
|
||||
'active',
|
||||
'created_at',
|
||||
'last_login'
|
||||
],
|
||||
|
||||
EMPLOYEES: [
|
||||
'employee_id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
'company_id',
|
||||
'location_id',
|
||||
'position',
|
||||
'employment_type',
|
||||
'terminal_pin',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'active'
|
||||
],
|
||||
|
||||
COMPANIES: [
|
||||
'company_id',
|
||||
'name',
|
||||
'ico',
|
||||
'active'
|
||||
],
|
||||
|
||||
LOCATIONS: [
|
||||
'location_id',
|
||||
'company_id',
|
||||
'name',
|
||||
'active'
|
||||
],
|
||||
|
||||
USER_COMPANIES: [
|
||||
'user_id',
|
||||
'company_id'
|
||||
],
|
||||
|
||||
PAY_RATES: [
|
||||
'pay_rate_id',
|
||||
'employee_id',
|
||||
'valid_from',
|
||||
'valid_to',
|
||||
'hourly_rate',
|
||||
'created_by',
|
||||
'created_at'
|
||||
],
|
||||
|
||||
ATTENDANCE_EVENTS: [
|
||||
'event_id',
|
||||
'employee_id',
|
||||
'timestamp',
|
||||
'event_type',
|
||||
'location_id',
|
||||
'source',
|
||||
'created_by'
|
||||
],
|
||||
|
||||
SHIFTS: [
|
||||
'shift_id',
|
||||
'employee_id',
|
||||
'work_date',
|
||||
'clock_in',
|
||||
'clock_out',
|
||||
'break_minutes',
|
||||
'worked_minutes',
|
||||
'night_minutes',
|
||||
'weekend_minutes',
|
||||
'holiday_minutes',
|
||||
'status',
|
||||
'location_id',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
],
|
||||
|
||||
SHIFT_CHANGE_REQUESTS: [
|
||||
'request_id',
|
||||
'shift_id',
|
||||
'employee_id',
|
||||
'field',
|
||||
'old_value',
|
||||
'requested_value',
|
||||
'reason',
|
||||
'status',
|
||||
'created_at',
|
||||
'resolved_at',
|
||||
'resolved_by'
|
||||
],
|
||||
|
||||
SCHEDULES: [
|
||||
'schedule_id',
|
||||
'employee_id',
|
||||
'location_id',
|
||||
'start_at',
|
||||
'end_at',
|
||||
'status',
|
||||
'note',
|
||||
'created_by',
|
||||
'created_at'
|
||||
],
|
||||
|
||||
REQUESTS: [
|
||||
'request_id',
|
||||
'employee_id',
|
||||
'type',
|
||||
'date_from',
|
||||
'date_to',
|
||||
'reason',
|
||||
'status',
|
||||
'created_at',
|
||||
'resolved_at',
|
||||
'resolved_by',
|
||||
'note'
|
||||
],
|
||||
|
||||
MONTH_CLOSURES: [
|
||||
'closure_id',
|
||||
'employee_id',
|
||||
'period',
|
||||
'employee_confirmed_at',
|
||||
'manager_approved_at',
|
||||
'manager_approved_by',
|
||||
'locked_at',
|
||||
'status'
|
||||
],
|
||||
|
||||
PAYROLL: [
|
||||
'payroll_id',
|
||||
'employee_id',
|
||||
'period',
|
||||
'approved_minutes',
|
||||
'base_amount',
|
||||
'bonus_amount',
|
||||
'tips_amount',
|
||||
'other_amount',
|
||||
'final_amount',
|
||||
'status',
|
||||
'payment_date',
|
||||
'updated_at'
|
||||
],
|
||||
|
||||
PAYROLL_ITEMS: [
|
||||
'item_id',
|
||||
'payroll_id',
|
||||
'employee_id',
|
||||
'period',
|
||||
'type',
|
||||
'quantity',
|
||||
'rate',
|
||||
'amount',
|
||||
'note',
|
||||
'created_by',
|
||||
'created_at'
|
||||
],
|
||||
|
||||
PAYSLIPS: [
|
||||
'payslip_id',
|
||||
'employee_id',
|
||||
'period',
|
||||
'drive_file_id',
|
||||
'file_name',
|
||||
'uploaded_at',
|
||||
'uploaded_by'
|
||||
],
|
||||
|
||||
NEWS: [
|
||||
'news_id',
|
||||
'title',
|
||||
'content',
|
||||
'category',
|
||||
'company_id',
|
||||
'location_id',
|
||||
'target_role',
|
||||
'published_from',
|
||||
'published_to',
|
||||
'require_confirmation',
|
||||
'active',
|
||||
'created_by',
|
||||
'created_at'
|
||||
],
|
||||
|
||||
NEWS_READS: [
|
||||
'news_read_id',
|
||||
'news_id',
|
||||
'user_id',
|
||||
'read_at',
|
||||
'confirmed_at'
|
||||
],
|
||||
|
||||
DOCUMENTS: [
|
||||
'document_id',
|
||||
'employee_id',
|
||||
'company_id',
|
||||
'type',
|
||||
'title',
|
||||
'drive_file_id',
|
||||
'visible_to_employee',
|
||||
'uploaded_at',
|
||||
'uploaded_by'
|
||||
],
|
||||
|
||||
NOTIFICATIONS: [
|
||||
'notification_id',
|
||||
'user_id',
|
||||
'type',
|
||||
'title',
|
||||
'message',
|
||||
'url',
|
||||
'read_at',
|
||||
'created_at',
|
||||
'email_sent_at'
|
||||
],
|
||||
|
||||
SESSIONS: [
|
||||
'session_id',
|
||||
'user_id',
|
||||
'token_hash',
|
||||
'expires_at',
|
||||
'created_at',
|
||||
'last_seen_at'
|
||||
],
|
||||
|
||||
LOGIN_CODES: [
|
||||
'code_id',
|
||||
'email',
|
||||
'purpose',
|
||||
'code_hash',
|
||||
'expires_at',
|
||||
'used_at',
|
||||
'created_at'
|
||||
],
|
||||
|
||||
AUDIT_LOG: [
|
||||
'audit_id',
|
||||
'user_id',
|
||||
'action',
|
||||
'entity_type',
|
||||
'entity_id',
|
||||
'old_value',
|
||||
'new_value',
|
||||
'created_at'
|
||||
],
|
||||
|
||||
SETTINGS: [
|
||||
'key',
|
||||
'value'
|
||||
]
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user