mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'dev/hafizh' into 'feat/BE/US-82/approval-workflow'
[FIX/BE] Req body approval and fix projectflock.dto See merge request mbugroup/lti-api!34
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -63,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)
|
||||
@@ -71,7 +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,
|
||||
@@ -105,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)
|
||||
@@ -126,38 +143,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
@@ -500,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)) {
|
||||
@@ -522,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,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 {
|
||||
|
||||
@@ -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"`
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user