mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
776 lines
24 KiB
Go
776 lines
24 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
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"
|
|
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"
|
|
"github.com/sirupsen/logrus"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type ProjectflockService interface {
|
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
|
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
|
|
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, req *validation.Approve) ([]entity.ProjectFlock, error)
|
|
}
|
|
|
|
type projectflockService struct {
|
|
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 {
|
|
Flock entity.Flock
|
|
NextPeriod int
|
|
}
|
|
|
|
func NewProjectflockService(
|
|
repo repository.ProjectflockRepository,
|
|
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,
|
|
PivotRepo: pivotRepo,
|
|
ApprovalSvc: approvalSvc,
|
|
approvalWorkflow: utils.ApprovalWorkflowProjectFlock,
|
|
}
|
|
}
|
|
|
|
func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
|
|
return db.
|
|
Preload("CreatedUser").
|
|
Preload("Flock").
|
|
Preload("Area").
|
|
Preload("Fcr").
|
|
Preload("Location").
|
|
Preload("Kandangs")
|
|
}
|
|
|
|
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
|
if err := s.Validate.Struct(params); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
if params.Page <= 0 {
|
|
params.Page = 1
|
|
}
|
|
if params.Limit <= 0 {
|
|
params.Limit = 10
|
|
}
|
|
|
|
offset := (params.Page - 1) * params.Limit
|
|
|
|
projectflocks, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
|
db = s.withRelations(db)
|
|
|
|
if params.AreaId > 0 {
|
|
db = db.Where("project_flocks.area_id = ?", params.AreaId)
|
|
}
|
|
if params.LocationId > 0 {
|
|
db = db.Where("project_flocks.location_id = ?", params.LocationId)
|
|
}
|
|
if params.Period > 0 {
|
|
db = db.Where("project_flocks.period = ?", params.Period)
|
|
}
|
|
if len(params.KandangIds) > 0 {
|
|
db = db.Where("EXISTS (SELECT 1 FROM kandangs WHERE kandangs.project_flock_id = project_flocks.id AND kandangs.id IN ?)", params.KandangIds)
|
|
}
|
|
|
|
if params.Search != "" {
|
|
normalizedSearch := strings.ToLower(strings.TrimSpace(params.Search))
|
|
if normalizedSearch == "" {
|
|
for _, expr := range s.buildOrderExpressions(params.SortBy, params.SortOrder) {
|
|
db = db.Order(expr)
|
|
}
|
|
return db
|
|
}
|
|
likeQuery := "%" + normalizedSearch + "%"
|
|
db = db.
|
|
Joins("LEFT JOIN flocks ON flocks.id = project_flocks.flock_id").
|
|
Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
|
|
Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id").
|
|
Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
|
|
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
|
|
Where(`
|
|
LOWER(flocks.name) LIKE ?
|
|
OR LOWER(areas.name) LIKE ?
|
|
OR LOWER(project_flocks.category) LIKE ?
|
|
OR LOWER(fcrs.name) LIKE ?
|
|
OR LOWER(locations.name) LIKE ?
|
|
OR LOWER(locations.address) LIKE ?
|
|
OR LOWER(created_users.name) LIKE ?
|
|
OR LOWER(created_users.email) LIKE ?
|
|
OR LOWER(CAST(project_flocks.period AS TEXT)) LIKE ?
|
|
OR EXISTS (
|
|
SELECT 1 FROM kandangs
|
|
WHERE kandangs.project_flock_id = project_flocks.id
|
|
AND LOWER(kandangs.name) LIKE ?
|
|
)
|
|
`,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
likeQuery,
|
|
)
|
|
}
|
|
for _, expr := range s.buildOrderExpressions(params.SortBy, params.SortOrder) {
|
|
db = db.Order(expr)
|
|
}
|
|
return db
|
|
})
|
|
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
|
projectflock, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
|
}
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error) {
|
|
if err := s.Validate.Struct(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cat := strings.ToUpper(req.Category)
|
|
if !utils.IsValidProjectFlockCategory(cat) {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
|
|
}
|
|
|
|
if len(req.KandangIds) == 0 {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
kandangIDs := uniqueUintSlice(req.KandangIds)
|
|
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
|
}
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandangs")
|
|
}
|
|
if len(kandangs) != len(kandangIDs) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
|
}
|
|
for _, kandang := range kandangs {
|
|
if kandang.ProjectFlockId != nil {
|
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang %s sudah memiliki project flock", kandang.Name))
|
|
}
|
|
}
|
|
|
|
createBody := &entity.ProjectFlock{
|
|
FlockId: req.FlockId,
|
|
AreaId: req.AreaId,
|
|
Category: cat,
|
|
FcrId: req.FcrId,
|
|
LocationId: req.LocationId,
|
|
CreatedBy: 1,
|
|
}
|
|
|
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
|
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
|
|
|
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
|
|
}
|
|
|
|
if err := s.attachKandangs(c.Context(), dbTransaction, createBody.Id, kandangIDs); err != nil {
|
|
return err
|
|
}
|
|
|
|
actorID := uint(1) //TODO: Change From Auth
|
|
action := entity.ApprovalActionCreated
|
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
|
_, 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")
|
|
}
|
|
s.Log.Errorf("Failed to create projectflock: %+v", err)
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetOne(c, createBody.Id)
|
|
}
|
|
|
|
func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) {
|
|
if err := s.Validate.Struct(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
|
}
|
|
if err != nil {
|
|
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)
|
|
hasBodyChanges := false
|
|
var relationChecks []commonSvc.RelationCheck
|
|
|
|
if req.FlockId != nil {
|
|
updateBody["flock_id"] = *req.FlockId
|
|
hasBodyChanges = true
|
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
|
Name: "Flock",
|
|
ID: req.FlockId,
|
|
Exists: relationExistsChecker[entity.Flock](s.Repository.DB()),
|
|
})
|
|
}
|
|
if req.AreaId != nil {
|
|
updateBody["area_id"] = *req.AreaId
|
|
hasBodyChanges = true
|
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
|
Name: "Area",
|
|
ID: req.AreaId,
|
|
Exists: relationExistsChecker[entity.Area](s.Repository.DB()),
|
|
})
|
|
}
|
|
if req.Category != nil {
|
|
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
|
|
hasBodyChanges = true
|
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
|
Name: "FCR",
|
|
ID: req.FcrId,
|
|
Exists: relationExistsChecker[entity.Fcr](s.Repository.DB()),
|
|
})
|
|
}
|
|
if req.LocationId != nil {
|
|
updateBody["location_id"] = *req.LocationId
|
|
hasBodyChanges = true
|
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
|
Name: "Location",
|
|
ID: req.LocationId,
|
|
Exists: relationExistsChecker[entity.Location](s.Repository.DB()),
|
|
})
|
|
}
|
|
|
|
if len(relationChecks) > 0 {
|
|
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")
|
|
}
|
|
newKandangIDs = uniqueUintSlice(req.KandangIds)
|
|
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), newKandangIDs, nil)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
|
}
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandangs")
|
|
}
|
|
if len(kandangs) != len(newKandangIDs) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
|
}
|
|
for _, k := range kandangs {
|
|
if k.ProjectFlockId != nil && *k.ProjectFlockId != id {
|
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang %s sudah terikat dengan project flock lain", k.Name))
|
|
}
|
|
}
|
|
}
|
|
|
|
hasChanges := hasBodyChanges || hasKandangChanges
|
|
if !hasChanges {
|
|
return s.GetOne(c, id)
|
|
}
|
|
|
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
|
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
|
|
|
if len(updateBody) > 0 {
|
|
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err := projectRepo.GetByID(c.Context(), id, nil); err != nil {
|
|
return 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 _, 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(), dbTransaction, id, toDetach, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(toAttach) > 0 {
|
|
if err := s.attachKandangs(c.Context(), dbTransaction, id, toAttach); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if hasChanges {
|
|
actorID := uint(1) //TODO: Change From Auth
|
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
|
if approvalSvc != nil {
|
|
latestBeforeReset, err := approvalSvc.LatestByTarget(c.Context(), s.approvalWorkflow, id, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
shouldRecordUpdate := latestBeforeReset == nil ||
|
|
latestBeforeReset.StepNumber != uint16(utils.ProjectFlockStepPengajuan) ||
|
|
latestBeforeReset.Action == nil ||
|
|
(latestBeforeReset.Action != nil && *latestBeforeReset.Action != entity.ApprovalActionUpdated)
|
|
|
|
if shouldRecordUpdate {
|
|
action := entity.ApprovalActionUpdated
|
|
if _, err := approvalSvc.CreateApproval(
|
|
c.Context(),
|
|
utils.ApprovalWorkflowProjectFlock,
|
|
id,
|
|
utils.ProjectFlockStepPengajuan,
|
|
&action,
|
|
actorID,
|
|
nil,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
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 update projectflock %d: %+v", id, err)
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetOne(c, id)
|
|
}
|
|
|
|
func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) {
|
|
if err := s.Validate.Struct(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
actorID := uint(1) // TODO: change from auth context
|
|
var action entity.ApprovalAction
|
|
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
|
case string(entity.ApprovalActionRejected):
|
|
action = entity.ApprovalActionRejected
|
|
case string(entity.ApprovalActionApproved):
|
|
action = entity.ApprovalActionApproved
|
|
default:
|
|
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 {
|
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
|
kandangRepoTx := kandangRepository.NewKandangRepository(dbTransaction)
|
|
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(),
|
|
utils.ApprovalWorkflowProjectFlock,
|
|
approvableID,
|
|
step,
|
|
&action,
|
|
actorID,
|
|
req.Notes,
|
|
); 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
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
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 projectflocks %+v: %+v", approvableIDs, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
|
}
|
|
|
|
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 {
|
|
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
|
}
|
|
if err != nil {
|
|
s.Log.Errorf("Failed to fetch projectflock %d before delete: %+v", id, err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
|
}
|
|
|
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
|
if len(existing.Kandangs) > 0 {
|
|
ids := make([]uint, len(existing.Kandangs))
|
|
for i, k := range existing.Kandangs {
|
|
ids[i] = k.Id
|
|
}
|
|
if err := s.detachKandangs(c.Context(), dbTransaction, id, ids, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := repository.NewProjectflockRepository(dbTransaction).DeleteOne(c.Context(), id); err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
|
}
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) {
|
|
flock, err := s.FlockRepo.GetByID(c.Context(), flockID, func(db *gorm.DB) *gorm.DB {
|
|
return db.Preload("CreatedUser")
|
|
})
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Flock not found")
|
|
}
|
|
if err != nil {
|
|
s.Log.Errorf("Failed get flock %d for period summary: %+v", flockID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
|
}
|
|
|
|
maxPeriod, err := s.Repository.GetMaxPeriodByFlock(c.Context(), flockID)
|
|
if err != nil {
|
|
s.Log.Errorf("Failed to compute next period for flock %d: %+v", flockID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to compute next period")
|
|
}
|
|
|
|
return &FlockPeriodSummary{
|
|
Flock: *flock,
|
|
NextPeriod: maxPeriod + 1,
|
|
}, 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
|
|
}
|
|
|
|
func relationExistsChecker[T any](db *gorm.DB) func(context.Context, uint) (bool, error) {
|
|
return func(ctx context.Context, id uint) (bool, error) {
|
|
return commonRepo.Exists[T](ctx, db, id)
|
|
}
|
|
}
|
|
|
|
func (s projectflockService) buildOrderExpressions(sortBy, sortOrder string) []string {
|
|
direction := "ASC"
|
|
if strings.ToLower(sortOrder) == "desc" {
|
|
direction = "DESC"
|
|
}
|
|
|
|
switch sortBy {
|
|
case "area":
|
|
return []string{
|
|
fmt.Sprintf("(SELECT name FROM areas WHERE areas.id = project_flocks.area_id) %s", direction),
|
|
fmt.Sprintf("project_flocks.id %s", direction),
|
|
}
|
|
case "location":
|
|
return []string{
|
|
fmt.Sprintf("(SELECT name FROM locations WHERE locations.id = project_flocks.location_id) %s", direction),
|
|
fmt.Sprintf("project_flocks.id %s", direction),
|
|
}
|
|
case "kandangs":
|
|
return []string{
|
|
fmt.Sprintf("(SELECT COUNT(*) FROM kandangs WHERE kandangs.project_flock_id = project_flocks.id) %s", direction),
|
|
fmt.Sprintf("project_flocks.id %s", direction),
|
|
}
|
|
case "period":
|
|
return []string{
|
|
fmt.Sprintf("project_flocks.period %s", direction),
|
|
fmt.Sprintf("project_flocks.id %s", direction),
|
|
}
|
|
default:
|
|
return []string{
|
|
"project_flocks.created_at DESC",
|
|
"project_flocks.updated_at DESC",
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint) error {
|
|
if len(kandangIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if err := dbTransaction.Model(&entity.Kandang{}).
|
|
Where("id IN ?", kandangIDs).
|
|
Updates(map[string]any{
|
|
"project_flock_id": projectFlockID,
|
|
"status": string(utils.KandangStatusPengajuan),
|
|
}).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
|
}
|
|
|
|
pivotRepo := s.pivotRepoWithTx(dbTransaction)
|
|
records := make([]*entity.ProjectFlockKandang, len(kandangIDs))
|
|
for i, id := range kandangIDs {
|
|
records[i] = &entity.ProjectFlockKandang{
|
|
ProjectFlockId: projectFlockID,
|
|
KandangId: id,
|
|
}
|
|
}
|
|
if err := pivotRepo.CreateMany(ctx, records); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint, resetStatus bool) error {
|
|
if len(kandangIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
updates := map[string]any{"project_flock_id": nil}
|
|
if resetStatus {
|
|
updates["status"] = string(utils.KandangStatusNonActive)
|
|
}
|
|
|
|
if err := dbTransaction.Model(&entity.Kandang{}).
|
|
Where("id IN ?", kandangIDs).
|
|
Updates(updates).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
|
}
|
|
|
|
if err := s.pivotRepoWithTx(dbTransaction).DeleteMany(ctx, projectFlockID, kandangIDs); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s projectflockService) pivotRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository {
|
|
if s.PivotRepo == nil {
|
|
return repository.NewProjectFlockKandangRepository(dbTransaction)
|
|
}
|
|
return s.PivotRepo.WithTx(dbTransaction)
|
|
}
|