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>
- 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>
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>
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>
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>
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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>