package processor import ( "testing" "github.com/stretchr/testify/assert" "apskel-pos-be/internal/entities" "apskel-pos-be/internal/models" ) // 1 EnakPoint per Rp 100 and 1 EnakCoin per Rp 25.000, as in the F1 example. func prdEarningSettings() models.OutletLoyaltySettings { return models.OutletLoyaltySettings{ Point: models.LoyaltyEarnSettings{Enabled: true, EarnPerAmount: 100, EarnValue: 1}, Coin: models.LoyaltyEarnSettings{Enabled: true, EarnPerAmount: 25000, EarnValue: 1}, } } func TestCalculateEarning_PRDExample(t *testing.T) { // Subtotal after discount Rp 87.500, paid in full in cash. order := &entities.Order{Subtotal: 97500, DiscountAmount: 10000, TaxAmount: 9625, TotalAmount: 97125} got := CalculateEarning(order, 0, prdEarningSettings()) assert.Equal(t, 87500.0, got.Basis) assert.Equal(t, int64(875), got.Point.Amount) assert.Equal(t, int64(3), got.Coin.Amount) // Rp 20.000 of it paid with EnakPoint earns nothing. got = CalculateEarning(order, 20000, prdEarningSettings()) assert.Equal(t, 67500.0, got.Basis) assert.Equal(t, int64(675), got.Point.Amount) assert.Equal(t, int64(2), got.Coin.Amount) } func TestCalculateEarning_TaxIsNotPartOfTheBasis(t *testing.T) { withoutTax := &entities.Order{Subtotal: 50000} withTax := &entities.Order{Subtotal: 50000, TaxAmount: 5500, TotalAmount: 55500} assert.Equal(t, CalculateEarning(withoutTax, 0, prdEarningSettings()), CalculateEarning(withTax, 0, prdEarningSettings())) assert.Equal(t, int64(500), CalculateEarning(withTax, 0, prdEarningSettings()).Point.Amount) } func TestCalculateEarning_BelowMinimum(t *testing.T) { s := prdEarningSettings() s.Point.MinOrderAmount = 50000 assert.Equal(t, int64(0), CalculateEarning(&entities.Order{Subtotal: 49999}, 0, s).Point.Amount) assert.Equal(t, int64(500), CalculateEarning(&entities.Order{Subtotal: 50000}, 0, s).Point.Amount, "the minimum itself earns") // The minimum applies to the basis, after discount and EnakPoint. assert.Equal(t, int64(0), CalculateEarning(&entities.Order{Subtotal: 60000}, 15000, s).Point.Amount) // Coin has its own minimum. assert.Equal(t, int64(1), CalculateEarning(&entities.Order{Subtotal: 49999}, 0, s).Coin.Amount) } func TestCalculateEarning_MaxPerOrder(t *testing.T) { s := prdEarningSettings() max := int64(300) s.Point.MaxPerOrder = &max got := CalculateEarning(&entities.Order{Subtotal: 87500}, 0, s) assert.Equal(t, int64(300), got.Point.Amount) assert.True(t, got.Point.Capped) assert.Equal(t, int64(300), got.Metadata(got.Point)["max_per_order"]) got = CalculateEarning(&entities.Order{Subtotal: 20000}, 0, s) assert.Equal(t, int64(200), got.Point.Amount) assert.False(t, got.Point.Capped) zero := int64(0) s.Point.MaxPerOrder = &zero assert.Equal(t, int64(0), CalculateEarning(&entities.Order{Subtotal: 87500}, 0, s).Point.Amount) } func TestCalculateEarning_DisabledEarnsNothing(t *testing.T) { s := prdEarningSettings() s.Point.Enabled = false got := CalculateEarning(&entities.Order{Subtotal: 87500}, 0, s) assert.Equal(t, int64(0), got.Point.Amount) assert.Equal(t, int64(3), got.Coin.Amount, "each currency is switched on its own") s.Coin.Enabled = false got = CalculateEarning(&entities.Order{Subtotal: 87500}, 0, s) assert.Equal(t, int64(0), got.Coin.Amount) assert.Equal(t, 87500.0, got.Basis, "the basis is still reported") // The defaults of an outlet that never set anything earn nothing. var defaults models.OutletLoyaltySettings loadLoyaltyFields(outletLoyaltyFields(&defaults), nil, "test") got = CalculateEarning(&entities.Order{Subtotal: 87500}, 0, defaults) assert.Equal(t, int64(0), got.Point.Amount) assert.Equal(t, int64(0), got.Coin.Amount) } func TestCalculateEarning_EdgeCases(t *testing.T) { s := prdEarningSettings() // floor, not round. assert.Equal(t, int64(875), CalculateEarning(&entities.Order{Subtotal: 87599.99}, 0, s).Point.Amount) // Values float64 cannot hold exactly do not lose a point: computed in float64 this // basis divides to 4956.999…, which a naive floor turns into 4956. per250 := prdEarningSettings() per250.Point.EarnPerAmount = 250 assert.Equal(t, int64(4957), CalculateEarning(&entities.Order{Subtotal: 1240155.48, DiscountAmount: 749.11}, 156.37, per250).Point.Amount) // Paying more with EnakPoint than the basis leaves nothing, never a negative amount. got := CalculateEarning(&entities.Order{Subtotal: 10000}, 15000, s) assert.Equal(t, 0.0, got.Basis) assert.Equal(t, int64(0), got.Point.Amount) // earn_value multiplies. s.Point.EarnValue = 5 assert.Equal(t, int64(4375), CalculateEarning(&entities.Order{Subtotal: 87500}, 0, s).Point.Amount) // A zero earn_value earns nothing even when enabled. s.Point.EarnValue = 0 assert.Equal(t, int64(0), CalculateEarning(&entities.Order{Subtotal: 87500}, 0, s).Point.Amount) } func TestCalculateEarning_MetadataSnapshot(t *testing.T) { got := CalculateEarning(&entities.Order{Subtotal: 87500}, 0, prdEarningSettings()) assert.Equal(t, entities.Metadata{ "basis": 87500.0, "earn_per_amount": int64(100), "earn_value": int64(1), "min_order_amount": int64(0), "capped": false, }, got.Metadata(got.Point)) }