@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.
- package/LICENSE +183 -0
- package/README.md +141 -2
- package/dist/AccountingBridge.d.ts +65 -0
- package/dist/AccountingBridge.d.ts.map +1 -0
- package/dist/AccountingBridge.js +102 -0
- package/dist/AccountingBridge.js.map +1 -0
- package/dist/AdvanceOrderStateOperation.d.ts +80 -0
- package/dist/AdvanceOrderStateOperation.d.ts.map +1 -0
- package/dist/AdvanceOrderStateOperation.js +267 -0
- package/dist/AdvanceOrderStateOperation.js.map +1 -0
- package/dist/ApplyAccountCreditOperation.d.ts +97 -0
- package/dist/ApplyAccountCreditOperation.d.ts.map +1 -0
- package/dist/ApplyAccountCreditOperation.js +275 -0
- package/dist/ApplyAccountCreditOperation.js.map +1 -0
- package/dist/BaseDeliveryChannel.d.ts +106 -0
- package/dist/BaseDeliveryChannel.d.ts.map +1 -0
- package/dist/BaseDeliveryChannel.js +36 -0
- package/dist/BaseDeliveryChannel.js.map +1 -0
- package/dist/BasePaymentProvider.d.ts +239 -0
- package/dist/BasePaymentProvider.d.ts.map +1 -0
- package/dist/BasePaymentProvider.js +91 -0
- package/dist/BasePaymentProvider.js.map +1 -0
- package/dist/BundleBehavior.d.ts +139 -0
- package/dist/BundleBehavior.d.ts.map +1 -0
- package/dist/BundleBehavior.js +173 -0
- package/dist/BundleBehavior.js.map +1 -0
- package/dist/BundleEngine.d.ts +60 -0
- package/dist/BundleEngine.d.ts.map +1 -0
- package/dist/BundleEngine.js +197 -0
- package/dist/BundleEngine.js.map +1 -0
- package/dist/CancelSubscriptionOperation.d.ts +98 -0
- package/dist/CancelSubscriptionOperation.d.ts.map +1 -0
- package/dist/CancelSubscriptionOperation.js +317 -0
- package/dist/CancelSubscriptionOperation.js.map +1 -0
- package/dist/CapturePaymentOperation.d.ts +62 -0
- package/dist/CapturePaymentOperation.d.ts.map +1 -0
- package/dist/CapturePaymentOperation.js +463 -0
- package/dist/CapturePaymentOperation.js.map +1 -0
- package/dist/CheckEntitlementOperation.d.ts +27 -0
- package/dist/CheckEntitlementOperation.d.ts.map +1 -0
- package/dist/CheckEntitlementOperation.js +45 -0
- package/dist/CheckEntitlementOperation.js.map +1 -0
- package/dist/CheckoutSessionService.d.ts +258 -0
- package/dist/CheckoutSessionService.d.ts.map +1 -0
- package/dist/CheckoutSessionService.js +1557 -0
- package/dist/CheckoutSessionService.js.map +1 -0
- package/dist/DeliveryBehavior.d.ts +121 -0
- package/dist/DeliveryBehavior.d.ts.map +1 -0
- package/dist/DeliveryBehavior.js +145 -0
- package/dist/DeliveryBehavior.js.map +1 -0
- package/dist/DeliveryRecipientResolver.d.ts +38 -0
- package/dist/DeliveryRecipientResolver.d.ts.map +1 -0
- package/dist/DeliveryRecipientResolver.js +93 -0
- package/dist/DeliveryRecipientResolver.js.map +1 -0
- package/dist/DeliveryResolver.d.ts +14 -0
- package/dist/DeliveryResolver.d.ts.map +1 -0
- package/dist/DeliveryResolver.js +45 -0
- package/dist/DeliveryResolver.js.map +1 -0
- package/dist/EmailDeliveryChannel.d.ts +31 -0
- package/dist/EmailDeliveryChannel.d.ts.map +1 -0
- package/dist/EmailDeliveryChannel.js +158 -0
- package/dist/EmailDeliveryChannel.js.map +1 -0
- package/dist/EntitlementBehavior.d.ts +235 -0
- package/dist/EntitlementBehavior.d.ts.map +1 -0
- package/dist/EntitlementBehavior.js +330 -0
- package/dist/EntitlementBehavior.js.map +1 -0
- package/dist/EntitlementEngine.d.ts +97 -0
- package/dist/EntitlementEngine.d.ts.map +1 -0
- package/dist/EntitlementEngine.js +338 -0
- package/dist/EntitlementEngine.js.map +1 -0
- package/dist/EntitlementGrantClaimDriver.d.ts +42 -0
- package/dist/EntitlementGrantClaimDriver.d.ts.map +1 -0
- package/dist/EntitlementGrantClaimDriver.js +158 -0
- package/dist/EntitlementGrantClaimDriver.js.map +1 -0
- package/dist/EntitlementRead.d.ts +82 -0
- package/dist/EntitlementRead.d.ts.map +1 -0
- package/dist/EntitlementRead.js +368 -0
- package/dist/EntitlementRead.js.map +1 -0
- package/dist/FulfillOrderLinesOperation.d.ts +34 -0
- package/dist/FulfillOrderLinesOperation.d.ts.map +1 -0
- package/dist/FulfillOrderLinesOperation.js +208 -0
- package/dist/FulfillOrderLinesOperation.js.map +1 -0
- package/dist/FulfillmentBehavior.d.ts +101 -0
- package/dist/FulfillmentBehavior.d.ts.map +1 -0
- package/dist/FulfillmentBehavior.js +145 -0
- package/dist/FulfillmentBehavior.js.map +1 -0
- package/dist/GLAccountResolver.d.ts +123 -0
- package/dist/GLAccountResolver.d.ts.map +1 -0
- package/dist/GLAccountResolver.js +175 -0
- package/dist/GLAccountResolver.js.map +1 -0
- package/dist/GetFulfillmentQueueOperation.d.ts +35 -0
- package/dist/GetFulfillmentQueueOperation.d.ts.map +1 -0
- package/dist/GetFulfillmentQueueOperation.js +221 -0
- package/dist/GetFulfillmentQueueOperation.js.map +1 -0
- package/dist/GetOverdueWorklistOperation.d.ts +41 -0
- package/dist/GetOverdueWorklistOperation.d.ts.map +1 -0
- package/dist/GetOverdueWorklistOperation.js +210 -0
- package/dist/GetOverdueWorklistOperation.js.map +1 -0
- package/dist/GiftCardBehavior.d.ts +97 -0
- package/dist/GiftCardBehavior.d.ts.map +1 -0
- package/dist/GiftCardBehavior.js +121 -0
- package/dist/GiftCardBehavior.js.map +1 -0
- package/dist/GiftCardEngine.d.ts +59 -0
- package/dist/GiftCardEngine.d.ts.map +1 -0
- package/dist/GiftCardEngine.js +197 -0
- package/dist/GiftCardEngine.js.map +1 -0
- package/dist/GuestOrderClaimDriver.d.ts +36 -0
- package/dist/GuestOrderClaimDriver.d.ts.map +1 -0
- package/dist/GuestOrderClaimDriver.js +161 -0
- package/dist/GuestOrderClaimDriver.js.map +1 -0
- package/dist/InvoiceBehavior.d.ts +394 -0
- package/dist/InvoiceBehavior.d.ts.map +1 -0
- package/dist/InvoiceBehavior.js +496 -0
- package/dist/InvoiceBehavior.js.map +1 -0
- package/dist/InvoiceBuilder.d.ts +48 -0
- package/dist/InvoiceBuilder.d.ts.map +1 -0
- package/dist/InvoiceBuilder.js +352 -0
- package/dist/InvoiceBuilder.js.map +1 -0
- package/dist/InvoiceDisplay.d.ts +96 -0
- package/dist/InvoiceDisplay.d.ts.map +1 -0
- package/dist/InvoiceDisplay.js +121 -0
- package/dist/InvoiceDisplay.js.map +1 -0
- package/dist/ListEntitlementsOperation.d.ts +21 -0
- package/dist/ListEntitlementsOperation.d.ts.map +1 -0
- package/dist/ListEntitlementsOperation.js +39 -0
- package/dist/ListEntitlementsOperation.js.map +1 -0
- package/dist/ManualPaymentProvider.d.ts +19 -0
- package/dist/ManualPaymentProvider.d.ts.map +1 -0
- package/dist/ManualPaymentProvider.js +92 -0
- package/dist/ManualPaymentProvider.js.map +1 -0
- package/dist/OrderEntityServer.d.ts +542 -0
- package/dist/OrderEntityServer.d.ts.map +1 -0
- package/dist/OrderEntityServer.js +2100 -0
- package/dist/OrderEntityServer.js.map +1 -0
- package/dist/OrderJournalEntryFactory.d.ts +140 -0
- package/dist/OrderJournalEntryFactory.d.ts.map +1 -0
- package/dist/OrderJournalEntryFactory.js +466 -0
- package/dist/OrderJournalEntryFactory.js.map +1 -0
- package/dist/OrderLineEntityServer.d.ts +90 -0
- package/dist/OrderLineEntityServer.d.ts.map +1 -0
- package/dist/OrderLineEntityServer.js +260 -0
- package/dist/OrderLineEntityServer.js.map +1 -0
- package/dist/OrdersSettings.d.ts +36 -0
- package/dist/OrdersSettings.d.ts.map +1 -0
- package/dist/OrdersSettings.js +147 -0
- package/dist/OrdersSettings.js.map +1 -0
- package/dist/PaymentAllocationFactory.d.ts +128 -0
- package/dist/PaymentAllocationFactory.d.ts.map +1 -0
- package/dist/PaymentAllocationFactory.js +235 -0
- package/dist/PaymentAllocationFactory.js.map +1 -0
- package/dist/PaymentHeaderEntityServer.d.ts +180 -0
- package/dist/PaymentHeaderEntityServer.d.ts.map +1 -0
- package/dist/PaymentHeaderEntityServer.js +656 -0
- package/dist/PaymentHeaderEntityServer.js.map +1 -0
- package/dist/PaymentIntentService.d.ts +101 -0
- package/dist/PaymentIntentService.d.ts.map +1 -0
- package/dist/PaymentIntentService.js +150 -0
- package/dist/PaymentIntentService.js.map +1 -0
- package/dist/PaymentJournalEntryFactory.d.ts +99 -0
- package/dist/PaymentJournalEntryFactory.d.ts.map +1 -0
- package/dist/PaymentJournalEntryFactory.js +127 -0
- package/dist/PaymentJournalEntryFactory.js.map +1 -0
- package/dist/PaymentLineEntityServer.d.ts +73 -0
- package/dist/PaymentLineEntityServer.d.ts.map +1 -0
- package/dist/PaymentLineEntityServer.js +303 -0
- package/dist/PaymentLineEntityServer.js.map +1 -0
- package/dist/PaymentProviderBehavior.d.ts +222 -0
- package/dist/PaymentProviderBehavior.d.ts.map +1 -0
- package/dist/PaymentProviderBehavior.js +368 -0
- package/dist/PaymentProviderBehavior.js.map +1 -0
- package/dist/PaymentProviderResolver.d.ts +84 -0
- package/dist/PaymentProviderResolver.d.ts.map +1 -0
- package/dist/PaymentProviderResolver.js +220 -0
- package/dist/PaymentProviderResolver.js.map +1 -0
- package/dist/PaymentReversalFactory.d.ts +107 -0
- package/dist/PaymentReversalFactory.d.ts.map +1 -0
- package/dist/PaymentReversalFactory.js +174 -0
- package/dist/PaymentReversalFactory.js.map +1 -0
- package/dist/PaymentSettlement.d.ts +56 -0
- package/dist/PaymentSettlement.d.ts.map +1 -0
- package/dist/PaymentSettlement.js +243 -0
- package/dist/PaymentSettlement.js.map +1 -0
- package/dist/PaymentTermsBehavior.d.ts +109 -0
- package/dist/PaymentTermsBehavior.d.ts.map +1 -0
- package/dist/PaymentTermsBehavior.js +172 -0
- package/dist/PaymentTermsBehavior.js.map +1 -0
- package/dist/PaymentWebhookHandler.d.ts +103 -0
- package/dist/PaymentWebhookHandler.d.ts.map +1 -0
- package/dist/PaymentWebhookHandler.js +246 -0
- package/dist/PaymentWebhookHandler.js.map +1 -0
- package/dist/PreviewPriceOperation.d.ts +62 -0
- package/dist/PreviewPriceOperation.d.ts.map +1 -0
- package/dist/PreviewPriceOperation.js +161 -0
- package/dist/PreviewPriceOperation.js.map +1 -0
- package/dist/PriceOrderOperation.d.ts +93 -0
- package/dist/PriceOrderOperation.d.ts.map +1 -0
- package/dist/PriceOrderOperation.js +146 -0
- package/dist/PriceOrderOperation.js.map +1 -0
- package/dist/ProductPriceEntityServer.d.ts +34 -0
- package/dist/ProductPriceEntityServer.d.ts.map +1 -0
- package/dist/ProductPriceEntityServer.js +97 -0
- package/dist/ProductPriceEntityServer.js.map +1 -0
- package/dist/RefundPaymentOperation.d.ts +61 -0
- package/dist/RefundPaymentOperation.d.ts.map +1 -0
- package/dist/RefundPaymentOperation.js +177 -0
- package/dist/RefundPaymentOperation.js.map +1 -0
- package/dist/RevenueRecognition.d.ts +76 -0
- package/dist/RevenueRecognition.d.ts.map +1 -0
- package/dist/RevenueRecognition.js +133 -0
- package/dist/RevenueRecognition.js.map +1 -0
- package/dist/ReversalBehavior.d.ts +82 -0
- package/dist/ReversalBehavior.d.ts.map +1 -0
- package/dist/ReversalBehavior.js +100 -0
- package/dist/ReversalBehavior.js.map +1 -0
- package/dist/ReversalResolver.d.ts +37 -0
- package/dist/ReversalResolver.d.ts.map +1 -0
- package/dist/ReversalResolver.js +96 -0
- package/dist/ReversalResolver.js.map +1 -0
- package/dist/SpawnRenewalsOperation.d.ts +109 -0
- package/dist/SpawnRenewalsOperation.d.ts.map +1 -0
- package/dist/SpawnRenewalsOperation.js +295 -0
- package/dist/SpawnRenewalsOperation.js.map +1 -0
- package/dist/StoredValuePaymentProvider.d.ts +52 -0
- package/dist/StoredValuePaymentProvider.d.ts.map +1 -0
- package/dist/StoredValuePaymentProvider.js +205 -0
- package/dist/StoredValuePaymentProvider.js.map +1 -0
- package/dist/StripeACHPaymentProvider.d.ts +50 -0
- package/dist/StripeACHPaymentProvider.d.ts.map +1 -0
- package/dist/StripeACHPaymentProvider.js +211 -0
- package/dist/StripeACHPaymentProvider.js.map +1 -0
- package/dist/StripePaymentProvider.d.ts +83 -0
- package/dist/StripePaymentProvider.d.ts.map +1 -0
- package/dist/StripePaymentProvider.js +442 -0
- package/dist/StripePaymentProvider.js.map +1 -0
- package/dist/SubscriptionBehavior.d.ts +197 -0
- package/dist/SubscriptionBehavior.d.ts.map +1 -0
- package/dist/SubscriptionBehavior.js +415 -0
- package/dist/SubscriptionBehavior.js.map +1 -0
- package/dist/checkoutCaptureAlert.d.ts +16 -0
- package/dist/checkoutCaptureAlert.d.ts.map +1 -0
- package/dist/checkoutCaptureAlert.js +55 -0
- package/dist/checkoutCaptureAlert.js.map +1 -0
- package/dist/checkoutCaptureRetry.d.ts +27 -0
- package/dist/checkoutCaptureRetry.d.ts.map +1 -0
- package/dist/checkoutCaptureRetry.js +50 -0
- package/dist/checkoutCaptureRetry.js.map +1 -0
- package/dist/claimDriverHelpers.d.ts +15 -0
- package/dist/claimDriverHelpers.d.ts.map +1 -0
- package/dist/claimDriverHelpers.js +34 -0
- package/dist/claimDriverHelpers.js.map +1 -0
- package/dist/entity-names.d.ts +15 -0
- package/dist/entity-names.d.ts.map +1 -0
- package/dist/entity-names.js +15 -0
- package/dist/entity-names.js.map +1 -0
- package/dist/identityClaimContracts.d.ts +118 -0
- package/dist/identityClaimContracts.d.ts.map +1 -0
- package/dist/identityClaimContracts.js +58 -0
- package/dist/identityClaimContracts.js.map +1 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +115 -0
- package/dist/index.js.map +1 -0
- package/dist/sql-guards.d.ts +78 -0
- package/dist/sql-guards.d.ts.map +1 -0
- package/dist/sql-guards.js +115 -0
- package/dist/sql-guards.js.map +1 -0
- package/package.json +50 -5
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a purchase grants, and for how long — the arithmetic, with no database in sight.
|
|
3
|
+
*
|
|
4
|
+
* A `ProductEntitlement` is a TEMPLATE: this product confers this feature, access level, or resource
|
|
5
|
+
* quantity. An `EntitlementGrant` is the INSTANCE created when somebody buys it, carrying a
|
|
6
|
+
* beneficiary and a validity window. This module decides what that instance should look like.
|
|
7
|
+
*
|
|
8
|
+
* THREE SETTINGS, ONE WALK (D76). Timing, quantity and validity are all configurable per deployment,
|
|
9
|
+
* and all three resolve down the SAME chain taxability uses:
|
|
10
|
+
*
|
|
11
|
+
* product → its category → that category's ancestors → the product type
|
|
12
|
+
*
|
|
13
|
+
* Nullable at product and category so "ask my parent" is sayable; NOT NULL at the type so the walk
|
|
14
|
+
* always terminates with a real answer. Sharing the chain is deliberate rather than tidy: anyone who
|
|
15
|
+
* understands how taxability resolves already understands this, and the two cannot drift into
|
|
16
|
+
* disagreeing about what "the product's category tree" means.
|
|
17
|
+
*
|
|
18
|
+
* WHY VALIDITY IS DIFFERENT FROM THE OTHER TWO. It is read from the `ProductEntitlement` FIRST and
|
|
19
|
+
* from the walk only when the template is silent, because one product can grant two entitlements with
|
|
20
|
+
* different windows. A course might give perpetual access to the downloadable materials and ninety
|
|
21
|
+
* days in the discussion forum. A policy resolved purely from the product could not say that.
|
|
22
|
+
*
|
|
23
|
+
* THE NON-SUBSCRIPTION CASES ARE THE INTERESTING ONES. It is tempting to assume access follows a
|
|
24
|
+
* subscription term, because that is the case with the most moving parts. But the commonest grants
|
|
25
|
+
* have nothing to do with subscriptions at all: a digital download is good forever, and a ticket to
|
|
26
|
+
* an online event is good for the length of the event and not a minute longer.
|
|
27
|
+
*
|
|
28
|
+
* CONNECTS TO:
|
|
29
|
+
* SERVER: ./EntitlementEngine.ts (the lookups and the writes)
|
|
30
|
+
* CALLER: OrderEntityServer (grants are created inside the booking transaction)
|
|
31
|
+
* MIRROR: ./TaxResolver.ts — the same walk, for the same reason
|
|
32
|
+
* DOC: plans/archive/bizapps-orders-master.md D27, D76
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Walk the chain for all three settings at once.
|
|
36
|
+
*
|
|
37
|
+
* ONE walk rather than three, because they resolve identically and a caller that walked separately
|
|
38
|
+
* would do the same lookups three times and could be given three different category chains.
|
|
39
|
+
*
|
|
40
|
+
* `entitlement` is consulted for VALIDITY ONLY — see the module header. Timing and quantity are
|
|
41
|
+
* properties of the purchase, not of the individual thing granted: it would make no sense for one
|
|
42
|
+
* entitlement on a line to appear at confirm and another to wait for payment.
|
|
43
|
+
*/
|
|
44
|
+
export function ResolveEntitlementPolicy(product,
|
|
45
|
+
/** NEAREST FIRST: the product's own category, then its parent, up to the root. */
|
|
46
|
+
categoryChain, type, entitlement) {
|
|
47
|
+
const walk = (key, typeValue) => {
|
|
48
|
+
const own = product[key];
|
|
49
|
+
if (own != null)
|
|
50
|
+
return { value: own, level: 'Product' };
|
|
51
|
+
for (const level of categoryChain) {
|
|
52
|
+
const v = level[key];
|
|
53
|
+
if (v != null)
|
|
54
|
+
return { value: v, level: 'ProductCategory' };
|
|
55
|
+
}
|
|
56
|
+
return { value: typeValue, level: 'ProductType' };
|
|
57
|
+
};
|
|
58
|
+
const timing = walk('EntitlementGrantTiming', type.DefaultEntitlementGrantTiming);
|
|
59
|
+
const quantity = walk('EntitlementQuantityMode', type.DefaultEntitlementQuantityMode);
|
|
60
|
+
// The template wins on validity, and only on validity.
|
|
61
|
+
const fromTemplate = entitlement?.ValidityMode ?? null;
|
|
62
|
+
const validity = fromTemplate != null
|
|
63
|
+
? { value: fromTemplate, level: 'ProductEntitlement' }
|
|
64
|
+
: walk('EntitlementValidityMode', type.DefaultEntitlementValidityMode);
|
|
65
|
+
return {
|
|
66
|
+
GrantTiming: timing.value,
|
|
67
|
+
QuantityMode: quantity.value,
|
|
68
|
+
ValidityMode: validity.value,
|
|
69
|
+
DecidedAt: {
|
|
70
|
+
GrantTiming: timing.level,
|
|
71
|
+
QuantityMode: quantity.level,
|
|
72
|
+
ValidityMode: validity.level,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* How much of the entitlement this line grants.
|
|
78
|
+
*
|
|
79
|
+
* `PerUnit` multiplies, so three 5-seat packs give fifteen seats — which is what somebody buying
|
|
80
|
+
* three packs expects. `Flat` ignores the line quantity entirely, for entitlements that are not
|
|
81
|
+
* countable: "access to the member portal" does not become three portals.
|
|
82
|
+
*
|
|
83
|
+
* ROUNDED UP, and only ever up. A prorated subscription line carries a fractional quantity (0.5833
|
|
84
|
+
* for a short first period), and 0.5833 × 5 seats is 2.9165. Rounding down hands the customer two
|
|
85
|
+
* seats for a five-seat product and generates a support ticket; rounding up hands them three and
|
|
86
|
+
* generates nothing. Under-granting is the expensive direction, exactly as under-collecting is with
|
|
87
|
+
* tax, and for the same reason: the customer notices.
|
|
88
|
+
*
|
|
89
|
+
* A null template quantity means the entitlement is not a countable thing (a Feature or AccessLevel
|
|
90
|
+
* rather than a ResourceQuantity), and stays null rather than becoming zero — zero would read as
|
|
91
|
+
* "granted none of it".
|
|
92
|
+
*/
|
|
93
|
+
export function ResolveGrantQuantity(templateQuantity, lineQuantity, mode) {
|
|
94
|
+
if (templateQuantity == null)
|
|
95
|
+
return null;
|
|
96
|
+
if (mode === 'Flat')
|
|
97
|
+
return templateQuantity;
|
|
98
|
+
const scaled = templateQuantity * Math.abs(lineQuantity);
|
|
99
|
+
return Math.ceil(Math.round(scaled * 1e6) / 1e6);
|
|
100
|
+
}
|
|
101
|
+
const HOUR = 60 * 60 * 1000;
|
|
102
|
+
const DAY = 24 * HOUR;
|
|
103
|
+
/**
|
|
104
|
+
* Turn a validity mode plus its context into an actual window.
|
|
105
|
+
*
|
|
106
|
+
* FALLS BACK RATHER THAN THROWING when a mode's own inputs are missing, and records what it actually
|
|
107
|
+
* applied. A ticket whose product has no event dates should not fail the customer's order — but the
|
|
108
|
+
* grant must not silently claim to be an event window either, so `ModeApplied` says `Perpetual` and
|
|
109
|
+
* an auditor can see the mode was requested and could not be honoured.
|
|
110
|
+
*
|
|
111
|
+
* The one thing it will not do is produce a window that ends before it starts. An event whose end
|
|
112
|
+
* date precedes its start, or a lag that somehow lands behind the lead, yields a window ending at its
|
|
113
|
+
* own start — an instantaneous grant, which reads as "nothing was granted" and is at least honest,
|
|
114
|
+
* rather than a negative window the database CHECK would reject at insert time with an error naming
|
|
115
|
+
* neither the product nor the event.
|
|
116
|
+
*/
|
|
117
|
+
export function ResolveValidityWindow(mode, ctx) {
|
|
118
|
+
const clamp = (from, to) => ({
|
|
119
|
+
ValidFrom: from,
|
|
120
|
+
ValidTo: to != null && to.getTime() < from.getTime() ? new Date(from.getTime()) : to,
|
|
121
|
+
ModeApplied: mode,
|
|
122
|
+
});
|
|
123
|
+
switch (mode) {
|
|
124
|
+
case 'Perpetual':
|
|
125
|
+
// A digital download. Good forever, and that is a fact rather than an omission.
|
|
126
|
+
return { ValidFrom: ctx.GrantedOn, ValidTo: null, ModeApplied: 'Perpetual' };
|
|
127
|
+
case 'FixedDuration': {
|
|
128
|
+
const days = ctx.DurationDays ?? 0;
|
|
129
|
+
if (days <= 0) {
|
|
130
|
+
return { ValidFrom: ctx.GrantedOn, ValidTo: null, ModeApplied: 'Perpetual' };
|
|
131
|
+
}
|
|
132
|
+
return clamp(ctx.GrantedOn, new Date(ctx.GrantedOn.getTime() + days * DAY));
|
|
133
|
+
}
|
|
134
|
+
case 'EventWindow': {
|
|
135
|
+
if (!ctx.EventStartsAt || !ctx.EventEndsAt) {
|
|
136
|
+
return { ValidFrom: ctx.GrantedOn, ValidTo: null, ModeApplied: 'Perpetual' };
|
|
137
|
+
}
|
|
138
|
+
// Lead and lag are separate decisions, which is why they are separate columns: opening
|
|
139
|
+
// the stream an hour early is a courtesy, and leaving the recording up for a day
|
|
140
|
+
// afterwards is a different courtesy with different cost.
|
|
141
|
+
const lead = (ctx.AccessLeadHours ?? 0) * HOUR;
|
|
142
|
+
const lag = (ctx.AccessLagHours ?? 0) * HOUR;
|
|
143
|
+
return clamp(new Date(ctx.EventStartsAt.getTime() - lead), new Date(ctx.EventEndsAt.getTime() + lag));
|
|
144
|
+
}
|
|
145
|
+
case 'SubscriptionTerm': {
|
|
146
|
+
if (!ctx.TermStartDate || !ctx.TermEndDate) {
|
|
147
|
+
// Asked to follow a term that does not exist — a non-subscription product configured
|
|
148
|
+
// as though it were one. Perpetual is the safe read: the customer bought something.
|
|
149
|
+
return { ValidFrom: ctx.GrantedOn, ValidTo: null, ModeApplied: 'Perpetual' };
|
|
150
|
+
}
|
|
151
|
+
return clamp(ctx.TermStartDate, ctx.TermEndDate);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Should the grant be ACTIVE yet, given when access is supposed to begin?
|
|
157
|
+
*
|
|
158
|
+
* The grant row is written at confirm either way — downstream apps poll grants (D27), and a grant
|
|
159
|
+
* that does not exist until payment clears cannot be seen coming. What timing changes is the STATUS:
|
|
160
|
+
*
|
|
161
|
+
* OnConfirm Active immediately. A confirmed order is a claim on the seller.
|
|
162
|
+
* OnPaidInFull Suspended until the order's balance reaches zero. Access follows cash.
|
|
163
|
+
* OnActivation Suspended until something explicitly activates it. Note that nothing does yet —
|
|
164
|
+
* this exists because it was asked for, and a deployment choosing it is choosing to
|
|
165
|
+
* grant nothing until it builds the caller.
|
|
166
|
+
*
|
|
167
|
+
* `Suspended` rather than a missing row, and rather than `Revoked`: revoked means somebody took it
|
|
168
|
+
* away, which is a different fact that an access dispute turns on.
|
|
169
|
+
*/
|
|
170
|
+
export function InitialGrantStatus(timing, order) {
|
|
171
|
+
if (timing === 'OnConfirm')
|
|
172
|
+
return 'Active';
|
|
173
|
+
if (timing === 'OnActivation')
|
|
174
|
+
return 'Suspended';
|
|
175
|
+
// OnPaidInFull. A zero-value order is paid by definition — a free line should not leave the
|
|
176
|
+
// customer waiting for a payment that will never arrive.
|
|
177
|
+
const gross = Number(order.TotalGross ?? 0);
|
|
178
|
+
if (gross <= 0)
|
|
179
|
+
return 'Active';
|
|
180
|
+
return Number(order.Balance ?? gross) <= 0 ? 'Active' : 'Suspended';
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* How much of a grant survives a partial return.
|
|
184
|
+
*
|
|
185
|
+
* A customer who bought five seats and sent two back keeps three. Returning everything revokes the
|
|
186
|
+
* grant outright rather than leaving a zero-quantity row, because zero is indistinguishable from a
|
|
187
|
+
* Feature grant that never had a quantity.
|
|
188
|
+
*/
|
|
189
|
+
export function ReduceGrantForReturn(grantQuantity, originalLineQuantity, returnedQuantity) {
|
|
190
|
+
const returned = Math.abs(returnedQuantity);
|
|
191
|
+
const original = Math.abs(originalLineQuantity);
|
|
192
|
+
if (original <= 0 || returned >= original)
|
|
193
|
+
return { Quantity: grantQuantity, Revoke: true };
|
|
194
|
+
if (grantQuantity == null) {
|
|
195
|
+
// A Feature or AccessLevel: not countable, so a partial return cannot partially remove it.
|
|
196
|
+
// The customer still holds some of the thing that conferred it, so the grant stands.
|
|
197
|
+
return { Quantity: null, Revoke: false };
|
|
198
|
+
}
|
|
199
|
+
const remaining = grantQuantity * ((original - returned) / original);
|
|
200
|
+
// Rounded UP, for the same reason ResolveGrantQuantity rounds up: taking away a seat the customer
|
|
201
|
+
// still paid for is worse than leaving one they did not.
|
|
202
|
+
return { Quantity: Math.ceil(Math.round(remaining * 1e6) / 1e6), Revoke: false };
|
|
203
|
+
}
|
|
204
|
+
/** Subscription statuses that still confer access. Anything else is inactive. */
|
|
205
|
+
const ACCESSING_SUB_STATUS = new Set(['Active', 'Trialing']);
|
|
206
|
+
/**
|
|
207
|
+
* Is this grant in force at `asOf`?
|
|
208
|
+
*
|
|
209
|
+
* ONE function, because a second “is this in force” will drift from the first — that failure
|
|
210
|
+
* has already bitten this codebase twice. Bounds are inclusive: access holds at the exact
|
|
211
|
+
* `ValidFrom` / `ValidTo` / cancel `EndDate` instant.
|
|
212
|
+
*
|
|
213
|
+
* Cancelled subscriptions are the interesting case. The grant's `ValidTo` is the original term
|
|
214
|
+
* end, which is a LIE after an immediate cancel: access-through is `subscription.EndDate`
|
|
215
|
+
* (grace included). Grace can also EXTEND past `ValidTo`. Either way, `EndDate` is the bound
|
|
216
|
+
* once the sub is `Canceled`. Paused/Migrated refuse immediately — they have no grace story.
|
|
217
|
+
*/
|
|
218
|
+
export function EvaluateGrantAccess(grant, asOf, subscription, term) {
|
|
219
|
+
const denied = (Decision) => ({
|
|
220
|
+
HasAccess: false,
|
|
221
|
+
Decision,
|
|
222
|
+
ValidFrom: grant.ValidFrom,
|
|
223
|
+
ValidTo: grant.ValidTo,
|
|
224
|
+
});
|
|
225
|
+
const granted = () => ({
|
|
226
|
+
HasAccess: true,
|
|
227
|
+
Decision: 'Granted',
|
|
228
|
+
ValidFrom: grant.ValidFrom,
|
|
229
|
+
ValidTo: grant.ValidTo,
|
|
230
|
+
});
|
|
231
|
+
if (grant.Status === 'Revoked')
|
|
232
|
+
return denied('Revoked');
|
|
233
|
+
if (grant.Status === 'Suspended')
|
|
234
|
+
return denied('Suspended');
|
|
235
|
+
if (grant.Status === 'Expired')
|
|
236
|
+
return denied('Expired');
|
|
237
|
+
if (grant.Status !== 'Active')
|
|
238
|
+
return denied('NoGrant');
|
|
239
|
+
if (grant.ValidFrom && asOf.getTime() < grant.ValidFrom.getTime()) {
|
|
240
|
+
return denied('NotYetValid');
|
|
241
|
+
}
|
|
242
|
+
// Linked rows we could not load: fail closed rather than grant on a guess.
|
|
243
|
+
if (grant.LinkedToSubscription && !subscription)
|
|
244
|
+
return denied('SubscriptionInactive');
|
|
245
|
+
if (grant.LinkedToTerm && !term)
|
|
246
|
+
return denied('SubscriptionInactive');
|
|
247
|
+
if (subscription && !ACCESSING_SUB_STATUS.has(subscription.Status)) {
|
|
248
|
+
if (subscription.Status === 'Canceled') {
|
|
249
|
+
if (!subscription.EndDate || asOf.getTime() > subscription.EndDate.getTime()) {
|
|
250
|
+
return {
|
|
251
|
+
HasAccess: false,
|
|
252
|
+
Decision: 'SubscriptionInactive',
|
|
253
|
+
ValidFrom: grant.ValidFrom,
|
|
254
|
+
ValidTo: subscription.EndDate,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
// Inside access-through: EndDate is the operative bound — it can cut the original
|
|
258
|
+
// ValidTo short or extend it (grace). Report that, so CacheUntil and "access
|
|
259
|
+
// through {ValidTo}" both tell the truth.
|
|
260
|
+
return {
|
|
261
|
+
HasAccess: true,
|
|
262
|
+
Decision: 'Granted',
|
|
263
|
+
ValidFrom: grant.ValidFrom,
|
|
264
|
+
ValidTo: subscription.EndDate,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return denied('SubscriptionInactive');
|
|
268
|
+
}
|
|
269
|
+
if (grant.ValidTo && asOf.getTime() > grant.ValidTo.getTime()) {
|
|
270
|
+
return denied('Expired');
|
|
271
|
+
}
|
|
272
|
+
if (term) {
|
|
273
|
+
if (asOf.getTime() < term.StartDate.getTime())
|
|
274
|
+
return denied('NotYetValid');
|
|
275
|
+
if (asOf.getTime() > term.EndDate.getTime())
|
|
276
|
+
return denied('SubscriptionInactive');
|
|
277
|
+
}
|
|
278
|
+
return granted();
|
|
279
|
+
}
|
|
280
|
+
/** Lower rank wins among denials — “not yet” is a more useful screen than “an old grant was revoked”. */
|
|
281
|
+
const DENIAL_RANK = {
|
|
282
|
+
Granted: 0,
|
|
283
|
+
NotYetValid: 1,
|
|
284
|
+
Suspended: 2,
|
|
285
|
+
SubscriptionInactive: 3,
|
|
286
|
+
Expired: 4,
|
|
287
|
+
Revoked: 5,
|
|
288
|
+
NoGrant: 6,
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* One answer from many grants for the same Code. A Granted perpetual beats a Granted with an
|
|
292
|
+
* end; among denials the most useful reason wins, not the most recent row.
|
|
293
|
+
*/
|
|
294
|
+
export function PickWinningAccess(results) {
|
|
295
|
+
if (!results.length)
|
|
296
|
+
return null;
|
|
297
|
+
const granted = results.filter((r) => r.HasAccess);
|
|
298
|
+
if (granted.length) {
|
|
299
|
+
return granted.reduce((best, r) => {
|
|
300
|
+
if (best.ValidTo == null)
|
|
301
|
+
return best;
|
|
302
|
+
if (r.ValidTo == null)
|
|
303
|
+
return r;
|
|
304
|
+
return r.ValidTo.getTime() > best.ValidTo.getTime() ? r : best;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return results.reduce((best, r) => DENIAL_RANK[r.Decision] < DENIAL_RANK[best.Decision] ? r : best);
|
|
308
|
+
}
|
|
309
|
+
/** Default CacheUntil cap for a check answer. Short so a revoke is felt quickly. */
|
|
310
|
+
export const ENTITLEMENT_CHECK_TTL_MS = 60_000;
|
|
311
|
+
/**
|
|
312
|
+
* When the caller may reuse this answer. Never past `ValidTo` on a grant that is in force —
|
|
313
|
+
* caching a Granted past its own end is how paid content stays open after the window closed.
|
|
314
|
+
*/
|
|
315
|
+
export function CacheUntilFor(evaluatedAt, validTo, hasAccess) {
|
|
316
|
+
const cap = new Date(evaluatedAt.getTime() + ENTITLEMENT_CHECK_TTL_MS);
|
|
317
|
+
if (hasAccess && validTo != null && validTo.getTime() < cap.getTime()) {
|
|
318
|
+
return new Date(validTo.getTime());
|
|
319
|
+
}
|
|
320
|
+
return cap;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* After `Orders.CancelSubscription`, stored grant Status is a lie unless we revoke when
|
|
324
|
+
* access has already ended. Grace (`AccessThroughDate` in the future) leaves the rows
|
|
325
|
+
* standing — the evaluator honours `subscription.EndDate`.
|
|
326
|
+
*/
|
|
327
|
+
export function ShouldRevokeGrantsOnCancel(accessThroughDate, now) {
|
|
328
|
+
return now.getTime() > accessThroughDate.getTime();
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=EntitlementBehavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntitlementBehavior.js","sourceRoot":"","sources":["../src/EntitlementBehavior.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntitlementEngine — turns a confirmed order into the grants it confers, and unwinds them on return.
|
|
3
|
+
*
|
|
4
|
+
* The server half of `EntitlementBehavior`: this finds the rows, that decides what they mean. Same
|
|
5
|
+
* split as `PriceResolver`/`PricingBehavior`, for the same reason — the rules about quantity, validity
|
|
6
|
+
* and timing are provable without a database, and only finding the templates needs one.
|
|
7
|
+
*
|
|
8
|
+
* BATCHED, NOT PER LINE. Every lookup here is one `RunView` across the whole order: the entitlement
|
|
9
|
+
* templates, the products, the category ancestors, the event dates, the terms. A sixty-line order
|
|
10
|
+
* would otherwise issue several hundred round trips inside the booking transaction, holding write
|
|
11
|
+
* locks the entire time. The volume bundle confirms sixty-line orders precisely to make that visible.
|
|
12
|
+
*
|
|
13
|
+
* THE FAST PATH MATTERS. Most orders grant nothing at all — no product on them has a single
|
|
14
|
+
* `ProductEntitlement` row. That case is one query and an early return, because entitlements must not
|
|
15
|
+
* make ordinary orders slower.
|
|
16
|
+
*
|
|
17
|
+
* WHY GRANTS ARE WRITTEN INSIDE THE BOOKING TRANSACTION. Access and the receivable are the same
|
|
18
|
+
* decision: an order that booked revenue but failed to grant access has charged for nothing, and one
|
|
19
|
+
* that granted access without booking has given something away. All-or-none is the only coherent
|
|
20
|
+
* option, which is also why a failure here rolls back the confirm rather than being logged.
|
|
21
|
+
*
|
|
22
|
+
* CONNECTS TO:
|
|
23
|
+
* PURE: ./EntitlementBehavior.ts
|
|
24
|
+
* CALLER: OrderEntityServer.Save (after subscriptions materialize — terms drive validity)
|
|
25
|
+
* DOC: plans/archive/bizapps-orders-master.md D27, D76
|
|
26
|
+
*/
|
|
27
|
+
import { EntitySaveOptions, IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
28
|
+
import { type ValidityMode } from './EntitlementBehavior.js';
|
|
29
|
+
/** The line facts grant creation needs. Deliberately structural, so a caller can pass anything shaped right. */
|
|
30
|
+
export interface GrantableLine {
|
|
31
|
+
ID: string;
|
|
32
|
+
ProductID: string;
|
|
33
|
+
Quantity: number;
|
|
34
|
+
/** Per-line ship-to doubles as WHO an intangible line is for (D61) — the natural beneficiary. */
|
|
35
|
+
ShipToPersonID?: string | null;
|
|
36
|
+
ShipToOrganizationID?: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** The order facts. `Balance`/`TotalGross` decide whether an OnPaidInFull grant starts Active. */
|
|
39
|
+
export interface GrantableOrder {
|
|
40
|
+
ID: string;
|
|
41
|
+
OrderDate: Date;
|
|
42
|
+
Balance: number | null;
|
|
43
|
+
TotalGross: number | null;
|
|
44
|
+
BillToPersonID?: string | null;
|
|
45
|
+
BillToOrganizationID?: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** What `materializeSubscriptions` already knows, passed in rather than re-queried. */
|
|
48
|
+
export interface TermForLine {
|
|
49
|
+
ID: string;
|
|
50
|
+
StartDate: Date;
|
|
51
|
+
EndDate: Date;
|
|
52
|
+
}
|
|
53
|
+
export interface GrantOutcome {
|
|
54
|
+
Created: number;
|
|
55
|
+
/** Per grant, for the caller to log or assert on. */
|
|
56
|
+
Grants: Array<{
|
|
57
|
+
ID: string;
|
|
58
|
+
OrderLineID: string;
|
|
59
|
+
Code: string;
|
|
60
|
+
Status: string;
|
|
61
|
+
ValidityModeApplied: ValidityMode;
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create every grant this order's lines confer.
|
|
66
|
+
*
|
|
67
|
+
* A REVERSAL LINE GRANTS NOTHING. It unwinds a purchase, so running the grant path on it would hand
|
|
68
|
+
* the customer access as a side effect of returning the thing that conferred it — the same class of
|
|
69
|
+
* mistake as materializing a second subscription on a cancellation (D16).
|
|
70
|
+
*/
|
|
71
|
+
export declare function CreateEntitlementGrants(order: GrantableOrder, lines: GrantableLine[], termsByLine: Map<string, TermForLine>, provider: IMetadataProvider, user: UserInfo, options?: EntitySaveOptions): Promise<GrantOutcome>;
|
|
72
|
+
/**
|
|
73
|
+
* Unwind the grants a returned line conferred.
|
|
74
|
+
*
|
|
75
|
+
* A full return REVOKES; a partial return reduces the quantity proportionally. Revocation stamps
|
|
76
|
+
* `RevokedAt` and a reason, because "this grant is revoked" with no record of when or why is exactly
|
|
77
|
+
* the shape that makes an access dispute unanswerable — and the database CHECK enforces the pairing.
|
|
78
|
+
*
|
|
79
|
+
* Uncountable grants (a Feature, an AccessLevel) survive a partial return: the customer still holds
|
|
80
|
+
* some of the thing that conferred it. Only sending all of it back takes the feature away.
|
|
81
|
+
*/
|
|
82
|
+
export declare function RevokeGrantsForReturn(originOrderLineID: string, originQuantity: number, returnedQuantity: number, reason: string, provider: IMetadataProvider, user: UserInfo, options?: EntitySaveOptions): Promise<{
|
|
83
|
+
Revoked: number;
|
|
84
|
+
Reduced: number;
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* After a subscription cancel, make stored grant Status less of a lie — still not the read path.
|
|
88
|
+
*
|
|
89
|
+
* Access-through in the future (grace) leaves the rows `Active`; `EvaluateGrantAccess` honours
|
|
90
|
+
* `subscription.EndDate`. Access-through already passed (immediate cancel, no remaining grace)
|
|
91
|
+
* REVOKES, with a reason, so a poll of Status at least stops saying Active.
|
|
92
|
+
*/
|
|
93
|
+
export declare function RevokeGrantsForCanceledSubscription(subscriptionID: string, accessThroughDate: Date, reason: string, provider: IMetadataProvider, user: UserInfo, options?: EntitySaveOptions, now?: Date): Promise<{
|
|
94
|
+
Revoked: number;
|
|
95
|
+
LeftStanding: number;
|
|
96
|
+
}>;
|
|
97
|
+
//# sourceMappingURL=EntitlementEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntitlementEngine.d.ts","sourceRoot":"","sources":["../src/EntitlementEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAGH,iBAAiB,EACjB,iBAAiB,EAGjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAUH,KAAK,YAAY,EACpB,MAAM,0BAA0B,CAAC;AAclC,gHAAgH;AAChH,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,uFAAuF;AACvF,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;CACvH;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CACzC,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,aAAa,EAAE,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAsKvB;AA+ID;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACvC,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC5B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2C/C;AAED;;;;;;GAMG;AACH,wBAAsB,mCAAmC,CACrD,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,iBAAiB,EAC3B,GAAG,GAAE,IAAiB,GACvB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCpD"}
|