@classytic/loyalty 0.7.0 → 0.10.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/CHANGELOG.md +134 -0
- package/dist/constants-DfrENxFG.mjs +75 -0
- package/dist/domain/earn-base.d.mts +35 -0
- package/dist/domain/earn-base.mjs +37 -0
- package/dist/domain/enums/index.d.mts +2 -2
- package/dist/domain/enums/index.mjs +1 -2
- package/dist/domain/rule-schedule.d.mts +24 -0
- package/dist/domain/rule-schedule.mjs +12 -0
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +3 -3
- package/dist/{events-BLjg3uLA.mjs → events-DhZolW61.mjs} +104 -4
- package/dist/{index-BIPHUsfR.d.mts → index-BvyzKU7R.d.mts} +283 -34
- package/dist/{index-BIhVuwIZ.d.mts → index-CorB0JL6.d.mts} +255 -109
- package/dist/index-DRrEhE0r.d.mts +604 -0
- package/dist/{index-YUFamhDk.d.mts → index-Db5Gb7x_.d.mts} +187 -39
- package/dist/index-tjMMmPzU.d.mts +36 -0
- package/dist/index.d.mts +121 -30
- package/dist/index.mjs +59 -10
- package/dist/{loyalty-events-DXirolM_.mjs → loyalty-events-j29B66YW.mjs} +20 -13
- package/dist/models/index.d.mts +2 -2
- package/dist/models/index.mjs +1 -2
- package/dist/{models-weWqS5Bc.mjs → models-BEGLduz1.mjs} +276 -7
- package/dist/notifications/index.d.mts +3 -4
- package/dist/repositories/index.d.mts +2 -2
- package/dist/repositories/index.mjs +1 -2
- package/dist/{repositories-DErtHQou.mjs → repositories-ER68JmUo.mjs} +1161 -99
- package/dist/types/index.d.mts +2 -3
- package/package.json +39 -22
- package/dist/config-CtkGj3xZ.d.mts +0 -178
- package/dist/constants-DOPBh6kt.mjs +0 -48
- package/dist/index-Cp_sXNpG.d.mts +0 -349
- package/dist/index-D_Oe3aDk.d.mts +0 -19
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as ReferralStatus, i as RedemptionStatus, t as EarnSource } from "./index-tjMMmPzU.mjs";
|
|
2
|
+
import { A as RepositoryPlugins, I as TierDefinition, L as EarningRule, P as StampRewardIssuer, _ as RedemptionValidation, b as PointTransaction, f as EarningResult, h as LoyaltyLiabilitySnapshot, j as ResolvedConfig, o as LoyaltyContext, p as ExpirationResult, u as EarnReversalResult, v as TierEvaluation, x as Member, y as TierEvaluationBatch } from "./index-DRrEhE0r.mjs";
|
|
3
3
|
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
4
|
-
import { c as
|
|
5
|
-
import { n as LoyaltyModels } from "./index-BIhVuwIZ.mjs";
|
|
4
|
+
import { c as StampOrderInput, d as StampProgress, f as StampRecordResult, i as StampAward, n as LoyaltyModels, p as StampReversal, s as StampEntry, u as StampProgram } from "./index-CorB0JL6.mjs";
|
|
6
5
|
import { PluginType, Repository } from "@classytic/mongokit";
|
|
7
6
|
import { ResolvedTenantConfig } from "@classytic/repo-core/tenant";
|
|
8
7
|
import { ClientSession, Model } from "mongoose";
|
|
@@ -25,12 +24,70 @@ interface UnitOfWork {
|
|
|
25
24
|
readonly allowsNonTransactional?: boolean;
|
|
26
25
|
}
|
|
27
26
|
//#endregion
|
|
27
|
+
//#region src/domain/program-settings.d.ts
|
|
28
|
+
interface EffectiveProgram {
|
|
29
|
+
pointValueMinor: number;
|
|
30
|
+
redemption: {
|
|
31
|
+
minPoints: number;
|
|
32
|
+
minOrderAmount: number;
|
|
33
|
+
maxRedeemPercent: number;
|
|
34
|
+
};
|
|
35
|
+
expiry: {
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
defaultDays?: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/repositories/tier-definition.repository.d.ts
|
|
42
|
+
declare class TierDefinitionRepository extends Repository<TierDefinition> {
|
|
43
|
+
memberRepo: MemberRepository;
|
|
44
|
+
events: EventTransport;
|
|
45
|
+
outbox: OutboxStore | undefined;
|
|
46
|
+
notifications: NotificationAdapter | null;
|
|
47
|
+
config: ResolvedConfig;
|
|
48
|
+
constructor(model: Model<TierDefinition>, plugins?: PluginType[]);
|
|
49
|
+
/** Auto-inject tenant id from options into doc payload. See MemberRepository. */
|
|
50
|
+
create(data: Parameters<Repository<TierDefinition>['create']>[0], options?: Parameters<Repository<TierDefinition>['create']>[1]): Promise<TierDefinition>;
|
|
51
|
+
createMany(docs: Parameters<Repository<TierDefinition>['createMany']>[0], options?: Parameters<Repository<TierDefinition>['createMany']>[1]): Promise<TierDefinition[]>;
|
|
52
|
+
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
53
|
+
private _dispatch;
|
|
54
|
+
/** The highest tier `lifetime` points qualify for in a program, or null. */
|
|
55
|
+
qualifiedTierName(programId: string, lifetime: number, tc: Record<string, unknown>): Promise<string | null>;
|
|
56
|
+
/**
|
|
57
|
+
* Re-place a member by lifetime points. `upgradeOnly` is the earn path: a credit
|
|
58
|
+
* may promote at once, while a downgrade waits for the periodic evaluation.
|
|
59
|
+
*/
|
|
60
|
+
evaluateMember(memberId: string, ctx: LoyaltyContext, options?: {
|
|
61
|
+
upgradeOnly?: boolean;
|
|
62
|
+
}): Promise<TierEvaluation>;
|
|
63
|
+
evaluateAll(ctx: LoyaltyContext): Promise<TierEvaluationBatch>;
|
|
64
|
+
setOverride(memberId: string, tierName: string, reason: string, ctx: LoyaltyContext, options?: {
|
|
65
|
+
expiresAt?: Date;
|
|
66
|
+
}): Promise<Member>;
|
|
67
|
+
clearOverride(memberId: string, ctx: LoyaltyContext): Promise<TierEvaluation>;
|
|
68
|
+
/**
|
|
69
|
+
* Sweeps members whose `tierOverrideExpiresAt` is past `now` and clears
|
|
70
|
+
* the override. Emits `TIER_OVERRIDE_EXPIRED` per member — distinct from
|
|
71
|
+
* the explicit `TIER_OVERRIDE_CLEARED` event — so audit logs can tell
|
|
72
|
+
* admin-initiated clears from TTL sweeps.
|
|
73
|
+
*
|
|
74
|
+
* Hosts call this on a cron schedule (e.g. hourly). Returns the number
|
|
75
|
+
* of overrides cleared. No-op when nothing matches.
|
|
76
|
+
*/
|
|
77
|
+
sweepExpiredOverrides(ctx: LoyaltyContext, now?: Date): Promise<{
|
|
78
|
+
cleared: number;
|
|
79
|
+
}>;
|
|
80
|
+
private applyUpgrade;
|
|
81
|
+
private applyDowngrade;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
28
84
|
//#region src/repositories/member.repository.d.ts
|
|
29
85
|
declare class MemberRepository extends Repository<Member> {
|
|
30
86
|
events: EventTransport;
|
|
31
87
|
outbox: OutboxStore | undefined;
|
|
32
88
|
notifications: NotificationAdapter | null;
|
|
33
89
|
config: ResolvedConfig;
|
|
90
|
+
tierRepo: TierDefinitionRepository;
|
|
34
91
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
35
92
|
private _dispatch;
|
|
36
93
|
constructor(model: Model<Member>, plugins?: PluginType[]);
|
|
@@ -73,7 +130,10 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
73
130
|
notifications: NotificationAdapter | null;
|
|
74
131
|
unitOfWork: UnitOfWork;
|
|
75
132
|
config: ResolvedConfig;
|
|
133
|
+
program: () => Promise<EffectiveProgram>;
|
|
76
134
|
constructor(model: Model<PointTransaction>, plugins?: PluginType[]);
|
|
135
|
+
/** `now + expiry.defaultDays` when the program expires points, else none. */
|
|
136
|
+
private defaultExpiry;
|
|
77
137
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
78
138
|
private _dispatch;
|
|
79
139
|
private get _dispatchDeps();
|
|
@@ -86,6 +146,8 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
86
146
|
earnPoints(input: {
|
|
87
147
|
memberId: string;
|
|
88
148
|
points: number;
|
|
149
|
+
/** Why the points are granted — required, closed set (`EARN_SOURCES`). */
|
|
150
|
+
source: EarnSource;
|
|
89
151
|
description: string;
|
|
90
152
|
referenceType?: string;
|
|
91
153
|
referenceId?: string;
|
|
@@ -138,6 +200,29 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
138
200
|
idempotencyKey?: string;
|
|
139
201
|
metadata?: Record<string, unknown>;
|
|
140
202
|
}, ctx: LoyaltyContext): Promise<PointTransaction>;
|
|
203
|
+
/**
|
|
204
|
+
* Take back points earned on a reference (an order) once it is refunded or canceled.
|
|
205
|
+
*
|
|
206
|
+
* CONVERGES BY STATE. The caller says how much of the purchase is undone
|
|
207
|
+
* (`fraction`, 1 = all); this reverses `floor(earned × fraction)` minus what earlier
|
|
208
|
+
* reversals already claimed. A replayed event reverses nothing extra, and the
|
|
209
|
+
* idempotency key is derived from the target so concurrent callers collide on the
|
|
210
|
+
* unique index instead of both debiting.
|
|
211
|
+
*
|
|
212
|
+
* A member cannot go below zero: points already spent are reported as `shortfall`
|
|
213
|
+
* on the row and the event. `lifetime` drops by the full claim — tier status must
|
|
214
|
+
* not keep credit for a sale that was undone.
|
|
215
|
+
*/
|
|
216
|
+
reverseEarned(input: {
|
|
217
|
+
referenceType: string;
|
|
218
|
+
referenceId: string;
|
|
219
|
+
fraction: number;
|
|
220
|
+
reason: string;
|
|
221
|
+
}, ctx: LoyaltyContext): Promise<EarnReversalResult[]>;
|
|
222
|
+
/** Per member: points earned on the reference, and points earlier reversals already claimed. */
|
|
223
|
+
private tallyReference;
|
|
224
|
+
/** Debit one member's `delta` in its own unit of work; a lost race on the target key has converged. */
|
|
225
|
+
private claimBack;
|
|
141
226
|
processExpirations(ctx: LoyaltyContext): Promise<ExpirationResult>;
|
|
142
227
|
}
|
|
143
228
|
//#endregion
|
|
@@ -145,6 +230,7 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
145
230
|
declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
146
231
|
memberRepo: MemberRepository;
|
|
147
232
|
txRepo: PointTransactionRepository;
|
|
233
|
+
tierRepo: TierDefinitionRepository;
|
|
148
234
|
config: ResolvedConfig;
|
|
149
235
|
constructor(model: Model<EarningRule>, plugins?: PluginType[]);
|
|
150
236
|
private applyCreateDefaults;
|
|
@@ -173,6 +259,12 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
173
259
|
quantity: number;
|
|
174
260
|
}>;
|
|
175
261
|
}, ctx: LoyaltyContext): Promise<EarningResult>;
|
|
262
|
+
/**
|
|
263
|
+
* The member's tier benefit `benefits.pointsMultiplier`: above 1, the order earns
|
|
264
|
+
* (m − 1) × what the rules awarded, as its own ledger row. Keyed on the order
|
|
265
|
+
* alone, so a retry after a tier change cannot award a second bonus.
|
|
266
|
+
*/
|
|
267
|
+
private tierMultiplierBonus;
|
|
176
268
|
evaluateAction(input: {
|
|
177
269
|
memberId: string;
|
|
178
270
|
action: string;
|
|
@@ -265,6 +357,60 @@ interface Redemption {
|
|
|
265
357
|
updatedAt: Date;
|
|
266
358
|
}
|
|
267
359
|
//#endregion
|
|
360
|
+
//#region src/domain/ports/redemption.port.d.ts
|
|
361
|
+
/**
|
|
362
|
+
* The points-redemption contract a checkout pipeline drives: reserve before the
|
|
363
|
+
* order is written, then confirm or release.
|
|
364
|
+
*
|
|
365
|
+
* Declared by the kernel that owns redemption so every channel (till, web)
|
|
366
|
+
* speaks one contract; pipelines `import type` it, adding nothing at runtime.
|
|
367
|
+
*
|
|
368
|
+
* The host binds it per request, so there is no context parameter. `reserve`
|
|
369
|
+
* DEBITS the balance: the caller must `release` on every failure path before the
|
|
370
|
+
* order exists, or the member loses points to an order that never did.
|
|
371
|
+
*/
|
|
372
|
+
/** A reserved redemption, as a pipeline needs it. */
|
|
373
|
+
interface LoyaltyRedemption {
|
|
374
|
+
/** Redemption id — needed to confirm or release. */
|
|
375
|
+
id: string;
|
|
376
|
+
/** Discount granted, integer MINOR units (same unit as order totals). */
|
|
377
|
+
discountAmount: number;
|
|
378
|
+
/** Points actually held — the kernel may cap the request at the redeemable maximum. */
|
|
379
|
+
pointsReserved?: number;
|
|
380
|
+
}
|
|
381
|
+
interface LoyaltyReserveInput {
|
|
382
|
+
memberId: string;
|
|
383
|
+
pointsToRedeem: number;
|
|
384
|
+
/** Amount the redemption is computed against, integer minor units. */
|
|
385
|
+
orderTotal: number;
|
|
386
|
+
ownerType: 'order';
|
|
387
|
+
/** Stable owner id, so a retry reuses the hold instead of spending twice. */
|
|
388
|
+
ownerId: string;
|
|
389
|
+
}
|
|
390
|
+
/** Who a redemption is held against. */
|
|
391
|
+
interface RedemptionOwner {
|
|
392
|
+
ownerType: string;
|
|
393
|
+
ownerId: string;
|
|
394
|
+
}
|
|
395
|
+
/** The confirm-time handover: the hold moves from the owner it was reserved under to the one it paid for. */
|
|
396
|
+
interface RedemptionOwnerRebind {
|
|
397
|
+
/** The CURRENT owner — the confirm CAS refuses any other. */
|
|
398
|
+
from: RedemptionOwner;
|
|
399
|
+
to: RedemptionOwner;
|
|
400
|
+
}
|
|
401
|
+
interface LoyaltyRedemptionPort {
|
|
402
|
+
/** The member id behind a customer, or null when they are not enrolled. */
|
|
403
|
+
findMemberId(customerId: string): Promise<string | null>;
|
|
404
|
+
reserve(input: LoyaltyReserveInput): Promise<LoyaltyRedemption>;
|
|
405
|
+
/**
|
|
406
|
+
* Finalize points already debited at reserve time. `owner`: hand the hold to the
|
|
407
|
+
* placed order (from the owner it was reserved under); absent ⇒ the reserve-time owner stays.
|
|
408
|
+
*/
|
|
409
|
+
confirm(redemptionId: string, owner?: RedemptionOwnerRebind): Promise<void>;
|
|
410
|
+
/** Return the points. MUST be best-effort — never masks the original error. */
|
|
411
|
+
release(redemptionId: string): Promise<void>;
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
268
414
|
//#region src/repositories/redemption.repository.d.ts
|
|
269
415
|
declare class RedemptionRepository extends Repository<Redemption> {
|
|
270
416
|
memberRepo: MemberRepository;
|
|
@@ -274,6 +420,7 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
274
420
|
notifications: NotificationAdapter | null;
|
|
275
421
|
unitOfWork: UnitOfWork;
|
|
276
422
|
config: ResolvedConfig;
|
|
423
|
+
program: () => Promise<EffectiveProgram>;
|
|
277
424
|
constructor(model: Model<Redemption>, plugins?: PluginType[]);
|
|
278
425
|
/** Auto-inject tenant id from options into doc payload. See MemberRepository. */
|
|
279
426
|
create(data: Parameters<Repository<Redemption>['create']>[0], options?: Parameters<Repository<Redemption>['create']>[1]): Promise<Redemption>;
|
|
@@ -281,6 +428,12 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
281
428
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
282
429
|
private _dispatch;
|
|
283
430
|
private get _dispatchDeps();
|
|
431
|
+
/**
|
|
432
|
+
* The points the program owes right now: every member's spendable balance plus the points
|
|
433
|
+
* held by open reservations (a reservation has left the balance but not yet been used), at
|
|
434
|
+
* the program's current point value. The measure a ledger's liability reconciles against.
|
|
435
|
+
*/
|
|
436
|
+
outstandingLiability(ctx: LoyaltyContext): Promise<LoyaltyLiabilitySnapshot>;
|
|
284
437
|
/**
|
|
285
438
|
* Atomic compare-and-set status transition.
|
|
286
439
|
*
|
|
@@ -300,7 +453,9 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
300
453
|
*
|
|
301
454
|
* Anything that throws in here is a real error and must propagate.
|
|
302
455
|
*/
|
|
303
|
-
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown
|
|
456
|
+
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>,
|
|
457
|
+
/** Extra predicates the CAS must also match (e.g. the current owner of a rebind). */
|
|
458
|
+
guard?: Record<string, unknown>): Promise<Redemption | null>;
|
|
304
459
|
/**
|
|
305
460
|
* Validates a redemption request. `orderTotal` is an **integer in the
|
|
306
461
|
* host's minor currency unit** (paisa for BDT); the returned
|
|
@@ -325,7 +480,20 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
325
480
|
ownerId: string;
|
|
326
481
|
expiresAt?: Date;
|
|
327
482
|
}, ctx: LoyaltyContext): Promise<Redemption>;
|
|
328
|
-
|
|
483
|
+
/**
|
|
484
|
+
* `ownerExists: true` skips the reservation-expiry refusal — for a caller that
|
|
485
|
+
* knows the order was committed, where refusing would return points the customer
|
|
486
|
+
* already spent as a discount.
|
|
487
|
+
*
|
|
488
|
+
* `rebind` hands the hold to the document it paid for (a checkout reserves under its
|
|
489
|
+
* request key, the placed order owns it), inside the same CAS as the confirm; the CAS
|
|
490
|
+
* also matches `rebind.from`, so a caller holding only an id cannot re-home another
|
|
491
|
+
* owner's points. Absent ⇒ the reserve-time owner stays.
|
|
492
|
+
*/
|
|
493
|
+
confirm(redemptionId: string, ctx: LoyaltyContext, options?: {
|
|
494
|
+
ownerExists?: boolean;
|
|
495
|
+
rebind?: RedemptionOwnerRebind;
|
|
496
|
+
}): Promise<Redemption>;
|
|
329
497
|
release(redemptionId: string, ctx: LoyaltyContext): Promise<Redemption>;
|
|
330
498
|
/**
|
|
331
499
|
* Reverses a confirmed redemption. Credits points back to the member as
|
|
@@ -346,6 +514,36 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
346
514
|
proportional?: boolean;
|
|
347
515
|
reason?: string;
|
|
348
516
|
}): Promise<Redemption>;
|
|
517
|
+
/**
|
|
518
|
+
* Confirm every redemption still `reserved` against an owner that now exists.
|
|
519
|
+
*
|
|
520
|
+
* The convergence net for a checkout whose inline confirm failed after the order
|
|
521
|
+
* was committed: left `reserved`, the expiry sweep would RELEASE it and the
|
|
522
|
+
* member would keep both the discount and the points. Safe to repeat — a status
|
|
523
|
+
* CAS, and a redemption another caller already settled is skipped.
|
|
524
|
+
*/
|
|
525
|
+
confirmForOwner(ownerIds: readonly string[], ctx: LoyaltyContext,
|
|
526
|
+
/** Hand each confirmed hold to this owner (the order), guarded on the owner it matched under. */
|
|
527
|
+
options?: {
|
|
528
|
+
rebindTo?: RedemptionOwner;
|
|
529
|
+
}): Promise<{
|
|
530
|
+
confirmed: number;
|
|
531
|
+
}>;
|
|
532
|
+
/**
|
|
533
|
+
* Undo every redemption held against an owner that was refunded or canceled:
|
|
534
|
+
* confirmed ones credit their points back, reserved ones release their hold.
|
|
535
|
+
*
|
|
536
|
+
* `ownerIds` lists every identity the owner was reserved under (a till reserves
|
|
537
|
+
* against its idempotency key, not the order number). Safe to repeat: each step is
|
|
538
|
+
* a status CAS, and a redemption another caller already settled is skipped.
|
|
539
|
+
*/
|
|
540
|
+
undoForOwner(ownerIds: readonly string[], ctx: LoyaltyContext, options: {
|
|
541
|
+
reason: string;
|
|
542
|
+
}): Promise<{
|
|
543
|
+
reversed: number;
|
|
544
|
+
released: number;
|
|
545
|
+
pointsReturned: number;
|
|
546
|
+
}>;
|
|
349
547
|
cleanupExpired(ctx: LoyaltyContext): Promise<number>;
|
|
350
548
|
}
|
|
351
549
|
//#endregion
|
|
@@ -412,39 +610,82 @@ declare class ReferralRepository extends Repository<Referral> {
|
|
|
412
610
|
private requireReferral;
|
|
413
611
|
}
|
|
414
612
|
//#endregion
|
|
415
|
-
//#region src/repositories/
|
|
416
|
-
|
|
613
|
+
//#region src/repositories/stamp-award.repository.d.ts
|
|
614
|
+
/** Awards are written only by `stamps.settle`; this repository is their read surface. */
|
|
615
|
+
declare class StampAwardRepository extends Repository<StampAward> {
|
|
616
|
+
config: ResolvedConfig;
|
|
617
|
+
constructor(model: Model<StampAward>, plugins?: PluginType[]);
|
|
618
|
+
create(data: Parameters<Repository<StampAward>['create']>[0], options?: Parameters<Repository<StampAward>['create']>[1]): Promise<StampAward>;
|
|
619
|
+
}
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/repositories/stamp-program.repository.d.ts
|
|
622
|
+
/**
|
|
623
|
+
* Stamp-card programs. Every write is judged whole by `assertStampProgram` — a create, and a patch
|
|
624
|
+
* merged over the stored program — so no path (HTTP or direct) stores a program that cannot award.
|
|
625
|
+
*/
|
|
626
|
+
declare class StampProgramRepository extends Repository<StampProgram> {
|
|
627
|
+
config: ResolvedConfig;
|
|
628
|
+
rewardIssuer: StampRewardIssuer | undefined;
|
|
629
|
+
constructor(model: Model<StampProgram>, plugins?: PluginType[]);
|
|
630
|
+
create(data: Parameters<Repository<StampProgram>['create']>[0], options?: Parameters<Repository<StampProgram>['create']>[1]): Promise<StampProgram>;
|
|
631
|
+
createMany(docs: Parameters<Repository<StampProgram>['createMany']>[0], options?: Parameters<Repository<StampProgram>['createMany']>[1]): Promise<StampProgram[]>;
|
|
632
|
+
/** A flat patch only: nested `earn` / `reward` are replaced whole, then the result is judged. */
|
|
633
|
+
update(id: Parameters<Repository<StampProgram>['update']>[0], data: Parameters<Repository<StampProgram>['update']>[1], options?: Parameters<Repository<StampProgram>['update']>[2]): Promise<StampProgram | null>;
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/repositories/stamp.repository.d.ts
|
|
637
|
+
/**
|
|
638
|
+
* Stamp-card ledger: one entry per (program, member, order), consumed oldest-first into awards.
|
|
639
|
+
*
|
|
640
|
+
* Every guard is an index or a CAS filter, never an `if` above a write: the order entry, the visit
|
|
641
|
+
* day, the award sequence and each entry's `consumed` count.
|
|
642
|
+
*/
|
|
643
|
+
declare class StampRepository extends Repository<StampEntry> {
|
|
417
644
|
memberRepo: MemberRepository;
|
|
645
|
+
txRepo: PointTransactionRepository;
|
|
646
|
+
programRepo: StampProgramRepository;
|
|
647
|
+
awardRepo: StampAwardRepository;
|
|
418
648
|
events: EventTransport;
|
|
419
649
|
outbox: OutboxStore | undefined;
|
|
420
|
-
|
|
650
|
+
unitOfWork: UnitOfWork;
|
|
421
651
|
config: ResolvedConfig;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
private _dispatch;
|
|
428
|
-
evaluateMember(memberId: string, ctx: LoyaltyContext): Promise<TierEvaluation>;
|
|
429
|
-
evaluateAll(ctx: LoyaltyContext): Promise<TierEvaluationBatch>;
|
|
430
|
-
setOverride(memberId: string, tierName: string, reason: string, ctx: LoyaltyContext, options?: {
|
|
431
|
-
expiresAt?: Date;
|
|
432
|
-
}): Promise<Member>;
|
|
433
|
-
clearOverride(memberId: string, ctx: LoyaltyContext): Promise<TierEvaluation>;
|
|
652
|
+
rewardIssuer: StampRewardIssuer | undefined;
|
|
653
|
+
constructor(model: Model<StampEntry>, plugins?: PluginType[]);
|
|
654
|
+
private get deps();
|
|
655
|
+
private tc;
|
|
656
|
+
create(data: Parameters<Repository<StampEntry>['create']>[0], options?: Parameters<Repository<StampEntry>['create']>[1]): Promise<StampEntry>;
|
|
434
657
|
/**
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* admin-initiated clears from TTL sweeps.
|
|
439
|
-
*
|
|
440
|
-
* Hosts call this on a cron schedule (e.g. hourly). Returns the number
|
|
441
|
-
* of overrides cleared. No-op when nothing matches.
|
|
658
|
+
* Record one paid order against every active stamp program of the member's loyalty program,
|
|
659
|
+
* then settle each. Re-recording the same order is a no-op that still settles, so a redelivery
|
|
660
|
+
* after a crash finishes the award the first delivery started.
|
|
442
661
|
*/
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
662
|
+
recordOrder(input: StampOrderInput, ctx: LoyaltyContext): Promise<StampRecordResult[]>;
|
|
663
|
+
/** Insert the order's entry; `false` when the unique index says it (or its visit day) exists. */
|
|
664
|
+
private insertEntry;
|
|
665
|
+
/**
|
|
666
|
+
* Exchange full cards for rewards until the member's stamps (or `maxRewardsPerMember`) run out.
|
|
667
|
+
* Voucher awards claimed earlier but not yet issued are resumed first.
|
|
668
|
+
*/
|
|
669
|
+
settle(stampProgramId: string, memberId: string, ctx: LoyaltyContext): Promise<StampAward[]>;
|
|
670
|
+
/**
|
|
671
|
+
* ONE award in ONE unit of work: consume the oldest stamps (CAS per entry), insert the award at
|
|
672
|
+
* the next sequence (unique — the race fence), and for points credit them in the same
|
|
673
|
+
* transaction. A voucher award commits as `pending`; its issuance happens after commit.
|
|
674
|
+
*/
|
|
675
|
+
private claimAward;
|
|
676
|
+
/** Issue a claimed voucher award through the host port (idempotent on `awardKey`), then mark it. */
|
|
677
|
+
private issueVoucher;
|
|
678
|
+
private dispatchAwardIssued;
|
|
679
|
+
/**
|
|
680
|
+
* Reverse an undone order's stamps. Policy: unconsumed stamps are removed; stamps already
|
|
681
|
+
* consumed into an award are kept (`retainedInAwards`, on the result and the event) — never clawed back.
|
|
682
|
+
*/
|
|
683
|
+
reverseOrder(orderRef: string, ctx: LoyaltyContext): Promise<StampReversal[]>;
|
|
684
|
+
private reverseEntry;
|
|
685
|
+
/** The member's card on every active program of their loyalty program. */
|
|
686
|
+
progress(memberId: string, ctx: LoyaltyContext): Promise<StampProgress[]>;
|
|
687
|
+
/** Unreversed, unexpired entries with stamps left, oldest first. */
|
|
688
|
+
private openEntries;
|
|
448
689
|
}
|
|
449
690
|
//#endregion
|
|
450
691
|
//#region src/repositories/index.d.ts
|
|
@@ -455,6 +696,10 @@ interface LoyaltyRepositories {
|
|
|
455
696
|
tierDefinition: TierDefinitionRepository;
|
|
456
697
|
redemption: RedemptionRepository;
|
|
457
698
|
referral: ReferralRepository;
|
|
699
|
+
stampProgram: StampProgramRepository;
|
|
700
|
+
/** Stamp-card verbs: `recordOrder`, `settle`, `reverseOrder`, `progress`. */
|
|
701
|
+
stamps: StampRepository;
|
|
702
|
+
stampAward: StampAwardRepository;
|
|
458
703
|
}
|
|
459
704
|
interface CreateRepositoriesInput {
|
|
460
705
|
models: LoyaltyModels;
|
|
@@ -465,7 +710,11 @@ interface CreateRepositoriesInput {
|
|
|
465
710
|
outbox?: OutboxStore | undefined;
|
|
466
711
|
notifications: NotificationAdapter | null;
|
|
467
712
|
unitOfWork: UnitOfWork;
|
|
713
|
+
/** Per-operation program settings — see `createProgramSettings`. */
|
|
714
|
+
programSettings: () => Promise<EffectiveProgram>;
|
|
715
|
+
/** Host port for stamp-card voucher rewards. */
|
|
716
|
+
rewardIssuer?: StampRewardIssuer | undefined;
|
|
468
717
|
}
|
|
469
718
|
declare function createRepositories(input: CreateRepositoriesInput): LoyaltyRepositories;
|
|
470
719
|
//#endregion
|
|
471
|
-
export {
|
|
720
|
+
export { PointTransactionRepository as _, StampProgramRepository as a, UnitOfWork as b, Referral as c, LoyaltyRedemptionPort as d, LoyaltyReserveInput as f, EarningRuleRepository as g, Redemption as h, StampRepository as i, RedemptionRepository as l, RedemptionOwnerRebind as m, LoyaltyRepositories as n, StampAwardRepository as o, RedemptionOwner as p, createRepositories as r, ReferralRepository as s, CreateRepositoriesInput as t, LoyaltyRedemption as u, MemberRepository as v, TierDefinitionRepository as y };
|