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 }