@classytic/loyalty 0.6.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 +180 -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-CR2k7jQ8.d.mts → index-BvyzKU7R.d.mts} +294 -34
- package/dist/{index-D6GvuvmF.d.mts → index-CorB0JL6.d.mts} +259 -113
- 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 +63 -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-B676z4bE.mjs → repositories-ER68JmUo.mjs} +1178 -102
- 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-D6GvuvmF.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";
|
|
@@ -12,6 +11,74 @@ import { ApprovalChain } from "@classytic/primitives/approval";
|
|
|
12
11
|
//#region src/domain/ports/unit-of-work.port.d.ts
|
|
13
12
|
interface UnitOfWork {
|
|
14
13
|
withTransaction<T>(callback: (session: ClientSession) => Promise<T>): Promise<T>;
|
|
14
|
+
/**
|
|
15
|
+
* This unit of work MAY hand the callback a session with no live transaction.
|
|
16
|
+
*
|
|
17
|
+
* True only where the host opted into `allowNonTransactional` — a standalone
|
|
18
|
+
* mongod accepts the degradation knowingly, and loyalty logs it at boot. The
|
|
19
|
+
* guard that otherwise refuses a transaction-less session reads this, so an
|
|
20
|
+
* accepted degradation is not re-refused one layer down. Absent means NO:
|
|
21
|
+
* an implementation that yields a bare session without saying so is a defect,
|
|
22
|
+
* and stays one.
|
|
23
|
+
*/
|
|
24
|
+
readonly allowsNonTransactional?: boolean;
|
|
25
|
+
}
|
|
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;
|
|
15
82
|
}
|
|
16
83
|
//#endregion
|
|
17
84
|
//#region src/repositories/member.repository.d.ts
|
|
@@ -20,6 +87,7 @@ declare class MemberRepository extends Repository<Member> {
|
|
|
20
87
|
outbox: OutboxStore | undefined;
|
|
21
88
|
notifications: NotificationAdapter | null;
|
|
22
89
|
config: ResolvedConfig;
|
|
90
|
+
tierRepo: TierDefinitionRepository;
|
|
23
91
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
24
92
|
private _dispatch;
|
|
25
93
|
constructor(model: Model<Member>, plugins?: PluginType[]);
|
|
@@ -62,7 +130,10 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
62
130
|
notifications: NotificationAdapter | null;
|
|
63
131
|
unitOfWork: UnitOfWork;
|
|
64
132
|
config: ResolvedConfig;
|
|
133
|
+
program: () => Promise<EffectiveProgram>;
|
|
65
134
|
constructor(model: Model<PointTransaction>, plugins?: PluginType[]);
|
|
135
|
+
/** `now + expiry.defaultDays` when the program expires points, else none. */
|
|
136
|
+
private defaultExpiry;
|
|
66
137
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
67
138
|
private _dispatch;
|
|
68
139
|
private get _dispatchDeps();
|
|
@@ -75,6 +146,8 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
75
146
|
earnPoints(input: {
|
|
76
147
|
memberId: string;
|
|
77
148
|
points: number;
|
|
149
|
+
/** Why the points are granted — required, closed set (`EARN_SOURCES`). */
|
|
150
|
+
source: EarnSource;
|
|
78
151
|
description: string;
|
|
79
152
|
referenceType?: string;
|
|
80
153
|
referenceId?: string;
|
|
@@ -127,6 +200,29 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
127
200
|
idempotencyKey?: string;
|
|
128
201
|
metadata?: Record<string, unknown>;
|
|
129
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;
|
|
130
226
|
processExpirations(ctx: LoyaltyContext): Promise<ExpirationResult>;
|
|
131
227
|
}
|
|
132
228
|
//#endregion
|
|
@@ -134,6 +230,7 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
134
230
|
declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
135
231
|
memberRepo: MemberRepository;
|
|
136
232
|
txRepo: PointTransactionRepository;
|
|
233
|
+
tierRepo: TierDefinitionRepository;
|
|
137
234
|
config: ResolvedConfig;
|
|
138
235
|
constructor(model: Model<EarningRule>, plugins?: PluginType[]);
|
|
139
236
|
private applyCreateDefaults;
|
|
@@ -162,6 +259,12 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
162
259
|
quantity: number;
|
|
163
260
|
}>;
|
|
164
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;
|
|
165
268
|
evaluateAction(input: {
|
|
166
269
|
memberId: string;
|
|
167
270
|
action: string;
|
|
@@ -254,6 +357,60 @@ interface Redemption {
|
|
|
254
357
|
updatedAt: Date;
|
|
255
358
|
}
|
|
256
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
|
|
257
414
|
//#region src/repositories/redemption.repository.d.ts
|
|
258
415
|
declare class RedemptionRepository extends Repository<Redemption> {
|
|
259
416
|
memberRepo: MemberRepository;
|
|
@@ -263,6 +420,7 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
263
420
|
notifications: NotificationAdapter | null;
|
|
264
421
|
unitOfWork: UnitOfWork;
|
|
265
422
|
config: ResolvedConfig;
|
|
423
|
+
program: () => Promise<EffectiveProgram>;
|
|
266
424
|
constructor(model: Model<Redemption>, plugins?: PluginType[]);
|
|
267
425
|
/** Auto-inject tenant id from options into doc payload. See MemberRepository. */
|
|
268
426
|
create(data: Parameters<Repository<Redemption>['create']>[0], options?: Parameters<Repository<Redemption>['create']>[1]): Promise<Redemption>;
|
|
@@ -270,6 +428,12 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
270
428
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
271
429
|
private _dispatch;
|
|
272
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>;
|
|
273
437
|
/**
|
|
274
438
|
* Atomic compare-and-set status transition.
|
|
275
439
|
*
|
|
@@ -289,7 +453,9 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
289
453
|
*
|
|
290
454
|
* Anything that throws in here is a real error and must propagate.
|
|
291
455
|
*/
|
|
292
|
-
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>;
|
|
293
459
|
/**
|
|
294
460
|
* Validates a redemption request. `orderTotal` is an **integer in the
|
|
295
461
|
* host's minor currency unit** (paisa for BDT); the returned
|
|
@@ -314,7 +480,20 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
314
480
|
ownerId: string;
|
|
315
481
|
expiresAt?: Date;
|
|
316
482
|
}, ctx: LoyaltyContext): Promise<Redemption>;
|
|
317
|
-
|
|
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>;
|
|
318
497
|
release(redemptionId: string, ctx: LoyaltyContext): Promise<Redemption>;
|
|
319
498
|
/**
|
|
320
499
|
* Reverses a confirmed redemption. Credits points back to the member as
|
|
@@ -335,6 +514,36 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
335
514
|
proportional?: boolean;
|
|
336
515
|
reason?: string;
|
|
337
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
|
+
}>;
|
|
338
547
|
cleanupExpired(ctx: LoyaltyContext): Promise<number>;
|
|
339
548
|
}
|
|
340
549
|
//#endregion
|
|
@@ -401,39 +610,82 @@ declare class ReferralRepository extends Repository<Referral> {
|
|
|
401
610
|
private requireReferral;
|
|
402
611
|
}
|
|
403
612
|
//#endregion
|
|
404
|
-
//#region src/repositories/
|
|
405
|
-
|
|
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> {
|
|
406
644
|
memberRepo: MemberRepository;
|
|
645
|
+
txRepo: PointTransactionRepository;
|
|
646
|
+
programRepo: StampProgramRepository;
|
|
647
|
+
awardRepo: StampAwardRepository;
|
|
407
648
|
events: EventTransport;
|
|
408
649
|
outbox: OutboxStore | undefined;
|
|
409
|
-
|
|
650
|
+
unitOfWork: UnitOfWork;
|
|
410
651
|
config: ResolvedConfig;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
private _dispatch;
|
|
417
|
-
evaluateMember(memberId: string, ctx: LoyaltyContext): Promise<TierEvaluation>;
|
|
418
|
-
evaluateAll(ctx: LoyaltyContext): Promise<TierEvaluationBatch>;
|
|
419
|
-
setOverride(memberId: string, tierName: string, reason: string, ctx: LoyaltyContext, options?: {
|
|
420
|
-
expiresAt?: Date;
|
|
421
|
-
}): Promise<Member>;
|
|
422
|
-
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>;
|
|
423
657
|
/**
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* admin-initiated clears from TTL sweeps.
|
|
428
|
-
*
|
|
429
|
-
* Hosts call this on a cron schedule (e.g. hourly). Returns the number
|
|
430
|
-
* 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.
|
|
431
661
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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;
|
|
437
689
|
}
|
|
438
690
|
//#endregion
|
|
439
691
|
//#region src/repositories/index.d.ts
|
|
@@ -444,6 +696,10 @@ interface LoyaltyRepositories {
|
|
|
444
696
|
tierDefinition: TierDefinitionRepository;
|
|
445
697
|
redemption: RedemptionRepository;
|
|
446
698
|
referral: ReferralRepository;
|
|
699
|
+
stampProgram: StampProgramRepository;
|
|
700
|
+
/** Stamp-card verbs: `recordOrder`, `settle`, `reverseOrder`, `progress`. */
|
|
701
|
+
stamps: StampRepository;
|
|
702
|
+
stampAward: StampAwardRepository;
|
|
447
703
|
}
|
|
448
704
|
interface CreateRepositoriesInput {
|
|
449
705
|
models: LoyaltyModels;
|
|
@@ -454,7 +710,11 @@ interface CreateRepositoriesInput {
|
|
|
454
710
|
outbox?: OutboxStore | undefined;
|
|
455
711
|
notifications: NotificationAdapter | null;
|
|
456
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;
|
|
457
717
|
}
|
|
458
718
|
declare function createRepositories(input: CreateRepositoriesInput): LoyaltyRepositories;
|
|
459
719
|
//#endregion
|
|
460
|
-
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 };
|