package repository import ( "context" "errors" "fmt" "github.com/google/uuid" "gorm.io/gorm" "apskel-pos-be/internal/entities" ) // ErrWalletTransactionNotFound means the ledger row does not exist or belongs to // another organization. var ErrWalletTransactionNotFound = errors.New("wallet: transaction not found") // WalletTraceRepository reads what tracing a ledger row lot by lot needs // (docs/prd-point-coin.md F7, ยง8.1). Nothing here changes a balance. type WalletTraceRepository interface { // GetTransaction returns ErrWalletTransactionNotFound unless the row belongs to // the organization. GetTransaction(ctx context.Context, organizationID, id uuid.UUID) (*entities.WalletTransaction, error) GetTransactions(ctx context.Context, ids []uuid.UUID) ([]entities.WalletTransaction, error) // ListAllocations returns what a debit took from each lot, in the order it took. ListAllocations(ctx context.Context, transactionID uuid.UUID) ([]entities.WalletLotAllocation, error) // ListLotsBySource returns the lots a credit created, oldest first. ListLotsBySource(ctx context.Context, transactionID uuid.UUID) ([]entities.WalletLot, error) GetLots(ctx context.Context, ids []uuid.UUID) ([]entities.WalletLot, error) CustomerNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error) } type walletTraceRepository struct { db *gorm.DB names *walletAdminRepository } func NewWalletTraceRepository(db *gorm.DB) WalletTraceRepository { return &walletTraceRepository{db: db, names: &walletAdminRepository{db: db}} } func (r *walletTraceRepository) GetTransaction(ctx context.Context, organizationID, id uuid.UUID) (*entities.WalletTransaction, error) { var rows []entities.WalletTransaction err := DBFromContext(ctx, r.db).WithContext(ctx). Where("id = ? AND organization_id = ?", id, organizationID). Limit(1). Find(&rows).Error if err != nil { return nil, fmt.Errorf("failed to get wallet transaction: %w", err) } if len(rows) == 0 { return nil, ErrWalletTransactionNotFound } return &rows[0], nil } func (r *walletTraceRepository) GetTransactions(ctx context.Context, ids []uuid.UUID) ([]entities.WalletTransaction, error) { var rows []entities.WalletTransaction if len(ids) == 0 { return rows, nil } if err := DBFromContext(ctx, r.db).WithContext(ctx).Where("id IN ?", ids).Find(&rows).Error; err != nil { return nil, fmt.Errorf("failed to get wallet transactions: %w", err) } return rows, nil } func (r *walletTraceRepository) ListAllocations(ctx context.Context, transactionID uuid.UUID) ([]entities.WalletLotAllocation, error) { var rows []entities.WalletLotAllocation // A debit draws in K9 order, so the lots' order is the order it took from them. err := DBFromContext(ctx, r.db).WithContext(ctx). Table("wallet_lot_allocations a"). Select("a.*"). Joins("JOIN wallet_lots l ON l.id = a.lot_id"). Where("a.transaction_id = ?", transactionID). Order("l.expires_at NULLS LAST, l.created_at, l.id"). Scan(&rows).Error if err != nil { return nil, fmt.Errorf("failed to list wallet lot allocations: %w", err) } return rows, nil } func (r *walletTraceRepository) ListLotsBySource(ctx context.Context, transactionID uuid.UUID) ([]entities.WalletLot, error) { var lots []entities.WalletLot err := DBFromContext(ctx, r.db).WithContext(ctx). Where("source_transaction_id = ?", transactionID). Order("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 *walletTraceRepository) GetLots(ctx context.Context, ids []uuid.UUID) ([]entities.WalletLot, error) { var lots []entities.WalletLot if len(ids) == 0 { return lots, nil } if err := DBFromContext(ctx, r.db).WithContext(ctx).Where("id IN ?", ids).Find(&lots).Error; err != nil { return nil, fmt.Errorf("failed to get wallet lots: %w", err) } return lots, nil } func (r *walletTraceRepository) CustomerNames(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error) { return r.names.CustomerNames(ctx, ids) }