@powerhousedao/service-offering 0.0.6 → 0.0.7
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/dist/editors/subscription-instance-editor/components/mapOfferingToSubscription.d.ts +6 -6
- package/dist/editors/subscription-instance-editor/components/mapOfferingToSubscription.d.ts.map +1 -1
- package/dist/editors/subscription-instance-editor/components/mapOfferingToSubscription.js +146 -81
- package/dist/subgraphs/resources-services/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/resources-services/resolvers.js +8 -0
- package/package.json +1 -1
|
@@ -19,12 +19,12 @@ export interface MapOfferingOptions {
|
|
|
19
19
|
* This is a one-time snapshot — the SI lives independently after creation.
|
|
20
20
|
*
|
|
21
21
|
* Logic:
|
|
22
|
-
* 1. Find the selected tier
|
|
23
|
-
* 2.
|
|
24
|
-
* 3.
|
|
25
|
-
* 4. Map
|
|
26
|
-
* 5. Map
|
|
27
|
-
* 6. Calculate tier price from service group sums (CALCULATED
|
|
22
|
+
* 1. Find the selected tier and resolve pricing from finalConfiguration
|
|
23
|
+
* 2. Map offering service groups with tier-specific pricing
|
|
24
|
+
* 3. Map option group configs from finalConfiguration as additional service groups
|
|
25
|
+
* 4. Map add-on configs from finalConfiguration as optional service groups
|
|
26
|
+
* 5. Map remaining standalone services with tier service levels and usage limits
|
|
27
|
+
* 6. Calculate tier price from finalConfig or service group sums (CALCULATED) or manual price
|
|
28
28
|
*/
|
|
29
29
|
export declare function mapOfferingToSubscription(options: MapOfferingOptions): InitializeSubscriptionInput;
|
|
30
30
|
//# sourceMappingURL=mapOfferingToSubscription.d.ts.map
|
package/dist/editors/subscription-instance-editor/components/mapOfferingToSubscription.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapOfferingToSubscription.d.ts","sourceRoot":"","sources":["../../../../editors/subscription-instance-editor/components/mapOfferingToSubscription.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,
|
|
1
|
+
{"version":3,"file":"mapOfferingToSubscription.d.ts","sourceRoot":"","sources":["../../../../editors/subscription-instance-editor/components/mapOfferingToSubscription.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EAMrB,MAAM,+DAA+D,CAAC;AACvE,OAAO,KAAK,EACV,2BAA2B,EAK3B,YAAY,IAAI,cAAc,EAC/B,MAAM,oEAAoE,CAAC;AAE5E,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,oBAAoB,EAAE,cAAc,CAAC;IACrC,oBAAoB;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,kBAAkB,GAC1B,2BAA2B,CAqF7B"}
|
|
@@ -4,12 +4,12 @@ import { generateId } from "document-model/core";
|
|
|
4
4
|
* This is a one-time snapshot — the SI lives independently after creation.
|
|
5
5
|
*
|
|
6
6
|
* Logic:
|
|
7
|
-
* 1. Find the selected tier
|
|
8
|
-
* 2.
|
|
9
|
-
* 3.
|
|
10
|
-
* 4. Map
|
|
11
|
-
* 5. Map
|
|
12
|
-
* 6. Calculate tier price from service group sums (CALCULATED
|
|
7
|
+
* 1. Find the selected tier and resolve pricing from finalConfiguration
|
|
8
|
+
* 2. Map offering service groups with tier-specific pricing
|
|
9
|
+
* 3. Map option group configs from finalConfiguration as additional service groups
|
|
10
|
+
* 4. Map add-on configs from finalConfiguration as optional service groups
|
|
11
|
+
* 5. Map remaining standalone services with tier service levels and usage limits
|
|
12
|
+
* 6. Calculate tier price from finalConfig or service group sums (CALCULATED) or manual price
|
|
13
13
|
*/
|
|
14
14
|
export function mapOfferingToSubscription(options) {
|
|
15
15
|
const { offering, tierId, selectedBillingCycle, customerId, customerName, customerEmail, createdAt, } = options;
|
|
@@ -17,21 +17,34 @@ export function mapOfferingToSubscription(options) {
|
|
|
17
17
|
if (!tier) {
|
|
18
18
|
throw new Error(`Tier ${tierId} not found in offering`);
|
|
19
19
|
}
|
|
20
|
-
const
|
|
20
|
+
const finalConfig = offering.finalConfiguration;
|
|
21
|
+
const currency = finalConfig?.tierCurrency ?? tier.pricing.currency;
|
|
21
22
|
const pricingMode = tier.pricingMode || "MANUAL_OVERRIDE";
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
// Map
|
|
25
|
-
const
|
|
23
|
+
// Track which services are accounted for in groups
|
|
24
|
+
const groupedServiceIds = new Set();
|
|
25
|
+
// 1. Map offering service groups with tier-specific pricing
|
|
26
|
+
const serviceGroups = mapOfferingServiceGroups(offering, tier, selectedBillingCycle, currency, groupedServiceIds);
|
|
27
|
+
// 2. Map option group configs from finalConfiguration as service groups
|
|
28
|
+
if (finalConfig) {
|
|
29
|
+
mapFinalConfigGroups(offering, tier, finalConfig, currency, groupedServiceIds, serviceGroups);
|
|
30
|
+
}
|
|
31
|
+
// 3. Map remaining standalone services (not in any group or option group)
|
|
32
|
+
const standaloneServices = offering.services
|
|
33
|
+
.filter((s) => !groupedServiceIds.has(s.id))
|
|
34
|
+
.filter((svc) => {
|
|
35
|
+
const level = tier.serviceLevels.find((sl) => sl.serviceId === svc.id);
|
|
36
|
+
return (level &&
|
|
37
|
+
level.level !== "NOT_INCLUDED" &&
|
|
38
|
+
level.level !== "NOT_APPLICABLE");
|
|
39
|
+
})
|
|
40
|
+
.map((svc) => mapServiceToInput(svc, tier, currency, selectedBillingCycle));
|
|
26
41
|
// Calculate tier price
|
|
27
42
|
let tierPrice;
|
|
28
43
|
if (pricingMode === "CALCULATED") {
|
|
29
|
-
tierPrice = serviceGroups.reduce((sum, grp) =>
|
|
30
|
-
return sum + (grp.recurringAmount ?? 0);
|
|
31
|
-
}, 0);
|
|
44
|
+
tierPrice = serviceGroups.reduce((sum, grp) => sum + (grp.recurringAmount ?? 0), 0);
|
|
32
45
|
}
|
|
33
46
|
else {
|
|
34
|
-
tierPrice = tier.pricing.amount ?? undefined;
|
|
47
|
+
tierPrice = finalConfig?.tierBasePrice ?? tier.pricing.amount ?? undefined;
|
|
35
48
|
}
|
|
36
49
|
return {
|
|
37
50
|
customerId: customerId ?? undefined,
|
|
@@ -51,18 +64,21 @@ export function mapOfferingToSubscription(options) {
|
|
|
51
64
|
serviceGroups,
|
|
52
65
|
};
|
|
53
66
|
}
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Maps offering service groups to subscription service groups.
|
|
69
|
+
* Finds services by their serviceGroupId and applies tier-specific pricing.
|
|
70
|
+
*/
|
|
71
|
+
function mapOfferingServiceGroups(offering, tier, selectedBillingCycle, globalCurrency, groupedServiceIds) {
|
|
72
|
+
return offering.serviceGroups.map((group) => {
|
|
73
|
+
// Find services that belong to this service group
|
|
74
|
+
const groupServices = offering.services.filter((s) => s.serviceGroupId === group.id);
|
|
75
|
+
groupServices.forEach((s) => groupedServiceIds.add(s.id));
|
|
56
76
|
// Find tier-specific pricing for this group
|
|
57
77
|
const tierPricing = group.tierPricing.find((tp) => tp.tierId === tier.id);
|
|
58
78
|
// Find the recurring price option matching the selected billing cycle
|
|
59
|
-
let recurringOption;
|
|
60
|
-
if (
|
|
61
|
-
recurringOption = tierPricing
|
|
62
|
-
// Fallback to group's own billing cycle if no match
|
|
63
|
-
if (!recurringOption) {
|
|
64
|
-
recurringOption = tierPricing.recurringPricing.find((rp) => rp.billingCycle === group.billingCycle);
|
|
65
|
-
}
|
|
79
|
+
let recurringOption = tierPricing?.recurringPricing.find((rp) => rp.billingCycle === selectedBillingCycle);
|
|
80
|
+
if (!recurringOption) {
|
|
81
|
+
recurringOption = tierPricing?.recurringPricing.find((rp) => rp.billingCycle === group.billingCycle);
|
|
66
82
|
}
|
|
67
83
|
// Apply billing cycle discount from tier if applicable
|
|
68
84
|
let discountedAmount = recurringOption?.amount;
|
|
@@ -72,12 +88,10 @@ function mapServiceGroups(soGroups, tier, selectedBillingCycle, globalCurrency)
|
|
|
72
88
|
if (cycleDiscount) {
|
|
73
89
|
const originalAmount = recurringOption.amount;
|
|
74
90
|
const rule = cycleDiscount.discountRule;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
discountedAmount = originalAmount - rule.discountValue;
|
|
80
|
-
}
|
|
91
|
+
discountedAmount =
|
|
92
|
+
rule.discountType === "PERCENTAGE"
|
|
93
|
+
? originalAmount * (1 - rule.discountValue / 100)
|
|
94
|
+
: originalAmount - rule.discountValue;
|
|
81
95
|
discountInput = {
|
|
82
96
|
originalAmount,
|
|
83
97
|
discountType: rule.discountType,
|
|
@@ -86,16 +100,14 @@ function mapServiceGroups(soGroups, tier, selectedBillingCycle, globalCurrency)
|
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
|
-
//
|
|
103
|
+
// Fallback to the recurring option's own discount
|
|
90
104
|
if (recurringOption?.discount && !discountInput) {
|
|
91
105
|
const originalAmount = recurringOption.amount;
|
|
92
106
|
const d = recurringOption.discount;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
discountedAmount = originalAmount - d.discountValue;
|
|
98
|
-
}
|
|
107
|
+
discountedAmount =
|
|
108
|
+
d.discountType === "PERCENTAGE"
|
|
109
|
+
? originalAmount * (1 - d.discountValue / 100)
|
|
110
|
+
: originalAmount - d.discountValue;
|
|
99
111
|
discountInput = {
|
|
100
112
|
originalAmount,
|
|
101
113
|
discountType: d.discountType,
|
|
@@ -113,8 +125,15 @@ function mapServiceGroups(soGroups, tier, selectedBillingCycle, globalCurrency)
|
|
|
113
125
|
setupCurrency = setupCostOption.currency;
|
|
114
126
|
}
|
|
115
127
|
}
|
|
116
|
-
// Map services in this group
|
|
117
|
-
const
|
|
128
|
+
// Map services in this group
|
|
129
|
+
const mappedServices = groupServices
|
|
130
|
+
.filter((svc) => {
|
|
131
|
+
const level = tier.serviceLevels.find((sl) => sl.serviceId === svc.id);
|
|
132
|
+
return (!level ||
|
|
133
|
+
(level.level !== "NOT_INCLUDED" && level.level !== "NOT_APPLICABLE"));
|
|
134
|
+
})
|
|
135
|
+
.map((svc) => mapServiceToInput(svc, tier, globalCurrency, (recurringOption?.billingCycle ??
|
|
136
|
+
group.billingCycle)));
|
|
118
137
|
return {
|
|
119
138
|
id: generateId(),
|
|
120
139
|
name: group.name,
|
|
@@ -127,54 +146,100 @@ function mapServiceGroups(soGroups, tier, selectedBillingCycle, globalCurrency)
|
|
|
127
146
|
recurringBillingCycle: (recurringOption?.billingCycle ??
|
|
128
147
|
group.billingCycle),
|
|
129
148
|
recurringDiscount: discountInput,
|
|
130
|
-
services:
|
|
149
|
+
services: mappedServices,
|
|
131
150
|
};
|
|
132
151
|
});
|
|
133
152
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Maps finalConfiguration option group configs and add-on configs
|
|
155
|
+
* into subscription service groups.
|
|
156
|
+
*/
|
|
157
|
+
function mapFinalConfigGroups(offering, tier, finalConfig, globalCurrency, groupedServiceIds, serviceGroups) {
|
|
158
|
+
// Non-add-on option groups
|
|
159
|
+
for (const ogConfig of finalConfig.optionGroupConfigs) {
|
|
160
|
+
const og = offering.optionGroups.find((g) => g.id === ogConfig.optionGroupId);
|
|
161
|
+
if (!og || og.isAddOn)
|
|
162
|
+
continue;
|
|
163
|
+
const services = offering.services.filter((s) => s.optionGroupId === og.id);
|
|
164
|
+
if (services.length === 0)
|
|
165
|
+
continue;
|
|
166
|
+
services.forEach((s) => groupedServiceIds.add(s.id));
|
|
167
|
+
serviceGroups.push({
|
|
147
168
|
id: generateId(),
|
|
148
|
-
name:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
name: og.name,
|
|
170
|
+
optional: false,
|
|
171
|
+
costType: og.costType ?? undefined,
|
|
172
|
+
recurringAmount: ogConfig.recurringAmount ?? undefined,
|
|
173
|
+
recurringCurrency: ogConfig.currency ?? globalCurrency,
|
|
174
|
+
recurringBillingCycle: ogConfig.effectiveBillingCycle,
|
|
175
|
+
recurringDiscount: mapResolvedDiscount(ogConfig.discount, og.discountMode === "INHERIT_TIER"
|
|
176
|
+
? "TIER_INHERITED"
|
|
177
|
+
: "GROUP_INDEPENDENT"),
|
|
178
|
+
setupAmount: ogConfig.setupCost ?? undefined,
|
|
179
|
+
setupCurrency: ogConfig.setupCostCurrency ?? undefined,
|
|
180
|
+
services: services.map((svc) => mapServiceToInput(svc, tier, globalCurrency, ogConfig.effectiveBillingCycle)),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Add-on option groups
|
|
184
|
+
for (const aoConfig of finalConfig.addOnConfigs) {
|
|
185
|
+
const og = offering.optionGroups.find((g) => g.id === aoConfig.optionGroupId);
|
|
186
|
+
if (!og)
|
|
187
|
+
continue;
|
|
188
|
+
const services = offering.services.filter((s) => s.optionGroupId === og.id);
|
|
189
|
+
if (services.length === 0)
|
|
190
|
+
continue;
|
|
191
|
+
services.forEach((s) => groupedServiceIds.add(s.id));
|
|
192
|
+
serviceGroups.push({
|
|
170
193
|
id: generateId(),
|
|
171
|
-
name:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
name: og.name,
|
|
195
|
+
optional: true,
|
|
196
|
+
costType: og.costType ?? undefined,
|
|
197
|
+
recurringAmount: aoConfig.recurringAmount ?? undefined,
|
|
198
|
+
recurringCurrency: aoConfig.currency ?? globalCurrency,
|
|
199
|
+
recurringBillingCycle: aoConfig.selectedBillingCycle,
|
|
200
|
+
recurringDiscount: mapResolvedDiscount(aoConfig.discount, og.discountMode === "INHERIT_TIER"
|
|
201
|
+
? "TIER_INHERITED"
|
|
202
|
+
: "GROUP_INDEPENDENT"),
|
|
203
|
+
setupAmount: aoConfig.setupCost ?? undefined,
|
|
204
|
+
setupCurrency: aoConfig.setupCostCurrency ?? undefined,
|
|
205
|
+
services: services.map((svc) => mapServiceToInput(svc, tier, globalCurrency, aoConfig.selectedBillingCycle)),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Maps a single service from the offering to an InitializeServiceInput.
|
|
211
|
+
* Includes name, description, customValue from tier service levels,
|
|
212
|
+
* billing cycle, and usage metrics.
|
|
213
|
+
*/
|
|
214
|
+
function mapServiceToInput(svc, tier, globalCurrency, billingCycle) {
|
|
215
|
+
const level = tier.serviceLevels.find((sl) => sl.serviceId === svc.id);
|
|
216
|
+
const metrics = mapUsageLimits(svc.id, tier.usageLimits, globalCurrency);
|
|
217
|
+
return {
|
|
218
|
+
id: generateId(),
|
|
219
|
+
name: svc.title,
|
|
220
|
+
description: svc.description ?? null,
|
|
221
|
+
customValue: level?.customValue ?? null,
|
|
222
|
+
recurringBillingCycle: billingCycle,
|
|
223
|
+
metrics,
|
|
224
|
+
};
|
|
177
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Maps a ResolvedDiscount from the offering to a DiscountInfoInitInput,
|
|
228
|
+
* or returns undefined if no discount.
|
|
229
|
+
*/
|
|
230
|
+
function mapResolvedDiscount(discount, source) {
|
|
231
|
+
if (!discount)
|
|
232
|
+
return undefined;
|
|
233
|
+
return {
|
|
234
|
+
originalAmount: discount.originalAmount,
|
|
235
|
+
discountType: discount.discountType,
|
|
236
|
+
discountValue: discount.discountValue,
|
|
237
|
+
source,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Maps usage limits from the tier to InitializeMetricInput for a given service.
|
|
242
|
+
*/
|
|
178
243
|
function mapUsageLimits(serviceId, usageLimits, globalCurrency) {
|
|
179
244
|
const limits = usageLimits.filter((ul) => ul.serviceId === serviceId);
|
|
180
245
|
return limits.map((ul) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/resources-services/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAsC5D,eAAO,MAAM,YAAY,GAAI,UAAU,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/resources-services/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAsC5D,eAAO,MAAM,YAAY,GAAI,UAAU,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAoaxE,CAAC"}
|
|
@@ -277,6 +277,14 @@ export const getResolvers = (subgraph) => {
|
|
|
277
277
|
resourceLabel: name,
|
|
278
278
|
resourceThumbnailUrl: serviceOfferingState.thumbnailUrl,
|
|
279
279
|
}));
|
|
280
|
+
// Set billing projection from tier price
|
|
281
|
+
const projectedAmount = subscriptionInput.tierPrice ?? finalConfiguration.tierBasePrice;
|
|
282
|
+
if (projectedAmount != null) {
|
|
283
|
+
await reactor.addAction(subscriptionInstanceDoc.header.id, SubscriptionInstance.actions.updateBillingProjection({
|
|
284
|
+
projectedBillAmount: projectedAmount,
|
|
285
|
+
projectedBillCurrency: finalConfiguration.tierCurrency || "USD",
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
280
288
|
return {
|
|
281
289
|
success: true,
|
|
282
290
|
data: {
|