package repository import ( "context" "errors" "fmt" "time" "github.com/google/uuid" "gorm.io/gorm" "apskel-pos-be/internal/constants" ) // WalletMoveCustomer is a customer on either side of an exchange or a transfer. type WalletMoveCustomer struct { ID uuid.UUID OrganizationID uuid.UUID Name string PhoneNumber *string IsDefault bool IsActive bool } // WalletMoveRepository reads what exchanges and transfers check before moving a // balance (docs/prd-point-coin.md F4, F5). The balance itself only moves through // WalletProcessor. type WalletMoveRepository interface { // GetCustomer returns ErrWalletNotFound when the customer does not exist. GetCustomer(ctx context.Context, customerID uuid.UUID) (*WalletMoveCustomer, error) // FindCustomerByPhone returns ErrWalletNotFound when no customer has the number. // Phone numbers are unique across organizations, so there is at most one. FindCustomerByPhone(ctx context.Context, phoneNumber string) (*WalletMoveCustomer, error) // TransferredOutSince sums what a customer has sent in one currency since a time. // Call it under the sender's wallet lock, so transfers at the same time count. TransferredOutSince(ctx context.Context, customerID uuid.UUID, currency string, since time.Time) (int64, error) } type walletMoveRepository struct { db *gorm.DB } func NewWalletMoveRepository(db *gorm.DB) WalletMoveRepository { return &walletMoveRepository{db: db} } func (r *walletMoveRepository) GetCustomer(ctx context.Context, customerID uuid.UUID) (*WalletMoveCustomer, error) { return r.customer(ctx, "id = ?", customerID) } func (r *walletMoveRepository) FindCustomerByPhone(ctx context.Context, phoneNumber string) (*WalletMoveCustomer, error) { return r.customer(ctx, "phone_number = ?", phoneNumber) } func (r *walletMoveRepository) TransferredOutSince(ctx context.Context, customerID uuid.UUID, currency string, since time.Time) (int64, error) { var total int64 err := DBFromContext(ctx, r.db).WithContext(ctx). Table("wallet_transactions"). Select("COALESCE(SUM(-amount), 0)"). Where("customer_id = ? AND currency = ? AND type = ? AND created_at >= ?", customerID, currency, constants.WalletTxTypeTransferOut, since). Scan(&total).Error if err != nil { return 0, fmt.Errorf("failed to sum transfers: %w", err) } return total, nil } func (r *walletMoveRepository) customer(ctx context.Context, where string, arg interface{}) (*WalletMoveCustomer, error) { var c WalletMoveCustomer err := DBFromContext(ctx, r.db).WithContext(ctx). Table("customers"). Select("id, organization_id, name, phone_number, is_default, is_active"). Where(where, arg). Take(&c).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrWalletNotFound } return nil, fmt.Errorf("failed to get customer: %w", err) } return &c, nil }