Files
apskel-pos-backend/internal/repository/wallet_admin_repository.go
T
2026-09-30 15:31:44 +07:00

127 lines
4.2 KiB
Go

package repository
import (
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
"apskel-pos-be/internal/entities"
)
// WalletCustomer is the customer a dashboard wallet screen is about.
type WalletCustomer struct {
ID uuid.UUID
Name string
Phone *string
}
// WalletAdminRepository serves what the dashboard adds on top of the customer's own
// wallet view (docs/prd-point-coin.md F7): the raw balances, every lot with balance,
// and the real names behind a ledger row. Nothing here changes a balance.
type WalletAdminRepository interface {
// GetCustomer returns ErrWalletNotFound unless the customer exists and belongs to
// the organization, so one organization can never read another's customers.
GetCustomer(ctx context.Context, organizationID, customerID uuid.UUID) (*WalletCustomer, error)
// GetWallet returns nil, nil for a customer who has never had a wallet.
GetWallet(ctx context.Context, customerID uuid.UUID) (*entities.CustomerWallet, error)
// ListLotsWithBalance returns every lot that still holds something, expired or
// not, in K9 spending order.
ListLotsWithBalance(ctx context.Context, customerID uuid.UUID) ([]entities.WalletLot, error)
CustomerNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error)
UserNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error)
OutletNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error)
}
type walletAdminRepository struct {
db *gorm.DB
}
func NewWalletAdminRepository(db *gorm.DB) WalletAdminRepository {
return &walletAdminRepository{db: db}
}
func (r *walletAdminRepository) GetCustomer(ctx context.Context, organizationID, customerID uuid.UUID) (*WalletCustomer, error) {
var customers []WalletCustomer
err := DBFromContext(ctx, r.db).WithContext(ctx).
Table("customers").
Select("id, name, COALESCE(phone_number, phone) AS phone").
Where("id = ? AND organization_id = ?", customerID, organizationID).
Limit(1).
Scan(&customers).Error
if err != nil {
return nil, fmt.Errorf("failed to get customer: %w", err)
}
if len(customers) == 0 {
return nil, ErrWalletNotFound
}
return &customers[0], nil
}
func (r *walletAdminRepository) GetWallet(ctx context.Context, customerID uuid.UUID) (*entities.CustomerWallet, error) {
var wallets []entities.CustomerWallet
err := DBFromContext(ctx, r.db).WithContext(ctx).
Where("customer_id = ?", customerID).
Limit(1).
Find(&wallets).Error
if err != nil {
return nil, fmt.Errorf("failed to get customer wallet: %w", err)
}
if len(wallets) == 0 {
return nil, nil
}
return &wallets[0], nil
}
func (r *walletAdminRepository) ListLotsWithBalance(ctx context.Context, customerID uuid.UUID) ([]entities.WalletLot, error) {
var lots []entities.WalletLot
err := DBFromContext(ctx, r.db).WithContext(ctx).
Where("customer_id = ? AND remaining_amount > 0", customerID).
Order("currency, expires_at NULLS LAST, created_at, id").
Find(&lots).Error
if err != nil {
return nil, fmt.Errorf("failed to list wallet lots: %w", err)
}
return lots, nil
}
func (r *walletAdminRepository) CustomerNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error) {
return r.names(ctx, "customers", ids)
}
func (r *walletAdminRepository) UserNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error) {
return r.names(ctx, "users", ids)
}
func (r *walletAdminRepository) OutletNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error) {
return r.names(ctx, "outlets", ids)
}
// names looks up the name column of a table by id. The table name is never taken
// from user input.
func (r *walletAdminRepository) names(ctx context.Context, table string, ids []uuid.UUID) (map[uuid.UUID]string, error) {
names := make(map[uuid.UUID]string, len(ids))
if len(ids) == 0 {
return names, nil
}
var rows []struct {
ID string
Name string
}
err := DBFromContext(ctx, r.db).WithContext(ctx).
Table(table).
Select("id::text AS id, name").
Where("id IN ?", ids).
Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("failed to look up %s names: %w", table, err)
}
for _, row := range rows {
if id, err := uuid.Parse(row.ID); err == nil {
names[id] = row.Name
}
}
return names, nil
}