package repository import ( "context" "fmt" "strings" "time" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) type ClosingRepository interface { repository.BaseRepository[entity.ProjectFlock] GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) } type ClosingRepositoryImpl struct { *repository.BaseRepositoryImpl[entity.ProjectFlock] } func NewClosingRepository(db *gorm.DB) ClosingRepository { return &ClosingRepositoryImpl{ BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db), } } type SapronakRow struct { Id uint64 `gorm:"column:id"` SortDate time.Time `gorm:"column:sort_date"` DateText string `gorm:"column:date_text"` ReferenceNumber string `gorm:"column:reference_number"` TransactionType string `gorm:"column:transaction_type"` ProductName string `gorm:"column:product_name"` ProductCategory string `gorm:"column:product_category"` ProductSubCategory string `gorm:"column:product_sub_category"` SourceWarehouse string `gorm:"column:source_warehouse"` DestinationWarehouse string `gorm:"column:destination_warehouse"` Destination string `gorm:"column:destination"` Quantity float64 `gorm:"column:quantity"` Unit string `gorm:"column:unit"` Notes string `gorm:"column:notes"` } type SapronakQueryParams struct { Type string WarehouseIDs []uint ProjectFlockKandangIDs []uint Limit int Offset int } func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error) { db := r.DB().WithContext(ctx) var ( unionParts []string args []any ) switch params.Type { case validation.SapronakTypeIncoming: if len(params.WarehouseIDs) == 0 { return []SapronakRow{}, 0, nil } unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL) args = append(args, params.WarehouseIDs, params.WarehouseIDs) case validation.SapronakTypeOutgoing: if len(params.WarehouseIDs) > 0 { unionParts = append(unionParts, sapronakOutgoingTransfersSQL) args = append(args, params.WarehouseIDs) } if len(params.ProjectFlockKandangIDs) > 0 { unionParts = append(unionParts, sapronakOutgoingMarketingsSQL) args = append(args, params.ProjectFlockKandangIDs) } if len(unionParts) == 0 { return []SapronakRow{}, 0, nil } default: return nil, 0, fmt.Errorf("invalid sapronak type: %s", params.Type) } unionSQL := strings.Join(unionParts, " UNION ALL ") var totalResults int64 countSQL := fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS combined", unionSQL) if err := db.Raw(countSQL, args...).Scan(&totalResults).Error; err != nil { return nil, 0, err } dataArgs := append(append([]any{}, args...), params.Limit, params.Offset) dataSQL := fmt.Sprintf("SELECT * FROM (%s) AS combined ORDER BY sort_date ASC, id ASC LIMIT ? OFFSET ?", unionSQL) var rows []SapronakRow if err := db.Raw(dataSQL, dataArgs...).Scan(&rows).Error; err != nil { return nil, 0, err } return rows, totalResults, nil } func (r *ClosingRepositoryImpl) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, 0, nil } var purchaseAgg struct { TotalIn float64 `gorm:"column:total_in"` } err := r.DB().WithContext(ctx). Table("purchase_items pi"). Joins("JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products'"). Where("f.name = ?", "PAKAN"). Where("pi.project_flock_kandang_id IN ?", projectFlockKandangIDs). Select("COALESCE(SUM(pi.total_qty), 0) AS total_in"). Scan(&purchaseAgg).Error if err != nil { return 0, 0, err } var usageAgg struct { TotalUsed float64 `gorm:"column:total_used"` } err = r.DB().WithContext(ctx). Table("recording_stocks rs"). Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id"). Joins("JOIN products prod ON prod.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). Where("f.name = ?", "PAKAN"). Select("COALESCE(SUM(COALESCE(rs.usage_qty, 0) + COALESCE(rs.pending_qty, 0)), 0) AS total_used"). Scan(&usageAgg).Error if err != nil { return 0, 0, err } return purchaseAgg.TotalIn, usageAgg.TotalUsed, nil } func (r *ClosingRepositoryImpl) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, nil } var agg struct { Total float64 `gorm:"column:total_culling"` } err := r.DB().WithContext(ctx). Table("recording_depletions rd"). Joins("JOIN product_warehouses pw ON pw.id = rd.product_warehouse_id"). Joins("JOIN products prod ON prod.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). Where("f.name = ?", utils.FlagAyamCulling). Select("COALESCE(SUM(rd.qty), 0) AS total_culling"). Scan(&agg).Error if err != nil { return 0, err } return agg.Total, nil } func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, 0, 0, nil } var agg struct { TotalWeight float64 `gorm:"column:total_weight"` TotalQty float64 `gorm:"column:total_qty"` TotalPrice float64 `gorm:"column:total_price"` } err := r.DB().WithContext(ctx). Table("marketing_products mp"). Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price"). Scan(&agg).Error if err != nil { return 0, 0, 0, err } return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil } func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) { if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 { return 0, 0, 0, nil } var agg struct { TotalWeight float64 `gorm:"column:total_weight"` TotalQty float64 `gorm:"column:total_qty"` TotalPrice float64 `gorm:"column:total_price"` } err := r.DB().WithContext(ctx). Table("marketing_products mp"). Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). Joins("JOIN products prod ON prod.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). Where("f.name IN ?", flagNames). Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price"). Scan(&agg).Error if err != nil { return 0, 0, 0, err } return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil } func (r *ClosingRepositoryImpl) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) { if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 { return 0, nil } var agg struct { TotalQty float64 `gorm:"column:total_qty"` } err := r.DB().WithContext(ctx). Table("recording_eggs re"). Joins("JOIN product_warehouses pw ON pw.id = re.product_warehouse_id"). Joins("JOIN products prod ON prod.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). Where("f.name IN ?", flagNames). Select("COALESCE(SUM(re.qty), 0) AS total_qty"). Scan(&agg).Error if err != nil { return 0, err } return agg.TotalQty, nil } func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) { if fcrID == 0 { return []entity.FcrStandard{}, nil } var standards []entity.FcrStandard if err := r.DB().WithContext(ctx). Where("fcr_id = ?", fcrID). Order("weight ASC"). Find(&standards).Error; err != nil { return nil, err } return standards, nil } const ( sapronakIncomingPurchasesSQL = ` SELECT CAST(pi.id AS BIGINT) AS id, COALESCE(pi.received_date, '1970-01-01') AS sort_date, COALESCE(TO_CHAR(pi.received_date, 'DD-Mon-YYYY'), '') AS date_text, COALESCE(p.po_number, '') AS reference_number, 'Purchase' AS transaction_type, prod.name AS product_name, pc.name AS product_category, COALESCE(( SELECT string_agg(f.name, ' ') FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_sub_category, 'External Supplier' AS source_warehouse, w.name AS destination_warehouse, '' AS destination, pi.total_qty AS quantity, u.name AS unit, COALESCE(p.notes, '') AS notes FROM purchase_items pi JOIN purchases p ON p.id = pi.purchase_id JOIN products prod ON prod.id = pi.product_id JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id JOIN warehouses w ON w.id = pi.warehouse_id WHERE pi.warehouse_id IN ? ` sapronakIncomingTransfersSQL = ` SELECT CAST(st.id AS BIGINT) AS id, st.transfer_date AS sort_date, TO_CHAR(st.transfer_date, 'DD-Mon-YYYY') AS date_text, st.movement_number AS reference_number, 'Internal Transfer In' AS transaction_type, prod.name AS product_name, pc.name AS product_category, COALESCE(( SELECT string_agg(f.name, ' ') FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_sub_category, COALESCE(fw.name, '') AS source_warehouse, COALESCE(tw.name, '') AS destination_warehouse, '' AS destination, std.quantity AS quantity, u.name AS unit, 'Stock Refill' AS notes FROM stock_transfer_details std JOIN stock_transfers st ON st.id = std.stock_transfer_id LEFT JOIN warehouses fw ON fw.id = st.from_warehouse_id LEFT JOIN warehouses tw ON tw.id = st.to_warehouse_id JOIN products prod ON prod.id = std.product_id JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id WHERE st.to_warehouse_id IN ? ` sapronakOutgoingTransfersSQL = ` SELECT CAST(st.id AS BIGINT) AS id, st.transfer_date AS sort_date, TO_CHAR(st.transfer_date, 'DD-Mon-YYYY') AS date_text, st.movement_number AS reference_number, 'Internal Transfer Out' AS transaction_type, prod.name AS product_name, pc.name AS product_category, COALESCE(( SELECT string_agg(f.name, ' ') FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_sub_category, COALESCE(fw.name, '') AS source_warehouse, '' AS destination_warehouse, COALESCE(tw.name, '') AS destination, std.quantity AS quantity, u.name AS unit, 'Transfer to other unit' AS notes FROM stock_transfer_details std JOIN stock_transfers st ON st.id = std.stock_transfer_id LEFT JOIN warehouses fw ON fw.id = st.from_warehouse_id LEFT JOIN warehouses tw ON tw.id = st.to_warehouse_id JOIN products prod ON prod.id = std.product_id JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id WHERE st.from_warehouse_id IN ? ` sapronakOutgoingMarketingsSQL = ` SELECT CAST(mp.id AS BIGINT) AS id, m.so_date AS sort_date, TO_CHAR(m.so_date, 'DD-Mon-YYYY') AS date_text, m.so_number AS reference_number, 'Trading Sales' AS transaction_type, prod.name AS product_name, pc.name AS product_category, COALESCE(( SELECT string_agg(f.name, ' ') FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_sub_category, w.name AS source_warehouse, '' AS destination_warehouse, 'RETAIL CUSTOMER' AS destination, mp.qty AS quantity, u.name AS unit, m.notes AS notes FROM marketing_products mp JOIN marketings m ON m.id = mp.marketing_id JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id JOIN products prod ON prod.id = pw.product_id JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id JOIN warehouses w ON w.id = pw.warehouse_id WHERE pw.project_flock_kandang_id IN ? ` )