@thezelijah/majik-subscription 1.0.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.
@@ -0,0 +1,431 @@
1
+ import { MajikMoney } from "@thezelijah/majik-money";
2
+ import { COSItem, ISODateString, MonthlyCapacity, ObjectType, StartDateInput, SubscriptionID, SubscriptionMetadata, SubscriptionRate, SubscriptionSettings, YYYYMM } from "./types";
3
+ import { CapacityPeriodResizeMode, RateUnit, SubscriptionStatus, SubscriptionType } from "./enums";
4
+ /**
5
+ * Represents a subscription in the Majik system.
6
+ * Handles metadata, capacity, COS, and finance calculations (revenue, COS, profit, margins) for recurring subscriptions.
7
+ */
8
+ export declare class MajikSubscription {
9
+ readonly __type = "MajikSubscription";
10
+ readonly __object: ObjectType;
11
+ id: SubscriptionID;
12
+ slug: string;
13
+ name: string;
14
+ category: string;
15
+ rate: SubscriptionRate;
16
+ status: SubscriptionStatus;
17
+ type: SubscriptionType;
18
+ timestamp: ISODateString;
19
+ last_update: ISODateString;
20
+ metadata: SubscriptionMetadata;
21
+ settings: SubscriptionSettings;
22
+ private financeDirty;
23
+ /**
24
+ * Creates a new `MajikSubscription` instance.
25
+ * @param {SubscriptionID | undefined} id - Optional subscription ID. Auto-generated if undefined.
26
+ * @param {string | undefined} slug - Optional slug. Auto-generated from name if undefined.
27
+ * @param {string} name - Name of the subscription.
28
+ * @param {SubscriptionMetadata} metadata - Metadata including type, category, rate, description, COS, and capacity plan.
29
+ * @param {SubscriptionSettings} settings - Settings including status, visibility, and system flags.
30
+ * @param {ISODateString} [timestamp=new Date().toISOString()] - Optional creation timestamp.
31
+ * @param {ISODateString} [last_update=new Date().toISOString()] - Optional last update timestamp.
32
+ */
33
+ constructor(id: SubscriptionID | undefined, slug: string | undefined, name: string, metadata: SubscriptionMetadata, settings: SubscriptionSettings, timestamp?: ISODateString, last_update?: ISODateString);
34
+ /** Marks finance calculations as dirty for lazy recomputation */
35
+ private markFinanceDirty;
36
+ /**
37
+ * Returns a zero-value MajikMoney object in the subscription currency.
38
+ * @param {string} [currencyCode] - Optional currency code. Defaults to subscription rate currency or PHP.
39
+ * @returns {MajikMoney} - A zero-value MajikMoney instance.
40
+ */
41
+ private DEFAULT_ZERO;
42
+ /**
43
+ * Initializes and creates a new `MajikSubscription` with default and null values.
44
+ * @param type - The type of service to initialize. Defaults to `TIME_BASED`. Use Enum `ServiceType`.
45
+ * @returns A new `MajikSubscription` instance.
46
+ */
47
+ static initialize(name: string, type: SubscriptionType | undefined, rate: SubscriptionRate, category?: string, descriptionText?: string, skuID?: string): MajikSubscription;
48
+ /**
49
+ * Updates the subscription name and regenerates the slug.
50
+ * @param {string} name - New subscription name.
51
+ * @returns {MajikSubscription} - Returns self for chaining.
52
+ */
53
+ setName(name: string): this;
54
+ /**
55
+ * Updates the subscription rate.
56
+ * @param {SubscriptionRate} rate - New rate object.
57
+ * @returns {MajikSubscription} - Returns self for chaining.
58
+ */
59
+ setRate(rate: SubscriptionRate): this;
60
+ /**
61
+ * Updates the rate unit.
62
+ * @param {RateUnit} unit - New rate unit (e.g., per subscriber, per month).
63
+ * @returns {MajikSubscription} - Returns self for chaining.
64
+ */
65
+ setRateUnit(unit: RateUnit): this;
66
+ /**
67
+ * Updates the numeric rate amount.
68
+ * @param {number} amount - New rate amount (must be positive).
69
+ * @returns {MajikSubscription} - Returns self for chaining.
70
+ * @throws Will throw an error if amount is non-positive.
71
+ */
72
+ setRateAmount(amount: number): this;
73
+ /**
74
+ * Updates the subscription category.
75
+ * @param {string} category - New category name.
76
+ * @returns {MajikSubscription} - Returns self for chaining.
77
+ */
78
+ setCategory(category: string): this;
79
+ /**
80
+ * Updates the HTML and plain text of the subscription description.
81
+ * @param {string} html - The new HTML description.
82
+ * @param {string} text - The new plain text description.
83
+ * @returns {MajikSubscription} - Returns self for chaining.
84
+ * @throws Will throw an error if either html or text is invalid.
85
+ */
86
+ setDescription(html: string, text: string): this;
87
+ /**
88
+ * Updates only the plain text of the subscription description.
89
+ * @param {string} text - The new plain text description.
90
+ * @returns {MajikSubscription} - Returns self for chaining.
91
+ * @throws Will throw an error if text is invalid.
92
+ */
93
+ setDescriptionText(text: string): this;
94
+ /**
95
+ * Updates only the HTML of the subscription description.
96
+ * @param {string} html - The new HTML description.
97
+ * @returns {MajikSubscription} - Returns self for chaining.
98
+ * @throws Will throw an error if html is invalid.
99
+ */
100
+ setDescriptionHTML(html: string): this;
101
+ /**
102
+ * Updates SEO-friendly text of the subscription description.
103
+ * @param {string} text - SEO text.
104
+ * @returns {MajikSubscription} - Returns self for chaining.
105
+ */
106
+ setDescriptionSEO(text: string): this;
107
+ /**
108
+ * Updates the Type of the Subscription.
109
+ * @param type - The new Type of the Subscription. Use Enum `SubscriptionType`.
110
+ * @throws Will throw an error if the `type` is not provided or is not a string.
111
+ */
112
+ setType(type: SubscriptionType): this;
113
+ /**
114
+ * Returns SEO text if available; otherwise the plain text description.
115
+ * @returns {string} - SEO or plain text description.
116
+ */
117
+ get seo(): string;
118
+ /**
119
+ * Returns true if the subscription has at least one COS (cost breakdown) item.
120
+ */
121
+ hasCostBreakdown(): boolean;
122
+ /**
123
+ * Adds a new COS (Cost of Subscription) item.
124
+ * @param {string} name - COS item name.
125
+ * @param {MajikMoney} unitCost - Cost per unit.
126
+ * @param {number} [quantity=1] - Number of units.
127
+ * @param {string} [unit] - Optional unit (e.g., "subscriber", "month").
128
+ * @returns {MajikSubscription} - Returns self for chaining.
129
+ */
130
+ addCOS(name: string, unitCost: MajikMoney, quantity?: number, unit?: string): this;
131
+ /**
132
+ * Pushes an existing COSItem into the metadata.
133
+ * @param {COSItem} item - COSItem to add.
134
+ * @returns {MajikSubscription} - Returns self for chaining.
135
+ * @throws Will throw an error if item is missing required properties or has currency mismatch.
136
+ */
137
+ pushCOS(item: COSItem): this;
138
+ /**
139
+ * Updates an existing COS item by ID.
140
+ * @param {string} id - COS item ID.
141
+ * @param {Partial<Pick<COSItem, "quantity" | "unitCost" | "unit">>} updates - Fields to update.
142
+ * @returns {MajikSubscription} - Returns self for chaining.
143
+ * @throws Will throw an error if the COS item does not exist or has invalid updates.
144
+ */
145
+ updateCOS(id: string, updates: Partial<Pick<COSItem, "quantity" | "unitCost" | "unit" | "item">>): this;
146
+ /**
147
+ * Replaces all COS items with a new array.
148
+ * @param {COSItem[]} items - Array of COS items.
149
+ * @returns {MajikSubscription} - Returns self for chaining.
150
+ * @throws Will throw an error if items are missing required properties or have currency mismatch.
151
+ */
152
+ setCOS(items: COSItem[]): this;
153
+ /**
154
+ * Removes a COS item by ID.
155
+ * @param {string} id - COS item ID.
156
+ * @returns {MajikSubscription} - Returns self for chaining.
157
+ * @throws Will throw an error if the COS item does not exist.
158
+ */
159
+ removeCOS(id: string): this;
160
+ /** Clears all COS items. */
161
+ clearCostBreakdown(): this;
162
+ /**
163
+ * Returns true if the subscription has at least one Capacity Plan item.
164
+ */
165
+ hasCapacity(): boolean;
166
+ /**
167
+ * Returns the earliest (initial) YYYYMM from the capacity plan.
168
+ */
169
+ get earliestCapacityMonth(): YYYYMM | null;
170
+ /**
171
+ * Returns the most recent (latest) YYYYMM from the capacity plan.
172
+ */
173
+ get latestCapacityMonth(): YYYYMM | null;
174
+ get capacity(): MonthlyCapacity[];
175
+ /**
176
+ * Returns the total capacity units across all months.
177
+ */
178
+ get totalCapacity(): number;
179
+ /**
180
+ * Returns the average capacity per month.
181
+ * Includes adjustments.
182
+ */
183
+ get averageMonthlyCapacity(): number;
184
+ /**
185
+ * Returns the MonthlyCapacity entry with the highest supply.
186
+ * Includes adjustments.
187
+ */
188
+ get maxSupplyMonth(): MonthlyCapacity | null;
189
+ /**
190
+ * Returns the MonthlyCapacity entry with the lowest supply.
191
+ * Includes adjustments.
192
+ */
193
+ get minSupplyMonth(): MonthlyCapacity | null;
194
+ /**
195
+ * Generates and replaces the capacity plan automatically.
196
+ *
197
+ * @param months - Number of months to generate from the start date.
198
+ * @param amount - Base units for the first month.
199
+ * @param growthRate - Optional growth rate per month (e.g. 0.03 = +3%).
200
+ * @param startDate - Date | ISO date | YYYYMM. Defaults to current month.
201
+ * @returns {this} Updated subscription instance.
202
+ */
203
+ generateCapacityPlan(months: number, amount: number, growthRate?: number, startDate?: StartDateInput): this;
204
+ /**
205
+ * Normalizes all supply plan entries to have the same unit amount.
206
+ *
207
+ * - Throws if supply plan is empty
208
+ * - If only one entry exists, does nothing
209
+ * - If multiple entries exist, sets all units to the provided amount
210
+ *
211
+ * @param amount - Unit amount to apply to all months
212
+ * @returns {this} Updated subscription instance
213
+ */
214
+ normalizeCapacityUnits(amount: number): this;
215
+ recomputeCapacityPeriod(start: YYYYMM, end: YYYYMM, mode?: CapacityPeriodResizeMode): this;
216
+ /**
217
+ * Sets the entire monthly capacity plan.
218
+ * @param {MonthlyCapacity[]} capacityPlan - Array of MonthlyCapacity.
219
+ * @returns {MajikSubscription} - Returns self for chaining.
220
+ * @throws Will throw an error if month format or capacity is invalid.
221
+ */
222
+ setCapacity(capacityPlan: MonthlyCapacity[]): this;
223
+ /**
224
+ * Adds capacity for a specific month.
225
+ * @param {YYYYMM} month - YYYY-MM string.
226
+ * @param {number} units - Number of subscription units for the month.
227
+ * @param {number} [adjustment] - Optional adjustment to capacity.
228
+ * @returns {MajikSubscription} - Returns self for chaining.
229
+ * @throws Will throw an error if month already exists.
230
+ */
231
+ addCapacity(month: YYYYMM, units: number, adjustment?: number): this;
232
+ /**
233
+ * Updates capacity units for a month.
234
+ * @param {YYYYMM} month - YYYY-MM string.
235
+ * @param {number} units - New subscription units.
236
+ * @returns {MajikSubscription} - Returns self for chaining.
237
+ * @throws Will throw an error if month does not exist.
238
+ */
239
+ updateCapacityUnits(month: YYYYMM, units: number): this;
240
+ /**
241
+ * Updates capacity adjustment for a month.
242
+ * @param {YYYYMM} month - YYYY-MM string.
243
+ * @param {number} [adjustment] - Optional adjustment value.
244
+ * @returns {MajikSubscription} - Returns self for chaining.
245
+ * @throws Will throw an error if month does not exist.
246
+ */
247
+ updateCapacityAdjustment(month: YYYYMM, adjustment?: number): this;
248
+ /**
249
+ * Removes a month from the capacity plan.
250
+ * @param {YYYYMM} month - YYYY-MM string.
251
+ * @returns {MajikSubscription} - Returns self for chaining.
252
+ * @throws Will throw an error if month does not exist.
253
+ */
254
+ removeCapacity(month: YYYYMM): this;
255
+ /** Clears the entire capacity plan. */
256
+ clearCapacity(): this;
257
+ /** Computes gross revenue across all months. */
258
+ private computeGrossRevenue;
259
+ /** Computes gross COS across all months. */
260
+ private computeGrossCOS;
261
+ /** Computes gross profit (revenue - COS). */
262
+ private computeGrossProfit;
263
+ /** Recomputes and stores aggregate finance info. */
264
+ private recomputeFinance;
265
+ get averageMonthlyRevenue(): MajikMoney;
266
+ get averageMonthlyProfit(): MajikMoney;
267
+ /**
268
+ * Returns total gross revenue across all months.
269
+ * @returns {MajikMoney} - Gross revenue.
270
+ */
271
+ get grossRevenue(): MajikMoney;
272
+ /**
273
+ * Returns total gross cost of subscription (COS) across all months.
274
+ * @returns {MajikMoney} - Gross COS.
275
+ */
276
+ get grossCost(): MajikMoney;
277
+ /**
278
+ * Returns total gross profit across all months.
279
+ * @returns {MajikMoney} - Gross profit.
280
+ */
281
+ get grossProfit(): MajikMoney;
282
+ /**
283
+ * Returns net revenue (same as gross in this model).
284
+ * @returns {MajikMoney} - Net revenue.
285
+ */
286
+ get netRevenue(): MajikMoney;
287
+ /**
288
+ * Returns net profit (same as gross in this model).
289
+ * @returns {MajikMoney} - Net profit.
290
+ */
291
+ get netProfit(): MajikMoney;
292
+ get unitCost(): MajikMoney;
293
+ get unitProfit(): MajikMoney;
294
+ get unitMargin(): number;
295
+ get price(): MajikMoney;
296
+ getMonthlySnapshot(month: YYYYMM): {
297
+ month: `${number}${number}${number}${number}-${number}${number}`;
298
+ revenue: MajikMoney;
299
+ cogs: MajikMoney;
300
+ profit: MajikMoney;
301
+ margin: number;
302
+ netRevenue: MajikMoney;
303
+ netIncome: MajikMoney;
304
+ };
305
+ /**
306
+ * Calculates Net Revenue for a given month.
307
+ * @param month - YYYYMM
308
+ * @param discounts - Total discounts for the month (optional)
309
+ * @param returns - Total returns for the month (optional)
310
+ * @param allowances - Total allowances for the month (optional)
311
+ * @returns {MajikMoney} Net Revenue
312
+ */
313
+ getNetRevenue(month: YYYYMM, discounts?: MajikMoney, returns?: MajikMoney, allowances?: MajikMoney): MajikMoney;
314
+ /**
315
+ * Calculates Net Profit for a given month.
316
+ * @param month - YYYYMM
317
+ * @param operatingExpenses - Total operating expenses (optional)
318
+ * @param taxes - Total taxes (optional)
319
+ * @param discounts - Total discounts for the month (optional)
320
+ * @param returns - Total returns for the month (optional)
321
+ * @param allowances - Total allowances for the month (optional)
322
+ * @returns {MajikMoney} Net Profit
323
+ */
324
+ getNetProfit(month: YYYYMM, operatingExpenses?: MajikMoney, taxes?: MajikMoney, discounts?: MajikMoney, returns?: MajikMoney, allowances?: MajikMoney): MajikMoney;
325
+ /**
326
+ * Alias for getNetProfit, same as Net Income
327
+ */
328
+ getNetIncome(month: YYYYMM, operatingExpenses?: MajikMoney, taxes?: MajikMoney, discounts?: MajikMoney, returns?: MajikMoney, allowances?: MajikMoney): MajikMoney;
329
+ /**
330
+ * Returns revenue for a specific month.
331
+ * @param {YYYYMM} month - Month in YYYY-MM format.
332
+ * @returns {MajikMoney} - Monthly revenue.
333
+ */
334
+ getRevenue(month: YYYYMM): MajikMoney;
335
+ /**
336
+ * Returns all COS items.
337
+ * @returns {readonly COSItem[]} - Array of COS items.
338
+ */
339
+ get cos(): readonly COSItem[];
340
+ /**
341
+ * Returns COS for a specific month.
342
+ * @param {YYYYMM} month - Month in YYYY-MM format.
343
+ * @returns {MajikMoney} - Monthly COS.
344
+ */
345
+ getCOS(month: YYYYMM): MajikMoney;
346
+ /**
347
+ * Alias for Get COS. Retrieves COS for a given month.
348
+ * @param month - YYYYMM month.
349
+ * @returns {MajikMoney} COS for the month.
350
+ */
351
+ getCost(month: YYYYMM): MajikMoney;
352
+ /**
353
+ * Returns profit for a specific month.
354
+ * @param {YYYYMM} month - Month in YYYY-MM format.
355
+ * @returns {MajikMoney} - Monthly profit.
356
+ */
357
+ getProfit(month: YYYYMM): MajikMoney;
358
+ /**
359
+ * Returns profit margin (as a decimal) for a specific month.
360
+ * @param {YYYYMM} month - Month in YYYY-MM format.
361
+ * @returns {number} - Profit margin (0–1).
362
+ */
363
+ getMargin(month: YYYYMM): number;
364
+ /**
365
+ * Applies a trial period to the subscription by reducing the capacity or revenue for the given number of months.
366
+ * @param {number} months - Number of months for the trial period.
367
+ * @returns {MajikSubscription} - Returns self for chaining.
368
+ * @throws {Error} - Throws if months is not a positive integer.
369
+ */
370
+ applyTrial(months: number): this;
371
+ /**
372
+ * Computes the next billing date for the subscription.
373
+ * Assumes subscription billing is monthly and starts at the first month in the capacity plan.
374
+ * @returns {ISODateString | null} - Next billing date in ISO format, or null if no capacity plan exists.
375
+ */
376
+ nextBillingDate(): ISODateString | null;
377
+ /**
378
+ * Forecasts revenue for the next N months based on current rate and capacity plan.
379
+ * @param {number} nextNMonths - Number of future months to forecast.
380
+ * @returns {MajikMoney} - Forecasted revenue as MajikMoney.
381
+ * @throws {Error} - Throws if nextNMonths is not a positive integer.
382
+ */
383
+ forecastRevenue(nextNMonths: number): MajikMoney;
384
+ /** Monthly Recurring Revenue (MRR) for a specific month or current month if not provided */
385
+ getMRR(month?: YYYYMM): MajikMoney;
386
+ /** Annual Recurring Revenue (ARR) based on sum of next 12 months revenue
387
+ *
388
+ * Forecasts revenue for the next N months based on current rate and capacity plan.
389
+ * @param {number} months - Number of future months to forecast. Defaults to 12.
390
+ */
391
+ getARR(months?: number): MajikMoney;
392
+ /**
393
+ * Validates the subscription instance.
394
+ * @param {boolean} [throwError=false] - Whether to throw an error on first invalid property.
395
+ * @returns {boolean} - True if valid, false if invalid and throwError is false.
396
+ * @throws {Error} - Throws error if throwError is true and a required field is missing/invalid.
397
+ */
398
+ validateSelf(throwError?: boolean): boolean;
399
+ /**
400
+ * Converts the subscription to a plain object and generates a new ID.
401
+ * @returns {object} - Serialized subscription.
402
+ */
403
+ finalize(): object;
404
+ /**
405
+ * Converts the subscription instance to a plain JSON object.
406
+ * @returns {object} - Plain object representation.
407
+ */
408
+ toJSON(): object;
409
+ /**
410
+ * Parses a plain object or JSON string into a MajikSubscription instance.
411
+ * @param {string | object} json - JSON string or object.
412
+ * @returns {MajikSubscription} - Parsed subscription instance.
413
+ * @throws {Error} - Throws if required properties are missing.
414
+ */
415
+ static parseFromJSON(json: string | object): MajikSubscription;
416
+ /**
417
+ * Updates the last_update timestamp to current time.
418
+ * Should be called whenever a property is modified.
419
+ * @private
420
+ */
421
+ private updateTimestamp;
422
+ /**
423
+ * Ensures the given MajikMoney object matches the subscription currency.
424
+ * @param {MajikMoney} money - Money object to validate.
425
+ * @private
426
+ * @throws {Error} - Throws if currency does not match subscription rate.
427
+ */
428
+ private assertCurrency;
429
+ }
430
+ export declare function isMajikSubscriptionClass(item: MajikSubscription): boolean;
431
+ export declare function isMajikSubscriptionJSON(item: MajikSubscription): boolean;