Commit Graph

15 Commits

Author SHA1 Message Date
admin 66c99c7d99 fix(api): use createRequire for pdf-parse (CJS-only, exports map blocks deep path)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:03 +03:00
admin ca06cd7e58 fix(api): import pdf-parse from lib path (avoid index.js auto-test mode in ESM)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:37:47 +03:00
admin 8c29760615 fix(api): pin @fastify/multipart to ^8 for fastify 4 compat
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:43:58 +03:00
admin e768d30fb6 feat: import DOCX/PDF/scanned templates via DeepSeek recognition
Backend pipeline:
- POST /api/templates/import (multipart, max 25 MB)
- extract.ts: DOCX→mammoth, PDF→pdf-parse, fallback to OCR via tesseract+poppler-utils
  (pdftoppm renders pages to PNG, tesseract reads with rus+eng)
- deepseek.ts: chat completions client with strict JSON response_format
- recognize.ts: structured prompt that produces simplified DocBody (string text),
  postprocessor wraps text in TipTap-compatible JSON, validates with zod schema
- prompt enforces placeholder substitution: {{customer.*}}, {{executor.*}},
  {{contract.number}}, {{contract.date}}, {{today}}
- error codes: NO_OCR / NO_DEEPSEEK_KEY / UNSUPPORTED_MIME / INVALID_DOC_BODY

Dockerfile: apk add tesseract-ocr (+rus +eng data), poppler-utils, imagemagick

Frontend:
- Templates page: ⤴ Загрузить документ → file picker (.docx,.pdf,.png,.jpg)
- doc type selector (contract/invoice/act/upd)
- import-banner with spinner shows uploading→analyzing stages
- on success navigates to /templates/:id (TemplateEdit) for review

Reuses DEEPSEEK_API_KEY pattern from Hall-planer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:40:28 +03:00
admin 624d378bb5 feat: DaData ИНН lookup for clients and companies
- New API endpoint GET /api/lookup/party?inn=... — proxies to DaData API,
  returns parsed party (name, kpp, ogrn, address, signatory, status)
- Env DADATA_API_KEY (optional) — without it endpoint returns 503/no_dadata_key
- Web: InnLookupButton component shown next to ИНН field in Clients form
  and Company requisites; on click fetches DaData and fills all matching
  fields. Warns if status is not ACTIVE (liquidated, etc.)

Free DaData tier: 10000 requests/day. Get key at
https://dadata.ru/api/find-party/ — paste into DADATA_API_KEY in
docker/.env on queoserver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:16:20 +03:00
admin 524789facc feat: multi-organization support + bank accounts
- Renamed singular /api/organization → CRUD /api/organizations
- New BankAccount table with CRUD under /api/organizations/:orgId/bank-accounts
- TochkaCredential gets optional bankAccountId for future per-account bank API config
- Active organization stored in cookie dm_org, /api/active-organization GET/POST
- activeOrgPlugin resolves req._orgId from cookie (or first non-archived as fallback)
- Migration 1_multiorg: BankAccount table + data backfill from legacy Organization.bank* fields
- Web: new /companies list + /companies/:id with tabs (Реквизиты, Банки и счета, Интеграции stub)
- Web: OrgSwitcher dropdown in header (active org + management link)
- Removed nav "Реквизиты", "Банк" — replaced by "Компании"
- Per-field error highlighting wired up on new forms

Existing organization data backfills cleanly: legacy bank* fields stay readable, but new
BankAccount becomes the source of truth going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:08:26 +03:00
admin b28c0463b3 fix(web): allow undefined for Field error prop (exactOptionalPropertyTypes)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:50:25 +03:00
admin bf360d6e53 feat(web): per-field error highlighting from API validation issues
ApiError.fieldErrors() returns map field→ru-message; forms set fieldErrors state
on save failure, pass error prop to Field components (red border + inline message),
clear individual error when user edits that field.

Wired up: Clients, Organization. Services и Documents — позже.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:49:37 +03:00
admin 747246197a fix(api): treat empty strings as null in optional fields; show issues on web
- zod-utils.ts: optionalRegex/optionalEmail/optionalText with preprocess('' -> null)
- clients/organizations/services schemas now accept empty strings for optional fields
  (controlled inputs naturally produce '' instead of null)
- ApiError.prettyMessage() unfolds zod fieldErrors so users see which field failed

Reproduce: POST /api/clients with email="" or empty inn returned 400 validation_error.
Now it succeeds (empty stays null), and any real validation error names the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 08:42:03 +03:00
admin 9807d47c8d feat(M3): contracts editor, templates, PDF render via Puppeteer/Chromium
Backend:
- numbers.ts — per-org per-doctype per-year sequential numbering (ДГ-2026/001, СЧ-2026/001…)
- money.ts — line totals + Russian rubInWords helper
- documents/routes.ts — CRUD with transactional lines bulk-replace, status changes, history endpoint for client-line autocomplete
- templates/routes.ts — CRUD + instantiate (clones template body into new draft document)
- shared/render/toHtml.ts — block→HTML renderer with placeholder substitution ({{customer.inn}}, {{contract.number}}, {{today}}…)
- documents/pdf.ts — Puppeteer-based PDF rendering with auto-detected Chromium executable
- documents/pdf.routes.ts — GET /:id/preview (HTML) and GET /:id/pdf
- Dockerfile.api — added apk chromium + cyrillic fonts

Web:
- api.ts — Document, DocumentTemplate, Block, LineHistoryItem types
- BlocksEditor — generic block list with reorder/add/remove and per-block forms (heading, party, services_table, totals, terms, signatures, custom_text, page_break)
- LinesEditor — services rows with auto sumCents, "from catalog" picker, "from history by client" panel
- ClientPicker — reusable client dropdown
- pages: Documents list, DocumentEdit (new+existing), Templates list, TemplateEdit
- richtext.ts — plain↔TipTap-JSON conversion (no TipTap yet, just keeps the format compatible)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 08:29:44 +03:00
admin 0722a25845 fix(auth): resource key follows Queo convention 'doc.queo.ru' (subdomain), not 'doc_manager'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:48:30 +03:00
admin d9bd6fc68f fix(web): no-cache for index.html so new bundle hashes propagate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:44:35 +03:00
admin 4965fdd60f fix: auth login URL is /login on Auth_server, not /auth/login
GET /auth/login → 404. Auth_server's user-facing login form lives at
GET https://auth.queo.ru/login (rendered by views/login.html), while
POST /auth/login is the JSON API. Web was redirecting to wrong path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:41:41 +03:00
admin de3af9d5fa chore: rename UI Tochka→Bank, prod-ready compose, init migration, Caddy snippet
- UI nav: Точка → Банк (route /bank), label-only — internal Tochka API module unchanged
- compose: drop our own Caddy (queoserver uses host-level Caddy), web-nginx proxies /api/*+/webhooks/* to api:3030 internally; expose only 127.0.0.1:3031
- Dockerfile.api: simpler single-stage with tsx for prod (avoids dist build complexity)
- prisma/migrations/0_init committed via `prisma migrate diff` for `migrate deploy` in prod
- docker/Caddyfile.snippet: copy-paste block for queoserver's /etc/caddy/Caddyfile
- .gitignore: data/ (covers embedded-pg, postgres, caddy data dirs)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:31:48 +03:00
admin 4553f63deb init: M1 scaffolding + M2 organization/clients/services CRUD
- monorepo (npm workspaces): apps/api (Fastify+Prisma+TS), apps/web (Vite+React+TS), packages/shared (zod schemas)
- SSO via auth.queo.ru: jose+JWKS plugin, requireDocPermission(viewer|user|admin)
- DEV_BYPASS_AUTH for local development (hard-checked off in production)
- M2: organization upsert, clients CRUD with search, services catalog with soft-delete
- BigInt -> Number serializer for Prisma money columns
- Embedded Postgres + npm run dev:demo for one-command local boot
- Docker compose for queoserver: postgres + api + web (nginx as ingress proxying /api -> api:3030)
- First migration 0_init committed (prisma migrate diff)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:24:26 +03:00