127 lines
4.2 KiB
Go
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
|
||
|
|
}
|