From 86f37a89c1f11f92b9a2328be203b02635295388 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 3 Nov 2025 09:16:29 +0700 Subject: [PATCH] Feat[BE]: add multilpple type of chickin growing and laying, make convertion product when chickin approved, add projectflockkandangid on projectflock api --- internal/database/seed/seeder.go | 11 + .../controllers/chickin.controller.go | 189 ++++++----- .../production/chickins/dto/chickin.dto.go | 64 +++- internal/modules/production/chickins/route.go | 6 +- .../chickins/services/chickin.service.go | 196 ++++++----- .../dto/project_flock_kandang.dto.go | 313 +++++++++--------- .../project_flocks/dto/projectflock.dto.go | 46 ++- .../services/projectflock.service.go | 4 +- internal/utils/constant.go | 2 + 9 files changed, 485 insertions(+), 346 deletions(-) diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index 3b7378c4..93c75b29 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -574,6 +574,7 @@ func seedProductCategories(tx *gorm.DB, createdBy uint) (map[string]uint, error) {"Bahan Baku", "RAW"}, {"Day Old Chick", "DOC"}, {"Pullet", "PULLET"}, + {"Layer", "LAYER"}, } result := make(map[string]uint, len(seeds)) @@ -808,6 +809,16 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"}, Flags: []utils.FlagType{utils.FlagPullet}, }, + { + Name: "Ayam Layer", + Brand: "MBU Layer", + Sku: "LAY0001", + Uom: "Ekor", + Category: "Layer", + Price: 20000, + Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"}, + Flags: []utils.FlagType{utils.FlagLayer}, + }, } for _, seed := range seeds { diff --git a/internal/modules/production/chickins/controllers/chickin.controller.go b/internal/modules/production/chickins/controllers/chickin.controller.go index c132b279..7f8e0d5b 100644 --- a/internal/modules/production/chickins/controllers/chickin.controller.go +++ b/internal/modules/production/chickins/controllers/chickin.controller.go @@ -1,7 +1,6 @@ package controller import ( - "math" "strconv" "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto" @@ -22,30 +21,84 @@ func NewChickinController(chickinService service.ChickinService) *ChickinControl } } -func (u *ChickinController) GetAll(c *fiber.Ctx) error { - query := &validation.Query{ - Page: c.QueryInt("page", 1), - Limit: c.QueryInt("limit", 10), - ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)), +// func (u *ChickinController) GetAll(c *fiber.Ctx) error { +// query := &validation.Query{ +// Page: c.QueryInt("page", 1), +// Limit: c.QueryInt("limit", 10), +// ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)), +// } + +// result, totalResults, err := u.ChickinService.GetAll(c, query) +// if err != nil { +// return err +// } + +// return c.Status(fiber.StatusOK). +// JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{ +// Code: fiber.StatusOK, +// Status: "success", +// Message: "Get all chickins successfully", +// Meta: response.Meta{ +// Page: query.Page, +// Limit: query.Limit, +// TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), +// TotalResults: totalResults, +// }, +// Data: dto.ToChickinListDTOs(result), +// }) +// } + +// func (u *ChickinController) GetOne(c *fiber.Ctx) error { +// param := c.Params("id") + +// id, err := strconv.Atoi(param) +// if err != nil { +// return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") +// } + +// result, err := u.ChickinService.GetOne(c, uint(id)) +// if err != nil { +// return err +// } + +// return c.Status(fiber.StatusOK). +// JSON(response.Success{ +// Code: fiber.StatusOK, +// Status: "success", +// Message: "Get chickin successfully", +// Data: dto.ToChickinListDTO(*result), +// }) +// } + +func (u *ChickinController) CreateOne(c *fiber.Ctx) error { + req := new(validation.Create) + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - result, totalResults, err := u.ChickinService.GetAll(c, query) + results, err := u.ChickinService.CreateOne(c, req) if err != nil { return err } - return c.Status(fiber.StatusOK). - JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{ - Code: fiber.StatusOK, + var ( + data interface{} + message = "Create chickin successfully" + ) + if len(results) == 1 { + data = dto.ToChickinListDTO(results[0]) + } else { + message = "Create chickins successfully" + data = dto.ToChickinListDTOs(results) + } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, Status: "success", - Message: "Get all chickins successfully", - Meta: response.Meta{ - Page: query.Page, - Limit: query.Limit, - TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), - TotalResults: totalResults, - }, - Data: dto.ToChickinListDTOs(result), + Message: message, + Data: data, }) } @@ -67,80 +120,60 @@ func (u *ChickinController) GetOne(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Get chickin successfully", - Data: dto.ToChickinListDTO(*result), + Data: dto.ToChickinDetailDTO(*result), }) } -func (u *ChickinController) CreateOne(c *fiber.Ctx) error { - req := new(validation.Create) +// func (u *ChickinController) UpdateOne(c *fiber.Ctx) error { +// req := new(validation.Update) +// param := c.Params("id") - if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } +// id, err := strconv.Atoi(param) +// if err != nil { +// return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") +// } - result, err := u.ChickinService.CreateOne(c, req) - if err != nil { - return err - } +// if err := c.BodyParser(req); err != nil { +// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") +// } - return c.Status(fiber.StatusCreated). - JSON(response.Success{ - Code: fiber.StatusCreated, - Status: "success", - Message: "Create chickin successfully", - Data: dto.ToChickinListDTO(*result), - }) -} +// result, err := u.ChickinService.UpdateOne(c, req, uint(id)) +// if err != nil { +// return err +// } -func (u *ChickinController) UpdateOne(c *fiber.Ctx) error { - req := new(validation.Update) - param := c.Params("id") +// return c.Status(fiber.StatusOK). +// JSON(response.Success{ +// Code: fiber.StatusOK, +// Status: "success", +// Message: "Update chickin successfully", +// Data: dto.ToChickinListDTO(*result), +// }) +// } - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } +// func (u *ChickinController) DeleteOne(c *fiber.Ctx) error { +// param := c.Params("id") - if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } +// id, err := strconv.Atoi(param) +// if err != nil { +// return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") +// } - result, err := u.ChickinService.UpdateOne(c, req, uint(id)) - if err != nil { - return err - } +// if err := u.ChickinService.DeleteOne(c, uint(id)); err != nil { +// return err +// } - return c.Status(fiber.StatusOK). - JSON(response.Success{ - Code: fiber.StatusOK, - Status: "success", - Message: "Update chickin successfully", - Data: dto.ToChickinListDTO(*result), - }) -} - -func (u *ChickinController) DeleteOne(c *fiber.Ctx) error { - param := c.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - - if err := u.ChickinService.DeleteOne(c, uint(id)); err != nil { - return err - } - - return c.Status(fiber.StatusOK). - JSON(response.Common{ - Code: fiber.StatusOK, - Status: "success", - Message: "Delete chickin successfully", - }) -} +// return c.Status(fiber.StatusOK). +// JSON(response.Common{ +// Code: fiber.StatusOK, +// Status: "success", +// Message: "Delete chickin successfully", +// }) +// } func (u *ChickinController) Approval(c *fiber.Ctx) error { req := new(validation.Approve) + if err := c.BodyParser(req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } diff --git a/internal/modules/production/chickins/dto/chickin.dto.go b/internal/modules/production/chickins/dto/chickin.dto.go index 1f35c5a8..44373b11 100644 --- a/internal/modules/production/chickins/dto/chickin.dto.go +++ b/internal/modules/production/chickins/dto/chickin.dto.go @@ -15,13 +15,13 @@ import ( // === DTO Structs (ordered) === type ChickinBaseDTO struct { - Id uint `json:"id"` - ProjectFlockKandangId uint `json:"project_flock_kandang_id"` - ChickInDate time.Time `json:"chick_in_date"` - ProductWarehouseId uint `json:"product_warehouse_id"` - UsageQty float64 `json:"usage_qty"` - PendingUsageQty float64 `json:"pending_usage_qty"` - Notes string `json:"notes"` + Id uint `json:"id"` + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` + ChickInDate time.Time `json:"chick_in_date"` + ProductWarehouseId uint `json:"product_warehouse_id"` + UsageQty float64 `json:"usage_qty"` + PendingUsageQty float64 `json:"pending_usage_qty"` + Notes string `json:"notes"` } type ProjectFlockDTO struct { @@ -61,7 +61,17 @@ type ChickinListDTO struct { } type ChickinDetailDTO struct { - ChickinListDTO + Id uint `json:"id"` + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` + ChickInDate time.Time `json:"chick_in_date"` + ProductWarehouseId uint `json:"product_warehouse_id"` + UsageQty float64 `json:"usage_qty"` + PendingUsageQty float64 `json:"pending_usage_qty"` + Notes string `json:"notes"` + CreatedBy uint `json:"created_by"` + CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // === Mapper Functions (ordered) === @@ -149,13 +159,13 @@ func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { projectFlockKandangId = e.ProjectFlockKandangId } return ChickinBaseDTO{ - Id: e.Id, + Id: e.Id, ProjectFlockKandangId: projectFlockKandangId, - ChickInDate: e.ChickInDate, - ProductWarehouseId: e.ProductWarehouseId, - UsageQty: e.UsageQty, - PendingUsageQty: e.PendingUsageQty, - Notes: e.Notes, + ChickInDate: e.ChickInDate, + ProductWarehouseId: e.ProductWarehouseId, + UsageQty: e.UsageQty, + PendingUsageQty: e.PendingUsageQty, + Notes: e.Notes, } } @@ -203,7 +213,31 @@ func ToChickinSimpleDTOs(e []entity.ProjectChickin) []ChickinSimpleDTO { } func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO { + var createdUser *userBaseDTO.UserBaseDTO + if e.CreatedUser != nil && e.CreatedUser.Id != 0 { + mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser) + createdUser = &mapped + } + return ChickinDetailDTO{ - ChickinListDTO: ToChickinListDTO(e), + Id: e.Id, + ProjectFlockKandangId: e.ProjectFlockKandangId, + ChickInDate: e.ChickInDate, + ProductWarehouseId: e.ProductWarehouseId, + UsageQty: e.UsageQty, + PendingUsageQty: e.PendingUsageQty, + Notes: e.Notes, + CreatedBy: e.CreatedBy, + CreatedUser: createdUser, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, } } + +func ToChickinDetailDTOs(e []entity.ProjectChickin) []ChickinDetailDTO { + result := make([]ChickinDetailDTO, len(e)) + for i, r := range e { + result[i] = ToChickinDetailDTO(r) + } + return result +} diff --git a/internal/modules/production/chickins/route.go b/internal/modules/production/chickins/route.go index 0bb5e93d..e2b64846 100644 --- a/internal/modules/production/chickins/route.go +++ b/internal/modules/production/chickins/route.go @@ -20,10 +20,10 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService // route.Patch("/:id", m.Auth(u), ctrl.UpdateOne) // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) - route.Get("/", ctrl.GetAll) + // route.Get("/", ctrl.GetAll) route.Post("/", ctrl.CreateOne) route.Get("/:id", ctrl.GetOne) - route.Patch("/:id", ctrl.UpdateOne) - route.Delete("/:id", ctrl.DeleteOne) + // route.Patch("/:id", ctrl.UpdateOne) + // route.Delete("/:id", ctrl.DeleteOne) route.Post("/approvals", ctrl.Approval) } diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 3074b36a..243bdf52 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -25,7 +25,7 @@ import ( type ChickinService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error) - CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) ([]entity.ProjectChickin, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) DeleteOne(ctx *fiber.Ctx, id uint) error Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error) @@ -109,7 +109,7 @@ func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, e return chickin, nil } -func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) { +func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]entity.ProjectChickin, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } @@ -150,6 +150,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit actorID := uint(1) // todo nanti ambil dari auth context newChikins := make([]*entity.ProjectChickin, 0) for _, productWarehouse := range productWarehouses { + + if productWarehouse.Quantity <= 0 { + continue + } + newChickin := &entity.ProjectChickin{ ProjectFlockKandangId: req.ProjectFlockKandangId, ChickInDate: chickinDate, @@ -167,6 +172,12 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, fiber.NewError(fiber.StatusBadRequest, "No chickins to create") } + existingChikins, err := s.Repository.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing chickins") + } + isFirstTime := len(existingChikins) == 0 + err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) @@ -187,16 +198,27 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("failed to update product warehouse quantity for id %d", chickin.ProductWarehouseId) + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", chickin.ProductWarehouseId)) } - return err + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update product warehouse quantity") } } + var approvalAction entity.ApprovalAction + if isFirstTime { + approvalAction = entity.ApprovalActionCreated + } else { + approvalAction = entity.ApprovalActionUpdated + } if latest == nil { - - action := entity.ApprovalActionCreated - if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &action, actorID, nil); err != nil { + if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &approvalAction, actorID, nil); err != nil { + lower := strings.ToLower(err.Error()) + if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) { + return err + } + } + } else if latest.StepNumber != uint16(utils.ProjectFlockKandangStepPengajuan) { + if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &approvalAction, actorID, nil); err != nil { lower := strings.ToLower(err.Error()) if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) { return err @@ -210,7 +232,19 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, fiber.NewError(fiber.StatusBadRequest, err.Error()) } - return newChikins[0], nil + result := make([]entity.ProjectChickin, 0, len(newChikins)) + for _, chickin := range newChikins { + loaded, err := s.GetOne(c, chickin.Id) + if err != nil { + return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to reload chickin %d with relations: %v", chickin.Id, err)) + } + result = append(result, *loaded) + } + if len(result) == 0 { + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load created chickins") + } + + return result, nil } func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) { @@ -257,7 +291,8 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit if err := s.Validate.Struct(req); err != nil { return nil, err } - actorID := uint(1) // todo nanti ambil dari auth context + + approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB())) var action entity.ApprovalAction switch strings.ToUpper(strings.TrimSpace(req.Action)) { @@ -269,21 +304,19 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED") } - approvableIDs := uniqueUintSlice(req.ApprovableIds) + approvableIDs := utils.UniqueUintSlice(req.ApprovableIds) if len(approvableIDs) == 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id") } - // Validate all ProjectFlockKandang IDs exist and have valid approval status - approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB())) for _, id := range approvableIDs { idCopy := id if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProjectFlockKandang", ID: &idCopy, Exists: s.ProjectflockKandangRepo.IdExists}); err != nil { return nil, err } - // Check latest approval status - must be PENGAJUAN to be approvable latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, id, nil) + if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status") } @@ -308,6 +341,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit for _, approvableID := range approvableIDs { + actorID := uint(1) // todo nanti ambil dari auth context if _, err := approvalSvc.CreateApproval( c.Context(), utils.ApprovalWorkflowProjectFlockKandang, @@ -348,21 +382,34 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit return err } - pulletPW, err := s.getOrCreatePulletProductWarehouse(c, warehouse.Id, dbTransaction, actorID) - if err != nil { + category := strings.ToUpper(strings.TrimSpace(kandangForApproval.ProjectFlock.Category)) + var conversionCategoryCode string - continue + switch category { + case string(utils.ProjectFlockCategoryGrowing): + conversionCategoryCode = "PULLET" + case string(utils.ProjectFlockCategoryLaying): + conversionCategoryCode = "LAYER" + default: + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Unknown category for conversion: %s", category)) } - if err := s.convertChickinsToPullet(c, chickins, pulletPW, dbTransaction, actorID); err != nil { + targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, conversionCategoryCode, dbTransaction, actorID) + if err != nil { + return fmt.Errorf("failed to get/create %s product warehouse: %w", conversionCategoryCode, err) + } + if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil { return err } } else if action == entity.ApprovalActionRejected { chickins, err := chickinRepoTx.GetByProjectFlockKandangID(c.Context(), approvableID) - if err != nil { - s.Log.Warnf("failed to get chickins for rejection %d: %v", approvableID, err) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("failed to get chickins for rejection %d: %w", approvableID, err) + } + + if len(chickins) == 0 { continue } @@ -371,12 +418,21 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit updates := map[string]any{"quantity": chickin.PendingUsageQty} if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil { - s.Log.Warnf("failed to restore product warehouse quantity for rejection: %v", err) + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found during rejection", chickin.ProductWarehouseId)) + } + return fmt.Errorf("failed to restore product warehouse quantity for chickin %d: %w", chickin.Id, err) + } + + if err := chickinRepoTx.DeleteOne(c.Context(), chickin.Id); err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("failed to delete rejected chickin %d: %w", chickin.Id, err) + } + s.Log.Infof("chickin %d already deleted during rejection", chickin.Id) } } } } - return nil }) @@ -387,7 +443,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found") } - s.Log.Errorf("Failed to record approval for chickins %+v: %+v", approvableIDs, err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval") } @@ -403,39 +458,39 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit return updated, nil } -func (s *chickinService) getOrCreatePulletProductWarehouse(ctx *fiber.Ctx, warehouseId uint, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) { +func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId uint, categoryCode string, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) { - pulletProducts, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), "PULLET", warehouseId) - if err == nil && len(pulletProducts) > 0 { - return &pulletProducts[0], nil + products, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), categoryCode, warehouseId) + if err == nil && len(products) > 0 { + return &products[0], nil } - pulletProduct, err := s.ProductWarehouseRepo.GetFirstProductByCategoryCode(ctx.Context(), "PULLET") + product, err := s.ProductWarehouseRepo.GetFirstProductByCategoryCode(ctx.Context(), categoryCode) if err != nil { - return nil, fmt.Errorf("failed to get PULLET product: %w", err) + return nil, fmt.Errorf("failed to get %s product: %w", categoryCode, err) } - if pulletProduct == nil { - return nil, fmt.Errorf("no PULLET product found in system") + if product == nil { + return nil, fmt.Errorf("no %s product found in system", categoryCode) } - newPulletPW := &entity.ProductWarehouse{ - ProductId: pulletProduct.Id, + newPW := &entity.ProductWarehouse{ + ProductId: product.Id, WarehouseId: warehouseId, Quantity: 0, CreatedBy: actorID, } - if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPulletPW, nil); err != nil { - return nil, fmt.Errorf("failed to create PULLET product warehouse: %w", err) + if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPW, nil); err != nil { + return nil, fmt.Errorf("failed to create %s product warehouse: %w", categoryCode, err) } - return newPulletPW, nil + return newPW, nil } -func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []entity.ProjectChickin, pulletPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error { +func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []entity.ProjectChickin, targetPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error { - if pulletPW == nil || pulletPW.Id == 0 { - return fmt.Errorf("invalid PULLET product warehouse") + if targetPW == nil || targetPW.Id == 0 { + return fmt.Errorf("invalid target product warehouse") } chickinRepoTx := repository.NewChickinRepository(dbTransaction) @@ -444,6 +499,16 @@ func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []enti for _, chickin := range chickins { + populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id) + if err != nil { + return fmt.Errorf("failed to check population existence for chickin %d: %w", chickin.Id, err) + } + + if populationExists { + s.Log.Infof("population already exists for chickin %d, skipping", chickin.Id) + continue + } + quantityToConvert := chickin.PendingUsageQty if err := chickinRepoTx.PatchOne(ctx.Context(), chickin.Id, map[string]any{ @@ -453,52 +518,31 @@ func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []enti return fmt.Errorf("failed to update chickin %d qty: %w", chickin.Id, err) } - // Update quantity di PULLET product warehouse dengan quantity dari chickin - if err := productWarehouseTx.PatchOne(ctx.Context(), pulletPW.Id, map[string]any{ + if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{ "quantity": gorm.Expr("quantity + ?", quantityToConvert), }, nil); err != nil { - s.Log.Warnf("failed to update PULLET warehouse quantity: %v", err) + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id)) + } + return fmt.Errorf("failed to update target warehouse quantity: %w", err) } - populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id) - if err != nil { - s.Log.Warnf("failed to check population existence for chickin %d: %v", chickin.Id, err) - continue + population := &entity.ProjectFlockPopulation{ + ProjectChickinId: chickin.Id, + ProductWarehouseId: targetPW.Id, + TotalQty: quantityToConvert, + TotalUsedQty: 0, + Notes: chickin.Notes, + CreatedBy: actorID, } - - if !populationExists { - population := &entity.ProjectFlockPopulation{ - ProjectChickinId: chickin.Id, - ProductWarehouseId: pulletPW.Id, - TotalQty: quantityToConvert, - TotalUsedQty: 0, - Notes: chickin.Notes, - CreatedBy: actorID, + if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil { + lower := strings.ToLower(err.Error()) + if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) { + return err } - if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil { - lower := strings.ToLower(err.Error()) - if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) { - return err - } - s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err) - } - } else { - s.Log.Infof("population already exists for chickin %d", chickin.Id) + s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err) } } return nil } - -func uniqueUintSlice(values []uint) []uint { - seen := make(map[uint]struct{}, len(values)) - result := make([]uint, 0, len(values)) - for _, v := range values { - if _, ok := seen[v]; ok { - continue - } - seen[v] = struct{}{} - result = append(result, v) - } - return result -} diff --git a/internal/modules/production/project-flock-kandangs/dto/project_flock_kandang.dto.go b/internal/modules/production/project-flock-kandangs/dto/project_flock_kandang.dto.go index ac0678bd..1656eaba 100644 --- a/internal/modules/production/project-flock-kandangs/dto/project_flock_kandang.dto.go +++ b/internal/modules/production/project-flock-kandangs/dto/project_flock_kandang.dto.go @@ -8,8 +8,9 @@ import ( areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto" - kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" + productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" + warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" chickinDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto" projectFlockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" @@ -27,7 +28,7 @@ type ProjectFlockCustomDTO struct { Category string `json:"category"` Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` - Kandangs []kandangDTO.KandangBaseDTO `json:"kandangs,omitempty"` + Kandangs []KandangCustomDTO `json:"kandangs,omitempty"` CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -39,22 +40,11 @@ type KandangCustomDTO struct { Status string `json:"status"` } -type ProductBaseDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type WarehouseBaseDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - Type string `json:"type"` -} - type ProductWarehouseDTO struct { - Id uint `json:"id"` - Quantity float64 `json:"quantity"` - Product *ProductBaseDTO `json:"product,omitempty"` - Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"` + Id uint `json:"id"` + Quantity float64 `json:"quantity"` + Product *productDTO.ProductBaseDTO `json:"product,omitempty"` + Warehouse *warehouseDTO.WarehouseBaseDTO `json:"warehouse,omitempty"` } type AvailableQtyDTO struct { @@ -95,74 +85,38 @@ func toProjectFlockCustomDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFl Category: pf.Category, Fcr: pf.Fcr, Location: pf.Location, - Kandangs: pf.Kandangs, + Kandangs: buildKandangCustomDTOs(pf.Kandangs), CreatedUser: pf.CreatedUser, CreatedAt: pf.CreatedAt, UpdatedAt: pf.UpdatedAt, } } -func buildAvailableQtys(chickins []entity.ProjectChickin) []AvailableQtyDTO { - if len(chickins) == 0 { - return nil +func ToProjectFlockKandangListDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtysRaw []map[string]interface{}) ProjectFlockKandangListDTO { + var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO + if e.ProjectFlock.Id != 0 { + mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock) + projectFlockSummary = &mapped } - availableQtyMap := make(map[uint]AvailableQtyDTO) - for _, ch := range chickins { - if ch.ProductWarehouse == nil || ch.ProductWarehouse.Quantity <= 0 { - continue - } - - if _, exists := availableQtyMap[ch.ProductWarehouseId]; exists { - continue - } - - pwDTO := &ProductWarehouseDTO{ - Id: ch.ProductWarehouse.Id, - Quantity: ch.ProductWarehouse.Quantity, - } - - if ch.ProductWarehouse.Product.Id != 0 { - pwDTO.Product = &ProductBaseDTO{ - Id: ch.ProductWarehouse.Product.Id, - Name: ch.ProductWarehouse.Product.Name, - } - } - - if ch.ProductWarehouse.Warehouse.Id != 0 { - pwDTO.Warehouse = &WarehouseBaseDTO{ - Id: ch.ProductWarehouse.Warehouse.Id, - Name: ch.ProductWarehouse.Warehouse.Name, - Type: ch.ProductWarehouse.Warehouse.Type, - } - } - - availableQtyMap[ch.ProductWarehouseId] = AvailableQtyDTO{ - ProductWarehouse: pwDTO, - } + return ProjectFlockKandangListDTO{ + ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e), + ProjectFlock: toProjectFlockCustomDTO(projectFlockSummary), + Kandang: buildKandangFromEntity(e.Kandang), + Chickins: buildChickins(e.Chickins), + AvailableQtys: buildAvailableQtysFromRaw(availableQtysRaw), + CreatedAt: e.CreatedAt, + CreatedUser: buildCreatedUser(e.ProjectFlock), + Approval: defaultProjectFlockKandangLatestApproval(e), } - - if len(availableQtyMap) == 0 { - return nil - } - - result := make([]AvailableQtyDTO, 0, len(availableQtyMap)) - for _, v := range availableQtyMap { - result = append(result, v) - } - return result } -func buildChickins(chickins []entity.ProjectChickin) []chickinDTO.ChickinBaseDTO { - if len(chickins) == 0 { - return nil +func toKandangCustomDTO(k projectFlockDTO.KandangWithProjectFlockIdDTO) KandangCustomDTO { + return KandangCustomDTO{ + Id: k.Id, + Name: k.Name, + Status: k.Status, } - - result := make([]chickinDTO.ChickinBaseDTO, len(chickins)) - for i, ch := range chickins { - result[i] = chickinDTO.ToChickinBaseDTO(ch) - } - return result } func defaultProjectFlockKandangLatestApproval(e entity.ProjectFlockKandang) *approvalDTO.ApprovalBaseDTO { @@ -180,32 +134,14 @@ func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKand projectFlockSummary = &mapped } - var kandangSummary *KandangCustomDTO - if e.Kandang.Id != 0 { - kandangSummary = &KandangCustomDTO{ - Id: e.Kandang.Id, - Name: e.Kandang.Name, - Status: e.Kandang.Status, - } - } - - var createdUser *userDTO.UserBaseDTO - if e.ProjectFlock.CreatedUser.Id != 0 { - mapped := userDTO.ToUserBaseDTO(e.ProjectFlock.CreatedUser) - createdUser = &mapped - } else if e.ProjectFlock.CreatedBy != 0 { - mapped := userDTO.UserBaseDTO{Id: e.ProjectFlock.CreatedBy, IdUser: int64(e.ProjectFlock.CreatedBy)} - createdUser = &mapped - } - return ProjectFlockKandangListDTO{ ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e), ProjectFlock: toProjectFlockCustomDTO(projectFlockSummary), - Kandang: kandangSummary, + Kandang: buildKandangFromEntity(e.Kandang), Chickins: buildChickins(e.Chickins), AvailableQtys: buildAvailableQtys(e.Chickins), CreatedAt: e.CreatedAt, - CreatedUser: createdUser, + CreatedUser: buildCreatedUser(e.ProjectFlock), Approval: defaultProjectFlockKandangLatestApproval(e), } } @@ -224,69 +160,6 @@ func ToProjectFlockKandangDetailDTO(e entity.ProjectFlockKandang) ProjectFlockKa } } -func ToProjectFlockKandangListDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtysRaw []map[string]interface{}) ProjectFlockKandangListDTO { - var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO - if e.ProjectFlock.Id != 0 { - mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock) - projectFlockSummary = &mapped - } - - var kandangSummary *KandangCustomDTO - if e.Kandang.Id != 0 { - kandangSummary = &KandangCustomDTO{ - Id: e.Kandang.Id, - Name: e.Kandang.Name, - Status: e.Kandang.Status, - } - } - - var createdUser *userDTO.UserBaseDTO - if e.ProjectFlock.CreatedUser.Id != 0 { - mapped := userDTO.ToUserBaseDTO(e.ProjectFlock.CreatedUser) - createdUser = &mapped - } else if e.ProjectFlock.CreatedBy != 0 { - mapped := userDTO.UserBaseDTO{Id: e.ProjectFlock.CreatedBy, IdUser: int64(e.ProjectFlock.CreatedBy)} - createdUser = &mapped - } - - // convert available qtys from raw map data - var availableQtys []AvailableQtyDTO - if len(availableQtysRaw) > 0 { - availableQtys = buildAvailableQtysFromRaw(availableQtysRaw) - } - - return ProjectFlockKandangListDTO{ - ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e), - ProjectFlock: toProjectFlockCustomDTO(projectFlockSummary), - Kandang: kandangSummary, - Chickins: buildChickins(e.Chickins), - AvailableQtys: availableQtys, - CreatedAt: e.CreatedAt, - CreatedUser: createdUser, - Approval: defaultProjectFlockKandangLatestApproval(e), - } -} - -func buildAvailableQtysFromRaw(availableQtysRaw []map[string]interface{}) []AvailableQtyDTO { - if len(availableQtysRaw) == 0 { - return nil - } - - result := make([]AvailableQtyDTO, len(availableQtysRaw)) - for i, v := range availableQtysRaw { - pwData, ok := v["product_warehouse"].(map[string]interface{}) - if !ok { - continue - } - - pwDTO := buildProductWarehouseFromMap(pwData) - result[i] = AvailableQtyDTO{ - ProductWarehouse: pwDTO, - } - } - return result -} - func buildProductWarehouseFromMap(pwData map[string]interface{}) *ProductWarehouseDTO { if pwData == nil { return nil @@ -315,12 +188,12 @@ func buildProductWarehouseFromMap(pwData map[string]interface{}) *ProductWarehou return dto } -func buildProductFromMap(pData map[string]interface{}) *ProductBaseDTO { +func buildProductFromMap(pData map[string]interface{}) *productDTO.ProductBaseDTO { if pData == nil { return nil } - product := &ProductBaseDTO{} + product := &productDTO.ProductBaseDTO{} if id, ok := pData["id"].(float64); ok { product.Id = uint(id) } else if id, ok := pData["id"].(uint); ok { @@ -332,12 +205,12 @@ func buildProductFromMap(pData map[string]interface{}) *ProductBaseDTO { return product } -func buildWarehouseFromMap(wData map[string]interface{}) *WarehouseBaseDTO { +func buildWarehouseFromMap(wData map[string]interface{}) *warehouseDTO.WarehouseBaseDTO { if wData == nil { return nil } - warehouse := &WarehouseBaseDTO{} + warehouse := &warehouseDTO.WarehouseBaseDTO{} if id, ok := wData["id"].(float64); ok { warehouse.Id = uint(id) } else if id, ok := wData["id"].(uint); ok { @@ -351,3 +224,123 @@ func buildWarehouseFromMap(wData map[string]interface{}) *WarehouseBaseDTO { } return warehouse } + +func buildCreatedUser(pf entity.ProjectFlock) *userDTO.UserBaseDTO { + if pf.CreatedUser.Id != 0 { + mapped := userDTO.ToUserBaseDTO(pf.CreatedUser) + return &mapped + } else if pf.CreatedBy != 0 { + return &userDTO.UserBaseDTO{ + Id: pf.CreatedBy, + IdUser: int64(pf.CreatedBy), + } + } + return nil +} + +func buildKandangCustomDTOs(kandangs []projectFlockDTO.KandangWithProjectFlockIdDTO) []KandangCustomDTO { + if len(kandangs) == 0 { + return nil + } + + result := make([]KandangCustomDTO, len(kandangs)) + for i, k := range kandangs { + result[i] = toKandangCustomDTO(k) + } + return result +} + +func buildKandangFromEntity(kandang entity.Kandang) *KandangCustomDTO { + if kandang.Id == 0 { + return nil + } + + return &KandangCustomDTO{ + Id: kandang.Id, + Name: kandang.Name, + Status: kandang.Status, + } +} + +func buildChickins(chickins []entity.ProjectChickin) []chickinDTO.ChickinBaseDTO { + if len(chickins) == 0 { + return nil + } + + result := make([]chickinDTO.ChickinBaseDTO, len(chickins)) + for i, ch := range chickins { + result[i] = chickinDTO.ToChickinBaseDTO(ch) + } + return result +} + +func buildAvailableQtys(chickins []entity.ProjectChickin) []AvailableQtyDTO { + if len(chickins) == 0 { + return nil + } + + availableQtyMap := make(map[uint]AvailableQtyDTO) + for _, ch := range chickins { + if ch.ProductWarehouse == nil || ch.ProductWarehouse.Quantity <= 0 { + continue + } + + if _, exists := availableQtyMap[ch.ProductWarehouseId]; exists { + continue + } + + pwDTO := &ProductWarehouseDTO{ + Id: ch.ProductWarehouse.Id, + Quantity: ch.ProductWarehouse.Quantity, + } + + if ch.ProductWarehouse.Product.Id != 0 { + pwDTO.Product = &productDTO.ProductBaseDTO{ + Id: ch.ProductWarehouse.Product.Id, + Name: ch.ProductWarehouse.Product.Name, + } + } + + if ch.ProductWarehouse.Warehouse.Id != 0 { + pwDTO.Warehouse = &warehouseDTO.WarehouseBaseDTO{ + Id: ch.ProductWarehouse.Warehouse.Id, + Name: ch.ProductWarehouse.Warehouse.Name, + Type: ch.ProductWarehouse.Warehouse.Type, + } + } + + availableQtyMap[ch.ProductWarehouseId] = AvailableQtyDTO{ + ProductWarehouse: pwDTO, + } + } + + if len(availableQtyMap) == 0 { + return nil + } + + result := make([]AvailableQtyDTO, 0, len(availableQtyMap)) + for _, v := range availableQtyMap { + result = append(result, v) + } + return result +} + +func buildAvailableQtysFromRaw(availableQtysRaw []map[string]interface{}) []AvailableQtyDTO { + if len(availableQtysRaw) == 0 { + return nil + } + + result := make([]AvailableQtyDTO, len(availableQtysRaw)) + for i, v := range availableQtysRaw { + pwData, ok := v["product_warehouse"].(map[string]interface{}) + if !ok { + continue + } + + pwDTO := buildProductWarehouseFromMap(pwData) + result[i] = AvailableQtyDTO{ + ProductWarehouse: pwDTO, + } + } + return result +} diff --git a/internal/modules/production/project_flocks/dto/projectflock.dto.go b/internal/modules/production/project_flocks/dto/projectflock.dto.go index dff3bc61..2efd979b 100644 --- a/internal/modules/production/project_flocks/dto/projectflock.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock.dto.go @@ -20,18 +20,25 @@ type ProjectFlockBaseDTO struct { Period int `json:"period"` } +type KandangWithProjectFlockIdDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` +} + type ProjectFlockListDTO struct { ProjectFlockBaseDTO - Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"` - Area *areaDTO.AreaBaseDTO `json:"area,omitempty"` - Category string `json:"category"` - Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` - Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` - Kandangs []kandangDTO.KandangBaseDTO `json:"kandangs,omitempty"` - CreatedUser *userDTO.UserBaseDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Approval approvalDTO.ApprovalBaseDTO `json:"approval"` + Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"` + Area *areaDTO.AreaBaseDTO `json:"area,omitempty"` + Category string `json:"category"` + Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` + Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` + Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"` + CreatedUser *userDTO.UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Approval approvalDTO.ApprovalBaseDTO `json:"approval"` } type ProjectFlockDetailDTO struct { @@ -50,11 +57,24 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { createdUser = &mapped } - var kandangSummaries []kandangDTO.KandangBaseDTO + var kandangSummaries []KandangWithProjectFlockIdDTO if len(e.Kandangs) > 0 { - kandangSummaries = make([]kandangDTO.KandangBaseDTO, len(e.Kandangs)) + kandangSummaries = make([]KandangWithProjectFlockIdDTO, len(e.Kandangs)) + + // Create a map of KandangId -> ProjectFlockKandangId from KandangHistory + kandangIdToProjectFlockKandangId := make(map[uint]uint) + for _, kh := range e.KandangHistory { + kandangIdToProjectFlockKandangId[kh.KandangId] = kh.Id + } + for i, kandang := range e.Kandangs { - kandangSummaries[i] = kandangDTO.ToKandangBaseDTO(kandang) + baseDTO := kandangDTO.ToKandangBaseDTO(kandang) + kandangSummaries[i] = KandangWithProjectFlockIdDTO{ + Id: baseDTO.Id, + Name: baseDTO.Name, + Status: baseDTO.Status, + ProjectFlockKandangId: kandangIdToProjectFlockKandangId[kandang.Id], + } } } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 23097585..6f99ccb0 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -85,7 +85,9 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB { Preload("Area"). Preload("Fcr"). Preload("Location"). - Preload("Kandangs") + Preload("Kandangs"). + Preload("KandangHistory"). + Preload("KandangHistory.Kandang") } func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { diff --git a/internal/utils/constant.go b/internal/utils/constant.go index c7bcc99e..ef08eaed 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -19,6 +19,7 @@ const ( FlagDOC FlagType = "DOC" FlagPullet FlagType = "PULLET" + FlagLayer FlagType = "LAYER" FlagPakan FlagType = "PAKAN" FlagPreStarter FlagType = "PRE-STARTER" FlagStarter FlagType = "STARTER" @@ -39,6 +40,7 @@ var flagGroupOptions = map[FlagGroup][]FlagType{ FlagGroupProduct: { FlagDOC, FlagPullet, + FlagLayer, FlagPakan, FlagPreStarter, FlagStarter,