feat(loyalty): trace a wallet row lot by lot in the dashboard
Adds GET /marketing/wallet-transactions/:id/trace (docs/prd-point-coin.md F7, §8.1, PC-404). From any ledger row of the organization, the trace lists the lots a debit took from, with how much it took from each, or the lots a credit created. Each lot is followed back through origin_lot_id, across transfers, exchanges and refunds, to the lot an EARN, ADJUSTMENT or MIGRATION first created. Every step shows the lot and the row that created it, with the real name of the customer it belongs to, so the example of §8 (A sends 120 to B, B pays 30) leads from B's payment to A's order #ORD-1. Lots are loaded a generation at a time, and a chain stops at 100 steps or at a lot it has already seen, which only bad data could cause. A row of another organization answers 404. The dashboard's wallet view now builds its lots with the same helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a18bb072f5
commit
8bf1d5c1a8
@@ -19,14 +19,25 @@ import (
|
||||
type WalletAdminService interface {
|
||||
GetCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, query models.ListCustomerWalletTransactionsQuery) *contract.Response
|
||||
AdjustCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, req *contract.AdjustCustomerWalletRequest) *contract.Response
|
||||
// TraceTransaction follows a ledger row lot by lot back to where its balance came from.
|
||||
TraceTransaction(ctx context.Context, apctx *appcontext.ContextInfo, transactionID uuid.UUID) *contract.Response
|
||||
}
|
||||
|
||||
type WalletAdminServiceImpl struct {
|
||||
walletAdminProcessor *processor.WalletAdminProcessor
|
||||
walletTraceProcessor *processor.WalletTraceProcessor
|
||||
}
|
||||
|
||||
func NewWalletAdminService(walletAdminProcessor *processor.WalletAdminProcessor) *WalletAdminServiceImpl {
|
||||
return &WalletAdminServiceImpl{walletAdminProcessor: walletAdminProcessor}
|
||||
func NewWalletAdminService(walletAdminProcessor *processor.WalletAdminProcessor, walletTraceProcessor *processor.WalletTraceProcessor) *WalletAdminServiceImpl {
|
||||
return &WalletAdminServiceImpl{walletAdminProcessor: walletAdminProcessor, walletTraceProcessor: walletTraceProcessor}
|
||||
}
|
||||
|
||||
func (s *WalletAdminServiceImpl) TraceTransaction(ctx context.Context, apctx *appcontext.ContextInfo, transactionID uuid.UUID) *contract.Response {
|
||||
trace, err := s.walletTraceProcessor.Trace(ctx, apctx.OrganizationID, transactionID)
|
||||
if err != nil {
|
||||
return walletErrorResponse(err)
|
||||
}
|
||||
return contract.BuildSuccessResponse(trace)
|
||||
}
|
||||
|
||||
func (s *WalletAdminServiceImpl) GetCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, query models.ListCustomerWalletTransactionsQuery) *contract.Response {
|
||||
@@ -56,6 +67,8 @@ func walletErrorResponse(err error) *contract.Response {
|
||||
switch {
|
||||
case errors.Is(err, repository.ErrWalletNotFound):
|
||||
code, message = constants.NotFoundErrorCode, "customer not found"
|
||||
case errors.Is(err, repository.ErrWalletTransactionNotFound):
|
||||
code, message = constants.NotFoundErrorCode, "wallet transaction not found"
|
||||
case errors.Is(err, repository.ErrWalletInsufficientBalance):
|
||||
code, message = constants.ValidationErrorCode, "the adjustment would take more than the customer can spend"
|
||||
case errors.Is(err, processor.ErrInvalidWalletQuery),
|
||||
|
||||
Reference in New Issue
Block a user