feat(loyalty): give new balances their expiry

Every lot now gets its expiry when it is created (docs/prd-point-coin.md
F12, PC-502), where it used to never expire until note N4 was settled:

- EARN and an ADJUSTMENT that adds: ComputeExpiry of the organization's
  settings for that currency, from the moment received.
- EXCHANGE_IN: the sooner of the EnakCoin lot's expiry and when EnakPoint
  received now expire (F4).
- PAYMENT_REFUND: the expiry of the lot the EnakPoint came from, but at
  least seven days from the refund (N4, decided). A lot that never expired
  stays so.
- TRANSFER_IN: unchanged, exactly the sender's expiry.

Turning expiry on for a currency for the first time dates every lot of the
organization that still holds something and has no expiry, MIGRATION lots
included, in the same transaction as the setting: a full period from now
when ROLLING, the second fixed date on or after today when FIXED_DATE, so
no customer loses a balance soon after the rule is announced (N4,
decided). Turning it off leaves dated lots as they are. PUT
/marketing/loyalty-settings reports these as expiry_activations (currency,
lots, amount, expires_at); a dry run counts them without dating anything.

The earning processor now also reads the organization settings, and the
wallet admin processor takes the settings reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
efrilm
2026-09-30 13:29:25 +07:00
co-authored by Claude Opus 5.5
parent 9ce55e6002
commit 4d63673a25
16 changed files with 269 additions and 63 deletions
+11
View File
@@ -125,12 +125,23 @@ type OrganizationLoyaltySettingsView struct {
Impact LoyaltySettingsImpact `json:"impact"`
// When a balance received now would expire under these settings (F12).
ExpiryPreview LoyaltyExpiryPreview `json:"expiry_preview"`
// The currencies this change turns expiry on for, and the balances affected.
ExpiryActivations []LoyaltyExpiryActivation `json:"expiry_activations"`
// On PUT, the keys that changed; on a dry run, the keys that would.
Changes []LoyaltySettingChange `json:"changes"`
// True when nothing was saved.
DryRun bool `json:"dry_run"`
}
// LoyaltyExpiryActivation is expiry being turned on for a currency: the balances that
// had no expiry and the expiry they get (F12). On a dry run nothing is dated yet.
type LoyaltyExpiryActivation struct {
Currency string `json:"currency"`
Lots int64 `json:"lots"`
Amount int64 `json:"amount"`
ExpiresAt time.Time `json:"expires_at"`
}
// LoyaltyExpiryPreview is what the dashboard shows next to the expiry settings: "the
// EnakPoint received today expire on …". Nil means they never expire.
type LoyaltyExpiryPreview struct {