Revert "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts merge commit645da30, returning main tof0ff59f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
645da3048e
commit
4e24f9bbb0
@@ -1,223 +0,0 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/repository"
|
||||
)
|
||||
|
||||
// walletDescriptionLimit is the size of wallet_transactions.description.
|
||||
const walletDescriptionLimit = 255
|
||||
|
||||
// WalletAdminProcessor serves the dashboard's view of one customer's wallet and the
|
||||
// manual adjustments an admin makes to it (docs/prd-point-coin.md F7).
|
||||
type WalletAdminProcessor struct {
|
||||
admin repository.WalletAdminRepository
|
||||
query repository.WalletQueryRepository
|
||||
wallet *WalletProcessor
|
||||
settings organizationSettingsReader
|
||||
tx TxRunner
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func NewWalletAdminProcessor(admin repository.WalletAdminRepository, query repository.WalletQueryRepository, wallet *WalletProcessor, settings organizationSettingsReader, tx TxRunner) *WalletAdminProcessor {
|
||||
return &WalletAdminProcessor{admin: admin, query: query, wallet: wallet, settings: settings, tx: tx, now: time.Now}
|
||||
}
|
||||
|
||||
// GetCustomerWallet returns the customer's balances, lots and a page of history.
|
||||
// A customer outside the organization is reported as not found.
|
||||
func (p *WalletAdminProcessor) GetCustomerWallet(ctx context.Context, organizationID, customerID uuid.UUID, query models.ListCustomerWalletTransactionsQuery) (*models.AdminCustomerWallet, error) {
|
||||
filter, page, err := walletTransactionFilter(customerID, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customer, err := p.admin.GetCustomer(ctx, organizationID, customerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := p.now()
|
||||
|
||||
result := &models.AdminCustomerWallet{
|
||||
Customer: models.AdminWalletCustomer{ID: customer.ID, Name: customer.Name, Phone: customer.Phone},
|
||||
Lots: []models.AdminWalletLot{},
|
||||
}
|
||||
wallet, err := p.admin.GetWallet(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wallet != nil {
|
||||
result.PointBalance = wallet.PointBalance
|
||||
result.CoinBalance = wallet.CoinBalance
|
||||
}
|
||||
spendable, err := p.query.SpendableBalances(ctx, customerID, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.SpendablePointBalance = spendable[constants.WalletCurrencyPoint]
|
||||
result.SpendableCoinBalance = spendable[constants.WalletCurrencyCoin]
|
||||
|
||||
lots, err := p.admin.ListLotsWithBalance(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, lot := range lots {
|
||||
result.Lots = append(result.Lots, adminWalletLot(lot, now))
|
||||
}
|
||||
|
||||
rows, total, err := p.query.ListTransactions(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transactions, err := p.adminTransactions(ctx, rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Transactions = models.PaginatedResponse[models.AdminWalletTransaction]{
|
||||
Data: transactions,
|
||||
Pagination: models.Pagination{
|
||||
Page: page,
|
||||
Limit: filter.Limit,
|
||||
Total: total,
|
||||
TotalPages: int((total + int64(filter.Limit) - 1) / int64(filter.Limit)),
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Adjust adds to or takes from a customer's balance by hand. It is recorded as an
|
||||
// ADJUSTMENT pointing at the admin, with the reason. Taking more than the customer
|
||||
// can spend is refused with repository.ErrWalletInsufficientBalance.
|
||||
func (p *WalletAdminProcessor) Adjust(ctx context.Context, organizationID, adminID, customerID uuid.UUID, in models.WalletAdjustment) (*models.AdminWalletAdjustmentResult, error) {
|
||||
reason := strings.TrimSpace(in.Reason)
|
||||
if adminID == uuid.Nil {
|
||||
return nil, fmt.Errorf("%w: the admin making the adjustment is unknown", ErrWalletInvalidEntry)
|
||||
}
|
||||
if in.Amount == 0 {
|
||||
return nil, fmt.Errorf("%w: amount must not be zero", ErrWalletInvalidEntry)
|
||||
}
|
||||
|
||||
entry := WalletEntry{
|
||||
CustomerID: customerID,
|
||||
Currency: in.Currency,
|
||||
Type: constants.WalletTxTypeAdjustment,
|
||||
Amount: abs(in.Amount),
|
||||
ReferenceType: constants.WalletRefTypeUser,
|
||||
ReferenceID: adminID,
|
||||
CreatedByUser: &adminID,
|
||||
Reason: &reason,
|
||||
Description: truncateRunes("Koreksi oleh admin: "+reason, walletDescriptionLimit),
|
||||
}
|
||||
if in.IdempotencyKey != "" {
|
||||
// Scoped to the customer so a key chosen by the dashboard cannot collide with
|
||||
// the keys other flows use.
|
||||
entry.IdempotencyKey = fmt.Sprintf("adjust:%s:%s", customerID, in.IdempotencyKey)
|
||||
}
|
||||
|
||||
var res *WalletResult
|
||||
err := p.tx.WithTransaction(ctx, func(ctx context.Context) error {
|
||||
if _, err := p.admin.GetCustomer(ctx, organizationID, customerID); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Amount < 0 {
|
||||
var err error
|
||||
res, err = p.wallet.Debit(ctx, WalletDebitInput{WalletEntry: entry})
|
||||
return err
|
||||
}
|
||||
// An adjustment that adds is a new balance, and expires like one (F7, F12).
|
||||
settings, err := p.settings.Organization(ctx, organizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err = p.wallet.Credit(ctx, WalletCreditInput{
|
||||
WalletEntry: entry,
|
||||
Lots: []WalletLotInput{{Amount: entry.Amount, ExpiresAt: ComputeExpiry(ExpirySettings(settings, in.Currency), p.now())}},
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transactions, err := p.adminTransactions(ctx, []entities.WalletTransaction{*res.Transaction})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spendable, err := p.query.SpendableBalances(ctx, customerID, p.now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &models.AdminWalletAdjustmentResult{
|
||||
Transaction: transactions[0],
|
||||
SpendablePointBalance: spendable[constants.WalletCurrencyPoint],
|
||||
SpendableCoinBalance: spendable[constants.WalletCurrencyCoin],
|
||||
Replayed: res.Replayed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// adminTransactions adds, to what the customer sees, the names behind each row.
|
||||
func (p *WalletAdminProcessor) adminTransactions(ctx context.Context, rows []entities.WalletTransaction) ([]models.AdminWalletTransaction, error) {
|
||||
base, err := buildWalletTransactions(ctx, p.query, rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var customerIDs, userIDs, outletIDs []uuid.UUID
|
||||
for _, row := range rows {
|
||||
if row.CounterpartyCustomerID != nil {
|
||||
customerIDs = append(customerIDs, *row.CounterpartyCustomerID)
|
||||
}
|
||||
if row.CreatedByUser != nil {
|
||||
userIDs = append(userIDs, *row.CreatedByUser)
|
||||
}
|
||||
if row.OutletID != nil {
|
||||
outletIDs = append(outletIDs, *row.OutletID)
|
||||
}
|
||||
}
|
||||
customers, err := p.admin.CustomerNames(ctx, customerIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users, err := p.admin.UserNames(ctx, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outlets, err := p.admin.OutletNames(ctx, outletIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
named := func(id *uuid.UUID, names map[uuid.UUID]string) *models.AdminWalletNamedRef {
|
||||
if id == nil {
|
||||
return nil
|
||||
}
|
||||
return &models.AdminWalletNamedRef{ID: *id, Name: names[*id]}
|
||||
}
|
||||
|
||||
out := make([]models.AdminWalletTransaction, 0, len(rows))
|
||||
for i, row := range rows {
|
||||
out = append(out, models.AdminWalletTransaction{
|
||||
CustomerWalletTransaction: base[i],
|
||||
Counterparty: named(row.CounterpartyCustomerID, customers),
|
||||
CreatedBy: named(row.CreatedByUser, users),
|
||||
Outlet: named(row.OutletID, outlets),
|
||||
Reason: row.Reason,
|
||||
Metadata: row.Metadata,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func truncateRunes(s string, limit int) string {
|
||||
if utf8.RuneCountInString(s) <= limit {
|
||||
return s
|
||||
}
|
||||
return string([]rune(s)[:limit])
|
||||
}
|
||||
Reference in New Issue
Block a user