diff --git a/internal/modules/master/production-standards/services/production-standard.service.go b/internal/modules/master/production-standards/services/production-standard.service.go index e1470170..2ea95cf3 100644 --- a/internal/modules/master/production-standards/services/production-standard.service.go +++ b/internal/modules/master/production-standards/services/production-standard.service.go @@ -1,8 +1,10 @@ package service import ( + "context" "errors" "fmt" + "strings" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" m "gitlab.com/mbugroup/lti-api.git/internal/middleware" @@ -22,6 +24,8 @@ type ProductionStandardService interface { CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProductionStandard, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProductionStandard, error) DeleteOne(ctx *fiber.Ctx, id uint) error + EnsureWeekStart(ctx context.Context, standardID uint, category string) error + EnsureWeekAvailable(ctx context.Context, standardID uint, category string, day int) error } type productionStandardService struct { @@ -299,3 +303,80 @@ func (s productionStandardService) DeleteOne(c *fiber.Ctx, id uint) error { } return nil } + +func (s productionStandardService) EnsureWeekStart(ctx context.Context, standardID uint, category string) error { + if standardID == 0 || strings.TrimSpace(category) == "" { + return nil + } + + switch strings.ToUpper(category) { + case string(utils.ProjectFlockCategoryLaying): + details, err := s.ProductionStandardDetailRepo.GetByProductionStandardID(ctx, standardID) + if err != nil { + return err + } + startWeek := 0 + if len(details) > 0 { + startWeek = details[0].Week + } + if startWeek != 18 { + return fiber.NewError(fiber.StatusBadRequest, "Week tidak sesuai dengan standart kategori project flock") + } + case string(utils.ProjectFlockCategoryGrowing): + details, err := s.StandardGrowthDetailRepo.GetByProductionStandardID(ctx, standardID) + if err != nil { + return err + } + startWeek := 0 + if len(details) > 0 { + startWeek = details[0].Week + } + if startWeek != 1 { + return fiber.NewError(fiber.StatusBadRequest, "Week tidak sesuai dengan standart kategori project flock") + } + } + + return nil +} + +func (s productionStandardService) EnsureWeekAvailable(ctx context.Context, standardID uint, category string, day int) error { + if standardID == 0 || day <= 0 { + return nil + } + + upperCategory := strings.ToUpper(category) + weekBase := 1 + if upperCategory == string(utils.ProjectFlockCategoryLaying) { + weekBase = 18 + } + week := ((day - 1) / 7) + weekBase + if week <= 0 { + return nil + } + + if upperCategory == string(utils.ProjectFlockCategoryLaying) { + detail, err := s.ProductionStandardDetailRepo.GetByStandardIDAndWeek(ctx, standardID, week) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Standart production tidak tersedia untuk week %d", week)) + } + return err + } + if detail == nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Standart production tidak tersedia untuk week %d", week)) + } + } + + growthDetail, err := s.StandardGrowthDetailRepo.GetByStandardIDAndWeek(ctx, standardID, week) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Standart production tidak tersedia untuk week %d", week)) + } + return err + } + if growthDetail == nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Standart production tidak tersedia untuk week %d", week)) + } + + return nil +} diff --git a/internal/modules/production/recordings/dto/recording.dto.go b/internal/modules/production/recordings/dto/recording.dto.go index f5a04821..4deb9f7e 100644 --- a/internal/modules/production/recordings/dto/recording.dto.go +++ b/internal/modules/production/recordings/dto/recording.dto.go @@ -14,44 +14,83 @@ import ( // === DTO Structs === +type RecordingProjectFlockDTO struct { + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` + FlockName string `json:"flock_name"` + ProjectFlockCategory string `json:"project_flock_category"` + Period int `json:"period"` + ProductionStandart *RecordingProductionStandardDTO `json:"production_standart,omitempty"` + Fcr *RecordingFcrDTO `json:"fcr,omitempty"` + TotalChickQty float64 `json:"total_chick_qty"` +} + +type RecordingProductionStandardDTO struct { + Id uint `json:"id"` + Week int `json:"week"` + Name string `json:"name"` + HenDayStd float64 `json:"hen_day_std"` + HenHouseStd float64 `json:"hen_house_std"` + FeedIntakeStd float64 `json:"feed_intake_std"` + MaxDepletionStd float64 `json:"max_depletion_std"` + EggMassStd float64 `json:"egg_mass_std"` + EggWeightStd float64 `json:"egg_weight_std"` +} + +type RecordingFcrDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + FcrStd float64 `json:"fcr_std"` +} + +type RecordingAreaDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type RecordingLocationDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Address string `json:"address"` +} + +type RecordingWarehouseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Area *RecordingAreaDTO `json:"area,omitempty"` + Location *RecordingLocationDTO `json:"location,omitempty"` +} + type RecordingRelationDTO struct { - Id uint `json:"id"` - ProjectFlockKandangId uint `json:"project_flock_kandang_id"` - RecordDatetime time.Time `json:"record_datetime"` - Day int `json:"day"` - ProjectFlockCategory string `json:"project_flock_category"` - TotalDepletionQty float64 `json:"total_depletion_qty"` - CumDepletionRate float64 `json:"cum_depletion_rate"` - CumIntake int `json:"cum_intake"` - FcrValue float64 `json:"fcr_value"` - TotalChickQty float64 `json:"total_chick_qty"` - HenDay float64 `json:"hen_day"` - HenHouse float64 `json:"hen_house"` - FeedIntake float64 `json:"feed_intake"` - EggMass float64 `json:"egg_mass"` - EggWeight float64 `json:"egg_weight"` - StandardHenDay *float64 `json:"hen_day_std,omitempty"` - StandardHenHouse *float64 `json:"hen_house_std,omitempty"` - StandardFeedIntake *float64 `json:"feed_intake_std,omitempty"` - StandardMaxDepletion *float64 `json:"max_depletion_std,omitempty"` - StandardEggMass *float64 `json:"egg_mass_std,omitempty"` - StandardEggWeight *float64 `json:"egg_weight_std,omitempty"` - StandardFcr *float64 `json:"fcr_std,omitempty"` - Approval approvalDTO.ApprovalRelationDTO `json:"approval"` + Id uint `json:"id"` + ProjectFlock RecordingProjectFlockDTO `json:"project_flock"` + RecordDatetime time.Time `json:"record_datetime"` + Day int `json:"day"` + TotalDepletionQty float64 `json:"total_depletion_qty"` + CumDepletionRate float64 `json:"cum_depletion_rate"` + CumIntake int `json:"cum_intake"` + FcrValue float64 `json:"fcr_value"` + HenDay float64 `json:"hen_day"` + HenHouse float64 `json:"hen_house"` + FeedIntake float64 `json:"feed_intake"` + EggMass float64 `json:"egg_mass"` + EggWeight float64 `json:"egg_weight"` + Approval approvalDTO.ApprovalRelationDTO `json:"approval"` } type RecordingListDTO struct { RecordingRelationDTO - CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type RecordingDetailDTO struct { RecordingListDTO - Depletions []RecordingDepletionDTO `json:"depletions"` - Stocks []RecordingStockDTO `json:"stocks"` - Eggs []RecordingEggDTO `json:"eggs"` + Warehouse *RecordingWarehouseDTO `json:"warehouse,omitempty"` + ProductCategory string `json:"product_category"` + Depletions []RecordingDepletionDTO `json:"depletions"` + Stocks []RecordingStockDTO `json:"stocks"` + Eggs []RecordingEggDTO `json:"eggs"` } type RecordingDepletionDTO struct { @@ -63,7 +102,7 @@ type RecordingDepletionDTO struct { type RecordingStockDTO struct { ProductWarehouseId uint `json:"product_warehouse_id"` UsageAmount float64 `json:"usage_amount"` - PendingQty *float64 `json:"pending_qty,omitempty"` + PendingQty float64 `json:"pending_qty"` ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"` } @@ -75,117 +114,10 @@ type RecordingEggDTO struct { ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"` } -type RecordingProductWarehouseDTO struct { - Id uint `json:"id"` - ProductId uint `json:"product_id"` - ProductName string `json:"product_name"` - WarehouseId uint `json:"warehouse_id"` - WarehouseName string `json:"warehouse_name"` -} - // === Mapper Functions === -func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { - var ( - projectFlockCategory string - day int - totalDepletionQty float64 - cumDepletionRate float64 - cumIntake int - fcrValue float64 - totalChickQty float64 - henDay float64 - henHouse float64 - feedIntake float64 - eggMass float64 - eggWeight float64 - ) - - if e.Day != nil { - day = *e.Day - } - if e.TotalDepletionQty != nil { - totalDepletionQty = *e.TotalDepletionQty - } - if e.CumDepletionRate != nil { - cumDepletionRate = *e.CumDepletionRate - } - if e.CumIntake != nil { - cumIntake = *e.CumIntake - } - if e.FcrValue != nil { - fcrValue = *e.FcrValue - } - if e.TotalChickQty != nil { - totalChickQty = *e.TotalChickQty - } - if e.HenDay != nil { - henDay = *e.HenDay - } - if e.HenHouse != nil { - henHouse = *e.HenHouse - } - if e.FeedIntake != nil { - feedIntake = *e.FeedIntake - } - if e.EggMass != nil { - eggMass = *e.EggMass - } - if e.EggWeight != nil { - eggWeight = *e.EggWeight - } - - if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.ProjectFlock.Id != 0 { - category := e.ProjectFlockKandang.ProjectFlock.Category - projectFlockCategory = category - } - - latestApproval := defaultRecordingLatestApproval(e) - if e.LatestApproval != nil { - snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval) - latestApproval = snapshot - } - - return RecordingRelationDTO{ - Id: e.Id, - ProjectFlockKandangId: e.ProjectFlockKandangId, - RecordDatetime: e.RecordDatetime, - Day: day, - ProjectFlockCategory: projectFlockCategory, - TotalDepletionQty: totalDepletionQty, - CumDepletionRate: cumDepletionRate, - CumIntake: cumIntake, - FcrValue: fcrValue, - TotalChickQty: totalChickQty, - HenDay: henDay, - HenHouse: henHouse, - FeedIntake: feedIntake, - EggMass: eggMass, - EggWeight: eggWeight, - StandardHenDay: e.StandardHenDay, - StandardHenHouse: e.StandardHenHouse, - StandardFeedIntake: e.StandardFeedIntake, - StandardMaxDepletion: e.StandardMaxDepletion, - StandardEggMass: e.StandardEggMass, - StandardEggWeight: e.StandardEggWeight, - StandardFcr: e.StandardFcr, - Approval: latestApproval, - } -} - func ToRecordingListDTO(e entity.Recording) RecordingListDTO { - var createdUser *userDTO.UserRelationDTO - if e.CreatedUser != nil && e.CreatedUser.Id != 0 { - mapped := userDTO.ToUserRelationDTO(*e.CreatedUser) - createdUser = &mapped - } - - return RecordingListDTO{ - RecordingRelationDTO: ToRecordingRelationDTO(e), - CreatedAt: e.CreatedAt, - UpdatedAt: e.UpdatedAt, - CreatedUser: createdUser, - } + return toRecordingListDTO(e) } func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO { @@ -197,20 +129,15 @@ func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO { } func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO { - listDTO := ToRecordingListDTO(e) - - var eggs []RecordingEggDTO - if strings.EqualFold(listDTO.ProjectFlockCategory, string(utils.ProjectFlockCategoryLaying)) { - eggs = ToRecordingEggDTOs(e.Eggs) - } else if len(e.Eggs) > 0 { - eggs = ToRecordingEggDTOs(e.Eggs) - } + listDTO := toRecordingListDTO(e) return RecordingDetailDTO{ RecordingListDTO: listDTO, - Depletions: ToRecordingDepletionDTOs(e.Depletions), - Stocks: ToRecordingStockDTOs(e.Stocks), - Eggs: eggs, + Warehouse: recordingWarehouseDTO(e), + ProductCategory: recordingProductCategory(e), + Depletions: ToRecordingDepletionDTOs(e.Depletions), + Stocks: ToRecordingStockDTOs(e.Stocks), + Eggs: ToRecordingEggDTOs(e.Eggs), } } @@ -233,11 +160,15 @@ func ToRecordingStockDTOs(stocks []entity.RecordingStock) []RecordingStockDTO { if s.UsageQty != nil { usageAmount = *s.UsageQty } + var pendingQty float64 + if s.PendingQty != nil { + pendingQty = *s.PendingQty + } result[i] = RecordingStockDTO{ ProductWarehouseId: s.ProductWarehouseId, UsageAmount: usageAmount, - PendingQty: s.PendingQty, + PendingQty: pendingQty, ProductWarehouse: mapProductWarehouseDTO(&s.ProductWarehouse), } } @@ -258,6 +189,184 @@ func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO { return result } +func toRecordingListDTO(e entity.Recording) RecordingListDTO { + relation := toRecordingRelationDTO(e) + + var createdUser *userDTO.UserRelationDTO + if e.CreatedUser != nil && e.CreatedUser.Id != 0 { + mapped := userDTO.ToUserRelationDTO(*e.CreatedUser) + createdUser = &mapped + } + + return RecordingListDTO{ + RecordingRelationDTO: relation, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + } +} + +func toRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { + latestApproval := defaultRecordingLatestApproval(e) + if e.LatestApproval != nil { + snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval) + latestApproval = snapshot + } + + return RecordingRelationDTO{ + Id: e.Id, + ProjectFlock: toRecordingProjectFlockDTO(e), + RecordDatetime: e.RecordDatetime, + Day: intValue(e.Day), + TotalDepletionQty: floatValue(e.TotalDepletionQty), + CumDepletionRate: floatValue(e.CumDepletionRate), + CumIntake: intValue(e.CumIntake), + FcrValue: floatValue(e.FcrValue), + HenDay: floatValue(e.HenDay), + HenHouse: floatValue(e.HenHouse), + FeedIntake: floatValue(e.FeedIntake), + EggMass: floatValue(e.EggMass), + EggWeight: floatValue(e.EggWeight), + Approval: latestApproval, + } +} + +func toRecordingProjectFlockDTO(e entity.Recording) RecordingProjectFlockDTO { + result := RecordingProjectFlockDTO{ + ProjectFlockKandangId: e.ProjectFlockKandangId, + } + + pfk := e.ProjectFlockKandang + if pfk == nil { + return result + } + + if pfk.ProjectFlock.Id != 0 { + result.FlockName = pfk.ProjectFlock.FlockName + if pfk.ProjectFlock.Category != "" { + result.ProjectFlockCategory = strings.ToUpper(pfk.ProjectFlock.Category) + } + } + + result.Period = pfk.Period + + if pfk.ProjectFlock.ProductionStandard.Id != 0 { + result.ProductionStandart = &RecordingProductionStandardDTO{ + Id: pfk.ProjectFlock.ProductionStandard.Id, + Week: recordingWeekValue(e), + Name: pfk.ProjectFlock.ProductionStandard.Name, + HenDayStd: floatValue(e.StandardHenDay), + HenHouseStd: floatValue(e.StandardHenHouse), + FeedIntakeStd: floatValue(e.StandardFeedIntake), + MaxDepletionStd: floatValue(e.StandardMaxDepletion), + EggMassStd: floatValue(e.StandardEggMass), + EggWeightStd: floatValue(e.StandardEggWeight), + } + } + + if pfk.ProjectFlock.Fcr.Id != 0 || e.StandardFcr != nil { + result.Fcr = &RecordingFcrDTO{ + Id: pfk.ProjectFlock.Fcr.Id, + Name: pfk.ProjectFlock.Fcr.Name, + FcrStd: floatValue(e.StandardFcr), + } + } + + result.TotalChickQty = floatValue(e.TotalChickQty) + + return result +} + +func recordingWeekValue(e entity.Recording) int { + day := intValue(e.Day) + if day <= 0 { + return 0 + } + weekBase := 1 + if isLayingRecording(e) { + weekBase = 18 + } + return ((day - 1) / 7) + weekBase +} + +func isLayingRecording(e entity.Recording) bool { + if e.ProjectFlockKandang == nil { + return false + } + return strings.EqualFold(e.ProjectFlockKandang.ProjectFlock.Category, string(utils.ProjectFlockCategoryLaying)) +} + +func recordingProductCategory(e entity.Recording) string { + if e.ProjectFlockKandang == nil { + return "" + } + project := e.ProjectFlockKandang.ProjectFlock + if project.Id == 0 { + return "" + } + if project.ProductionStandard.Id != 0 && project.ProductionStandard.ProjectCategory != "" { + return strings.ToUpper(project.ProductionStandard.ProjectCategory) + } + if project.Category != "" { + return strings.ToUpper(project.Category) + } + return "" +} + +func recordingWarehouseDTO(e entity.Recording) *RecordingWarehouseDTO { + pw := primaryProductWarehouse(e) + if pw == nil || pw.Warehouse.Id == 0 { + return nil + } + return mapWarehouseDTO(&pw.Warehouse) +} + +func primaryProductWarehouse(e entity.Recording) *entity.ProductWarehouse { + if len(e.Stocks) > 0 { + pw := e.Stocks[0].ProductWarehouse + if pw.Id != 0 { + return &pw + } + } + if len(e.Depletions) > 0 { + pw := e.Depletions[0].ProductWarehouse + if pw.Id != 0 { + return &pw + } + } + if len(e.Eggs) > 0 { + pw := e.Eggs[0].ProductWarehouse + if pw.Id != 0 { + return &pw + } + } + return nil +} + +func mapWarehouseDTO(wh *entity.Warehouse) *RecordingWarehouseDTO { + if wh == nil || wh.Id == 0 { + return nil + } + dto := &RecordingWarehouseDTO{ + Id: wh.Id, + Name: wh.Name, + } + if wh.Area.Id != 0 { + dto.Area = &RecordingAreaDTO{ + Id: wh.Area.Id, + Name: wh.Area.Name, + } + } + if wh.Location != nil && wh.Location.Id != 0 { + dto.Location = &RecordingLocationDTO{ + Id: wh.Location.Id, + Name: wh.Location.Name, + Address: wh.Location.Address, + } + } + return dto +} + func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.ProductWarehouseDTO { if pw == nil { return productWarehouseDTO.ProductWarehouseDTO{} @@ -271,6 +380,20 @@ func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.Pro return *mapped } +func floatValue(value *float64) float64 { + if value == nil { + return 0 + } + return *value +} + +func intValue(value *int) int { + if value == nil { + return 0 + } + return *value +} + func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalRelationDTO { result := approvalDTO.ApprovalRelationDTO{} diff --git a/internal/modules/production/recordings/module.go b/internal/modules/production/recordings/module.go index a19faa33..91b024ac 100644 --- a/internal/modules/production/recordings/module.go +++ b/internal/modules/production/recordings/module.go @@ -11,6 +11,8 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" + rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories" + sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services" @@ -29,6 +31,16 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) stockAllocationRepo := commonRepo.NewStockAllocationRepository(db) + productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db) + productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db) + standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db) + + productionStandardService := sProductionStandard.NewProductionStandardService( + productionStandardRepo, + productionStandardDetailRepo, + standardGrowthDetailRepo, + validate, + ) fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log) if err := fifoService.RegisterUsable(fifo.UsableConfig{ @@ -63,6 +75,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate approvalRepo, approvalService, fifoService, + productionStandardService, validate, ) userService := sUser.NewUserService(userRepo, validate) diff --git a/internal/modules/production/recordings/repositories/recording.repository.go b/internal/modules/production/recordings/repositories/recording.repository.go index bf08b78b..fbb628ff 100644 --- a/internal/modules/production/recordings/repositories/recording.repository.go +++ b/internal/modules/production/recordings/repositories/recording.repository.go @@ -82,19 +82,28 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB { return db. Preload("CreatedUser"). Preload("ProjectFlockKandang"). + Preload("ProjectFlockKandang.Kandang"). Preload("ProjectFlockKandang.ProjectFlock"). + Preload("ProjectFlockKandang.ProjectFlock.ProductionStandard"). + Preload("ProjectFlockKandang.ProjectFlock.Fcr"). Preload("Depletions"). Preload("Depletions.ProductWarehouse"). Preload("Depletions.ProductWarehouse.Product"). Preload("Depletions.ProductWarehouse.Warehouse"). + Preload("Depletions.ProductWarehouse.Warehouse.Area"). + Preload("Depletions.ProductWarehouse.Warehouse.Location"). Preload("Stocks"). Preload("Stocks.ProductWarehouse"). Preload("Stocks.ProductWarehouse.Product"). Preload("Stocks.ProductWarehouse.Warehouse"). + Preload("Stocks.ProductWarehouse.Warehouse.Area"). + Preload("Stocks.ProductWarehouse.Warehouse.Location"). Preload("Eggs"). Preload("Eggs.ProductWarehouse"). Preload("Eggs.ProductWarehouse.Product"). - Preload("Eggs.ProductWarehouse.Warehouse") + Preload("Eggs.ProductWarehouse.Warehouse"). + Preload("Eggs.ProductWarehouse.Warehouse.Area"). + Preload("Eggs.ProductWarehouse.Warehouse.Location") } func (r *RecordingRepositoryImpl) GetLatestByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (*entity.Recording, error) { diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 819552dc..88ed4cf7 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -10,6 +10,7 @@ import ( m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories" + sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations" @@ -53,6 +54,7 @@ type recordingService struct { ProjectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository ApprovalRepo commonRepo.ApprovalRepository ApprovalSvc commonSvc.ApprovalService + ProductionStandardSvc sProductionStandard.ProductionStandardService FifoSvc commonSvc.FifoService } @@ -64,6 +66,7 @@ func NewRecordingService( approvalRepo commonRepo.ApprovalRepository, approvalSvc commonSvc.ApprovalService, fifoSvc commonSvc.FifoService, + productionStandardSvc sProductionStandard.ProductionStandardService, validate *validator.Validate, ) RecordingService { return &recordingService{ @@ -75,6 +78,7 @@ func NewRecordingService( ProjectFlockPopulationRepo: projectFlockPopulationRepo, ApprovalRepo: approvalRepo, ApprovalSvc: approvalSvc, + ProductionStandardSvc: productionStandardSvc, FifoSvc: fifoSvc, } } @@ -169,6 +173,14 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent } ctx := c.Context() + recordTime := time.Now().UTC() + if req.RecordDate != nil && strings.TrimSpace(*req.RecordDate) != "" { + parsed, err := time.Parse("2006-01-02", strings.TrimSpace(*req.RecordDate)) + if err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "record_date must be in YYYY-MM-DD format") + } + recordTime = parsed.UTC() + } pfk, err := s.ProjectFlockKandangRepo.GetByID(ctx, req.ProjectFlockKandangId) if err != nil { @@ -188,6 +200,11 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent if err := s.ensureChickInExists(ctx, pfk.Id); err != nil { return nil, err } + if s.ProductionStandardSvc != nil { + if err := s.ProductionStandardSvc.EnsureWeekStart(ctx, pfk.ProjectFlock.ProductionStandardId, category); err != nil { + return nil, err + } + } if !isLaying && len(req.Eggs) > 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks") @@ -210,8 +227,12 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent s.Log.Errorf("Failed to determine recording day: %+v", err) return err } + if s.ProductionStandardSvc != nil { + if err := s.ProductionStandardSvc.EnsureWeekAvailable(ctx, pfk.ProjectFlock.ProductionStandardId, category, nextDay); err != nil { + return err + } + } - recordTime := time.Now().UTC() existsToday, err := s.Repository.ExistsOnDate(ctx, req.ProjectFlockKandangId, recordTime) if err != nil { s.Log.Errorf("Failed to verify existing recording on date: %+v", err) @@ -1157,7 +1178,7 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm var fcrValue float64 if usageInGrams > 0 && totalEggWeightGrams > 0 { - fcrValue = totalEggWeightGrams / usageInGrams + fcrValue = usageInGrams / totalEggWeightGrams updates["fcr_value"] = fcrValue recording.FcrValue = &fcrValue } else { @@ -1330,12 +1351,16 @@ func (s *recordingService) attachProductionStandard(ctx context.Context, item *e return nil } - week := ((int(*item.Day) - 1) / 7) + 1 + category := strings.ToUpper(item.ProjectFlockKandang.ProjectFlock.Category) + weekBase := 1 + if category == string(utils.ProjectFlockCategoryLaying) { + weekBase = 18 + } + week := ((int(*item.Day) - 1) / 7) + weekBase if week <= 0 { return nil } - category := strings.ToUpper(item.ProjectFlockKandang.ProjectFlock.Category) db := s.Repository.DB() standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db) growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db) diff --git a/internal/modules/production/recordings/validations/recording.validation.go b/internal/modules/production/recordings/validations/recording.validation.go index a1d6aaf7..8b4eab57 100644 --- a/internal/modules/production/recordings/validations/recording.validation.go +++ b/internal/modules/production/recordings/validations/recording.validation.go @@ -21,6 +21,7 @@ type ( type Create struct { ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"` + RecordDate *string `json:"record_date,omitempty" validate:"omitempty,datetime=2006-01-02"` Stocks []Stock `json:"stocks" validate:"dive"` Depletions []Depletion `json:"depletions" validate:"dive"` Eggs []Egg `json:"eggs" validate:"omitempty,dive"`