@mj-biz-apps/orders-core-entities-server 0.0.1 → 5.2.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.
Files changed (267) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +141 -2
  3. package/dist/AccountingBridge.d.ts +65 -0
  4. package/dist/AccountingBridge.d.ts.map +1 -0
  5. package/dist/AccountingBridge.js +102 -0
  6. package/dist/AccountingBridge.js.map +1 -0
  7. package/dist/AdvanceOrderStateOperation.d.ts +80 -0
  8. package/dist/AdvanceOrderStateOperation.d.ts.map +1 -0
  9. package/dist/AdvanceOrderStateOperation.js +267 -0
  10. package/dist/AdvanceOrderStateOperation.js.map +1 -0
  11. package/dist/ApplyAccountCreditOperation.d.ts +97 -0
  12. package/dist/ApplyAccountCreditOperation.d.ts.map +1 -0
  13. package/dist/ApplyAccountCreditOperation.js +275 -0
  14. package/dist/ApplyAccountCreditOperation.js.map +1 -0
  15. package/dist/BaseDeliveryChannel.d.ts +106 -0
  16. package/dist/BaseDeliveryChannel.d.ts.map +1 -0
  17. package/dist/BaseDeliveryChannel.js +36 -0
  18. package/dist/BaseDeliveryChannel.js.map +1 -0
  19. package/dist/BasePaymentProvider.d.ts +239 -0
  20. package/dist/BasePaymentProvider.d.ts.map +1 -0
  21. package/dist/BasePaymentProvider.js +91 -0
  22. package/dist/BasePaymentProvider.js.map +1 -0
  23. package/dist/BundleBehavior.d.ts +139 -0
  24. package/dist/BundleBehavior.d.ts.map +1 -0
  25. package/dist/BundleBehavior.js +173 -0
  26. package/dist/BundleBehavior.js.map +1 -0
  27. package/dist/BundleEngine.d.ts +60 -0
  28. package/dist/BundleEngine.d.ts.map +1 -0
  29. package/dist/BundleEngine.js +197 -0
  30. package/dist/BundleEngine.js.map +1 -0
  31. package/dist/CancelSubscriptionOperation.d.ts +98 -0
  32. package/dist/CancelSubscriptionOperation.d.ts.map +1 -0
  33. package/dist/CancelSubscriptionOperation.js +317 -0
  34. package/dist/CancelSubscriptionOperation.js.map +1 -0
  35. package/dist/CapturePaymentOperation.d.ts +62 -0
  36. package/dist/CapturePaymentOperation.d.ts.map +1 -0
  37. package/dist/CapturePaymentOperation.js +463 -0
  38. package/dist/CapturePaymentOperation.js.map +1 -0
  39. package/dist/CheckEntitlementOperation.d.ts +27 -0
  40. package/dist/CheckEntitlementOperation.d.ts.map +1 -0
  41. package/dist/CheckEntitlementOperation.js +45 -0
  42. package/dist/CheckEntitlementOperation.js.map +1 -0
  43. package/dist/CheckoutSessionService.d.ts +258 -0
  44. package/dist/CheckoutSessionService.d.ts.map +1 -0
  45. package/dist/CheckoutSessionService.js +1557 -0
  46. package/dist/CheckoutSessionService.js.map +1 -0
  47. package/dist/DeliveryBehavior.d.ts +121 -0
  48. package/dist/DeliveryBehavior.d.ts.map +1 -0
  49. package/dist/DeliveryBehavior.js +145 -0
  50. package/dist/DeliveryBehavior.js.map +1 -0
  51. package/dist/DeliveryRecipientResolver.d.ts +38 -0
  52. package/dist/DeliveryRecipientResolver.d.ts.map +1 -0
  53. package/dist/DeliveryRecipientResolver.js +93 -0
  54. package/dist/DeliveryRecipientResolver.js.map +1 -0
  55. package/dist/DeliveryResolver.d.ts +14 -0
  56. package/dist/DeliveryResolver.d.ts.map +1 -0
  57. package/dist/DeliveryResolver.js +45 -0
  58. package/dist/DeliveryResolver.js.map +1 -0
  59. package/dist/EmailDeliveryChannel.d.ts +31 -0
  60. package/dist/EmailDeliveryChannel.d.ts.map +1 -0
  61. package/dist/EmailDeliveryChannel.js +158 -0
  62. package/dist/EmailDeliveryChannel.js.map +1 -0
  63. package/dist/EntitlementBehavior.d.ts +235 -0
  64. package/dist/EntitlementBehavior.d.ts.map +1 -0
  65. package/dist/EntitlementBehavior.js +330 -0
  66. package/dist/EntitlementBehavior.js.map +1 -0
  67. package/dist/EntitlementEngine.d.ts +97 -0
  68. package/dist/EntitlementEngine.d.ts.map +1 -0
  69. package/dist/EntitlementEngine.js +338 -0
  70. package/dist/EntitlementEngine.js.map +1 -0
  71. package/dist/EntitlementGrantClaimDriver.d.ts +42 -0
  72. package/dist/EntitlementGrantClaimDriver.d.ts.map +1 -0
  73. package/dist/EntitlementGrantClaimDriver.js +158 -0
  74. package/dist/EntitlementGrantClaimDriver.js.map +1 -0
  75. package/dist/EntitlementRead.d.ts +82 -0
  76. package/dist/EntitlementRead.d.ts.map +1 -0
  77. package/dist/EntitlementRead.js +368 -0
  78. package/dist/EntitlementRead.js.map +1 -0
  79. package/dist/FulfillOrderLinesOperation.d.ts +34 -0
  80. package/dist/FulfillOrderLinesOperation.d.ts.map +1 -0
  81. package/dist/FulfillOrderLinesOperation.js +208 -0
  82. package/dist/FulfillOrderLinesOperation.js.map +1 -0
  83. package/dist/FulfillmentBehavior.d.ts +101 -0
  84. package/dist/FulfillmentBehavior.d.ts.map +1 -0
  85. package/dist/FulfillmentBehavior.js +145 -0
  86. package/dist/FulfillmentBehavior.js.map +1 -0
  87. package/dist/GLAccountResolver.d.ts +123 -0
  88. package/dist/GLAccountResolver.d.ts.map +1 -0
  89. package/dist/GLAccountResolver.js +175 -0
  90. package/dist/GLAccountResolver.js.map +1 -0
  91. package/dist/GetFulfillmentQueueOperation.d.ts +35 -0
  92. package/dist/GetFulfillmentQueueOperation.d.ts.map +1 -0
  93. package/dist/GetFulfillmentQueueOperation.js +221 -0
  94. package/dist/GetFulfillmentQueueOperation.js.map +1 -0
  95. package/dist/GetOverdueWorklistOperation.d.ts +41 -0
  96. package/dist/GetOverdueWorklistOperation.d.ts.map +1 -0
  97. package/dist/GetOverdueWorklistOperation.js +210 -0
  98. package/dist/GetOverdueWorklistOperation.js.map +1 -0
  99. package/dist/GiftCardBehavior.d.ts +97 -0
  100. package/dist/GiftCardBehavior.d.ts.map +1 -0
  101. package/dist/GiftCardBehavior.js +121 -0
  102. package/dist/GiftCardBehavior.js.map +1 -0
  103. package/dist/GiftCardEngine.d.ts +59 -0
  104. package/dist/GiftCardEngine.d.ts.map +1 -0
  105. package/dist/GiftCardEngine.js +197 -0
  106. package/dist/GiftCardEngine.js.map +1 -0
  107. package/dist/GuestOrderClaimDriver.d.ts +36 -0
  108. package/dist/GuestOrderClaimDriver.d.ts.map +1 -0
  109. package/dist/GuestOrderClaimDriver.js +161 -0
  110. package/dist/GuestOrderClaimDriver.js.map +1 -0
  111. package/dist/InvoiceBehavior.d.ts +394 -0
  112. package/dist/InvoiceBehavior.d.ts.map +1 -0
  113. package/dist/InvoiceBehavior.js +496 -0
  114. package/dist/InvoiceBehavior.js.map +1 -0
  115. package/dist/InvoiceBuilder.d.ts +48 -0
  116. package/dist/InvoiceBuilder.d.ts.map +1 -0
  117. package/dist/InvoiceBuilder.js +352 -0
  118. package/dist/InvoiceBuilder.js.map +1 -0
  119. package/dist/InvoiceDisplay.d.ts +96 -0
  120. package/dist/InvoiceDisplay.d.ts.map +1 -0
  121. package/dist/InvoiceDisplay.js +121 -0
  122. package/dist/InvoiceDisplay.js.map +1 -0
  123. package/dist/ListEntitlementsOperation.d.ts +21 -0
  124. package/dist/ListEntitlementsOperation.d.ts.map +1 -0
  125. package/dist/ListEntitlementsOperation.js +39 -0
  126. package/dist/ListEntitlementsOperation.js.map +1 -0
  127. package/dist/ManualPaymentProvider.d.ts +19 -0
  128. package/dist/ManualPaymentProvider.d.ts.map +1 -0
  129. package/dist/ManualPaymentProvider.js +92 -0
  130. package/dist/ManualPaymentProvider.js.map +1 -0
  131. package/dist/OrderEntityServer.d.ts +542 -0
  132. package/dist/OrderEntityServer.d.ts.map +1 -0
  133. package/dist/OrderEntityServer.js +2100 -0
  134. package/dist/OrderEntityServer.js.map +1 -0
  135. package/dist/OrderJournalEntryFactory.d.ts +140 -0
  136. package/dist/OrderJournalEntryFactory.d.ts.map +1 -0
  137. package/dist/OrderJournalEntryFactory.js +466 -0
  138. package/dist/OrderJournalEntryFactory.js.map +1 -0
  139. package/dist/OrderLineEntityServer.d.ts +90 -0
  140. package/dist/OrderLineEntityServer.d.ts.map +1 -0
  141. package/dist/OrderLineEntityServer.js +260 -0
  142. package/dist/OrderLineEntityServer.js.map +1 -0
  143. package/dist/OrdersSettings.d.ts +36 -0
  144. package/dist/OrdersSettings.d.ts.map +1 -0
  145. package/dist/OrdersSettings.js +147 -0
  146. package/dist/OrdersSettings.js.map +1 -0
  147. package/dist/PaymentAllocationFactory.d.ts +128 -0
  148. package/dist/PaymentAllocationFactory.d.ts.map +1 -0
  149. package/dist/PaymentAllocationFactory.js +235 -0
  150. package/dist/PaymentAllocationFactory.js.map +1 -0
  151. package/dist/PaymentHeaderEntityServer.d.ts +180 -0
  152. package/dist/PaymentHeaderEntityServer.d.ts.map +1 -0
  153. package/dist/PaymentHeaderEntityServer.js +656 -0
  154. package/dist/PaymentHeaderEntityServer.js.map +1 -0
  155. package/dist/PaymentIntentService.d.ts +101 -0
  156. package/dist/PaymentIntentService.d.ts.map +1 -0
  157. package/dist/PaymentIntentService.js +150 -0
  158. package/dist/PaymentIntentService.js.map +1 -0
  159. package/dist/PaymentJournalEntryFactory.d.ts +99 -0
  160. package/dist/PaymentJournalEntryFactory.d.ts.map +1 -0
  161. package/dist/PaymentJournalEntryFactory.js +127 -0
  162. package/dist/PaymentJournalEntryFactory.js.map +1 -0
  163. package/dist/PaymentLineEntityServer.d.ts +73 -0
  164. package/dist/PaymentLineEntityServer.d.ts.map +1 -0
  165. package/dist/PaymentLineEntityServer.js +303 -0
  166. package/dist/PaymentLineEntityServer.js.map +1 -0
  167. package/dist/PaymentProviderBehavior.d.ts +222 -0
  168. package/dist/PaymentProviderBehavior.d.ts.map +1 -0
  169. package/dist/PaymentProviderBehavior.js +368 -0
  170. package/dist/PaymentProviderBehavior.js.map +1 -0
  171. package/dist/PaymentProviderResolver.d.ts +84 -0
  172. package/dist/PaymentProviderResolver.d.ts.map +1 -0
  173. package/dist/PaymentProviderResolver.js +220 -0
  174. package/dist/PaymentProviderResolver.js.map +1 -0
  175. package/dist/PaymentReversalFactory.d.ts +107 -0
  176. package/dist/PaymentReversalFactory.d.ts.map +1 -0
  177. package/dist/PaymentReversalFactory.js +174 -0
  178. package/dist/PaymentReversalFactory.js.map +1 -0
  179. package/dist/PaymentSettlement.d.ts +56 -0
  180. package/dist/PaymentSettlement.d.ts.map +1 -0
  181. package/dist/PaymentSettlement.js +243 -0
  182. package/dist/PaymentSettlement.js.map +1 -0
  183. package/dist/PaymentTermsBehavior.d.ts +109 -0
  184. package/dist/PaymentTermsBehavior.d.ts.map +1 -0
  185. package/dist/PaymentTermsBehavior.js +172 -0
  186. package/dist/PaymentTermsBehavior.js.map +1 -0
  187. package/dist/PaymentWebhookHandler.d.ts +103 -0
  188. package/dist/PaymentWebhookHandler.d.ts.map +1 -0
  189. package/dist/PaymentWebhookHandler.js +246 -0
  190. package/dist/PaymentWebhookHandler.js.map +1 -0
  191. package/dist/PreviewPriceOperation.d.ts +62 -0
  192. package/dist/PreviewPriceOperation.d.ts.map +1 -0
  193. package/dist/PreviewPriceOperation.js +161 -0
  194. package/dist/PreviewPriceOperation.js.map +1 -0
  195. package/dist/PriceOrderOperation.d.ts +93 -0
  196. package/dist/PriceOrderOperation.d.ts.map +1 -0
  197. package/dist/PriceOrderOperation.js +146 -0
  198. package/dist/PriceOrderOperation.js.map +1 -0
  199. package/dist/ProductPriceEntityServer.d.ts +34 -0
  200. package/dist/ProductPriceEntityServer.d.ts.map +1 -0
  201. package/dist/ProductPriceEntityServer.js +97 -0
  202. package/dist/ProductPriceEntityServer.js.map +1 -0
  203. package/dist/RefundPaymentOperation.d.ts +61 -0
  204. package/dist/RefundPaymentOperation.d.ts.map +1 -0
  205. package/dist/RefundPaymentOperation.js +177 -0
  206. package/dist/RefundPaymentOperation.js.map +1 -0
  207. package/dist/RevenueRecognition.d.ts +76 -0
  208. package/dist/RevenueRecognition.d.ts.map +1 -0
  209. package/dist/RevenueRecognition.js +133 -0
  210. package/dist/RevenueRecognition.js.map +1 -0
  211. package/dist/ReversalBehavior.d.ts +82 -0
  212. package/dist/ReversalBehavior.d.ts.map +1 -0
  213. package/dist/ReversalBehavior.js +100 -0
  214. package/dist/ReversalBehavior.js.map +1 -0
  215. package/dist/ReversalResolver.d.ts +37 -0
  216. package/dist/ReversalResolver.d.ts.map +1 -0
  217. package/dist/ReversalResolver.js +96 -0
  218. package/dist/ReversalResolver.js.map +1 -0
  219. package/dist/SpawnRenewalsOperation.d.ts +109 -0
  220. package/dist/SpawnRenewalsOperation.d.ts.map +1 -0
  221. package/dist/SpawnRenewalsOperation.js +295 -0
  222. package/dist/SpawnRenewalsOperation.js.map +1 -0
  223. package/dist/StoredValuePaymentProvider.d.ts +52 -0
  224. package/dist/StoredValuePaymentProvider.d.ts.map +1 -0
  225. package/dist/StoredValuePaymentProvider.js +205 -0
  226. package/dist/StoredValuePaymentProvider.js.map +1 -0
  227. package/dist/StripeACHPaymentProvider.d.ts +50 -0
  228. package/dist/StripeACHPaymentProvider.d.ts.map +1 -0
  229. package/dist/StripeACHPaymentProvider.js +211 -0
  230. package/dist/StripeACHPaymentProvider.js.map +1 -0
  231. package/dist/StripePaymentProvider.d.ts +83 -0
  232. package/dist/StripePaymentProvider.d.ts.map +1 -0
  233. package/dist/StripePaymentProvider.js +442 -0
  234. package/dist/StripePaymentProvider.js.map +1 -0
  235. package/dist/SubscriptionBehavior.d.ts +197 -0
  236. package/dist/SubscriptionBehavior.d.ts.map +1 -0
  237. package/dist/SubscriptionBehavior.js +415 -0
  238. package/dist/SubscriptionBehavior.js.map +1 -0
  239. package/dist/checkoutCaptureAlert.d.ts +16 -0
  240. package/dist/checkoutCaptureAlert.d.ts.map +1 -0
  241. package/dist/checkoutCaptureAlert.js +55 -0
  242. package/dist/checkoutCaptureAlert.js.map +1 -0
  243. package/dist/checkoutCaptureRetry.d.ts +27 -0
  244. package/dist/checkoutCaptureRetry.d.ts.map +1 -0
  245. package/dist/checkoutCaptureRetry.js +50 -0
  246. package/dist/checkoutCaptureRetry.js.map +1 -0
  247. package/dist/claimDriverHelpers.d.ts +15 -0
  248. package/dist/claimDriverHelpers.d.ts.map +1 -0
  249. package/dist/claimDriverHelpers.js +34 -0
  250. package/dist/claimDriverHelpers.js.map +1 -0
  251. package/dist/entity-names.d.ts +15 -0
  252. package/dist/entity-names.d.ts.map +1 -0
  253. package/dist/entity-names.js +15 -0
  254. package/dist/entity-names.js.map +1 -0
  255. package/dist/identityClaimContracts.d.ts +118 -0
  256. package/dist/identityClaimContracts.d.ts.map +1 -0
  257. package/dist/identityClaimContracts.js +58 -0
  258. package/dist/identityClaimContracts.js.map +1 -0
  259. package/dist/index.d.ts +126 -0
  260. package/dist/index.d.ts.map +1 -0
  261. package/dist/index.js +115 -0
  262. package/dist/index.js.map +1 -0
  263. package/dist/sql-guards.d.ts +78 -0
  264. package/dist/sql-guards.d.ts.map +1 -0
  265. package/dist/sql-guards.js +115 -0
  266. package/dist/sql-guards.js.map +1 -0
  267. package/package.json +50 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BundleBehavior.d.ts","sourceRoot":"","sources":["../src/BundleBehavior.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,0CAA0C;AAC1C,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,YAAY,CAAC;AAEzD,4DAA4D;AAC5D,MAAM,WAAW,eAAe;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,oCAAoC;AACpC,MAAM,WAAW,kBAAkB;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GACnB,YAAY,GACZ,cAAc,GACd,qBAAqB,GACrB,cAAc,GACd,cAAc,CAAC;AAErB,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,2FAA2F;IAC3F,eAAe,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CA2BvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,eAAe,EAAE,GAC9B;IAAE,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAqBtD;AAED,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,0FAA0F;IAC1F,SAAS,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,eAAe,EAAE,GAC9B,UAAU,CAgCZ;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAKvF;AAED,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,WAAW,EAAE,GACxB,YAAY,EAAE,CAchB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAE7F"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Bundle expansion arithmetic, with no database in sight.
3
+ *
4
+ * WHY EXPAND AT ALL. A bundle sold as one line forces ONE of everything: one tax treatment, one
5
+ * revenue schedule, one GL account, one entitlement, one returnable unit. A bundle of a publication
6
+ * (often exempt) and a conference registration (not) taxed at the header is simply wrong, and a
7
+ * bundle spanning a subscription and an event cannot be scheduled from a single line at all. So the
8
+ * components become real order lines and everything downstream keeps working per line.
9
+ *
10
+ * WHAT THE PARENT IS FOR. The customer bought "the Gold Package", not four things, and reporting
11
+ * needs to know a component came from a bundle — and from WHICH bundle line, since two Gold Packages
12
+ * on one order produce two indistinguishable sets otherwise. So the parent line survives as the
13
+ * customer-facing row, carrying `IsRollupParent`, and contributes ZERO to every total. The children
14
+ * carry the money.
15
+ *
16
+ * THE ARITHMETIC THAT BITES. Allocating one bundle price across components by relative standalone
17
+ * selling price does not divide evenly. Allocate $100 across three equal components and naive
18
+ * rounding gives 33.33 × 3 = 99.99 — a penny that vanishes, on an order that still balances because
19
+ * every line agrees with itself. `AllocateBundlePrice` uses largest-remainder so the parts sum to
20
+ * the whole EXACTLY, and its tests assert that on deliberately awkward numbers.
21
+ *
22
+ * CONNECTS TO:
23
+ * CODE: BundleEngine (the rows) · OrderEntityServer (the lifecycle point)
24
+ * DOC: plans/archive/bizapps-orders-master.md D32/D41 (bundles), D45 (ParentOrderLineID)
25
+ */
26
+ /** Round to cents the way the rest of the engine does. */
27
+ const Money = (n) => Math.round((n + Number.EPSILON) * 100) / 100;
28
+ /**
29
+ * Split `total` across `weights` so the parts sum to `total` EXACTLY.
30
+ *
31
+ * Largest-remainder: floor every share to cents, then hand the leftover pennies one at a time to
32
+ * whichever shares were cut by the most. The naive alternative — round each share independently —
33
+ * loses or invents money on most inputs, and does it invisibly: each line is individually plausible
34
+ * and the order still balances against itself.
35
+ *
36
+ * Ties break toward the earlier index, so the result is deterministic for a given input rather than
37
+ * dependent on sort stability.
38
+ */
39
+ export function SplitExactly(total, weights) {
40
+ const n = weights.length;
41
+ if (n === 0)
42
+ return [];
43
+ const cents = Math.round(Money(total) * 100);
44
+ const weightSum = weights.reduce((s, w) => s + w, 0);
45
+ // No usable weights: split as evenly as cents allow rather than putting it all on the first.
46
+ if (!(weightSum > 0)) {
47
+ const base = Math.floor(cents / n);
48
+ const out = new Array(n).fill(base);
49
+ for (let i = 0; i < cents - base * n; i++)
50
+ out[i] += 1;
51
+ return out.map((c) => c / 100);
52
+ }
53
+ const exact = weights.map((w) => (cents * w) / weightSum);
54
+ const floors = exact.map((e) => Math.floor(e));
55
+ let remaining = cents - floors.reduce((s, f) => s + f, 0);
56
+ const order = exact
57
+ .map((e, i) => ({ i, rem: e - Math.floor(e) }))
58
+ .sort((a, b) => (b.rem === a.rem ? a.i - b.i : b.rem - a.rem));
59
+ const out = [...floors];
60
+ for (let k = 0; k < order.length && remaining > 0; k++, remaining--)
61
+ out[order[k].i] += 1;
62
+ return out.map((c) => c / 100);
63
+ }
64
+ /**
65
+ * Allocate a bundle's price across its components by relative standalone selling price.
66
+ *
67
+ * `Bundled` components share the bundle price. `SumOfParts` components are priced on their own and
68
+ * take no share of it — a bundle may mix the two, which is how "everything in the package plus a
69
+ * discounted add-on priced separately" is expressed.
70
+ *
71
+ * WHEN NO COMPONENT HAS A PRICE the weights are all zero and there is nothing to allocate BY. Rather
72
+ * than refuse the sale or silently dump the whole amount on the first component, it splits evenly
73
+ * and says so through `AllocatedEvenly`, so a caller can record that the allocation was arbitrary.
74
+ * An even split is defensible; a silent lopsided one is not.
75
+ */
76
+ export function AllocateBundlePrice(bundleTotal, components) {
77
+ const bundled = components.map((c) => c.PricingMode === 'Bundled');
78
+ // Weight by the component's standalone value FOR THE WHOLE LINE — its price times how many of
79
+ // it the bundle contains. Weighting by unit price alone would under-allocate to a component
80
+ // that appears three times.
81
+ const weights = components.map((c, i) => bundled[i] ? Math.max(0, Number(c.StandaloneSellingPrice ?? 0)) * Math.max(0, c.Quantity) : 0);
82
+ const bundledCount = bundled.filter(Boolean).length;
83
+ const weightSum = weights.reduce((s, w) => s + w, 0);
84
+ const allocatedEvenly = bundledCount > 0 && !(weightSum > 0);
85
+ if (allocatedEvenly) {
86
+ // Even across the BUNDLED ones only; SumOfParts components still take nothing.
87
+ const evenWeights = components.map((_, i) => (bundled[i] ? 1 : 0));
88
+ return { PerComponent: SplitExactly(bundleTotal, evenWeights), AllocatedEvenly: true };
89
+ }
90
+ return { PerComponent: SplitExactly(bundleTotal, weights), AllocatedEvenly: false };
91
+ }
92
+ /**
93
+ * Plan the child lines a bundle line expands into.
94
+ *
95
+ * ONE LEVEL ONLY. A component that is itself a bundle is refused rather than expanded recursively:
96
+ * the quantity ripple and the allocation both get considerably harder, and nothing in the catalog
97
+ * needs it yet. Easy to relax; very hard to un-ship once orders exist that depend on it.
98
+ */
99
+ export function PlanBundleExpansion(line, components) {
100
+ const empty = (reason) => ({
101
+ Children: [],
102
+ Refusal: reason,
103
+ AllocatedEvenly: false,
104
+ });
105
+ if (!components.length)
106
+ return empty('NotABundle');
107
+ if (line.ReversesOrderLineID)
108
+ return empty('ReversalLine');
109
+ if (line.HasParent)
110
+ return empty('NestedBundle');
111
+ const qty = Number(line.Quantity ?? 0);
112
+ if (!(qty > 0))
113
+ return empty('NonPositiveQuantity');
114
+ // What the whole parent line is worth, which is what gets allocated.
115
+ const bundleTotal = Money(Money(line.UnitPrice ?? 0) * qty);
116
+ const { PerComponent, AllocatedEvenly } = AllocateBundlePrice(bundleTotal, components);
117
+ const children = components.map((c, i) => {
118
+ const childQty = ChildQuantity(qty, c.Quantity);
119
+ // The allocation is a TOTAL for the component across the whole line; the line stores a UNIT
120
+ // price, so divide back out. Dividing by zero would be a NaN that saves cleanly, so guard it.
121
+ const unitPrice = childQty > 0 ? Money(PerComponent[i] / childQty) : 0;
122
+ return {
123
+ ComponentProductID: c.ComponentProductID,
124
+ Quantity: childQty,
125
+ UnitPrice: c.PricingMode === 'Bundled' ? unitPrice : Money(c.StandaloneSellingPrice ?? 0),
126
+ SortOrder: c.SortOrder,
127
+ };
128
+ });
129
+ return { Children: children, Refusal: null, AllocatedEvenly };
130
+ }
131
+ /** A child's quantity: how many bundles times how many of the component each contains. */
132
+ export function ChildQuantity(parentQuantity, componentQuantity) {
133
+ const q = Number(parentQuantity ?? 0) * Number(componentQuantity ?? 0);
134
+ // Component quantities are DECIMAL(18,4); keep the same scale rather than rounding to an integer,
135
+ // since 0.5 hours of a service per bundle is a legitimate thing to sell.
136
+ return Math.round((q + Number.EPSILON) * 10000) / 10000;
137
+ }
138
+ /**
139
+ * What a change to the parent's quantity should do to its children.
140
+ *
141
+ * A child whose quantity was hand-edited DETACHES from the ripple. Without that, bumping the bundle
142
+ * quantity silently overwrites a deliberate correction — data loss that looks exactly like
143
+ * arithmetic, because the number it lands on is the number the formula says it should be.
144
+ *
145
+ * Returns only the children that actually change, so a no-op ripple writes nothing.
146
+ */
147
+ export function PlanQuantityRipple(parentQuantity, children) {
148
+ const changes = [];
149
+ for (const child of children) {
150
+ if (child.IsQuantityOverridden)
151
+ continue;
152
+ const target = ChildQuantity(parentQuantity, child.ComponentQuantity);
153
+ if (target !== child.CurrentQuantity) {
154
+ changes.push({
155
+ OrderLineID: child.ID,
156
+ FromQuantity: child.CurrentQuantity,
157
+ ToQuantity: target,
158
+ });
159
+ }
160
+ }
161
+ return changes;
162
+ }
163
+ /**
164
+ * Does this set of lines total correctly once parents are excluded?
165
+ *
166
+ * The invariant every rollup depends on: a rollup parent contributes nothing, so the sum over
167
+ * non-parent lines is the order's real value. Exported because it is worth asserting from the
168
+ * outside rather than trusting each rollup to remember.
169
+ */
170
+ export function RollupTotal(lines) {
171
+ return Money(lines.filter((l) => !l.IsRollupParent).reduce((s, l) => s + Number(l.Amount ?? 0), 0));
172
+ }
173
+ //# sourceMappingURL=BundleBehavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BundleBehavior.js","sourceRoot":"","sources":["../src/BundleBehavior.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Expand bundle lines into their components, in memory, before anything is written.
3
+ *
4
+ * The ARITHMETIC lives in `BundleBehavior` and is unit-tested without a database. What lives here is
5
+ * the catalog lookup and the mutation of the pending line collection.
6
+ *
7
+ * WHY IT RUNS IN MEMORY, BEFORE THE INSERT. A Confirmed line is frozen by trigger 51003, and because
8
+ * the CRUD procs run under INSERT-EXEC a trigger rollback surfaces as "Cannot use the ROLLBACK
9
+ * statement within an INSERT-EXEC statement" — an error naming neither the line nor the rule. So
10
+ * anything that changes a line's money has to be settled before the row goes down, not corrected
11
+ * afterwards. Expansion changes a great deal of money: it zeroes the parent and creates children.
12
+ *
13
+ * WHY THE PARENT'S ID IS ASSIGNED HERE. A child needs `ParentOrderLineID` at INSERT time for the
14
+ * same reason — setting it afterwards would be an update to a frozen line. So the parent gets its
15
+ * ID before either row is written, and the children point at it from the start.
16
+ *
17
+ * SNAPSHOT, NOT A LIVE VIEW. Expansion is frozen onto the order when it is placed. Editing
18
+ * `ProductBundleItem` later must never mutate a historical order, so nothing re-derives children
19
+ * from the bundle definition after the fact.
20
+ *
21
+ * CONNECTS TO:
22
+ * PURE: BundleBehavior (+ its unit tests)
23
+ * CODE: OrderEntityServer.expandBundles (the lifecycle point)
24
+ * DOC: plans/archive/bizapps-orders-master.md D32/D41, D45
25
+ */
26
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
27
+ import { mjBizAppsOrdersOrderLineEntity } from '@mj-biz-apps/orders-entities';
28
+ /** The subset of an order line this engine reads and writes. Kept structural so there is no import cycle. */
29
+ /**
30
+ * An order line this engine can expand.
31
+ *
32
+ * Was a structural duck-type built on `Get`/`Set` — which meant every field this module touched
33
+ * was a string literal the compiler could not check. It is the order-line entity; say so.
34
+ */
35
+ export type ExpandableLine = mjBizAppsOrdersOrderLineEntity;
36
+ export interface BundleExpansionOutcome {
37
+ /** How many parent lines were expanded. */
38
+ Expanded: number;
39
+ /** How many child lines were created. */
40
+ ChildrenCreated: number;
41
+ /** Parent line IDs whose allocation had no prices to go on and was split evenly. */
42
+ ArbitrarilyAllocated: string[];
43
+ }
44
+ /**
45
+ * Expand every bundle line in `lines`, appending children to the same collection.
46
+ *
47
+ * `makeLine` creates a fresh, unsaved order-line entity — supplied by the caller so this module does
48
+ * not need to know how the host builds one. Returns what happened rather than throwing when there is
49
+ * nothing to expand, since most orders contain no bundles.
50
+ */
51
+ export declare function ExpandBundleLines(lines: ExpandableLine[], makeLine: () => Promise<ExpandableLine>, provider: IMetadataProvider, user: UserInfo): Promise<BundleExpansionOutcome>;
52
+ /**
53
+ * Apply a parent's quantity change to its children.
54
+ *
55
+ * Exported for the edit path rather than the confirm path — on a DRAFT order a parent's quantity may
56
+ * still change, and its children have to follow. A child whose quantity was hand-edited detaches, so
57
+ * a deliberate correction is not silently overwritten by the formula.
58
+ */
59
+ export declare function RippleBundleQuantity(parentOrderLineID: string, parentQuantity: number, provider: IMetadataProvider, user: UserInfo): Promise<number>;
60
+ //# sourceMappingURL=BundleEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BundleEngine.d.ts","sourceRoot":"","sources":["../src/BundleEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAEH,iBAAiB,EAGjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAc9E,6GAA6G;AAC7G;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,8BAA8B,CAAC;AAE5D,MAAM,WAAW,sBAAsB;IACnC,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB,oFAAoF;IACpF,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,cAAc,EAAE,EACvB,QAAQ,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,EACvC,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,GACf,OAAO,CAAC,sBAAsB,CAAC,CAkIjC;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACtC,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,GACf,OAAO,CAAC,MAAM,CAAC,CA0DjB"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Expand bundle lines into their components, in memory, before anything is written.
3
+ *
4
+ * The ARITHMETIC lives in `BundleBehavior` and is unit-tested without a database. What lives here is
5
+ * the catalog lookup and the mutation of the pending line collection.
6
+ *
7
+ * WHY IT RUNS IN MEMORY, BEFORE THE INSERT. A Confirmed line is frozen by trigger 51003, and because
8
+ * the CRUD procs run under INSERT-EXEC a trigger rollback surfaces as "Cannot use the ROLLBACK
9
+ * statement within an INSERT-EXEC statement" — an error naming neither the line nor the rule. So
10
+ * anything that changes a line's money has to be settled before the row goes down, not corrected
11
+ * afterwards. Expansion changes a great deal of money: it zeroes the parent and creates children.
12
+ *
13
+ * WHY THE PARENT'S ID IS ASSIGNED HERE. A child needs `ParentOrderLineID` at INSERT time for the
14
+ * same reason — setting it afterwards would be an update to a frozen line. So the parent gets its
15
+ * ID before either row is written, and the children point at it from the start.
16
+ *
17
+ * SNAPSHOT, NOT A LIVE VIEW. Expansion is frozen onto the order when it is placed. Editing
18
+ * `ProductBundleItem` later must never mutate a historical order, so nothing re-derives children
19
+ * from the bundle definition after the fact.
20
+ *
21
+ * CONNECTS TO:
22
+ * PURE: BundleBehavior (+ its unit tests)
23
+ * CODE: OrderEntityServer.expandBundles (the lifecycle point)
24
+ * DOC: plans/archive/bizapps-orders-master.md D32/D41, D45
25
+ */
26
+ import { RunView, } from '@memberjunction/core';
27
+ import { PlanBundleExpansion, } from './BundleBehavior.js';
28
+ const PRODUCT_BUNDLE_ITEM_ENTITY = 'MJ_BizApps_Orders: Product Bundle Items';
29
+ const PRODUCT_PRICE_ENTITY = 'MJ_BizApps_Orders: Product Prices';
30
+ const ORDER_LINE_ENTITY = 'MJ_BizApps_Orders: Order Lines';
31
+ const key = (id) => (id ?? '').toLowerCase();
32
+ const quote = (ids) => [...new Set(ids.map((i) => `'${i}'`))].join(',');
33
+ /**
34
+ * Expand every bundle line in `lines`, appending children to the same collection.
35
+ *
36
+ * `makeLine` creates a fresh, unsaved order-line entity — supplied by the caller so this module does
37
+ * not need to know how the host builds one. Returns what happened rather than throwing when there is
38
+ * nothing to expand, since most orders contain no bundles.
39
+ */
40
+ export async function ExpandBundleLines(lines, makeLine, provider, user) {
41
+ const out = { Expanded: 0, ChildrenCreated: 0, ArbitrarilyAllocated: [] };
42
+ if (!lines.length)
43
+ return out;
44
+ const rv = new RunView(provider);
45
+ // WHICH of these products are bundles? One query, not one per line.
46
+ const productIDs = [...new Set(lines.map((l) => l.ProductID))].filter(Boolean);
47
+ if (!productIDs.length)
48
+ return out;
49
+ const items = await rv.RunView({
50
+ EntityName: PRODUCT_BUNDLE_ITEM_ENTITY,
51
+ ExtraFilter: `BundleProductID IN (${quote(productIDs)})`,
52
+ ResultType: 'simple',
53
+ }, user);
54
+ const rows = items.Results ?? [];
55
+ if (!rows.length)
56
+ return out;
57
+ const componentsByBundle = new Map();
58
+ for (const row of rows) {
59
+ const k = key(row.BundleProductID);
60
+ if (!componentsByBundle.has(k))
61
+ componentsByBundle.set(k, []);
62
+ componentsByBundle.get(k).push(row);
63
+ }
64
+ for (const list of componentsByBundle.values())
65
+ list.sort((a, b) => a.SortOrder - b.SortOrder);
66
+ // STANDALONE SELLING PRICES for the components, so allocation has something to weight by. Only
67
+ // Active, undated-or-current rules with no price list — a list-scoped price is a negotiated
68
+ // price for one customer, not the product's standalone value.
69
+ const componentIDs = [...new Set(rows.map((r) => r.ComponentProductID))];
70
+ const prices = await rv.RunView({
71
+ EntityName: PRODUCT_PRICE_ENTITY,
72
+ ExtraFilter: `ProductID IN (${quote(componentIDs)}) AND Status = 'Active' ` +
73
+ `AND PriceListID IS NULL AND MinQuantity IS NULL AND MaxQuantity IS NULL`,
74
+ ResultType: 'simple',
75
+ }, user);
76
+ const sspByProduct = new Map();
77
+ for (const p of (prices.Results ?? []).sort((a, b) => (b.Priority ?? 0) - (a.Priority ?? 0))) {
78
+ // Highest priority wins; the ambiguity guard already stops two rules tying.
79
+ if (!sspByProduct.has(key(p.ProductID)))
80
+ sspByProduct.set(key(p.ProductID), Number(p.Amount));
81
+ }
82
+ // Snapshot the collection: children are appended as we go and must not themselves be scanned.
83
+ const originals = [...lines];
84
+ for (const line of originals) {
85
+ const components = componentsByBundle.get(key(line.ProductID));
86
+ if (!components?.length)
87
+ continue;
88
+ const facts = {
89
+ ID: line.ID,
90
+ ProductID: line.ProductID,
91
+ Quantity: Number(line.Quantity ?? 0),
92
+ UnitPrice: Number(line.UnitPrice ?? 0),
93
+ ReversesOrderLineID: line.ReversesOrderLineID ?? null,
94
+ HasParent: !!line.ParentOrderLineID,
95
+ };
96
+ const plan = PlanBundleExpansion(facts, components.map((c) => ({
97
+ ComponentProductID: c.ComponentProductID,
98
+ Quantity: Number(c.Quantity ?? 1),
99
+ PricingMode: c.PricingMode,
100
+ SortOrder: c.SortOrder,
101
+ StandaloneSellingPrice: sspByProduct.get(key(c.ComponentProductID)) ?? null,
102
+ })));
103
+ if (!plan.Children.length) {
104
+ // A reversal line is a legitimate non-expansion — the return path unwinds the children
105
+ // that already exist. A NESTED bundle is not, and saying so beats expanding it wrongly.
106
+ if (plan.Refusal === 'NestedBundle') {
107
+ throw new Error(`Order line ${line.ID} is a bundle inside another bundle. Bundles expand ONE level ` +
108
+ `only (D45) — the quantity ripple and the price allocation are not defined for ` +
109
+ `deeper nesting. Sell the inner bundle's components directly, or sell it as its ` +
110
+ `own line.`);
111
+ }
112
+ continue;
113
+ }
114
+ // THE PARENT KEEPS ITS PRICE FOR DISPLAY AND CONTRIBUTES NOTHING. Every rollup skips a
115
+ // rollup parent, so leaving money on it would double the order — which is exactly what the
116
+ // CK_OrderLine_RollupParentIsFree constraint refuses at the database.
117
+ line.IsRollupParent = true;
118
+ line.DiscountAmount = 0;
119
+ line.ChargeAmount = 0;
120
+ line.LineTax = 0;
121
+ for (const child of plan.Children) {
122
+ const row = await makeLine();
123
+ row.ProductID = child.ComponentProductID;
124
+ row.Quantity = child.Quantity;
125
+ row.UnitPrice = child.UnitPrice;
126
+ row.ParentOrderLineID = line.ID;
127
+ row.SourceBundleProductID = line.ProductID;
128
+ row.IsRollupParent = false;
129
+ row.IsQuantityOverridden = false;
130
+ lines.push(row);
131
+ out.ChildrenCreated++;
132
+ }
133
+ out.Expanded++;
134
+ if (plan.AllocatedEvenly)
135
+ out.ArbitrarilyAllocated.push(line.ID);
136
+ }
137
+ if (out.ArbitrarilyAllocated.length) {
138
+ console.warn(`Bundle line(s) ${out.ArbitrarilyAllocated.join(', ')}: no component carried a standalone ` +
139
+ `price, so the bundle price was split EVENLY rather than by relative value. The order ` +
140
+ `totals correctly, but the per-component revenue split is arbitrary. Price the ` +
141
+ `components to make the allocation meaningful.`);
142
+ }
143
+ return out;
144
+ }
145
+ /**
146
+ * Apply a parent's quantity change to its children.
147
+ *
148
+ * Exported for the edit path rather than the confirm path — on a DRAFT order a parent's quantity may
149
+ * still change, and its children have to follow. A child whose quantity was hand-edited detaches, so
150
+ * a deliberate correction is not silently overwritten by the formula.
151
+ */
152
+ export async function RippleBundleQuantity(parentOrderLineID, parentQuantity, provider, user) {
153
+ const rv = new RunView(provider);
154
+ const children = await rv.RunView({
155
+ EntityName: ORDER_LINE_ENTITY,
156
+ ExtraFilter: `ParentOrderLineID = '${parentOrderLineID}'`,
157
+ ResultType: 'simple',
158
+ }, user);
159
+ const rows = children.Results ?? [];
160
+ if (!rows.length)
161
+ return 0;
162
+ // The per-bundle component quantity is not stored on the child — it is the catalog's, and the
163
+ // catalog may have changed since. Read it from the SNAPSHOT instead: the child's current
164
+ // quantity divided by the parent's current quantity is what was agreed at expansion time.
165
+ // Falls back to the catalog only when the parent's old quantity is unknown.
166
+ const bundleProductID = rows[0].SourceBundleProductID;
167
+ const items = bundleProductID
168
+ ? await rv.RunView({
169
+ EntityName: PRODUCT_BUNDLE_ITEM_ENTITY,
170
+ ExtraFilter: `BundleProductID = '${bundleProductID}'`,
171
+ ResultType: 'simple',
172
+ }, user)
173
+ : { Results: [] };
174
+ const perBundle = new Map((items.Results ?? []).map((i) => [key(i.ComponentProductID), Number(i.Quantity ?? 1)]));
175
+ let changed = 0;
176
+ for (const child of rows) {
177
+ if (child.IsQuantityOverridden)
178
+ continue;
179
+ const per = perBundle.get(key(child.ProductID));
180
+ if (per == null)
181
+ continue;
182
+ const target = Math.round((parentQuantity * per + Number.EPSILON) * 10000) / 10000;
183
+ if (target === Number(child.Quantity))
184
+ continue;
185
+ const entity = await provider.GetEntityObject(ORDER_LINE_ENTITY, user);
186
+ if (!(await entity.Load(child.ID)))
187
+ continue;
188
+ entity.Quantity = target;
189
+ if (!(await entity.Save())) {
190
+ throw new Error(`Could not ripple quantity ${target} to bundle child ${child.ID}: ` +
191
+ `${entity.LatestResult?.CompleteMessage ?? 'no reason given'}`);
192
+ }
193
+ changed++;
194
+ }
195
+ return changed;
196
+ }
197
+ //# sourceMappingURL=BundleEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BundleEngine.js","sourceRoot":"","sources":["../src/BundleEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Orders.CancelSubscription — cancel a subscription atomically, by POLICY (design §5).
3
+ *
4
+ * WHY THIS IS AN OPERATION AND NOT A FORM
5
+ * The mechanics already worked: a reversal order line with a negative quantity produces mirrored
6
+ * journal entries through the ordinary booking path (D16). What did not work was asking a human to
7
+ * do it. Amith's case — a subscription running 1/1–12/31, cancelled on 7/1 — needs a line of
8
+ * quantity `-0.5`. That is correct double-entry and terrible data entry, and it silently ignores
9
+ * the `SubscriptionType`'s own rules about WHEN coverage may end and WHETHER money comes back.
10
+ *
11
+ * So the caller supplies a subscription, a date and a reason. `SubscriptionBehavior` decides what
12
+ * the rules permit; this operation performs it in ONE transaction:
13
+ *
14
+ * 1. resolve the affected term (the one whose window covers the request, else the latest)
15
+ * 2. ask the behaviour what the rules say — effective date, refund, reversal fraction
16
+ * 3. when there is something to reverse, emit a reversal ORDER whose single line carries the
17
+ * negative quantity and points at the original line, and confirm it — booking mirrors the JEs
18
+ * 4. stamp the term (Canceled/Completed, CanceledAt, CancellationEffectiveDate)
19
+ * 5. stamp the subscription (Canceled, EndDate = access-through, so grace is visible)
20
+ * 6. revoke standing grants when access-through has already passed; leave them when
21
+ * grace remains — the read evaluator honours `subscription.EndDate` either way
22
+ * 7. log a `SubscriptionEvent` — the lifecycle record that made the table worth having
23
+ *
24
+ * ATOMICITY: everything above shares one transaction opened here. The reversal order's own
25
+ * `OrderEntityServer.Save` nests inside it as savepoints (the same composition booking already
26
+ * relies on), so a failure anywhere leaves the subscription exactly as it was — never a reversed
27
+ * ledger with a still-active subscription, or vice versa.
28
+ *
29
+ * FAILURE MODEL: logical failures (unknown subscription, already cancelled, no term) come back
30
+ * INSIDE the output as `Success: false` with a message — the same contract accounting's operations
31
+ * use. Only genuine faults throw.
32
+ *
33
+ * CONNECTS TO:
34
+ * POLICY: SubscriptionBehavior.DecideCancellation (./SubscriptionBehavior.ts)
35
+ * BOOKING: OrderEntityServer.Save (./OrderEntityServer.ts) — the reversal order goes through it
36
+ * TABLES: __mj_BizAppsOrders.{Subscription,SubscriptionTerm,SubscriptionEvent,OrderHeader,OrderLine}
37
+ */
38
+ import { BaseRemotableOperation, IMetadataProvider, UserInfo } from '@memberjunction/core';
39
+ import { type CancellationDecision } from './SubscriptionBehavior.js';
40
+ export interface CancelSubscriptionInput {
41
+ SubscriptionID: string;
42
+ /** When the customer asked. Defaults to today. The RULES decide when coverage actually ends. */
43
+ RequestDate?: Date | string;
44
+ /** Free text, stored on the lifecycle event. */
45
+ Reason?: string;
46
+ /**
47
+ * Compute and return the decision WITHOUT writing anything — for a confirmation screen that
48
+ * shows "you will be refunded $X, coverage ends Y" before the user commits.
49
+ */
50
+ Preview?: boolean;
51
+ }
52
+ export interface CancelSubscriptionOutput {
53
+ Success: boolean;
54
+ Message?: string;
55
+ /** What the rules decided. Present even on a preview. */
56
+ Decision?: CancellationDecision;
57
+ /** The term that was (or would be) cancelled. */
58
+ SubscriptionTermID?: string;
59
+ /** The reversal order, when one was needed. Absent when nothing was refunded. */
60
+ ReversalOrderID?: string;
61
+ ReversalOrderNumber?: string;
62
+ }
63
+ export declare class CancelSubscriptionOperation extends BaseRemotableOperation<CancelSubscriptionInput, CancelSubscriptionOutput> {
64
+ OperationKey: string;
65
+ protected InternalExecute(input: CancelSubscriptionInput, provider: IMetadataProvider, user: UserInfo): Promise<CancelSubscriptionOutput>;
66
+ private loadSubscription;
67
+ private loadRules;
68
+ /**
69
+ * The term the request lands in, or — when the request falls outside every window (an early
70
+ * cancellation of a future term, or a late one after everything lapsed) — the latest term.
71
+ * Cancelling always has to act on SOMETHING for the reversal to point at.
72
+ */
73
+ private resolveTerm;
74
+ /**
75
+ * A reversal order carrying ONE line: the negative slice of the original purchase.
76
+ *
77
+ * It goes through the ordinary confirm path rather than writing journal entries directly, so the
78
+ * mirrored ledger, the GL account resolution and the all-or-none guarantee are the SAME code
79
+ * that booked the original. `ReversesOrderLineID` is what makes a negative quantity legal
80
+ * (OrderLineEntityServer validates exactly that).
81
+ */
82
+ private emitReversalOrder;
83
+ private loadOriginalLine;
84
+ private stampTerm;
85
+ private stampSubscription;
86
+ private logEvent;
87
+ /**
88
+ * Stored grant Status is a lie after cancel unless we revoke when access has already
89
+ * ended. Grace (AccessThroughDate still in the future) leaves the rows standing — the
90
+ * evaluator honours subscription.EndDate rather than the original ValidTo.
91
+ */
92
+ private syncGrantsOnCancel;
93
+ /** The base behaviour, or the type's registered subclass when it names one (D45). */
94
+ private behaviorFor;
95
+ }
96
+ /** Tree-shaking anchor — called from the server bootstrap so the registration is retained. */
97
+ export declare function LoadCancelSubscriptionOperation(): void;
98
+ //# sourceMappingURL=CancelSubscriptionOperation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CancelSubscriptionOperation.d.ts","sourceRoot":"","sources":["../src/CancelSubscriptionOperation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAEH,sBAAsB,EAGtB,iBAAiB,EAIjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAW9B,OAAO,EAEH,KAAK,oBAAoB,EAE5B,MAAM,2BAA2B,CAAC;AASnC,MAAM,WAAW,uBAAuB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,WAAW,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC5B,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAgCD,qBACa,2BAA4B,SAAQ,sBAAsB,CACnE,uBAAuB,EACvB,wBAAwB,CAC3B;IACU,YAAY,SAA+B;cAElC,eAAe,CAC3B,KAAK,EAAE,uBAAuB,EAC9B,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,GACf,OAAO,CAAC,wBAAwB,CAAC;YAuFtB,gBAAgB;YA6BhB,SAAS;IAavB;;;;OAIG;YACW,WAAW;IA6BzB;;;;;;;OAOG;YACW,iBAAiB;YAgEjB,gBAAgB;YAmBhB,SAAS;YAmBT,iBAAiB;YAwBjB,QAAQ;IA8BtB;;;;OAIG;YACW,kBAAkB;IAgBhC,qFAAqF;IACrF,OAAO,CAAC,WAAW;CAatB;AAED,8FAA8F;AAC9F,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}