package repository import ( "context" "fmt" "github.com/google/uuid" "gorm.io/gorm" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/entities" ) // LegacyBalance is what one customer holds in customer_points and customer_tokens, // the tables the wallet replaces (docs/prd-point-coin.md ยง10). type LegacyBalance struct { CustomerID uuid.UUID // Nil when the customer has no customer_points row. PointsRowID *uuid.UUID Points int64 Tokens []entities.CustomerTokens } // Coins is the sum of every token type: all of them become EnakCoin (Q6). func (b LegacyBalance) Coins() int64 { var total int64 for _, t := range b.Tokens { total += t.Balance } return total } // WalletMigrationTotals compares the legacy tables with what has been migrated. type WalletMigrationTotals struct { LegacyPoints int64 LegacyCoins int64 MigratedPoints int64 MigratedCoins int64 WalletPoints int64 WalletCoins int64 } // WalletMigrationRepository reads the legacy balances for the one-time move into the // wallet. The writes go through the wallet processor like any other credit. type WalletMigrationRepository interface { // ListLegacyCustomers returns, in id order, up to limit customers after the given // id that have a row in customer_points or customer_tokens. ListLegacyCustomers(ctx context.Context, after uuid.UUID, limit int) ([]uuid.UUID, error) GetLegacyBalance(ctx context.Context, customerID uuid.UUID) (*LegacyBalance, error) // SumMigrated returns how much has already been credited to the customer by // MIGRATION ledger rows in the currency. SumMigrated(ctx context.Context, customerID uuid.UUID, currency string) (int64, error) Totals(ctx context.Context) (*WalletMigrationTotals, error) } type walletMigrationRepository struct { db *gorm.DB } func NewWalletMigrationRepository(db *gorm.DB) WalletMigrationRepository { return &walletMigrationRepository{db: db} } func (r *walletMigrationRepository) ListLegacyCustomers(ctx context.Context, after uuid.UUID, limit int) ([]uuid.UUID, error) { var ids []uuid.UUID err := DBFromContext(ctx, r.db).WithContext(ctx).Raw(` SELECT customer_id FROM ( SELECT customer_id FROM customer_points UNION SELECT customer_id FROM customer_tokens ) legacy WHERE customer_id > ? ORDER BY customer_id LIMIT ?`, after, limit). Scan(&ids).Error if err != nil { return nil, fmt.Errorf("failed to list legacy customers: %w", err) } return ids, nil } func (r *walletMigrationRepository) GetLegacyBalance(ctx context.Context, customerID uuid.UUID) (*LegacyBalance, error) { db := DBFromContext(ctx, r.db).WithContext(ctx) balance := &LegacyBalance{CustomerID: customerID} // Find rather than First: many customers have tokens but no points row, and First // would log each of them as a "record not found" error. var points []entities.CustomerPoints if err := db.Where("customer_id = ?", customerID).Limit(1).Find(&points).Error; err != nil { return nil, fmt.Errorf("failed to get legacy points: %w", err) } if len(points) > 0 { balance.PointsRowID = &points[0].ID balance.Points = points[0].Balance } err := db.Where("customer_id = ?", customerID).Order("token_type").Find(&balance.Tokens).Error if err != nil { return nil, fmt.Errorf("failed to get legacy tokens: %w", err) } return balance, nil } func (r *walletMigrationRepository) SumMigrated(ctx context.Context, customerID uuid.UUID, currency string) (int64, error) { var total int64 err := DBFromContext(ctx, r.db).WithContext(ctx). Model(&entities.WalletTransaction{}). Where("customer_id = ? AND currency = ? AND type = ?", customerID, currency, constants.WalletTxTypeMigration). Select("COALESCE(SUM(amount), 0)"). Scan(&total).Error if err != nil { return 0, fmt.Errorf("failed to sum migrated balance: %w", err) } return total, nil } func (r *walletMigrationRepository) Totals(ctx context.Context) (*WalletMigrationTotals, error) { var totals WalletMigrationTotals err := DBFromContext(ctx, r.db).WithContext(ctx).Raw(` SELECT (SELECT COALESCE(SUM(balance), 0) FROM customer_points) AS legacy_points, (SELECT COALESCE(SUM(balance), 0) FROM customer_tokens) AS legacy_coins, (SELECT COALESCE(SUM(amount), 0) FROM wallet_transactions WHERE type = ? AND currency = ?) AS migrated_points, (SELECT COALESCE(SUM(amount), 0) FROM wallet_transactions WHERE type = ? AND currency = ?) AS migrated_coins, (SELECT COALESCE(SUM(point_balance), 0) FROM customer_wallets) AS wallet_points, (SELECT COALESCE(SUM(coin_balance), 0) FROM customer_wallets) AS wallet_coins`, constants.WalletTxTypeMigration, constants.WalletCurrencyPoint, constants.WalletTxTypeMigration, constants.WalletCurrencyCoin). Scan(&totals).Error if err != nil { return nil, fmt.Errorf("failed to compute wallet migration totals: %w", err) } return &totals, nil }