mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE): approval_workflow, adjusment project_flocks, common, and migration
This commit is contained in:
@@ -7,13 +7,14 @@ import (
|
||||
"strings"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -28,15 +29,18 @@ 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)
|
||||
}
|
||||
|
||||
type projectflockService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.ProjectflockRepository
|
||||
FlockRepo flockRepository.FlockRepository
|
||||
KandangRepo kandangRepository.KandangRepository
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.ProjectflockRepository
|
||||
FlockRepo flockRepository.FlockRepository
|
||||
KandangRepo kandangRepository.KandangRepository
|
||||
PivotRepo repository.ProjectFlockKandangRepository
|
||||
ApprovalSvc commonSvc.ApprovalService
|
||||
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
||||
}
|
||||
|
||||
type FlockPeriodSummary struct {
|
||||
@@ -49,15 +53,18 @@ func NewProjectflockService(
|
||||
flockRepo flockRepository.FlockRepository,
|
||||
kandangRepo kandangRepository.KandangRepository,
|
||||
pivotRepo repository.ProjectFlockKandangRepository,
|
||||
approvalSvc commonSvc.ApprovalService,
|
||||
validate *validator.Validate,
|
||||
) ProjectflockService {
|
||||
return &projectflockService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
FlockRepo: flockRepo,
|
||||
KandangRepo: kandangRepo,
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
FlockRepo: flockRepo,
|
||||
KandangRepo: kandangRepo,
|
||||
PivotRepo: pivotRepo,
|
||||
ApprovalSvc: approvalSvc,
|
||||
approvalWorkflow: utils.ApprovalWorkflowProjectFlock,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +75,7 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("Area").
|
||||
Preload("Fcr").
|
||||
Preload("Location").
|
||||
Preload("Kandangs")
|
||||
Preload("Kandangs.Location")
|
||||
}
|
||||
|
||||
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||
@@ -154,6 +161,27 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
||||
s.Log.Errorf("Failed to get projectflocks: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if s.ApprovalSvc != nil && len(projectflocks) > 0 {
|
||||
ids := make([]uint, len(projectflocks))
|
||||
for i, item := range projectflocks {
|
||||
ids[i] = item.Id
|
||||
}
|
||||
|
||||
latestMap, err := s.ApprovalSvc.LatestByTargets(c.Context(), s.approvalWorkflow, ids, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ActionUser")
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Warnf("Unable to load latest approvals for projectflocks: %+v", err)
|
||||
} else if len(latestMap) > 0 {
|
||||
for i := range projectflocks {
|
||||
if approval, ok := latestMap[projectflocks[i].Id]; ok {
|
||||
projectflocks[i].LatestApproval = approval
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return projectflocks, total, nil
|
||||
}
|
||||
|
||||
@@ -166,6 +194,23 @@ func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock
|
||||
s.Log.Errorf("Failed get projectflock by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.ApprovalSvc != nil {
|
||||
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ActionUser")
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
|
||||
} else if len(approvals) > 0 {
|
||||
if projectflock.LatestApproval == nil {
|
||||
latest := approvals[len(approvals)-1]
|
||||
projectflock.LatestApproval = &latest
|
||||
}
|
||||
} else {
|
||||
projectflock.LatestApproval = nil
|
||||
}
|
||||
}
|
||||
|
||||
return projectflock, nil
|
||||
}
|
||||
|
||||
@@ -183,11 +228,11 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
|
||||
}
|
||||
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Flock", ID: &req.FlockId, Exists: relationExistsChecker[entity.Flock](s.Repository.DB())},
|
||||
common.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: relationExistsChecker[entity.Area](s.Repository.DB())},
|
||||
common.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: relationExistsChecker[entity.Fcr](s.Repository.DB())},
|
||||
common.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: relationExistsChecker[entity.Location](s.Repository.DB())},
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Flock", ID: &req.FlockId, Exists: relationExistsChecker[entity.Flock](s.Repository.DB())},
|
||||
commonSvc.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: relationExistsChecker[entity.Area](s.Repository.DB())},
|
||||
commonSvc.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: relationExistsChecker[entity.Fcr](s.Repository.DB())},
|
||||
commonSvc.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: relationExistsChecker[entity.Location](s.Repository.DB())},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,19 +254,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
}
|
||||
}
|
||||
|
||||
tx := s.Repository.DB().Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||
}
|
||||
|
||||
projectRepo := repository.NewProjectflockRepository(tx)
|
||||
nextPeriod, err := projectRepo.GetNextPeriodForFlock(c.Context(), req.FlockId)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
s.Log.Errorf("Failed to determine next period for flock %d: %+v", req.FlockId, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine next period")
|
||||
}
|
||||
|
||||
createBody := &entity.ProjectFlock{
|
||||
FlockId: req.FlockId,
|
||||
AreaId: req.AreaId,
|
||||
@@ -232,8 +264,60 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
CreatedBy: 1,
|
||||
}
|
||||
|
||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
tx.Rollback()
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
projectRepo := repository.NewProjectflockRepository(tx)
|
||||
// kandangRepo := kandangRepository.NewKandangRepository(tx)
|
||||
|
||||
period, err := projectRepo.GetNextPeriodForFlock(c.Context(), req.FlockId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createBody.Period = period
|
||||
|
||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kandangUpdates := make([]*entity.Kandang, len(kandangs))
|
||||
// for i := range kandangs {
|
||||
// kandangs[i].ProjectFlockId = &createBody.Id
|
||||
// kandangUpdates[i] = &kandangs[i]
|
||||
// }
|
||||
// if err := kandangRepo.UpdateMany(
|
||||
// c.Context(),
|
||||
// kandangUpdates,
|
||||
// func(db *gorm.DB) *gorm.DB {
|
||||
// return db.Select("project_flock_id")
|
||||
// },
|
||||
// ); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if err := tx.Model(&entity.Kandang{}).
|
||||
Where("id IN ?", kandangIDs).
|
||||
Updates(map[string]any{
|
||||
"project_flock_id": createBody.Id,
|
||||
"status": string(utils.KandangStatusPengajuan),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actorID := uint(1) //TODO: Change From Auth
|
||||
action := entity.ApprovalActionCreated
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||
_, err = approvalSvcTx.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowProjectFlock,
|
||||
createBody.Id,
|
||||
utils.ProjectFlockStepPengajuan,
|
||||
&action,
|
||||
actorID,
|
||||
nil,
|
||||
)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
||||
}
|
||||
@@ -268,13 +352,14 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
s.Log.Errorf("Failed to fetch projectflock %d before update: %+v", id, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
var relationChecks []common.RelationCheck
|
||||
hasBodyChanges := false
|
||||
var relationChecks []commonSvc.RelationCheck
|
||||
|
||||
if req.FlockId != nil {
|
||||
updateBody["flock_id"] = *req.FlockId
|
||||
relationChecks = append(relationChecks, common.RelationCheck{
|
||||
hasBodyChanges = true
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "Flock",
|
||||
ID: req.FlockId,
|
||||
Exists: relationExistsChecker[entity.Flock](s.Repository.DB()),
|
||||
@@ -282,7 +367,8 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
if req.AreaId != nil {
|
||||
updateBody["area_id"] = *req.AreaId
|
||||
relationChecks = append(relationChecks, common.RelationCheck{
|
||||
hasBodyChanges = true
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "Area",
|
||||
ID: req.AreaId,
|
||||
Exists: relationExistsChecker[entity.Area](s.Repository.DB()),
|
||||
@@ -297,7 +383,8 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
if req.FcrId != nil {
|
||||
updateBody["fcr_id"] = *req.FcrId
|
||||
relationChecks = append(relationChecks, common.RelationCheck{
|
||||
hasBodyChanges = true
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "FCR",
|
||||
ID: req.FcrId,
|
||||
Exists: relationExistsChecker[entity.Fcr](s.Repository.DB()),
|
||||
@@ -305,7 +392,8 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
if req.LocationId != nil {
|
||||
updateBody["location_id"] = *req.LocationId
|
||||
relationChecks = append(relationChecks, common.RelationCheck{
|
||||
hasBodyChanges = true
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "Location",
|
||||
ID: req.LocationId,
|
||||
Exists: relationExistsChecker[entity.Location](s.Repository.DB()),
|
||||
@@ -313,16 +401,19 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
if req.Period != nil {
|
||||
updateBody["period"] = *req.Period
|
||||
hasBodyChanges = true
|
||||
}
|
||||
|
||||
if len(relationChecks) > 0 {
|
||||
if err := common.EnsureRelations(c.Context(), relationChecks...); err != nil {
|
||||
if err := commonSvc.EnsureRelations(c.Context(), relationChecks...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var newKandangIDs []uint
|
||||
hasKandangChanges := false
|
||||
if req.KandangIds != nil {
|
||||
hasKandangChanges = true
|
||||
if len(req.KandangIds) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids cannot be empty")
|
||||
}
|
||||
@@ -344,46 +435,47 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
}
|
||||
|
||||
tx := s.Repository.DB().Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||
hasChanges := hasBodyChanges || hasKandangChanges
|
||||
if !hasChanges {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
projectRepo := repository.NewProjectflockRepository(tx)
|
||||
if len(updateBody) > 0 {
|
||||
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
tx.Rollback()
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
projectRepo := repository.NewProjectflockRepository(tx)
|
||||
|
||||
if len(updateBody) > 0 {
|
||||
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Log.Errorf("Failed to update projectflock: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if req.KandangIds != nil {
|
||||
existingIDs := make(map[uint]struct{}, len(existing.Kandangs))
|
||||
for _, k := range existing.Kandangs {
|
||||
existingIDs[k.Id] = struct{}{}
|
||||
}
|
||||
newSet := make(map[uint]struct{}, len(newKandangIDs))
|
||||
for _, id := range newKandangIDs {
|
||||
newSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
var toDetach []uint
|
||||
for id := range existingIDs {
|
||||
if _, ok := newSet[id]; !ok {
|
||||
toDetach = append(toDetach, id)
|
||||
} else {
|
||||
if _, err := projectRepo.GetByID(c.Context(), id, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var toAttach []uint
|
||||
for id := range newSet {
|
||||
if _, ok := existingIDs[id]; !ok {
|
||||
toAttach = append(toAttach, id)
|
||||
if req.KandangIds != nil {
|
||||
existingIDs := make(map[uint]struct{}, len(existing.Kandangs))
|
||||
for _, k := range existing.Kandangs {
|
||||
existingIDs[k.Id] = struct{}{}
|
||||
}
|
||||
newSet := make(map[uint]struct{}, len(newKandangIDs))
|
||||
for _, kid := range newKandangIDs {
|
||||
newSet[kid] = struct{}{}
|
||||
}
|
||||
|
||||
var toDetach []uint
|
||||
for kid := range existingIDs {
|
||||
if _, ok := newSet[kid]; !ok {
|
||||
toDetach = append(toDetach, kid)
|
||||
}
|
||||
}
|
||||
|
||||
var toAttach []uint
|
||||
for kid := range newSet {
|
||||
if _, ok := existingIDs[kid]; !ok {
|
||||
toAttach = append(toAttach, kid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toDetach) > 0 {
|
||||
if err := s.detachKandangs(c.Context(), tx, id, toDetach, false); err != nil {
|
||||
@@ -437,18 +529,21 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := repository.NewProjectflockRepository(tx).DeleteOne(c.Context(), id); err != nil {
|
||||
tx.Rollback()
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||
if err := repository.NewProjectflockRepository(tx).DeleteOne(c.Context(), id); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.Log.Errorf("Failed to delete projectflock: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return fiberErr
|
||||
}
|
||||
s.Log.Errorf("Failed to delete projectflock %d: %+v", id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user