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) SumProjectChickinUsageByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (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) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error) FetchSapronakUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error) FetchSapronakUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error) FetchSapronakChickinUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error) FetchSapronakChickinUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error) FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, 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 } 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, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, f.name ) FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_category, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, f.name ) FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_sub_category, '-' 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 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, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, f.name ) FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_category, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, 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.usage_qty 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 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, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, f.name ) FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_category, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, 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.usage_qty 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 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, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, f.name ) FROM flags f WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id ), '') AS product_category, COALESCE(( SELECT string_agg( f.name, ' ' ORDER BY CASE WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0 ELSE 1 END, 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 uoms u ON u.id = prod.uom_id JOIN warehouses w ON w.id = pw.warehouse_id WHERE pw.project_flock_kandang_id IN ? ` ) type SapronakIncomingRow struct { ProductID uint ProductName string Flag string Qty float64 Value float64 DefaultPrice float64 } type SapronakUsageRow struct { ProductID uint ProductName string Flag string Qty float64 DefaultPrice float64 } type SapronakDetailRow struct { ProductID uint ProductName string Flag string Date *time.Time Reference string QtyIn float64 QtyOut float64 Price float64 } func (r *ClosingRepositoryImpl) withCtx(ctx context.Context) *gorm.DB { return r.DB().WithContext(ctx) } func applyJoins(db *gorm.DB, joins ...string) *gorm.DB { for _, j := range joins { if strings.TrimSpace(j) != "" { db = db.Joins(j) } } return db } func sapronakFlags(flags ...utils.FlagType) []string { out := make([]string, len(flags)) for i, f := range flags { out[i] = string(f) } return out } var ( sapronakFlagsAll = sapronakFlags(utils.FlagDOC, utils.FlagPakan, utils.FlagOVK, utils.FlagPullet) sapronakFlagsUsage = sapronakFlags(utils.FlagPakan, utils.FlagOVK) sapronakFlagsChickin = sapronakFlags(utils.FlagDOC, utils.FlagPullet) ) func groupSapronakDetails(rows []SapronakDetailRow) map[uint][]SapronakDetailRow { m := make(map[uint][]SapronakDetailRow) for _, row := range rows { m[row.ProductID] = append(m[row.ProductID], row) } return m } func scanAndGroupDetails(db *gorm.DB) (map[uint][]SapronakDetailRow, error) { rows := make([]SapronakDetailRow, 0) if err := db.Scan(&rows).Error; err != nil { return nil, err } return groupSapronakDetails(rows), nil } // ========================= // Usage (summary + details) // ========================= func (r *ClosingRepositoryImpl) usageQuery( ctx context.Context, table string, pwJoinCond string, joins []string, where string, args ...any, ) *gorm.DB { db := r.withCtx(ctx).Table(table).Select(` pw.product_id AS product_id, p.name AS product_name, f.name AS flag, COALESCE(SUM(usage_qty), 0) AS qty, COALESCE(p.product_price, 0) AS default_price `) db = applyJoins(db, joins...) return db. Joins("JOIN product_warehouses pw ON "+pwJoinCond). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Where(where, args...) } func (r *ClosingRepositoryImpl) fetchSapronakUsage( ctx context.Context, table string, pwJoinCond string, joins []string, where string, args ...any, ) ([]SapronakUsageRow, error) { rows := make([]SapronakUsageRow, 0) db := r.usageQuery(ctx, table, pwJoinCond, joins, where, args...) if err := db.Group("pw.product_id, p.name, f.name, p.product_price").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *ClosingRepositoryImpl) detailQuery( ctx context.Context, table string, pwJoinCond string, joins []string, selectSQL string, where string, args ...any, ) *gorm.DB { db := r.withCtx(ctx). Table(table). Joins("JOIN product_warehouses pw ON "+pwJoinCond). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct) db = applyJoins(db, joins...) return db.Select(selectSQL).Where(where, args...) } func (r *ClosingRepositoryImpl) fetchSapronakDetails( ctx context.Context, table string, pwJoinCond string, joins []string, selectSQL string, where string, args ...any, ) (map[uint][]SapronakDetailRow, error) { return scanAndGroupDetails(r.detailQuery(ctx, table, pwJoinCond, joins, selectSQL, where, args...)) } func (r *ClosingRepositoryImpl) FetchSapronakUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error) { if pfkID == 0 { return nil, nil } return r.fetchSapronakUsage( ctx, "recording_stocks rs", "pw.id = rs.product_warehouse_id", []string{"JOIN recordings r ON r.id = rs.recording_id AND r.deleted_at IS NULL"}, "r.project_flock_kandangs_id = ? AND f.name IN ?", pfkID, sapronakFlagsUsage, ) } func (r *ClosingRepositoryImpl) FetchSapronakChickinUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error) { if pfkID == 0 { return []SapronakUsageRow{}, nil } return r.fetchSapronakUsage( ctx, "project_chickins pc", "pw.id = pc.product_warehouse_id", nil, "pc.project_flock_kandang_id = ? AND pc.usage_qty > 0 AND f.name IN ?", pfkID, sapronakFlagsChickin, ) } func (r *ClosingRepositoryImpl) FetchSapronakUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error) { return r.fetchSapronakDetails( ctx, "recording_stocks rs", "pw.id = rs.product_warehouse_id", []string{"JOIN recordings r ON r.id = rs.recording_id AND r.deleted_at IS NULL"}, // penting: supaya alias r valid ` pw.product_id AS product_id, p.name AS product_name, f.name AS flag, r.record_datetime AS date, CAST(r.id AS TEXT) AS reference, 0 AS qty_in, COALESCE(rs.usage_qty,0) AS qty_out, COALESCE(p.product_price,0) AS price `, "r.project_flock_kandangs_id = ? AND f.name IN ?", pfkID, sapronakFlagsUsage, ) } func (r *ClosingRepositoryImpl) FetchSapronakChickinUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error) { return r.fetchSapronakDetails( ctx, "project_chickins pc", "pw.id = pc.product_warehouse_id", nil, ` pw.product_id AS product_id, p.name AS product_name, f.name AS flag, pc.chick_in_date AS date, CAST(pc.id AS TEXT) AS reference, 0 AS qty_in, COALESCE(pc.usage_qty,0) AS qty_out, COALESCE(p.product_price,0) AS price `, "pc.project_flock_kandang_id = ? AND pc.usage_qty > 0 AND f.name IN ?", pfkID, sapronakFlagsChickin, ) } func (r *ClosingRepositoryImpl) incomingPurchaseBase(ctx context.Context, kandangID uint) *gorm.DB { return r.withCtx(ctx). Table("purchase_items AS pi"). Joins("JOIN purchases po ON po.id = pi.purchase_id AND po.deleted_at IS NULL"). Joins("JOIN products p ON p.id = pi.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("JOIN warehouses w ON w.id = pi.warehouse_id"). Where("w.kandang_id = ?", kandangID). Where("f.name IN ?", sapronakFlagsAll). Where("pi.received_date IS NOT NULL") } func (r *ClosingRepositoryImpl) FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error) { rows := make([]SapronakIncomingRow, 0) db := r.incomingPurchaseBase(ctx, kandangID).Select(` pi.product_id AS product_id, p.name AS product_name, f.name AS flag, COALESCE(SUM(pi.total_qty), 0) AS qty, COALESCE(SUM(pi.total_qty * pi.price), 0) AS value, COALESCE(p.product_price, 0) AS default_price `) if err := db.Group("pi.product_id, p.name, f.name, p.product_price").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *ClosingRepositoryImpl) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error) { return scanAndGroupDetails( r.incomingPurchaseBase(ctx, kandangID).Select(` pi.product_id AS product_id, p.name AS product_name, f.name AS flag, pi.received_date AS date, COALESCE(po.po_number, '') AS reference, COALESCE(pi.total_qty,0) AS qty_in, 0 AS qty_out, COALESCE(pi.price,0) AS price `), ) } type stockLogSapronakRow struct { ID uint `gorm:"column:id"` ProductID uint `gorm:"column:product_id"` ProductName string `gorm:"column:product_name"` Flag string `gorm:"column:flag"` CreatedAt *time.Time `gorm:"column:created_at"` Increase float64 `gorm:"column:increase"` Decrease float64 `gorm:"column:decrease"` Price float64 `gorm:"column:price"` MovementNumber string `gorm:"column:movement_number"` } func (r *ClosingRepositoryImpl) fetchStockLogs(ctx context.Context, kandangID uint, logType any, withMovement bool) ([]stockLogSapronakRow, error) { rows := make([]stockLogSapronakRow, 0) movementSelect := "'' AS movement_number" joins := []string{} if withMovement { movementSelect = "COALESCE(st.movement_number,'') AS movement_number" joins = append(joins, "JOIN stock_transfers st ON st.id = sl.loggable_id") } db := r.withCtx(ctx). Table("stock_logs sl"). Select(` sl.id AS id, pw.product_id AS product_id, p.name AS product_name, f.name AS flag, sl.created_at AS created_at, COALESCE(sl.increase,0) AS increase, COALESCE(sl.decrease,0) AS decrease, COALESCE(p.product_price,0) AS price, `+movementSelect+` `). Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id"). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("JOIN warehouses w ON w.id = pw.warehouse_id") db = applyJoins(db, joins...) if err := db. Where("sl.loggable_type = ?", logType). Where("w.kandang_id = ?", kandangID). Where("f.name IN ?", sapronakFlagsAll). Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func splitStockLogs(rows []stockLogSapronakRow, refFn func(stockLogSapronakRow) string) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow) { in := make(map[uint][]SapronakDetailRow) out := make(map[uint][]SapronakDetailRow) for _, row := range rows { base := SapronakDetailRow{ ProductID: row.ProductID, ProductName: row.ProductName, Flag: row.Flag, Date: row.CreatedAt, Reference: refFn(row), Price: row.Price, } if row.Increase > 0 { d := base d.QtyIn = row.Increase in[row.ProductID] = append(in[row.ProductID], d) } if row.Decrease > 0 { d := base d.QtyOut = row.Decrease out[row.ProductID] = append(out[row.ProductID], d) } } return in, out } func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeAdjustment), false) if err != nil { return nil, nil, err } in, out := splitStockLogs(rows, func(row stockLogSapronakRow) string { return fmt.Sprintf("ADJ-%d", row.ID) }) return in, out, nil } func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeTransfer), true) if err != nil { return nil, nil, err } in, out := splitStockLogs(rows, func(row stockLogSapronakRow) string { if ref := strings.TrimSpace(row.MovementNumber); ref != "" { return ref } return fmt.Sprintf("TRF-%d", row.ID) }) return in, out, nil } // === CLOSING DATA PRODUKSI METHODS === 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) SumProjectChickinUsageByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, nil } var total float64 if err := r.DB().WithContext(ctx). Model(&entity.ProjectChickin{}). Where("project_flock_kandang_id IN ?", projectFlockKandangIDs). Select("COALESCE(SUM(usage_qty), 0)"). Scan(&total).Error; err != nil { return 0, err } return total, 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 } func (r *ClosingRepositoryImpl) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) { if len(productIDs) == 0 { return []entity.Product{}, nil } var products []entity.Product err := r.DB().WithContext(ctx). Preload("Flags"). Where("id IN ?", productIDs). Find(&products).Error if err != nil { return nil, err } return products, nil }