feat(loyalty): show what an order earned on the order and receipt
Adds points_earned and coins_earned to the order response (docs/prd-point-coin.md F3, PC-205). The POS prints the receipt from this response, so the receipt gets them too. The values are the sums of the order's EARN rows, read in one query for a list of orders. They are filled for create, add items, update, detail and list, and are 0 for an order that earned nothing. UpdateOrder earns before building its response, so a payment completed there already shows the earning. A failure to read them is logged and leaves them at 0 rather than failing the order read. The self-order session listing reads orders directly from the repository and still shows 0. The two order hooks are merged into one OrderLoyalty interface (paid, refunded, earned by orders) with a single SetLoyalty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
eb5b63677f
commit
fc97c78300
@@ -103,8 +103,10 @@ func (f splitFake) SplitByItem(_ context.Context, _ *models.SplitBillRequest, or
|
||||
}
|
||||
|
||||
type orderPaidHookFake struct {
|
||||
calls []uuid.UUID
|
||||
ctxs []context.Context
|
||||
calls []uuid.UUID
|
||||
ctxs []context.Context
|
||||
refunded []uuid.UUID
|
||||
earned map[uuid.UUID]OrderEarned
|
||||
}
|
||||
|
||||
func (h *orderPaidHookFake) OnOrderPaid(ctx context.Context, orderID uuid.UUID) {
|
||||
@@ -112,6 +114,14 @@ func (h *orderPaidHookFake) OnOrderPaid(ctx context.Context, orderID uuid.UUID)
|
||||
h.ctxs = append(h.ctxs, ctx)
|
||||
}
|
||||
|
||||
func (h *orderPaidHookFake) OnOrderRefunded(_ context.Context, orderID uuid.UUID) {
|
||||
h.refunded = append(h.refunded, orderID)
|
||||
}
|
||||
|
||||
func (h *orderPaidHookFake) EarnedByOrders(context.Context, []uuid.UUID) (map[uuid.UUID]OrderEarned, error) {
|
||||
return h.earned, nil
|
||||
}
|
||||
|
||||
func newHookedOrderProcessor(split SplitBillProcessor) (*OrderProcessorImpl, *hookOrderRepo, *orderPaidHookFake) {
|
||||
hook := &orderPaidHookFake{}
|
||||
orders := &hookOrderRepo{
|
||||
@@ -126,7 +136,7 @@ func newHookedOrderProcessor(split SplitBillProcessor) (*OrderProcessorImpl, *ho
|
||||
splitBillProcessor: split,
|
||||
txManager: repository.NewTxManager(nil),
|
||||
}
|
||||
p.SetOrderPaidHook(hook)
|
||||
p.SetLoyalty(hook)
|
||||
return p, orders, hook
|
||||
}
|
||||
|
||||
@@ -175,7 +185,7 @@ func TestOrderPaidHook_SplitBillOnlyOnTheSettlingPayment(t *testing.T) {
|
||||
|
||||
func TestOrderPaidHook_NoHookIsFine(t *testing.T) {
|
||||
p, orders, _ := newHookedOrderProcessor(nil)
|
||||
p.SetOrderPaidHook(nil)
|
||||
p.SetLoyalty(nil)
|
||||
_, err := p.UpdateOrder(context.Background(), orders.order.ID, &models.UpdateOrderRequest{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -204,43 +214,59 @@ func (r *hookPaymentRepo) RefundPayment(_ context.Context, id uuid.UUID, amount
|
||||
return nil
|
||||
}
|
||||
|
||||
type orderRefundedHookFake struct{ calls []uuid.UUID }
|
||||
|
||||
func (h *orderRefundedHookFake) OnOrderRefunded(_ context.Context, orderID uuid.UUID) {
|
||||
h.calls = append(h.calls, orderID)
|
||||
}
|
||||
|
||||
func TestOrderRefundedHook(t *testing.T) {
|
||||
t.Run("void", func(t *testing.T) {
|
||||
p, orders, _ := newHookedOrderProcessor(nil)
|
||||
hook := &orderRefundedHookFake{}
|
||||
p.SetOrderRefundedHook(hook)
|
||||
p, orders, hook := newHookedOrderProcessor(nil)
|
||||
require.NoError(t, p.VoidOrder(context.Background(), &models.VoidOrderRequest{OrderID: orders.order.ID, Type: "ALL", Reason: "salah input"}, uuid.New()))
|
||||
assert.Equal(t, []uuid.UUID{orders.order.ID}, hook.calls)
|
||||
assert.Equal(t, []uuid.UUID{orders.order.ID}, hook.refunded)
|
||||
})
|
||||
|
||||
t.Run("refund order", func(t *testing.T) {
|
||||
p, orders, _ := newHookedOrderProcessor(nil)
|
||||
p, orders, hook := newHookedOrderProcessor(nil)
|
||||
orders.order.PaymentStatus = entities.PaymentStatusCompleted
|
||||
hook := &orderRefundedHookFake{}
|
||||
p.SetOrderRefundedHook(hook)
|
||||
amount := 25000.0
|
||||
require.NoError(t, p.RefundOrder(context.Background(), orders.order.ID, &models.RefundOrderRequest{RefundAmount: &amount}, uuid.New()))
|
||||
assert.Equal(t, []uuid.UUID{orders.order.ID}, hook.calls)
|
||||
assert.Equal(t, []uuid.UUID{orders.order.ID}, hook.refunded)
|
||||
})
|
||||
|
||||
t.Run("refund payment", func(t *testing.T) {
|
||||
p, orders, _ := newHookedOrderProcessor(nil)
|
||||
p, orders, hook := newHookedOrderProcessor(nil)
|
||||
payments := p.paymentRepo.(*hookPaymentRepo)
|
||||
payment := &entities.Payment{ID: uuid.New(), OrderID: orders.order.ID, Amount: 50000, Status: entities.PaymentTransactionStatusCompleted}
|
||||
payments.created = append(payments.created, payment)
|
||||
hook := &orderRefundedHookFake{}
|
||||
p.SetOrderRefundedHook(hook)
|
||||
require.NoError(t, p.RefundPayment(context.Background(), payment.ID, 20000, "kembali", uuid.New()))
|
||||
assert.Equal(t, []uuid.UUID{orders.order.ID}, hook.calls)
|
||||
assert.Equal(t, []uuid.UUID{orders.order.ID}, hook.refunded)
|
||||
|
||||
// A refund that fails does not run the hook.
|
||||
assert.Error(t, p.RefundPayment(context.Background(), payment.ID, 60000, "terlalu besar", uuid.New()))
|
||||
assert.Len(t, hook.calls, 1)
|
||||
assert.Len(t, hook.refunded, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *hookOrderRepo) List(context.Context, map[string]interface{}, int, int) ([]*entities.Order, int64, error) {
|
||||
o := *r.order
|
||||
other := entities.Order{ID: uuid.New()}
|
||||
return []*entities.Order{&o, &other}, 2, nil
|
||||
}
|
||||
|
||||
func TestOrderResponsesCarryEarnings(t *testing.T) {
|
||||
p, orders, hook := newHookedOrderProcessor(nil)
|
||||
hook.earned = map[uuid.UUID]OrderEarned{orders.order.ID: {Points: 875, Coins: 3}}
|
||||
|
||||
got, err := p.GetOrderByID(context.Background(), orders.order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(875), got.PointsEarned)
|
||||
assert.Equal(t, int64(3), got.CoinsEarned)
|
||||
|
||||
list, err := p.ListOrders(context.Background(), &models.ListOrdersRequest{Page: 1, Limit: 10})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Orders, 2)
|
||||
assert.Equal(t, int64(875), list.Orders[0].PointsEarned)
|
||||
assert.Equal(t, int64(0), list.Orders[1].PointsEarned, "an order that earned nothing shows 0")
|
||||
|
||||
// Without the loyalty program the fields stay at 0.
|
||||
p.SetLoyalty(nil)
|
||||
got, err = p.GetOrderByID(context.Background(), orders.order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), got.PointsEarned)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user