mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'development-before-sso' of https://gitlab.com/mbugroup/lti-api into refactor-to-serve/with-middleware
This commit is contained in:
@@ -10,10 +10,13 @@ import (
|
||||
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"
|
||||
auth "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||
warehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||
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"
|
||||
@@ -29,21 +32,24 @@ type ProjectflockService interface {
|
||||
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)
|
||||
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error)
|
||||
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, 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
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.ProjectflockRepository
|
||||
FlockRepo flockRepository.FlockRepository
|
||||
KandangRepo kandangRepository.KandangRepository
|
||||
WarehouseRepo warehouseRepository.WarehouseRepository
|
||||
ProductWarehouseRepo productWarehouseRepository.ProductWarehouseRepository
|
||||
PivotRepo repository.ProjectFlockKandangRepository
|
||||
ApprovalSvc commonSvc.ApprovalService
|
||||
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
||||
}
|
||||
|
||||
type FlockPeriodSummary struct {
|
||||
@@ -56,31 +62,25 @@ func NewProjectflockService(
|
||||
flockRepo flockRepository.FlockRepository,
|
||||
kandangRepo kandangRepository.KandangRepository,
|
||||
pivotRepo repository.ProjectFlockKandangRepository,
|
||||
warehouseRepo warehouseRepository.WarehouseRepository,
|
||||
productWarehouseRepo productWarehouseRepository.ProductWarehouseRepository,
|
||||
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,
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
FlockRepo: flockRepo,
|
||||
KandangRepo: kandangRepo,
|
||||
WarehouseRepo: warehouseRepo,
|
||||
ProductWarehouseRepo: productWarehouseRepo,
|
||||
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
|
||||
@@ -95,79 +95,11 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
||||
|
||||
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 project_flock_kandangs pfk
|
||||
WHERE pfk.project_flock_id = project_flocks.id
|
||||
AND pfk.kandang_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
|
||||
})
|
||||
projectflocks, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params)
|
||||
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get projectflocks: %+v", err)
|
||||
return nil, 0, err
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flocks")
|
||||
}
|
||||
|
||||
if s.ApprovalSvc != nil && len(projectflocks) > 0 {
|
||||
@@ -194,13 +126,13 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
||||
}
|
||||
|
||||
func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
||||
projectflock, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
projectflock, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
|
||||
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
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||
}
|
||||
|
||||
if s.ApprovalSvc != nil {
|
||||
@@ -227,6 +159,11 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actorID, err := actorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cat := strings.ToUpper(req.Category)
|
||||
if !utils.IsValidProjectFlockCategory(cat) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
|
||||
@@ -236,15 +173,28 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
|
||||
}
|
||||
|
||||
baseName := strings.TrimSpace(req.FlockName)
|
||||
if baseName == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Flock name cannot be empty")
|
||||
}
|
||||
|
||||
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())},
|
||||
commonSvc.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: s.Repository.AreaExists},
|
||||
commonSvc.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: s.Repository.FcrExists},
|
||||
commonSvc.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
canonicalBase := baseName
|
||||
if s.FlockRepo != nil {
|
||||
baseFlock, err := s.ensureFlockByName(c.Context(), actorID, baseName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canonicalBase = baseFlock.Name
|
||||
}
|
||||
|
||||
kandangIDs := uniqueUintSlice(req.KandangIds)
|
||||
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
||||
if err != nil {
|
||||
@@ -257,19 +207,13 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||
}
|
||||
// larang kalau ada yg sudah terikat ke project lain
|
||||
if linked, err := s.anyKandangLinkedToOtherProject(c.Context(), s.Repository.DB(), kandangIDs, nil); err != nil {
|
||||
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), kandangIDs, nil); err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||
} else if linked {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, "Beberapa kandang sudah terikat dengan project flock lain")
|
||||
}
|
||||
|
||||
actorID, err := actorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createBody := &entity.ProjectFlock{
|
||||
FlockId: req.FlockId,
|
||||
AreaId: req.AreaId,
|
||||
Category: cat,
|
||||
FcrId: req.FcrId,
|
||||
@@ -280,11 +224,16 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
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)
|
||||
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), canonicalBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createBody.Period = period
|
||||
generatedName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, nextSeq, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createBody.FlockName = generatedName
|
||||
createBody.Period = seq
|
||||
|
||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
return err
|
||||
@@ -309,11 +258,14 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return nil, fiberErr
|
||||
}
|
||||
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 nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create project flock")
|
||||
}
|
||||
|
||||
return s.GetOne(c, createBody.Id)
|
||||
@@ -324,7 +276,12 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
actorID, err := actorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||
}
|
||||
@@ -335,15 +292,28 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
updateBody := make(map[string]any)
|
||||
hasBodyChanges := false
|
||||
var relationChecks []commonSvc.RelationCheck
|
||||
existingBase := pfutils.DeriveBaseName(existing.FlockName)
|
||||
targetBaseName := existingBase
|
||||
needFlockNameRegenerate := false
|
||||
|
||||
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.FlockName != nil {
|
||||
trimmed := strings.TrimSpace(*req.FlockName)
|
||||
if trimmed == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Flock name cannot be empty")
|
||||
}
|
||||
canonicalBase := trimmed
|
||||
if s.FlockRepo != nil {
|
||||
flockEntity, err := s.ensureFlockByName(c.Context(), actorID, trimmed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canonicalBase = flockEntity.Name
|
||||
}
|
||||
if !strings.EqualFold(canonicalBase, existingBase) {
|
||||
needFlockNameRegenerate = true
|
||||
targetBaseName = canonicalBase
|
||||
hasBodyChanges = true
|
||||
}
|
||||
}
|
||||
if req.AreaId != nil {
|
||||
updateBody["area_id"] = *req.AreaId
|
||||
@@ -351,7 +321,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "Area",
|
||||
ID: req.AreaId,
|
||||
Exists: relationExistsChecker[entity.Area](s.Repository.DB()),
|
||||
Exists: s.Repository.AreaExists,
|
||||
})
|
||||
}
|
||||
if req.Category != nil {
|
||||
@@ -368,7 +338,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "FCR",
|
||||
ID: req.FcrId,
|
||||
Exists: relationExistsChecker[entity.Fcr](s.Repository.DB()),
|
||||
Exists: s.Repository.FcrExists,
|
||||
})
|
||||
}
|
||||
if req.LocationId != nil {
|
||||
@@ -377,7 +347,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||
Name: "Location",
|
||||
ID: req.LocationId,
|
||||
Exists: relationExistsChecker[entity.Location](s.Repository.DB()),
|
||||
Exists: s.Repository.LocationExists,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -405,7 +375,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
if len(kandangs) != len(newKandangIDs) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||
}
|
||||
if linked, err := s.anyKandangLinkedToOtherProject(c.Context(), s.Repository.DB(), newKandangIDs, &id); err != nil {
|
||||
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), newKandangIDs, &id); err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||
} else if linked {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, "Beberapa kandang sudah terikat dengan project flock lain")
|
||||
@@ -418,14 +388,32 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
actorID, authErr := actorIDFromContext(c)
|
||||
if authErr != nil {
|
||||
return nil, authErr
|
||||
}
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
||||
|
||||
baseForGeneration := targetBaseName
|
||||
if strings.TrimSpace(baseForGeneration) == "" {
|
||||
baseForGeneration = existingBase
|
||||
}
|
||||
if strings.TrimSpace(baseForGeneration) == "" {
|
||||
baseForGeneration = strings.TrimSpace(existing.FlockName)
|
||||
}
|
||||
|
||||
if needFlockNameRegenerate {
|
||||
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), baseForGeneration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, baseForGeneration, nextSeq, &id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateBody["flock_name"] = newName
|
||||
if seq != existing.Period {
|
||||
updateBody["period"] = seq
|
||||
}
|
||||
}
|
||||
|
||||
if len(updateBody) > 0 {
|
||||
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
return err
|
||||
@@ -513,7 +501,10 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update projectflock %d: %+v", id, err)
|
||||
return nil, err
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update project flock")
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
@@ -621,7 +612,7 @@ func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]
|
||||
}
|
||||
|
||||
func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
existing, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||
}
|
||||
@@ -655,38 +646,32 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
return fiberErr
|
||||
}
|
||||
s.Log.Errorf("Failed to delete projectflock %d: %+v", id, err)
|
||||
return err
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete project flock")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s projectflockService) GetProjectFlockKandang(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, error) {
|
||||
// keep for backward compatibility; delegate to new consolidated method
|
||||
return s.GetProjectFlockKandangByParams(ctx, fmt.Sprintf("%d", id), "", "")
|
||||
}
|
||||
|
||||
func actorIDFromContext(c *fiber.Ctx) (uint, error) {
|
||||
user, ok := auth.AuthenticatedUser(c)
|
||||
if !ok || user == nil || user.Id == 0 {
|
||||
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||
}
|
||||
return user.Id, nil
|
||||
}
|
||||
|
||||
func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) {
|
||||
func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error) {
|
||||
|
||||
pfk, err := s.PivotRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||
return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||
}
|
||||
return nil, err
|
||||
s.Log.Errorf("Failed to fetch project_flock_kandang by project %d and kandang %d: %+v", projectFlockID, kandangID, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||
}
|
||||
return pfk, nil
|
||||
|
||||
availableQuantity, err := s.GetAvailableDocQuantity(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return pfk, availableQuantity, nil
|
||||
}
|
||||
|
||||
func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) {
|
||||
func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, float64, error) {
|
||||
idStr = strings.TrimSpace(idStr)
|
||||
projectFlockIdStr = strings.TrimSpace(projectFlockIdStr)
|
||||
kandangIdStr = strings.TrimSpace(kandangIdStr)
|
||||
@@ -694,52 +679,107 @@ func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idSt
|
||||
if idStr != "" {
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil || id <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
pfk, err := s.PivotRepo.GetByID(ctx.Context(), uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||
return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||
}
|
||||
return nil, err
|
||||
s.Log.Errorf("Failed to fetch project_flock_kandang %d: %+v", id, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||
}
|
||||
return pfk, nil
|
||||
|
||||
availableQuantity, err := s.GetAvailableDocQuantity(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return pfk, availableQuantity, nil
|
||||
}
|
||||
|
||||
if projectFlockIdStr == "" || kandangIdStr == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters")
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters")
|
||||
}
|
||||
pfid, err := strconv.Atoi(projectFlockIdStr)
|
||||
if err != nil || pfid <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id")
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id")
|
||||
}
|
||||
kid, err := strconv.Atoi(kandangIdStr)
|
||||
if err != nil || kid <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id")
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id")
|
||||
}
|
||||
return s.GetProjectFlockKandangByProjectAndKandang(ctx, uint(pfid), uint(kid))
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error) {
|
||||
|
||||
wh, err := s.WarehouseRepo.GetByKandangID(ctx.Context(), kandangID)
|
||||
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")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
maxPeriod, err := s.Repository.GetMaxPeriodByFlock(c.Context(), flockID)
|
||||
productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), "DOC", wh.Id)
|
||||
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 0, err
|
||||
}
|
||||
|
||||
total := 0.0
|
||||
for _, pw := range productWarehouses {
|
||||
total += pw.Quantity
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, projectFlockKandangID uint) (*FlockPeriodSummary, error) {
|
||||
if projectFlockKandangID == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
||||
}
|
||||
|
||||
pivot, err := s.pivotRepo().GetByID(c.Context(), projectFlockKandangID)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch project_flock_kandang %d: %+v", projectFlockKandangID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||
}
|
||||
|
||||
var baseName string
|
||||
var referenceFlock *entity.Flock
|
||||
if pivot.ProjectFlock.Id != 0 {
|
||||
baseName = pfutils.DeriveBaseName(pivot.ProjectFlock.FlockName)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(baseName) != "" {
|
||||
referenceFlock, err = s.FlockRepo.GetByName(c.Context(), baseName)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to fetch flock %q: %+v", baseName, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
||||
}
|
||||
}
|
||||
|
||||
if referenceFlock == nil {
|
||||
referenceFlock = &entity.Flock{Name: pivot.ProjectFlock.FlockName}
|
||||
}
|
||||
|
||||
maxPeriod := pivot.ProjectFlock.Period
|
||||
if strings.TrimSpace(baseName) != "" {
|
||||
if headerMax, err := s.Repository.GetMaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
||||
s.Log.Warnf("Unable to compute header period for base %q: %+v", baseName, err)
|
||||
} else if headerMax > maxPeriod {
|
||||
maxPeriod = headerMax
|
||||
}
|
||||
|
||||
if pivotMax, err := s.pivotRepo().MaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
||||
s.Log.Warnf("Unable to compute pivot period for base %q: %+v", baseName, err)
|
||||
} else if pivotMax > maxPeriod {
|
||||
maxPeriod = pivotMax
|
||||
}
|
||||
}
|
||||
|
||||
return &FlockPeriodSummary{
|
||||
Flock: *flock,
|
||||
Flock: *referenceFlock,
|
||||
NextPeriod: maxPeriod + 1,
|
||||
}, nil
|
||||
}
|
||||
@@ -757,45 +797,64 @@ func uniqueUintSlice(values []uint) []uint {
|
||||
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) generateSequentialFlockName(ctx context.Context, repo repository.ProjectflockRepository, baseName string, startNumber int, excludeID *uint) (string, int, error) {
|
||||
name := strings.TrimSpace(baseName)
|
||||
if name == "" {
|
||||
return "", 0, fiber.NewError(fiber.StatusBadRequest, "Base flock name cannot be empty")
|
||||
}
|
||||
|
||||
number := startNumber
|
||||
if number <= 0 {
|
||||
number = 1
|
||||
}
|
||||
|
||||
attempts := 0
|
||||
for {
|
||||
candidate := fmt.Sprintf("%s %03d", name, number)
|
||||
exists, err := repo.ExistsByFlockName(ctx, candidate, excludeID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed checking project flock name uniqueness for %q: %+v", candidate, err)
|
||||
return "", 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate flock name")
|
||||
}
|
||||
if !exists {
|
||||
return candidate, number, nil
|
||||
}
|
||||
number++
|
||||
attempts++
|
||||
if attempts > 9999 {
|
||||
return "", 0, fiber.NewError(fiber.StatusInternalServerError, "Unable to generate unique flock name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s projectflockService) buildOrderExpressions(sortBy, sortOrder string) []string {
|
||||
direction := "ASC"
|
||||
if strings.ToLower(sortOrder) == "desc" {
|
||||
direction = "DESC"
|
||||
func (s projectflockService) ensureFlockByName(ctx context.Context, actorID uint, name string) (*entity.Flock, error) {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
if trimmed == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Flock name cannot be empty")
|
||||
}
|
||||
|
||||
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 project_flock_kandangs pfk WHERE pfk.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",
|
||||
}
|
||||
flock, err := s.FlockRepo.GetByName(ctx, trimmed)
|
||||
if err == nil {
|
||||
return flock, nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to fetch flock by name %q: %+v", trimmed, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to prepare flock data")
|
||||
}
|
||||
|
||||
newFlock := &entity.Flock{
|
||||
Name: trimmed,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
if err := s.FlockRepo.CreateOne(ctx, newFlock, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return s.FlockRepo.GetByName(ctx, trimmed)
|
||||
}
|
||||
s.Log.Errorf("Failed to create flock %q: %+v", trimmed, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to prepare flock data")
|
||||
}
|
||||
|
||||
return newFlock, nil
|
||||
}
|
||||
|
||||
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint) error {
|
||||
@@ -803,20 +862,12 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := dbTransaction.
|
||||
Model(&entity.Kandang{}).
|
||||
Where("id IN ?", kandangIDs).
|
||||
Updates(map[string]any{
|
||||
"status": string(utils.KandangStatusPengajuan),
|
||||
}).Error; err != nil {
|
||||
if err := s.kandangRepoWithTx(dbTransaction).UpdateStatusByIDs(ctx, kandangIDs, utils.KandangStatusPengajuan); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs status")
|
||||
}
|
||||
|
||||
var already []uint
|
||||
if err := dbTransaction.
|
||||
Table("project_flock_kandangs").
|
||||
Where("project_flock_id = ? AND kandang_id IN ?", projectFlockID, kandangIDs).
|
||||
Pluck("kandang_id", &already).Error; err != nil {
|
||||
already, err := s.pivotRepoWithTx(dbTransaction).ListExistingKandangIDs(ctx, projectFlockID, kandangIDs)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing pivot")
|
||||
}
|
||||
exists := make(map[uint]struct{}, len(already))
|
||||
@@ -847,6 +898,9 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *
|
||||
})
|
||||
}
|
||||
if err := s.pivotRepoWithTx(dbTransaction).CreateMany(ctx, records); err != nil {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return fiber.NewError(fiber.StatusConflict, "Beberapa kandang sudah terhubung dengan project flock ini")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history")
|
||||
}
|
||||
return nil
|
||||
@@ -857,13 +911,25 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction *
|
||||
return nil
|
||||
}
|
||||
|
||||
blocked, err := s.pivotRepoWithTx(dbTransaction).FindKandangsWithRecordings(ctx, projectFlockID, kandangIDs)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to check recordings before detaching kandangs: %+v", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandang detachment")
|
||||
}
|
||||
if len(blocked) > 0 {
|
||||
names := make([]string, 0, len(blocked))
|
||||
for _, item := range blocked {
|
||||
label := fmt.Sprintf("ID %d", item.Id)
|
||||
if strings.TrimSpace(item.Name) != "" {
|
||||
label = fmt.Sprintf("%s (%s)", label, item.Name)
|
||||
}
|
||||
names = append(names, label)
|
||||
}
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak dapat melepas kandang karena sudah memiliki recording: %s", strings.Join(names, ", ")))
|
||||
}
|
||||
|
||||
if resetStatus {
|
||||
if err := dbTransaction.
|
||||
Model(&entity.Kandang{}).
|
||||
Where("id IN ?", kandangIDs).
|
||||
Updates(map[string]any{
|
||||
"status": string(utils.KandangStatusNonActive),
|
||||
}).Error; err != nil {
|
||||
if err := s.kandangRepoWithTx(dbTransaction).UpdateStatusByIDs(ctx, kandangIDs, utils.KandangStatusNonActive); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs status")
|
||||
}
|
||||
}
|
||||
@@ -875,22 +941,33 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction *
|
||||
}
|
||||
|
||||
func (s projectflockService) pivotRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository {
|
||||
if s.PivotRepo == nil {
|
||||
return repository.NewProjectFlockKandangRepository(dbTransaction)
|
||||
if dbTransaction == nil {
|
||||
return s.pivotRepo()
|
||||
}
|
||||
return s.PivotRepo.WithTx(dbTransaction)
|
||||
return s.pivotRepo().WithTx(dbTransaction)
|
||||
}
|
||||
|
||||
func (s projectflockService) anyKandangLinkedToOtherProject(ctx context.Context, db *gorm.DB, kandangIDs []uint, exceptProjectID *uint) (bool, error) {
|
||||
q := db.WithContext(ctx).
|
||||
Table("project_flock_kandangs").
|
||||
Where("kandang_id IN ?", kandangIDs)
|
||||
if exceptProjectID != nil {
|
||||
q = q.Where("project_flock_id <> ?", *exceptProjectID)
|
||||
func (s projectflockService) pivotRepo() repository.ProjectFlockKandangRepository {
|
||||
if s.PivotRepo != nil {
|
||||
return s.PivotRepo
|
||||
}
|
||||
var count int64
|
||||
if err := q.Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
return repository.NewProjectFlockKandangRepository(s.Repository.DB())
|
||||
}
|
||||
|
||||
func (s projectflockService) kandangRepoWithTx(tx *gorm.DB) kandangRepository.KandangRepository {
|
||||
if tx != nil {
|
||||
return kandangRepository.NewKandangRepository(tx)
|
||||
}
|
||||
if s.KandangRepo != nil {
|
||||
return s.KandangRepo
|
||||
}
|
||||
return kandangRepository.NewKandangRepository(s.Repository.DB())
|
||||
}
|
||||
|
||||
func actorIDFromContext(c *fiber.Ctx) (uint, error) {
|
||||
user, ok := authmiddleware.AuthenticatedUser(c)
|
||||
if !ok || user == nil || user.Id == 0 {
|
||||
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||
}
|
||||
return user.Id, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user