Files
nick-doc/02 - Data Models/PointTransaction.md
2026-05-23 20:35:34 +03:30

3.9 KiB

title, tags, aliases
title tags aliases
PointTransaction
data-model
mongoose
Point Ledger
Loyalty Transaction
IPointTransaction

PointTransaction

Append-only ledger of loyalty point movements. Each row represents one earn / spend / expire event for a user, with a source attribution (purchase / referral / bonus / admin / redemption), the amount moved, and the resulting balance snapshot. Metadata is flexible to support different sources (order amount, commission, level changes, referenced PurchaseRequest).

[!note] Source backend/src/models/PointTransaction.ts:25 — schema definition backend/src/models/PointTransaction.ts:84 — model export

Schema

Field Type Required Default Validation Index Description
user ObjectId → User yes yes (single + compound) Owner of the transaction.
type String yes enum: earn / spend / expire yes (compound) Movement direction.
source String yes enum: purchase / referral / bonus / admin / redemption yes (compound) Source bucket.
amount Number yes Points moved (positive integer; semantics by type).
balance Number yes Available balance after the move.
order ObjectId → Order no Linked order id (legacy ref, see warning).
referredUser ObjectId → User no Referred user (for referral earns).
description String yes Human label.
metadata.orderAmount Number no Order amount snapshot.
metadata.commission Number no Commission snapshot.
metadata.levelBefore Number no Pre-level snapshot.
metadata.levelAfter Number no Post-level snapshot.
metadata.purchaseRequestId String no Linked PurchaseRequest id.
expiresAt Date no yes (sparse) When the points expire (for earn).
createdAt Date auto yes (compound, desc) Mongoose timestamp.
updatedAt Date auto Mongoose timestamp.

[!warning] order reference The schema declares ref: 'Order', but there is no Order model in backend/src/models/. In practice this slot is used for the PurchaseRequest id; consumers should not rely on Mongoose populate('order') working.

Virtuals

None defined.

Indexes

Defined at backend/src/models/PointTransaction.ts:80-82. Plus the implicit index from user being declared with index: true:

  • { user: 1, createdAt: -1 } — user ledger view.
  • { type: 1, source: 1 } — analytics.
  • { expiresAt: 1 } (sparse) — expiry sweeps.

Pre/Post Hooks

None declared.

Instance Methods

None defined.

Static Methods

None defined.

Relationships

  • References: User (user, referredUser).
  • Referenced by: none. Loosely related to PurchaseRequest via metadata.purchaseRequestId (string).

State Transitions

No status field — entries are immutable once written. A consumer scans for expiresAt < now to create offsetting type: 'expire' rows.

Common Queries

// User ledger
PointTransaction.find({ user: userId }).sort({ createdAt: -1 }).limit(50);

// Latest balance (most recent row)
PointTransaction.findOne({ user: userId }).sort({ createdAt: -1 });

// Referral earnings
PointTransaction.find({ user: userId, source: 'referral', type: 'earn' });

// Points expiring soon
PointTransaction.find({ expiresAt: { $lte: oneWeekFromNow }, type: 'earn' });

// Analytics: total earned vs spent per source
PointTransaction.aggregate([
  { $group: { _id: { type: '$type', source: '$source' }, total: { $sum: '$amount' } } }
]);

Related: User, LevelConfig, PurchaseRequest.