@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/AGENTS.md +62 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +118 -0
- package/dist/index.d.ts +932 -0
- package/dist/index.js +1387 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +1968 -0
- package/dist/smrt-knowledge.json +997 -0
- package/package.json +60 -0
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
|