package models import ( "math" "time" "github.com/google/uuid" ) // OutletLoyaltySettings are an outlet's loyalty settings (docs/prd-point-coin.md F1). type OutletLoyaltySettings struct { Point LoyaltyEarnSettings `json:"point"` Coin LoyaltyEarnSettings `json:"coin"` // Paying with EnakPoint. EnakCoin cannot pay, so it has no counterpart. PointPayment LoyaltyPointPaymentSettings `json:"point_payment"` } // LoyaltyEarnSettings is how much of one currency an order earns: // floor(basis / EarnPerAmount) × EarnValue, nothing below MinOrderAmount, and at most // MaxPerOrder when set. type LoyaltyEarnSettings struct { Enabled bool `json:"enabled"` EarnPerAmount int64 `json:"earn_per_amount"` EarnValue int64 `json:"earn_value"` MinOrderAmount int64 `json:"min_order_amount"` MaxPerOrder *int64 `json:"max_per_order"` } type LoyaltyPointPaymentSettings struct { AcceptPayment bool `json:"accept_payment"` MinPaymentPoints int64 `json:"min_payment_points"` // Largest share of the order total, 0–100, that EnakPoint may pay. MaxPaymentPercent int64 `json:"max_payment_percent"` } // OrganizationLoyaltySettings are the loyalty settings shared by every outlet of an // organization (docs/prd-point-coin.md F2, F12). type OrganizationLoyaltySettings struct { // Rupiah value of one EnakPoint when paying. PointValue int64 `json:"point_value"` // CoinAmount EnakCoin exchange into PointAmount EnakPoint. Exchange LoyaltyExchangeSettings `json:"exchange"` Transfer LoyaltyTransferSettings `json:"transfer"` PointExpiry LoyaltyExpirySettings `json:"point_expiry"` CoinExpiry LoyaltyExpirySettings `json:"coin_expiry"` } type LoyaltyExchangeSettings struct { CoinAmount int64 `json:"coin_amount"` PointAmount int64 `json:"point_amount"` } type LoyaltyTransferSettings struct { Enabled bool `json:"enabled"` MinAmount int64 `json:"min_amount"` MaxPerTransaction *int64 `json:"max_per_transaction"` DailyLimit *int64 `json:"daily_limit"` } // LoyaltyExpirySettings is how long one currency lasts once received. The expiry // model is still open (note N4); these are only the stored settings. type LoyaltyExpirySettings struct { Enabled bool `json:"enabled"` Period int64 `json:"period"` // DAY or MONTH. Unit string `json:"unit"` EndOfMonth bool `json:"end_of_month"` ReminderDays int64 `json:"reminder_days"` } // LoyaltySettingChange is one row of the loyalty settings history. type LoyaltySettingChange struct { ID uuid.UUID `json:"id"` OrganizationID uuid.UUID `json:"organization_id"` OutletID *uuid.UUID `json:"outlet_id"` Key string `json:"key"` // Nil when the key had no stored value, that is it was on its default. OldValue *string `json:"old_value"` NewValue *string `json:"new_value"` ChangedBy uuid.UUID `json:"changed_by"` CreatedAt time.Time `json:"created_at"` } // OutletLoyaltySettingsView is GET and PUT /outlets/:id/loyalty-settings. type OutletLoyaltySettingsView struct { OutletID uuid.UUID `json:"outlet_id"` OutletLoyaltySettings // The organization's rupiah value of one EnakPoint, which the cashback depends on. PointValue int64 `json:"point_value"` // Effective EnakPoint cashback in percent: earn_value × point_value / // earn_per_amount × 100. Shown next to the setting so an owner cannot misread the // scale (F1). PointCashbackPercent float64 `json:"point_cashback_percent"` // Set on PUT: the keys that changed. Changes []LoyaltySettingChange `json:"changes,omitempty"` } // LoyaltyCashbackPercent is earnValue × pointValue / earnPerAmount as a percentage, // rounded to two decimals. func LoyaltyCashbackPercent(earnValue, pointValue, earnPerAmount int64) float64 { if earnPerAmount <= 0 { return 0 } return math.Round(float64(earnValue)*float64(pointValue)*10000/float64(earnPerAmount)) / 100 } // OrganizationLoyaltySettingsView is GET and PUT /marketing/loyalty-settings. type OrganizationLoyaltySettingsView struct { OrganizationLoyaltySettings // What the balances in circulation are worth, before and after the change. Impact LoyaltySettingsImpact `json:"impact"` // 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"` } // LoyaltySettingsImpact shows how a change of point value or exchange rate changes what // the balances in circulation are worth (F2). Before and after are equal when neither // changes. type LoyaltySettingsImpact struct { OutstandingPoints int64 `json:"outstanding_points"` OutstandingCoins int64 `json:"outstanding_coins"` PointValueBefore int64 `json:"point_value_before"` PointValueAfter int64 `json:"point_value_after"` PointRupiahBefore int64 `json:"point_rupiah_before"` PointRupiahAfter int64 `json:"point_rupiah_after"` // The coins in circulation exchanged at the rate, in EnakPoint and in rupiah. CoinsAsPointsBefore int64 `json:"coins_as_points_before"` CoinsAsPointsAfter int64 `json:"coins_as_points_after"` CoinRupiahBefore int64 `json:"coin_rupiah_before"` CoinRupiahAfter int64 `json:"coin_rupiah_after"` } // NewLoyaltySettingsImpact computes the impact of moving from one organization setting // to another on the balances in circulation. func NewLoyaltySettingsImpact(points, coins int64, before, after OrganizationLoyaltySettings) LoyaltySettingsImpact { asPoints := func(s OrganizationLoyaltySettings) int64 { if s.Exchange.CoinAmount <= 0 { return 0 } return coins * s.Exchange.PointAmount / s.Exchange.CoinAmount } impact := LoyaltySettingsImpact{ OutstandingPoints: points, OutstandingCoins: coins, PointValueBefore: before.PointValue, PointValueAfter: after.PointValue, PointRupiahBefore: points * before.PointValue, PointRupiahAfter: points * after.PointValue, CoinsAsPointsBefore: asPoints(before), CoinsAsPointsAfter: asPoints(after), } impact.CoinRupiahBefore = impact.CoinsAsPointsBefore * before.PointValue impact.CoinRupiahAfter = impact.CoinsAsPointsAfter * after.PointValue return impact }