@happyvertical/smrt-affiliates 0.30.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/dist/index.js ADDED
@@ -0,0 +1,1387 @@
1
+ import { ObjectRegistry, crossPackageRef, foreignKey, smrt, SmrtObject, SmrtCollection } from "@happyvertical/smrt-core";
2
+ ObjectRegistry.registerPackageManifest(
3
+ new URL("./manifest.json", import.meta.url)
4
+ );
5
+ var PartnerType = /* @__PURE__ */ ((PartnerType2) => {
6
+ PartnerType2["PUBLISHER"] = "publisher";
7
+ PartnerType2["SALESPERSON"] = "salesperson";
8
+ PartnerType2["REFERRER"] = "referrer";
9
+ return PartnerType2;
10
+ })(PartnerType || {});
11
+ var PartnerStatus = /* @__PURE__ */ ((PartnerStatus2) => {
12
+ PartnerStatus2["PENDING"] = "pending";
13
+ PartnerStatus2["ACTIVE"] = "active";
14
+ PartnerStatus2["SUSPENDED"] = "suspended";
15
+ return PartnerStatus2;
16
+ })(PartnerStatus || {});
17
+ var CommissionType = /* @__PURE__ */ ((CommissionType2) => {
18
+ CommissionType2["DISPLAY"] = "display";
19
+ CommissionType2["REFERRAL"] = "referral";
20
+ CommissionType2["SALES"] = "sales";
21
+ CommissionType2["PARENT"] = "parent";
22
+ CommissionType2["OVERHEAD"] = "overhead";
23
+ return CommissionType2;
24
+ })(CommissionType || {});
25
+ var CommissionStatus = /* @__PURE__ */ ((CommissionStatus2) => {
26
+ CommissionStatus2["PENDING"] = "pending";
27
+ CommissionStatus2["INCLUDED"] = "included";
28
+ CommissionStatus2["PAID"] = "paid";
29
+ return CommissionStatus2;
30
+ })(CommissionStatus || {});
31
+ var PayoutStatus = /* @__PURE__ */ ((PayoutStatus2) => {
32
+ PayoutStatus2["PENDING"] = "pending";
33
+ PayoutStatus2["APPROVED"] = "approved";
34
+ PayoutStatus2["PROCESSING"] = "processing";
35
+ PayoutStatus2["COMPLETED"] = "completed";
36
+ PayoutStatus2["FAILED"] = "failed";
37
+ return PayoutStatus2;
38
+ })(PayoutStatus || {});
39
+ var PayoutMethod = /* @__PURE__ */ ((PayoutMethod2) => {
40
+ PayoutMethod2["BANK_TRANSFER"] = "bank_transfer";
41
+ PayoutMethod2["CHECK"] = "check";
42
+ PayoutMethod2["PAYPAL"] = "paypal";
43
+ PayoutMethod2["CREDIT"] = "credit";
44
+ return PayoutMethod2;
45
+ })(PayoutMethod || {});
46
+ var __defProp$2 = Object.defineProperty;
47
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
48
+ var __decorateClass$2 = (decorators, target, key, kind) => {
49
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
50
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
51
+ if (decorator = decorators[i])
52
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
53
+ if (kind && result) __defProp$2(target, key, result);
54
+ return result;
55
+ };
56
+ let Commission = class extends SmrtObject {
57
+ /**
58
+ * Calculate commission amount from gross revenue and rate
59
+ *
60
+ * @param grossRevenue - Gross revenue in cents
61
+ * @param rate - Commission rate (0-1)
62
+ * @returns Commission amount in cents (rounded)
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const amount = Commission.calculateAmount(1000, 0.50); // 500 cents
67
+ * ```
68
+ */
69
+ static calculateAmount(grossRevenue, rate) {
70
+ return Math.round(grossRevenue * rate);
71
+ }
72
+ eventId = "";
73
+ partnerId = "";
74
+ /**
75
+ * Commission type (display, referral, sales, parent)
76
+ */
77
+ commissionType = CommissionType.DISPLAY;
78
+ /**
79
+ * Gross revenue from the event in cents
80
+ * This is the total ad revenue before commission split
81
+ */
82
+ grossRevenue = 0;
83
+ /**
84
+ * Commission rate applied (0-1)
85
+ * Copied from partner at time of event for audit trail
86
+ */
87
+ commissionRate = 0;
88
+ /**
89
+ * Calculated commission amount in cents
90
+ * = grossRevenue * commissionRate
91
+ */
92
+ commissionAmount = 0;
93
+ /**
94
+ * Currency code
95
+ * Defaults to CAD
96
+ */
97
+ currency = "CAD";
98
+ payoutId = "";
99
+ /**
100
+ * Commission status
101
+ */
102
+ status = CommissionStatus.PENDING;
103
+ /**
104
+ * Event timestamp (copied from AdEvent for reporting)
105
+ */
106
+ eventTimestamp = /* @__PURE__ */ new Date();
107
+ /**
108
+ * Network ID for aggregate queries (optional context)
109
+ */
110
+ networkId = "";
111
+ /**
112
+ * Site/Property ID for aggregate queries (optional context)
113
+ */
114
+ siteId = "";
115
+ /**
116
+ * Campaign ID for aggregate queries (optional context)
117
+ */
118
+ campaignId = "";
119
+ /**
120
+ * Additional metadata as JSON string
121
+ */
122
+ metadata = "";
123
+ constructor(options = {}) {
124
+ super(options);
125
+ if (options.eventId !== void 0) this.eventId = options.eventId;
126
+ if (options.partnerId !== void 0) this.partnerId = options.partnerId;
127
+ if (options.commissionType !== void 0)
128
+ this.commissionType = options.commissionType;
129
+ if (options.grossRevenue !== void 0)
130
+ this.grossRevenue = options.grossRevenue;
131
+ if (options.commissionRate !== void 0)
132
+ this.commissionRate = options.commissionRate;
133
+ if (options.commissionAmount !== void 0)
134
+ this.commissionAmount = options.commissionAmount;
135
+ if (options.currency !== void 0) this.currency = options.currency;
136
+ if (options.payoutId !== void 0) this.payoutId = options.payoutId;
137
+ if (options.status !== void 0) this.status = options.status;
138
+ if (options.eventTimestamp !== void 0)
139
+ this.eventTimestamp = options.eventTimestamp;
140
+ if (options.networkId !== void 0) this.networkId = options.networkId;
141
+ if (options.siteId !== void 0) this.siteId = options.siteId;
142
+ if (options.campaignId !== void 0) this.campaignId = options.campaignId;
143
+ if (options.metadata !== void 0) this.metadata = options.metadata;
144
+ }
145
+ /**
146
+ * Check if commission is pending (not yet in a payout)
147
+ */
148
+ isPending() {
149
+ return this.status === CommissionStatus.PENDING;
150
+ }
151
+ /**
152
+ * Check if commission is included in a payout
153
+ */
154
+ isIncluded() {
155
+ return this.status === CommissionStatus.INCLUDED;
156
+ }
157
+ /**
158
+ * Check if commission has been paid
159
+ */
160
+ isPaid() {
161
+ return this.status === CommissionStatus.PAID;
162
+ }
163
+ /**
164
+ * Check if this is a display commission
165
+ */
166
+ isDisplay() {
167
+ return this.commissionType === CommissionType.DISPLAY;
168
+ }
169
+ /**
170
+ * Check if this is a referral commission
171
+ */
172
+ isReferral() {
173
+ return this.commissionType === CommissionType.REFERRAL;
174
+ }
175
+ /**
176
+ * Check if this is a sales commission
177
+ */
178
+ isSales() {
179
+ return this.commissionType === CommissionType.SALES;
180
+ }
181
+ /**
182
+ * Check if this is a parent commission
183
+ */
184
+ isParent() {
185
+ return this.commissionType === CommissionType.PARENT;
186
+ }
187
+ /**
188
+ * Check if this is an overhead commission
189
+ */
190
+ isOverhead() {
191
+ return this.commissionType === CommissionType.OVERHEAD;
192
+ }
193
+ /**
194
+ * Get commission amount in dollars (for display)
195
+ */
196
+ getAmountInDollars() {
197
+ return this.commissionAmount / 100;
198
+ }
199
+ /**
200
+ * Get gross revenue in dollars (for display)
201
+ */
202
+ getGrossRevenueInDollars() {
203
+ return this.grossRevenue / 100;
204
+ }
205
+ /**
206
+ * Get metadata as object
207
+ */
208
+ getMetadata() {
209
+ if (!this.metadata) return {};
210
+ try {
211
+ return JSON.parse(this.metadata);
212
+ } catch {
213
+ return {};
214
+ }
215
+ }
216
+ /**
217
+ * Set metadata from object
218
+ */
219
+ setMetadata(data) {
220
+ this.metadata = JSON.stringify(data);
221
+ }
222
+ };
223
+ __decorateClass$2([
224
+ crossPackageRef("@happyvertical/smrt-ads:AdEvent")
225
+ ], Commission.prototype, "eventId", 2);
226
+ __decorateClass$2([
227
+ foreignKey("Partner")
228
+ ], Commission.prototype, "partnerId", 2);
229
+ __decorateClass$2([
230
+ foreignKey("Payout")
231
+ ], Commission.prototype, "payoutId", 2);
232
+ Commission = __decorateClass$2([
233
+ smrt({
234
+ api: { include: ["create", "list", "get"] },
235
+ // No delete (audit trail)
236
+ mcp: { include: ["create", "list"] },
237
+ cli: false
238
+ // High volume, not useful in CLI
239
+ })
240
+ ], Commission);
241
+ class CommissionCollection extends SmrtCollection {
242
+ static _itemClass = Commission;
243
+ /**
244
+ * Find commissions by partner
245
+ *
246
+ * @param partnerId - Partner ID
247
+ * @returns Array of commissions
248
+ */
249
+ async findByPartner(partnerId) {
250
+ return await this.list({
251
+ where: { partnerId },
252
+ orderBy: "event_timestamp DESC"
253
+ });
254
+ }
255
+ /**
256
+ * Find commissions by ad event
257
+ *
258
+ * @param eventId - Ad Event ID
259
+ * @returns Array of commissions
260
+ */
261
+ async findByEvent(eventId) {
262
+ return await this.list({
263
+ where: { eventId },
264
+ orderBy: "created_at DESC"
265
+ });
266
+ }
267
+ /**
268
+ * Find commissions by payout
269
+ *
270
+ * @param payoutId - Payout ID
271
+ * @returns Array of commissions
272
+ */
273
+ async findByPayout(payoutId) {
274
+ return await this.list({
275
+ where: { payoutId },
276
+ orderBy: "event_timestamp DESC"
277
+ });
278
+ }
279
+ /**
280
+ * Find commissions by status
281
+ *
282
+ * @param status - Commission status
283
+ * @returns Array of commissions
284
+ */
285
+ async findByStatus(status) {
286
+ return await this.list({
287
+ where: { status },
288
+ orderBy: "event_timestamp DESC"
289
+ });
290
+ }
291
+ /**
292
+ * Find all pending commissions
293
+ */
294
+ async findPending() {
295
+ return await this.findByStatus(CommissionStatus.PENDING);
296
+ }
297
+ /**
298
+ * Find pending commissions for a partner
299
+ *
300
+ * @param partnerId - Partner ID
301
+ * @returns Array of pending commissions
302
+ */
303
+ async findPendingByPartner(partnerId) {
304
+ return await this.list({
305
+ where: { partnerId, status: CommissionStatus.PENDING },
306
+ orderBy: "event_timestamp DESC"
307
+ });
308
+ }
309
+ /**
310
+ * Find commissions by type
311
+ *
312
+ * @param commissionType - Commission type
313
+ * @returns Array of commissions
314
+ */
315
+ async findByType(commissionType) {
316
+ return await this.list({
317
+ where: { commissionType },
318
+ orderBy: "event_timestamp DESC"
319
+ });
320
+ }
321
+ /**
322
+ * Find commissions in date range
323
+ *
324
+ * @param start - Start date
325
+ * @param end - End date
326
+ * @returns Array of commissions
327
+ */
328
+ async findByDateRange(start, end) {
329
+ return await this.list({
330
+ where: {
331
+ "event_timestamp >=": start.toISOString(),
332
+ "event_timestamp <=": end.toISOString()
333
+ },
334
+ orderBy: "event_timestamp DESC"
335
+ });
336
+ }
337
+ /**
338
+ * Find commissions for a partner in date range
339
+ *
340
+ * @param partnerId - Partner ID
341
+ * @param start - Start date
342
+ * @param end - End date
343
+ * @returns Array of commissions
344
+ */
345
+ async findByPartnerAndDateRange(partnerId, start, end) {
346
+ return await this.list({
347
+ where: {
348
+ partnerId,
349
+ "event_timestamp >=": start.toISOString(),
350
+ "event_timestamp <=": end.toISOString()
351
+ },
352
+ orderBy: "event_timestamp DESC"
353
+ });
354
+ }
355
+ // --- Network-scoped queries ---
356
+ /**
357
+ * Find all commissions for a network
358
+ *
359
+ * @param networkId - Network ID
360
+ * @returns Array of commissions
361
+ */
362
+ async findByNetwork(networkId) {
363
+ return await this.list({
364
+ where: { networkId },
365
+ orderBy: "event_timestamp DESC"
366
+ });
367
+ }
368
+ /**
369
+ * Find commissions for a network filtered by type
370
+ *
371
+ * @param networkId - Network ID
372
+ * @param commissionType - Commission type
373
+ * @returns Array of commissions
374
+ */
375
+ async findByNetworkAndType(networkId, commissionType) {
376
+ return await this.list({
377
+ where: { networkId, commissionType },
378
+ orderBy: "event_timestamp DESC"
379
+ });
380
+ }
381
+ /**
382
+ * Find pending commissions for a network
383
+ *
384
+ * @param networkId - Network ID
385
+ * @returns Array of pending commissions
386
+ */
387
+ async findPendingByNetwork(networkId) {
388
+ return await this.list({
389
+ where: { networkId, status: CommissionStatus.PENDING },
390
+ orderBy: "event_timestamp DESC"
391
+ });
392
+ }
393
+ /**
394
+ * Get aggregate summary of commissions by type and site for a network.
395
+ *
396
+ * @param networkId - Network ID
397
+ * @param options - Optional filters (siteId, from, to, commissionType)
398
+ * @returns Summary with byType, bySite, total, and count
399
+ */
400
+ async getSummaryByNetwork(networkId, options = {}) {
401
+ const where = { networkId };
402
+ if (options.siteId) where.siteId = options.siteId;
403
+ if (options.commissionType) where.commissionType = options.commissionType;
404
+ if (options.from) where["event_timestamp >="] = options.from.toISOString();
405
+ if (options.to) where["event_timestamp <="] = options.to.toISOString();
406
+ const filtered = await this.list({ where });
407
+ const byType = {
408
+ overhead: 0,
409
+ display: 0,
410
+ referral: 0,
411
+ sales: 0,
412
+ parent: 0
413
+ };
414
+ const bySite = {};
415
+ let total = 0;
416
+ for (const c of filtered) {
417
+ byType[c.commissionType] = (byType[c.commissionType] ?? 0) + c.commissionAmount;
418
+ if (c.siteId) {
419
+ bySite[c.siteId] = (bySite[c.siteId] ?? 0) + c.commissionAmount;
420
+ }
421
+ total += c.commissionAmount;
422
+ }
423
+ return { byType, bySite, total, count: filtered.length };
424
+ }
425
+ /**
426
+ * Get pending commissions grouped by partnerId for a network.
427
+ *
428
+ * @param networkId - Network ID
429
+ * @returns Array of payout groups (partnerId, totalPending, currency, entries)
430
+ */
431
+ async getPendingPayoutsByNetwork(networkId) {
432
+ const pending = await this.findPendingByNetwork(networkId);
433
+ const withPartner = pending.filter(
434
+ (c) => c.partnerId && c.partnerId !== ""
435
+ );
436
+ const grouped = /* @__PURE__ */ new Map();
437
+ for (const c of withPartner) {
438
+ const existing = grouped.get(c.partnerId) ?? [];
439
+ existing.push(c);
440
+ grouped.set(c.partnerId, existing);
441
+ }
442
+ const payouts = [];
443
+ for (const [partnerId, group] of grouped) {
444
+ const totalPending = group.reduce(
445
+ (sum, c) => sum + c.commissionAmount,
446
+ 0
447
+ );
448
+ const currency = group[0]?.currency ?? "CAD";
449
+ payouts.push({
450
+ partnerId,
451
+ totalPending,
452
+ currency,
453
+ entryCount: group.length,
454
+ entries: group.map((c) => ({
455
+ id: c.id ?? "",
456
+ commissionType: c.commissionType,
457
+ commissionAmount: c.commissionAmount,
458
+ campaignId: c.campaignId,
459
+ siteId: c.siteId
460
+ }))
461
+ });
462
+ }
463
+ payouts.sort((a, b) => b.totalPending - a.totalPending);
464
+ return payouts;
465
+ }
466
+ // --- Aggregation queries ---
467
+ /**
468
+ * Sum pending commissions for a partner
469
+ *
470
+ * @param partnerId - Partner ID
471
+ * @returns Total pending amount in cents
472
+ */
473
+ async sumPendingByPartner(partnerId) {
474
+ const pending = await this.findPendingByPartner(partnerId);
475
+ return pending.reduce((sum, c) => sum + c.commissionAmount, 0);
476
+ }
477
+ /**
478
+ * Sum commissions by type for a partner
479
+ *
480
+ * @param partnerId - Partner ID
481
+ * @param commissionType - Commission type
482
+ * @returns Total amount in cents
483
+ */
484
+ async sumByPartnerAndType(partnerId, commissionType) {
485
+ const commissions = await this.list({
486
+ where: { partnerId, commissionType }
487
+ });
488
+ return commissions.reduce((sum, c) => sum + c.commissionAmount, 0);
489
+ }
490
+ /**
491
+ * Get earnings breakdown for a partner
492
+ *
493
+ * @param partnerId - Partner ID
494
+ * @returns Breakdown by commission type
495
+ */
496
+ async getEarningsBreakdown(partnerId) {
497
+ const commissions = await this.findByPartner(partnerId);
498
+ const breakdown = {
499
+ display: 0,
500
+ referral: 0,
501
+ sales: 0,
502
+ parent: 0,
503
+ overhead: 0,
504
+ total: 0
505
+ };
506
+ for (const c of commissions) {
507
+ if (c.isPaid() || c.isIncluded()) {
508
+ switch (c.commissionType) {
509
+ case CommissionType.DISPLAY:
510
+ breakdown.display += c.commissionAmount;
511
+ break;
512
+ case CommissionType.REFERRAL:
513
+ breakdown.referral += c.commissionAmount;
514
+ break;
515
+ case CommissionType.SALES:
516
+ breakdown.sales += c.commissionAmount;
517
+ break;
518
+ case CommissionType.PARENT:
519
+ breakdown.parent += c.commissionAmount;
520
+ break;
521
+ case CommissionType.OVERHEAD:
522
+ breakdown.overhead += c.commissionAmount;
523
+ break;
524
+ }
525
+ }
526
+ }
527
+ breakdown.total = breakdown.display + breakdown.referral + breakdown.sales + breakdown.parent + breakdown.overhead;
528
+ return breakdown;
529
+ }
530
+ /**
531
+ * Get pending earnings breakdown for a partner
532
+ *
533
+ * @param partnerId - Partner ID
534
+ * @returns Breakdown of pending commissions by type
535
+ */
536
+ async getPendingBreakdown(partnerId) {
537
+ const pending = await this.findPendingByPartner(partnerId);
538
+ const breakdown = {
539
+ display: 0,
540
+ referral: 0,
541
+ sales: 0,
542
+ parent: 0,
543
+ overhead: 0,
544
+ total: 0
545
+ };
546
+ for (const c of pending) {
547
+ switch (c.commissionType) {
548
+ case CommissionType.DISPLAY:
549
+ breakdown.display += c.commissionAmount;
550
+ break;
551
+ case CommissionType.REFERRAL:
552
+ breakdown.referral += c.commissionAmount;
553
+ break;
554
+ case CommissionType.SALES:
555
+ breakdown.sales += c.commissionAmount;
556
+ break;
557
+ case CommissionType.PARENT:
558
+ breakdown.parent += c.commissionAmount;
559
+ break;
560
+ case CommissionType.OVERHEAD:
561
+ breakdown.overhead += c.commissionAmount;
562
+ break;
563
+ }
564
+ }
565
+ breakdown.total = breakdown.display + breakdown.referral + breakdown.sales + breakdown.parent + breakdown.overhead;
566
+ return breakdown;
567
+ }
568
+ }
569
+ var __defProp$1 = Object.defineProperty;
570
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
571
+ var __decorateClass$1 = (decorators, target, key, kind) => {
572
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
573
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
574
+ if (decorator = decorators[i])
575
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
576
+ if (kind && result) __defProp$1(target, key, result);
577
+ return result;
578
+ };
579
+ let Partner = class extends SmrtObject {
580
+ profileId = "";
581
+ propertyId = "";
582
+ /**
583
+ * Partner types as JSON array (publisher, salesperson, referrer)
584
+ * A partner can have multiple types
585
+ */
586
+ partnerTypes = "[]";
587
+ parentPartnerId = "";
588
+ referredById = "";
589
+ /**
590
+ * Share of commission that goes to parent partner (0-1)
591
+ * e.g., 0.20 means 20% of this partner's sales commission goes to parent
592
+ */
593
+ parentCommissionShare = 0;
594
+ /**
595
+ * Commission rate for display (impression) revenue (0-1)
596
+ * Default 50% - publisher earns half of ad impression revenue
597
+ */
598
+ displayCommissionRate = 0.5;
599
+ /**
600
+ * Commission rate for referral revenue (0-1)
601
+ * Default 5% - referrer earns 5% of referred partner's first-year revenue
602
+ */
603
+ referralCommissionRate = 0.05;
604
+ /**
605
+ * Commission rate for sales revenue (0-1)
606
+ * Default 10% - salesperson earns 10% of advertiser spend they brought in
607
+ */
608
+ salesCommissionRate = 0.1;
609
+ /**
610
+ * Minimum payout threshold in cents
611
+ * Default $50 = 5000 cents
612
+ */
613
+ payoutThreshold = 5e3;
614
+ /**
615
+ * Preferred payout method
616
+ */
617
+ payoutMethod = PayoutMethod.BANK_TRANSFER;
618
+ /**
619
+ * Currency code for all monetary values
620
+ * Defaults to CAD, allows future multi-currency support
621
+ */
622
+ currency = "CAD";
623
+ /**
624
+ * Partner status
625
+ */
626
+ status = PartnerStatus.PENDING;
627
+ /**
628
+ * Additional metadata as JSON string
629
+ * (bank details, tax info, etc.)
630
+ */
631
+ metadata = "";
632
+ constructor(options = {}) {
633
+ super(options);
634
+ if (options.profileId !== void 0) this.profileId = options.profileId;
635
+ if (options.propertyId !== void 0) this.propertyId = options.propertyId;
636
+ if (options.partnerTypes !== void 0)
637
+ this.partnerTypes = options.partnerTypes;
638
+ if (options.parentPartnerId !== void 0)
639
+ this.parentPartnerId = options.parentPartnerId;
640
+ if (options.referredById !== void 0)
641
+ this.referredById = options.referredById;
642
+ if (options.parentCommissionShare !== void 0)
643
+ this.parentCommissionShare = options.parentCommissionShare;
644
+ if (options.displayCommissionRate !== void 0)
645
+ this.displayCommissionRate = options.displayCommissionRate;
646
+ if (options.referralCommissionRate !== void 0)
647
+ this.referralCommissionRate = options.referralCommissionRate;
648
+ if (options.salesCommissionRate !== void 0)
649
+ this.salesCommissionRate = options.salesCommissionRate;
650
+ if (options.payoutThreshold !== void 0)
651
+ this.payoutThreshold = options.payoutThreshold;
652
+ if (options.payoutMethod !== void 0)
653
+ this.payoutMethod = options.payoutMethod;
654
+ if (options.currency !== void 0) this.currency = options.currency;
655
+ if (options.status !== void 0) this.status = options.status;
656
+ if (options.metadata !== void 0) this.metadata = options.metadata;
657
+ }
658
+ /**
659
+ * Get partner types as array
660
+ */
661
+ getPartnerTypes() {
662
+ if (!this.partnerTypes) return [];
663
+ try {
664
+ return JSON.parse(this.partnerTypes);
665
+ } catch {
666
+ return [];
667
+ }
668
+ }
669
+ /**
670
+ * Set partner types from array
671
+ */
672
+ setPartnerTypes(types) {
673
+ this.partnerTypes = JSON.stringify(types);
674
+ }
675
+ /**
676
+ * Check if partner has a specific type
677
+ */
678
+ hasType(type) {
679
+ return this.getPartnerTypes().includes(type);
680
+ }
681
+ /**
682
+ * Check if partner is a publisher
683
+ */
684
+ isPublisher() {
685
+ return this.hasType(PartnerType.PUBLISHER);
686
+ }
687
+ /**
688
+ * Check if partner is a salesperson
689
+ */
690
+ isSalesperson() {
691
+ return this.hasType(PartnerType.SALESPERSON);
692
+ }
693
+ /**
694
+ * Check if partner is a referrer
695
+ */
696
+ isReferrer() {
697
+ return this.hasType(PartnerType.REFERRER);
698
+ }
699
+ /**
700
+ * Check if partner is active
701
+ */
702
+ isActive() {
703
+ return this.status === PartnerStatus.ACTIVE;
704
+ }
705
+ /**
706
+ * Check if partner is pending approval
707
+ */
708
+ isPending() {
709
+ return this.status === PartnerStatus.PENDING;
710
+ }
711
+ /**
712
+ * Check if partner is suspended
713
+ */
714
+ isSuspended() {
715
+ return this.status === PartnerStatus.SUSPENDED;
716
+ }
717
+ /**
718
+ * Check if partner has a parent (site-attached)
719
+ */
720
+ hasParent() {
721
+ return !!this.parentPartnerId;
722
+ }
723
+ /**
724
+ * Check if partner was referred by another partner
725
+ */
726
+ wasReferred() {
727
+ return !!this.referredById;
728
+ }
729
+ /**
730
+ * Get metadata as object
731
+ */
732
+ getMetadata() {
733
+ if (!this.metadata) return {};
734
+ try {
735
+ return JSON.parse(this.metadata);
736
+ } catch {
737
+ return {};
738
+ }
739
+ }
740
+ /**
741
+ * Set metadata from object
742
+ */
743
+ setMetadata(data) {
744
+ this.metadata = JSON.stringify(data);
745
+ }
746
+ /**
747
+ * Calculate effective sales commission rate (after parent share)
748
+ */
749
+ getEffectiveSalesRate() {
750
+ if (this.parentPartnerId && this.parentCommissionShare > 0) {
751
+ return this.salesCommissionRate * (1 - this.parentCommissionShare);
752
+ }
753
+ return this.salesCommissionRate;
754
+ }
755
+ };
756
+ __decorateClass$1([
757
+ crossPackageRef("@happyvertical/smrt-profiles:Profile")
758
+ ], Partner.prototype, "profileId", 2);
759
+ __decorateClass$1([
760
+ crossPackageRef("@happyvertical/smrt-properties:Property")
761
+ ], Partner.prototype, "propertyId", 2);
762
+ __decorateClass$1([
763
+ foreignKey("Partner")
764
+ ], Partner.prototype, "parentPartnerId", 2);
765
+ __decorateClass$1([
766
+ foreignKey("Partner")
767
+ ], Partner.prototype, "referredById", 2);
768
+ Partner = __decorateClass$1([
769
+ smrt({
770
+ api: { include: ["list", "get", "create", "update"] },
771
+ mcp: { include: ["list", "get", "create"] },
772
+ cli: true
773
+ })
774
+ ], Partner);
775
+ class PartnerCollection extends SmrtCollection {
776
+ static _itemClass = Partner;
777
+ /**
778
+ * Find partners by profile ID
779
+ *
780
+ * @param profileId - Profile ID
781
+ * @returns Array of partners
782
+ */
783
+ async findByProfile(profileId) {
784
+ return await this.list({
785
+ where: { profileId },
786
+ orderBy: "created_at DESC"
787
+ });
788
+ }
789
+ /**
790
+ * Find partner by property ID (publishers)
791
+ *
792
+ * @param propertyId - Property ID
793
+ * @returns Partner or null
794
+ */
795
+ async findByProperty(propertyId) {
796
+ const results = await this.list({
797
+ where: { propertyId },
798
+ limit: 1
799
+ });
800
+ return results[0] || null;
801
+ }
802
+ /**
803
+ * Find partners by parent partner ID
804
+ *
805
+ * @param parentPartnerId - Parent partner ID
806
+ * @returns Array of child partners
807
+ */
808
+ async findByParent(parentPartnerId) {
809
+ return await this.list({
810
+ where: { parentPartnerId },
811
+ orderBy: "created_at DESC"
812
+ });
813
+ }
814
+ /**
815
+ * Find partners referred by a specific partner
816
+ *
817
+ * @param referredById - Referrer partner ID
818
+ * @returns Array of referred partners
819
+ */
820
+ async findByReferrer(referredById) {
821
+ return await this.list({
822
+ where: { referredById },
823
+ orderBy: "created_at DESC"
824
+ });
825
+ }
826
+ /**
827
+ * Find partners by status
828
+ *
829
+ * @param status - Partner status
830
+ * @returns Array of partners
831
+ */
832
+ async findByStatus(status) {
833
+ return await this.list({
834
+ where: { status },
835
+ orderBy: "created_at DESC"
836
+ });
837
+ }
838
+ /**
839
+ * Find all active partners
840
+ */
841
+ async findActive() {
842
+ return await this.findByStatus(PartnerStatus.ACTIVE);
843
+ }
844
+ /**
845
+ * Find all pending partners (awaiting approval)
846
+ */
847
+ async findPending() {
848
+ return await this.findByStatus(PartnerStatus.PENDING);
849
+ }
850
+ /**
851
+ * Find all suspended partners
852
+ */
853
+ async findSuspended() {
854
+ return await this.findByStatus(PartnerStatus.SUSPENDED);
855
+ }
856
+ /**
857
+ * Find partners by type (requires in-memory filtering for JSON field)
858
+ *
859
+ * @param type - Partner type to filter by
860
+ * @returns Array of partners with that type
861
+ */
862
+ async findByType(type) {
863
+ const all = await this.list({});
864
+ return all.filter((partner) => partner.hasType(type));
865
+ }
866
+ /**
867
+ * Find all publisher partners
868
+ */
869
+ async findPublishers() {
870
+ return await this.findByType(PartnerType.PUBLISHER);
871
+ }
872
+ /**
873
+ * Find all salesperson partners
874
+ */
875
+ async findSalespeople() {
876
+ return await this.findByType(PartnerType.SALESPERSON);
877
+ }
878
+ /**
879
+ * Find all referrer partners
880
+ */
881
+ async findReferrers() {
882
+ return await this.findByType(PartnerType.REFERRER);
883
+ }
884
+ /**
885
+ * Find active publishers (for ad serving)
886
+ */
887
+ async findActivePublishers() {
888
+ const publishers = await this.findPublishers();
889
+ return publishers.filter((p) => p.isActive());
890
+ }
891
+ /**
892
+ * Find partner for ad serving by property ID
893
+ * Returns active publisher partner for the property
894
+ *
895
+ * @param propertyId - Property ID
896
+ * @returns Active publisher partner or null
897
+ */
898
+ async findActiveByProperty(propertyId) {
899
+ const partner = await this.findByProperty(propertyId);
900
+ if (partner?.isActive() && partner.isPublisher()) {
901
+ return partner;
902
+ }
903
+ return null;
904
+ }
905
+ /**
906
+ * Find partners eligible for payout
907
+ * (active partners with pending commissions above threshold)
908
+ * Note: Actual threshold check requires joining with commissions
909
+ */
910
+ async findEligibleForPayout() {
911
+ const active = await this.findActive();
912
+ return active;
913
+ }
914
+ }
915
+ var __defProp = Object.defineProperty;
916
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
917
+ var __decorateClass = (decorators, target, key, kind) => {
918
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
919
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
920
+ if (decorator = decorators[i])
921
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
922
+ if (kind && result) __defProp(target, key, result);
923
+ return result;
924
+ };
925
+ let Payout = class extends SmrtObject {
926
+ partnerId = "";
927
+ /**
928
+ * Period start date
929
+ */
930
+ periodStart = /* @__PURE__ */ new Date();
931
+ /**
932
+ * Period end date
933
+ */
934
+ periodEnd = /* @__PURE__ */ new Date();
935
+ /**
936
+ * Total display earnings in cents
937
+ */
938
+ displayEarnings = 0;
939
+ /**
940
+ * Total referral earnings in cents
941
+ */
942
+ referralEarnings = 0;
943
+ /**
944
+ * Total sales earnings in cents
945
+ */
946
+ salesEarnings = 0;
947
+ /**
948
+ * Total parent earnings in cents (from site-attached salespeople)
949
+ */
950
+ parentEarnings = 0;
951
+ /**
952
+ * Total overhead earnings in cents (network overhead commissions)
953
+ */
954
+ overheadEarnings = 0;
955
+ /**
956
+ * Total payout amount in cents
957
+ * = displayEarnings + referralEarnings + salesEarnings + parentEarnings + overheadEarnings
958
+ */
959
+ totalAmount = 0;
960
+ /**
961
+ * Currency code
962
+ * Defaults to CAD
963
+ */
964
+ currency = "CAD";
965
+ invoiceId = "";
966
+ /**
967
+ * Payout status
968
+ */
969
+ status = PayoutStatus.PENDING;
970
+ /**
971
+ * Payment reference (check number, transfer ID, etc.)
972
+ */
973
+ paymentReference = "";
974
+ /**
975
+ * Date payment was processed
976
+ */
977
+ paidAt = null;
978
+ /**
979
+ * Notes from admin (approval notes, issues, etc.)
980
+ */
981
+ notes = "";
982
+ /**
983
+ * Additional metadata as JSON string
984
+ */
985
+ metadata = "";
986
+ constructor(options = {}) {
987
+ super(options);
988
+ if (options.partnerId !== void 0) this.partnerId = options.partnerId;
989
+ if (options.periodStart !== void 0)
990
+ this.periodStart = options.periodStart;
991
+ if (options.periodEnd !== void 0) this.periodEnd = options.periodEnd;
992
+ if (options.displayEarnings !== void 0)
993
+ this.displayEarnings = options.displayEarnings;
994
+ if (options.referralEarnings !== void 0)
995
+ this.referralEarnings = options.referralEarnings;
996
+ if (options.salesEarnings !== void 0)
997
+ this.salesEarnings = options.salesEarnings;
998
+ if (options.parentEarnings !== void 0)
999
+ this.parentEarnings = options.parentEarnings;
1000
+ if (options.overheadEarnings !== void 0)
1001
+ this.overheadEarnings = options.overheadEarnings;
1002
+ if (options.totalAmount !== void 0)
1003
+ this.totalAmount = options.totalAmount;
1004
+ if (options.currency !== void 0) this.currency = options.currency;
1005
+ if (options.invoiceId !== void 0) this.invoiceId = options.invoiceId;
1006
+ if (options.status !== void 0) this.status = options.status;
1007
+ if (options.paymentReference !== void 0)
1008
+ this.paymentReference = options.paymentReference;
1009
+ if (options.paidAt !== void 0) this.paidAt = options.paidAt;
1010
+ if (options.notes !== void 0) this.notes = options.notes;
1011
+ if (options.metadata !== void 0) this.metadata = options.metadata;
1012
+ }
1013
+ /**
1014
+ * Check if payout is pending approval
1015
+ */
1016
+ isPending() {
1017
+ return this.status === PayoutStatus.PENDING;
1018
+ }
1019
+ /**
1020
+ * Check if payout is approved (waiting for processing)
1021
+ */
1022
+ isApproved() {
1023
+ return this.status === PayoutStatus.APPROVED;
1024
+ }
1025
+ /**
1026
+ * Check if payout is being processed
1027
+ */
1028
+ isProcessing() {
1029
+ return this.status === PayoutStatus.PROCESSING;
1030
+ }
1031
+ /**
1032
+ * Check if payout is completed
1033
+ */
1034
+ isCompleted() {
1035
+ return this.status === PayoutStatus.COMPLETED;
1036
+ }
1037
+ /**
1038
+ * Check if payout failed
1039
+ */
1040
+ isFailed() {
1041
+ return this.status === PayoutStatus.FAILED;
1042
+ }
1043
+ /**
1044
+ * Get total amount in dollars (for display)
1045
+ */
1046
+ getTotalInDollars() {
1047
+ return this.totalAmount / 100;
1048
+ }
1049
+ /**
1050
+ * Get display earnings in dollars
1051
+ */
1052
+ getDisplayEarningsInDollars() {
1053
+ return this.displayEarnings / 100;
1054
+ }
1055
+ /**
1056
+ * Get referral earnings in dollars
1057
+ */
1058
+ getReferralEarningsInDollars() {
1059
+ return this.referralEarnings / 100;
1060
+ }
1061
+ /**
1062
+ * Get sales earnings in dollars
1063
+ */
1064
+ getSalesEarningsInDollars() {
1065
+ return this.salesEarnings / 100;
1066
+ }
1067
+ /**
1068
+ * Get parent earnings in dollars
1069
+ */
1070
+ getParentEarningsInDollars() {
1071
+ return this.parentEarnings / 100;
1072
+ }
1073
+ /**
1074
+ * Get overhead earnings in dollars
1075
+ */
1076
+ getOverheadEarningsInDollars() {
1077
+ return this.overheadEarnings / 100;
1078
+ }
1079
+ /**
1080
+ * Calculate total from component earnings
1081
+ */
1082
+ calculateTotal() {
1083
+ return this.displayEarnings + this.referralEarnings + this.salesEarnings + this.parentEarnings + this.overheadEarnings;
1084
+ }
1085
+ /**
1086
+ * Get period as human-readable string
1087
+ */
1088
+ getPeriodString() {
1089
+ const start = this.periodStart.toISOString().split("T")[0];
1090
+ const end = this.periodEnd.toISOString().split("T")[0];
1091
+ return `${start} to ${end}`;
1092
+ }
1093
+ /**
1094
+ * Get metadata as object
1095
+ */
1096
+ getMetadata() {
1097
+ if (!this.metadata) return {};
1098
+ try {
1099
+ return JSON.parse(this.metadata);
1100
+ } catch {
1101
+ return {};
1102
+ }
1103
+ }
1104
+ /**
1105
+ * Set metadata from object
1106
+ */
1107
+ setMetadata(data) {
1108
+ this.metadata = JSON.stringify(data);
1109
+ }
1110
+ // ============================================================================
1111
+ // Status Transitions
1112
+ // ============================================================================
1113
+ /**
1114
+ * Approve the payout for processing
1115
+ *
1116
+ * @throws Error if payout is not in pending status
1117
+ */
1118
+ approve() {
1119
+ if (this.status !== PayoutStatus.PENDING) {
1120
+ throw new Error(
1121
+ `Cannot approve payout with status '${this.status}'. Only pending payouts can be approved.`
1122
+ );
1123
+ }
1124
+ this.status = PayoutStatus.APPROVED;
1125
+ }
1126
+ /**
1127
+ * Mark payout as processing (payment in progress)
1128
+ *
1129
+ * @throws Error if payout is not in approved status
1130
+ */
1131
+ markProcessing() {
1132
+ if (this.status !== PayoutStatus.APPROVED) {
1133
+ throw new Error(
1134
+ `Cannot mark payout as processing with status '${this.status}'. Only approved payouts can be processed.`
1135
+ );
1136
+ }
1137
+ this.status = PayoutStatus.PROCESSING;
1138
+ }
1139
+ /**
1140
+ * Mark payout as completed
1141
+ *
1142
+ * @param paymentReference - Payment reference (check number, transfer ID, etc.)
1143
+ * @throws Error if payout is not in processing status
1144
+ */
1145
+ complete(paymentReference) {
1146
+ if (this.status !== PayoutStatus.PROCESSING) {
1147
+ throw new Error(
1148
+ `Cannot complete payout with status '${this.status}'. Only processing payouts can be completed.`
1149
+ );
1150
+ }
1151
+ this.status = PayoutStatus.COMPLETED;
1152
+ this.paymentReference = paymentReference;
1153
+ this.paidAt = /* @__PURE__ */ new Date();
1154
+ }
1155
+ /**
1156
+ * Mark payout as failed
1157
+ *
1158
+ * @param reason - Reason for failure (stored in notes)
1159
+ */
1160
+ fail(reason) {
1161
+ if (this.status !== PayoutStatus.APPROVED && this.status !== PayoutStatus.PROCESSING) {
1162
+ throw new Error(
1163
+ `Cannot fail payout with status '${this.status}'. Only approved or processing payouts can fail.`
1164
+ );
1165
+ }
1166
+ this.status = PayoutStatus.FAILED;
1167
+ this.notes = reason;
1168
+ }
1169
+ };
1170
+ __decorateClass([
1171
+ foreignKey("Partner")
1172
+ ], Payout.prototype, "partnerId", 2);
1173
+ __decorateClass([
1174
+ crossPackageRef("@happyvertical/smrt-commerce:Invoice")
1175
+ ], Payout.prototype, "invoiceId", 2);
1176
+ Payout = __decorateClass([
1177
+ smrt({
1178
+ api: { include: ["list", "get", "create", "update"] },
1179
+ mcp: { include: ["list", "get", "create"] },
1180
+ cli: true
1181
+ })
1182
+ ], Payout);
1183
+ class PayoutCollection extends SmrtCollection {
1184
+ static _itemClass = Payout;
1185
+ /**
1186
+ * Find payouts by partner
1187
+ *
1188
+ * @param partnerId - Partner ID
1189
+ * @returns Array of payouts
1190
+ */
1191
+ async findByPartner(partnerId) {
1192
+ return await this.list({
1193
+ where: { partnerId },
1194
+ orderBy: "period_end DESC"
1195
+ });
1196
+ }
1197
+ /**
1198
+ * Find payouts by status
1199
+ *
1200
+ * @param status - Payout status
1201
+ * @returns Array of payouts
1202
+ */
1203
+ async findByStatus(status) {
1204
+ return await this.list({
1205
+ where: { status },
1206
+ orderBy: "period_end DESC"
1207
+ });
1208
+ }
1209
+ /**
1210
+ * Find all pending payouts (awaiting approval)
1211
+ */
1212
+ async findPending() {
1213
+ return await this.findByStatus(PayoutStatus.PENDING);
1214
+ }
1215
+ /**
1216
+ * Find all approved payouts (ready for processing)
1217
+ */
1218
+ async findApproved() {
1219
+ return await this.findByStatus(PayoutStatus.APPROVED);
1220
+ }
1221
+ /**
1222
+ * Find all processing payouts
1223
+ */
1224
+ async findProcessing() {
1225
+ return await this.findByStatus(PayoutStatus.PROCESSING);
1226
+ }
1227
+ /**
1228
+ * Find all completed payouts
1229
+ */
1230
+ async findCompleted() {
1231
+ return await this.findByStatus(PayoutStatus.COMPLETED);
1232
+ }
1233
+ /**
1234
+ * Find all failed payouts
1235
+ */
1236
+ async findFailed() {
1237
+ return await this.findByStatus(PayoutStatus.FAILED);
1238
+ }
1239
+ /**
1240
+ * Find payouts by invoice
1241
+ *
1242
+ * @param invoiceId - Invoice ID
1243
+ * @returns Array of payouts
1244
+ */
1245
+ async findByInvoice(invoiceId) {
1246
+ return await this.list({
1247
+ where: { invoiceId },
1248
+ orderBy: "period_end DESC"
1249
+ });
1250
+ }
1251
+ /**
1252
+ * Find payouts in period range
1253
+ *
1254
+ * @param start - Period start date
1255
+ * @param end - Period end date
1256
+ * @returns Array of payouts
1257
+ */
1258
+ async findByPeriod(start, end) {
1259
+ return await this.list({
1260
+ where: {
1261
+ "period_start >=": start.toISOString(),
1262
+ "period_end <=": end.toISOString()
1263
+ },
1264
+ orderBy: "period_end DESC"
1265
+ });
1266
+ }
1267
+ /**
1268
+ * Find payouts for a partner by status
1269
+ *
1270
+ * @param partnerId - Partner ID
1271
+ * @param status - Payout status
1272
+ * @returns Array of payouts
1273
+ */
1274
+ async findByPartnerAndStatus(partnerId, status) {
1275
+ return await this.list({
1276
+ where: { partnerId, status },
1277
+ orderBy: "period_end DESC"
1278
+ });
1279
+ }
1280
+ /**
1281
+ * Find pending payouts for a partner
1282
+ *
1283
+ * @param partnerId - Partner ID
1284
+ * @returns Array of pending payouts
1285
+ */
1286
+ async findPendingByPartner(partnerId) {
1287
+ return await this.findByPartnerAndStatus(partnerId, PayoutStatus.PENDING);
1288
+ }
1289
+ /**
1290
+ * Find completed payouts for a partner
1291
+ *
1292
+ * @param partnerId - Partner ID
1293
+ * @returns Array of completed payouts
1294
+ */
1295
+ async findCompletedByPartner(partnerId) {
1296
+ return await this.findByPartnerAndStatus(partnerId, PayoutStatus.COMPLETED);
1297
+ }
1298
+ /**
1299
+ * Sum total paid to a partner
1300
+ *
1301
+ * @param partnerId - Partner ID
1302
+ * @returns Total paid in cents
1303
+ */
1304
+ async sumPaidByPartner(partnerId) {
1305
+ const completed = await this.findCompletedByPartner(partnerId);
1306
+ return completed.reduce((sum, p) => sum + p.totalAmount, 0);
1307
+ }
1308
+ /**
1309
+ * Sum total pending for a partner
1310
+ *
1311
+ * @param partnerId - Partner ID
1312
+ * @returns Total pending in cents
1313
+ */
1314
+ async sumPendingByPartner(partnerId) {
1315
+ const pending = await this.findPendingByPartner(partnerId);
1316
+ return pending.reduce((sum, p) => sum + p.totalAmount, 0);
1317
+ }
1318
+ /**
1319
+ * Get most recent payout for a partner
1320
+ *
1321
+ * @param partnerId - Partner ID
1322
+ * @returns Most recent payout or null
1323
+ */
1324
+ async findLatestByPartner(partnerId) {
1325
+ const results = await this.list({
1326
+ where: { partnerId },
1327
+ orderBy: "period_end DESC",
1328
+ limit: 1
1329
+ });
1330
+ return results[0] || null;
1331
+ }
1332
+ /**
1333
+ * Get payout statistics
1334
+ *
1335
+ * @returns Stats object
1336
+ */
1337
+ async getStats() {
1338
+ const all = await this.list({});
1339
+ const stats = {
1340
+ pending: 0,
1341
+ approved: 0,
1342
+ processing: 0,
1343
+ completed: 0,
1344
+ failed: 0,
1345
+ totalPaid: 0,
1346
+ totalPending: 0
1347
+ };
1348
+ for (const p of all) {
1349
+ switch (p.status) {
1350
+ case PayoutStatus.PENDING:
1351
+ stats.pending++;
1352
+ stats.totalPending += p.totalAmount;
1353
+ break;
1354
+ case PayoutStatus.APPROVED:
1355
+ stats.approved++;
1356
+ stats.totalPending += p.totalAmount;
1357
+ break;
1358
+ case PayoutStatus.PROCESSING:
1359
+ stats.processing++;
1360
+ break;
1361
+ case PayoutStatus.COMPLETED:
1362
+ stats.completed++;
1363
+ stats.totalPaid += p.totalAmount;
1364
+ break;
1365
+ case PayoutStatus.FAILED:
1366
+ stats.failed++;
1367
+ break;
1368
+ }
1369
+ }
1370
+ return stats;
1371
+ }
1372
+ }
1373
+ export {
1374
+ Commission,
1375
+ CommissionCollection,
1376
+ CommissionStatus,
1377
+ CommissionType,
1378
+ Partner,
1379
+ PartnerCollection,
1380
+ PartnerStatus,
1381
+ PartnerType,
1382
+ Payout,
1383
+ PayoutCollection,
1384
+ PayoutMethod,
1385
+ PayoutStatus
1386
+ };
1387
+ //# sourceMappingURL=index.js.map