Download Latest Version OpenConstructionERP_12.2.0_x64_en-US.msi (375.0 MB)
Email in envelope

Get an email when there's a new version of OpenConstructionERP

Home / v2.9.18
Name Modified Size InfoDownloads / Week
Parent folder
README.md 2026-05-07 3.0 kB
v2.9.18 -- 3-way invoice match + risk owner picker + Gantt resize source code.tar.gz 2026-05-07 458.7 MB
v2.9.18 -- 3-way invoice match + risk owner picker + Gantt resize source code.zip 2026-05-07 460.4 MB
Totals: 3 Items   919.1 MB 0

Three independent improvements landed in one batch.

Procurement — 3-way invoice match

create_invoice_from_po previously copied PO line items verbatim into Invoices with no link to confirmed Goods Receipts — overbilling unblocked. Now procurement/service._validate_3way_match sums quantity_received over confirmed-status GRs per po_item_id; for each proposed Invoice line it compares against received_qty and reports per-line violations. The route raises HTTP 422 with structured {message, errors: [...]} when invoice qty exceeds received qty.

force=true query param overrides for service-only POs (no goods to physically receive). When set, the invoice persists with metadata_['force_3way_match'] = True and a structured logger.warning is emitted with {po_id, user_id, violations} for audit aggregation.

A no_confirmed_grs sentinel reason is returned when a PO has zero confirmed GRs and the invoice has any positive qty — lets the FE render the precise "service-only PO? pass force=true" message.

All 19 existing procurement tests still pass.

Risk — owner picker + assignment notification

Risk.owner_user_id (nullable FK to oe_users_user, indexed, ON DELETE SET NULL) replaces the free-text owner_name for new rows; legacy data still renders via the fallback. Migration v2918_risk_owner_user_id.py is inspector-guarded (uses batch_alter_table for SQLite compatibility).

risk.assigned event now fires on new or changed owner. New subscriber _on_risk_assigned in notifications/events.py produces an in-app notification "You were assigned risk {{code}}: {{title}}".

Frontend swapped the free-text input for the existing UserSearchInput (avatar + name + role + email search). On selection, owner_user_id is sent as UUID and the resolved display name mirrored into owner_name so the list-row column keeps working uniformly across picker-sourced and legacy rows.

Schedule — Gantt drag-to-resize

Two transparent 7 px ew-resize zones (left + right) on every standard task bar; left handle moves start_date, right handle moves end_date. Snap-to-day via the existing pxToDate; clamped to keep end_date >= start_date + 1 day. Group and milestone bars are unaffected. Esc cancels the in-flight resize and reverts the preview.

onActivityResize?: (id, newStart, newEnd) => void prop added to GanttChart. Existing onActivityMove (drag-to-translate) untouched; resize is purely additive.

SchedulePage wires it through React Query: new resizeActivity mutation calls scheduleApi.updateActivity(id, {start_date, end_date}), optimistically updates the cached ['gantt', schedule.id] payload, snapshots and reverts on error.

See [CHANGELOG.md](https://github.com/datadrivenconstruction/OpenConstructionERP/blob/v2.9.18/CHANGELOG.md#2918 for the full list.

Source: README.md, updated 2026-05-07