diff --git a/internal/modules/dashboards/module.go b/internal/modules/dashboards/module.go index 24574dc7..d7d0d477 100644 --- a/internal/modules/dashboards/module.go +++ b/internal/modules/dashboards/module.go @@ -5,6 +5,8 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + commonService "gitlab.com/mbugroup/lti-api.git/internal/common/service" rDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories" sDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/services" @@ -16,11 +18,12 @@ type DashboardModule struct{} func (DashboardModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { dashboardRepo := rDashboard.NewDashboardRepository(db) + hppCostRepo := commonRepo.NewHppCostRepository(db) userRepo := rUser.NewUserRepository(db) - dashboardService := sDashboard.NewDashboardService(dashboardRepo, validate) + hppSvc := commonService.NewHppService(hppCostRepo) + dashboardService := sDashboard.NewDashboardService(dashboardRepo, validate, hppSvc) userService := sUser.NewUserService(userRepo, validate) DashboardRoutes(router, userService, dashboardService) } - diff --git a/internal/modules/dashboards/repositories/dashboard.repository.go b/internal/modules/dashboards/repositories/dashboard.repository.go index 90ee3bf8..eb9f6060 100644 --- a/internal/modules/dashboards/repositories/dashboard.repository.go +++ b/internal/modules/dashboards/repositories/dashboard.repository.go @@ -21,6 +21,7 @@ type DashboardRepository interface { SumSellingPrice(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (SellingPriceAggregate, error) SumEggProductionWeightGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) SumEggProductionWeightKg(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) + ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error) GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error) GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error) GetStandardWeeklyMetrics(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyMetric, error) diff --git a/internal/modules/dashboards/repositories/dashboard_stats.repository.go b/internal/modules/dashboards/repositories/dashboard_stats.repository.go index 828dd96c..493851e5 100644 --- a/internal/modules/dashboards/repositories/dashboard_stats.repository.go +++ b/internal/modules/dashboards/repositories/dashboard_stats.repository.go @@ -309,6 +309,27 @@ func (r *DashboardRepositoryImpl) SumEggProductionWeightKg(ctx context.Context, 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 @@ -553,7 +574,7 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context var rows []ComparisonWeeklyMetric db := r.DB().WithContext(ctx). Table("recordings AS r"). - Select(fmt.Sprintf(`((r.day - 1) / 7 + 1) AS week, + Select(fmt.Sprintf(`(CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 ELSE ((r.day - 1) / 7 + 1) END) 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"). @@ -561,8 +582,7 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context 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") + Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) diff --git a/internal/modules/dashboards/services/dashboard.service.go b/internal/modules/dashboards/services/dashboard.service.go index b4635b2e..ce2cc0f8 100644 --- a/internal/modules/dashboards/services/dashboard.service.go +++ b/internal/modules/dashboards/services/dashboard.service.go @@ -10,6 +10,7 @@ import ( "strings" "time" + commonService "gitlab.com/mbugroup/lti-api.git/internal/common/service" "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/dto" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations" @@ -27,13 +28,15 @@ type dashboardService struct { Log *logrus.Logger Validate *validator.Validate Repository repository.DashboardRepository + HppSvc commonService.HppService } -func NewDashboardService(repo repository.DashboardRepository, validate *validator.Validate) DashboardService { +func NewDashboardService(repo repository.DashboardRepository, validate *validator.Validate, hppSvc commonService.HppService) DashboardService { return &dashboardService{ Log: utils.Log, Validate: validate, Repository: repo, + HppSvc: hppSvc, } } @@ -592,13 +595,13 @@ func buildAggregateComparisonPercent(weeks []int, seriesRows []repository.Compar count++ } - if count == 0 { - continue - } - if result[week] == nil { result[week] = map[uint]float64{} } + if count == 0 { + result[week][series.Id] = 0 + continue + } result[week][series.Id] = sum / count } } @@ -846,6 +849,21 @@ func percentDelta(current, last float64) float64 { } func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, endExclusive, endDate time.Time, location *time.Location) (float64, float64, error) { + if s.HppSvc != nil { + currentHpp, err := s.hppGlobalForPeriod(ctx, startDate, endExclusive) + if err != nil { + return 0, 0, err + } + + lastMonthStart, lastMonthEndExclusive := monthRange(endDate.AddDate(0, -1, 0), location) + lastHpp, err := s.hppGlobalForPeriod(ctx, lastMonthStart, lastMonthEndExclusive) + if err != nil { + return 0, 0, err + } + + return currentHpp, lastHpp, nil + } + totalEggKg, err := s.Repository.SumEggProductionWeightKg(ctx, startDate, endExclusive, nil) if err != nil { return 0, 0, err @@ -878,6 +896,37 @@ func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, end return hppCurrent, hppLast, nil } +func (s dashboardService) hppGlobalForPeriod(ctx context.Context, startDate, endExclusive time.Time) (float64, error) { + kandangIDs, err := s.Repository.ListProjectFlockKandangIDsByEggProduction(ctx, startDate, endExclusive, nil) + if err != nil { + return 0, err + } + if len(kandangIDs) == 0 { + return 0, nil + } + + endOfPeriod := endExclusive.Add(-time.Nanosecond) + totalCost := 0.0 + totalWeightKg := 0.0 + for _, kandangID := range kandangIDs { + hppCost, err := s.HppSvc.CalculateHppCost(kandangID, &endOfPeriod) + if err != nil { + return 0, err + } + if hppCost == nil { + continue + } + totalCost += hppCost.Estimation.Total + totalWeightKg += hppCost.Estimation.Kg + } + + if totalWeightKg <= 0 { + return 0, nil + } + + return totalCost / totalWeightKg, nil +} + func (s dashboardService) calculateSellingPrice(ctx context.Context, endDate time.Time, location *time.Location) (float64, float64, error) { startPrevMonth, endPrevMonthExclusive := monthRange(endDate.AddDate(0, -1, 0), location) currentEndExclusive := endDate.AddDate(0, 0, 1) diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index d490185d..25103c2f 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -287,6 +287,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent } mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions) + depletionDesired := resetDepletionQuantitiesForFIFO(mappedDepletions, s.FifoSvc != nil) if s.FifoSvc != nil && len(mappedDepletions) > 0 { sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, req.ProjectFlockKandangId) if err != nil { @@ -301,6 +302,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent return err } if s.FifoSvc != nil { + applyDepletionDesiredQuantities(mappedDepletions, depletionDesired, true) note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id) if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil { return err @@ -465,6 +467,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin } mappedDepletions := recordingutil.MapDepletions(recordingEntity.Id, req.Depletions) + depletionDesired := resetDepletionQuantitiesForFIFO(mappedDepletions, s.FifoSvc != nil) if s.FifoSvc != nil && len(mappedDepletions) > 0 { sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, recordingEntity.ProjectFlockKandangId) if err != nil { @@ -480,6 +483,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin } if s.FifoSvc != nil { + applyDepletionDesiredQuantities(mappedDepletions, depletionDesired, true) note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id) if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil { return err @@ -929,6 +933,9 @@ func (s *recordingService) consumeRecordingDepletions( destDelta := depletion.Qty + depletion.PendingQty if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 { + if depletion.ProductWarehouseId == sourceWarehouseID { + continue + } log := &entity.StockLog{ ProductWarehouseId: depletion.ProductWarehouseId, CreatedBy: actorID, @@ -1066,6 +1073,9 @@ func (s *recordingService) releaseRecordingDepletions( destDelta := depletion.Qty + depletion.PendingQty if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 { + if depletion.ProductWarehouseId == sourceWarehouseID { + continue + } log := &entity.StockLog{ ProductWarehouseId: depletion.ProductWarehouseId, CreatedBy: actorID, @@ -1235,6 +1245,11 @@ type desiredStock struct { Pending float64 } +type desiredDepletion struct { + Qty float64 + Pending float64 +} + func resetStockQuantitiesForFIFO(stocks []entity.RecordingStock, enabled bool) []desiredStock { desired := make([]desiredStock, len(stocks)) for i := range stocks { @@ -1269,6 +1284,33 @@ func applyStockDesiredQuantities(stocks []entity.RecordingStock, desired []desir } } +func resetDepletionQuantitiesForFIFO(depletions []entity.RecordingDepletion, enabled bool) []desiredDepletion { + desired := make([]desiredDepletion, len(depletions)) + for i := range depletions { + desired[i].Qty = depletions[i].Qty + desired[i].Pending = depletions[i].PendingQty + if !enabled { + continue + } + depletions[i].Qty = 0 + depletions[i].PendingQty = 0 + } + return desired +} + +func applyDepletionDesiredQuantities(depletions []entity.RecordingDepletion, desired []desiredDepletion, enabled bool) { + if !enabled { + return + } + for i := range depletions { + if i >= len(desired) { + break + } + depletions[i].Qty = desired[i].Qty + depletions[i].PendingQty = desired[i].Pending + } +} + func (s *recordingService) syncRecordingStocks( ctx context.Context, tx *gorm.DB,