Skip to content

Releases: OneTwo3D/IMS

v2.0.0 — Tax model, accounting sync, manufacturing-aware reorder

12 Jun 19:14
e487595

Choose a tag to compare

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, every BomItem.componentProductId adds (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 with reorderQty = 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 sales when daily demand > 0, BOM <parent SKU> for each contributing parent BOM (sorted + deduped), Stock policy fallback when neither applies.
  • One-click "Generate POs + draft MOs for visible rows" toolbar on the Reorder Planning page. Splits the visible rows by productType and routes purchased rows to createReorderPOs (one draft PO per supplier) and manufactured rows to createReorderMOs (one draft ProductionOrder per BOM product, copying manufacturerId + warehouseId from the product's latest MO, picking the most recently-updated active Bom as parent). Operator scopes via the existing filter form.
  • Replenishment CSV export gains the neededFor column (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. TaxRate becomes a full tax profile: ordered TaxRateComponent rows for compound or multi-element taxes (e.g. Canada GST + PST compounding to 12.35%), a reverseCharge flag, an isCompound flag, and a reportingCategory (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 (accountingInvoiceId present) enqueues a SALES_INVOICE_UPDATE instead 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 logs sales_invoice_update_skipped_unsupported_connector WARNING instead.
  • Purchase bill edits sync back to Xero. Unpaid bills can be edited from the PO detail page; saving content changes enqueues PURCHASE_INVOICE_UPDATE with 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.reverseCharge is true now post to Xero / QBO with the line's taxType swapped 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 parent accountingTaxType.
  • IMS → Xero TaxRate sync with TaxComponents. Multi-component IMS TaxRate rows now auto-sync to Xero via POST /TaxRates with the matching TaxComponents payload (idempotent by Name) so the VAT return shows the component breakdown without operator hand-configuration. Gated by xero_sync_tax_rate setting. QBO has no equivalent API; the trigger logs tax_rate_sync_skipped_unsupported_connector instead.
  • Rejected accounting-update alerts. Failed SALES_INVOICE_UPDATE and PURCHASE_INVOICE_UPDATE sync 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-encoded X-IMS-Export-Metadata response 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=redis with REDIS_URL for 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_LIMIT eligible 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 schedule GET /api/cron/account-balance-snapshot daily, 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_DIR stores branding/avatar assets. Local defaults
    preserve the previous ./uploads and ./public/uploads behavior, 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 default disabled mode preserves existing upload behavior.
  • Decimal boundary guard. Added npm run check:decimal-boundaries and wired it into npm run validate plus GitHub Actions so guarded domain/accounting paths must document any direct decimalToNumber import with a decimal-boundary-ok: rationale. The leading rationale token is now a closed vocabulary, and current legacy-pre-stage-4 annotations 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...
Read more