Commit Graph

694 Commits

Author SHA1 Message Date
BOHA
e868ecf769 refactor(ui): shared searchable CustomerPicker across offers/invoices/issued-orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:42:24 +02:00
BOHA
20177e8f87 fix(ui): guard delete on paid invoices / ordered-or-locked offers; add guarded delete to issued orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:36:32 +02:00
BOHA
39565767ef fix(ui): wire invoice draft restore (read-back) so the banner actually restores and autosave can't destroy the draft
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:32:20 +02:00
BOHA
c4668357aa feat(ui): shared record-scoped useDocumentDraft hook; fix half-wired invoice draft banner; dedupe draft keys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:24:22 +02:00
BOHA
b4f1b6ce2b refactor(ui): OrderDetail uses shared ORDER_STATUS (removes the last duplicate status map)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:17:52 +02:00
BOHA
66235ff567 refactor(ui): single shared documentStatus map across offers/invoices/orders (fix issued color drift, add offers status chip)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:12:44 +02:00
BOHA
d341db2e51 docs: cross-module UI + state/draft consistency audit (Offers/Invoices/Orders)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:05:00 +02:00
BOHA
430ec3e76b fix(ui): make item description a drag-resizable textarea (rows + resize:vertical) across invoice/order/offer
Replaces MUI autosize (no handle, mis-wraps in table cells) with a fixed 2-row textarea + resize:vertical so the box wraps and the user can drag it taller. Restores the dead '& input' sx as '& textarea'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:37:34 +02:00
BOHA
f295af4a14 fix(ui): auto-grow item detail-description box + offer money columns to 8rem (consistency across invoice/order/offer)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:28:17 +02:00
BOHA
db11999c85 fix(ui): widen 'Jedn. cena' column in invoice + issued-order line-item tables (5.5rem -> 8rem)
The editable unit-price number input was cramped (5.5rem, same as qty/unit); widened to 8rem to match the Celkem column and the read-only detail view, so real prices fit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:23:16 +02:00
BOHA
afef0e6759 docs(orders): sync spec/plan/CLAUDE.md to as-built (Vydané-first tabs, month filter, shared PDF template, Prisma 7 / React 19)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:50:41 +02:00
BOHA
62f50d31af Merge feat/issued-orders: issued purchase orders (objednávky vydané)
New issued-purchase-order document type (schema, numbering, service, routes, PDF, frontend Vydané tab) plus the orders/invoices consistency pass (Vydané-first tabs, shared month filter, shared red-accent PDF template). Dependency upgrades (React 19, Prisma 7, file-type 22) were landed separately on master first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:41:46 +02:00
BOHA
3d3df6db85 feat(orders): invoices-style landing — month selector + Vydane-first tabs, content-only tab bodies
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:33:36 +02:00
BOHA
162f9b9fdf feat(orders): issued-order PDF on the shared invoice/confirmation template
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:27:12 +02:00
BOHA
3b48bd0523 feat(orders): month/year filter on both order lists + fix received-orders search
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:22:39 +02:00
BOHA
2b7b480144 fix(orders): back button links to Vydané tab, not the nonexistent /orders/issued
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:53:23 +02:00
BOHA
e26fce092a fix(orders): authenticated PDF open in issued-orders list
The list opened the PDF route via raw window.open, an unauthenticated top-level navigation that 401s on the token-guarded endpoint. Now uses the apiFetch -> blob -> window pattern from InvoiceDetail/IssuedOrderDetail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:46:50 +02:00
BOHA
d7e0ba933d feat(orders): issued-order create/edit form + PDF export button
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:44:02 +02:00
BOHA
8b90cfed1f feat(orders): Orders page Přijaté|Vydané tabs + issued list
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:37:03 +02:00
BOHA
8476ffebd6 feat(orders): issued-order query options + types
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:32:51 +02:00
BOHA
63ccf8fda7 feat(orders): issued-order PDF export route
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:31:05 +02:00
BOHA
9f4b8a897a feat(orders): issued-orders CRUD routes + audit entity type
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:26:32 +02:00
BOHA
2a3df15565 fix(orders): release PO number against creation year; tighten sort-dir + enum typing
Addresses review of the issued-orders service: deleteIssuedOrder now derives the release year from created_at (cross-year deletes reclaim the right sequence); list sort direction is normalized to asc/desc; status cast uses $Enums instead of as-never. Adds a cross-year delete test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:23:45 +02:00
BOHA
b1f48df30c feat(orders): issued-orders service (CRUD + totals + transitions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:14:48 +02:00
BOHA
f00fcf95fd feat(orders): issued-order Zod schemas
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:09:50 +02:00
BOHA
4093664936 feat(orders): issued-order number sequence
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:04:48 +02:00
BOHA
ecb83d4654 feat(orders): issued_orders schema + migration
Pre-existing quotations.project_code + ai_chat_messages FK drift is captured in its own reconcile_quotations_aichat_fk migration; the issued_orders migration is purely additive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:52:42 +02:00
BOHA
a06ea2e1cd docs(orders): plan fix — no manual migration SQL edits (code-level numbering defaults)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:37:40 +02:00
BOHA
15a2da38cc docs(orders): implementation plan for issued purchase orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:25:36 +02:00
BOHA
4072197f4a docs(orders): design spec for issued purchase orders (objednávky vydané)
Brainstormed design for a new 'objednávky vydané' document type — purchase
orders you author with line items and export to PDF, alongside the existing
customer orders (which become the 'přijaté' tab).

Key decisions: dedicated issued_orders/issued_order_items models mirroring
invoices; customers reused as supplier/partner; NET+VAT-on-top; dedicated
numbering sequence; reuse of orders.* permissions; standalone v1 (no
warehouse/received-invoice links).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 08:35:02 +02:00
BOHA
1914fddb4d chore(deps): upgrade file-type 16.5.4 -> 22.0.1 (clears the ASF-parser DoS)
file-type 16 -> 22 fixes GHSA-5v7r-6r5c-r473 (infinite loop in the ASF parser on
malformed input). v22 is ESM-only with a strict `exports` map, so the CommonJS
server can no longer `require()` it (and tsc would down-level a normal dynamic
import() back to require()). Converted the single call site in
nas-file-manager.ts to a cached runtime dynamic ESM import via the
Function('return import("file-type")') indirection (the same pattern server.ts
already uses for Vite), and updated the API to the v22 named export
`fileTypeFromBuffer` (was the v16 default `FileType.fromBuffer`).

Verified at runtime that the dynamic import loads v22 from CJS and detects
PNG/JPEG/PDF correctly. `npm audit` no longer lists file-type (6 -> 5 vulns; the
rest are quill [Phase 4] and Prisma 7's dev-tooling @hono/node-server).

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:12:23 +02:00
BOHA
63192dc781 chore(deps): upgrade Prisma 6.19 -> 7.8.0 (Rust-free client + mariadb adapter)
prisma / @prisma/client 6.19.2 -> 7.8.0; added @prisma/adapter-mariadb 7.8.0 and
mariadb 3.5.2 (the runtime driver).

Prisma 7 breaking changes handled (verified via Prisma's own tooling — the
official upgrade-guide summary had hallucinated package names, so I confirmed
everything against `prisma validate`/`generate` and the installed type defs):

- The Rust query engine is removed; the runtime connection is now made through a
  driver adapter. src/config/database.ts wires PrismaMariaDb, parsing
  DATABASE_URL into an explicit mariadb pool config so the literal `@` in our
  password (which a naive connection-string parser mishandles) is correct.
- datasource `url` is no longer allowed in schema.prisma -> moved to a new
  prisma.config.ts (schema + datasource.url + migrations.seed). Prisma 7 no
  longer auto-loads .env, so the config does `import "dotenv/config"`.
- The deprecated package.json#prisma seed config was removed (now in the config).
- seed.ts and the two maintenance scripts now use the shared adapter-wired client.

The legacy `prisma-client-js` generator STILL works under v7 (generates to
@prisma/client as before), so NO import-path changes were needed across the 16
@prisma/client import sites — switching to the new prisma-client/output generator
is not required for us. No prisma.$use() middleware exists. Node 24 / TS 5.9
exceed the v7 minimums (20.19 / 5.4).

No migrations were run (your DBs are untouched); removing datasource.url is a
config change, not DDL, so this phase needs no new migration.

Gates: tsc 0 | build 0 | vitest 247/247 against app_test (proves the mariadb
adapter works at runtime, incl. raw SELECT...FOR UPDATE locking and Decimal/VAT)
| eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:06:02 +02:00
BOHA
6147131aad chore(deps): upgrade React 18.3 -> 19.2.7
react / react-dom 18.3.1 -> 19.2.7; @types/react 18.3.31 -> 19.2.17;
@types/react-dom 18.3.7 -> 19.2.3 (latest stable 19.2.x).

Only breaking change hitting our code: React 19 removed the no-arg useRef<T>()
overload. AppShell's logoutTimer -> useRef<T | undefined>(undefined) — the same
transform the official `types-react-codemod preset-19` (useRef-required-initial)
applies. No other preset-19 transforms were relevant (already on createRoot; no
ReactDOM.render, string refs, propTypes/defaultProps on function components).

No Next.js / RSC in this project (it's a Vite SPA served by Fastify), so those
React 19 concerns don't apply here. MUI v7, framer-motion 12, TanStack Query 5,
react-router-dom 6 and react-quill-new all installed cleanly against React 19
(no peer-dependency conflicts).

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:51:43 +02:00
BOHA
b4f859ea6e docs: codify audit rules in CLAUDE.md + README + audit records
CLAUDE.md: new "2026-06-09 audit" enforced-rules section (mandatory coercion
helpers, deterministic id-tiebreak ordering, $queryRaw FOR-UPDATE locking
discipline, uniqueness-in-transaction, guard reads, Rules-of-Hooks, seed prod
guard), lint/typecheck/format commands, and the app_test/.env.test testing
section with the hard-throw guard.

README rewritten from the placeholder stub (setup/run/build/test/gates/migrations).

REVIEW.md / REVIEW_FINDINGS.md / REVIEW_FIXES.md: the full audit record — 277
files reviewed, ~362 findings, every one fixed and verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:37 +02:00
BOHA
519edce373 fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace
the raw number|string transform idiom across every schema (the root-cause NaN bug
class); emailOrEmpty + lenient isoDateString/timeString.

Security: roles privilege-escalation closed; refresh-token family revocation on
reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices
gross VAT on all paths; orders-pdf custom-items authz.

Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse
confirm/cancel, attendance lockUserRow); uniqueness checks moved into create
transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision
timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO).

Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted
fields; dashboard invalidation gaps; stale-closure confirm bugs.

Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier;
tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit
fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes),
isolated on app_test via .env.test with a hard-throw setup guard.

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:26 +02:00
BOHA
c454d1a3fc feat(db): audit schema hardening (onDelete, unique doc-numbers, indexes)
From the 2026-06-09 full-codebase audit. Warehouse line->header relations
CASCADE on delete; line->master-data and the financial FKs are explicit
RESTRICT; UNIQUE on receipt/issue/session numbers; new indexes on hot FK
columns (sklad_issues/receipts, bank_accounts); dropped a duplicate
audit_logs created_at index. Applied to dev `app` and `app_test`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:04 +02:00
BOHA
b1f21a1f48 docs(claude): document Odin AI assistant + correct stale counts
- Add "AI Assistant — Odin" section (v2.1.5): scope (invoice-only guard),
  SDK/model, backend/data/frontend layout, gross-VAT received-invoices,
  Phase 2 pointer.
- Add AI row to the tech stack.
- Correct counts: 49→52 Prisma models, ~14→17 test files / 152→195 tests,
  ~105→114 admin .tsx; note odin/ components.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:36:58 +02:00
BOHA
b6cd5046d5 chore(release): v2.1.5 — Odin multi-conversation chat (sidebar, invoice import)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v2.1.5
2026-06-08 21:28:43 +02:00
BOHA
25c8c39ba6 feat(odin): scope to invoice processing only (guard general chat)
Odin currently advertises general Q&A via the /chat path, which burns API
credits on open-ended questions. Guard it: a text-only message makes NO AI
call — it gets a canned reply telling the user to attach an invoice. The
invoice path (attachments → extract) is unchanged. Removing the guard
re-enables the general /chat path for a later phase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:26:54 +02:00
BOHA
4b2770a000 fix(odin): show conversation auto-title immediately (no refresh needed)
ensureActive() created the conversation and invalidated the list before the
server-side auto-title was applied (that happens on the message append), so
the sidebar showed "Nová konverzace" until a refresh. Move the
["ai","conversations"] invalidation to persist()'s success path — after the
append (and thus the auto-title) lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:22:31 +02:00
BOHA
434a12b4a8 feat(odin): mobile composer + responsive sidebar + message-entrance animation
- Composer rebuilt claude-style: a rounded pill with a multiline auto-growing
  textarea and attach/send ICON buttons (vertically centered), so there's room
  to type on mobile (was squeezed between two text buttons).
- Conversation list collapses into a slide-in Drawer below md, with a menu
  button in the chat header; chat is full-width on mobile.
- Each new message bubble animates in (fade + slide-up; fade-only under
  prefers-reduced-motion) via framer-motion, so sent messages and Odin's
  replies appear smoothly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:19:54 +02:00
BOHA
28fb399217 feat(odin): refine hero — Newsreader serif, red name, staggered entrance
- Swap Fraunces → Newsreader (literary serif, full Czech, optical sizing).
- Split the greeting so the user's first name renders in brand-red italic.
- Add a subtle staggered fade-in (framer-motion), skipped under
  prefers-reduced-motion.
- New description that names Odin and covers chat + invoice import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:03:16 +02:00
BOHA
c0ff7b4549 feat(odin): editorial serif hero — Fraunces greeting + italic subtitle
Drop the gradient "Odin" wordmark from the new-conversation hero and make the
personalized greeting the centerpiece in Fraunces (optical-sized serif), with
an italic Fraunces description — a refined, literary, claude-like treatment.
Loads Fraunces (upright + italic) via the existing Google Fonts link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:57:15 +02:00
BOHA
1e21c12f33 feat(odin): personalized greeting hero on the new-conversation screen
Time-based Czech greeting + the user's first name (e.g. "Dobrý večer, Admin")
above the big gradient "Odin" wordmark, with a stylish description line. Drop
the redundant mark from the hero (it lives on the nav item + thinking state).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:51:00 +02:00
BOHA
ab12f9d791 feat(odin): sidebar brand is a big gradient "Odin" wordmark (drop the icon)
The mark already lives on the nav menu item; the conversation sidebar's brand
no longer duplicates it — just a larger gradient "Odin" wordmark above the new
chat button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:47:39 +02:00
BOHA
cf442b9a99 feat(odin): big claude.ai-style hero on the new-conversation screen
Replace the small empty-state with a large centred hero — the animated Odin
mark + a big gradient "Odin" wordmark (responsive clamp font) and the helper
line beneath.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:44:38 +02:00
BOHA
2e7caea5a6 feat(odin): use the Odin mark as the sidebar nav icon
Replace the chat-bubble glyph on the "Odin" nav item with the OdinMark, and
make the mark's SVG sizing !important so the nav's `& svg { width:18 }` rule
can't shrink it (its `size` prop stays authoritative everywhere).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:41:20 +02:00
BOHA
702a4984ee feat(odin): animated Odin brand mark (AI core orbiting business data)
A self-contained animated SVG mark in BOHA red: a 4-point AI spark (Odin) that
slow-rotates and breathes, a data node orbiting a faint ring (the assistant at
the centre of the business system). An "idle" and a "thinking" state (faster
orbit + red glow); honours prefers-reduced-motion. Used as the sidebar brand,
the empty-state hero, and the busy/thinking indicator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:31:48 +02:00
BOHA
769767c33d feat(odin): left sidebar + lazy conversation creation (claude.ai-style)
- Replace top tabs with a left sidebar (New chat + scrollable conversation
  list, per-row rename/delete menu, active highlight), chat on the right.
- Fix duplicate-empty-conversations bug: "Nová konverzace" now just opens an
  empty composer; a DB row is created only on the first successful message
  (and a failed AI call no longer leaves an orphan conversation). Repeated
  clicks never spawn empties, and nothing empty survives a refresh.
- Land on a fresh new chat on mount; deleting the open conversation returns
  to new chat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:07:07 +02:00
BOHA
f18e2e0f2c docs(odin): forward-looking notes for a Phase 2 general assistant
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:52:30 +02:00