package repositories import ( "context" "fmt" "strings" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) type DebtSupplierRepository interface { GetSuppliersWithPurchases(ctx context.Context, offset, limit int, filters *validation.DebtSupplierQuery) ([]entity.Supplier, int64, error) GetPurchasesBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Purchase, error) GetPaymentsBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Payment, error) GetPaymentTotalsByReferences(ctx context.Context, supplierIDs []uint, references []string) (map[string]float64, error) GetPurchaseTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error) GetPaymentTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error) } type debtSupplierRepositoryImpl struct { db *gorm.DB } func NewDebtSupplierRepository(db *gorm.DB) DebtSupplierRepository { return &debtSupplierRepositoryImpl{db: db} } func resolveDebtSupplierDateColumn(filterBy string) string { switch strings.ToLower(strings.TrimSpace(filterBy)) { case "po_date": return "purchases.po_date" case "received_date", "": return "purchase_items.received_date" default: return "purchase_items.received_date" } } func (r *debtSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context, filters *validation.DebtSupplierQuery) *gorm.DB { dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy) db := r.db.WithContext(ctx). Model(&entity.Supplier{}). Joins("JOIN purchases ON purchases.supplier_id = suppliers.id"). Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id") if len(filters.SupplierIDs) > 0 { db = db.Where("suppliers.id IN ?", filters.SupplierIDs) } if filters.StartDate != "" { if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom) } } if filters.EndDate != "" { if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil { db = db.Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), dateTo) } } return db } func (r *debtSupplierRepositoryImpl) GetSuppliersWithPurchases(ctx context.Context, offset, limit int, filters *validation.DebtSupplierQuery) ([]entity.Supplier, int64, error) { query := r.baseSupplierQuery(ctx, filters) var totalSuppliers int64 if err := query. Distinct("suppliers.id"). Count(&totalSuppliers).Error; err != nil { return nil, 0, err } if totalSuppliers == 0 { return []entity.Supplier{}, 0, nil } if offset < 0 { offset = 0 } var supplierIDs []uint if err := query. Select("suppliers.id"). Order("suppliers.id ASC"). Offset(offset). Limit(limit). Pluck("suppliers.id", &supplierIDs).Error; err != nil { return nil, 0, err } if len(supplierIDs) == 0 { return []entity.Supplier{}, totalSuppliers, nil } var suppliers []entity.Supplier if err := r.db.WithContext(ctx). Where("id IN ?", supplierIDs). Find(&suppliers).Error; err != nil { return nil, 0, err } return suppliers, totalSuppliers, nil } func (r *debtSupplierRepositoryImpl) GetPurchasesBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Purchase, error) { if len(supplierIDs) == 0 { return []entity.Purchase{}, nil } purchaseIDs, err := r.getPurchaseIDs(ctx, supplierIDs, filters) if err != nil { return nil, err } if len(purchaseIDs) == 0 { return []entity.Purchase{}, nil } preloadItems := func(db *gorm.DB) *gorm.DB { db = db. Preload("Warehouse"). Preload("Warehouse.Area"). Order("purchase_items.id ASC") if strings.EqualFold(strings.TrimSpace(filters.FilterBy), "received_date") || strings.TrimSpace(filters.FilterBy) == "" { if filters.StartDate != "" { if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where("DATE(purchase_items.received_date) >= ?", dateFrom) } } if filters.EndDate != "" { if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil { db = db.Where("DATE(purchase_items.received_date) <= ?", dateTo) } } } return db } var purchases []entity.Purchase if err := r.db.WithContext(ctx). Model(&entity.Purchase{}). Preload("Supplier"). Preload("Items", preloadItems). Where("purchases.id IN ?", purchaseIDs). Order("purchases.id ASC"). Find(&purchases).Error; err != nil { return nil, err } return purchases, nil } func (r *debtSupplierRepositoryImpl) GetPaymentsBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Payment, error) { if len(supplierIDs) == 0 { return []entity.Payment{}, nil } db := r.db.WithContext(ctx). Model(&entity.Payment{}). Where("party_type = ?", string(utils.PaymentPartySupplier)). Where("direction = ?", "OUT"). Where("party_id IN ?", supplierIDs) if strings.TrimSpace(filters.StartDate) != "" { if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where("DATE(payment_date) >= ?", dateFrom) } } if strings.TrimSpace(filters.EndDate) != "" { if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil { db = db.Where("DATE(payment_date) <= ?", dateTo) } } var payments []entity.Payment if err := db. Order("payment_date ASC, id ASC"). Find(&payments).Error; err != nil { return nil, err } return payments, nil } func (r *debtSupplierRepositoryImpl) getPurchaseIDs(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]uint, error) { dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy) db := r.db.WithContext(ctx). Table("purchases"). Select("DISTINCT purchases.id"). Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id"). Where("purchases.supplier_id IN ?", supplierIDs) if filters.StartDate != "" { if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom) } } if filters.EndDate != "" { if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil { db = db.Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), dateTo) } } var purchaseIDs []uint if err := db.Order("purchases.id ASC").Pluck("purchases.id", &purchaseIDs).Error; err != nil { return nil, err } return purchaseIDs, nil } func (r *debtSupplierRepositoryImpl) GetPaymentTotalsByReferences(ctx context.Context, supplierIDs []uint, references []string) (map[string]float64, error) { if len(supplierIDs) == 0 || len(references) == 0 { return map[string]float64{}, nil } type paymentRow struct { ReferenceNumber *string `gorm:"column:reference_number"` Total float64 `gorm:"column:total"` } rows := make([]paymentRow, 0) if err := r.db.WithContext(ctx). Model(&entity.Payment{}). Select("reference_number, SUM(nominal) AS total"). Where("party_type = ?", string(utils.PaymentPartySupplier)). Where("direction = ?", "OUT"). Where("party_id IN ?", supplierIDs). Where("reference_number IN ?", references). Group("reference_number"). Scan(&rows).Error; err != nil { return nil, err } result := make(map[string]float64, len(rows)) for _, row := range rows { if row.ReferenceNumber == nil || strings.TrimSpace(*row.ReferenceNumber) == "" { continue } result[*row.ReferenceNumber] = row.Total } return result, nil } func (r *debtSupplierRepositoryImpl) GetPurchaseTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error) { if len(supplierIDs) == 0 || strings.TrimSpace(filters.StartDate) == "" { return map[uint]float64{}, nil } dateFrom, err := utils.ParseDateString(filters.StartDate) if err != nil { return map[uint]float64{}, nil } dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy) type purchaseTotalRow struct { SupplierID uint `gorm:"column:supplier_id"` Total float64 `gorm:"column:total"` } rows := make([]purchaseTotalRow, 0) if err := r.db.WithContext(ctx). Table("purchases"). Select("purchases.supplier_id AS supplier_id, SUM(purchase_items.total_price) AS total"). Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id"). Where("purchases.supplier_id IN ?", supplierIDs). Where(fmt.Sprintf("DATE(%s) < ?", dateColumn), dateFrom). Group("purchases.supplier_id"). Scan(&rows).Error; err != nil { return nil, err } result := make(map[uint]float64, len(rows)) for _, row := range rows { result[row.SupplierID] = row.Total } return result, nil } func (r *debtSupplierRepositoryImpl) GetPaymentTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error) { if len(supplierIDs) == 0 || strings.TrimSpace(filters.StartDate) == "" { return map[uint]float64{}, nil } dateFrom, err := utils.ParseDateString(filters.StartDate) if err != nil { return map[uint]float64{}, nil } type paymentTotalRow struct { SupplierID uint `gorm:"column:supplier_id"` Total float64 `gorm:"column:total"` } rows := make([]paymentTotalRow, 0) if err := r.db.WithContext(ctx). Model(&entity.Payment{}). Select("party_id AS supplier_id, SUM(nominal) AS total"). Where("party_type = ?", string(utils.PaymentPartySupplier)). Where("direction = ?", "OUT"). Where("party_id IN ?", supplierIDs). Where("DATE(payment_date) < ?", dateFrom). Group("party_id"). Scan(&rows).Error; err != nil { return nil, err } result := make(map[uint]float64, len(rows)) for _, row := range rows { result[row.SupplierID] = row.Total } return result, nil }