mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
feat[BE]: implement customer payment report retrieval with pagination and filtering
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
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"`
|
||||
CreditNote float64 `gorm:"column:credit_note"`
|
||||
FinalPrice float64 `gorm:"column:final_price"`
|
||||
PPN float64 `gorm:"column:ppn"`
|
||||
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) {
|
||||
// Build SALES subquery
|
||||
salesQuery := r.db.WithContext(ctx).
|
||||
Table("marketings m").
|
||||
Select(`
|
||||
'SALES' AS transaction_type,
|
||||
m.id::BIGINT AS transaction_id,
|
||||
c.id::BIGINT AS customer_id,
|
||||
m.so_date::DATE AS trans_date,
|
||||
MAX(mdp.delivery_date)::DATE AS delivery_date,
|
||||
m.so_number AS reference,
|
||||
COALESCE(STRING_AGG(DISTINCT mdp.vehicle_number, ', ') FILTER (WHERE mdp.vehicle_number IS NOT NULL AND mdp.vehicle_number != ''), '') AS vehicle_numbers,
|
||||
COALESCE(SUM(COALESCE(mp.qty, 0)), 0)::NUMERIC(15,3) AS qty,
|
||||
COALESCE(SUM(COALESCE(mdp.total_weight, mp.total_weight, 0)), 0)::NUMERIC(15,3) AS weight,
|
||||
CASE WHEN COALESCE(SUM(COALESCE(mp.qty, 0)), 0) > 0
|
||||
THEN (COALESCE(SUM(COALESCE(mdp.total_weight, mp.total_weight, 0)), 0) / COALESCE(SUM(COALESCE(mp.qty, 0)), 0))::NUMERIC(15,3)
|
||||
ELSE 0::NUMERIC(15,3)
|
||||
END AS average_weight,
|
||||
COALESCE(AVG(COALESCE(mdp.unit_price, mp.unit_price, 0)), 0)::NUMERIC(15,3) AS price,
|
||||
0::NUMERIC(15,3) AS credit_note,
|
||||
COALESCE(SUM(COALESCE(mdp.total_price, mp.total_price)), 0)::NUMERIC(15,3) AS final_price,
|
||||
0::NUMERIC(15,3) AS ppn,
|
||||
COALESCE(SUM(COALESCE(mdp.total_price, mp.total_price)), 0)::NUMERIC(15,3) AS total_price,
|
||||
0::NUMERIC(15,3) AS payment_amount,
|
||||
COALESCE(STRING_AGG(DISTINCT w.name, ', ') FILTER (WHERE w.name IS NOT NULL), '') AS pickup_info,
|
||||
MAX(u.name) AS sales_person
|
||||
`).
|
||||
Joins("INNER JOIN customers c ON c.id = m.customer_id").
|
||||
Joins("LEFT JOIN marketing_products mp ON mp.marketing_id = m.id").
|
||||
Joins("LEFT JOIN marketing_delivery_products mdp ON mdp.marketing_product_id = mp.id").
|
||||
Joins("LEFT JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
||||
Joins("LEFT JOIN warehouses w ON w.id = pw.warehouse_id").
|
||||
Joins("LEFT JOIN users u ON u.id = m.sales_person_id").
|
||||
Where("m.deleted_at IS NULL").
|
||||
Where("c.deleted_at IS NULL")
|
||||
|
||||
if customerID != nil {
|
||||
salesQuery = salesQuery.Where("c.id = ?", *customerID)
|
||||
}
|
||||
|
||||
salesQuery = salesQuery.Group("m.id, c.id, m.so_date, m.so_number")
|
||||
|
||||
// Build PAYMENT subquery
|
||||
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 credit_note,
|
||||
0::NUMERIC(15,3) AS final_price,
|
||||
0::NUMERIC(15,3) AS ppn,
|
||||
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)
|
||||
}
|
||||
|
||||
// Combine with UNION ALL and execute
|
||||
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 to get all distinct customer IDs with transactions
|
||||
subQuery := r.db.WithContext(ctx).
|
||||
Table("(" +
|
||||
"SELECT DISTINCT c.id as customer_id FROM marketings m " +
|
||||
"INNER JOIN customers c ON c.id = m.customer_id " +
|
||||
"WHERE 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")
|
||||
|
||||
// Count total customers
|
||||
var total int64
|
||||
if err := subQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated customer IDs
|
||||
var customerIDs []uint
|
||||
err := r.db.WithContext(ctx).
|
||||
Table("("+
|
||||
"SELECT DISTINCT c.id as customer_id FROM marketings m "+
|
||||
"INNER JOIN customers c ON c.id = m.customer_id "+
|
||||
"WHERE 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
|
||||
}
|
||||
Reference in New Issue
Block a user