fix(BE): req body approval and fix projectflock.dto

This commit is contained in:
Hafizh A. Y
2025-10-22 16:49:45 +07:00
parent 8d0bd3724d
commit 56b1134872
5 changed files with 103 additions and 55 deletions
@@ -191,29 +191,33 @@ func (u *ProjectflockController) DeleteOne(c *fiber.Ctx) error {
} }
func (u *ProjectflockController) Approval(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) req := new(validation.Approve)
if err := c.BodyParser(req); err != nil { if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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 { if err != nil {
return err 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). return c.Status(fiber.StatusOK).
JSON(response.Success{ JSON(response.Success{
Code: fiber.StatusOK, Code: fiber.StatusOK,
Status: "success", Status: "success",
Message: "Submit projectflock approval successfully", Message: message,
Data: dto.ToProjectFlockListDTO(*result), Data: data,
}) })
} }
@@ -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) latestApproval := defaultProjectFlockLatestApproval(e)
if e.LatestApproval != nil { if e.LatestApproval != nil {
snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval) snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval)
@@ -66,8 +90,12 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
return ProjectFlockListDTO{ return ProjectFlockListDTO{
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e), ProjectFlockBaseDTO: createProjectFlockBaseDTO(e),
Flock: flockSummary,
Area: areaSummary,
Kandangs: kandangSummaries, Kandangs: kandangSummaries,
Category: e.Category, Category: e.Category,
Fcr: fcrSummary,
Location: locationSummary,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser, CreatedUser: createdUser,
@@ -101,13 +129,6 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv
result.StepName = label result.StepName = label
} }
} }
if result.StepName == "" {
result.StepName = "Pengajuan"
}
if !e.CreatedAt.IsZero() {
result.ActionAt = e.CreatedAt
}
if e.CreatedUser.Id != 0 { if e.CreatedUser.Id != 0 {
result.ActionBy = userDTO.ToUserBaseDTO(e.CreatedUser) result.ActionBy = userDTO.ToUserBaseDTO(e.CreatedUser)
@@ -25,6 +25,6 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
route.Get("/:id", ctrl.GetOne) route.Get("/:id", ctrl.GetOne)
route.Patch("/:id", ctrl.UpdateOne) route.Patch("/:id", ctrl.UpdateOne)
route.Delete("/:id", ctrl.DeleteOne) route.Delete("/:id", ctrl.DeleteOne)
route.Post("/:id/approvals", ctrl.Approval) route.Post("/approvals", ctrl.Approval)
route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary) route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary)
} }
@@ -29,7 +29,7 @@ type ProjectflockService interface {
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error)
DeleteOne(ctx *fiber.Ctx, id uint) error DeleteOne(ctx *fiber.Ctx, id uint) error
GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, 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 { type projectflockService struct {
@@ -501,17 +501,11 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
return s.GetOne(c, 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 { if err := s.Validate.Struct(req); err != nil {
return nil, err 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 actorID := uint(1) // TODO: change from auth context
var action entity.ApprovalAction var action entity.ApprovalAction
switch strings.ToUpper(strings.TrimSpace(req.Action)) { 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") 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 step := utils.ProjectFlockStepPengajuan
if action == entity.ApprovalActionApproved { if action == entity.ApprovalActionApproved {
step = utils.ProjectFlockStepAktif 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)) 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) kandangRepoTx := kandangRepository.NewKandangRepository(dbTransaction)
switch action { projectRepoTx := repository.NewProjectflockRepository(dbTransaction)
case entity.ApprovalActionApproved:
if err := kandangRepoTx.UpdateStatusByProjectFlockID( 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(), c.Context(),
project.Id, utils.ApprovalWorkflowProjectFlock,
utils.KandangStatusActive, approvableID,
step,
&action,
actorID,
req.Notes,
); err != nil { ); err != nil {
return err return err
} }
case entity.ApprovalActionRejected:
if err := kandangRepoTx.UpdateStatusByProjectFlockID( switch action {
c.Context(), case entity.ApprovalActionApproved:
project.Id, if err := kandangRepoTx.UpdateStatusByProjectFlockID(
utils.KandangStatusNonActive, c.Context(),
); err != nil { approvableID,
return err 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 err != nil {
if fiberErr, ok := err.(*fiber.Error); ok {
return nil, fiberErr
}
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found") 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 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 { func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
@@ -31,6 +31,7 @@ type Query struct {
} }
type Approve struct { type Approve struct {
Action string `json:"action" validate:"required_strict"` Action string `json:"action" validate:"required_strict"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
} }