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 db := r.DB().WithContext(ctx). Table("recordings AS r"). Select(`((r.day - 1) / 7 + 1) 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`). 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"). 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 } filterClause := "" filterArgs := make([]interface{}, 0) if filters != nil { if len(filters.FlockIds) > 0 { filterClause += " AND pf.id IN ?" filterArgs = append(filterArgs, filters.FlockIds) } if len(filters.KandangIds) > 0 { filterClause += " AND k.id IN ?" filterArgs = append(filterArgs, filters.KandangIds) } if len(filters.LokasiIds) > 0 { filterClause += " AND k.location_id IN ?" filterArgs = append(filterArgs, filters.LokasiIds) } } query := fmt.Sprintf(` WITH src AS ( SELECT DISTINCT pf.production_standard_id, pf.fcr_id FROM project_flocks pf JOIN project_flock_kandangs pfk ON pfk.project_flock_id = pf.id JOIN kandangs k ON k.id = pfk.kandang_id WHERE pf.production_standard_id > 0 AND pf.fcr_id > 0 %s ), actual AS ( SELECT u.week AS week, pf.fcr_id AS fcr_id, AVG((u.chart_data->'statistics'->>'average_weight')::numeric) AS avg_weight FROM project_flock_kandang_uniformity u JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id JOIN project_flocks pf ON pf.id = pfk.project_flock_id JOIN kandangs k ON k.id = pfk.kandang_id WHERE u.week IN ? AND u.uniform_date IS NOT NULL AND pf.fcr_id > 0 %s GROUP BY u.week, pf.fcr_id ), target AS ( SELECT sgd.week AS week, src.fcr_id AS fcr_id, AVG(sgd.target_mean_bw) AS target_mean_bw FROM standard_growth_details sgd JOIN src ON src.production_standard_id = sgd.production_standard_id WHERE sgd.week IN ? GROUP BY sgd.week, src.fcr_id ), weights AS ( SELECT COALESCE(a.week, t.week) AS week, COALESCE(a.fcr_id, t.fcr_id) AS fcr_id, COALESCE( CASE WHEN a.avg_weight > 10 THEN a.avg_weight / 1000 ELSE a.avg_weight END, CASE WHEN t.target_mean_bw > 10 THEN t.target_mean_bw / 1000 ELSE t.target_mean_bw END ) AS weight FROM actual a FULL OUTER JOIN target t ON t.week = a.week AND t.fcr_id = a.fcr_id ) SELECT w.week AS week, COALESCE(AVG( COALESCE( (SELECT fs.fcr_number FROM fcr_standards fs WHERE fs.fcr_id = w.fcr_id AND fs.weight >= w.weight ORDER BY fs.weight ASC LIMIT 1), (SELECT fs.fcr_number FROM fcr_standards fs WHERE fs.fcr_id = w.fcr_id ORDER BY fs.weight DESC LIMIT 1) ) ), 0) AS std_fcr FROM weights w GROUP BY w.week ORDER BY w.week ASC `, filterClause, filterClause) args := make([]interface{}, 0, len(filterArgs)*2+2) args = append(args, filterArgs...) args = append(args, weeks) args = append(args, filterArgs...) args = append(args, weeks) var rows []StandardWeeklyFcrMetric if err := r.DB().WithContext(ctx).Raw(query, args...).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.qty * re.weight), 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) 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) standardSourceSubquery(filters *validation.DashboardFilter) *gorm.DB { db := r.DB(). Table("project_flocks AS pf"). Select("DISTINCT pf.production_standard_id, pf.fcr_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"). Where("pf.fcr_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 db := r.DB().WithContext(ctx). Table("recordings AS r"). Select(fmt.Sprintf(`((r.day - 1) / 7 + 1) AS week, %s AS series_id, COALESCE(AVG(%s), 0) AS value`, 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"). Where("r.day IS NOT NULL AND r.day > 0") 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 db := r.DB().WithContext(ctx). Table("recording_eggs AS re"). Select(` ((r.day - 1) / 7 + 1) 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`, 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 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 db := r.DB().WithContext(ctx). Table("recording_eggs AS re"). Select(` ((r.day - 1) / 7 + 1) AS week, COALESCE(SUM(re.qty * re.weight), 0) AS egg_weight_grams`). 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"). 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 db := r.DB().WithContext(ctx). Table("recording_stocks AS rs"). Select(` ((r.day - 1) / 7 + 1) AS week, 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"). 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") } }