- Base font 9pt→10pt, all sub-elements scaled proportionally
- Order number and date shown in dates column when invoice linked to order
- Uses customer_order_number with fallback to internal order_number
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Header with red accent border, larger invoice number
- Address blocks in connected table grid with equal heights
- Customer and bank info highlighted with gray background
- Bank info uses same row layout as dates (aligned labels/values)
- Labels nowrap, values right-aligned
- Item font size 8pt, table header border gray
- Removed duplicate separator lines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Linux lacks Segoe UI semibold, so weight 500 rendered as regular.
Changed to 600 which maps to bold on both Windows and Linux.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admins were seeing all requests on their own requests page.
Added mine=1 param to force user_id filter regardless of role.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Filter attendance admin/balances/workfund to users with attendance.record
permission or admin role
- New attendance_users API action for user dropdown
- Fix missing prisma import in attendance route
- Fix user edit: empty password no longer blocks save (preprocess to undefined)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Queried status "converted"/"expired" but actual DB values are
"ordered"/"invalidated". Updated label "Prošlé" → "Zneplatněné".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- System settings page with tabs: Security, System, Firma
- Configurable attendance rules (break thresholds, rounding) from DB
- Configurable document numbering with template patterns ({YYYY}/{PREFIX}/{NNN})
- Dynamic logo upload (light/dark variants) served from DB instead of static files
- Email settings (SMTP from/name, alert/leave emails) configurable in UI
- Currency and VAT rate lists configurable, used across all modules
- Permissions simplified: offers.settings + settings.roles + settings.security → settings.manage
- Leaflet bundled locally, removed unpkg.com from CSP
- Silent catch blocks fixed with proper logging
- console.log replaced with app.log.info in server.ts
- Schema renamed: company-settings.schema → settings.schema
- App info section: version, Node.js, uptime, memory, DB status, NAS status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- NAS storage for created invoices (PDF via puppeteer), received invoices,
and offers with auto-save on create/edit
- Deterministic file paths derived from DB fields (no file_path column needed)
- Separate NAS mount points: NAS_FINANCIALS_PATH, NAS_OFFERS_PATH
- Invoice language field (cs/en) stored per invoice, replaces lang modal
- Invoices list filtered by month/year matching KPI card selection
- Centralized date helpers (src/utils/date.ts) replacing all .toISOString()
calls that returned UTC instead of local time
- Attendance project switching uses exact time (not rounded)
- Comment cleanup: removed ~100 unnecessary/Czech comments
- Removed as-any casts in orders and attendance
- Prisma migrations: add invoice language, drop received_invoices BLOB columns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Route handlers: add exhaustive return after error checks so TypeScript
narrows the union and result properties are accessible without casts
- attendance.service: use Prisma.attendanceGetPayload for included relations
- projects.service: remove unnecessary cast on orders relation
- Dashboard.tsx: replace Record<string,any> with proper DashData interface
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical:
- InvoiceDetail: sanitize notes HTML with DOMPurify
- OrderDetail: use proper DOMPurify import instead of window fallback
Important:
- AttendanceBalances: add fund_to_date to interface, remove as-any casts
- All schemas: replace z.any() with z.preprocess for boolean fields
- Routes: simplify boolean coercion (Zod handles it now)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user A opens an offer, a lock is acquired (locked_by + locked_at).
User B opening the same offer sees a warning banner and the form is
read-only. Lock expires after 5 minutes without heartbeat.
Backend:
- POST /:id/lock — acquire lock (returns 423 if locked by another)
- POST /:id/heartbeat — refresh lock timestamp (every 2 min)
- POST /:id/unlock — release lock
- GET /:id — includes locked_by info
- PUT /:id — auto-releases lock on save
Frontend:
- Acquires lock on page load (edit mode only)
- Sends heartbeat every 2 minutes
- Releases lock on page unmount (navigate away)
- Shows warning banner with locker's name
- All inputs read-only + action buttons hidden when locked
Migration: adds locked_by (INT) and locked_at (DATETIME) to quotations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was using toISOString() which outputs UTC time. Now formats with
getHours/getMinutes (local time via TZ=Europe/Prague), outputting
"07:45" instead of "2026-03-24T06:45:00.000Z".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
clearCookie was missing httpOnly, secure, sameSite — browser ignored
the Set-Cookie header because the options didn't match the original
cookie attributes. Cookie persisted after logout, allowing F5 to
re-authenticate via silent refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The TOTP verification endpoint always created refresh tokens with
remember_me=false and 1-hour expiry, regardless of what the user
selected at login.
Fix:
- Frontend now sends remember_me in the TOTP verify request body
- Backend reads remember_me and uses it for token expiry (30 days)
and cookie maxAge
Users with 2FA who checked "remember me" will now stay logged in
for 30 days instead of being kicked out after 1 hour.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added billing_text column to invoices table (VARCHAR 500)
- Prisma migration: 20260323_add_billing_text
- Form field on invoice create page with placeholder
- PDF uses billing_text, falls back to default translation
- Stored on create and editable on draft invoices
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API now only returns data sections the user has permission to see:
- my_shift: attendance.record
- attendance: attendance.admin
- offers: offers.view
- projects: projects.view
- invoices: invoices.view
- orders: orders.view
- leave_pending: attendance.approve
- recent_activity: settings.audit
Frontend hides KPI cards, activity feed, and attendance sections
for users without the matching permissions.
Regular employees now only see their shift status, quick actions,
profile, and sessions — not company KPIs or admin data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added GET /api/admin/received-invoices/suppliers endpoint (distinct names)
- Upload and edit forms use HTML datalist for browser-native autocomplete
- Suggestions loaded once on page mount
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vehicle list (GET) now requires only authentication, not trips.vehicles
permission. Users with trips.view can see available cars in the trip
modal. Create/update/delete still require trips.vehicles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- mailer.ts: nodemailer transport via local sendmail
- leave-notification.ts: HTML email matching PHP template
- Sends notification to LEAVE_NOTIFY_EMAIL on new leave request
- Non-blocking: errors logged but don't fail the request
- Added LEAVE_NOTIFY_EMAIL and APP_URL env vars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no trips exist for a vehicle, the last-km endpoint now returns
the vehicle's initial_km instead of 0, matching the PHP behavior:
COALESCE(MAX(end_km), vehicle.initial_km, 0)
Also fixed ordering from id DESC to end_km DESC for correctness.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generates QR code in SVG format using the SPAYD payment standard,
matching the PHP implementation. Contains: IBAN, amount, currency,
variable symbol, constant symbol, and invoice reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>