Extend createOrder service to accept optional attachmentBuffer/attachmentName
parameters and persist them. Restructure the POST / multipart branch in the
orders route to dispatch on quotationId presence: from-quotation path is
unchanged, new manual-multipart path validates with CreateOrderSchema and
forwards the attachment. Add two TDD tests covering price line items and
PO file attachment storage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `create_project` flag (default true) to `CreateOrderSchema` and
`CreateOrderData`. When true and `quotation_id` is absent, `createOrder`
creates a `projects` row sharing the order's shared-pool number and
`order_id` link — mirroring the from-quotation flow. The route audits
the new project creation when it occurs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces CreateProjectSchema (Zod 4, NaN-guarded coercions), the
createProject() service function (atomic shared-number generation inside
a $transaction), getNextProjectNumber() preview helper, and two Vitest
tests that verify standalone project creation and distinct sequential
numbers. Routes are deferred to a subsequent task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nas-file-manager (21) and nas-financials-manager (9) had empty catch blocks that swallowed mkdir/write/rm/rename/stat/readdir failures with zero logging, making NAS issues undebuggable (violates the 'never silently swallow errors' rule). Added a contextual console.error to each real-failure catch (matching the existing service console.* pattern); control flow and return values unchanged. Left 3 ENOENT-as-expected catches (move/createFolder existence checks, symlink-traversal guard) as deliberate no-logs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
isAdminLike read authData.role, which doesn't exist on AuthData (it's roleName; role is on JwtPayload), so admins were always self-scoped on /entries and /overrides. Typed the param (no more any). Also removed 4 'category: input.category as any' casts now that category is a String column.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
verifyAccessToken logged every invalid/expired access token via console.error with a full stack trace. Access tokens are ~15 min and the client silently refreshes + retries on the 401, so this is an expected condition that flooded production logs (~every 15 min per active user) and buried real errors. Now only genuinely unexpected failures (non-JsonWebTokenError, e.g. a DB error in loadAuthData) are logged. No behavior change — verification still returns null on any token error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update validates the category only when it changes, so a note-only edit on an entry whose category was later hidden/deleted no longer 400s. The edit select also shows the current (retired) category marked '(skryta)' instead of silently snapping to the first active option; create defaults to an active category.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DELETE /plan/categories/:id now hard-deletes the row, guarded: refuses when any live entry/override uses the key (suggests hide instead) and keeps >=1 active category. Modal gains a per-row Smazat with inline confirm. Hiding stays via PATCH is_active.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace hardcoded planCategoryEnum z.enum with z.string().trim().min(1)
- Add assertActiveCategory() helper that rejects inactive/unknown keys
- Add resolveCategoryLabel() helper that resolves the Czech label from plan_categories DB table, falling back to built-in map
- Change PlanAuditDescriptionInput.category -> categoryLabel (pre-resolved string)
- Update buildPlanAuditDescription to use opts.categoryLabel directly
- Update all 6 buildPlanAuditDescription call sites in plan.service.ts to await resolveCategoryLabel(row.category)
- Add category validation in createEntry, updateEntry, createOverride, updateOverride
- Update planAuditDescription tests to pass categoryLabel instead of category
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Work-plan fixes from this session:
- Czech audit descriptions for plan entries/overrides (was empty '-')
- server-embedded project label on grid cells (was '-' past the 100-project cap)
- dashboard activity + audit-log cache invalidation on plan mutations
- read-only cells: no '+' on empty, keep hover on cells with a record
- view modal: Czech category + formatted dates
- sticky date column made opaque (no bleed-through on horizontal scroll)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three related fixes from manual smoke testing:
- Zod schema: drop min(1) on note field across all four schemas — note is
optional in real usage, and the modal initialized it to empty.
- assertNotPastDate: replace toISOString() (UTC) with local-time getters
to match the rest of the codebase. Fixes off-by-one near day boundaries.
- createEntry / updateEntry: also check the date_to endpoint for past
dates, so a range can't extend backwards into the past even when its
start is today or future. Defense in depth.
Modal: drop `required` attribute and asterisk from the note field. Tests:
add 5 new cases covering empty note, past date HTTP rejection, and the
new to-endpoint check in updateEntry.
- Add created_at to offer create/update schemas so user-selected date is stored instead of always defaulting to now()
- Translate payment method in invoice PDF based on language (Příkazem → Bank transfer, etc.)
- Add order_date and cnb_rate translation keys, replace hardcoded inline strings
- Remove duplicate has_order filter from offers backend route
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove exchange_rate and exchange_rate_date from quotations (schema, service, PDF, form)
- Add item_description field to invoice_items (schema, migration, service, form, PDF)
- Add offer status/customer/order filters with tab-based UI
- Clean up offer statuses to active/ordered/invalidated
- Allow invalidate action for non-ordered offers (not just expired)
- Add fade animations on offers and invoices table data changes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Change attendance idx_attendance_user_date from unique to index (allow multiple shifts per day)
- Reset migrations to single baseline init migration
- Add seed script with admin user (admin/admin)
- Update CLAUDE.md with migration workflow documentation
- Various frontend fixes (queries, pages, hooks)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove ProjectCreate page, POST /projects endpoint, and next-number endpoint
- Projects can only be created through orders (shared numbering sequence)
- Remove dead CreateProjectSchema and createProject service function
- Delete 'order' row from number_sequences (unused; code uses 'shared')
- Smart sequence release: decrement last_number only when deleting the highest number
- Fix received-invoices stats referencing non-existent is_deleted and amount_czk columns
- Update deploy instructions in CLAUDE.md (npm install, prisma migrate deploy)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove ref-mirror useEffect in AuthContext (cachedUserRef already written at mutation sites)
- Replace useEffect slide direction in ReceivedInvoices with render-time computation
- Fix Login.tsx useEffect dependency array (mount-only alert should have [] deps)
- Move "project created" alert to navigation source in ProjectCreate, remove useEffect in ProjectDetail
- Move companySettings defaults into fetch callbacks in InvoiceDetail and OfferDetail
- Replace due_date useEffect with useMemo in InvoiceDetail
- Capture initial snapshots from API data instead of useEffect in InvoiceDetail, OfferDetail, OrderDetail
- Replace localStorage draft useEffect with lazy useState initializer in OfferDetail
- Fix attendance dropdown to filter by attendance.record permission only
- Fix clock-out 404 on update-address (remove departure_time filter for departure action)
- Fix received-invoices stats endpoint referencing non-existent is_deleted and amount_czk columns
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
$queryRaw on MySQL returns BigInt for integer columns and 0/1 for booleans.
Passing these raw values back to Prisma client methods causes validation errors:
- Expected Int, provided BigInt
- Expected Boolean, provided Int
Fixed in auth refresh, TOTP login, and TOTP backup code flows by wrapping
storedToken.id, storedToken.user_id with Number() and remember_me with Boolean().
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- feat: order confirmation PDF generation with VAT support
- feat: order confirmation modal with custom item editing
- fix: attendance negative duration clamping and switchProject timing
- fix: Quill editor locked to Tahoma 14px, PDF heading sizes
- fix: invoice/offer PDF font consistency (Tahoma enforcement)
- fix: invoice alert cron improvements
- fix: NAS financials manager edge cases
- refactor: numbering service with unique sequence constraints
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Due date uses days selector in edit mode (same as create)
- Overdue invoices fully editable (same as issued)
- Overdue status reversed to issued when due date moved to future
- Invoice list: edit icon for issued/overdue, eye for paid
- Invoice list: PDF opens blob from NAS (removed lang modal)
- NAS cleanup: properly scans directories when cleaning old PDFs
- Fixed syntax error from leftover else block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Invoice edit mode now uses the same form as create mode (all fields editable)
- Bank account pre-selected by matching IBAN/account number
- Invoice number read-only in edit mode
- Paid invoices remain read-only
- NAS: old PDF deleted when invoice date changes to different month
- Buttons: Zobrazit fakturu, Uložit, Smazat + status transitions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Schema now accepts both string and number user_ids (frontend sends strings)
- Bulk fill now skips Czech public holidays in addition to weekends
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>
- 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>
Frontend expected flat customer_name and responsible_user_name but API
returned nested customers/users objects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Project detail API returned nested orders/quotations objects but frontend
expected flat order_number, order_status, quotation_number fields.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Daily cron (8:00 AM) checks created and received invoices
- Alerts 3 days before due date and on due date
- Summary email to INVOICE_ALERT_EMAIL with grouped tables
- Tracks sent alerts in invoice_alert_log to prevent duplicates
- node-cron scheduler runs inside the app process
- Favicon files copied from PHP project
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>
Bug #1: completed shifts in today_shifts had no project names,
showing "undefined" in the UI. Now includes attendance_project_logs
relation and enriches with project names from projects table.
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>
- Created czech-holidays.ts with 11 fixed + 2 Easter-based holidays
- Fund now automatically excludes public holidays (no manual records needed)
- covered = worked + vacation + sick (NOT holidays — already in fund)
- Renamed "Odpracováno" to "Pokryto" (worked + leave = what counts)
- Removed dependency on holiday attendance records per employee
Matches PHP CzechHolidays::getMonthlyWorkFund() logic exactly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously: holidays reduced the fund (fund = bizDays - holidays).
This caused a mismatch — frontend compared covered against full
month fund, but backend used reduced fund.
Now: holidays count as covered hours (like vacation/sick). Fund stays
at full working days. So worked + vacation + sick + holidays = covered,
and covered >= fund means fulfilled.
Example: Jan has 22 days (176h), 1 holiday. Haas worked 168h.
Before: fund=168, covered=168, OK but frontend saw fund=176, not OK.
After: fund=176, covered=168+8=176, OK everywhere.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>