@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,146 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ /**
8
+ * @fileoverview `Orders.PriceOrder` — what a whole order comes to, persisting nothing.
9
+ *
10
+ * THE ONE RULE THAT MATTERS: this runs `OrderPricingService`, which is precisely what
11
+ * `OrderEntityServer.Save()` and `Orders.PreviewPrice` call. There is no second implementation to
12
+ * drift, which is the only way the number on the screen and the number in the ledger stay the same.
13
+ *
14
+ * WHY IT EXISTS ALONGSIDE `PreviewPrice`. That operation is a one-line call to the same service and
15
+ * reports one SKU's unit price. This one prices the whole header — promotions, charges, tax.
16
+ *
17
+ * WHAT IT REPLACED. An earlier `Orders.PreviewOrder` ran the REAL save inside a transaction that
18
+ * always rolled back, then read the computed values off the entities before they vanished. The
19
+ * reasoning was sound — a preview that reimplements pricing is a second copy of the rules — but the
20
+ * cost was not: it fired on every keystroke, so composing one order ran the full booking walk
21
+ * (journal entries, subscription decisions, entitlement grants, sequence numbers) dozens of times
22
+ * and discarded all of it, and the confirm was GATED on it. Extracting the pricing walk is what
23
+ * makes the honest version cheap: the decide step without the write.
24
+ *
25
+ * @module @mj-biz-apps/orders-core-entities-server
26
+ */
27
+ import { BaseRemotableOperation, Metadata, } from '@memberjunction/core';
28
+ import { RegisterClass } from '@memberjunction/global';
29
+ import { RequireOptionalUUID, RequireUUID } from './sql-guards.js';
30
+ import { OrderPricingService } from '@mj-biz-apps/orders-entities';
31
+ const ORDER_LINE_ENTITY = 'MJ_BizApps_Orders: Order Lines';
32
+ let PriceOrderOperation = class PriceOrderOperation extends BaseRemotableOperation {
33
+ constructor() {
34
+ super(...arguments);
35
+ this.OperationKey = 'Orders.PriceOrder';
36
+ }
37
+ async InternalExecute(input, provider, user) {
38
+ const empty = { Lines: [], Totals: { Net: 0, Discount: 0, Charges: 0, Tax: 0, Gross: 0 }, UnusableCodes: [] };
39
+ if (!input?.CompanyID)
40
+ return { Success: false, Message: 'CompanyID is required.', ...empty };
41
+ if (!input.Lines?.length)
42
+ return { Success: false, Message: 'At least one line is required.', ...empty };
43
+ // Caller-supplied ids reach SQL filter text downstream. Validated at the boundary so every
44
+ // frame below can trust them.
45
+ RequireUUID(input.CompanyID, 'CompanyID');
46
+ RequireOptionalUUID(input.OrderHeaderID, 'OrderHeaderID');
47
+ RequireOptionalUUID(input.BillToPersonID, 'BillToPersonID');
48
+ RequireOptionalUUID(input.BillToOrganizationID, 'BillToOrganizationID');
49
+ RequireOptionalUUID(input.ShipToAddressID, 'ShipToAddressID');
50
+ input.Lines.forEach((l, i) => RequireUUID(l.ProductID, `Lines[${i}].ProductID`));
51
+ // REAL LINE ENTITIES, NEVER SAVED.
52
+ //
53
+ // The pricing walk reads and writes OrderLine entities — that is what it does for the booking
54
+ // path, and reusing it verbatim is the entire point. So the input is materialised into
55
+ // entities, priced, and read back. `NewRecord()` is called and `Save()` never is.
56
+ const md = new Metadata();
57
+ const lines = [];
58
+ for (const spec of input.Lines) {
59
+ const line = await md.GetEntityObject(ORDER_LINE_ENTITY, user);
60
+ line.NewRecord();
61
+ line.ProductID = spec.ProductID;
62
+ line.Quantity = Number(spec.Quantity ?? 0);
63
+ // An absent UnitPrice must stay UNTOUCHED so the engine resolves one. Assigning 0 would
64
+ // look like a deliberate free line and suppress resolution — a silently free order.
65
+ if (spec.UnitPrice != null)
66
+ line.UnitPrice = spec.UnitPrice;
67
+ line.DiscountPct = spec.DiscountPct ?? 0;
68
+ if (spec.ServicePeriodStart)
69
+ line.ServicePeriodStart = new Date(spec.ServicePeriodStart);
70
+ if (spec.ServicePeriodEnd)
71
+ line.ServicePeriodEnd = new Date(spec.ServicePeriodEnd);
72
+ lines.push(line);
73
+ }
74
+ try {
75
+ const result = await new OrderPricingService({ Provider: provider, User: user }).Price({
76
+ OrderHeaderID: input.OrderHeaderID ?? null,
77
+ CompanyID: input.CompanyID,
78
+ BillToPersonID: input.BillToPersonID ?? null,
79
+ BillToOrganizationID: input.BillToOrganizationID ?? null,
80
+ OrderDate: input.OrderDate ?? null,
81
+ ShipToAddressID: input.ShipToAddressID ?? null,
82
+ Lines: lines,
83
+ PromotionCodes: input.PromotionCodes ?? [],
84
+ ManualDiscounts: (input.ManualDiscounts ?? []),
85
+ Charges: (input.Charges ?? []),
86
+ });
87
+ const priced = lines.map((line, i) => {
88
+ const gross = Math.round(Number(line.Quantity ?? 0) * Number(line.UnitPrice ?? 0) * 100) / 100;
89
+ const discount = Number(line.DiscountAmount ?? 0);
90
+ const charge = Number(line.ChargeAmount ?? 0);
91
+ const tax = Number(line.LineTax ?? 0);
92
+ const net = Math.round((gross - discount) * 100) / 100;
93
+ return {
94
+ ProductID: line.ProductID,
95
+ Quantity: Number(line.Quantity ?? 0),
96
+ UnitPrice: Number(line.UnitPrice ?? 0),
97
+ DiscountAmount: discount,
98
+ ChargeAmount: charge,
99
+ LineTax: tax,
100
+ LineTotalNet: net,
101
+ LineTotalGross: Math.round((net + charge + tax) * 100) / 100,
102
+ Components: result.PriceComponents.get(line)?.Components?.map((c) => ({
103
+ Kind: String(c.Kind ?? ''),
104
+ Label: String(c.Label ?? ''),
105
+ Amount: Number(c.Amount ?? 0),
106
+ })),
107
+ TaxExemptReason: result.TaxReasons.get(i) ?? null,
108
+ };
109
+ });
110
+ const sum = (pick) => Math.round(priced.reduce((t, l) => t + pick(l), 0) * 100) / 100;
111
+ return {
112
+ Success: true,
113
+ Lines: priced,
114
+ Totals: {
115
+ Net: sum((l) => l.LineTotalNet),
116
+ Discount: sum((l) => l.DiscountAmount),
117
+ Charges: sum((l) => l.ChargeAmount),
118
+ Tax: sum((l) => l.LineTax),
119
+ Gross: sum((l) => l.LineTotalGross),
120
+ },
121
+ UnusableCodes: result.UnusableCodes,
122
+ };
123
+ }
124
+ catch (err) {
125
+ // A pricing failure is an ANSWER here, not a crash: the screen asks this on every edit,
126
+ // and "this cannot be priced, because X" is what the user needs to see.
127
+ return {
128
+ Success: false,
129
+ Message: err instanceof Error ? err.message : String(err),
130
+ ...empty,
131
+ };
132
+ }
133
+ }
134
+ };
135
+ PriceOrderOperation = __decorate([
136
+ RegisterClass(BaseRemotableOperation, 'Orders.PriceOrder')
137
+ ], PriceOrderOperation);
138
+ export { PriceOrderOperation };
139
+ /**
140
+ * Force the class registration. Tree-shaking removes a class nobody imports, and the decorator only
141
+ * runs if the module is loaded — so the server's bootstrap calls this.
142
+ */
143
+ export function LoadPriceOrderOperation() {
144
+ // no-op by design
145
+ }
146
+ //# sourceMappingURL=PriceOrderOperation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceOrderOperation.js","sourceRoot":"","sources":["../src/PriceOrderOperation.ts"],"names":[],"mappings":";;;;;;AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EACH,sBAAsB,EACtB,QAAQ,GAGX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,MAAM,iBAAiB,GAAG,gCAAgC,CAAC;AA4CpD,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,sBAAyD;IAA3F;;QACI,iBAAY,GAAG,mBAAmB,CAAC;IAyG9C,CAAC;IAvGa,KAAK,CAAC,eAAe,CAC3B,KAAsB,EACtB,QAA2B,EAC3B,IAAc;QAEd,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;QAE9G,IAAI,CAAC,KAAK,EAAE,SAAS;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,EAAE,GAAG,KAAK,EAAE,CAAC;QAC9F,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,gCAAgC,EAAE,GAAG,KAAK,EAAE,CAAC;QAEzG,2FAA2F;QAC3F,8BAA8B;QAC9B,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAC1C,mBAAmB,CAAC,KAAK,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;QAC1D,mBAAmB,CAAC,KAAK,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QAC5D,mBAAmB,CAAC,KAAK,CAAC,oBAAoB,EAAE,sBAAsB,CAAC,CAAC;QACxE,mBAAmB,CAAC,KAAK,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;QAC9D,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;QAEjF,mCAAmC;QACnC,EAAE;QACF,8FAA8F;QAC9F,uFAAuF;QACvF,kFAAkF;QAClF,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAqC,EAAE,CAAC;QACnD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAiC,iBAAiB,EAAE,IAAI,CAAC,CAAC;YAC/F,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;YAChC,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;YAC3C,wFAAwF;YACxF,oFAAoF;YACpF,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;gBAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;YAC5D,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;YACzC,IAAI,IAAI,CAAC,kBAAkB;gBAAE,IAAI,CAAC,kBAAkB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACzF,IAAI,IAAI,CAAC,gBAAgB;gBAAE,IAAI,CAAC,gBAAgB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACnF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QAED,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,mBAAmB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC;gBACnF,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,IAAI;gBAC1C,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,IAAI;gBAC5C,oBAAoB,EAAE,KAAK,CAAC,oBAAoB,IAAI,IAAI;gBACxD,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;gBAClC,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,IAAI;gBAC9C,KAAK,EAAE,KAAK;gBACZ,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,EAAE;gBAC1C,eAAe,EAAE,CAAC,KAAK,CAAC,eAAe,IAAI,EAAE,CAAU;gBACvD,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAU;aAC1C,CAAC,CAAC;YAEH,MAAM,MAAM,GAAiB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;gBAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;gBAC/F,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC;gBAClD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC;gBAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;gBACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;gBACvD,OAAO;oBACH,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;oBACpC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;oBACtC,cAAc,EAAE,QAAQ;oBACxB,YAAY,EAAE,MAAM;oBACpB,OAAO,EAAE,GAAG;oBACZ,YAAY,EAAE,GAAG;oBACjB,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG;oBAC5D,UAAU,EAAE,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBAClE,IAAI,EAAE,MAAM,CAAE,CAAuB,CAAC,IAAI,IAAI,EAAE,CAAC;wBACjD,KAAK,EAAE,MAAM,CAAE,CAAwB,CAAC,KAAK,IAAI,EAAE,CAAC;wBACpD,MAAM,EAAE,MAAM,CAAE,CAAyB,CAAC,MAAM,IAAI,CAAC,CAAC;qBACzD,CAAC,CAAC;oBACH,eAAe,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI;iBACpD,CAAC;YACN,CAAC,CAAC,CAAC;YAEH,MAAM,GAAG,GAAG,CAAC,IAA+B,EAAE,EAAE,CAC5C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;YAEpE,OAAO;gBACH,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE;oBACJ,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;oBAC/B,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;oBACtC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;oBACnC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;oBAC1B,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;iBACtC;gBACD,aAAa,EAAE,MAAM,CAAC,aAAa;aACtC,CAAC;QACN,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,wFAAwF;YACxF,wEAAwE;YACxE,OAAO;gBACH,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACzD,GAAG,KAAK;aACX,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AA1GY,mBAAmB;IAD/B,aAAa,CAAC,sBAAsB,EAAE,mBAAmB,CAAC;GAC9C,mBAAmB,CA0G/B;;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB;IACnC,kBAAkB;AACtB,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ProductPrice server subclass — refuses an AMBIGUOUS rule set when it is written (plan D69).
3
+ *
4
+ * WHY AT WRITE TIME
5
+ * `PickPriceRule` reports a priority tie rather than resolving it, so an ambiguous set fails at
6
+ * ORDER time — which is the worst possible moment: a customer is waiting, the person who created
7
+ * the collision is elsewhere, and the failure looks like a bug in pricing rather than a gap in
8
+ * configuration. Catching it as the second rule is saved puts the error in front of the person who
9
+ * caused it, while they still have the context to fix it.
10
+ *
11
+ * It is deliberately NOT a database constraint. Ambiguity is not a property of a row — two rows are
12
+ * only ambiguous relative to a quantity and a moment, because non-overlapping quantity bands or
13
+ * disjoint seasons make equal priorities perfectly legitimate. A UNIQUE index would forbid a great
14
+ * deal of correct configuration to prevent one specific mistake.
15
+ *
16
+ * WHAT COUNTS AS A COLLISION
17
+ * Same product, same price list, same fee type, same priority, with OVERLAPPING quantity bands AND
18
+ * overlapping absolute windows. Recurrence is not considered: a monthly and a weekday rule can
19
+ * genuinely never coincide, and proving that here would mean reimplementing the applicability
20
+ * engine against every possible date. The order-time check remains the backstop for the cases this
21
+ * cannot see — this catches the common, obvious, avoidable ones.
22
+ *
23
+ * CONNECTS TO:
24
+ * PURE: ./PricingBehavior.ts (PickPriceRule — the reader this protects)
25
+ * DOC: plans/archive/pricing-schema.md
26
+ */
27
+ import { ValidationResult } from '@memberjunction/core';
28
+ import { mjBizAppsOrdersProductPriceEntity } from '@mj-biz-apps/orders-entities';
29
+ export declare class ProductPriceEntityServer extends mjBizAppsOrdersProductPriceEntity {
30
+ /** BaseEntity skips ValidateAsync by default; without this the check never runs. */
31
+ get DefaultSkipAsyncValidation(): boolean;
32
+ ValidateAsync(): Promise<ValidationResult>;
33
+ }
34
+ //# sourceMappingURL=ProductPriceEntityServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductPriceEntityServer.d.ts","sourceRoot":"","sources":["../src/ProductPriceEntityServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAA8D,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEpH,OAAO,EAAE,iCAAiC,EAAE,MAAM,8BAA8B,CAAC;AAqCjF,qBACa,wBAAyB,SAAQ,iCAAiC;IAC3E,oFAAoF;IACpF,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAEqB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;CAmDnE"}
@@ -0,0 +1,97 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ /**
8
+ * ProductPrice server subclass — refuses an AMBIGUOUS rule set when it is written (plan D69).
9
+ *
10
+ * WHY AT WRITE TIME
11
+ * `PickPriceRule` reports a priority tie rather than resolving it, so an ambiguous set fails at
12
+ * ORDER time — which is the worst possible moment: a customer is waiting, the person who created
13
+ * the collision is elsewhere, and the failure looks like a bug in pricing rather than a gap in
14
+ * configuration. Catching it as the second rule is saved puts the error in front of the person who
15
+ * caused it, while they still have the context to fix it.
16
+ *
17
+ * It is deliberately NOT a database constraint. Ambiguity is not a property of a row — two rows are
18
+ * only ambiguous relative to a quantity and a moment, because non-overlapping quantity bands or
19
+ * disjoint seasons make equal priorities perfectly legitimate. A UNIQUE index would forbid a great
20
+ * deal of correct configuration to prevent one specific mistake.
21
+ *
22
+ * WHAT COUNTS AS A COLLISION
23
+ * Same product, same price list, same fee type, same priority, with OVERLAPPING quantity bands AND
24
+ * overlapping absolute windows. Recurrence is not considered: a monthly and a weekday rule can
25
+ * genuinely never coincide, and proving that here would mean reimplementing the applicability
26
+ * engine against every possible date. The order-time check remains the backstop for the cases this
27
+ * cannot see — this catches the common, obvious, avoidable ones.
28
+ *
29
+ * CONNECTS TO:
30
+ * PURE: ./PricingBehavior.ts (PickPriceRule — the reader this protects)
31
+ * DOC: plans/archive/pricing-schema.md
32
+ */
33
+ import { BaseEntity, RunView, ValidationErrorInfo } from '@memberjunction/core';
34
+ import { RegisterClass } from '@memberjunction/global';
35
+ import { mjBizAppsOrdersProductPriceEntity } from '@mj-biz-apps/orders-entities';
36
+ const PRODUCT_PRICE_ENTITY = 'MJ_BizApps_Orders: Product Prices';
37
+ /** Do two [min,max] bands overlap? Null bounds are open. */
38
+ function bandsOverlap(aMin, aMax, bMin, bMax) {
39
+ const lo1 = aMin ?? -Infinity;
40
+ const hi1 = aMax ?? Infinity;
41
+ const lo2 = bMin ?? -Infinity;
42
+ const hi2 = bMax ?? Infinity;
43
+ return lo1 <= hi2 && lo2 <= hi1;
44
+ }
45
+ /** Do two date windows overlap? Null end is open. */
46
+ function windowsOverlap(aFrom, aTo, bFrom, bTo) {
47
+ const s1 = new Date(aFrom).getTime();
48
+ const e1 = aTo ? new Date(aTo).getTime() : Infinity;
49
+ const s2 = new Date(bFrom).getTime();
50
+ const e2 = bTo ? new Date(bTo).getTime() : Infinity;
51
+ return s1 <= e2 && s2 <= e1;
52
+ }
53
+ let ProductPriceEntityServer = class ProductPriceEntityServer extends mjBizAppsOrdersProductPriceEntity {
54
+ /** BaseEntity skips ValidateAsync by default; without this the check never runs. */
55
+ get DefaultSkipAsyncValidation() {
56
+ return false;
57
+ }
58
+ async ValidateAsync() {
59
+ const result = await super.ValidateAsync();
60
+ if (this.Status !== 'Active')
61
+ return result; // an inactive rule collides with nothing
62
+ // Statically imported. A dynamic `import()` here violated the house rule and bought nothing:
63
+ // `@memberjunction/core` is already imported at the top of this file for BaseEntity.
64
+ const rv = new RunView(this.ProviderToUse);
65
+ const listClause = this.PriceListID ? `PriceListID = '${this.PriceListID}'` : `PriceListID IS NULL`;
66
+ const notSelf = this.IsSaved ? ` AND ID <> '${this.ID}'` : '';
67
+ const res = await rv.RunView({
68
+ EntityName: PRODUCT_PRICE_ENTITY,
69
+ ExtraFilter: `ProductID = '${this.ProductID}' AND Status = 'Active' AND ${listClause} ` +
70
+ `AND FeeType = '${String(this.FeeType).replace(/'/g, "''")}' ` +
71
+ `AND Priority = ${Number(this.Priority)}${notSelf}`,
72
+ ResultType: 'simple',
73
+ BypassCache: true,
74
+ }, this.ContextCurrentUser);
75
+ // Loud on failure: silently answering "no siblings" would let the ambiguity through, and the
76
+ // whole point is that the ambiguity is otherwise invisible until an order fails.
77
+ if (!res?.Success) {
78
+ throw new Error(`ProductPriceEntityServer: could not check for conflicting price rules: ${res?.ErrorMessage ?? 'unknown error'}`);
79
+ }
80
+ const clash = (res.Results ?? []).find((o) => bandsOverlap(this.MinQuantity, this.MaxQuantity, o.MinQuantity, o.MaxQuantity) &&
81
+ windowsOverlap(this.EffectiveFrom, this.EffectiveTo, o.EffectiveFrom, o.EffectiveTo));
82
+ if (clash) {
83
+ result.Success = false;
84
+ result.Errors.push(new ValidationErrorInfo('ProductPriceEntityServer.ValidateAsync', `This price rule collides with an existing one: both have priority ${this.Priority}, overlapping ` +
85
+ `quantity bands and overlapping effective windows for the same product, price list and fee ` +
86
+ `type. The other is ${clash.Description?.trim() || clash.ID}. Pricing resolves by highest ` +
87
+ `priority and refuses to break a tie, so an order using either quantity would fail. Give one ` +
88
+ `of them a different priority, narrow a quantity band, or separate the date windows.`, null));
89
+ }
90
+ return result;
91
+ }
92
+ };
93
+ ProductPriceEntityServer = __decorate([
94
+ RegisterClass(BaseEntity, PRODUCT_PRICE_ENTITY)
95
+ ], ProductPriceEntityServer);
96
+ export { ProductPriceEntityServer };
97
+ //# sourceMappingURL=ProductPriceEntityServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductPriceEntityServer.js","sourceRoot":"","sources":["../src/ProductPriceEntityServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Orders.RefundPayment — refund a captured payment, atomically (plan D17).
3
+ *
4
+ * WHAT A REFUND IS HERE
5
+ * A NEW `PaymentHeader` that reverses an existing one, not an edit of the original. The original
6
+ * capture is history: it happened, it has a journal entry, and rewriting it would destroy the audit
7
+ * trail of money that genuinely moved. `ReversesPaymentHeaderID` links the two.
8
+ *
9
+ * The reversal carries `Status='Refunded'`, which is what makes `PaymentHeaderEntityServer` book the
10
+ * MIRROR of the capture entry (D53): `Dr AR / Cr Cash`, positive amounts. The receivable comes back,
11
+ * the bank goes down. The reversal's `PaymentLine` then un-applies the cash from the order, and the
12
+ * rollup triggers move `Balance` / `PaymentStatus` back on their own (D41).
13
+ *
14
+ * THE GUARDS, all of which are the reason this is an operation and not a form:
15
+ * - the payment must exist and be `Captured` — a Pending or Failed payment never took money
16
+ * - never more than was captured, counting refunds already issued (partial refunds accumulate)
17
+ * - never a second full refund of the same payment
18
+ * - the refund's applications mirror the original's, so a payment split across three orders
19
+ * refunds proportionally rather than dumping the whole reversal on the first one
20
+ *
21
+ * FAILURE MODEL: logical refusals come back INSIDE the output as `Success: false` with a reason —
22
+ * the same contract accounting's operations and `Orders.CancelSubscription` use. Only genuine faults
23
+ * throw.
24
+ *
25
+ * CONNECTS TO:
26
+ * BOOKING: PaymentHeaderEntityServer.Save — the reversal books through the ordinary path
27
+ * TABLES: __mj_BizAppsOrders.{PaymentHeader,PaymentLine,PaymentDetail}
28
+ */
29
+ import { BaseRemotableOperation, IMetadataProvider, UserInfo } from '@memberjunction/core';
30
+ export interface RefundPaymentInput {
31
+ PaymentHeaderID: string;
32
+ /** Omit for a full refund of whatever remains refundable. */
33
+ Amount?: number;
34
+ Reason?: string;
35
+ /** Provider's refund id, when the money moved through one. */
36
+ ProviderRefundID?: string;
37
+ /** Compute and validate without writing — for a confirmation screen. */
38
+ Preview?: boolean;
39
+ }
40
+ export interface RefundPaymentOutput {
41
+ Success: boolean;
42
+ Message?: string;
43
+ /** What was (or would be) refunded. */
44
+ RefundAmount?: number;
45
+ /** Still refundable AFTER this refund. */
46
+ RemainingRefundable?: number;
47
+ RefundPaymentHeaderID?: string;
48
+ RefundPaymentNumber?: string;
49
+ /** The reversing journal entry, when one was booked. */
50
+ JournalEntryID?: string;
51
+ }
52
+ export declare class RefundPaymentOperation extends BaseRemotableOperation<RefundPaymentInput, RefundPaymentOutput> {
53
+ OperationKey: string;
54
+ protected InternalExecute(input: RefundPaymentInput, provider: IMetadataProvider, user: UserInfo): Promise<RefundPaymentOutput>;
55
+ private loadPayment;
56
+ /** Total already refunded against this payment — partial refunds accumulate. */
57
+ private refundedSoFar;
58
+ }
59
+ /** Tree-shaking anchor — called from the server bootstrap so the registration is retained. */
60
+ export declare function LoadRefundPaymentOperation(): void;
61
+ //# sourceMappingURL=RefundPaymentOperation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RefundPaymentOperation.d.ts","sourceRoot":"","sources":["../src/RefundPaymentOperation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EACH,sBAAsB,EAEtB,iBAAiB,EAIjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAY9B,MAAM,WAAW,kBAAkB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAiBD,qBACa,sBAAuB,SAAQ,sBAAsB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC;IAChG,YAAY,SAA0B;cAE7B,eAAe,CAC3B,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,GACf,OAAO,CAAC,mBAAmB,CAAC;YAyGjB,WAAW;IA8BzB,gFAAgF;YAClE,aAAa;CAc9B;AAED,8FAA8F;AAC9F,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
@@ -0,0 +1,177 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ /**
8
+ * Orders.RefundPayment — refund a captured payment, atomically (plan D17).
9
+ *
10
+ * WHAT A REFUND IS HERE
11
+ * A NEW `PaymentHeader` that reverses an existing one, not an edit of the original. The original
12
+ * capture is history: it happened, it has a journal entry, and rewriting it would destroy the audit
13
+ * trail of money that genuinely moved. `ReversesPaymentHeaderID` links the two.
14
+ *
15
+ * The reversal carries `Status='Refunded'`, which is what makes `PaymentHeaderEntityServer` book the
16
+ * MIRROR of the capture entry (D53): `Dr AR / Cr Cash`, positive amounts. The receivable comes back,
17
+ * the bank goes down. The reversal's `PaymentLine` then un-applies the cash from the order, and the
18
+ * rollup triggers move `Balance` / `PaymentStatus` back on their own (D41).
19
+ *
20
+ * THE GUARDS, all of which are the reason this is an operation and not a form:
21
+ * - the payment must exist and be `Captured` — a Pending or Failed payment never took money
22
+ * - never more than was captured, counting refunds already issued (partial refunds accumulate)
23
+ * - never a second full refund of the same payment
24
+ * - the refund's applications mirror the original's, so a payment split across three orders
25
+ * refunds proportionally rather than dumping the whole reversal on the first one
26
+ *
27
+ * FAILURE MODEL: logical refusals come back INSIDE the output as `Success: false` with a reason —
28
+ * the same contract accounting's operations and `Orders.CancelSubscription` use. Only genuine faults
29
+ * throw.
30
+ *
31
+ * CONNECTS TO:
32
+ * BOOKING: PaymentHeaderEntityServer.Save — the reversal books through the ordinary path
33
+ * TABLES: __mj_BizAppsOrders.{PaymentHeader,PaymentLine,PaymentDetail}
34
+ */
35
+ import { BaseRemotableOperation, LogError, RunView, } from '@memberjunction/core';
36
+ import { RegisterClass } from '@memberjunction/global';
37
+ import { RequireUUID } from './sql-guards.js';
38
+ import { BuildUnapplyLines, CreateReversingPayment, LoadAppliedAllocations, } from './PaymentReversalFactory.js';
39
+ const PAYMENT_HEADER_ENTITY = 'MJ_BizApps_Orders: Payment Headers';
40
+ const money = (v) => Math.round((v + Number.EPSILON) * 100) / 100;
41
+ let RefundPaymentOperation = class RefundPaymentOperation extends BaseRemotableOperation {
42
+ constructor() {
43
+ super(...arguments);
44
+ this.OperationKey = 'Orders.RefundPayment';
45
+ }
46
+ async InternalExecute(input, provider, user) {
47
+ // Caller-supplied ids reach SQL filter text downstream. Validated here,
48
+ // at the boundary, so every frame below this one can trust them.
49
+ RequireUUID(input.PaymentHeaderID, 'PaymentHeaderID');
50
+ const payment = await this.loadPayment(provider, user, input.PaymentHeaderID);
51
+ if (!payment) {
52
+ return { Success: false, Message: `No payment found with ID '${input.PaymentHeaderID}'.` };
53
+ }
54
+ if (payment.Status !== 'Captured') {
55
+ return {
56
+ Success: false,
57
+ Message: `Payment ${payment.PaymentNumber} is ${payment.Status}, not Captured. ` +
58
+ `Only a captured payment has money to refund.`,
59
+ };
60
+ }
61
+ const alreadyRefunded = await this.refundedSoFar(provider, user, payment.ID);
62
+ const refundable = money(payment.Amount - alreadyRefunded);
63
+ if (refundable <= 0) {
64
+ return {
65
+ Success: false,
66
+ Message: `Payment ${payment.PaymentNumber} has already been fully refunded ` +
67
+ `(${alreadyRefunded} of ${payment.Amount}).`,
68
+ RemainingRefundable: 0,
69
+ };
70
+ }
71
+ const requested = money(input.Amount ?? refundable);
72
+ if (requested <= 0) {
73
+ return { Success: false, Message: `A refund must be greater than zero, got ${requested}.` };
74
+ }
75
+ if (requested > refundable) {
76
+ return {
77
+ Success: false,
78
+ Message: `Cannot refund ${requested} of payment ${payment.PaymentNumber}: only ${refundable} ` +
79
+ `remains refundable (${payment.Amount} captured, ${alreadyRefunded} already refunded).`,
80
+ RemainingRefundable: refundable,
81
+ };
82
+ }
83
+ const applications = await LoadAppliedAllocations(provider, user, payment.ID);
84
+ if (applications.length === 0) {
85
+ return {
86
+ Success: false,
87
+ Message: `Payment ${payment.PaymentNumber} is not applied to any order, so there is nothing ` +
88
+ `to un-apply. Refund it at the provider instead.`,
89
+ };
90
+ }
91
+ if (input.Preview) {
92
+ return {
93
+ Success: true,
94
+ Message: `${requested} of ${payment.PaymentNumber} is refundable.`,
95
+ RefundAmount: requested,
96
+ RemainingRefundable: money(refundable - requested),
97
+ };
98
+ }
99
+ const dbProvider = provider;
100
+ await dbProvider.BeginTransaction();
101
+ try {
102
+ // The un-apply lines are built BEFORE the header is saved and ride its Lines collection,
103
+ // because the header's save is what checks the D68 invariant — saving it first with no
104
+ // allocations would fail on a payment that is about to be perfectly consistent.
105
+ const lines = await BuildUnapplyLines(provider, user, applications, requested);
106
+ const refund = await CreateReversingPayment(provider, user, payment, {
107
+ Amount: requested,
108
+ Reason: input.Reason ?? null,
109
+ ProviderRefundID: input.ProviderRefundID ?? null,
110
+ }, lines);
111
+ await dbProvider.CommitTransaction();
112
+ return {
113
+ Success: true,
114
+ Message: `Refunded ${requested} of payment ${payment.PaymentNumber} as ${refund.Number}.`,
115
+ RefundAmount: requested,
116
+ RemainingRefundable: money(refundable - requested),
117
+ RefundPaymentHeaderID: refund.ID,
118
+ RefundPaymentNumber: refund.Number,
119
+ JournalEntryID: refund.JournalEntryID,
120
+ };
121
+ }
122
+ catch (err) {
123
+ LogError(`Orders.RefundPayment failed for ${input.PaymentHeaderID}: ${err}`);
124
+ try {
125
+ await dbProvider.RollbackTransaction();
126
+ }
127
+ catch (rollbackErr) {
128
+ LogError(`Rollback failed after refund error: ${rollbackErr}`);
129
+ }
130
+ return { Success: false, Message: err instanceof Error ? err.message : String(err) };
131
+ }
132
+ }
133
+ // ─── Reads ─────────────────────────────────────────────────────────────────
134
+ async loadPayment(provider, user, id) {
135
+ const rv = new RunView(provider);
136
+ const result = await rv.RunView({
137
+ EntityName: PAYMENT_HEADER_ENTITY,
138
+ ExtraFilter: `ID='${id}'`,
139
+ Fields: [
140
+ 'ID',
141
+ 'PaymentNumber',
142
+ 'ReceivingCompanyID',
143
+ 'BillToOrganizationID',
144
+ 'BillToPersonID',
145
+ 'PaymentTypeID',
146
+ 'PaymentDetailID',
147
+ 'Amount',
148
+ 'ProcessingFeeAmount',
149
+ 'Status',
150
+ ],
151
+ ResultType: 'simple',
152
+ BypassCache: true,
153
+ }, user);
154
+ return result?.Results?.[0] ?? null;
155
+ }
156
+ /** Total already refunded against this payment — partial refunds accumulate. */
157
+ async refundedSoFar(provider, user, paymentID) {
158
+ const rv = new RunView(provider);
159
+ const result = await rv.RunView({
160
+ EntityName: PAYMENT_HEADER_ENTITY,
161
+ ExtraFilter: `ReversesPaymentHeaderID='${paymentID}' AND Status='Refunded'`,
162
+ Fields: ['Amount'],
163
+ ResultType: 'simple',
164
+ BypassCache: true,
165
+ }, user);
166
+ return money((result?.Results ?? []).reduce((sum, r) => sum + Math.abs(Number(r.Amount ?? 0)), 0));
167
+ }
168
+ };
169
+ RefundPaymentOperation = __decorate([
170
+ RegisterClass(BaseRemotableOperation, 'Orders.RefundPayment')
171
+ ], RefundPaymentOperation);
172
+ export { RefundPaymentOperation };
173
+ /** Tree-shaking anchor — called from the server bootstrap so the registration is retained. */
174
+ export function LoadRefundPaymentOperation() {
175
+ // intentionally empty
176
+ }
177
+ //# sourceMappingURL=RefundPaymentOperation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RefundPaymentOperation.js","sourceRoot":"","sources":["../src/RefundPaymentOperation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /** What a driver is given. Deliberately plain data — no entities, no provider, no I/O. */
2
+ export interface RevRecContext {
3
+ /** The line's net amount to be recognized (after discount, before tax). */
4
+ Amount: number;
5
+ /** The order's accounting date — when the sale was booked. */
6
+ BookingDate: Date;
7
+ /** Coverage window, when the line has one (subscriptions, services). */
8
+ ServicePeriodStart?: Date | null;
9
+ ServicePeriodEnd?: Date | null;
10
+ /** For informative descriptions only. */
11
+ ProductName?: string;
12
+ /**
13
+ * Months per recognition slice (D45 `RecognitionCadence`). Absent = monthly. This is how a
14
+ * subscription that recognizes QUARTERLY gets 4 slices a year instead of 12 — the cadence is
15
+ * the subscription type's rule, not the driver's assumption.
16
+ */
17
+ PeriodMonths?: number;
18
+ }
19
+ export interface RevRecEntry {
20
+ /** The date this slice is earned — becomes the JE's EffectiveDate. */
21
+ RecognitionDate: Date;
22
+ Amount: number;
23
+ /** Period bounds. They land on the forward-dated journal entry, which IS the schedule (D84). */
24
+ PeriodStart: Date;
25
+ PeriodEnd: Date;
26
+ }
27
+ export interface RevRecSchedule {
28
+ Entries: RevRecEntry[];
29
+ }
30
+ /**
31
+ * Base driver. Subclass, override `BuildSchedule` (or just the protected helpers), and register
32
+ * under an existing key to replace a shipped type:
33
+ *
34
+ * @RegisterClass(RevenueRecognitionDriver, 'EvenOverTime')
35
+ * export class OurEvenOverTime extends EvenOverTimeDriver { ... }
36
+ */
37
+ export declare abstract class RevenueRecognitionDriver {
38
+ /** Compute when and how much is earned. Pure: no I/O, no side effects. */
39
+ abstract BuildSchedule(context: RevRecContext): RevRecSchedule;
40
+ /**
41
+ * Split `amount` across `periods`, front-loading the rounding remainder into the first period
42
+ * so the slices always sum EXACTLY to the amount.
43
+ */
44
+ protected AllocateEvenly(amount: number, periods: number): number[];
45
+ /** Whole months between two dates, minimum 1. */
46
+ protected MonthSpan(start: Date, end: Date): number;
47
+ protected RequireServicePeriod(context: RevRecContext, driver: string): {
48
+ start: Date;
49
+ end: Date;
50
+ };
51
+ }
52
+ /**
53
+ * UpFront — 100% earned when the sale is booked. One-time goods and services.
54
+ * The booking entry credits Sales directly; there is no deferral round-trip.
55
+ */
56
+ export declare class UpFrontDriver extends RevenueRecognitionDriver {
57
+ BuildSchedule(context: RevRecContext): RevRecSchedule;
58
+ }
59
+ /**
60
+ * EvenOverTime — straight-line across the line's service period. Most subscriptions.
61
+ * A 12-month $1,200 subscription produces 12 monthly $100 entries dated on the anniversaries.
62
+ */
63
+ export declare class EvenOverTimeDriver extends RevenueRecognitionDriver {
64
+ BuildSchedule(context: RevRecContext): RevRecSchedule;
65
+ }
66
+ /**
67
+ * AllBackEnd — 100% earned on the END date. Events and milestone deliverables, where nothing is
68
+ * earned until the thing actually happens: cash is collected up front and sits in Deferred Revenue
69
+ * until the event date.
70
+ */
71
+ export declare class AllBackEndDriver extends RevenueRecognitionDriver {
72
+ BuildSchedule(context: RevRecContext): RevRecSchedule;
73
+ }
74
+ /** Tree-shaking anchor — the shipped drivers must be registered before booking runs. */
75
+ export declare function LoadRevenueRecognitionDrivers(): void;
76
+ //# sourceMappingURL=RevenueRecognition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RevenueRecognition.d.ts","sourceRoot":"","sources":["../src/RevenueRecognition.ts"],"names":[],"mappings":"AA0BA,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC1B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,WAAW,EAAE,IAAI,CAAC;IAClB,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACjC,gBAAgB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IACxB,sEAAsE;IACtE,eAAe,EAAE,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,WAAW,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,WAAW,EAAE,CAAC;CAC1B;AAcD;;;;;;GAMG;AACH,8BAAsB,wBAAwB;IAC1C,0EAA0E;aAC1D,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc;IAErE;;;OAGG;IACH,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAQnE,iDAAiD;IACjD,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM;IAMnD,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,IAAI,CAAA;KAAE;CAUrG;AAED;;;GAGG;AACH,qBACa,aAAc,SAAQ,wBAAwB;IAChD,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc;CAI/D;AAED;;;GAGG;AACH,qBACa,kBAAmB,SAAQ,wBAAwB;IACrD,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc;CAc/D;AAED;;;;GAIG;AACH,qBACa,gBAAiB,SAAQ,wBAAwB;IACnD,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc;CAI/D;AAED,wFAAwF;AACxF,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}