| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| README.md | 2026-05-06 | 2.7 kB | |
| v2.9.17 -- Procurement-Finance commitments + CO budget delta + PO retry + tasks i18n source code.tar.gz | 2026-05-06 | 458.7 MB | |
| v2.9.17 -- Procurement-Finance commitments + CO budget delta + PO retry + tasks i18n source code.zip | 2026-05-06 | 460.4 MB | |
| Totals: 3 Items | 919.1 MB | 0 | |
Wires two missing publisher→subscriber connections that left financial dashboards lying. Plus a concurrency fix to PO numbering and a 9-locale backfill on the tasks importer.
Procurement → Finance commitment
procurement.po.issued was published in procurement/service.py but had zero subscribers — ProjectBudget.committed stayed permanently 0 even when POs totalled millions. New subscriber in finance/events.py opens an isolated session, picks the budget row by (project_id, wbs_id) against the PO's first line item (falls back to oldest budget by created_at), and increments committed.
procurement.gr.confirmed now flips committed → actual: decrements committed (clamped) and increments actual by the GR value. The GR publisher now actually computes the receipt amount as Σ(quantity_received × matched po_item.unit_rate) instead of emitting only the IDs.
Change Order → Budget delta line
Approved COs previously wrote cost_impact to the single string field project.budget_estimate; ProjectBudget was never touched and EVM BAC didn't reflect approved COs. Now changeorders/service._write_budget_delta_row keys off metadata_->>'change_order_id' == co.id, sets original_budget=0, revised_budget=delta, currency from co.currency → project default → EUR. Idempotent on re-approve. Wrapped in try/except so a budget-write failure logs a warning but doesn't roll back the CO approval.
PO numbering race
procurement/service.create_po previously picked MAX(po_number) LIKE 'PO-%' and inserted; concurrent creates collided with HTTP 500. Now mirrors the change-order retry pattern: 5 attempts, refetch MAX + retry on IntegrityError. Auto-generated numbers loop; user-supplied numbers bubble up as 409 instead of looping. Backed by a new (project_id, po_number) unique constraint; migration v2917_po_number_unique.py is inspector-guarded and de-duplicates pre-existing collisions before applying.
Tasks importer foreign-language headers
tasks/router.py:_TASK_COLUMN_MAP previously mapped only EN/DE Excel/CSV column headers. Russian "Название", Spanish "Título", Japanese "タイトル" etc. silently dropped on import. Added 81 entries across 9 locales (ru/fr/es/it/ja/zh/pt/nl/ko) for 8 target fields. Map grew 19 → 100. Lookup is case-insensitive; CJK headers match verbatim.
See [CHANGELOG.md](https://github.com/datadrivenconstruction/OpenConstructionERP/blob/v2.9.17/CHANGELOG.md#2917 for the full list.