@openlup/platform 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +327 -269
- package/README.md +123 -12
- package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +3 -2
- package/dist/communications/contracts/subscriptionPaymentExpired.js +1 -1
- package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +4 -3
- package/dist/communications/contracts/subscriptionPaymentFailed.js +4 -5
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/composition/subscriptionContribution.d.ts +18 -0
- package/dist/composition/subscriptionContribution.js +19 -0
- package/dist/fulfillment/contracts/dispatch.d.ts +4 -0
- package/dist/fulfillment/dispatchProjection.d.ts +1 -1
- package/dist/fulfillment/dispatchProjection.js +4 -2
- package/dist/fulfillment/omnipack/client.js +14 -6
- package/dist/fulfillment/omnipack/index.js +5 -2
- package/dist/fulfillment/providerDispatchWorker.d.ts +38 -0
- package/dist/fulfillment/providerDispatchWorker.js +19 -1
- package/dist/inventory/index.d.ts +2 -0
- package/dist/inventory/index.js +1 -0
- package/dist/inventory/postgresStockAvailability.d.ts +24 -0
- package/dist/inventory/postgresStockAvailability.js +48 -0
- package/dist/invoicing/accountingCanonicalProviderLines.d.ts +5 -0
- package/dist/invoicing/accountingCanonicalProviderLines.js +104 -0
- package/dist/invoicing/accountingLineAllocation.d.ts +2 -0
- package/dist/invoicing/accountingLineAllocation.js +69 -0
- package/dist/invoicing/adapterConformance.d.ts +42 -0
- package/dist/invoicing/adapterConformance.js +296 -0
- package/dist/invoicing/artifacts.d.ts +7 -0
- package/dist/invoicing/artifacts.js +20 -0
- package/dist/invoicing/bindings.d.ts +46 -0
- package/dist/invoicing/bindings.js +69 -0
- package/dist/invoicing/channelPolicy.d.ts +28 -0
- package/dist/invoicing/channelPolicy.js +27 -0
- package/dist/invoicing/continuity.d.ts +12 -0
- package/dist/invoicing/continuity.js +18 -0
- package/dist/invoicing/contracts/facts.d.ts +11 -1
- package/dist/invoicing/contracts/host.d.ts +3 -4
- package/dist/invoicing/contracts/index.d.ts +1 -1
- package/dist/invoicing/contracts/store.d.ts +15 -5
- package/dist/invoicing/contribution.d.ts +21 -0
- package/dist/invoicing/contribution.js +19 -0
- package/dist/invoicing/correction.d.ts +42 -0
- package/dist/invoicing/correction.js +44 -0
- package/dist/invoicing/delivery.d.ts +85 -0
- package/dist/invoicing/delivery.js +79 -0
- package/dist/invoicing/documentHistory.d.ts +50 -0
- package/dist/invoicing/documentHistory.js +213 -0
- package/dist/invoicing/documentSync.d.ts +24 -0
- package/dist/invoicing/documentSync.js +26 -0
- package/dist/invoicing/dutyCollector.d.ts +15 -0
- package/dist/invoicing/dutyCollector.js +35 -0
- package/dist/invoicing/fulfillmentTrigger.d.ts +56 -0
- package/dist/invoicing/fulfillmentTrigger.js +90 -0
- package/dist/invoicing/handoff.d.ts +37 -0
- package/dist/invoicing/handoff.js +62 -0
- package/dist/invoicing/index.d.ts +38 -0
- package/dist/invoicing/index.js +36 -0
- package/dist/invoicing/issue.d.ts +39 -0
- package/dist/invoicing/issue.js +55 -0
- package/dist/invoicing/migrations.d.ts +48 -0
- package/dist/invoicing/migrations.js +25 -0
- package/dist/invoicing/nativeUnsupported.d.ts +9 -0
- package/dist/invoicing/nativeUnsupported.js +22 -0
- package/dist/invoicing/paymentReadback.d.ts +20 -0
- package/dist/invoicing/paymentReadback.js +47 -0
- package/dist/invoicing/postgresInvoiceStore.d.ts +55 -0
- package/dist/invoicing/postgresInvoiceStore.js +344 -0
- package/dist/invoicing/readModel.d.ts +74 -0
- package/dist/invoicing/readModel.js +90 -0
- package/dist/invoicing/readback.d.ts +4 -0
- package/dist/invoicing/readback.js +30 -0
- package/dist/invoicing/reversal.d.ts +37 -0
- package/dist/invoicing/reversal.js +28 -0
- package/dist/invoicing/sellerRevision.d.ts +19 -0
- package/dist/invoicing/sellerRevision.js +14 -0
- package/dist/invoicing/settlementSync.d.ts +18 -0
- package/dist/invoicing/settlementSync.js +32 -0
- package/dist/invoicing/snapshot.d.ts +52 -0
- package/dist/invoicing/snapshot.js +141 -0
- package/dist/subscriptions/commandExamples.d.ts +6 -0
- package/dist/subscriptions/commandExamples.js +20 -0
- package/dist/subscriptions/contracts/commands.d.ts +8 -1
- package/dist/subscriptions/contracts/index.d.ts +2 -2
- package/dist/subscriptions/contracts/stockWait.d.ts +12 -1
- package/dist/subscriptions/index.d.ts +22 -4
- package/dist/subscriptions/index.js +22 -3
- package/dist/subscriptions/lifecycleMessages.d.ts +9 -0
- package/dist/subscriptions/lifecycleMessages.js +38 -0
- package/dist/subscriptions/paymentOutcomes.js +1 -1
- package/dist/subscriptions/postgresCommandStore.d.ts +44 -0
- package/dist/subscriptions/postgresCommandStore.js +172 -0
- package/dist/subscriptions/postgresRetryStore.d.ts +6 -0
- package/dist/subscriptions/postgresRetryStore.js +11 -0
- package/dist/subscriptions/postgresStockWaitStore.d.ts +62 -0
- package/dist/subscriptions/postgresStockWaitStore.js +174 -0
- package/dist/subscriptions/services.d.ts +31 -0
- package/dist/subscriptions/services.js +15 -0
- package/package.json +3 -3
- package/release-gates.json +21 -7
- package/sql/20260809210000_accounting_document_rail.sql +889 -0
- package/sql/20260811150000_accounting_payout_rail.sql +333 -0
- package/sql/20260812130000_accounting_document_lifecycle_rail.sql +460 -0
- package/sql/20261022190000_invoicing_durable_duties.sql +494 -0
- package/sql/20261022191000_invoicing_document_effects.sql +249 -0
- package/sql/20261022192000_invoicing_payment_decoupling.sql +144 -0
- package/sql/20261022195000_invoicing_seller_revision.sql +65 -0
- package/sql/20261024170000_subscription_command_lifecycle.sql +404 -0
- package/sql/20261024180000_subscription_stock_wait.sql +330 -0
- package/sql/20261024183000_inventory_available_to_promise.sql +70 -0
- package/sql/20261024184000_subscription_renewal_reserve_uses_seam.sql +147 -0
- package/sql/20261024184500_subscription_retry_late_positive_stock.sql +59 -0
- package/sql/20261024184600_subscription_retry_candidate_payment_filter.sql +22 -0
- package/src/communications/contracts/subscriptionPaymentExpired.ts +4 -3
- package/src/communications/contracts/subscriptionPaymentFailed.ts +8 -8
- package/src/composition/index.ts +1 -0
- package/src/composition/subscriptionContribution.ts +25 -0
- package/src/fulfillment/contracts/dispatch.ts +4 -0
- package/src/fulfillment/dispatchProjection.ts +4 -2
- package/src/fulfillment/omnipack/client.ts +13 -6
- package/src/fulfillment/omnipack/index.ts +5 -2
- package/src/fulfillment/providerDispatchWorker.ts +14 -1
- package/src/inventory/index.ts +2 -0
- package/src/inventory/postgresStockAvailability.ts +54 -0
- package/src/invoicing/accountingCanonicalProviderLines.ts +134 -0
- package/src/invoicing/accountingLineAllocation.ts +69 -0
- package/src/invoicing/adapterConformance.ts +249 -0
- package/src/invoicing/artifacts.ts +19 -0
- package/src/invoicing/bindings.ts +80 -0
- package/src/invoicing/channelPolicy.ts +50 -0
- package/src/invoicing/continuity.ts +15 -0
- package/src/invoicing/contracts/facts.ts +12 -1
- package/src/invoicing/contracts/host.ts +3 -4
- package/src/invoicing/contracts/index.ts +1 -1
- package/src/invoicing/contracts/store.ts +11 -7
- package/src/invoicing/contribution.ts +28 -0
- package/src/invoicing/correction.ts +56 -0
- package/src/invoicing/delivery.ts +122 -0
- package/src/invoicing/documentHistory.ts +301 -0
- package/src/invoicing/documentSync.ts +51 -0
- package/src/invoicing/dutyCollector.ts +31 -0
- package/src/invoicing/fulfillmentTrigger.ts +144 -0
- package/src/invoicing/handoff.ts +71 -0
- package/src/invoicing/index.ts +38 -0
- package/src/invoicing/issue.ts +64 -0
- package/src/invoicing/migrations.ts +25 -0
- package/src/invoicing/nativeUnsupported.ts +24 -0
- package/src/invoicing/paymentReadback.ts +70 -0
- package/src/invoicing/postgresInvoiceStore.ts +334 -0
- package/src/invoicing/readModel.ts +80 -0
- package/src/invoicing/readback.ts +26 -0
- package/src/invoicing/reversal.ts +34 -0
- package/src/invoicing/sellerRevision.ts +20 -0
- package/src/invoicing/settlementSync.ts +37 -0
- package/src/invoicing/snapshot.ts +192 -0
- package/src/subscriptions/commandExamples.ts +19 -0
- package/src/subscriptions/contracts/commands.ts +35 -2
- package/src/subscriptions/contracts/index.ts +2 -2
- package/src/subscriptions/contracts/stockWait.ts +14 -2
- package/src/subscriptions/index.ts +30 -3
- package/src/subscriptions/lifecycleMessages.ts +37 -0
- package/src/subscriptions/paymentOutcomes.ts +1 -1
- package/src/subscriptions/postgresCommandStore.ts +183 -0
- package/src/subscriptions/postgresRetryStore.ts +11 -0
- package/src/subscriptions/postgresStockWaitStore.ts +170 -0
- package/src/subscriptions/services.ts +21 -0
package/README.md
CHANGED
|
@@ -1,25 +1,51 @@
|
|
|
1
1
|
# @openlup/platform
|
|
2
2
|
|
|
3
|
+
Invoicing exports explicit `createInvoiceServices`, `InvoiceServices` and inert
|
|
4
|
+
`createInvoiceContribution` through `/invoicing`, with no new composition entry.
|
|
5
|
+
`claimInvoiceSellerRevision` reads an injected selection before claim; the pinned
|
|
6
|
+
SQL wrappers persist it for eligible obligations. `InvoiceSellerRevision` is also
|
|
7
|
+
available from the type-only contracts entry. Legacy obligations keep fallback.
|
|
8
|
+
|
|
3
9
|
Development preview. This package is part of the publishable set with core and outbox; preparation does not establish a registry release or a stable API. Its repository-directory publish command refuses publication; the authorized package-release workflow owns publication.
|
|
4
10
|
|
|
5
11
|
It holds the runnable server substrate an OpenLup application composes: the Node HTTP runtime, BFF dispatch, scheduling and job leases, blob storage, data gateways, the node-postgres migration runner and bundle composition. Ports and their contracts stay in `@openlup/core/platform-runtime`.
|
|
6
12
|
|
|
7
|
-
For the prepared `0.
|
|
8
|
-
limitations](CHANGELOG.md#
|
|
9
|
-
Install the complete native chain through position
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
For the prepared `0.25.0` set, read the [adjacent-release migration and known
|
|
14
|
+
limitations](CHANGELOG.md#0250) before selecting optional services.
|
|
15
|
+
Install the complete native chain through position 86, including N2 commands,
|
|
16
|
+
N3 stock wait, the ATP seam/allocator and late-payment/retry-selection forwards;
|
|
17
|
+
sparse SQL projections are not an install chain. Native runtime roles need the
|
|
18
|
+
ATP/exposure EXECUTE and underlying SELECT grants described in the release notes.
|
|
19
|
+
Managed hosts apply the complete prefix through `20261024185100` for stock-wait
|
|
20
|
+
and refund-evidence functions. Preserve captured obligations and receipts on rollback.
|
|
21
|
+
M10 subscription services and contributions remain **EXPERIMENTAL and DEFAULT-OFF**.
|
|
22
|
+
Hosts explicitly supply readiness, execution, commands, mandatory G23 admission,
|
|
23
|
+
a population lease and M04 delivery. Factories activate no serving work. The inert
|
|
24
|
+
native reference composes renewal, dunning and stock wait; managed TypeScript
|
|
25
|
+
stock recovery, native multi-cycle prepaid and M07 prepared-attempt-store
|
|
26
|
+
capabilities retain their named unsupported boundaries. Follow the older native-row
|
|
27
|
+
migration precautions when upgrading `0.22.0` data.
|
|
28
|
+
M09 invoicing remains **EXPERIMENTAL and DEFAULT-OFF**, available for explicit
|
|
29
|
+
provider/capability selection and ready host composition, including issue,
|
|
30
|
+
correction, independent duties, delivery/read-side and seller revision helpers.
|
|
31
|
+
Adopters must not activate unselected or unready invoicing; installation and
|
|
32
|
+
inert contributions register no runner, worker, route or schedule.
|
|
16
33
|
M06 managed-host proof classes remain pending in the
|
|
17
34
|
[M06 parity ledger](../../docs/platform/plans/oss-m06-stock-reservations-delivery.md#parity-pending-rows).
|
|
18
|
-
Size comparison uses the published `0.
|
|
35
|
+
Size comparison uses the published `0.24.0` tarball. Preparation and offline
|
|
19
36
|
adapter evidence grant no publication or provider-account certification.
|
|
20
37
|
|
|
21
38
|
Longer module recipes live in the [platform package reference](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md).
|
|
22
39
|
|
|
40
|
+
The prepared set requires Zod `^4.6.6` for GHSA-mrhx-c7xc-m3w5.
|
|
41
|
+
Public quote requests retain their collection limits and valid-input behavior;
|
|
42
|
+
oversized collections now fail with the size error before accumulating invalid
|
|
43
|
+
element errors. Package API and installed-consumer checks remain required.
|
|
44
|
+
The observation adapter explicitly retains minute-precision timestamps
|
|
45
|
+
alongside seconds/fractions, compact offsets and local timestamps. Calendar
|
|
46
|
+
validation still rejects impossible dates; validation never rewrites the raw
|
|
47
|
+
timestamp used in the webhook's idempotency key.
|
|
48
|
+
|
|
23
49
|
## Package surface
|
|
24
50
|
|
|
25
51
|
`release-gates.json` declares kind `rail`: this foundation provides runtime infrastructure over core ports, matching the architecture's rail responsibility. Its experimental entries each bind a declaration snapshot and package-owned smoke tests. The API checker reads this same inventory.
|
|
@@ -45,7 +71,7 @@ Longer module recipes live in the [platform package reference](../../docs/platfo
|
|
|
45
71
|
| `./payments` | Experimental prepared-attempt ordering with injected persistence, execution and failure policy; contracts at `./payments/contracts`. |
|
|
46
72
|
| `./fulfillment`, `./fulfillment/contracts` | [Experimental fulfillment services, contracts and status canon](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#fulfillment-contracts). |
|
|
47
73
|
| `./subscriptions`, `./subscriptions/contracts` | Default-off, incomplete; [experimental subscription contracts and renewal port](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#subscription-contracts). |
|
|
48
|
-
| `./invoicing`, `./invoicing/contracts` | Default-off
|
|
74
|
+
| `./invoicing`, `./invoicing/contracts` | Default-off invoicing contracts and independent duty collection; no provider activation. |
|
|
49
75
|
| `./inventory` | Experimental schemas/ports, handlers, native stores, projection, injected forecast and SQL descriptor; `./inventory/contracts` DTOs. |
|
|
50
76
|
| `./catalog` | Experimental neutral catalog schemas, drafts, reads, media source packs and publication ports |
|
|
51
77
|
| `./catalog/contracts` | Dependency-free catalog DTOs, media resolver, authority and canonicalization helpers |
|
|
@@ -703,6 +729,30 @@ The initial binding preserves case-before-outcome ordering with atomic bookkeepi
|
|
|
703
729
|
N1's same-obligation recovery repairs the native decline stall (F16); it does not
|
|
704
730
|
supply M07 prepared-store parity or activate a runner.
|
|
705
731
|
|
|
732
|
+
With N1 then N2 installed, `createPostgresSubscriptionCommandStore` and
|
|
733
|
+
`createPostgresSubscriptionCommands` support native generic bundle commands and
|
|
734
|
+
command-bound receipts. Preview persists a quote; apply atomically updates future
|
|
735
|
+
terms, event and receipt with no proration or current-cycle repricing. Hosts own
|
|
736
|
+
authorization, address validation, SKU/composition translation and the transaction
|
|
737
|
+
connection. Prepaid remains unsupported; the serving adapter refuses pause.
|
|
738
|
+
|
|
739
|
+
Native N3 plus the ATP seam/allocator and X/Y suffix provide
|
|
740
|
+
`createPostgresStockWaitStore` and `createPostgresStockAvailability` (the latter
|
|
741
|
+
at `./inventory`). Hosts supply transactions, clock, source and the existing
|
|
742
|
+
wire codec. Candidate discovery preserves uncaptured frozen operations; shortage
|
|
743
|
+
creates a durable wait, and elapsed stock-only refusals may reacquire on the same
|
|
744
|
+
identity with a new claim generation. Resumed means paid realization admitted.
|
|
745
|
+
The fixed 14-day soft priority is distinct from durable advance reservations.
|
|
746
|
+
The unbound descriptor keeps stock recovery unavailable. The opt-in native M10
|
|
747
|
+
reference composition selects it after composed PostgreSQL proof; managed
|
|
748
|
+
TypeScript recovery remains unavailable.
|
|
749
|
+
|
|
750
|
+
`createSubscriptionServices`, lifecycle-message and neutral command facades are
|
|
751
|
+
in `./subscriptions`; `createSubscriptionContribution` is in `./composition`.
|
|
752
|
+
They are default-off and describe one host executor/job. The
|
|
753
|
+
[composed module recipe](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#composed-subscription-module)
|
|
754
|
+
owns explicit selection, frozen mandate/replay, M04 delivery and M12 boundaries.
|
|
755
|
+
|
|
706
756
|
## Invoicing contracts
|
|
707
757
|
|
|
708
758
|
Experimental `@openlup/platform/invoicing` exposes `readInvoiceCapabilities`;
|
|
@@ -710,4 +760,65 @@ Experimental `@openlup/platform/invoicing` exposes `readInvoiceCapabilities`;
|
|
|
710
760
|
Frozen facts, correction recovery, exact absence, owed duties and host-only fiscal,
|
|
711
761
|
artifact, delivery, payout and owned-history seams are described in the
|
|
712
762
|
[invoicing reference](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#invoicing-contracts).
|
|
713
|
-
|
|
763
|
+
The contract entry supplies no executor or activation.
|
|
764
|
+
`createNativeUnsupportedInvoicePorts` supplies inert typed refusals for every
|
|
765
|
+
host, store and provider seam. `runInvoiceAdapterConformance` checks adapter-owned
|
|
766
|
+
offline fixtures; its sanitized evidence does not establish live fiscal readiness.
|
|
767
|
+
|
|
768
|
+
Issue helpers in `invoicing` preserve claim/preflight/projection/fresh-money/
|
|
769
|
+
create/fenced-finalize order. Hosts bind compatibility payloads, seller/date/tax
|
|
770
|
+
policy, captions, event descriptors and key scopes. Canonical and legacy line
|
|
771
|
+
checks share the unchanged donor allocation; handoff helpers register no job or
|
|
772
|
+
executor. Eight issue/line/payment/handoff cases join the existing package smoke
|
|
773
|
+
selection. Continuity cutover and activation remain separate.
|
|
774
|
+
|
|
775
|
+
Correction and reversal workers preserve durable identity, recovery and fenced
|
|
776
|
+
outcomes through host callbacks. The reference host requires a conforming
|
|
777
|
+
correction bridge; the legacy vendor method stays unselected. Reversal handlers pass
|
|
778
|
+
through the RPC action (`voided`, `correction_requested`, `corrected`, `no_invoice`). The required package
|
|
779
|
+
selection includes correction/reversal witnesses in `test/invoicing/contracts.test.ts`.
|
|
780
|
+
See the [invoicing design](../../docs/platform/plans/oss-m09-invoicing.md#p3-correction-and-reversal-boundary)
|
|
781
|
+
for managed migration and reconciliation limits.
|
|
782
|
+
|
|
783
|
+
See the [M09 forward owner](../../docs/platform/DATA_AND_MIGRATIONS.md#m09-invoicing-forward-design-and-control-approval) for the registered managed-correction limit and follow-up.
|
|
784
|
+
|
|
785
|
+
`createPostgresInvoiceStore` uses a caller-owned structural executor for native
|
|
786
|
+
owed duties, split fact leases, claims and fenced issue/correction results.
|
|
787
|
+
Missing host facts refuse before writes; clearance remains host work. Delivery
|
|
788
|
+
requires an explicit host envelope projection and M04 transport. `INVOICING_MIGRATIONS` separates native child-document and managed
|
|
789
|
+
base-row correction storage. Install the complete native catalogue, not its
|
|
790
|
+
seven sparse byte-identical SQL assets. The local-only Postgres adapter consumes
|
|
791
|
+
this store without claiming fiscal parity or registering a worker. The
|
|
792
|
+
[invoicing architecture owner](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#invoicing-contracts)
|
|
793
|
+
describes reversal recovery, capture faults and cutover fencing.
|
|
794
|
+
`collectInvoiceDuties` and `createIndependentInvoiceDutyBinding` collect retained
|
|
795
|
+
paid/handoff duties through separately committed store phases. Hosts invoke them
|
|
796
|
+
outside payment and fulfillment work; missing facts remain owed. The pinned
|
|
797
|
+
payment-decoupling SQL is a sparse catalogue projection, requiring the full P4
|
|
798
|
+
native chain. See the architecture owner for continuity and activation limits.
|
|
799
|
+
|
|
800
|
+
`runNativeInvoiceIssueJob` supplies the native create/readback binding over
|
|
801
|
+
explicit local/fresh money and provider ports. Fresh non-ready results settle
|
|
802
|
+
before returning; stale results write no failure. No scheduler or default
|
|
803
|
+
composition is registered. Operator DDL/ACL limits of the unchanged M07 readback
|
|
804
|
+
remain registered in the architecture owner.
|
|
805
|
+
|
|
806
|
+
For bounded duty discovery before facts exist, including cursor continuation past
|
|
807
|
+
unavailable facts, follow the [invoicing architecture owner](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#invoicing-contracts).
|
|
808
|
+
|
|
809
|
+
The invoicing read side adds `runInvoiceDeliveryJob`, `assertInvoicePdf`,
|
|
810
|
+
`invoicePdfFilename`, `syncAcceptedInvoiceDocuments`,
|
|
811
|
+
`syncInvoicePayoutSettlements`, `resolveInvoiceDocumentHistory` and
|
|
812
|
+
`createInvoiceReadModel`. Hosts supply authorization, storage, M04 transport,
|
|
813
|
+
terminal/backoff callbacks or the native store, and safe native history projection. See the
|
|
814
|
+
[read-helper recipe](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#invoicing-read-helpers).
|
|
815
|
+
|
|
816
|
+
`createInvoiceServices` and `createInvoiceContribution` compose the existing
|
|
817
|
+
helpers through `/invoicing`, with explicit selection, default-off and readiness
|
|
818
|
+
before execution. Native collection, issue, delivery, document/payout sync and
|
|
819
|
+
history use their module implementations; host facades may bind factories where
|
|
820
|
+
transport or query projection is host-owned. No executor is installed.
|
|
821
|
+
`claimInvoiceSellerRevision` selects a seller before leasing. The optional native
|
|
822
|
+
store seller binding and managed wrappers persist its first-ever claim revision;
|
|
823
|
+
legacy fallback and forward-only migration limits are owned by the
|
|
824
|
+
[M09 migration section](../../docs/platform/DATA_AND_MIGRATIONS.md#m09-invoicing-forward-design-and-control-approval).
|
|
@@ -14,8 +14,9 @@ export interface SubscriptionPaymentExpiredEmailVars {
|
|
|
14
14
|
*/
|
|
15
15
|
cause?: PaymentFailureCustomerCause | null;
|
|
16
16
|
/**
|
|
17
|
-
* Recovery-link URL for the case's
|
|
18
|
-
*
|
|
17
|
+
* Recovery-link URL for the case's payment-recovery token. Current exhaustion
|
|
18
|
+
* uses `repair_payment`; legacy expired cases may carry `resume_subscription`.
|
|
19
|
+
* When present the template renders the recovery CTA; otherwise it keeps the support-led
|
|
19
20
|
* wording, because a button with no destination is worse than no button.
|
|
20
21
|
*/
|
|
21
22
|
recoveryUrl?: string | null;
|
|
@@ -8,7 +8,7 @@ export function createSubscriptionPaymentExpiredContentFactory(input) {
|
|
|
8
8
|
const causeSentence = subscriptionPaymentCauseSentence(locale, vars.cause ?? "unknown");
|
|
9
9
|
const detail = [
|
|
10
10
|
// Leads the box for the same reason as in the failed notice: it answers the
|
|
11
|
-
// question the
|
|
11
|
+
// question the unsuccessful payment raises, before the figures.
|
|
12
12
|
...(causeSentence ? [causeSentence] : []),
|
|
13
13
|
...(recoveryUrl
|
|
14
14
|
? [
|
|
@@ -12,9 +12,10 @@ export interface SubscriptionPaymentFailedEmailVars {
|
|
|
12
12
|
recoveryUrl?: string | null;
|
|
13
13
|
/**
|
|
14
14
|
* Pre-formatted calendar day of the scheduled next charge, in the merchant's
|
|
15
|
-
* zone (e.g. "11.08.2026").
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* zone (e.g. "11.08.2026"). Present only when a retry is actually scheduled:
|
|
16
|
+
* a first-rung hard decline selects this template with no next retry, and
|
|
17
|
+
* `retryScheduled: false` renders the no-retry copy. null/absent keeps the
|
|
18
|
+
* sentence without a date rather than printing a placeholder date.
|
|
18
19
|
*/
|
|
19
20
|
nextRetryDateLabel?: string | null;
|
|
20
21
|
/** Card scheme as stored on the method's consent snapshot, e.g. "visa". */
|
|
@@ -14,11 +14,10 @@ export function createSubscriptionPaymentFailedContentFactory(input) {
|
|
|
14
14
|
// also why it is PREPENDED and never appended — the retry sentence must stay
|
|
15
15
|
// last (see below).
|
|
16
16
|
const causeSentence = subscriptionPaymentCauseSentence(locale, vars.cause ?? "unknown");
|
|
17
|
-
// Only an explicit `false` turns the promises off.
|
|
18
|
-
//
|
|
19
|
-
// must
|
|
20
|
-
//
|
|
21
|
-
// cancel a subscription that was about to recover on its own.
|
|
17
|
+
// Only an explicit `false` turns the retry promises off. Absence preserves
|
|
18
|
+
// the legacy rendering default; it does not prove a retry is scheduled.
|
|
19
|
+
// Producers must pass the actual schedule fact when retries have stopped,
|
|
20
|
+
// including class termination or exhaustion.
|
|
22
21
|
const retryScheduled = vars.retryScheduled !== false;
|
|
23
22
|
const detail = [
|
|
24
23
|
...(causeSentence ? [causeSentence] : []),
|
|
@@ -5,3 +5,4 @@ export { createCatalogContribution, type CatalogContributionInput } from "./cata
|
|
|
5
5
|
export { createInventoryContribution } from "./inventoryContribution.js";
|
|
6
6
|
export { createShippingContribution } from "./shippingContribution.js";
|
|
7
7
|
export { createFulfillmentContribution } from "./fulfillmentContribution.js";
|
|
8
|
+
export { createSubscriptionContribution } from "./subscriptionContribution.js";
|
|
@@ -6,3 +6,4 @@ export { createCatalogContribution } from "./catalogContribution.js";
|
|
|
6
6
|
export { createInventoryContribution } from "./inventoryContribution.js";
|
|
7
7
|
export { createShippingContribution } from "./shippingContribution.js";
|
|
8
8
|
export { createFulfillmentContribution } from "./fulfillmentContribution.js";
|
|
9
|
+
export { createSubscriptionContribution } from "./subscriptionContribution.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
|
|
2
|
+
/** One selected subscription job; no second scheduler or automatic registration. */
|
|
3
|
+
export declare function createSubscriptionContribution(input: {
|
|
4
|
+
version: string;
|
|
5
|
+
requiredSchema: RequiredSchema;
|
|
6
|
+
run: (() => Promise<unknown>) | null;
|
|
7
|
+
cadenceSeconds: number;
|
|
8
|
+
}): {
|
|
9
|
+
contributions: Contribution[];
|
|
10
|
+
ports: {
|
|
11
|
+
"@openlup/platform/subscriptions:run": () => Promise<unknown>;
|
|
12
|
+
} | {
|
|
13
|
+
"@openlup/platform/subscriptions:run"?: undefined;
|
|
14
|
+
};
|
|
15
|
+
triggers: {
|
|
16
|
+
[k: string]: () => Promise<unknown>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** One selected subscription job; no second scheduler or automatic registration. */
|
|
2
|
+
export function createSubscriptionContribution(input) {
|
|
3
|
+
if (!Number.isFinite(input.cadenceSeconds) || input.cadenceSeconds <= 0)
|
|
4
|
+
throw new Error("subscription_cadence_invalid");
|
|
5
|
+
const schedules = input.run ? [{ id: "subscriptions-renewal", cadenceSeconds: input.cadenceSeconds, run: input.run }] : [];
|
|
6
|
+
const contribution = {
|
|
7
|
+
manifest: {
|
|
8
|
+
name: "@openlup/platform", version: input.version, kind: "rail",
|
|
9
|
+
module: { id: "subscriptions", kind: "capability", provides: ["./subscriptions", "./subscriptions/contracts"], dependsOn: [] },
|
|
10
|
+
emits: [], handles: [], schedules: schedules.map(({ id, cadenceSeconds }) => ({ id, cadenceSeconds })),
|
|
11
|
+
env: [], requiredPorts: ["run"], routes: [], requiredSchema: input.requiredSchema,
|
|
12
|
+
}, handlers: [], schedules, routes: [],
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
contributions: [contribution],
|
|
16
|
+
ports: input.run ? { "@openlup/platform/subscriptions:run": input.run } : {},
|
|
17
|
+
triggers: Object.fromEntries(schedules.map(schedule => [`@openlup/platform/subscriptions:${schedule.id}`, schedule.run])),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -90,6 +90,10 @@ export interface FulfillmentOperationError {
|
|
|
90
90
|
status: number | null;
|
|
91
91
|
retryable: boolean;
|
|
92
92
|
mayHaveSucceeded: boolean;
|
|
93
|
+
/** Whether provider submission was attempted; absent means unknown, never proven false. */
|
|
94
|
+
submissionAttempted?: boolean;
|
|
95
|
+
/** Whether the HTTP response body was read, including empty or malformed JSON; absent means no body-read evidence. */
|
|
96
|
+
responseBodyRead?: boolean;
|
|
93
97
|
/** Machine cause only; never an arbitrary exception message. */
|
|
94
98
|
cause?: string;
|
|
95
99
|
}
|
|
@@ -19,7 +19,7 @@ export declare function durableStrings(priorCount?: number): {
|
|
|
19
19
|
/** Copy only the durable allowlist, including when an adapter object has extra runtime properties. */
|
|
20
20
|
export declare function dispatchEvidence(evidence: DispatchEvidence): DispatchEvidence;
|
|
21
21
|
/** Shadow has a local command identity; it never invokes a provider or resolves credentials. */
|
|
22
|
-
export declare function shadowDispatchEvidence(request: FulfillmentDispatchRequest): DispatchEvidence;
|
|
22
|
+
export declare function shadowDispatchEvidence(request: FulfillmentDispatchRequest, commandFingerprint?: string): DispatchEvidence;
|
|
23
23
|
export declare function dispatchResult(result: FulfillmentDispatchResult): FulfillmentDispatchResult;
|
|
24
24
|
export declare function acceptedProviderProof(record: DispatchRecord | null): record is DispatchRecord & {
|
|
25
25
|
externalRef: string;
|
|
@@ -22,6 +22,8 @@ export function operationError(error) {
|
|
|
22
22
|
const cause = typeof error.cause === "string" ? privacy.token(error.cause, value => /^ack(?::[A-Za-z0-9_]{1,96}){0,2}$/u.test(value) ? value.slice(0, 180) : null) : null;
|
|
23
23
|
return { code, status: Number.isInteger(error.status) && error.status >= 100 && error.status <= 599 ? error.status : null,
|
|
24
24
|
retryable: error.retryable === true, mayHaveSucceeded: error.mayHaveSucceeded === true,
|
|
25
|
+
...(typeof error.submissionAttempted === "boolean" ? { submissionAttempted: error.submissionAttempted } : {}),
|
|
26
|
+
...(typeof error.responseBodyRead === "boolean" ? { responseBodyRead: error.responseBodyRead } : {}),
|
|
25
27
|
...(cause ? { cause } : {}), ...(privacy.redactedFieldCount ? { redactedFieldCount: privacy.redactedFieldCount } : {}) };
|
|
26
28
|
}
|
|
27
29
|
// The fulfillment opaque-ID rule; never accept free-form metadata as a reference.
|
|
@@ -78,10 +80,10 @@ export function dispatchEvidence(evidence) {
|
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
/** Shadow has a local command identity; it never invokes a provider or resolves credentials. */
|
|
81
|
-
export function shadowDispatchEvidence(request) {
|
|
83
|
+
export function shadowDispatchEvidence(request, commandFingerprint) {
|
|
82
84
|
if (!request.selection.providerRef || !request.lines.length || request.lines.some(line => !Number.isSafeInteger(line.quantity) || line.quantity <= 0))
|
|
83
85
|
throw new Error("dispatch_shadow_command_invalid");
|
|
84
|
-
return dispatchEvidence({ commandFingerprint: createHash("sha256").update(JSON.stringify(request)).digest("hex"),
|
|
86
|
+
return dispatchEvidence({ commandFingerprint: commandFingerprint ?? createHash("sha256").update(JSON.stringify(request)).digest("hex"),
|
|
85
87
|
contactRevision: request.contactRevision, providerRef: request.selection.providerRef,
|
|
86
88
|
carrierRef: request.selection.carrierRef, serviceRef: request.selection.serviceRef,
|
|
87
89
|
transcript: { itemCount: request.lines.length, items: request.lines.map(line => ({ skuRef: line.skuRef, quantity: line.quantity,
|
|
@@ -58,24 +58,32 @@ async function requestJson(fetchImpl, config, operation, path, init, refuseRedir
|
|
|
58
58
|
let lastError = null;
|
|
59
59
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
60
60
|
let submitted = false;
|
|
61
|
+
let responseBodyRead;
|
|
62
|
+
const dispatchFacts = () => operation === "createOrder" ? { submissionAttempted: submitted,
|
|
63
|
+
...(responseBodyRead !== undefined ? { responseBodyRead } : {}) } : {};
|
|
61
64
|
try {
|
|
62
65
|
const base = new URL(config.baseUrl);
|
|
63
66
|
const url = new URL(path, base);
|
|
64
67
|
if (url.origin !== base.origin || url.username || url.password || url.protocol !== "https:")
|
|
65
68
|
throw new Error("Invalid fulfillment path");
|
|
66
69
|
const response = await fetchWithTimeout(fetchImpl, url, config, init, () => { submitted = true; });
|
|
70
|
+
responseBodyRead = false;
|
|
71
|
+
const body = await readJsonBody(response).then(value => { responseBodyRead = true; return value; }).catch(error => {
|
|
72
|
+
if (response.ok)
|
|
73
|
+
throw error;
|
|
74
|
+
return null;
|
|
75
|
+
});
|
|
67
76
|
if (response.ok)
|
|
68
|
-
return
|
|
77
|
+
return body;
|
|
69
78
|
const retryable = method === "GET" && isRetryableStatus(response.status);
|
|
70
79
|
// A failed body read cannot erase the rejection status already received.
|
|
71
|
-
|
|
72
|
-
lastError = new OmnipackProviderError({
|
|
80
|
+
lastError = Object.assign(new OmnipackProviderError({
|
|
73
81
|
operation,
|
|
74
82
|
status: response.status,
|
|
75
83
|
code: refuseRedirectRetry && response.status >= 300 && response.status < 400 ? "redirect_refused" : readProviderErrorCode(body) ?? `http_${response.status}`,
|
|
76
84
|
retryable,
|
|
77
85
|
mayHaveSucceeded: mayPostHaveSucceeded(method, response.status),
|
|
78
|
-
});
|
|
86
|
+
}), dispatchFacts());
|
|
79
87
|
if (!retryable || attempt === attempts)
|
|
80
88
|
throw lastError;
|
|
81
89
|
}
|
|
@@ -83,13 +91,13 @@ async function requestJson(fetchImpl, config, operation, path, init, refuseRedir
|
|
|
83
91
|
const retryable = method === "GET" && attempt < attempts;
|
|
84
92
|
lastError = submitted && error instanceof OmnipackProviderError
|
|
85
93
|
? error
|
|
86
|
-
: new OmnipackProviderError({
|
|
94
|
+
: Object.assign(new OmnipackProviderError({
|
|
87
95
|
operation,
|
|
88
96
|
status: null,
|
|
89
97
|
code: diagnoseRedirectFailure && isRedirectRefusal(error) ? "redirect_refused" : "network_error",
|
|
90
98
|
retryable,
|
|
91
99
|
mayHaveSucceeded: submitted && mayPostHaveSucceeded(method, null),
|
|
92
|
-
});
|
|
100
|
+
}), dispatchFacts());
|
|
93
101
|
if (!retryable || (refuseRedirectRetry && lastError.code === "redirect_refused"))
|
|
94
102
|
throw lastError;
|
|
95
103
|
}
|
|
@@ -242,10 +242,13 @@ export function createOmnipackFulfillmentAdapter(config) {
|
|
|
242
242
|
return { kind: "refused", refusal: refusal("unsupported_event", "normalize") };
|
|
243
243
|
const original = payload;
|
|
244
244
|
// Validate calendar fields here only; keep compact offsets and local
|
|
245
|
-
// timestamps accepted by the base parser,
|
|
245
|
+
// timestamps and minute precision accepted by the base parser,
|
|
246
|
+
// without rewriting key bytes.
|
|
246
247
|
if (original.occurredAt !== undefined && original.occurredAt !== null
|
|
247
248
|
&& (typeof original.occurredAt !== "string" || !timestamp(original.occurredAt)
|
|
248
|
-
|| !z.iso.datetime({ offset: true, local: true })
|
|
249
|
+
|| !z.iso.datetime({ offset: true, local: true })
|
|
250
|
+
.or(z.iso.datetime({ offset: true, local: true, precision: -1 }))
|
|
251
|
+
.safeParse(original.occurredAt.replace(/([+-]\d{2})(\d{2})$/u, "$1:$2")).success))
|
|
249
252
|
return { kind: "refused", refusal: refusal("malformed", "normalize"), diagnostics: { invalidTimestamp: true } };
|
|
250
253
|
const rejectedCorrelationRefs = [original.orderId, original.providerOrderId, original.orderNumber, original.fulfilmentNumber]
|
|
251
254
|
.filter(value => value !== undefined && value !== null && (typeof value !== "string" || !correlationReference(value, config.observationCodecs))).length;
|
|
@@ -16,8 +16,46 @@ export declare function runFulfillmentDispatchWorker(input: {
|
|
|
16
16
|
mode: DispatchRecord["mode"];
|
|
17
17
|
batchLimit: number;
|
|
18
18
|
};
|
|
19
|
+
/** Pure local shadow fingerprint: a lowercase 64-hex SHA-256 digest of the adopter's canonical command bytes. Invalid output or a throw refuses before evidence writes. Never return raw command/PII. Used across shadow claim/readback/attempt; shadow never begins, and live preparation must produce the same identity. */
|
|
20
|
+
shadowCommandFingerprint?: (request: FulfillmentDispatchRequest) => string;
|
|
19
21
|
now?: () => Date;
|
|
20
22
|
}): Promise<{
|
|
23
|
+
redactedFieldCount: number;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
failures: number;
|
|
26
|
+
reason: "adopter_fingerprint_invalid" | "adopter_fingerprint_failed";
|
|
27
|
+
refusal: {
|
|
28
|
+
reason: "malformed";
|
|
29
|
+
phase: "prepare";
|
|
30
|
+
retryable: false;
|
|
31
|
+
cause: "adopter_fingerprint_invalid" | "adopter_fingerprint_failed";
|
|
32
|
+
};
|
|
33
|
+
skipped: boolean;
|
|
34
|
+
checked: number;
|
|
35
|
+
updated: number;
|
|
36
|
+
mode: "shadow" | "stage" | "live";
|
|
37
|
+
replayed: number;
|
|
38
|
+
providerCalls: number;
|
|
39
|
+
readBacks: number;
|
|
40
|
+
} | {
|
|
41
|
+
redactedFieldCount?: undefined;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
failures: number;
|
|
44
|
+
reason: "adopter_fingerprint_invalid" | "adopter_fingerprint_failed";
|
|
45
|
+
refusal: {
|
|
46
|
+
reason: "malformed";
|
|
47
|
+
phase: "prepare";
|
|
48
|
+
retryable: false;
|
|
49
|
+
cause: "adopter_fingerprint_invalid" | "adopter_fingerprint_failed";
|
|
50
|
+
};
|
|
51
|
+
skipped: boolean;
|
|
52
|
+
checked: number;
|
|
53
|
+
updated: number;
|
|
54
|
+
mode: "shadow" | "stage" | "live";
|
|
55
|
+
replayed: number;
|
|
56
|
+
providerCalls: number;
|
|
57
|
+
readBacks: number;
|
|
58
|
+
} | {
|
|
21
59
|
redactedFieldCount: number;
|
|
22
60
|
ok: boolean;
|
|
23
61
|
skipped: boolean;
|
|
@@ -53,8 +53,26 @@ export async function runFulfillmentDispatchWorker(input) {
|
|
|
53
53
|
for (let remapCount = 0;; remapCount += 1) {
|
|
54
54
|
const request = { ...mappedCandidate, requestKey };
|
|
55
55
|
stage = "dispatch_command_prepare_failed";
|
|
56
|
+
let shadowFingerprint;
|
|
57
|
+
if (input.config.mode === "shadow" && input.shadowCommandFingerprint) {
|
|
58
|
+
let value;
|
|
59
|
+
let cause;
|
|
60
|
+
try {
|
|
61
|
+
value = input.shadowCommandFingerprint(request);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
cause = "adopter_fingerprint_failed";
|
|
65
|
+
}
|
|
66
|
+
if (!cause && typeof value === "string" && /^[a-f0-9]{64}$/.test(value))
|
|
67
|
+
shadowFingerprint = value;
|
|
68
|
+
else
|
|
69
|
+
cause ??= "adopter_fingerprint_invalid";
|
|
70
|
+
if (cause)
|
|
71
|
+
return { ...result, ok: false, failures: result.failures + 1, reason: cause,
|
|
72
|
+
refusal: { reason: "malformed", phase: "prepare", retryable: false, cause }, ...diagnostics() };
|
|
73
|
+
}
|
|
56
74
|
const prepared = input.config.mode === "shadow"
|
|
57
|
-
? { kind: "prepared", evidence: shadowDispatchEvidence(request) }
|
|
75
|
+
? { kind: "prepared", evidence: shadowDispatchEvidence(request, shadowFingerprint) }
|
|
58
76
|
: await input.provider.prepare(request);
|
|
59
77
|
if (prepared.kind === "refused") {
|
|
60
78
|
result.ok = false;
|
|
@@ -12,3 +12,5 @@ export { forecastSubscriptionInventoryShortages, buildSubscriptionInventoryForec
|
|
|
12
12
|
export { calculateSubscriptionForecastSkus, calculateSubscriptionForecastTotals } from "./subscriptionForecastCalculation.js";
|
|
13
13
|
export { INVENTORY_MIGRATIONS } from "./sql.js";
|
|
14
14
|
export { createInventoryBindings, type InventoryBindingDependencies, type InventoryBindings } from "./bindings.js";
|
|
15
|
+
export { createPostgresStockAvailability } from "./postgresStockAvailability.js";
|
|
16
|
+
export type { NativeStockPromise } from "./postgresStockAvailability.js";
|
package/dist/inventory/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { forecastSubscriptionInventoryShortages, buildSubscriptionInventoryForec
|
|
|
9
9
|
export { calculateSubscriptionForecastSkus, calculateSubscriptionForecastTotals } from "./subscriptionForecastCalculation.js";
|
|
10
10
|
export { INVENTORY_MIGRATIONS } from "./sql.js";
|
|
11
11
|
export { createInventoryBindings } from "./bindings.js";
|
|
12
|
+
export { createPostgresStockAvailability } from "./postgresStockAvailability.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NewPurchaseStockReadPort } from "./newPurchaseStockAvailability.js";
|
|
2
|
+
interface StockQueryExecutor {
|
|
3
|
+
query(text: string, values?: unknown[]): Promise<{
|
|
4
|
+
rows: Record<string, unknown>[];
|
|
5
|
+
}>;
|
|
6
|
+
}
|
|
7
|
+
export interface NativeStockPromise {
|
|
8
|
+
availableQuantity: number;
|
|
9
|
+
forSaleQuantity: number;
|
|
10
|
+
hardHoldQuantity: number;
|
|
11
|
+
safetyQuantity: number;
|
|
12
|
+
softSubscriptionDemandQuantity: number;
|
|
13
|
+
channelExposureQuantity: number;
|
|
14
|
+
}
|
|
15
|
+
/** Native read-only ATP. The fixed 14-day priority cannot be waived by a new
|
|
16
|
+
* purchase caller. Only the fenced native SQL allocator uses the renewal exemption. */
|
|
17
|
+
export declare function createPostgresStockAvailability(executor: StockQueryExecutor, options: {
|
|
18
|
+
sourceKey: string;
|
|
19
|
+
now: () => string;
|
|
20
|
+
}): {
|
|
21
|
+
readPort: NewPurchaseStockReadPort;
|
|
22
|
+
availableToPromise(sku: string, asOf: string): Promise<NativeStockPromise>;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function object(value) {
|
|
2
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3
|
+
throw new Error("inventory_stock_response_invalid");
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
function quantity(value) {
|
|
7
|
+
if ((typeof value !== "number" && typeof value !== "string") || String(value).trim() === "")
|
|
8
|
+
throw new Error("inventory_stock_quantity_invalid");
|
|
9
|
+
const n = Number(value);
|
|
10
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
11
|
+
throw new Error("inventory_stock_quantity_invalid");
|
|
12
|
+
return n;
|
|
13
|
+
}
|
|
14
|
+
/** Native read-only ATP. The fixed 14-day priority cannot be waived by a new
|
|
15
|
+
* purchase caller. Only the fenced native SQL allocator uses the renewal exemption. */
|
|
16
|
+
export function createPostgresStockAvailability(executor, options) {
|
|
17
|
+
return {
|
|
18
|
+
async availableToPromise(sku, asOf) {
|
|
19
|
+
const row = object((await executor.query(`SELECT public.inventory_available_to_promise(
|
|
20
|
+
$1::text,$2::text,$3::timestamptz,false) AS response`, [options.sourceKey, sku, asOf])).rows[0]?.response);
|
|
21
|
+
return { availableQuantity: quantity(row.available_quantity), forSaleQuantity: quantity(row.for_sale_quantity),
|
|
22
|
+
hardHoldQuantity: quantity(row.hard_hold_quantity), safetyQuantity: quantity(row.safety_quantity),
|
|
23
|
+
softSubscriptionDemandQuantity: quantity(row.soft_subscription_demand_quantity), channelExposureQuantity: quantity(row.channel_exposure_quantity) };
|
|
24
|
+
},
|
|
25
|
+
readPort: {
|
|
26
|
+
async read(input) {
|
|
27
|
+
if (input.horizonDays !== 14 || input.excludeOrderId !== null)
|
|
28
|
+
throw new Error("native_stock_read_options_unsupported");
|
|
29
|
+
if (!input.skus.length)
|
|
30
|
+
return new Map();
|
|
31
|
+
const value = (await executor.query(`SELECT public.inventory_new_purchase_stock_read(
|
|
32
|
+
$1::text,$2::jsonb,$3::timestamptz) AS response`, [options.sourceKey, JSON.stringify(input.skus), options.now()])).rows[0]?.response;
|
|
33
|
+
if (!Array.isArray(value))
|
|
34
|
+
throw new Error("inventory_stock_response_invalid");
|
|
35
|
+
return new Map(value.map(value => {
|
|
36
|
+
const row = object(value);
|
|
37
|
+
if (typeof row.sku !== "string" || !["provider", "missing"].includes(String(row.source)) || typeof row.stale !== "boolean")
|
|
38
|
+
throw new Error("inventory_stock_response_invalid");
|
|
39
|
+
const result = { sku: row.sku, catalogSkuId: row.catalogSkuId == null ? null : String(row.catalogSkuId),
|
|
40
|
+
source: row.source, providerForSale: row.providerForSale == null ? null : quantity(row.providerForSale),
|
|
41
|
+
activeHolds: quantity(row.activeHolds), safetyStock: quantity(row.safetyStock), available: quantity(row.available), stale: row.stale,
|
|
42
|
+
renewalDemand: quantity(row.renewalDemand), availableForNewPurchases: quantity(row.availableForNewPurchases), excludedOrderHolds: quantity(row.excludedOrderHolds) };
|
|
43
|
+
return [result.sku, result];
|
|
44
|
+
}));
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { InvoiceLine } from "./contracts/facts.js";
|
|
2
|
+
export declare function mapCanonicalIssueLines(linesSnapshot: unknown[], chargedTotalGrossMinor: number, chargedTotalNetMinor: number, defaultLabel: string): Array<Pick<InvoiceLine, "quantity" | "unitGrossMinor" | "unitNetMinor" | "totalGrossMinor" | "totalNetMinor"> & {
|
|
3
|
+
name: string;
|
|
4
|
+
vatRate: string;
|
|
5
|
+
}>;
|