Releases: OneTwo3D/IMS
Releases · OneTwo3D/IMS
v2.0.0 — Tax model, accounting sync, manufacturing-aware reorder
Reorder Planning & manufacturing (cycle PRs #188–#190)
- Reorder report covers manufactured products end-to-end. BOM-typed products no longer show "Unassigned" in the supplier column; they display Manufactured by [name] using the manufacturer set on the product's most recent
ProductionOrder, or Manufactured in-house when none has been set. - Raw materials inherit demand from BOM parents that need replenishment. For each BOM row whose own
suggestedReorderQty > 0, everyBomItem.componentProductIdadds(bomSuggestedQty × bomItem.qty)to its component's reorder point. Raw materials used by multiple BOMs aggregate demand across all parents; raw materials that are also sellable add BOM demand on top of their sales-driven demand. Products withreorderQty = 0(explicit opt-out) stay out of the report unless a BOM is driving demand into them. - New "Needed for" column lists the demand sources per row:
Direct saleswhen daily demand > 0,BOM <parent SKU>for each contributing parent BOM (sorted + deduped),Stock policyfallback when neither applies. - One-click "Generate POs + draft MOs for visible rows" toolbar on the Reorder Planning page. Splits the visible rows by
productTypeand routes purchased rows tocreateReorderPOs(one draft PO per supplier) and manufactured rows tocreateReorderMOs(one draftProductionOrderper BOM product, copyingmanufacturerId+warehouseIdfrom the product's latest MO, picking the most recently-updated activeBomas parent). Operator scopes via the existing filter form. - Replenishment CSV export gains the
neededForcolumn (semicolon-joined). Consumers with pinned column maps need updating. - Analytics report description + methodology notices fold into a single (i) tooltip next to each report title. The paragraph below the title and the amber notices box lower on the page are gone; the same content shows on hover or keyboard focus, and the data table sits ~24px higher on every report page.
Tax & accounting (cycle PRs #169–#186)
- Tax rate profiles.
TaxRatebecomes a full tax profile: orderedTaxRateComponentrows for compound or multi-element taxes (e.g. Canada GST + PST compounding to 12.35%), areverseChargeflag, anisCompoundflag, and areportingCategory(DOMESTIC/REVERSE_CHARGE/EC_SALES/OSS). The single effective rate is still snapshotted on each document line so historical documents stay stable; the profile metadata drives connector mapping and VAT reporting. - VAT analytics report groups by reporting category. A 20% UK domestic sale and a 20% EU OSS sale now appear as separate rows. A new Reporting category dropdown filter scopes the page to one category for OSS or reverse-charge return preparation; the filter round-trips through the URL so CSV exports and pagination links preserve it.
- Sales invoice edit syncs back to Xero. Editing a sales order that has already pushed to Xero (
accountingInvoiceIdpresent) enqueues aSALES_INVOICE_UPDATEinstead of silently dropping the change. The payload uses the same builder as the create path; a payload-derived idempotency key prevents duplicates on unchanged re-saves. QuickBooks does not support invoice updates and logssales_invoice_update_skipped_unsupported_connectorWARNING instead. - Purchase bill edits sync back to Xero. Unpaid bills can be edited from the PO detail page; saving content changes enqueues
PURCHASE_INVOICE_UPDATEwith the same idempotency-key semantics as the sales-side flow. Editability is re-checked inside the transaction to handle concurrent payment correctly. - Reverse-charge accounting tax-type swap. Lines whose
TaxRate.reverseChargeis true now post to Xero / QBO with the line'staxTypeswapped to a configurable code (accounting_reverse_charge_sales_tax_type, typical Xero:ECOUTPUTSERVICES;accounting_reverse_charge_purchase_tax_type, typical Xero:REVERSECHARGES) so the VAT return classifies them on box 1 / box 8. Empty settings preserve existing posting via the parentaccountingTaxType. - IMS → Xero TaxRate sync with
TaxComponents. Multi-component IMSTaxRaterows now auto-sync to Xero viaPOST /TaxRateswith the matchingTaxComponentspayload (idempotent byName) so the VAT return shows the component breakdown without operator hand-configuration. Gated byxero_sync_tax_ratesetting. QBO has no equivalent API; the trigger logstax_rate_sync_skipped_unsupported_connectorinstead. - Rejected accounting-update alerts. Failed
SALES_INVOICE_UPDATEandPURCHASE_INVOICE_UPDATEsync rows surface as an amber alert on the related sales-order or purchase-order detail page with connector, timestamp, retry count, and a safely truncated error message. The raw sync payload is never displayed. - PO FX rebase preserves consistency. Currency- or rate-only edits on a DRAFT purchase order recompute every persisted base amount (header + lines + freight cost lines) inside a single transaction. If any subsequent step fails (tax resolution, validation), the entire rebase rolls back — lines never drift away from the parent header.
Developer-facing
- Integration settings now require a successful connection test before enablement. Xero, WooCommerce, Mintsoft, and SMTP connection tests persist their latest result, timestamp, and configuration fingerprint in the settings store. Newly enabling sync features, activating Mintsoft, or saving SMTP transport settings now fails until the current connection settings have passed their test.
- Report CSV exports no longer repeat report-level metadata on each data row. Stock-position, inventory-ledger, and inventory-costing exports move fields such as
asOf,source,generatedAt, date windows, and opening/closing totals out of per-row schemas. The CSV file now keeps metadata once in trailing#comment rows, while API clients can read the same data from the base64url-encodedX-IMS-Export-Metadataresponse header. Consumers with hardcoded column maps should update them to the new row schemas and read metadata from the comment rows or decoded header. - Cron endpoint rate limits now include high-frequency headroom and source-IP scoping. Daily/hourly jobs default to one accepted run per hour, 5-minute jobs allow 15 runs per hour, and 15-minute jobs allow 6 runs per hour so normal scheduling jitter is not denied. Multi-replica deployments must use
RATE_LIMIT_BACKEND=rediswithREDIS_URLfor cluster-wide login/TOTP and cron throttles. - High-volume Xero daily batch journals can split into multiple entries per day. Tenants posting more than
XERO_DAILY_BATCH_LIMITeligible orders or shipments in a daily-batch group receive multiple Xero journals for the same business date. References include deterministic hash suffixes, and reconciliation should sum entries by the shared date/group prefix or payload metadata (batchDate,batchGroup,batchReferenceId). - Account-balance snapshots now have a daily cron dependency. Installations
should scheduleGET /api/cron/account-balance-snapshotdaily, before
accounting reports are reviewed, so inventory and COGS GL variance reports
have previous-day Xero Trial Balance snapshots available. The production
rollout readiness check treats a missing or stale run as a blocker. - Upload storage roots are now environment-configured.
UPLOAD_STORAGE_DIR
stores private uploads such as supplier invoice PDFs and
PUBLIC_UPLOAD_STORAGE_DIRstores branding/avatar assets. Local defaults
preserve the previous./uploadsand./public/uploadsbehavior, while
production logs a warning if either root is unset. Avatar URLs keep the
historical/uploads/avatars/*shape; branding uploads now rotate filenames
instead of relying on query-string-only cache busting. - Invoice PDF uploads can be scanned before storage.
FILE_SCAN_MODE=command
writes invoice PDFs to quarantine, runs a configured scanner command, and
moves only clean files into final upload storage. Scanner processes receive an
explicit environment allowlist, rejected quarantine files are deleted for disk
hygiene, and audit metadata records scan status without scanner output or file
paths. The defaultdisabledmode preserves existing upload behavior. - Decimal boundary guard. Added
npm run check:decimal-boundariesand wired it intonpm run validateplus GitHub Actions so guarded domain/accounting paths must document any directdecimalToNumberimport with adecimal-boundary-ok:rationale. The leading rationale token is now a closed vocabulary, and currentlegacy-pre-stage-4annotations mark Decimal conversion follow-up work planned for Stage 4. Developers rebasing older branches that touch guarded paths may need to add or narrow these comments before validation passes. - Integration outbox payload registry. WooCommerce stock-sync, Xero accounting-post, and Mintsoft booked-in outbox operations now have registered Zod payload schemas. Registered payloads are validated and normalized on enqueue and again by connector processors before execution, while unknown outbox operations remain backwards-compatible for existing rows and future connectors.
- Integration outbox retry backoff. Retryable IntegrationOutbox failures now use configurable exponential backoff with tail jitter (
OUTBOX_RETRY_BASE_MS,OUTBOX_RETRY_MAX_MS,OUTBOX_RETRY_JITTER_MS) instead of the fixed default delay. WooCommerce stock-sync retries now follow the shared 5/10/20/40/60-minute capped curve, extending the 12-attempt time-to-permanent-failure from roughly 6.5 hours to roughly 9.25 hours before jitter. Connector-specific explicit retry delays, such as rate-limit backoff, remain supported. - Inventory invariant SQL collector. Production inventory invariant reports now use a SQL-backed collector with cursor pagination, product/warehouse/severity filters, and bounded cron defa...