EnakPoint can only be redeemed for vouchers now: it can no longer pay for orders and is never cashed out (docs/enakgame-prd.md §3.2, EG-001, EG-002). No order was ever paid with EnakPoint, so there is no data to move. Removed: - POST /customer/wallet/payment-code, POST /customer/orders/:id/pay-with-points and GET /orders/:id/point-payment/preview, with their processors, repositories, services, handlers and tests. - The point payment method type: paying, splitting and refunding with it, the outlet filter on the method list, and the system-method guard. - points and payment_code on CreatePayment; points_used and point_value on payments; accepts_point_payment on the customer outlets. - The outlet point_payment settings. A PUT that still sends them is rejected as an unknown field. - The EnakPoint split in the payment method analytics. - PAYMENT and PAYMENT_REFUND from the wallet type rules. Tests that used them as a generic EnakPoint debit use REWARD_REDEEM. - The EnakPoint-paid part from the earning basis, which is subtotal − discount again. Migration 000102 drops the trigger, the point methods and their index, the payments columns, and the outlet settings, and restores the method type CHECK without point. payments.payment_method_id is ON DELETE RESTRICT, so it fails rather than lose a payment made with EnakPoint. The integration docs list the removed endpoints and fields, and the EnakPoint & EnakCoin PRD and tasks note what is superseded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
5.2 KiB
Go
146 lines
5.2 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"apskel-pos-be/internal/models"
|
|
"apskel-pos-be/internal/repository"
|
|
)
|
|
|
|
type customerOrderRepoFake struct {
|
|
owner map[uuid.UUID]uuid.UUID // order -> customer
|
|
orders []repository.CustomerOrderRow
|
|
items map[uuid.UUID][]repository.CustomerOrderItemRow
|
|
payments map[uuid.UUID][]repository.CustomerOrderPaymentRow
|
|
offset int
|
|
limit int
|
|
}
|
|
|
|
func (f *customerOrderRepoFake) ListOrders(_ context.Context, customerID uuid.UUID, offset, limit int) ([]repository.CustomerOrderRow, int64, error) {
|
|
f.offset, f.limit = offset, limit
|
|
var mine []repository.CustomerOrderRow
|
|
for _, o := range f.orders {
|
|
if f.owner[o.ID] == customerID {
|
|
mine = append(mine, o)
|
|
}
|
|
}
|
|
return mine, int64(len(mine)), nil
|
|
}
|
|
|
|
func (f *customerOrderRepoFake) GetOrder(_ context.Context, customerID, orderID uuid.UUID) (*repository.CustomerOrderRow, error) {
|
|
for _, o := range f.orders {
|
|
if o.ID == orderID && f.owner[o.ID] == customerID {
|
|
c := o
|
|
return &c, nil
|
|
}
|
|
}
|
|
return nil, repository.ErrCustomerOrderNotFound
|
|
}
|
|
|
|
func (f *customerOrderRepoFake) ListItems(_ context.Context, orderID uuid.UUID) ([]repository.CustomerOrderItemRow, error) {
|
|
return f.items[orderID], nil
|
|
}
|
|
|
|
func (f *customerOrderRepoFake) ListPayments(_ context.Context, orderID uuid.UUID) ([]repository.CustomerOrderPaymentRow, error) {
|
|
return f.payments[orderID], nil
|
|
}
|
|
|
|
type earnedFake map[uuid.UUID]OrderEarned
|
|
|
|
func (f earnedFake) EarnedByOrders(_ context.Context, ids []uuid.UUID) (map[uuid.UUID]OrderEarned, error) {
|
|
out := map[uuid.UUID]OrderEarned{}
|
|
for _, id := range ids {
|
|
if e, ok := f[id]; ok {
|
|
out[id] = e
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func newCustomerOrderTest() (*customerOrderRepoFake, uuid.UUID, uuid.UUID, uuid.UUID) {
|
|
customer, other := uuid.New(), uuid.New()
|
|
mine, theirs := uuid.New(), uuid.New()
|
|
repo := &customerOrderRepoFake{
|
|
owner: map[uuid.UUID]uuid.UUID{mine: customer, theirs: other},
|
|
orders: []repository.CustomerOrderRow{
|
|
{ID: mine, OrderNumber: "ORD-1", OutletName: "Gokuna 1", Status: "completed", PaymentStatus: "completed", Subtotal: 90000, TotalAmount: 99000, ItemCount: 2, CreatedAt: time.Now()},
|
|
{ID: theirs, OrderNumber: "ORD-2"},
|
|
},
|
|
}
|
|
return repo, customer, mine, theirs
|
|
}
|
|
|
|
func TestCustomerOrders_ListShowsOnlyTheCustomersOrdersWithEarning(t *testing.T) {
|
|
repo, customer, mine, _ := newCustomerOrderTest()
|
|
p := NewCustomerOrderProcessor(repo, earnedFake{mine: {Points: 900, Coins: 3}})
|
|
|
|
got, err := p.List(context.Background(), customer, models.ListCustomerOrdersQuery{Page: 2, Limit: 10})
|
|
require.NoError(t, err)
|
|
require.Len(t, got.Data, 1)
|
|
assert.Equal(t, "ORD-1", got.Data[0].OrderNumber)
|
|
assert.Equal(t, int64(900), got.Data[0].PointsEarned)
|
|
assert.Equal(t, int64(3), got.Data[0].CoinsEarned)
|
|
assert.Equal(t, 10, repo.offset, "page 2 of 10 skips the first 10")
|
|
assert.Equal(t, 10, repo.limit)
|
|
assert.Equal(t, 2, got.Pagination.Page)
|
|
}
|
|
|
|
func TestCustomerOrders_ListDefaultsAndLimits(t *testing.T) {
|
|
repo, customer, _, _ := newCustomerOrderTest()
|
|
p := NewCustomerOrderProcessor(repo, earnedFake{})
|
|
|
|
got, err := p.List(context.Background(), customer, models.ListCustomerOrdersQuery{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, got.Pagination.Page)
|
|
assert.Equal(t, 20, got.Pagination.Limit)
|
|
|
|
for _, q := range []models.ListCustomerOrdersQuery{{Page: -1}, {Limit: 101}, {Limit: -5}} {
|
|
_, err := p.List(context.Background(), customer, q)
|
|
assert.ErrorIs(t, err, ErrInvalidWalletQuery, "%+v", q)
|
|
}
|
|
}
|
|
|
|
func TestCustomerOrders_DetailHasItemsPaymentsAndEarning(t *testing.T) {
|
|
repo, customer, mine, _ := newCustomerOrderTest()
|
|
variant, unit := "Large", "ons"
|
|
weight := 4.2
|
|
repo.items = map[uuid.UUID][]repository.CustomerOrderItemRow{
|
|
mine: {
|
|
{ProductName: "Kopi Susu", VariantName: &variant, Quantity: 2, UnitPrice: 25000, TotalPrice: 50000, Status: "completed"},
|
|
{ProductName: "Ikan Tude", Quantity: 1, Weight: &weight, UnitName: &unit, UnitPrice: 4500, TotalPrice: 18900, Status: "completed"},
|
|
},
|
|
}
|
|
repo.payments = map[uuid.UUID][]repository.CustomerOrderPaymentRow{
|
|
mine: {
|
|
{MethodName: "Card", MethodType: "card", Amount: 12500, Status: "completed"},
|
|
{MethodName: "Cash", MethodType: "cash", Amount: 86500, Status: "completed"},
|
|
},
|
|
}
|
|
p := NewCustomerOrderProcessor(repo, earnedFake{mine: {Points: 865}})
|
|
|
|
got, err := p.Detail(context.Background(), customer, mine)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Gokuna 1", got.OutletName)
|
|
assert.Equal(t, float64(90000), got.Subtotal)
|
|
assert.Equal(t, int64(865), got.PointsEarned)
|
|
require.Len(t, got.Items, 2)
|
|
assert.Equal(t, "Large", *got.Items[0].VariantName)
|
|
assert.Equal(t, []map[string]interface{}{}, got.Items[0].Modifiers, "no modifiers is an empty list, not null")
|
|
assert.Equal(t, 4.2, *got.Items[1].Weight)
|
|
require.Len(t, got.Payments, 2)
|
|
assert.Equal(t, "Card", got.Payments[0].MethodName)
|
|
assert.Equal(t, float64(12500), got.Payments[0].Amount)
|
|
}
|
|
|
|
func TestCustomerOrders_AnotherCustomersOrderIsNotFound(t *testing.T) {
|
|
repo, customer, _, theirs := newCustomerOrderTest()
|
|
_, err := NewCustomerOrderProcessor(repo, earnedFake{}).Detail(context.Background(), customer, theirs)
|
|
assert.ErrorIs(t, err, repository.ErrCustomerOrderNotFound)
|
|
}
|