package repositories import ( "context" "time" "gorm.io/gorm" ) type CustomerPaymentTransaction struct { TransactionType string `gorm:"column:transaction_type"` TransactionID int64 `gorm:"column:transaction_id"` CustomerID int64 `gorm:"column:customer_id"` TransDate time.Time `gorm:"column:trans_date"` DeliveryDate *time.Time `gorm:"column:delivery_date"` Reference string `gorm:"column:reference"` VehicleNumbers string `gorm:"column:vehicle_numbers"` Qty float64 `gorm:"column:qty"` Weight float64 `gorm:"column:weight"` AverageWeight float64 `gorm:"column:average_weight"` Price float64 `gorm:"column:price"` FinalPrice float64 `gorm:"column:final_price"` TotalPrice float64 `gorm:"column:total_price"` PaymentAmount float64 `gorm:"column:payment_amount"` PickupInfo string `gorm:"column:pickup_info"` SalesPerson string `gorm:"column:sales_person"` } type CustomerPaymentRepository interface { GetCustomerPaymentTransactions(ctx context.Context, customerID *uint) ([]CustomerPaymentTransaction, error) GetInitialBalanceByCustomer(ctx context.Context, customerID uint) (float64, error) GetCustomerIDsWithTransactions(ctx context.Context, limit, offset int) ([]uint, int64, error) } type customerPaymentRepositoryImpl struct { db *gorm.DB } func NewCustomerPaymentRepository(db *gorm.DB) CustomerPaymentRepository { return &customerPaymentRepositoryImpl{db: db} } func (r *customerPaymentRepositoryImpl) GetCustomerPaymentTransactions(ctx context.Context, customerID *uint) ([]CustomerPaymentTransaction, error) { salesQuery := r.db.WithContext(ctx). Table("marketing_delivery_products mdp"). Select(` 'SALES' AS transaction_type, mdp.id::BIGINT AS transaction_id, c.id::BIGINT AS customer_id, m.so_date::DATE AS trans_date, mdp.delivery_date::DATE AS delivery_date, m.so_number || '-' || TO_CHAR(mdp.delivery_date, 'YYYYMMDD') || '-' || CAST(pw.warehouse_id AS VARCHAR) AS reference, COALESCE(mdp.vehicle_number, '') AS vehicle_numbers, COALESCE(mdp.usage_qty, 0)::NUMERIC(15,3) AS qty, COALESCE(mdp.total_weight, 0)::NUMERIC(15,3) AS weight, COALESCE(mdp.avg_weight, 0)::NUMERIC(15,3) AS average_weight, COALESCE(mdp.unit_price, 0)::NUMERIC(15,3) AS price, COALESCE(mdp.total_price, 0)::NUMERIC(15,3) AS final_price, COALESCE(mdp.total_price, 0)::NUMERIC(15,3) AS total_price, 0::NUMERIC(15,3) AS payment_amount, w.name AS pickup_info, u.name AS sales_person `). Joins("INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id"). Joins("INNER JOIN marketings m ON m.id = mp.marketing_id"). Joins("INNER JOIN customers c ON c.id = m.customer_id"). Joins("INNER JOIN product_warehouses pw ON pw.id = mdp.product_warehouse_id"). Joins("INNER JOIN warehouses w ON w.id = pw.warehouse_id"). Joins("INNER JOIN users u ON u.id = m.sales_person_id"). Where("mdp.delivery_date IS NOT NULL"). Where("m.deleted_at IS NULL"). Where("c.deleted_at IS NULL") if customerID != nil { salesQuery = salesQuery.Where("c.id = ?", *customerID) } paymentQuery := r.db.WithContext(ctx). Table("payments p"). Select(` 'PAYMENT' AS transaction_type, p.id::BIGINT AS transaction_id, c.id::BIGINT AS customer_id, p.payment_date::DATE AS trans_date, NULL AS delivery_date, COALESCE(p.reference_number, p.payment_code) AS reference, '-' AS vehicle_numbers, 0::NUMERIC(15,3) AS qty, 0::NUMERIC(15,3) AS weight, 0::NUMERIC(15,3) AS average_weight, 0::NUMERIC(15,3) AS price, 0::NUMERIC(15,3) AS final_price, 0::NUMERIC(15,3) AS total_price, p.nominal::NUMERIC(15,3) AS payment_amount, '-' AS pickup_info, '-' AS sales_person `). Joins("INNER JOIN customers c ON c.id = p.party_id"). Where("p.party_type = ?", "CUSTOMER"). Where("p.direction = ?", "IN"). Where("p.transaction_type = ?", "PENJUALAN"). Where("p.deleted_at IS NULL"). Where("c.deleted_at IS NULL") if customerID != nil { paymentQuery = paymentQuery.Where("c.id = ?", *customerID) } var results []CustomerPaymentTransaction err := r.db.WithContext(ctx). Raw("? UNION ALL ? ORDER BY customer_id, trans_date, transaction_type DESC, transaction_id", salesQuery, paymentQuery, ). Scan(&results). Error if err != nil { return nil, err } return results, nil } func (r *customerPaymentRepositoryImpl) GetInitialBalanceByCustomer(ctx context.Context, customerID uint) (float64, error) { var result struct { Nominal float64 } err := r.db.WithContext(ctx). Table("payments"). Select("COALESCE(SUM(nominal), 0) as nominal"). Where("party_type = ?", "CUSTOMER"). Where("party_id = ?", customerID). Where("transaction_type = ?", "SALDO_AWAL"). Where("deleted_at IS NULL"). Scan(&result). Error if err != nil { return 0, err } return result.Nominal, nil } func (r *customerPaymentRepositoryImpl) GetCustomerIDsWithTransactions(ctx context.Context, limit, offset int) ([]uint, int64, error) { subQuery := r.db.WithContext(ctx). Table("(" + "SELECT DISTINCT c.id as customer_id FROM marketing_delivery_products mdp " + "INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id " + "INNER JOIN marketings m ON m.id = mp.marketing_id " + "INNER JOIN customers c ON c.id = m.customer_id " + "WHERE mdp.delivery_date IS NOT NULL AND m.deleted_at IS NULL AND c.deleted_at IS NULL " + "UNION " + "SELECT DISTINCT c.id as customer_id FROM payments p " + "INNER JOIN customers c ON c.id = p.party_id " + "WHERE p.party_type = 'CUSTOMER' AND p.direction = 'IN' " + "AND p.transaction_type = 'PENJUALAN' AND p.deleted_at IS NULL AND c.deleted_at IS NULL" + ") as customer_ids") var total int64 if err := subQuery.Count(&total).Error; err != nil { return nil, 0, err } var customerIDs []uint err := r.db.WithContext(ctx). Table("("+ "SELECT DISTINCT c.id as customer_id FROM marketing_delivery_products mdp "+ "INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id "+ "INNER JOIN marketings m ON m.id = mp.marketing_id "+ "INNER JOIN customers c ON c.id = m.customer_id "+ "WHERE mdp.delivery_date IS NOT NULL AND m.deleted_at IS NULL AND c.deleted_at IS NULL "+ "UNION "+ "SELECT DISTINCT c.id as customer_id FROM payments p "+ "INNER JOIN customers c ON c.id = p.party_id "+ "WHERE p.party_type = 'CUSTOMER' AND p.direction = 'IN' "+ "AND p.transaction_type = 'PENJUALAN' AND p.deleted_at IS NULL AND c.deleted_at IS NULL"+ ") as customer_ids"). Select("customer_id"). Order("customer_id ASC"). Limit(limit). Offset(offset). Pluck("customer_id", &customerIDs). Error if err != nil { return nil, 0, err } return customerIDs, total, nil }