Commit Graph

494 Commits

Author SHA1 Message Date
00e350cac9 fix(products): DETTE-STD-9 shared pricing predicate + DETTE-STD-8-A audit logs (SPEC-STD-9-DETTES)
DETTE-STD-9: shared lineRequiresPricing predicate in src/shared/pricing-line-types.ts
consumed by products.service.ts (badge L51) and orders.service.ts (createStandard
L240 + preview L583) to prevent badge/canCreate divergence.

DETTE-STD-8-A: PRODUCT_DELETE audit in $transaction with snapshot (id/code/name/
smetaId/counts) captured BEFORE cascade. PRODUCT_ARCHIVE audit best-effort.
userId passed from route handlers.

E2E fix: Popconfirm selector scoped via .ant-popover:visible + .first() for
duplicate toast messages (T-STD8-02/03).

13/13 Vitest, 23/23 Playwright products green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-05 02:18:56 +04:00
0e4f28d4b8 feat(products): SPEC-STD-8 product deletion — ADMIN-only cascade + $transaction + 409 guard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-05 01:10:07 +04:00
73f9be47bf feat(std-6): promotion UI + 5 E2E tests + display:none bug fix (SPEC-STD-6-UI-PROMOTION)
PromoteSmetaDrawer 3-step UI (info→compare→result), findSimilarSmetas
Jaccard ≥80%, comparison table with override "Б" for MATERIAL lines,
non-MATERIAL read-only + Alert banner. Fix AntD Form.Items unmount on
conditional rendering (display:none pattern preserves values).
5 E2E Playwright tests (T-PROM-01..05). 18/18 std-6 green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-05 00:00:38 +04:00
673f816d99 feat(products): promotion smeta→product + OZ57 comparison script (SPEC-STD-2)
ProductPromotionService: validate source smeta, create DRAFT product, clone
smeta LOCKED via cloneSmeta (reused as-is), init ACTIVE pricings for MATERIAL
lines from default supplier, best-effort image copy. Route POST /products/
promote-from-smeta (WRITE_ROLES). 13 Vitest + 13 clone non-regression green.
compare-oz57-smetas.cjs: DB-driven comparison script for smetas 18 vs 25.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 23:04:06 +04:00
b65121ef50 feat(std): generic cloneSmeta service + wire into createStandardOrder (SPEC-STD-1-CLONE-SMETA)
Phase 0: CHECK constraints chk_smeta_xor_parent + chk_order_standard_product
deployed on erp_local, erp_test, erp_staging (already existed on erp_local).

Phase 1: smeta-clone.service.ts — generic cloneSmeta(prisma, sourceId, opts)
deep-copies Smeta + SmetaLine[] + SmetaLaborNode[] in $transaction, best-effort
photo file copies after commit. Designed bidirectional (product→order, order→product).

Phase 2: createStandardOrder now clones the product smeta into the order,
initializes FinancialTracking from the cloned smeta (budgetX × qty).

Phase 3: 13 Vitest unit tests (T-CLONE-01..13). 0 TS errors. 0 regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 21:58:55 +04:00
818e4edc72 fix(engagement): replace paidAt with status filter on ClientInstallment
ClientInstallment has no paidAt field — use status IN (PENDING,
PARTIALLY_PAID) to find unpaid overdue installments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 21:10:13 +04:00
545e45e1f6 fix(inbound-email): resilient FETCH + 2h alert cooldown + merge heartbeat metadata
- processMessage: wrap client.fetch() in try/catch, skip gracefully on
  IMAP protocol errors instead of creating ERROR records
- Cron heartbeat: merge with existing metadata (preserves lastUid)
- Alert cooldown: suppress duplicate INBOUND_EMAIL_ERROR notifications
  within 2h window, downgrade priority URGENT→HIGH
- Cleans up the spam caused by 4 malformed emails on zakupki@t-laser.ru

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 21:08:23 +04:00
a1b96ed98d feat(smeta): persist Excel file at import via Document+DocumentFile (SPEC-SMETA-FILE-PERSIST-1)
Backend: importXlsx() now creates a Document(type=SMETA) and stores the
Excel buffer via DocumentFileService after successful parse+transaction.
Smeta.filePath is written with the storedPath. Best-effort try/catch —
a storage failure never blocks a successful import.

Frontend: EstimatesTab drawer and GeneralTab show a download button
(FileExcelOutlined → /storage/{filePath}) when filePath starts with
'documents/', falling back to the UNC FilePathButton for legacy paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 12:39:07 +04:00
44eedc5a22 fix(work-log): replace ruble sign with руб. in PDF (LiberationSans lacks U+20BD)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 02:50:32 +04:00
42559614f0 feat(work-log): salary report PDF with Cyrillic + X/Y pagination (SPEC-TRUD-RAPPORT-PDF)
- Add LiberationSans TTF fonts (versioned in src/assets/fonts/)
- New labor-report.pdf.ts: pdfkit canvas, landscape A4, bufferPages for page X/Y
- Extend salary endpoint format=pdf (alongside json/xlsx)
- Frontend PDF button (FilePdfOutlined) in SalaryReportsTab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 02:47:29 +04:00
a767aec689 feat(work-log): admin recompute endpoint + convention actualLabor (SPEC-RECOMPUTE-ACTUALLABOR-ROBUSTE)
POST /work-logs/recompute/:orderId (ADMIN-only, idempotent) repairs
ghost actualLabor after SQL-direct deletion of VERIFIED WorkLogs.
Convention documented in CLAUDE.md. 3 test-gardes (T-TRUD3-19/20/18a).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 00:59:33 +04:00
de5e4de0f9 fix(backend): resolve all 23 TypeScript errors — 2 real bugs found (SPEC-BACKEND-FIX-TS)
8 error categories fixed across 11 files:
- TenderStatus enum: added CANCELLED (real bug — order cancel crashed for tenders)
- creatorId→createdById: 6 sites in copilot engagement tools (real bug — ownership checks always false)
- Order.constructor Prisma 5 clash: removed select constraints in estimates+labor-report
- Prisma Json null: DbNull for Json? fields, InputJsonValue for Record<string,unknown>
- Anthropic SDK ContentBlock: TextBlock→TextBlockParam, ToolUseBlock→ToolUseBlockParam
- Role[] vs Role: estimates.service approveFinancial+delete accept union type
- ActionValueClass: string→enum in discharge-kpi CatalogEntry
- SendEmailOptions: added required html field in claim-draft

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-01 22:48:15 +04:00
7890ee748f feat(labor): node photos in operation progress cards (TRUD-4C)
Backend: enrich summaryByOrder with priceSource and nodePhoto (photoPath
from SmetaLaborNode via sourceLaborNode relation). No new endpoint —
reuses existing @fastify/static /storage/ prefix for image serving.

Frontend: AntD Image with preview in NODE_PRICE operation cards.
Defensive: no photo = no thumbnail, 404 = transparent fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-01 09:12:47 +04:00
54490cf2b8 feat(labor-reports): SPEC-TRUD-4D salary/labor reports module
Backend: LaborReportService (aggregation by worker/category, order detail),
ExcelJS export, Zod schema with CSV preprocess, GET /labor-reports/salary
(JSON + xlsx format). Frontend: SalaryReportsTab with filters (period presets,
workers, categories, orders, groupBy radio, detail/pending toggles),
hierarchical AntD Table with expandable order rows, KPI cards, print CSS
(@media print landscape), Excel download. Tab enabled in WorkersPage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 22:07:56 +04:00
e088a9edfd fix(work-log): verify 400 + summary VERIFIED-only + HOURLY % cost-based (TRUD-4B-2-FIX)
FIX A: verifyWorkLog sends {} body (Fastify FST_ERR_CTP_EMPTY_JSON_BODY).
Batch verify collects per-line errors with unified recap message.

FIX B: summary progressPercent + Факт now use VERIFIED-only data.
PENDING shown separately (в ожидании: X ₽). New fields: quantityVerified,
costPending, baremeUnitPrice per operation.

FIX C: HOURLY progressPercent uses cost-based formula (costVerified/baremeTotal)
instead of qty-based (hours/smetaQty units mismatch).

FEATURE D: operation cards show total target (X шт / X ч / X м²) with
verified progress in matching unit.

23/23 Vitest green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 12:04:28 +04:00
2e9e1c9e1f feat(work-log): SPEC-TRUD-4B-2 blocking ceiling + full UI (SAISIE-DECOMPTE-ET-VERIFICATION)
Phase 0: excessJustification field on WorkLog, anti-sur-décompte now BLOCKING
(reject 400 with CEILING_EXCEEDED/BUDGET_EXCEEDED if no justification), route
returns structured error with ceilingCheck/hourlyBudgetCheck for frontend.
22 Vitest (6 new: blocking reject, accept with justification, HOURLY cost,
no-store when OK). DB pushed erp_local+erp_test.

Phase 1-3: LaborTab.tsx rewritten — entry form (op/worker/date/qty selects,
live cost calc, remaining ceiling display, dynamic justification field),
verification queue (multi-select PENDING + batch verify, reject modal),
summary KPIs (progress per operation, excess alerts), barème detail preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 17:11:42 +04:00
bc7f1e7229 fix(work-log): TS Order.constructor clash workaround (as any cast)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 17:09:01 +04:00
30488ff007 feat(work-log): native work accounting system (SPEC-TRUD-3-DECOMPTE)
Worker model + WorkLog with 3 payment modes (PIECE/HOURLY/AREA),
anti-sur-décompte ceiling alerts (non-bloquant), production
verification workflow (PENDING→VERIFIED→actualLabor recompute),
9 REST endpoints, 18 Vitest tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 17:06:26 +04:00
c5d4da87d9 fix(labor-bareme): PIECE default classification — hourly reserved for 4 non-normable ops (SPEC-TRUD-2-CLASSEMENT)
Default classification changed from HOURLY+needsReview to PIECE normée.
HOURLY = only 4 configurable patterns (Погрузочно-разгрузочные, Упаковочные, Наклейка, Мехобработка).
PIECE formula: smetaTotal × 50% (not unitPrice × qty — unitPrice may be hourly estimation rate).
PIECE_RATE branch merged into default (same formula, workerCategory=SLESAR preserved).
needsReview reserved for true unknowns (currently none).
Fixes orderId=11 from 359% to expected ~50%.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 16:41:27 +04:00
304d5cf050 fix(labor-bareme): 3 corrections cascade réel (SPEC-TRUD-2-CORRECTION)
- #1: Skip zero-value lines (К-во узлов template rows → 0₽ noise)
- #2: Hourly pattern detection (configurable list, qty/60 conversion)
- #3: Exclude aggregate Сварочно-сборочные when labor nodes exist
- needsReview flag for unclassified default lines
- LaborConfig extended: hourlyPatterns + aggregateExcludePatterns
- 25 Vitest (6 new: T-TRUD2-20..25)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 13:23:40 +04:00
cd7f3cb4e7 fix(indirect): make preferredSupplierId required + editable after creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 11:48:20 +04:00
ee55c031ae fix(smeta): tabular format parser + Anthropic model ID update (claude-sonnet-4-6)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 11:37:33 +04:00
fca75994b5 feat(labor-bareme): barème travail auto-généré au lancement commande (SPEC-TRUD-2)
6 enums + 2 modèles Prisma (LaborConfig singleton, OrderLaborOperation).
Cascade NODE_PRICE→PAINT_RATE→DEFAULT 50%, snapshot config, cost guard,
override MANUAL, split LABOR_NODE. 7 routes REST. Câblé orders.service.ts
IN_PRODUCTION L942-944. 19 Vitest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 16:25:05 +04:00
02d96070f4 feat(smeta): add per-node welding cost parser (SPEC-TRUD-1-PARSER-POUZLOVOY)
Parse ПОУЗЛОВОЙ РАСЧЕТ СЕБЕСТОИМОСТИ ТРУДОЗАТРАТ section from smeta Excel.
SmetaLaborNode model (23 fields), state machine parser, image extractor,
proximity photo matching. Non-regressive: smetas without section unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 15:49:47 +04:00
9f749cb8af feat(emails): add messenger connection instructions (Telegram + MAX) in supplier email signature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 14:40:40 +04:00
131b65063b feat(tenders): global conditions (payment terms, delivery, comment) + fix Invalid Date
- Add 4 fields to Tender model: paymentTerms, desiredDeliveryDays, desiredDeliveryDate, tenderComment
- Update create/update Zod schemas with new fields
- Include conditions in email templates via buildConditionsHtml helper
- Frontend: TenderConditionsEdit component in detail drawer (DRAFT editable, non-DRAFT read-only)
- Fix Invalid Date in dispatch table (sentAt null renders dash)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 14:02:19 +04:00
67bb150280 fix(expense-codes): hard delete with FK fallback + feat(notifications): deep links in all supervisor reminders/escalations
Expense codes: delete now attempts hard delete first, falls back to
soft-delete (isActive=false) only when FK constraint prevents deletion.
Frontend shows appropriate message for each case.

Deep links: new buildEntityDeepLink helper maps all entity types
(Order, Tender, PurchaseOrder, Smeta, SDN, ClientInstallment) to
frontend URLs. Wired into reminder.action.ts (replaces Order-only
check) and escalation.action.ts (adds deepLink to both router and
direct email paths). Telegram reminders also include clickable link.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 13:41:17 +04:00
dcfdb8f142 fix(expense-codes): make all expense codes fully editable + move З11→Д, З12→Ж
Remove frontend-only restrictions: code/groupCode fields now editable in edit mode,
delete button enabled for all codes (soft-delete only). Move З11 (Аренда склада)
to group Д (Ремонт и обслуживание) and З12 (Логистика склад) to group Ж
(Производство субподряд) per financial director request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 12:36:08 +04:00
1aa4ec9f74 feat(max-relay): inbound message processor — surface supplier messages to ACHETEUR (SPEC-MAX-3B)
MaxInboundProcessorService: 3-level correlation (chatId→supplier, PO/tender regex, fallback recent PO),
raw text surface to ACHETEUR via NotificationHelper, attachment detection with email redirect hint,
anti-silence for unknown senders, idempotence via processed flag. 11 Vitest tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 12:36:08 +04:00
f1143d754b feat(help+kb): add supplier channel linking article + contextual Alert
- New help article workflow-supplier-channel-linking (slug, WORKFLOW category)
- Contextual Alert in SupplierChannelsSection when no links/codes exist
- Points user to help drawer (Ctrl+/) for full instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 20:47:34 +04:00
2b527e6c9f feat(supplier-channels): supplier notification dispatch with channel resolution (SPEC-MAX-3A)
SupplierNotificationService resolves preferred channel (MAX/Telegram/email fallback),
dispatches anodin notifications via MaxOutboundMessage queue or existing senders.
Hooked into tender send() + addAndSendToSupplier() + PO sendPOEmail(). Idempotent
via SupplierNotificationLog unique constraint. 11 Vitest tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 20:29:34 +04:00
c050f36ff9 feat(clients): add reactivate endpoint + UI button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 15:27:20 +04:00
541b52f5c7 feat(po-email): include TLT company requisites (bank, INN, address)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 15:14:23 +04:00
c5ac883564 fix(po-email): payment terms in Russian + delivery date instead of days delta
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 15:08:38 +04:00
b09e3bda21 feat(max-relay): SPEC-MAX-2B ERP relay endpoints for MAX messenger
3 relay endpoints guarded by X-Relay-Secret (constant-time comparison):
- GET /max-relay/outbound — pending messages for relay to poll
- POST /max-relay/outbound/:id/ack — idempotent ack (SENT/FAILED)
- POST /max-relay/inbound — persist + auto-dispatch pairing codes via linkChatToSupplier (Spec 1)

Admin test endpoint POST /max-relay/test-enqueue (normal ERP auth ADMIN).
2 Prisma models: MaxOutboundMessage (PENDING/SENT/FAILED), MaxInboundMessage (processed flag).
16 Vitest tests (guard secret, idempotence ack, pairing dispatch, error resilience).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 14:42:38 +04:00
8dd92e0ce3 feat(ai-analyzer): editable results + semantic matching + KB feedback
- Improve offer analyzer prompt for semantic matching via Claude
- Load past corrections from KB into prompt for learning
- Add POST analyzer-feedback route to save user corrections
- Make results table editable (price InputNumber, match Select)
- Add feedback TextArea for user comments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 14:13:44 +04:00
d8b990f9d2 feat(supplier-channels): SPEC-MAX-1 opt-in mapping canal fournisseur↔messager
Appariement fournisseur↔messager par code jetable (XXXX-XXX, 14j, usage unique).
Rattachement idempotent: même chatId+supplier=no-op, chatId lié à autre supplier=CONFLICT (jamais de double rattachement silencieux — test reine T-SC-06).

Backend: SupplierChannelLink + ChannelPairingCode modèles Prisma, MessagingChannel + PairingStatus enums, Supplier.preferredChannel, SupplierChannelsService (generateCode, linkChatToSupplier, setPreferredChannel, deactivateLink, revokeCode, expireStaleCodes), 7 REST endpoints /api/v1/supplier-channels, 16 Vitest. Frontend: SupplierChannelsSection (preferred channel, active links, pending codes, generate modal RU/FR), intégré SuppliersTab drawer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 14:02:22 +04:00
3a6f9ae205 fix(builder): use template literal in fetchAndRebase for GIT_REMOTE interpolation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 12:19:32 +04:00
a8b70091c0 fix(builder): configurable git remote via BUILDER_GIT_REMOTE env var
VPS uses 'github' as remote name, not 'origin'. All git push/fetch/rebase
commands in GitOps, MergeService, RevertService now use BUILDER_GIT_REMOTE
(default 'origin', set to 'github' on VPS).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 12:17:46 +04:00
01850f48fd fix(client-contracts): add COMMERCIAL to READ+WRITE roles
COMMERCIAL (sales manager) was blocked from creating client contracts.
WRITE_ROLES was ['ADMIN', 'DIRECTION_FIN'] only. Added COMMERCIAL to both
READ_ROLES and WRITE_ROLES so sales managers can manage client contracts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 12:15:13 +04:00
4cf988f1a1 fix(builder): raise MAX_BUILDER_TOOL_ROUNDS 15→30
Opus needs more MCP rounds to gather sufficient codebase context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 12:07:15 +04:00
905fa49340 feat(builder): reset FAILED→DRAFT with retry button
FAILED was terminal with no way to retry. Added:
- Backend: FAILED→DRAFT transition + resetFeature service + POST /reset route
- Frontend: 'Повторить' button on FAILED status + resetFeature API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 12:03:41 +04:00
07b42924d4 fix(builder): raise MAX_BUILDER_TOOL_ROUNDS 8→15
The AI Builder analysis phase needs more MCP tool rounds to gather
sufficient context about complex features spanning multiple modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 11:56:39 +04:00
a049a51c1f feat(doc-control): claim draft service + UKD watchdog (pretenziya Phase 1)
Schema: claimDraftText, claimSentMessageId, claimUkdDueDate on Discrepancy.

ClaimDraftService: generateDraft (deterministic facts from records,
no Август signature, contract-missing warning) + confirmAndSend
(CLAIM_DRAFTED guard, email via zakupki@, capture Message-ID,
auto-transition CLAIM_SENT→AWAITING_UKD, UKD SLA 5 business days).

UkdWatchdogService: detects AWAITING_UKD past claimUkdDueDate,
alerts ACHETEUR only (0 email fournisseur), dry-run mode.

Routes: POST /discrepancies/:id/generate-claim,
POST /discrepancies/:id/confirm-send,
POST /run-ukd-watchdog.

Cron: UKD watchdog added to daily doc-watchdog tick.

10 Vitest (3 deterministic facts + 4 service guards + doctrine test
"non confirmé = 0 envoi" + 3 UKD watchdog).

Preuve câblage: ClaimDraftService.generateDraft appelé par
document-control.routes.ts:110; confirmAndSend par routes.ts:120;
UkdWatchdogService.run par doc-watchdog.cron.ts:25 et routes.ts:133.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 19:56:44 +04:00
f9350c01d2 feat(doc-control): internal control stage machine + discrepancy + SLA watchdog
Schema: ControlStage enum (6 states), DiscrepancyKind/Status enums,
Discrepancy model, 4 fields on ExpectedDocument (controlStage,
controlStageEnteredAt, internalDueDate, internalStallNotifiedAt).

ControlStageService: transition validation matrix, ball holder
derivation (RESP_ENTREPOT/ACHETEUR/COMPTABLE), business day SLA.

DiscrepancyService: create (stage guards, PRICE only at AT_ACHETEUR),
transition validation, auto-return to AT_ACHETEUR on all resolved,
UKD linking.

InternalSlaWatchdogService: detects stalled docs past internalDueDate,
alerts ball holder + escalation to ACHETEUR, dry-run mode, heartbeat.

Routes: PATCH /transition, GET /transition-matrix, POST/GET discrepancies,
PATCH discrepancy transition/link-ukd, POST /run-internal-sla.

UI: DocControlTab extended with controlStage column, ball holder,
internal SLA badge, transition buttons, discrepancy creation modal,
stage filters.

32 Vitest (7 stage transitions + 5 ball holder + 3 service + 7 discrepancy
transitions + 5 discrepancy service + 5 internal SLA watchdog).

Preuve câblage: ControlStageService.transition appelé par
document-control.routes.ts:49; DiscrepancyService.create appelé par
document-control.routes.ts:70; InternalSlaWatchdogService.run appelé
par doc-watchdog.cron.ts:20 et document-control.routes.ts:104.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 19:44:34 +04:00
a6f65484c3 feat(inbound-email): wire PO correlation branch into pipeline
Classifier: detect PO_CORRESPONDENCE BEFORE tender fallback —
"Запрос счёта PO-2606-001" no longer misclassified as tender.

POCorrelationService: extracts PO code + intent from subject/body,
correlates to PurchaseOrder, anti-silence notif on UNMATCHED.

Schema: correlatedPurchaseOrderId + detectedIntent on InboundEmail.

Pipeline: PO correlation runs before tender correlation in cron.
Route: POST /:id/reprocess-po (ADMIN) for reclassification.

10 Vitest (classifier non-regression + correlation + reprocess).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 19:25:51 +04:00
8d03a6567a test(price-memory): fuzzy dedup verification — proves trgm path is dead
4 tests documenting the greedy Phase 1 behavior:
- T-FUZZY-01: true duplicate "гайка м6 ск" vs existing "гайка ск. m6 din 7040"
  → Phase 1 creates new item instead of proposing TRGM match (BUG documented)
- T-FUZZY-02: discriminant "Болт M10" vs "Болт M8" → separate items (CORRECT)
- T-FUZZY-03: exact match happy path (CORRECT)
- T-FUZZY-04: proves $queryRaw (Phase 2) is never called — structurally dead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 19:00:35 +04:00
fdaea58939 feat(doc-control): daily cron for expected doc watchdog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 18:42:22 +04:00
36e75287f0 feat(doc-control): AXE3 foundation — expected documents + overdue detection
Schema: ExpectedDocument model, DocType + ExpectedDocStatus enums,
poNature + deliveryBySupplier on PurchaseOrder.

Rules engine: computeExpectedDocuments (goods/service, delivery mode,
alternative group RECEPTION_VAT for UPD vs TORG12+SF).

Watchdog: generates expected docs for SENT+ POs, detects OVERDUE
(5 business days SLA), alerts ACHETEUR. Observable heartbeat.

Classifier: extractPOCode (PO-XXXX-XXX regex), detectIntent
(INVOICE_REQUEST for requisites/invoice emission requests).

Routes: GET /expected, PATCH /:id/waive, POST /run-watchdog.
UI: DocControlTab in Documentooborot page.

11 Vitest tests (rules + alternative satisfaction + PO extraction
+ Promgaz intent detection).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 18:35:44 +04:00
a1c2e612c6 feat(price-memory): Spec 2 canonical nomenclature + dedup
CanonicalItem model with @@unique(canonicalLabel). MatchStatus enum
(UNMATCHED/PROPOSED/CONFIRMED/REJECTED) on PriceObservation.

normalizeLabel: deterministic preprocessing (lower, trim, dimension
separator normalization, standard extraction, RAL variant, family).

CanonicalSeedService: Phase 1 exact match (group by normalized),
Phase 2 fuzzy via pg_trgm similarity. Observable heartbeat.

Review endpoints: confirm/reject/reassign/create on observations.
NomenclatureTab UI in Purchases page.

16 Vitest tests (10 normalizer incl. M8/M10 discriminant test + 6 sync).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 18:17:00 +04:00