package repository import ( "context" "fmt" "strings" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) type SellingPriceAggregate struct { TotalPrice float64 TotalWeight float64 } type FeedUsageByUom struct { TotalQty float64 UomName string } type RecordingWeeklyMetric struct { Week int HenDay float64 EggWeight float64 FeedIntake float64 FcrValue float64 CumDepletionRate float64 } type UniformityWeeklyMetric struct { Week int Uniformity float64 AverageWeight float64 } type StandardWeeklyMetric struct { Week int StdLaying float64 StdEggWeight float64 StdFeedIntake float64 StdUniformity float64 StdDepletion float64 StdBodyWeight float64 } type StandardWeeklyFcrMetric struct { Week int StdFcr float64 } type ComparisonSeries struct { Id uint Label string } type ComparisonWeeklyMetric struct { Week int SeriesId uint Value float64 } type ComparisonUniformityMetric struct { Week int SeriesId uint Uniformity float64 AverageWeight float64 } type EggQualityWeeklyMetric struct { Week int NormalQty float64 AbnormalQty float64 TotalQty float64 } type WeeklyEggWeightMetric struct { Week int EggWeightGrams float64 } type WeeklyFeedUsageMetric struct { Week int TotalQty float64 UomName string } func applyDashboardFilters(db *gorm.DB, filters *validation.DashboardFilter) *gorm.DB { if filters == nil { return db } if len(filters.FlockIds) > 0 { db = db.Where("pfk.project_flock_id IN ?", filters.FlockIds) } if len(filters.KandangIds) > 0 { db = db.Where("k.id IN ?", filters.KandangIds) } if len(filters.LokasiIds) > 0 { db = db.Where("k.location_id IN ?", filters.LokasiIds) } return db } func (r *DashboardRepositoryImpl) GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error) { var rows []RecordingWeeklyMetric weekExpr := `CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17 ELSE ((r.day - 1) / 7 + 1) END` db := r.DB().WithContext(ctx). Table("recordings AS r"). Select(fmt.Sprintf(`%s AS week, COALESCE(AVG(r.hen_day), 0) AS hen_day, COALESCE(AVG(r.egg_weight), 0) AS egg_weight, COALESCE(AVG(r.feed_intake), 0) AS feed_intake, COALESCE(AVG(r.fcr_value), 0) AS fcr_value, COALESCE(AVG(r.cum_depletion_rate), 0) AS cum_depletion_rate`, weekExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL"). Where("r.day IS NOT NULL AND r.day > 0") db = applyDashboardFilters(db, filters) if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error) { var rows []UniformityWeeklyMetric db := r.DB().WithContext(ctx). Table("project_flock_kandang_uniformity AS u"). Select(`u.week AS week, COALESCE(AVG(u.uniformity), 0) AS uniformity, COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("u.uniform_date IS NOT NULL"). Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end) db = applyDashboardFilters(db, filters) if err := db.Group("u.week").Order("u.week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetStandardWeeklyMetrics(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyMetric, error) { if len(weeks) == 0 { return nil, nil } standardIDs := r.standardIDSubquery(filters) if standardIDs == nil { return nil, nil } var rows []StandardWeeklyMetric db := r.DB().WithContext(ctx). Table("standard_growth_details AS sgd"). Select(`sgd.week AS week, COALESCE(AVG(psd.target_hen_day_production), 0) AS std_laying, COALESCE(AVG(psd.target_egg_weight), 0) AS std_egg_weight, COALESCE(AVG(sgd.feed_intake), 0) AS std_feed_intake, COALESCE(AVG(sgd.min_uniformity), 0) AS std_uniformity, COALESCE(AVG(sgd.max_depletion), 0) AS std_depletion, COALESCE(AVG(sgd.target_mean_bw), 0) AS std_body_weight`). Joins("LEFT JOIN production_standard_details AS psd ON psd.production_standard_id = sgd.production_standard_id AND psd.week = sgd.week"). Where("sgd.week IN ?", weeks). Where("sgd.production_standard_id IN (?)", standardIDs) if err := db.Group("sgd.week").Order("sgd.week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetStandardFcrWeekly(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyFcrMetric, error) { if len(weeks) == 0 { return nil, nil } standardIDs := r.standardIDSubquery(filters) if standardIDs == nil { return nil, nil } var rows []StandardWeeklyFcrMetric db := r.DB().WithContext(ctx). Table("production_standard_details AS psd"). Select("psd.week AS week, COALESCE(AVG(psd.standard_fcr), 0) AS std_fcr"). Where("psd.week IN ?", weeks). Where("psd.production_standard_id IN (?)", standardIDs) if err := db.Group("psd.week").Order("psd.week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) SumEggProductionWeightGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) { var total float64 db := r.DB().WithContext(ctx). Table("recording_eggs AS re"). Select("COALESCE(SUM(re.weight * 1000), 0)"). Joins("JOIN recordings AS r ON r.id = re.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) if err := db.Scan(&total).Error; err != nil { return 0, err } return total, nil } func (r *DashboardRepositoryImpl) SumEggProductionWeightKg(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) { grams, err := r.SumEggProductionWeightGrams(ctx, start, end, filters) if err != nil { return 0, err } return grams / 1000, nil } func (r *DashboardRepositoryImpl) ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error) { var ids []uint db := r.DB().WithContext(ctx). Table("recording_eggs AS re"). Select("DISTINCT r.project_flock_kandangs_id"). Joins("JOIN recordings AS r ON r.id = re.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) if err := db.Scan(&ids).Error; err != nil { return nil, err } return ids, nil } func (r *DashboardRepositoryImpl) GetFeedUsageByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]FeedUsageByUom, error) { var rows []FeedUsageByUom db := r.DB().WithContext(ctx). Table("recording_stocks AS rs"). Select("COALESCE(SUM(rs.usage_qty), 0) + COALESCE(SUM(rs.pending_qty), 0) AS total_qty, LOWER(uoms.name) AS uom_name"). Joins("JOIN recordings AS r ON r.id = rs.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id"). Joins("JOIN products AS p ON p.id = pw.product_id"). Joins("JOIN uoms ON uoms.id = p.uom_id"). Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ? AND UPPER(f.name) = ?", entity.FlagableTypeProduct, "PAKAN"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) if err := db.Group("LOWER(uoms.name)").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) SumDepletions(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) { var total float64 db := r.DB().WithContext(ctx). Table("recording_depletions AS rd"). Select("COALESCE(SUM(rd.qty), 0)"). Joins("JOIN recordings AS r ON r.id = rd.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) if err := db.Scan(&total).Error; err != nil { return 0, err } return total, nil } func (r *DashboardRepositoryImpl) SumInitialPopulation(ctx context.Context, endDate time.Time, filters *validation.DashboardFilter) (float64, error) { var total float64 endOfDate := endDate.AddDate(0, 0, 1) db := r.DB().WithContext(ctx). Table("project_chickins AS pc"). Select("COALESCE(SUM(pc.usage_qty), 0)"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("pc.chick_in_date < ?", endOfDate). Where("pc.deleted_at IS NULL") db = applyDashboardFilters(db, filters) if err := db.Scan(&total).Error; err != nil { return 0, err } return total, nil } func (r *DashboardRepositoryImpl) SumSellingPrice(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (SellingPriceAggregate, error) { var result SellingPriceAggregate db := r.DB().WithContext(ctx). Table("marketing_delivery_products AS mdp"). Select("COALESCE(SUM(mdp.total_price), 0) AS total_price, COALESCE(SUM(mdp.total_weight), 0) AS total_weight"). Joins("JOIN marketing_products AS mp ON mp.id = mdp.marketing_product_id"). Joins("JOIN product_warehouses AS pw ON pw.id = mp.product_warehouse_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pw.project_flock_kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("mdp.delivery_date IS NOT NULL"). Where("mdp.delivery_date >= ? AND mdp.delivery_date < ?", start, end) db = applyDashboardFilters(db, filters) if err := db.Scan(&result).Error; err != nil { return SellingPriceAggregate{}, err } return result, nil } func (r *DashboardRepositoryImpl) SumSapronakCost(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) { var total float64 db := r.DB().WithContext(ctx). Table("purchase_items AS pi"). Select("COALESCE(SUM(pi.total_price), 0) AS total"). Joins("JOIN products AS p ON p.id = pi.product_id"). Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("LEFT JOIN product_warehouses AS pw ON pw.id = pi.product_warehouse_id"). Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.id = COALESCE(pi.project_flock_kandang_id, pw.project_flock_kandang_id)"). Joins("LEFT JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("f.name IN ?", []utils.FlagType{utils.FlagDOC, utils.FlagPakan, utils.FlagOVK}). Where("pi.received_date IS NOT NULL"). Where("pi.received_date >= ? AND pi.received_date < ?", start, end) db = applyDashboardFilters(db, filters) if err := db.Scan(&total).Error; err != nil { return 0, err } return total, nil } func (r *DashboardRepositoryImpl) SumBopCost(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) { return r.sumExpenseRealization(ctx, start, end, filters, func(db *gorm.DB) *gorm.DB { return db. Where("e.category = ?", utils.ExpenseCategoryBOP). Joins("LEFT JOIN nonstocks AS n ON n.id = en.nonstock_id"). Joins("LEFT JOIN flags AS f ON f.flagable_id = n.id AND f.flagable_type = ? AND f.name = ?", entity.FlagableTypeNonstock, utils.FlagEkspedisi). Where("f.id IS NULL") }) } func (r *DashboardRepositoryImpl) SumEkspedisiCost(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) { return r.sumExpenseRealization(ctx, start, end, filters, func(db *gorm.DB) *gorm.DB { return db. Joins("JOIN nonstocks AS n ON n.id = en.nonstock_id"). Joins("JOIN flags AS f ON f.flagable_id = n.id AND f.flagable_type = ?", entity.FlagableTypeNonstock). Where("f.name = ?", utils.FlagEkspedisi) }) } func (r *DashboardRepositoryImpl) sumExpenseRealization(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, modifier func(*gorm.DB) *gorm.DB) (float64, error) { var total float64 db := r.DB().WithContext(ctx). Table("expense_realizations AS er"). Select("COALESCE(SUM(er.qty * er.price), 0) AS total"). Joins("JOIN expense_nonstocks AS en ON en.id = er.expense_nonstock_id"). Joins("JOIN expenses AS e ON e.id = en.expense_id"). Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.id = en.project_flock_kandang_id"). Joins("LEFT JOIN kandangs AS k ON k.id = COALESCE(en.kandang_id, pfk.kandang_id)"). Where("e.realization_date >= ? AND e.realization_date < ?", start, end) db = applyDashboardFilters(db, filters) if modifier != nil { db = modifier(db) } if err := db.Scan(&total).Error; err != nil { return 0, err } return total, nil } func (r *DashboardRepositoryImpl) standardIDSubquery(filters *validation.DashboardFilter) *gorm.DB { db := r.DB(). Table("project_flocks AS pf"). Select("DISTINCT pf.production_standard_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Where("pf.production_standard_id > 0") if filters != nil { if len(filters.FlockIds) > 0 { db = db.Where("pf.id IN ?", filters.FlockIds) } if len(filters.KandangIds) > 0 { db = db.Where("k.id IN ?", filters.KandangIds) } if len(filters.LokasiIds) > 0 { db = db.Where("k.location_id IN ?", filters.LokasiIds) } } return db } func (r *DashboardRepositoryImpl) GetComparisonSeries(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType string) ([]ComparisonSeries, error) { seriesExpr, labelExpr, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType) if err != nil { return nil, err } var rows []ComparisonSeries db := r.DB().WithContext(ctx). Table("recordings AS r"). Select(fmt.Sprintf("%s AS id, %s AS label", seriesExpr, labelExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) if err := db.Group(groupExpr).Order(orderExpr).Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType, metric string) ([]ComparisonWeeklyMetric, error) { seriesExpr, _, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType) if err != nil { return nil, err } metricExpr, err := comparisonMetricColumn(metric) if err != nil { return nil, err } var rows []ComparisonWeeklyMetric weekExpr := `CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17 ELSE ((r.day - 1) / 7 + 1) END` db := r.DB().WithContext(ctx). Table("recordings AS r"). Select(fmt.Sprintf(`%s AS week, %s AS series_id, COALESCE(AVG(%s), 0) AS value`, weekExpr, seriesExpr, metricExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) groupBy := fmt.Sprintf("week, %s", groupExpr) orderBy := fmt.Sprintf("week ASC, %s", orderExpr) if err := db.Group(groupBy).Order(orderBy).Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetComparisonWeeklyUniformityMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType string) ([]ComparisonUniformityMetric, error) { seriesExpr, _, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType) if err != nil { return nil, err } var rows []ComparisonUniformityMetric db := r.DB().WithContext(ctx). Table("project_flock_kandang_uniformity AS u"). Select(fmt.Sprintf(`u.week AS week, %s AS series_id, COALESCE(AVG(u.uniformity), 0) AS uniformity, COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`, seriesExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id"). Where("u.uniform_date IS NOT NULL"). Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end) db = applyDashboardFilters(db, filters) groupBy := fmt.Sprintf("u.week, %s", groupExpr) orderBy := fmt.Sprintf("u.week ASC, %s", orderExpr) if err := db.Group(groupBy).Order(orderBy).Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetEggQualityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]EggQualityWeeklyMetric, error) { var rows []EggQualityWeeklyMetric weekExpr := `CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17 ELSE ((r.day - 1) / 7 + 1) END` db := r.DB().WithContext(ctx). Table("recording_eggs AS re"). Select(fmt.Sprintf(` %s AS week, COALESCE(SUM(CASE WHEN f.name = ? THEN re.qty ELSE 0 END), 0) AS normal_qty, COALESCE(SUM(CASE WHEN f.name IN (?, ?, ?) THEN re.qty ELSE 0 END), 0) AS abnormal_qty, COALESCE(SUM(re.qty), 0) AS total_qty`, weekExpr), utils.FlagTelurUtuh, utils.FlagTelurPutih, utils.FlagTelurRetak, utils.FlagTelurPecah, ). Joins("JOIN recordings AS r ON r.id = re.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN product_warehouses AS pw ON pw.id = re.product_warehouse_id"). Joins("JOIN products AS p ON p.id = pw.product_id"). Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Where("f.name IN ?", []utils.FlagType{utils.FlagTelurUtuh, utils.FlagTelurPutih, utils.FlagTelurRetak, utils.FlagTelurPecah}). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL"). Where("r.day IS NOT NULL AND r.day > 0") db = applyDashboardFilters(db, filters) if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetEggWeightWeeklyGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]WeeklyEggWeightMetric, error) { var rows []WeeklyEggWeightMetric weekExpr := `CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17 ELSE ((r.day - 1) / 7 + 1) END` db := r.DB().WithContext(ctx). Table("recording_eggs AS re"). Select(fmt.Sprintf(` %s AS week, COALESCE(SUM(re.weight * 1000), 0) AS egg_weight_grams`, weekExpr)). Joins("JOIN recordings AS r ON r.id = re.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL"). Where("r.day IS NOT NULL AND r.day > 0") db = applyDashboardFilters(db, filters) if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *DashboardRepositoryImpl) GetFeedUsageWeeklyByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]WeeklyFeedUsageMetric, error) { var rows []WeeklyFeedUsageMetric weekExpr := `CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17 ELSE ((r.day - 1) / 7 + 1) END` db := r.DB().WithContext(ctx). Table("recording_stocks AS rs"). Select(fmt.Sprintf(` %s AS week, COALESCE(SUM(rs.usage_qty), 0) + COALESCE(SUM(rs.pending_qty), 0) AS total_qty, LOWER(uoms.name) AS uom_name`, weekExpr)). Joins("JOIN recordings AS r ON r.id = rs.recording_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id"). Joins("JOIN products AS p ON p.id = pw.product_id"). Joins("JOIN uoms ON uoms.id = p.uom_id"). Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ? AND UPPER(f.name) = ?", entity.FlagableTypeProduct, "PAKAN"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.deleted_at IS NULL"). Where("r.day IS NOT NULL AND r.day > 0") db = applyDashboardFilters(db, filters) if err := db.Group("week, LOWER(uoms.name)").Order("week ASC").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func comparisonSeriesColumns(comparisonType string) (string, string, string, string, error) { switch strings.ToUpper(strings.TrimSpace(comparisonType)) { case validation.ComparisonTypeFarm: return "loc.id", "loc.name", "loc.id, loc.name", "loc.name", nil case validation.ComparisonTypeFlock: return "pf.id", "pf.flock_name", "pf.id, pf.flock_name", "pf.flock_name", nil case validation.ComparisonTypeKandang: return "k.id", "k.name", "k.id, k.name", "k.name", nil default: return "", "", "", "", fmt.Errorf("invalid comparison_type") } } func comparisonMetricColumn(metric string) (string, error) { switch strings.ToLower(strings.TrimSpace(metric)) { case validation.MetricFcr: return "r.fcr_value", nil case validation.MetricMortality: return "r.cum_depletion_rate", nil case validation.MetricLaying: return "r.hen_day", nil case validation.MetricEggWeight: return "r.egg_weight", nil case validation.MetricFeedIntake: return "r.feed_intake", nil default: return "", fmt.Errorf("invalid metric") } }