План проекту ESWF / DOP¶
Архітектура, tech stack, структура модулів — у ../CLAUDE.md та dop/spec.md. Цей файл — лише активний backlog. Виконане → done.md. Відкладене з тригерами → BACKLOG.md.
Як читати backlog¶
Класифікація задач для autonomous mode (деталі — ../CLAUDE.md § Autonomous task execution):
- 🤖 auto — Claude виконує самостійно від плану до коміту, дрібні розвилки → ADR. Береться у batch-режимі.
- 🧭 review — Claude виконує, але стоп після коміту. Скіпається у batch.
- 👤 decision — Claude НЕ пише код, готує ADR-пропозицію з варіантами. Скіпається у batch.
- без маркера — лише в інтерактивній сесії, не запускати автономно.
Класифікація задач по треках (memory three-tracks-discipline, three-tracks-definition):
- [ESWF] — framework: core/, form-shell, common/fields/list/toolbar, skills, AFP framework
- [ESS] — Essentials horizontal ERP: essentials/, accounting/, crm/, hrm/, production/, plugins
- [SHC] — ShipCore vertical: fleet/, logistic/, shipcore/, mobile/, compliance
- (+TAG) — spillover, додатково зачеплений трек
Пріоритет (memory feedback_priority_criteria — порядок: архітектурна правильність → рівномірний розвиток треків → відповідність західним ERP стандартам):
- P0 — блокує architectural completeness або працюючий pattern зламано. Беремо першим.
- P1 — завершує нещодавно landed pattern, заповнює відомий gap у коді, тех-борг від MVP. Активна черга.
- P2 — completeness/uniformity без архітектурного blocker'а. YAGNI поки не зробимо shape. Беремо коли P0/P1 спорожніли.
НЕ використовуємо як тригер активації (memory feedback_no_client_triggers, feedback_priority_criteria):
- ❌ «коли клієнт X підпише», «коли pilot активує», «коли operator-side запит»
- ❌ «pilot signal на конкретний document type / dashboard / report»
- ❌ «при потребі демо для pilot client»
Валідні тригери реактивації: - ✅ «хочу зараз» / «потім» від користувача - ✅ Architectural prerequisite landed (e.g. «Phase 4 refactor closed → Phase 7 pricing concept unblocked») - ✅ Real external dependency resolved (e.g. Maersk creds in hand, УЗ B2B-договір підписаний) - ✅ Regulatory deadline approaching (eTTN 2027-01-01, eFTI 2027-07-09) - ✅ Data-driven threshold crossed (e.g. «коли unmapped Group MDM entries > 1000»)
При першому «далі»/«продовжимо»/«наступна» у сесії — Claude робить git lookback по треках за 14 днів і пропонує сплячий трек.
Backlog-grooming gap — частково закрито 2026-06-06. Нотатка 2026-06-04 фіксувала що P1+P2 не має 🤖-actionable айтемів. Grooming-сесія 2026-06-06 виявила, що причина була не брак роботи, а stale-беклог + promotion lag: (1) кілька ESS
[shaped]пунктів насправді вже зроблені (subtable-міграція, drilldown — як LocalizedTextField 2026-06-05), вони створювали ілюзію «є робота, але вся 👤»; (2) Document approval Phase 2 уже був shaped «ready 🤖» у long-term-ess.md § 3, але ніколи не потрапив у чергу — тепер промоутнутий у P1 (вище). Урок: перед «черга порожня → грумити нові ідеї» спершу audit-first звірити наявні[shaped]з кодом — половина вже done. Залишковий gap: після Phase 2 черга знову проріджається — наступний refill з тих самих long-term доків.Re-audit 2026-06-09 (обидва кандидати закрито): (1) bulk-operations — зроблено 2026-06-09 (ESWF kernel primitive + поле bulk-edit, 2 commits, P2 вище). (2) ПКО/РКО print-форми — вже були реалізовані у cash-bank S6a (commit
8d2126e2):IncomingPaymentContract/OutgoingPaymentContractрендерять КО-1/КО-2 legal-форми (3-гілковий шаблон UA+cash→КО-1/КО-2 з відривною квитанцією + кореспонденцією, UA+bank→0410001, else→generic), зареєстровані вessentials/apps.py, runtime-резолв ✅. Черговий доказ grooming-уроку: stale-кандидат, давно done. Наступний refill — лише через свіжий read long-term-eswf/ess + audit-first, не з цього (вичерпаного) списку кандидатів.
🟢 P1 — активна черга¶
[SHC] Logistic — оптимайзер: редагування джерел/транспорту прямо в калькуляторі (Фаза 2) 🧭¶
Повний промт для нової сесії: planning/grainflow-optimizer-editing-phase2-2026-07-14.md. Модель обрана користувачем 2026-07-14: A — обмеження (pins) + переоптимізація (переви користовує «замки»). Фаза 1 (видимість залишків джерел) ✅ зроблена 2026-07-14 —
sources_summary(всього/використано/залишок) + тултіп + таблиця в калькуляторі; Хмельницький 20k вільних як тест-кандидат. ✅ Фаза 2 виконана 2026-07-14 (порядок 2b→2c за вибором користувача; pins→is_locked на save; панель обмежень: так). 2a бекенд:calculate_grainflow(pins=)—_parse_pinsматеріалізує transient-pins черезprice_pinned_rowу locked-like псевдорядки,_collect_matrix(pin_rows_by_plan, excludes_by_plan)знімає supply/demand як замки / робитьexcludeзаборону (cost=None);save=true→ pins стаютьis_lockedрядками (_apply, lock_reason «закріплено в оптимайзері»); хелперoptimizer.price_leg+ ендпоінтPOST /logistic/lot-plans/price-leg/;sources_summaryдіставlat/lon. 2b фронт: колонка «Обмеження» в таблиці рішень (мода-Select→pin / «виключити»→exclude / «зняти»),RedirectPopoverна рядках залишків (план+мода з ₴/т-підказкою+обсяг), панель активних обмежень зі «зняти» + «Перерахувати з обмеженнями» (pinsSig/appliedSigstaleness). 2c drag-drop:FlowMapдістав opt-ineditMode/extraSources/onLink(SVG pointer-drag, screen→viewBox через getScreenCTM, hit-test споживача, ghost-лінія, dashed pool-0 вузли невикористаних джерел), кнопка «Редагувати на карті» +DropLegModal(мода+обсяг). Verify: control savings 6 085 509 ₴ незмінний; pin/exclude/save/price-leg через сервіс+view; 5 нових pytest (test_optimizer); tsc 0;checkчисто. Bundle: ai/domains/logistic/grainflow.md.
[SHC] Logistic — розклад виконання лота проти пропускної здатності портового елеватора 👤¶
ADR-пропозиція: ai/architecture/adr-2026-07-15-lot-execution-scheduling.md (status
proposed— чекає відповідей клієнта). Походження: 7-ме зауваження клієнта (2026-07-15) — «алгоритм має враховувати, чи готовий отримувач прийняти всю партію». Первинне тлумачення «обмежити план закупок ємністю елеватора» — ВІДХИЛЕНО (судно порожнє, готове прийняти все; вартість sourcing'у від пропускної здатності не залежить; GrainFlow LP не чіпати). Правильна постановка: зерно не завжди перевалюється напряму вагон/авто→трюм — частина потребує підробки в портовому елеваторі (підсушка/очистка/фумігація), і саме там є пропускна здатність (скільки й КОЛИ приймає). Це задача розкладу/послідовності (задача-3 класу ADR ядра), execution-шар, НЕ transportation-LP. Модель: портовий елеватор = конвеєр із вузьким місцем — прийом (t/добу) → підробка (t/добу + dwell, фумігація тримає партію N діб) → буфер (вільна ємність) → навантаження на судно (t/год, обмежене laycan+demurrage). Що є: документи ФАКТУ (GrainDrying/Cleaning/Fumigation, ShrinkageJournal, Silo.capacity/fill, SOF/laytime/demurrage). Чого немає: планового шару ТЕМПІВ і ЧАСУ (rates, dwell, графік прибуття ТЗ, laycan-як-обмеження, зарезервована ємність під лот). Прапорці клієнта переосмислені: «дробити партію чи ні» = цілісність технологічної партії (фумігована опломбована на dwell); «немає сенсу везти 200 т» = мінімальна партія на операцію. Обидва — processing-рівень, не sourcing. Рекомендація: MVP = перевірка здійсненності темпу + таймлайн (Gantt), людина рулить (патернtransport_plannerown/hire+bottleneck); повний оптимізатор розкладу (MILP/CP-SAT) — окремий трек пізніше. ГЕЙТ — 3 питання клієнту перед кодом: (1) елеватор наш чи третьої сторони? (2) головна мета — не платити demurrage / не переповнити елеватор / вкластися в laycan? (3) підробка для частини зерна чи для всього лота?
[SHC] GrainFlow Stage 2 — наймане авто / вагонний движок у ЖД / еТТН / бухгалтерія (грумінг 2026-07-12)¶
Контекст: після передачі тенанта grainflow логісту користувач замовив 4 треки. Повний план з розвідкою кодової бази: planning/grainflow-stage2-plan-2026-07-12.md. Рекомендований порядок: 5 → 1 → 4 → 3 → 2 (сумарно ~8 дн). Маркери (🤖/🧭/👤) не розставлені — класифікація за користувачем.
- [x] Етап 5 — Бухгалтерія + P&L по контракту ✅ 2026-07-12 — appAccounting активний; управлінський = перейменований
pcg_eur(«Управлінський облік (PCG)»); фільтр contract у profit_loss + drill-down + вимір contract у pnl_drilldown + фронт-пікер (ReportFilterKind 'contract' на обох P&L-обгортках). 2 БАГФІКСИ: подвійна FX-конвертація (functional-сума + валюта документа у post_accounting_entry — USD-інвойс GASC роздувався до 37.97 млрд) і JE ledger=None (ручні проводки невидимі для standard-звітів — тепер стемп за стандартом рахунків). JournalEntryLine.contract (migr 0101); voyage-pack: Contract-дзеркала + invoice.contract + по-плановий COGS з факт-собівартості fulfilments в ОБИДВА контури; export-finance JE-дзеркала per-standard. Контроль: контури копійка-в-копійку 182,978,352; P&L по EXP-ZP-C02 = 312М − 278.45М = 33.55М валового.- [x] Етап 1 — Наймане авто ✅ 2026-07-12 — ТТН для hired-легів (перевізник з наряду, авто = hired-картка round-robin через _next_hired_vehicle, cleanup за ttn_hired); БАГФІКС plan_transport знищував тендерні закріплення при переплануванні (перенос за natural key mode+origin_name); гард путьових проти hired у серіалізаторі; сід 2 авто/перевізника + пост-тендерний стан; колонка «Належність» у списку авто. 17 ТТН всі з перевізником і авто.
- [x] Етап 4 — еТТН ✅ 2026-07-12 — appeTTN у whitelist; seed_grain_ettn (gf_ettn): реєстрація + справжні Ed25519-підписи через edo.SigningKey per-ЄДРПОУ — 6 confirmed (виконані) + 7 carrier_signed (у дорозі) + 4 submitted, verify всі valid; пункт е-ТТН у grain-trader § Виконання.
- [x] Етап 3 — Вагонний движок у ЖД ✅ 2026-07-12 — tenders-style cutover: моделі → shipcore_rail (нові таблиці, task_ref soft-ref, logistic/0027 + rail/0011); НОВА WagonDemandLine (ручна потреба станція×вагони×дедлайн = самостійна задача ЖД, adapter-лінії матеріалізуються compute'ом); registry wagon_demand_registry + logistic TransportPlanWagonDemandAdapter (guarded); планер у rail з soft-bridge до RouteContext; фронт → @dop/rail + пункт у railSection (сабтейбли Потреба/Наряди) + editable WagonDemandLines; grain-trader/logistic запозичують. 21/21 test_optimizer; WD 78/185 + 11 demand-ліній на свіжому тенанті.
- [x] Етап 2 — Акт перевізника (carrier-side MVP ТЕД) ✅ 2026-07-12 — ExpeditionAct/Line (logistic/0028): рядки = виконані наймані плечі (ТТН, тонни, ставка vs договірна тендерна); пост → 3-way match → PurchaseInvoice перевізнику (SRV-FREIGHT-ROAD service-item, без стоку) → кредиторка; build-lines/post/unpost екшни + меню (logistic + grain-trader); сід gf_carrier_acts: АП0000001/2 (15,000 т + 28,534.92 т = 16.1М ₴, matched). Горизонтальна фаза 1a ТЕД свідомо не зачеплена — повний ТЕД за своїм треком.
Виконано повністю 2026-07-12 (7 комітів, тенант пересіяний з нуля pk=8, 21 фаза, 11 ліцензій). Session log: ai/sessions/2026-07-12-autonomous-log.md § Stage 2.
[SHC] Logistic — зауваження клієнта з тестової демонстрації 2026-07-10 🧭¶
Контекст: тестова демонстрація калькулятора зернотрейдера клієнту → 6 зауважень. Зведення для узгодження з клієнтом: planning/demo-remarks-client-2026-07-10.md. Порядок затверджено користувачем у сесії 2026-07-10. Кожен пункт = 1 коміт, стоп після коміту (🧭).
- [x] 🧭 1. «Замки» на рядках плану (зауваж. 2+3: фіксація обсягу/джерела/судна + примусова мода) ✅ 2026-07-10 —
CollectionOrder.is_locked/lock_reason(міграція logistic/0021), препроцесинг у greedy + GrainFlow (тонни замків знімаються з demand/supply ДО розв'язання, солверoptimize.flowsнезмінний),price_pinned_row(заморозка вартості на момент фіксації, примусова мода черезmode_costs), екшниpin-candidates/lock-order/add-pinned-order(guard на posted), фронт: колонка-замок +CollectionOrderLockModal+ «Додати фіксацію» + помаранчева підсвітка, 🔒 у GrainFlow-таблиці. Перевірено: 101 млн ₴ контроль незмінний (59.69 пшениця + 41.32 кукурудза), 17/17 test_optimizer, tsc 0.- [x] 🧭 2. Довідник вивірених маршрутів (зауваж. 4) ✅ 2026-07-10 —
VerifiedRoute(міграція logistic/0022): координатні кінці + мітки, mode road/rail (rail = тарифні відстані УЗ → у RailFreightTariff замість авто-апроксимації), effective dating (кейс моста: зміна дороги = новий рядок з періодом дії), source manual/osrm/gps_track. Драбина: VerifiedRoute (чинний на дату плану) → haversine×detour → legacy; матчинг за близькістю кінців (5 км, симетрично A↔B),RouteContext-кеш на прогін оптимізатора. OSRM свідомо НЕ в рантаймі (повільно/недетерміновано) — наповнює довідник офлайн командоюfill_verified_routes --tenant. Довідник у меню логістики (default MASTERDATA форма). Перевірено: кейс моста наскрізно (geo 11.7 → verified 92 → об'їзд 147 на дату плану), 101 млн контроль незмінний, 24/24 тестів, tsc 0.- [x] 🧭 3a. Вхідний тендер TradeTender (зауваж. 1, хоп 0) ✅ 2026-07-10 —
TradeTender+TradeTenderLot(міграція logistic/0023, номери TN…, статуси draft→submitted→won/lost, БЕЗ облікової природи — state інертний). What-if прайсинг: лоти → draft-плани (носії параметрівTN…-L#) →calculate_grainflow(save=False)ОДНІЄЮ задачею (лоти конкурують за джерела), нічого не матеріалізується; маржа чесно на покритий обсяг (дефіцит = прапор, не маржа). Подання вимагає розрахунку. Won → чернетки GrainContract (side=sell) по лотах + contract_ref на план/лот; lost → порожні draft-плани прибираються. Повний custom-form стек (Page/Form/Fields/Lots+модалка/List/Toolbar за UI-стандартом TransportOrder), група «Тендери» в конфізі, ролі forwarder/_crud + auditor/_readonly (apply_system_roles на tenant 3). Перевірено: 16/16 тестів (4 нових), живий HTTP-цикл create→lots→price→submit→won (маржа 119.9 млн ₴, 29.4%, 2 контракти) з rollback, 101 млн контроль незмінний, tsc 0.- [x] 🧭 3b. Вихідний тендер FreightTender на найм авто (зауваж. 6) ✅ 2026-07-10 —
FreightTender+Lot+Bid+TransportTask.carrier/awarded_rate_per_ton(міграція logistic/0024, номери FT…, статуси draft→collecting→awarded, без облікової природи). Лоти ідемпотентно з hired road-легів плану розвозу (стеля = планова ставка × км; перезбирання з пропозиціями заборонене). Пропозиції = FK FreightContractor (role=carrier). Частковий авард по машинах (лот ділиться між перевізниками); фіналізація: покриті тонни за середньозваженою договірною ставкою, недобір чесно за стелею; write-back: TransportTask.carrier + awarded_rate + план.hire_cost_estimate. Кнопка «Тендер на найм» на плані розвозу (активна при trucks_hired>0) → створює і відкриває тендер. Повний UI-стек (лоти/пропозиції/авард-модалка/List). Перевірено: 19/19 тестів (3 нових), живий цикл на плані 1 tenant 3 (2 лоти × 9 500 т: план 1 700 500 ₴ → договірна 1 496 440 ₴, економія 204 060 ₴, write-back ✓) з rollback, 101 млн контроль незмінний, tsc 0. Scope cut: ТТН для найманих легів (авто перевізника невідомі), вагонна оренда (mode закладений), P1 ТЕД 3-way match.- [x] 🧭 4. Вагонний Канторович — подача порожняка (зауваж. 5 + часове уточнення клієнта) ✅ 2026-07-10 —
WagonDispatchPlan+Order(міграція logistic/0025, номери WD…, без облікової природи). Часова модель: парк = порожні на станціях + порожні у поверненні (готові після дороги) + завантажені (після дороги + нормативу вигрузки, потрібен direction_to); пара допустима лише якщо прибуття ≤ window_to плану — хто не встигає, заборонена пара → чесний дефіцит. Нормативи на плані (швидкість УЗ км/добу, вигрузка діб; дефолти LOGISTIC_). Км порожняка: verified rail → haversine×1.2. Demand: rail-леги активних планів (станція: silo→site.nearest_rail_station → fallback ім'я Site → чесний пропуск зовнішніх). Бенчмарк — наївне пер-легове призначення (Decimal-квантування як в оптимуму — float-епсилон ловився тестом).seed_wagon_dislocation --tenantрозкидає порожні по станціях. Повний UI-стек + пункт «Подача вагонів» у планах вантажопотоків. Перевірено: 21/21 тестів (2 нових: часовий пул, дедлайн-гейт), живий план WD0000001 на tenant 3: 90/118 ваг (дефіцит 28 чесний), 8 534 ваг·км оптимум проти 16 911 наївних (−49.5%)*, 101 млн контроль незмінний, tsc 0. Фаза 2 (deferred): конкретні номери вагонів + заявки/ЗН на подачу.- [x] 🧭 4c. Митна декларація (МД / ЕК-10) ✅ 2026-07-12 — питання клієнта «документи відходу судна». Audit-first: export-pack родина ВЖЕ існувала (фіто/радіо/походження на GrainDispatch + readiness endpoint) — МД доданий як її четвертий член, але standalone (якір = GrainContract + опційні voyage/BoL, статуси митні: draft → submitted → cleared/rejected → resubmit; voided).
CustomsDeclaration(міграція shipcore_grain/0021, номери МД…, БЕЗ облікової природи — 0% ПДВ експорт + вивізного мита немає, чекпоінт A.7 зафіксовано в docstring). Guards: подача вимагає контракту+нетто, пропуск — № МД митниці; дати штампуються. Повний стек: serializer edit-lock (rejected редагований для resubmit) + viewset (form-data/refs, post=подати, unpost=анулювати, advance_status=рішення митниці) + EntityRegistry + ролі terminal_dispatcher + фронт на grain-doc двигуні (Config/Form/Page, кнопки «Пропуск»/«Відмова» у submitted) + сід voyage-pack (3 cleared + 2 submitted, purge за маркером). Перевірено: 57/57 grain-тестів (7 нових), tsc 0, контроль voyage-pack інвойсів 931М (315+312+304), C02 312М posted. ⚠️ Контроль P&L оновлено: 166,870,431.44 обидва контури (старі 182,978,352 були виміряні ДО посту актів перевізників: −16,107,920.40 AP = точна дельта; моя зміна проводок не додає). Scope cuts: АСМО/M.E.Doc подання (немає API), multi-item позиції МД, розширення export-pack readiness endpoint на МД (він dispatch-anchored), кастомний print-бланк; AI УКТЗЕД-підбір — розглянуто, відкладено.- [x] 🧭 4b. Вагонний трек Phase 2 — конкретні номери + ЗН подачі ✅ 2026-07-12 — (а) наряд подачі отримує поіменний зріз групи дислокації (
WagonDispatchOrder.assigned_wagonsJSON, патернTransportTask.assigned_units); пул чесно виключає закріплені деінде вагони (вихідні draft-ЗН — семантикаcomplete_wagons— + наряди інших планівin_execution), нотатка «закріплені деінде: N ваг» у planner_notes; (б)generate_dispatch_waybills— draft-ЗН порожнього рейсу на кожен наряд (нове полеRailwayWaybill.is_empty_run; пост рухає дислокацію уempty_in_transitдо станції навантаження, не loaded — гілка уwagon_tracking), consignor_org → direction='out' → draft сам резервує свої вагони від complete_wagons інших документів; ідемпотентна регенерація за FK+маркером[auto:подача]; проведена ЗН = факт: skip при регенерації, перерахунок блокується («переплан не знищує факт»); (в) екшнgenerate-waybills+ кнопка «Сформувати ЗН» + колонки «Номери вагонів»/«ЗН подачі» у нарядах; (г) міграція shipcore_rail/0012. Перевірено: 44/44 logistic+rail (4 нових: номери/генерація/резервації/факт-заморозка), GrainFlow WD0000001 контроль 78/185 точний (резервацій на тенанті 0 — пул не зсунувся), 78 поіменних унікальних, live-генерація 28 ЗН з rollback, tsc 0. Scope cuts: подача-ЗН не сідуються (демо показує кнопкою); WagonOperationLog (unpost ЗН стирає сідовану дислокацію) — і далі відкладений; заявка ГУ-12 окремим документом не моделювалась (draft-ЗН покриває MVP).Після зауважень — повернення до P0 постинг виконання (рух зерна по залишках + факт-собівартість, див. memory
grain-calculator-readiness).
[SHC] Logistic — калькулятор зернотрейдера: вихід у продукт (грумінг 2026-07-11, ПЕРШОЧЕРГОВО — вище ТЕД)¶
Контекст: після закриття хопа 8 користувач вніс 5 першочергових задач (сесія 2026-07-11). Три архітектурні розвилки узгоджені: (1) тендерний engine витягається ЗАРАЗ з міграцією TradeTender/FreightTender на нього (дані лише demo — міграція дешева, поки без клієнтського легасі); (2) зовнішні біди у магазині — тільки зареєстровані CustomerProfile; (3) дозьє контракту — варіант А: read-only агрегатор по наявних refs. ADR: ai/architecture/adr-2026-07-11-tenders-engine-extraction.md. Порядок: швидкі видимі виграші (секція + звіти + дозьє) → структурний engine. Кожен пункт = 1 коміт.
- [x] 🤖 1. Секція «Калькулятор зернотрейдера» у бічному меню ✅ 2026-07-11 — showcase-секція
grainTraderза preset-рецептом (3-й доведений випадок після Stevedoring/ContainerHub):frontend/dop/grain-trader/config.ts— 6 груп (Тендери / Планування з GrainFlow-процесом / Зерно / Вагони з дислокацією і тарифом УЗ / Виконання ВЗ+ЗН / Звіти), custom-component items імпортовані напряму (lazy для крос-модульних), default-form items зmodule:override (logistic/shipcoreGrain/shipcoreRail); app-картаappGrainTrader(requires logistic+grain+rail, zero own models, іконка WheatOutlined); seed_shop PRODUCTS запис + AppEntry синхронізовано + License активована на tenant 3; section-landing helpgrain-trader.md+.ua.md(ланцюг угоди 6 кроків). tsc 0. Звіти задач 2-3 додаються у групу «Звіти» цієї секції їхніми комітами.- [x] 🤖 2. Звіт «Доступне зерно для тендерів» ✅ 2026-07-11 (skill
new-report) —logistic/reports/available_grain.py(codeavailable-grain): секції по commodity_class, рядки = силос×власність (відкриті OwnershipLot через grain_bridge, депоненти згорнуті) + чинні GrainSupplyOffer; станція silo→site→nearest_rail_station; ₴/т = зважена batch.purchase_price / price_per_ton; два grand-totals — «Разом в обох пулах» і «Доступно для тендерів» (custody/consignment показані, але чесно виключені — оперувати не можна); фільтр commodity_class на API-рівні (UI — групування по класах). Меню: logistic Analytics + grainTrader Звіти. Verify: live tenant 3 — 32 рядки (45.2К barley + 115К corn + 182.2К wheat = 342 382.993 т), фільтр wheat ok, permission smoke 200/403 по ролях, py_compile + tsc 0. Bundle: reports.md § available-grain. Scope cut: drill-down немає (джерела не ledger-регістри).- [x] 🤖 3. Звіт «Дислокація вагонів» ✅ 2026-07-11 — audit-first: звіт УЖЕ існував (
shipcore_rail/reports/wagon_dislocation.py, list+map+stale-days) — розширено замість написання з нуля: (а) колонка «Прогноз прибуття» для рейсових (норма 250 км/добу; відстань verified rail через soft bridge до logistic → haversine×1.2; ETA у минулому = overdue-сигнал + лічильник); (б) колонка «Зайнятість» — бронь на вихідному draft-ЗН (семантика complete_wagons; прив'язка до наряду подачі свідомо ні — Phase 2 конкретних номерів deferred) / «Виконує перевезення» / «Повернення порожняком»; (в) багфікс «невидимий порожняк»: фільтр as_of викидав рядки з last_event_at=NULL — 40/90 вагонів (УВЕСЬ вільний парк) були відсутні у звіті. Фронт без змін (колонки динамічні). Verify: rollback-сценарій на tenant 3 — 90 рядків (було 60), reserved_count 14, ETA+overdue 10; pytest rail 7 passed. Bundle: shipcore_rail/wagon_dislocation/README.md § Розширення. Доповнення (фідбек «прочерки у колонках»):seed_wagon_dislocationпереписано у повний снапшот трекера (source='csv_import'): статуси/дати/напрямки/власники детерміновано від pk — 104/104 рядки з датами й власниками, 46 рейсових з ETA (3 overdue навмисно), простій 1–45 діб за статусом; планер подачі на новій картині −49.7% порожнього пробігу.- [x] 🤖 4. Дозьє «Справа контракту» ✅ 2026-07-11 (узгоджений варіант А, нуль нових моделей) —
logistic/services/contract_dossier.py+GET /logistic/contract-dossier/[{ref}/]: збирає по наявних ключах — лот-плани (contract_ref) → плани розвозу → ВЗ (маркер + fulfilment.dispatch_ref) / ЗН / ТТН (маркер) + прийомки (fulfilment.reception_ref, вхідні) + GrainSettlement (FK) + тендер-джерело (TradeTenderLot.contract_ref); totals target/collected/shipped; секції вихідні/вхідні, статуси, навігаційні refs. ФронтContractDossierPage(пікер + KPI + 2 таблиці, клік → документ), зареєстрована у grainTrader § Виконання + logistic § shipment_execution; iconMap += FolderOpenOutlined. Verify: tenant 3 — пікер 8 контрактів, справа №1 = 22 док. (14 ВЗ + 3 ЗН + 4 ТТН + розрахунок), shipped 26 202.37 т; tsc 0, py_compile чисто. Bundle: ai/domains/logistic/contract-dossier.md. Scope cuts: рахунки — після P1 ТЕД (немає ключа essentials.Contract↔GrainContract), оплата (allocations) — там само; ручний attach — P2 binder-добавка.- [~] 🧭 5. Тендерний engine — horizontal plugin
tenders(узгоджено: витягати зараз; патерн = approvals: engine + registry + консументи; skillnew-moduleCheckpoint 0 → plugin, forModules=['essentials']). T1+T2 ✅ 2026-07-11: appbackend/tenders/(Tender state-інертний + TenderLot soft-ref/payload + TenderBid internal/portal + TenderAward частковий; migr 0001), registryregister_tender_adapter(Protocol: build_lots/write_back, finalize викликає), API/api/v1/tenders/(nested lots/bids + advance-status/award-bid/finalize-award/adapters), ролі (crud у forwarder-блоці, readonly auditor), картка appTenders (plugin на essentials) + seed_shop + License t3, секція «Тендери» на УНІВЕРСАЛЬНИХ формах (нуль кастомних компонентів = T2), демо-сід TD0000001 (закупівля дизпалива, 3 біди, частковий авард verified 30000 л @ 52.80) + TD0000002 (вхідний service). Verify: API-цикл + posted-guard 400 + permission 403 + tsc 0 + vitest 24/24. Bundle: ai/domains/tenders/README.md. T1b-freight ✅ 2026-07-11: адаптер logistic.freight_hire (build_lots з найманих легів + write_back зваженої ставки у TransportTask/план через мапінг Client→FreightContractor), кнопка «Тендер на найм» створює документ движка, демо FT змігровано (FT0000001→TD0000003, старі рядки видалені), менюпункти freightTenders → universal item; повний цикл verified у rollback (write-back перевізник+370.91 ₴/т+кошторис), контроль 101 009 460.88 ₴ точно, pytest 29, tsc 0. T1b-trade ✅ 2026-07-11 — cutover завершено: адаптер logistic.trade_sale (what-if прайсинг через adapter-action + хуки on_status: won→контракти, lost→cleanup, submit-guard), Tender.payload (migr tenders/0002), СТАРІ моделі Trade/FreightTender видалені (logistic migr 0026, сервіси/views/urls/ролі прибрані), кастомний UI трейдера портовано на движок (payload-mapper, refs-ендпоінт), FreightTenderHire UI видалено, 7 тестів портовано (21/21, test-БД перебудована --create-db). Verify: живий цикл price(маржа 16.8М, 29.26%)→submit→won→GrainContract у rollback; контроль 101 009 460.88 ₴ точно; suite 21+19 passed; tsc 0; vitest 24/24. Лишились фази:- T1 backend engine + міграція logistic: моделі Tender (direction in/out, kind purchase/sale/freight/service, state інертний — тендери НЕ проводяться), TenderLot (предмет = soft-ref
<base>_ref+_label), TenderBid (учасник Client FK), частковий авард; registryregister_tenderable(model, adapter)— адаптер дає стелю ціни, побудову лотів, write-back аварду. TradeTender/FreightTender → консументи engine (data-migration demo-tenant 3; кастомні UI логістики лишаються поверх). Контроль 101 009 460.88 ₴ після міграції обовʼязковий.- T2 генеричний UI тендерів — custom-form стек для «просто тендера» без домен-адаптера (інші предметні області без коду).
- T3 shop-публікація вихідних тендерів — Client Portal: сторінка «Тендери», біди ТІЛЬКИ від зареєстрованих CustomerProfile (патерн TransportRequest: user FK + статуси), Kanban бідів для менеджера у commerce (патерн E-Commerce Manager). Публікація = явна дія «Опублікувати в портал» (не автоматична).
[SHC] Logistic — ТЕД фінансовий шар: розділення експедиторської винагороди 🧭¶
Промоутнуто 2026-07-08 — закриває gap, свідомо відкладений у ADR adr-2026-07-07-logistic-documents § «Що НЕ ввійшло у scope» («якщо колись знадобиться нарахування фрахтових витрат — окремий ADR»). Модуль планувався як мультимодальна логістика + експедирування; мультимодальність є, економіка експедирування (транзитні послуги сторонніх перевізників через облік компанії з виділенням винагороди у первинці) — відсутня повністю.
Аналіз + поетапний план: planning/logistic-forwarding-remuneration-2026-07-08.md. Рішення (ратифіковані користувачем): ADR adr-2026-07-08-forwarding-remuneration — (A) обидві облікові моделі через
settlement_mode; (B) новийExpeditionActу logistic генерує essentials Invoice+PurchaseInvoice; (C) genericInvoiceLine.line_kind(торкає горизонталь → regression обов'язковий); (D)TaxRate «0% міжнар.»+ mirror; (E)remuneration_typeна контракті.Чому 🧭 (стоп після коміту): фаза 1a/3 змінюють горизонтальний posting-контракт essentials (
InvoiceLine.line_kind→ маршрутизація проводки) — blast-radius на звичайні інвойси, потрібне ревʼю. Скіпається у batch; береться явним «зроби Logistic ТЕД» / «продовж фазу N».
- [x] Фаза 0 — ADR ✅ 2026-07-08 (
adr-2026-07-08-forwarding-remuneration, accepted; обґрунтування горизонтального торкання окремим підрозділом).- [ ] 🧭 Фаза 1a — горизонталь:
InvoiceLine.line_kind ∈ {revenue, reimbursable, remuneration}(nullable, defaultrevenue) + serializer + frontend line-editor + regression на звичайні інвойси + міграція essentials.- [ ] 🧭 Фаза 1b — домен logistic: VAT-поля
FreightContractor;remuneration_type/value/basisнаFreightContract;ShipmentLeg.tax_rate; сідTaxRate «0% міжнар.»; міграція плагіна.- [ ] 🧭 Фаза 2 —
ExpeditionAct(skillcustom-form): model+lines (settlement_mode,line_kind), serializer (+PostedGuard), viewset, urls, EntityRegistry, ролі, plugin-urls.- [ ] 🧭 Фаза 3 — проводки (обидві моделі): posting-routing за
line_kind; BusinessOperations транзит/дохід/AP;post/unpost_expedition_actгенерує Invoice+PurchaseInvoice; intermediary+gross гілки + ПДВ split; PartyLedger. (rule:document-posting).- [ ] 🧭 Фаза 4 — frontend (skill
custom-form):ExpeditionActPage/Form/Fields/Lines/List; поле «винагорода» у TransportOrder; KPI «Винагорода/Маржа» у футерах Shipment+TransportOrder; config.- [ ] 🧭 Фаза 5 — print (skill
new-print-template): PrintContract Акт ТЕП + Рахунок + Звіт експедитора (net-new — logistic print-базису немає).- [ ] 🧭 Фаза 6 — reports (skill
new-report): «Реєстр винагороди» + «Транзит vs винагорода» (замінитиcomingSoon freightCostAnalysis).- [ ] 🧭 Фаза 7: розширити
seed_logistic_documents; тести ПДВ-розділення+проводок; bundledocs/ai/domains/logistic/.MVP-цінність (розділення винагороди у первинці + посередницькі проводки) — наприкінці фази 4. Оцінка ~6–6.5 дн.
[SHC] Logistic — план закупівель → план розвозу (execution hand-off) 🧭¶
Контекст 2026-07-10 — readiness-аудит калькулятора зернотрейдера (перед демо клієнту) виявив головний розрив: оптимізатор давав лише плановий набір закупівель, а виконуваного плану розвозу з призначенням конкретного транспорту не було. Артефакт оцінки готовності — окрема сторінка (chain / demo-стратегія / roadmap).
- [x] Крок 1 — призначення ресурсів ✅ 2026-07-10. Нова дія
POST /logistic/lot-plans/{id}/build-delivery-plan/+ сервісdispatch_planner.build_delivery_plan: надбудова надplan_transport, закріплює поіменно власні авто (держ-номери) і вагони (номери) за кожним плечем, overflow → найм/оренда, план →in_execution. ПоляTransportTask.assigned_units/dispatch_from/dispatch_to+TransportPlan.dispatch_ready/dispatch_summary(міграціяlogistic/0018). Фронт: кнопка «Сформувати план розвозу» на формі LotAccumulationPlan → навігація на TransportPlan + колонка «Призначено» у нарядах. Перевірено наскрізно (HTTP + tsc 0).- [~] Крок 2 — документи виконання (P0): ✅ 2026-07-10 (майже) —
build_delivery_planдрафтитьfleet.Waybill(ТТН, 1/власне авто, з водієм — авто-driver або round-robin fallback) +shipcore_rail.RailwayWaybill(ЗН, 1/ЖД-плече, wagon_count+gross), state=draft, ідемпотентно (delete-and-recreate за doc_ref), plugin-safe. Окрема діяgenerate-documents+ кнопка «Згенерувати ТТН/ЗН» на самому Плані перевезень (generate_documents_only— регенерація з поточного призначення). Doc-номери в колонці «Призначено» + нотифікації. station FK-резолв для ЗН ✅;GrainDispatch✅ 2026-07-10 — draft ВЗ (відвантаження зерна) на кожне власне плече (batch з силоса, buyer з контракту, transport_doc_type rail/auto),state/status='draft'→ петлі план↔факт немає (auto_match шукаєshipped, чернетки ігнорує; стають фактом після відвантаження). Ідемпотентно за маркером[auto:відвантаження], у savepoint (збій ВЗ не валить план розвозу). Крок 2 закрито повністю.- [x] Крок 3 — реальні дані ЖД (P0): ✅ 2026-07-10. Команда
seed_rail_tariff --tenant— 5 hopper distance-band рядківRailFreightTariff(регресивна ₴/т·км × УЗ-індекс 1.10) + проставляєSite.nearest_rail_station(6 елеваторів) /Port.nearest_rail_station(7 портів) за найближчоюRailwayStation(haversine). Фазаcase_rail_tariffуseed_demoпередcase_grain_lot_accumulation. Оптимізатор_rail_linehaulтепер резолвить реальний УЗ line-haul для власних силосів (зовнішні пропозиції — константи, немає station-link). GrainFlow-числа стабільні (тариф чіпає лише власні силоси; значення близькі до констант: 464 vs 420, 705 vs 692) — економія лишилась ~101 млн ₴.- [~] Крок 4 — вартісний own-vs-hire (P1): ✅ 2026-07-10 (частково) — крос-планове бронювання:
_units_committed_elsewhereвиключає з пулу власні авто/вагони, вже закріплені за ІНШИМИ планами розвозу → одна одиниця ≤1 плану, 0 подвійних бронювань (перевірено на 8 планах, 40 вагонів + 7 авто). де-хардкод норм ✅ 2026-07-10 —plan_transportбере середній payload авто / ємність вагона з реального флоту (available_road_units.avg_payload_t/avg_capacity_t), fallback на константу коли флот без даних; cycle/turnaround лишились константами (потрібні маршрути+графіки). Лишилось (не баг): явне порівняння opex власне-vs-найм — зараз fill-own-first, що вже cost-optimal (власне завжди дешевше за найм). Крок 4 практично закрито.- [x] Крок 6 — звіт план-факт виконання плану закупівель (P1): ✅ 2026-07-10. Перший звіт логістики на Report Framework —
purchase-plan-fact(logistic/reports/purchase_plan_fact.py+ реєстрація в apps.py + фронт-обгорткаPurchasePlanFactReport+ пунктpurchasePlanFactзcomponent:замість мок-ReportViewer). План (CollectionOrder) vs факт (нові поляCollectionOrderFulfilment.actual_*_cost_per_ton, міграція 0019, заповнюються вlot_fact_service). Показує зерно/перевезення/landed ₴/т план vs факт + Δ перевезення (реальний ЖД-тариф вище планового: rail ×1.08–1.14) + перевитрату ₴. Усе з реальних полів, не мок. Endpoint/api/v1/reports/purchase-plan-fact/(+xlsx). Лишилось: факт-транспорт із реального акта фрахту (не варіаційна модель) — трекshipcore_rail_uz_cargo.- [x] Крок 5 — прибрати мок-fallback дошок (P1): ✅ 2026-07-10.
LotBoard/DistributionBoard/ShipmentTracker— мок-дані гейтнуто уimport.meta.env.DEV: прод на порожньому/помилковому тенанті показує чесний порожній стан (не вигадані дані), у dev — мок з помітним filled-бейджем «ДЕМО-ДАНІ — не реальні операції». На засіяному тенанті дошки й далі показують реальні дані.- [x] Крок 7 — постинг виконання, хоп 8 (P0): ✅ 2026-07-11. Audit-first показав: усі 3 постинг-механізми ВЖЕ існували (ВЗ
ship_dispatchрухає стік+FIFO лоти; ЗНpost_railway_waybillрухає дислокацію; ПЛpost_documentпише fleet-регістри) — бракувало постабельності драфтів і UI. Зроблено: clamp тоннажу ВЗ до партії;complete_wagons_from_dislocation(конкретні вільні вагони 3-tier за станцією, анти-double-booking по вихідних драфтах, чесний shortfall) + кнопка «Укомплектувати вагони» на ЗН; валідація посту ЗН — train/terminal лише для вхідних (per model help_text) +consignor_orgу планері (direction='out');WaybillTaskу драфтах ПЛ; self-healing purge сиріт по маркеру (було 20 на tenant 3); station-fallback по origin_name (ре-оптимізація рве FK SET_NULL); фронт-таб «Факт виконання» на LotAccumulationPlan (auto-match + fulfilments + unlink) — backend-петля існувала, UI не викликав. Verify: 22/22 E2E чеки rollback (ship 6/8 — 2 guards правомірні: фумігація+застава; стік/відкат точні; auto-match 6 док., 65 491.95 т), контроль 101 009 460.88 ₴ точно, pytest 40 passed, tsc 0. Bundle: ai/domains/logistic/execution-posting.md. Scope cuts: unpost ЗН стирає сідовану дислокацію (потрібен WagonOperationLog, Phase 2); факт-тариф варіаційний (P1 акт фрахту); фін-проводки — P1 ТЕД.
[ESS] Reports drilldown — ✅ cash_flow зроблено 2026-06-09; aging_report re-scoped (deferred)¶
Промоутнуто refill-грумінгом 2026-06-09 з long-term-ess.md § 5. При взятті — другий audit-first reframe: фронтова
ReportDrillDownPageregister-орієнтована (REGISTER_ENDPOINTS= лише ledger-регістри postingEntries/cashJournal/…, рендерить Дт/Кт-таблицю). Тож drilldown лягає тільки на register-sourced звіти.
- ✅ cash_flow зроблено 2026-06-09 (commit): джерело
PostingEntry(клас-5) → ідеально лягає. Per-month flow-cells (inflows/outflows/net_cash) дісталиDrillDownTarget(register='postingEntries', filters={ledger, account__code__startswith:5, group__date range, org})— ті самі filter-ключі, що йpnl_drilldown(proven проти/data/postingEntries/).closing_balanceсвідомо без drilldown (cumulative). Helper_month_bounds. +1 тест (18/18 уtest_reports.py), нуль нового frontend UI. - ⏸️ aging_report — deferred (НЕ механічно): джерело = Invoice (документи, не ledger). Register-drilldown сторінка ledger-shaped (Дт/Кт) → Invoice-рядки не вписуються. Потрібен окремий documents-drilldown register (новий
REGISTER_ENDPOINTS-entry + documents-shaped drilldown view) — це capability, не one-liner. Реактивація: коли захочемо documents-level drilldown (теж покриє majority-документні звіти). - Решта 7/9 звітів drilldown уже мали (profit_loss, balance_sheet, partner_turnover, trial_balance, cash_book, cash_orders_register, inventory_reports). Після cash_flow — 8/9, лишилось тільки aging (за окремим capability).
[ESS] Document approval Phase 2 — ✅✅ FULLY DONE 2026-06-08 (7 доків + generic UI + промоушн + tenant-default + admin-UI)¶
Promoted з long-term-ess.md § 3 під час backlog-grooming 2026-06-06 — пункт уже був shaped «ready 🤖» у long-term доку, але ніколи не потрапив у чергу (promotion lag, а не брак роботи). Перший actionable ESS-айтем у P1 — rebalance треків (критерій пріоритету #2).
✅ OutgoingPayment зроблено 2026-06-06 (1/4, commit): model+viewset+serializer+receiver+migration 0079 + frontend ApprovalTrack tab. Gate у
outgoing_payment_service.validate_before_post(B2 — не inline у viewset, бо post іде через сервіс). 11/11 нових тестів + 7/7 PI регрес, tsc=0.✅ PurchaseOrder зроблено 2026-06-08 (2/4, commit): ApprovableTransactionMixin + status(approval choices) + approval_routing_context на моделі; ApprovalDocumentActionsMixin + gate inline у
PurchaseOrderViewSet.post_document(A1 — PO без posting-service);route_name+statusread-only у serializer; PurchaseOrder додано у_APPROVABLE_MODELS; migration 0087; frontend ApprovalTrack tab + header badge. Reconcile: прибрано вестигіальне дзеркалоstatus='posted'/'draft'з post/unpost (PO тепер як PurchaseInvoice —status= чисте approval-поле, фронт-бейджі читаютьstate; ніхто не читав PO.status='posted'). 10/10 нових + 18/18 PI+OP регрес, tsc=0.✅ GoodsWriteoff backend зроблено 2026-06-08 (3/4, commit): mixin + status(approval choices) + approval_routing_context; gate у
goods_writeoff_service.validate_before_post(B2 — post іде через сервіс, як OutgoingPayment); ApprovalDocumentActionsMixin на viewset;route_name+statusread-only у serializer; GoodsWriteoff у_APPROVABLE_MODELS; migration 0088. 10/10 нових + 38/38 повний approval-suite + 6/6 існуючі writeoff-тести (gate = no-op без route). ✅ Generic approval-таб у kernel + InventoryCount + StockTransfer зроблено 2026-06-08 (commit): фреймворк-табcore/TransactionData/TransactionForm/ApprovalSection.tsx(тягне/approvals/routes/, route-picker + send-кнопка +ApprovalTrack+ badge) вмонтований уTransactionForm— світиться для будь-якого generic-form документа зroute-полем (детект'route' in record), нуль per-doc frontend. ApprovalTrack перенесеноdop/internal-documents → core/components/shared(domain-neutral kernel-primitive; dop-файл = re-export shim → нульова churn для PI/OP/PO/Contract).routeвиключено з header-гріда + save-payload (таб володіє через прямий PATCH). InventoryCount + StockTransfer backend (mixin+gate B2+serializer+receiver+migration 0089) — щоб таб реально покрив усі три generic-form доки. 14/14 нових IC+ST + 69/69 повний approval+posting регрес, tsc=0, vitest 4/4, no core→dop leak. ✅ JournalEntry зроблено 2026-06-08 (4/4 цільових, commit): mixin + status(approval choices) + routing-context ({amount: total_debit()}); gate inline уJournalEntryViewSet.post_document(власний viewset уaccounting_extra.py, post черезpost_journal_entryбез validate_before_post → gate перед balance-перевіркою);ApprovalDocumentActionsMixin; serializerroute_name/statusread-only; receiver += JournalEntry; migration 0090. Frontend: кастомнийJournalEntryPageпереюзає той самий coreApprovalSection(через новий опційнийextraInvalidateKeysдля його власних query-ключівjournal-entry/journal-entries). 8/8 нових + 97/97 повний approval+posting регрес, tsc=0, vitest 4/4.Підсумок rollout: усі 4 цільові документи (OutgoingPayment, PurchaseOrder, GoodsWriteoff, JournalEntry) + бонус generic-form (InventoryCount, StockTransfer) = 7 документів (з пілотом PurchaseInvoice) мають posting-gate + approval UI.
✅ Промоушн
essentials → approvals/зроблено 2026-06-08 (commit):ApprovableTransactionMixin→approvals/mixins.py(essentials shim re-export → 7 моделей незмінні); completion-sync receiver → generic registry-keyedapprovals/receivers.py(конект уApprovalsConfig.ready()); новийapprovals/registry.py(register_approvable(Model, status_map)+DEFAULT_STATUS_MAP);essentials/approval_sync.pyтепер лише реєструє 7 доків. Міграційно-нейтрально (abstract mixin не має таблиці —makemigrations --check= no changes). Seam reusable будь-яким app поза essentials. 97/97 approval + 53/53 internal_documents+approvals регрес, check exit=0. Consumer-contract bundle оновлено. internal_documents лишив власний bespoke receiver (pre-promotion консумент, кандидат на міграцію в registry — не чіпав). ✅ Phase 1.1 tenant-default route зроблено 2026-06-08 (commit): нова модельapprovals.ApprovalDefault(tenant, content_type, route, is_active)— unique per (tenant, content_type);ApprovableTransactionMixin.resolve_approval_routeтепер консультує її (explicit route > tenant default > None); serializer + EntityRegistryapprovalDefaults(universal CRUD) + endpointGET /api/v1/approvals/approvable-models/(фідить picker списком зареєстрованих доків з registry); serializer-guard відхиляє незареєстровані моделі; migration 0005. 8/8 нових + 61/61 approvals+internal_documents регрес, check=0. Реалізує C4 opt-in на рівні tenant — «кожен документ типу X вимагає узгодження» без ручного route.Phase 2 — повністю завершено (7 доків gate+UI + промоушн у engine + tenant-default).
✅ internal_documents → registry зроблено 2026-06-08 (commit): InternalDocument+ContractDocument тепер реєструються через register_approvable у InternalDocumentsConfig.ready(); bespoke internal_documents/signals.py видалено. Усі консументи (9 моделей) на одному generic engine-receiver, нуль per-consumer receiver'ів. 61/61 регрес, runtime-assert: registry=9 / receivers=1.
✅ ApprovalDefault admin-UI зроблено 2026-06-08 (commit): кастомна сторінка internal-documents/components/ApprovalDefaultsPage.tsx (PROCESS-item «Типові маршрути» поряд з «Налаштування узгодження») — ContentType-picker (фідиться /approvals/approvable-models/, ховає вже сконфігуровані типи) + route-select + per-row route/active edit (PATCH) + delete + add. Зареєстровано в internal-documents/config.ts. tsc=0. Admin-доступ (як sibling RouteDesigner — requiresEntities + isWildcard bypass). Не browser-verified (Playwright не запускався).
Phase 2 ПОВНІСТЮ ЗАКРИТО. Опційний хвіст: розширити requiresEntities-доступ на не-admin ролі через role-catalog (за потребою; зараз admin-only, що коректно для tenant-config).
- Арх.бенефіт: завершує нещодавно landed pattern. Phase 1 shipped 2026-06-05 (PurchaseInvoice pilot + seam, 8/8 tests). Generic
approvals/engine (S1–S7) +ApprovableTransactionMixin/ApprovalActionsMixin/receiver + frontendApprovalTrack/inbox + mobile-director + push — усе вже є. Phase 2 = застосувати той самий перевірений posting-gate seam до наступних документів. - Audit-first 2026-06-06 (premise свіжий):
ApprovableTransactionMixinспожитий лишеessentials/models/purchase_invoice.py(Phase 1); ціліOutgoingPayment/PurchaseOrder/GoodsWriteoff/JournalEntryіснують уessentials/models/, але mixin ще не підключено. Робота механічна (copy PurchaseInvoice wiring), не green-field. - Scope (ADR adr-2026-06-05-document-approval-posting-gate F1, accepted): на кожен з 4 документів — (1)
ApprovableTransactionMixinна моделі + lifecyclestatus='on_approval'(D1); (2)ApprovalActionsMixin+send-for-approvalaction на viewset; (3)posting_gate.approval_gate_errors()уvalidate_before_post(A1/B2); (4) signal-receiver completion-sync; (5) frontend ApprovalTrack tab + send-кнопка. + Phase 1.1 tenant-default route (C4 opt-in). + промоушн mixin/receiveressentials → approvals/(reuse поза essentials). - Перевірки:
py_compile+ точкові approval-тести на кожен документ (eталон — Phase 1 8/8),tsc --noEmit. Skill: прямого немає;custom-formposting+AI checkpoint'и дотичні. - Тригер: ready зараз — pattern проявлено, ADR accepted, 4 цілі існують. Береться у batch як 🤖. Розбивка можлива по 1 документу = 1 commit (OutgoingPayment першим — найближчий до PurchaseInvoice posting-shape).
[SHC] Grain Phase H-3 — Warehouse Receipt full (Е-зерно / dispatch coupling / bank cabinet) — reframe 2026-06-04¶
- Audit-first reframe 2026-06-04 (ADR adr-2026-06-04-grain-warehouse-receipt-h3): базовий
WarehouseReceipt(TransactionModel)уже відвантажено 2026-05-27 (entity + lifecycle + pledge/endorse/redeem + dispatch-FK + route + frontend + role_catalog). Backlog-питання (a) [single-table vs окремі моделі] — МЕРТВЕ (вже single-table +receipt_typeenum). Решта осей ратифіковано й розщеплено по маркерах: - ✅ D — GrainDispatch coupling (D1) виконано 2026-06-04: collateral-lock у
dispatch_service._validate_ship(pledged/endorsed WR на лотах, що підуть у consume → ship blocked) + auto-redeem вільних WR коли covered-лот повністю спожитий (ship_dispatch) + exact reverse наcancel_dispatchчерез нове полеGrainDispatch.redeemed_receipts_log(migration 0019). 5 тестів + 12 регрес зелені. Реальний інваріант: не можна відвантажити зерно в заставі. - ✅ B — Е-зерно Мінагро submission seam (B1) виконано 2026-06-04:
shipcore_grain/integrations/ezerno_transport.py(EzernoTransportProtocol +FakeEzernoTransport+EzernoRegistryAdapterNotConfigured-bait + factory) + моделіEzernoSettings/EzernoSubmissionLog(migration 0020) +WarehouseReceipt.ezerno_registry_id/ezerno_status+ servicesubmit_receipt/submit_if_configured+ commandezerno_submit. 6 тестів зелені. External-blocker: real Мінагро API-контракт + КЕП-провайдер (Вчасно.КЕП / дія.підпис) — реактивація коли API-контракт у руках. - ⏸️ C — Bank cabinet defer (C4) — окремий sub-продукт; арх-тригер = AFP Phase 1 outbox/inbox primitive landed (не client-trigger). Шляхи: C1 filtered-view-scoped, C2 external portal, C3 federation.
- 🟡 E — Double-warrant ПДСС — E1 (single-entity моделює ~80%) достатньо для MVP; E2 фізичний split складська/варрант частин (незалежний обіг половин) → V2.
- ⏸️ H-3.1 KMU-form PDF — окремий
new-print-templatesprint (двосторонній warrant + заставний талон). - Western ERP ref: Bushel «Warehouse Receipt Manager», AgVantage «Negotiable Warehouse Receipt», Cargill Cultura «Pledge Tracker» — life-cycle
issued → pledged → endorsed → redeemed. - Reference: session research 2026-05-27 ТОП-5 § B («ЗУ Про зерно та ринок зерна» ст. 37-43, Реєстр Мінагро Е-зерно). Bundle: domains/shipcore_grain/warehouse_receipt.md.
[SHC] Grain Phase H-4 — EUDR compliance: thin scaffold shipped 2026-06-04, real internals deferred¶
- ✅ Thin scaffold відвантажено 2026-06-04 (ратифіковано «роби каркас»). ADR adr-2026-06-04-eudr-compliance (accepted). У
agro_field: моделіEudrSettings/DueDiligenceStatement/EudrSubmissionLog(migration 0002) + 2 NotConfigured-bait seam (integrations/eudr_transport.py: verification + EU DDS) +eudr_service.py(collect_geolocations зField.geometry, run_verification, submit_dds, submit_if_configured, export-clearance gate) + commandeudr_submit_dds+ API (DDS actions from-harvest/run-verification/submit-dds; settingscurrent) + frontend DDS entity (group EUDR Compliance). 10 тестів зелені, tsc=0, migration applied. - ⏸️ Залишилось (deferred, external dep): real satellite provider + EU Information System API internals (зараз
NotImplementedErrorзаNotConfigured); wiring export-clearance gate у живий dispatch/export flow (зараз ready-функція, не викликається); custom DDS-форма з action-кнопками; settings UI + submission-log surfacing. Реактивація: реальний соєвий EU-export pilot + стабільний post-simplification EU DDS API. - Audit-first reframe (2 виправлення фактів + 2 «мертві» осі):
- ⚠️ Дедлайн виправлено: Reg (EU) 2025/2650 (груд. 2025) переніс — large+medium → 30.12.2026, micro+small → 30.06.2027. Старе «30.06.2026 для малих/середніх» — stale. NIBULON-архетип = large → 30.12.2026 (~6 міс). Режим ще спрощується (DDS IT-система у русі).
- ⚠️ Scope виправлено: в EUDR Annex I зернових — лише соя. Соняшник/пшениця/кукурудза — НЕ в scope (кукурудза under review). Gate соя-scoped, не «всі зернові».
- ✅ (b) геометрія — МЕРТВА:
agro_field.Field.geometryвжеJSONField/GeoJSON (покриває Art.9: >4 га polygon, ≤4 га point). Spatial-overlap робить provider, не ми → без PostGIS. - ✅ (d) Field ownership — вирішено для MVP: Field вже own master data в
agro_field. AFP-federation = майбутній AFP Phase 4+. - 🔧 (c) DDS submission — обмежено: queue infra немає → синхронний best-effort hook + management command (як
ezerno_service), «queued» не опція без нової інфри. - 🔧 (a) Satellite provider — provider-agnostic bait: НЕ обирати вендора зараз; Protocol+Fake+NotConfigured (як Maersk/Е-зерно). Реальний (Satelligence vs DIY JRC Forest 2020 overlay) = runtime config, deferred.
- Реальні рішення (звужено): DDS data-model (
EudrSettings/DueDiligenceStatement/EudrSubmissionLog) + commodity+destination-scoped gate на export-cert issuance + 2 bait-seam (verification + EU DDS API). Foundation (Field→Harvest→GrainReception→OwnershipLot.source_reception traceability) вже є. - Рекомендація: thin-seam-now (model + bait + gate scaffold), real-internals deferred до реального соєвого EU-export pilot + стабілізації EU API.
- Western ERP ref: Satelligence, Whisker, AgroVISTA, Meridia (EU Information System / TRACES NT DDS); Trase.earth / EU JRC Global Forest Cover 2020 (cut-off 31.12.2020).
- Тригер реактивації (не client-trigger): реальний соєвий EU-export pilot АБО стабільний EU DDS API post-simplification. Поки — proposed-ADR readiness.
[SHC] GrainReception — intent-only vs UA-form intake (Form 47 alignment) — A-lite shipped 2026-06-04, B deferred¶
- ✅ A-lite відвантажено 2026-06-04 (ратифіковано). ADR adr-2026-06-04-grain-reception-intake (accepted). Архітектура лишилась intent-only. Сторінка
/postingsприймання тепер показує чесний intent-only-напис + deep-link на проводки сертифіката (cert-anchored, вже працюють) — через genericINTENT_ONLY_POSTINGSmap +RegisterRecordsPageempty-state + read-only поляquality_certificate_id/numberнаGrainReceptionSerializer(без міграції). tsc=0, chain-тест зелений. A-full відкинуто (показ cert-проводок на прийманні = leak grain-фільтрів в essentials + вдавання «приймання запостило»). - ⏸️ B (intake-anchored редизайн) — deferred за реальним regulatory/pilot forcing function (не client-trigger, memory
feedback_no_client_triggers). Архітектурний вибір intake-vs-cert лишається intent-only, доки не спрацює тригер B. - Питання: поточна архітектура — Reception нічого не пише в регістри, всі рухи (Batch + StockTransaction + Silo fill + Shrinkage) створюються в одній атомарній транзакції cert.issue. Це конфліктує з UA agro practice — Положення про облік зерна очікує що Прибуткова накладна (Форма 47 / ВЗСГ-3) формує облікову позицію на момент фізичного приймання, а cert.issue лише корегує grade/weight.
- Симптом: Reception's
/postingspage порожня (truthful empty state); operator бачить status='Проведено' але регістри прив'язані до cert. Доводиться навігувати через cert для бачення Batch + StockTransaction. - Варіанти (не вирішено в S1-S7):
- A (UI-only ~30 хв): backend filter
cert__reception=<id>для ShrinkageJournal + custom action на Reception що повертає cert-derived register entries; POSTING_CONFIG entry для grainReceptions. Архітектура та сама. - B (архітектурний редизайн ~3-4 год): Reception → 'weighed' пише
StockTransaction(RECEIPT)з tentative grade + створює Batch. Cert.issue робитьStockTransaction(ADJUSTMENT)за різницю замість initial entry. Складніший unwind на cert.void, але operator бачить інвентар одразу після зважування. - Тригер на decision: pilot client відкриває Form-47 і питає «де це у balance sheet?» → реальний UA-compliance тиск форсує вибір.
- Reference:
docs/ai/domains/shipcore_grain/grain_reception/postings.md§ Open question.
🟡 P2 — completeness / без архітектурного blocker'а¶
[SHC] Logistic — FlowMap: реальна гео-підкладка (GeoJSON контур України) 👤¶
Контекст (сесія 2026-07-14): карта потоків (
frontend/core/src/components/shared/FlowMap/FlowMap.tsx) ставить вузли за реальнимиlat/lon, але без мапи-підкладки — лише спроєктовані точки на чистому фоні. Спроба домалювати контур мальованим полігоном відхилена користувачем: калічити кордони країни неприйнятно (відкочено того ж дня). Потрібен точний контур з реальних даних. - Що треба (3 складові): (1) дані — Natural Earth admin-0 Ukraine (public domain, де-юре кордони, Крим = Україна), спрощенийmapshaper/topojson-simplifyдо ~1–3 КБ, збережений якukraine.geo.jsonу репо; (2) проєкція —d3-geo(geoConicConformal/geoMercator+fitSize/fitExtent) проєктує контур і вузли однією трансформацією → збігаються; або cos(lat)-корекція руками; (3) рендер —geoPath→ SVG<path>(тонкий stroke + ледь помітна заливка), той самий projection для вузлів. - Рішення до старту (чому 👤): (а) джерело/ліцензія (рекомендація — Natural Earth PD); (б) залежністьd3-geo(~30 КБ) чи руками; (в) масштаб — вся Україна (південні вузли скупчуються) vsfitExtentна робочий регіон; (г) тип: тонкий SVG-контур vs повноцінна Leaflet-мапа (Leaflet+react-leaflet уже в стеку для GPS/yard) з тайлами+GeoJSON-шаром — «жива» мапа з зумом, але важча й потребує тайл-провайдера. - Прапор-перемикач «показати мапу» на карті (контур усієї країни знову скупчить південь → або зум, або toggle). - Нюанс середовища: GeoJSON треба завантажити (мережа обмежена) — або файл у репо, або WebFetch спрощеного контуру з публічного джерела. - Тригер: бажання географічного контексту на картах потоків. Косметика, не блокер.
[ESS+ACC] Повноцінний двовалютний GL (Variant B) — presentation-сума у проводках + звітність у двох валютах 👤¶
Виявлено аудитом 2026-06-19 (інтерактивна сесія, currency-change guard). Двовалютність доведена лише до складського шару:
currency_service.convert_to_dual+DualAmountматеріалізують*_presentationуBatch/StockTransaction/InventoryJournal(+ KPI/дашборди). GL одновалютний —PostingGroup/PostingEntryзберігають суми лише в обліковій (функціональній) валюті, presentation-поля немає. Усі фінзвіти (profit_loss,balance_sheet,trial_balance,partner_turnover,cash_book,cash_orders_register) читаютьPostingEntryі хардкодять валюту (буквальноmeta={"currency":"UAH"}) → P&L/баланс у валюті презентацій отримати неможливо. Користувач обрав Variant B (двовалютний GL) над A (трансляція на момент звіту) — консистентно з уже наявним складським патерном.
- Чому 👤 (старт з ADR): варіант (B) обрано, але лишаються рішення з реальними наслідками — рівень суворості трансляції: спрощений (один presentation-leg за курсом на дату проведення/документа) vs строгий IAS 21 (середній курс за період для P&L, closing-курс для балансу, курсові різниці / CTA окремим рахунком). Також: дата курсу для presentation-leg; стратегія backfill наявних проводок (повний repost vs інкремент). Це ADR
docs/ai/architecture/adr-YYYY-MM-DD-dual-currency-gl.mdперед кодом. - Scope (B), орієнтовна розбивка 1 фаза = 1 commit:
- Схема:
PostingEntry.debit_presentation/credit_presentation+PostingGroup.presentation_currency/presentation_ratesnapshot; migration. - Проведення: матеріалізувати presentation-leg через
convert_to_dualуaccounting_service.post_accounting_entry(еталон — інвентарні сервіси, що вже так роблять); покрити всі posting-шляхи (services/posting.py, party-ledger). - Звіти: параметр валюти (
accounting|presentation) уbuild()для P&L/balance/trial_balance/partner_turnover/cash_ — читати відповідну колонку; прибрати хардкод"UAH"* → реальний код зsettings.accounting_currency/presentation_currency(це і окремий дрібний баг — звіти мислейблять валюту, можна закрити раніше). - Repost-рушій: оркестратор перепроведення (reverse+post) для backfill presentation-колонок наявних документів — те, що свідомо не будували у guard-задачі 2026-06-19.
- Currency-change wiring: підключити repost до підтвердження зміни валюти — зараз модалка лише попереджає (backend 409
currency_change_confirmation_requiredу essentials/views/essentials_settings.py, frontend модалка у AccountingSettings.tsx), рушія перерахунку немає. - Наявне (не green-field):
convert_to_dual/DualAmount/get_settingsготові; складський шар — робочий еталон двовалютної матеріалізації; currency-change guard уже блокує+попереджає (2026-06-19);verify_postingsL5 (InventoryJournal↔PostingEntry) — інструмент перевірки консистентності до/після repost. - Перевірки: posting-тести (presentation-leg =
convert_to_dual(...).presentation), reports-тести (P&L у двох валютах сходиться за курсом),verify_postings,manage.py check+ migration,tsc --noEmit. Skill:new-report(для валютного параметра) + правило document-posting.md (repost інваріанти). - Тригер: потреба мультивалютної фінзвітності (P&L/баланс у presentation). Зараз presentation = лише складська управлінська валюта + KPI — свідома часткова реалізація, не баг.
[ESS] Одиниці виміру номенклатури — таблична частина з коефіцієнтами + одиниця за замовчуванням 👤¶
Виявлено інтерактивно 2026-06-27 (ввід приходної на пальне: 10,234 т). Зараз
Itemмає ОДНУ одиницю (Item.unit),Unit— плоский довідник без коефіцієнтів, перерахунку одиниць немає ніде (grep: 0Conversion/factor/base_unit/density). Кількість у рядках —Decimal(15,3), тож сам ввід 10,234 т працює (точність до кг); маску «3 знаки» у полі кількості вже виправлено 2026-06-27 (fixedDecimalScale + фікс-відображення у GoodsReceipt/Invoice/GoodsShipment/PurchaseInvoice). Відкритим лишається кілька одиниць на номенклатуру (пальне: тонни на закупівлю ↔ літри на відпуск).
- Рішення користувача (явне): НЕ «друга одиниця» (flat alternative UoM), а таблична частина одиниць виміру на номенклатурі — список одиниць з коефіцієнтами перерахунку до базової + одна за замовчуванням (патерн 1С «Одиниці виміру»).
- Дизайн + відкриті питання — в ADR adr-2026-06-27-item-units-subtable (Proposed): нова ТЧ
ItemUnit(item, unit, factor, is_default); база =Item.unit(factor=1); document-рядок обирає одиницю з ТЧ, перерахунок у базову при проведенні. 👤 ратифікувати перед кодом: (1) де база (Item.unit vs is_base-прапор); (2) зберігати чи рахуватиquantity_baseдля Batch/StockTransaction/FIFO; (3) ціна за обрану чи базову одиницю; (4) точність factor; (5) міграція наявних Item → авто base-рядок; (6) температурна корекція (VCF) для пального — поза MVP. - Скоуп (фази): (1) модель
ItemUnit+міграція+авто base-рядок; (2) ТЧ «Одиниці виміру» в картці номенклатури черезFormSubtableList(skillform-subtable, еталонItem.prices); (3) picker одиниці в документах + дефолт-підстановка + перерахунок у базову при проведенні; (4) звіти/друкформи (обрана + базова одиниця). - Ризик: усі inventory/posting-сервіси мають враховувати перерахунок у базову одиницю (FIFO/собівартість/звіти) — найбільший вузол консистентності.
- Тригер: потреба кількох одиниць (пальне т↔л; пакування шт/ящик/палета). Поки — одна одиниця на номенклатуру.
[ESS+ESWF] App Store add-ons track — хвости (груминг 2026-06-17)¶
Трек App Store add-ons landed інтерактивно 2026-06-09…06-11 (6 ADR: orders-as-addons, ar-driven-by-delivery, manufacturing-without-accounting, item-category-accounting-fields, payment-calendar-addon, group-mdm-addon), зафіксовано у done.md. Нові kernel/essentials-примітиви:
core.licensing.field_gates+essentials.services.addon_gating. Тести 71/71 зелені. Нижче — виявлені відкриті хвости.
- ✅
appGroupMdmcatalog-entry зроблено 2026-06-17 (commit): додано App Store-картку в frontend/dop/applications.ts (поля узгоджені з seed_shop-записом:plugin:group_mdm,requires:[appEssentials],forModules:[essentials],price:community, iconDeploymentUnitOutlined,status:availableбо not-installed-by-default). Десктопний App Store тепер показує/дає встановити Group MDM Hub. Diff seed_shop↔applications.ts чистий (0 розбіжностей),tsc --noEmit=0. Реальний install-стан backend-driven (installedSetз/active-plugins/), статичнийstatusвпливає лише на dev-фільтр — тожavailableкоректний без хибного лейбла. - ✅ Bulk-edit UI у
TransactionListзроблено 2026-06-17 (commit): переюзано genericBulkEditModal(metadata-driven field-picker + type-aware input черезbulk-updateendpoint) — кнопка «Масове редагування (N)» у toolbar-блоціselectedCount > 1поряд з bulk Post/Unpost. Нуль нового backend-коду (endpoint generic з bulk-ops треку).tsc=0, vitest 97/97. - 🟡 Browser-verify add-on-гейтів — Playwright не запускався по жодному з add-on'ів (orders/payment-calendar/group-mdm/manufacturing-no-accounting). За явним запитом —
webapp-testing.
[SHC] NIBULON-archetype gap pack — фіксація після research-сесії 2026-05-27¶
Research-сесія за архетипом НІБУЛОНа (commit master) виявила що agro_holding tenant покриває full cycle gatehouse → consolidation (24/26 етапів зеленим), але 6 документів і 2 vertical-модулі відсутні для повної реалістичності експорт-орієнтованого агрохолдингу.
Більшість пунктів пакета закрито (травень–червень 2026, → done.md): grain seed + sourcing optimizer, export-сертифікати, Charter Party + SOF, GrainContract, WarehouseReceipt, Lab Kanban,
agro_field,shipyard, settlement engine. Відкритим лишивсяRailFreightTariff(нижче).
[SHC] RailFreightTariff — модель ЖД-фрахту у shipcore_rail (C2 з optimizer de-hardcode) ✅ shipped 2026-06-06¶
- ✅ Відвантажено 2026-06-06 — ADR adr-2026-06-04-rail-freight-tariff (Implemented). Усі осі ратифіковані тоді ж і реалізовані на явний запит користувача (gate overridden). (c) C-rail —
RailFreightTariff(MasterDataModel)уshipcore_railбіля station/wagon master data;shipcore_pricingлишається observed/commercial шаром. (a) A3 — band-рядки[from,to)× wagon_type + optional station-pair override. (b) B1+B3 —effective_from+index_coefficientfield (re-index = одна колонка). (d) D1 — nullableSite.nearest_rail_station+Port.nearest_rail_station(migrations gatehouse/0021 + shipcore/0004); optimizer_rail_linehaulsoft-bridge (apps.is_installed + try/except) → tariff коли обидва кінці + рядок резолвляться, інакше graceful fallback наLOGISTIC_RAIL_*; external offers завжди константи (нема station-link). Resolutionservices/rail_tariff.resolve_rail_linehaul. EntityRegistryrailFreightTariffs+ frontend Rail → Tariffs (kernel-default form). 16 тестів зелені (8 resolution + 4 optimizer wiring + 4 pre-existing),manage.py check0 errors,tsc --noEmit0. Bundle: shipcore_rail/README § Rail freight tariff + logistic/lot-accumulation-optimizer. - Audit-first reframe (2 правки фактів): (1) беклог-вісь (c) «shipcore_rail vs core» застаріла — після цього запису приземлився
shipcore_pricing(P7, 2026-06-01) як окремий rating-движок → стала тривимірною; (2) УЗ-тариф ≠ західні lane-tables (регульована дистанційно-брекетна матриця), тож band-granularity чесніша за station-pair lane table (відкинутий A1). - Арх.бенефіт: оптимізатор закупівлі зерна (logistic) і майбутні rail-документи рахують ЖД-плече на хардкод-константах (
LOGISTIC_RAIL_*, settings-overridable per ADR adr-2026-06-03-optimizer-dehardcode вісь C3). Реальний UA ЖД-тариф — це матриця (станція-пара × рід вагона × відстань × держ-індексація УЗ), її не можна чесно звести до одногоContractSpecification(per_ton_km)рядка (відкинутий варіант C1 = фейкова точність). - Western ERP ref: TMS rail rating engines (MercuryGate / BluJay) — station-pair lane tables + fuel/index surcharge (caveat: моделюють негоційовані комерційні lane'и, не регульований УЗ тариф).
- Залишені shape-питання (a/b/d): ратифікувати рекомендації або звузити (band-only A2 / model+admin-first D3) — на момент implementation.
- Тригер: реальні rail-перевезення в demo/pilot або потреба точного landed-cost для rail-плеча. Поки — константи (свідомий demo borrow, не баг).
[ESS] Variants V4b extension — wire VariantPicker у решта line types 🟡 частково 2026-05-28¶
- Арх.бенефіт: uniformity completion — V4b 2026-05-21 закрило 4 high-traffic line types (Invoice/PurchaseInvoice/GoodsReceipt/GoodsShipment). Pattern уже встановлений у V4b commit.
- Shipped 2026-05-28: backend serializer denorm (
item_is_template+variant_sku+variant_attribute_signature) для 7 LineSerializer'ів (PurchaseOrder, GoodsWriteoff, InventoryCount, StockTransfer, ManufacturingInput, ManufacturingOutput, SalesOrder). Frontend variant-колонка (hasPlugin('essentials_variants')gate +item_is_templategate +VariantPicker, onChange→onUpdateRow) для 3 документів з кастомними subtables:PurchaseOrderSubtables,SalesOrderSubtables,ManufacturingSubtables(inputs+outputs, onChange→kind). - Audit-first reframe (2026-05-28): «8 решта» виявилось гетерогенним —
GoodsWriteoff/InventoryCount/StockTransferмаютьformType:'default';SalesReturnживе вsales_mobile_api(frontend = RN mobile-app). Тому backend denorm зроблено для всіх з FK variant, а frontend — лише для 3, що мають кастомні subtables. - ✅ (a) виконано 2026-06-04 — kernel-generic форми (GoodsWriteoff/InventoryCount/StockTransfer). Audit-first reframe #2: ці доки взагалі не рендерили рядків (відсутні в
LINES_CONFIG, без customcomponent:) — premise «generic inline-renderer» був хибний. Повний scope-B: (1) зареєстровано уLINES_CONFIG+LINE_REFERENCE_CONFIG→ kernelLinesTableрендерить рядки (закрито latent gapInventoryCountFromStock); (2) framework line-cell renderer registry (core/.../lineCellRegistry.ts) дозволяє@dop/essentialsінжектитиVariantPickerу genericLineCellбез core→dop імпорту; dop-бікVariants/registerVariantCell.tsx(side-effect import уmain.tsx). ADR adr-2026-06-04-line-cell-registry. tsc=0, 4 тести зелені. - Залишилось (🟡, mobile-only): (b) SalesReturn mobile — variant у RN-схемі (mobile schema bump, явно відкладено у моделі). (c)
SalesReturnLineSerializerbackend denorm — пропущено разом з (b), бо змінює mobile-sync схему. Решта desktop-форм покрита. - Western ERP ref: NetSuite Item Matrix uniformly applies to all transaction subforms.
[SHC] Product Variants Phase B — ShipCore грузообработка adapter (+ESS) ⏸️ 👤¶
- Арх.бенефіт: generalize-before-specialize (memory
feedback_generalize_before_specialize). V1-V5 universal landed без прокат-bias 2026-05-21. Phase B = специфічний adapter поверх universal: прокат-attributes (Метал/Профіль/Діаметр/ГОСТ/Партія), Variant UoM overrides (для прокату вага variant'а різна), прокат-specific forms (steel-mark selector, ГОСТ-номер lookup), інтеграція з ShipCorecargo_handlingdomain. - Western ERP ref: SAP Variant Configuration з industry-specific add-ons (Steel/Chemical/Pharma), Microsoft Dynamics Industry Solutions — convergent pattern «universal core + vertical adapters».
- Деталі: залежить від внутрішнього prerequisite — ShipCore Phase 6 (rail/road) повинен принести concrete
cargo_handlingdomain. Зараз domain поки шкаралупа. До тих пір — premature.
[ESWF] Dashboard S4c — Personal Launchpad customisation (Variant C) ✅ shipped 2026-06-04¶
- ✅ Shipped 2026-06-04 (frontend-only + 1 backend allowlist рядок,
tsc --noEmit=0). Per-user override розкладки дашборда: reorder (native drag + up/down) / resize (¼/½/full) / add (palette надGET /dashboard/widgets/) / remove + Save/Cancel/Reset + stale-default banner. Персистенс —User.ui_preferences.dashboardLayouts(keyed per layout) через наявний PATCH/auth/me/. Підключено Home (layoutKey="home") + config persona-дашборди (section:<itemCode>). Файли:editableLayout.ts,EditableDashboard.tsx,WidgetPalette.tsx,uiStore.ts,DashboardPage.tsx,SectionPage.tsx,auth.py. Bundle: dashboard-framework.md § S4c. - ✅ Shaped + ratified 2026-06-04 — ADR adr-2026-06-04-dashboard-s4c-personal-launchpad (Implemented). Осі: A3 hybrid (реалізовано нативним HTML5-drag + always-on up/down замість dnd-kit — той самий контракт без нової залежності); C1 explicit edit-mode + Save/Cancel; D1 baseHash + non-destructive stale-banner; E (team presets) deferred — лише per-user override у S4c.
- Audit-first reframe (2 хибні premise беклогу): (1) персистенс — це
User.ui_preferencesJSONField з уже наявним PATCH-ендпойнтом + allowlist (ALLOWED_PREFERENCE_KEYS), а НЕ новаUserPreferences.dashboard_layoutмодель → бекенд = +1 рядок, без моделі/міграції; (2) палітра «додати віджет» вже має ендпойнтGET /dashboard/widgets/(manifest) +dashboardApi.getWidgets(). Тобто scope — фронт-only на інфрі ~90% готовій; оцінка «~2-3 тижні» спиралась на неіснуючу модель. - Арх.бенефіт: завершує Dashboard Framework S4 vision (кастомізація Home). S1-S4b shipped 2026-05-09, S5-mini shipped 2026-05-22. S4c = remaining gap до feature-complete Personal Launchpad.
- Western ERP ref: SAP Fiori Launchpad Edit Mode / NetSuite Dashboard Personalize / Dynamics Saved Views / Salesforce Home Page Layouts.
- Scope cut: team-wide/admin-editable role-default presets (вісь E), міграція bespoke Gatehouse/CHub дашбордів, per-widget param-кастомізація — поза S4c.
[ESWF] Kernel import hygiene — інвертувати lazy product-імпорти в core-pkg ✅ shipped 2026-06-04 (request-path + seed-relocation)¶
- ✅ Request-path інверсія зроблено 2026-06-04 (commit). Єдиний справжній runtime-leak —
views/auth.py×2 (from shop.models import CustomerProfileу Google-login + register) — інвертовано через новийcore.signals.user_registered+shop/receivers.py(@receiverпід'єднано вShopConfig.ready()). Kernel auth більше не імпортує shop; на kernel-only send — no-op (раніше → ImportError). Перевірка:py_compile+django.setup()(усі ready() чисті, сигнал має 1 receiver) + leak-grep (auth.py зник зі списку). - Audit-first reframe «7 імпортів»:
print/registry.py:10— docstring, не імпорт;views/reports.py:56— уже graceful (Phase D L-C4: lazy+try/except→[]на kernel-only), не leak. Тобто runtime-leak був лише auth.py ×2 (закрито). - ✅ Seed-relocation зроблено 2026-06-04 (commit).
core-pkg/src/core/seeders/(~16.6K LoC / 45 файлів) + командиseed/seed_demo→ новий flat product-appbackend/demo_seed/(model-less, без міграцій, НЕ в skeleton/bootstrap INSTALLED_APPS). Усіcore.seeders→demo_seed.seeders(нуль stragglers). Refinement vs план: kernel seed-hook не потрібен — wholesale-перенос команд на product-side чистіший за registry. Заодно закрило 3seed_demoproduct-імпорти (тепер легітимні). Перевірка на dev Postgres: import-all 44 модулі + 2 команди,manage.py check(0 errors),seed --list, реальнийseed currencies units(5/12) у rolled-back транзакції. Пакетeswf-coreтепер несе нуль DOP-коду. - Чому НЕ folder-split: косметична
backend/dop/-симетрія відкинута (ADR adr-2026-06-04-backend-core-dop-symmetry O1) — кордон тримає пакет; реальна робота — import-гігієна.
[ESWF] Bulk operations — kernel primitive ✅ shipped 2026-06-09¶
- ✅ Shipped 2026-06-09 (interactive, ратифіковано scope «Delete + post/unpost»). ADR adr-2026-06-09-bulk-operations (Implemented). Backend:
BulkActionsMixin(core/views/bulk.py) вмонтований уTenantFilterMixin→ кожен per-module entity-viewset дістаєbulk-delete/bulk-deletion-mark(MasterData-only) /bulk-post/bulk-unpost(guard наpost_document) безкоштовно, нуль per-viewset churn. bulk-post re-invoke'ить наявний detail-action per id → той самий posting-gate (lock-date, approval) reused verbatim. Best-effort per-item{success, errors},MAX_BULK_IDS=500, tenant-scoped. Frontend: спільнийuseBulkActionхук;MasterDataCustomListfan-out N→1 (delete+deletion-mark);TransactionListнові Post/Unpost toolbar-кнопки (приselectedCount>1) + multi-delete. 9/9 нових + 8/8 approval regression, check=0, tsc=0, vitest 97/97. - Audit-first reframe: premise «green-field» наполовину хибний —
useListSelection(multi-row) +entitiesApi.bulkActionстаб + типізований{success,errors}контракт уже існували;MasterDataCustomListуже робив bulk через N-запитовий fan-out. Реальна робота — generic backend-mixin + фронт N→1, не «з нуля». - ✅ Continuation — поле bulk-edit зроблено 2026-06-09 (2-й commit): новий
bulk-updateaction (body{field, value}, значення через серіалізаторpartial=True→ type-coercion + FK + choices + posted-doc guard;_BULK_EDIT_FORBIDDEN={number,code,deletion_mark}+ read-only/unknown → 400 наперед). FrontendBulkEditModal(metadata-driven field-picker + type-aware input) через новий overflow-item «Масове редагування» у MasterDataListToolbar. +6 тестів (15/15 total), tsc=0, vitest 97/97. Bulk-edit у TransactionList — deferred (endpoint generic, UI тривіально додається). - Scope cut: per-bulk-action permission granularity не вводили (успадкування viewset-перм); не browser-verified (Playwright не запускався).
🌐 Application Federation Protocol (AFP) track — deferred Phase 4-7¶
Concept session 2026-05-17 закрито. Повна архітектура у planning/application-federation-protocol-2026-05-17.md: 7 фаз, 10 ADR. Phase 1-3 — у P1 черзі вище. Phase 4-7 deferred з legitimate non-client тригерами.
- [ ] AFP Phase 4 Shop↔T1 — реактивація коли архітектурно знадобиться separation
frontend/shopу окремий tenant від T1. Зараз shop живе в тому ж tenant — sync не потрібна. - [ ] AFP Phase 5 UA/MD→DE cross-server consolidation — реактивація коли DE-сервер фізично доступний (real external dependency).
- [ ] AFP Phase 6 MDM AI matching — реактивація коли обсяг unmapped entries у Group MDM перевищить ~1000 (data-driven тригер на основі Phase 3 metrics).
- [ ] AFP Phase 7 Drill-down UI на DE — архітектурно після Phase 5.
🚢 ShipCore implementation track¶
ShipCore concept session закрито 2026-05-12 (21 файл / ~85K слів / 25 ADR у dop/modules/vertical/shipcore/). Phase 4 backend renames + Phase 5
shipcore_seabackend + R-1..R-5 terminal decomposition + Variants V1-V5 CLOSED 2026-05-21. Phase 7 pricing concept + Phase 5 Day-9+ frontend shape — у P1 черзі вище.
Phase 5 ShipCore Sea — deferred deliverables (external/internal blockers)¶
- [ ] ⏸️ Phase 5 Day-7 — MaerskSpotAdapter real internals — DEFERRED, external dependency. Maersk Developer Portal sandbox credentials (~2 days self-service signup + customer-agreement step для production keys). Bait готовий: scaffold у
c815044, NotConfigured guard працює, 5 Protocol-методів raise NotImplementedError. Реактивація: Maersk creds у руках. Імплементація автономна після цього (DCSA Booking 2.0 + T&T 2.2 + VGM 1.0 + BL 3.0 mapping вже типізовано через 3 згенеровані pydantic families). - [ ] ⏸️ Phase 5 — TNT codegen — DEFERRED, internal small autonomous fix.
TNT_v2.2.0.yamlмає external$refURLs у dcsaorg/DCSA-domain, datamodel-code-generator ламається. Workaround:swagger-cli bundleперед regen. ~30 хв. Беремо коли потрібно T&T-events ingestion (real-time tracking). - [ ] ⏸️ Phase 5 — VGM codegen — DEFERRED, external mini-step. DCSA VGM 1.0 опубліковано Nov 2025 але YAML лише на SwaggerHub. Manual
SwaggerHub → Export → Download API → YAML Resolvedonce, drop уyaml/vgm_1.0.yaml. До того часуMaerskSpotAdapter.submit_vgmпрацює з hand-crafted dict (~10 полів).
External blockers (factual — non-client triggers)¶
- [ ] [SHC] Maersk Spot sandbox credentials — outreach + onboarding ~1-2 тижні. Потрібно для Phase 5 deliverable. Sandbox self-service, production вимагає customer agreement.
- [ ] [SHC] УЗ Cargo / ГІОЦ B2B-договір + NDA — onboarding 2-6 тижнів per shipcore/phase2-ua-integrations.md. Потрібно для Phase 6
shipcore_railintegration через АС Клієнт УЗ + е.Портал. - [ ] [SHC] Awery outreach (Tristan Koch CCO) — Phase 9+ avia partnership (Model C bidirectional ONE Record). Можна стартувати у Phase 4-5 calendar window паралельно з основним impl track.
- [ ] [SHC] Pilot Wave 1 UA road carrier — identify + agreement — outreach через AsMAP membership list / LinkedIn long-list. Узгодити з менеджментом обраного pilot per shipcore/phase1-ua-customers.md § Profile 2. Pilot outreach policy (memory
pilot_outreach_policy): не починаємо до коли product не demo-grade.
[SHC] ShipCore Stevedoring — MVP module — класифікація закрита 2026-07-04¶
- Класифікація закрита (ADR adr-2026-07-04-shipcore-stevedoring-mvp, на основі паралельного domain-research + code-audit): гібрид, не preset і не повністю ізольований модуль. Новий тонкий
shipcore_stevedoring(Gang,TallyRecord,CargoDamageSurvey,StevedoringTariff) + перевикористання без змінgatehouse/YardZone+YardInventory/shipcore_sea(CharterParty+StatementOfFacts+demurrage_service). - Знахідка при аудиті:
GateTransaction/StorageChargeLedgerдокументально позначені generic (ADRadr-2026-05-21-shipcore-terminal-decomposition.mdD-3), але фактично мають обов'язковий (non-nullable) FK наContainer— «генеральний термінал без контейнерів» зараз не працює технічно, попри що написано в доках. - Арх.бенефіт: перший реальний споживач уже написаних, але не підключених заготовок
CargoClassifiable/TransportUnit(thin core); другий консументshipcore_sealaytime/demurrage машини (підтверджує її cargo-agnostic природу). - Тригер: ready зараз — domain-research і code-audit зроблені, класифікація прийнята, скіл
new-module/custom-formзастосовні напряму без додаткового дослідження. - [x] 🤖 D-3 (передумова, окремо від модуля) — generic-ізація
GateTransaction/StorageChargeLedger:ContainerFK →null=True, blank=True, підключити вже написанийCargoClassifiablemixin. Механічно, обережна міграція (не чіпати існуючі рядки). ✅ виконано 2026-07-04: обидві моделі nullable + mixin підключено (is_dangerousна GateTransaction лишився локальним override — та ж колонка, без ALTER); guard уGateTransactionViewSet.post_document/unpost_documentнаcontainer is None(ContainerLedger лишається container-only); міграція0003_generic_cargo_classification(7×AddField + 2×AlterField); 4 точкові тестиshipcore_cargo_ops/tests/; докстрінги +docs/ai/domains/{shipcore_cargo_ops,shipcore}/оновлено. - [x] 🧭 R-1 —
shipcore_stevedoringMVP:Gang+StevedoringTariff(master data), backend+frontend за skillnew-module. ✅ виконано 2026-07-04: новий Django-застосунок (paid plugin module,price: subscription), 2 моделі + serializers/views/urls, рольstevedoring_supervisor,seed_shopPRODUCTS запис, frontendshipcoreStevedoringSection(Master Data group, 2 items) +applications.tsзапис +sections.tsреєстрація; 5 точкових тестів;docs/ai/domains/shipcore_stevedoring/README.mdстворено.tsc --noEmitчистий,dependencies.test.ts24/24,manage.py checkбез нових помилок. - [x] 🧭 R-2 —
TallyRecord+CargoDamageSurvey(transaction documents) за skillcustom-form. ✅ виконано 2026-07-04: обидві моделіTransactionModel+CargoClassifiable, опційний анкор наshipcore_sea.StatementOfFacts(судно) абоshipcore_cargo_ops.GateTransaction(суходіл) — без нового проміжного «operation»-заголовка (Open Question 1 закрито);CargoDamageSurvey— окрема модель (Open Question 2 закрито);photossubtable;PostedDocumentGuardMixinу серіалізаторах; post/unpost без бух. проводок (обидва — операційні записи, задокументовано в bundle); frontend —formType: defaultбез кастомного компонента (як GateTransaction); роль розширена; 10 нових тестів (15 разом з R-1). AI-чекпоінт розглянуто, не реалізовано (ідеї занотовано). - [x] 🧭 R-3 — App Store preset
appShipcoreStevedoring+docs/ai/domains/shipcore_stevedoring/домен-документація (spec-as-you-go). ✅ виконано 2026-07-04 — audit-closed, без нового коду: обидва deliverables вже зроблені побічно (AppEntry+applications.ts+sections.ts у R-1, домен-документація у R-1+R-2). Перевірено живим запитомAppEntry.objects.get(...)+ grep count у трьох файлах.appShipcoreStevedoring— НЕ preset у сенсіappContainerHub(0 власних моделей, лише композиція) — тонкий застосунок з 4 власними моделями і опційними nullable FK на shipcore_sea/shipcore_cargo_ops,requires: [appEssentials]коректний. Звірено з shipcore_grain/shipyard/agro_field — жоден не отримавdocs/dop/modules/vertical/shipcore/modules/*.md, стара planning-дерево не є живим спеком з Phase 6. ShipCore Stevedoring MVP (R-1/R-2/R-3) — завершено. - [x] 🧭 R-4 (рефакторинг) — злиття
shipcore_stevedoring→shipcore_cargo_opsпісля тесту користувачем. ✅ виконано 2026-07-05 (ADR adr-2026-07-05-stevedoring-merge-into-cargo-ops, суперсідить D-1 класифікацію 2026-07-04): усі 4 сутності — generic cargo-handling примітиви, не ніша-специфіка. State-only takeover (фізичніshipcore_stevedoring_*таблиці без змін, дані вижили на dry_port+agro_holding),StevedoringTariff→CargoHandlingTariff(entitycargoHandlingTariffs), URL →/api/v1/shipcore-cargo-ops/…, backend-app → migration-ghost,appShipcoreStevedoring→ composition preset (requires cargo_ops+sea — заодно чесно задекларовано міграційну залежність від sea, яку «тонкий» модуль мав недокументовано), секція → витрина зmodule: 'shipcoreCargoOps'overrides, help-статті переїхали в canonical cargo-ops дім. Звіти/роль/сідер переїхали. 28 backend-тестів + 106 frontend + tsc чисто. Пастка спіймана: nested-only viewset без EntityRegistry потребує явнийpermission_classes=[IsAuthenticated](HasRolePermission → 403). - [x] 🧭 R-5 (D-5 dissolve) — розчинення
shipcore_terminal+ ContainerHub як повний showcase-preset. ✅ виконано 2026-07-05 (ADR adr-2026-07-05-terminal-dissolve-into-cargo-ops, закриває D-5/R-4.1 з ADR 2026-05-21):Terminal+IntegrationConfig+EDIMessage→shipcore_cargo_ops,ShippingLine→shipcore_containers(sea її не використовує — FK-аудит; ADR-ескіз «→ sea» відхилено), stacking engine + контейнерні звіти → containers, integration hub + GateTransactionContract + seed-команди → cargo_ops. State-only takeover ×5 міграцій (autodetector сам розставив reverse-deps і зловив 2 пропущені FK — TrainManifest.shipping_line, RailwayWaybill.edi_message),shipcore_terminalіcontainerhub— migration-ghosts (containerhub тримає load-bearing URL-alias). Frontend:containerhubSection→ повна витрина за стиводорним патерном (Master Data / Documents / Registers / Analytics / Processes, module-overrides на shipcoreCargoOps/shipcoreContainers), cargo-ops секція отримала terminals+yard-процеси+integrationHub, containers — stackingOptimizer+demurrageTracker+shippingLines native.appContainerHub0.3.0. Fresh-DB 30 тестів + 11 суміжних + 106 frontend + tsc чисто. Scope cut: R-3.5 ContainerGateExtension — deferred як і був;core/integrationsframework — до другого споживача; ShippingLine↔Carrier уніфікація — [idea] у long-term-shipcore.
Календарні deadlines (regulatory — non-client triggers)¶
- ❌ 2026-06-01 — ICS2 Release 3 mandatory для PL/RO/SK/HR/LV — пропускаємо свідомо (v1 = UA-only per decisions § C7)
- ✅ 2027-01-01 — eTTN UA mandatory — Phase 4 deliverable, must hit (2 backends Vchasno + M.E.Doc)
- ⚠️ 2027-07-09 — eFTI mandatory для public authorities (EU Reg 2020/1056) — Phase 8 deliverable, ризик якщо Phase 4 ковзне >2 months
- 📅 2030 — DCSA eBL 100% adoption (9 carriers commit) — Phase 8 хаби (CargoX + WaveBL)
🎯 Окремі сесії (стартують за явним запитом користувача)¶
[ESWF] ESWF skeleton experiment — «порожня кімната» 👤¶
- Арх.бенефіт: гіпотеза-перевірка чи ESWF справді framework для швидкої розробки бізнес-додатків, чи маркетингове відмежування. Підхід переглянуто 2026-05-17 з sibling-папки на frontend monorepo split після 4 leak'ів сесії. Несуча конструкція для 4 запланованих ініціатив: module versioning, ShipCore Phase 4 refactor, update delivery (community vs enterprise), AFP Phase 1.
- Прийнята структура:
frontend/core/+frontend/dop/(22 модулі) +frontend/blank_demo/(другий продукт-консумер, sibling до dop/) +portal/+news/+shop/(окремі веб-сайти). Цільовий читач blueprint — 1С-ник без Python/JS у термінах довідник/документ/реквізит/табчастина. - План фаз A-D + 4 виявлені leak'и + 5 рішень по осях — planning/skeleton-experiment-2026-05-17.md. Backend (
backend/dop/симетрія?) — окрема сесія, відкладено до завершення frontend Phase A-C. Окрема сесія, не batch.
💡 Ідеї (raw, без пріоритету)¶
- AI-агент «Виписати рахунок» — Phase 1 (chat-trigger) ✅ shipped 2026-05-11. Деталі: dop/features/ai-invoice-agent.md. Phase 2 (email-trigger через IMAP+HMAC) — у backlog, deferred до спільної інфри з Agent Inbox.
- Agent Inbox — автономна розробка через AI-агента — лист клієнта з багом/фічею → AI-агент тріажить → Draft PR на worktree → Dev preview → approval власника → prod deploy. Реактивація: (а) період недоступності власника (army — real external constraint), або (б) природне продовження
core/federationPhase 1 — коли outbox/inbox primitive дасть baseline, Agent Inbox стає архітектурним extension цього паттерну.