From 445789edfe76da4be9824f0f339f92405ae77b5e Mon Sep 17 00:00:00 2001 From: ragilap Date: Tue, 21 Oct 2025 15:51:19 +0700 Subject: [PATCH 1/3] FIX(BE): category dto do not show --- .../project_flocks/dto/projectflock.dto.go | 43 +++---------------- .../services/projectflock.service.go | 13 +++--- .../validations/projectflock.validation.go | 6 +-- internal/utils/constant.go | 17 +++----- 4 files changed, 20 insertions(+), 59 deletions(-) diff --git a/internal/modules/production/project_flocks/dto/projectflock.dto.go b/internal/modules/production/project_flocks/dto/projectflock.dto.go index cb35eb0f..e639e968 100644 --- a/internal/modules/production/project_flocks/dto/projectflock.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock.dto.go @@ -16,13 +16,8 @@ import ( ) type ProjectFlockBaseDTO struct { - Id uint `json:"id"` - Period int `json:"period"` - Category string `json:"category"` - Flock *flockDTO.FlockBaseDTO `json:"flock"` - Area *areaDTO.AreaBaseDTO `json:"area"` - Fcr *fcrDTO.FcrBaseDTO `json:"fcr"` - Location *locationDTO.LocationBaseDTO `json:"location"` + Id uint `json:"id"` + Period int `json:"period"` } type ProjectFlockListDTO struct { @@ -72,6 +67,7 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { return ProjectFlockListDTO{ ProjectFlockBaseDTO: createProjectFlockBaseDTO(e), Kandangs: kandangSummaries, + Category: e.Category, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, @@ -126,38 +122,9 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv } func createProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO { - var flock *flockDTO.FlockBaseDTO - if e.Flock.Id != 0 { - mapped := flockDTO.ToFlockBaseDTO(e.Flock) - flock = &mapped - } - - var area *areaDTO.AreaBaseDTO - if e.Area.Id != 0 { - mapped := areaDTO.ToAreaBaseDTO(e.Area) - area = &mapped - } - - var fcr *fcrDTO.FcrBaseDTO - if e.Fcr.Id != 0 { - mapped := fcrDTO.ToFcrBaseDTO(e.Fcr) - fcr = &mapped - } - - var location *locationDTO.LocationBaseDTO - if e.Location.Id != 0 { - mapped := locationDTO.ToLocationBaseDTO(e.Location) - location = &mapped - } - return ProjectFlockBaseDTO{ - Id: e.Id, - Period: e.Period, - Category: e.Category, - Flock: flock, - Area: area, - Fcr: fcr, - Location: location, + Id: e.Id, + Period: e.Period, } } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 49401dd4..f6ebf6e7 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -219,8 +219,8 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* return nil, err } - category, ok := utils.NormalizeProjectFlockCategory(req.Category) - if !ok { + cat := strings.ToUpper(req.Category) + if !utils.IsValidProjectFlockCategory(cat) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category") } @@ -257,7 +257,7 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* createBody := &entity.ProjectFlock{ FlockId: req.FlockId, AreaId: req.AreaId, - Category: string(category), + Category: cat, FcrId: req.FcrId, LocationId: req.LocationId, CreatedBy: 1, @@ -342,11 +342,12 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id }) } if req.Category != nil { - if normalized, ok := utils.NormalizeProjectFlockCategory(*req.Category); ok { - updateBody["category"] = string(normalized) - } else { + cat := strings.ToUpper(*req.Category) + if !utils.IsValidProjectFlockCategory(cat) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category") } + + updateBody["category"] = cat } if req.FcrId != nil { updateBody["fcr_id"] = *req.FcrId diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index 00c9eab8..9ee066af 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -3,7 +3,7 @@ package validation type Create struct { FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"` AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"` - Category string `json:"category" validate:"required_strict,oneof=growing laying GROWING LAYING"` + Category string `json:"category" validate:"required_strict"` FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"` LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"` @@ -12,7 +12,7 @@ type Create struct { type Update struct { FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"` AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"` - Category *string `json:"category,omitempty" validate:"omitempty,oneof=growing laying GROWING LAYING"` + Category *string `json:"category,omitempty" validate:"omitempty"` FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` KandangIds []uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,gt=0"` @@ -22,7 +22,7 @@ type Query struct { Page int `query:"page" validate:"omitempty,number,min=1"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` Search string `query:"search" validate:"omitempty,max=50"` - SortBy string `query:"sort_by" validate:"omitempty,oneof=area location kandangs period"` + SortBy string `query:"sort_by" validate:"omitempty"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"` AreaId uint `query:"area_id" validate:"omitempty,number,gt=0"` LocationId uint `query:"location_id" validate:"omitempty,number,gt=0"` diff --git a/internal/utils/constant.go b/internal/utils/constant.go index 5ab236b0..bdbc53b6 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -250,19 +250,12 @@ func IsValidCustomerSupplierType(v string) bool { return false } -func NormalizeProjectFlockCategory(v string) (ProjectFlockCategory, bool) { - normalized := ProjectFlockCategory(strings.ToUpper(strings.TrimSpace(v))) - switch normalized { - case ProjectFlockCategoryGrowing, ProjectFlockCategoryLaying: - return normalized, true - default: - return "", false - } -} - func IsValidProjectFlockCategory(v string) bool { - _, ok := NormalizeProjectFlockCategory(v) - return ok + switch ProjectFlockCategory(v) { + case ProjectFlockCategoryGrowing, ProjectFlockCategoryLaying: + return true + } + return false } func IsValidSupplierCategory(v string) bool { From 8d0bd3724d43e3c00b028046b93abd4a7eb72ecb Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Tue, 21 Oct 2025 15:59:00 +0700 Subject: [PATCH 2/3] fix(BE): preload in projectflock.service --- .../production/project_flocks/services/projectflock.service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index f6ebf6e7..7dfddef9 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -75,7 +75,7 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB { Preload("Area"). Preload("Fcr"). Preload("Location"). - Preload("Kandangs.Location") + Preload("Kandangs") } func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { From 56b1134872f616c600ced35ad8898cfe52e4b9c6 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Wed, 22 Oct 2025 16:49:45 +0700 Subject: [PATCH 3/3] fix(BE): req body approval and fix projectflock.dto --- .../controllers/projectflock.controller.go | 24 +++-- .../project_flocks/dto/projectflock.dto.go | 35 +++++-- .../production/project_flocks/route.go | 2 +- .../services/projectflock.service.go | 92 ++++++++++++------- .../validations/projectflock.validation.go | 5 +- 5 files changed, 103 insertions(+), 55 deletions(-) diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index 31d0b9f0..a7de23fd 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -191,29 +191,33 @@ func (u *ProjectflockController) DeleteOne(c *fiber.Ctx) error { } func (u *ProjectflockController) Approval(c *fiber.Ctx) error { - param := c.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - req := new(validation.Approve) if err := c.BodyParser(req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - result, err := u.ProjectflockService.Approval(c, uint(id), req) + results, err := u.ProjectflockService.Approval(c, req) if err != nil { return err } + var ( + data interface{} + message = "Submit projectflock approval successfully" + ) + if len(results) == 1 { + data = dto.ToProjectFlockListDTO(results[0]) + } else { + message = "Submit projectflock approvals successfully" + data = dto.ToProjectFlockListDTOs(results) + } + return c.Status(fiber.StatusOK). JSON(response.Success{ Code: fiber.StatusOK, Status: "success", - Message: "Submit projectflock approval successfully", - Data: dto.ToProjectFlockListDTO(*result), + Message: message, + Data: data, }) } diff --git a/internal/modules/production/project_flocks/dto/projectflock.dto.go b/internal/modules/production/project_flocks/dto/projectflock.dto.go index e639e968..dff3bc61 100644 --- a/internal/modules/production/project_flocks/dto/projectflock.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock.dto.go @@ -58,6 +58,30 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { } } + var flockSummary *flockDTO.FlockBaseDTO + if e.Flock.Id != 0 { + mapped := flockDTO.ToFlockBaseDTO(e.Flock) + flockSummary = &mapped + } + + var areaSummary *areaDTO.AreaBaseDTO + if e.Area.Id != 0 { + mapped := areaDTO.ToAreaBaseDTO(e.Area) + areaSummary = &mapped + } + + var fcrSummary *fcrDTO.FcrBaseDTO + if e.Fcr.Id != 0 { + mapped := fcrDTO.ToFcrBaseDTO(e.Fcr) + fcrSummary = &mapped + } + + var locationSummary *locationDTO.LocationBaseDTO + if e.Location.Id != 0 { + mapped := locationDTO.ToLocationBaseDTO(e.Location) + locationSummary = &mapped + } + latestApproval := defaultProjectFlockLatestApproval(e) if e.LatestApproval != nil { snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval) @@ -66,8 +90,12 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { return ProjectFlockListDTO{ ProjectFlockBaseDTO: createProjectFlockBaseDTO(e), + Flock: flockSummary, + Area: areaSummary, Kandangs: kandangSummaries, Category: e.Category, + Fcr: fcrSummary, + Location: locationSummary, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, @@ -101,13 +129,6 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv result.StepName = label } } - if result.StepName == "" { - result.StepName = "Pengajuan" - } - - if !e.CreatedAt.IsZero() { - result.ActionAt = e.CreatedAt - } if e.CreatedUser.Id != 0 { result.ActionBy = userDTO.ToUserBaseDTO(e.CreatedUser) diff --git a/internal/modules/production/project_flocks/route.go b/internal/modules/production/project_flocks/route.go index 7282c020..bc9d8797 100644 --- a/internal/modules/production/project_flocks/route.go +++ b/internal/modules/production/project_flocks/route.go @@ -25,6 +25,6 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj route.Get("/:id", ctrl.GetOne) route.Patch("/:id", ctrl.UpdateOne) route.Delete("/:id", ctrl.DeleteOne) - route.Post("/:id/approvals", ctrl.Approval) + route.Post("/approvals", ctrl.Approval) route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary) } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 7dfddef9..b36b5774 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -29,7 +29,7 @@ type ProjectflockService interface { UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) DeleteOne(ctx *fiber.Ctx, id uint) error GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) - Approval(ctx *fiber.Ctx, id uint, req *validation.Approve) (*entity.ProjectFlock, error) + Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) } type projectflockService struct { @@ -501,17 +501,11 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id return s.GetOne(c, id) } -func (s projectflockService) Approval(c *fiber.Ctx, id uint, req *validation.Approve) (*entity.ProjectFlock, error) { +func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } - project, err := s.GetOne(c, id) - if err != nil { - s.Log.Errorf("Failed to fetch projectflock %d before approval: %+v", id, err) - return nil, err - } - actorID := uint(1) // TODO: change from auth context var action entity.ApprovalAction switch strings.ToUpper(strings.TrimSpace(req.Action)) { @@ -523,42 +517,58 @@ func (s projectflockService) Approval(c *fiber.Ctx, id uint, req *validation.App return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED") } + approvableIDs := uniqueUintSlice(req.ApprovableIds) + if len(approvableIDs) == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id") + } + step := utils.ProjectFlockStepPengajuan if action == entity.ApprovalActionApproved { step = utils.ProjectFlockStepAktif } - err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) - if _, err := approvalSvc.CreateApproval( - c.Context(), - utils.ApprovalWorkflowProjectFlock, - project.Id, - step, - &action, - actorID, - req.Notes, - ); err != nil { - return err - } - kandangRepoTx := kandangRepository.NewKandangRepository(dbTransaction) - switch action { - case entity.ApprovalActionApproved: - if err := kandangRepoTx.UpdateStatusByProjectFlockID( + projectRepoTx := repository.NewProjectflockRepository(dbTransaction) + + for _, approvableID := range approvableIDs { + if _, err := projectRepoTx.GetByID(c.Context(), approvableID, nil); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Projectflock %d not found", approvableID)) + } + return err + } + + if _, err := approvalSvc.CreateApproval( c.Context(), - project.Id, - utils.KandangStatusActive, + utils.ApprovalWorkflowProjectFlock, + approvableID, + step, + &action, + actorID, + req.Notes, ); err != nil { return err } - case entity.ApprovalActionRejected: - if err := kandangRepoTx.UpdateStatusByProjectFlockID( - c.Context(), - project.Id, - utils.KandangStatusNonActive, - ); err != nil { - return err + + switch action { + case entity.ApprovalActionApproved: + if err := kandangRepoTx.UpdateStatusByProjectFlockID( + c.Context(), + approvableID, + utils.KandangStatusActive, + ); err != nil { + return err + } + case entity.ApprovalActionRejected: + if err := kandangRepoTx.UpdateStatusByProjectFlockID( + c.Context(), + approvableID, + utils.KandangStatusNonActive, + ); err != nil { + return err + } } } @@ -566,14 +576,26 @@ func (s projectflockService) Approval(c *fiber.Ctx, id uint, req *validation.App }) if err != nil { + if fiberErr, ok := err.(*fiber.Error); ok { + return nil, fiberErr + } if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found") } - s.Log.Errorf("Failed to record approval for projectflock %d: %+v", id, err) + s.Log.Errorf("Failed to record approval for projectflocks %+v: %+v", approvableIDs, err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval") } - return s.GetOne(c, id) + updated := make([]entity.ProjectFlock, 0, len(approvableIDs)) + for _, approvableID := range approvableIDs { + project, err := s.GetOne(c, approvableID) + if err != nil { + return nil, err + } + updated = append(updated, *project) + } + + return updated, nil } func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error { diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index 9ee066af..f853c883 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -31,6 +31,7 @@ type Query struct { } type Approve struct { - Action string `json:"action" validate:"required_strict"` - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + Action string `json:"action" validate:"required_strict"` + ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` }