2026-09-30 10:23:08 +07:00
|
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-09-30 10:28:40 +07:00
|
|
|
|
"math"
|
2026-09-30 10:23:08 +07:00
|
|
|
|
"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"`
|
|
|
|
|
|
}
|
2026-09-30 10:28:40 +07:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|