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:
efrilm
2026-09-30 10:50:37 +07:00
co-authored by Claude Opus 5.5
parent eb5b63677f
commit fc97c78300
10 changed files with 194 additions and 53 deletions
+14
View File
@@ -182,3 +182,17 @@ func earningDescription(order *repository.EarningOrder) string {
}
return truncateRunes(description, walletDescriptionLimit)
}
// EarnedByOrders returns what each order earned, as written in its EARN rows, for the
// order screen and the receipt (F3). Orders that earned nothing are absent.
func (p *EarningProcessor) EarnedByOrders(ctx context.Context, orderIDs []uuid.UUID) (map[uuid.UUID]OrderEarned, error) {
rows, err := p.orders.EarnedByOrders(ctx, orderIDs)
if err != nil {
return nil, err
}
out := make(map[uuid.UUID]OrderEarned, len(rows))
for id, byCurrency := range rows {
out[id] = OrderEarned{Points: byCurrency[constants.WalletCurrencyPoint], Coins: byCurrency[constants.WalletCurrencyCoin]}
}
return out, nil
}