diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index 8c33e053..c0c77aef 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -359,6 +359,40 @@ func applyCutOverLayingLookupOverride(result *dto.ProjectFlockKandangDTO) { result.IsLaying = true } +func (u *ProjectflockController) UpdateOne(c *fiber.Ctx) error { + param := c.Params("id") + req := new(validation.Update) + + id, err := strconv.Atoi(param) + if err != nil || id <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.ProjectflockService.UpdateOne(c, req, uint(id)) + if err != nil { + return err + } + + var period int + if periods, err := u.ProjectflockService.GetProjectPeriods(c, []uint{uint(id)}); err == nil { + if p, ok := periods[uint(id)]; ok { + period = p + } + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Update projectflock successfully", + Data: dto.ToProjectFlockListDTOWithPeriod(*result, period), + }) +} + func (u *ProjectflockController) Resubmit(c *fiber.Ctx) error { param := c.Params("id") req := new(validation.Resubmit) diff --git a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go index e70a77fb..ed31b157 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go @@ -17,6 +17,7 @@ type ProjectFlockKandangRepository interface { GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) GetActiveByKandangID(ctx context.Context, kandangID uint) (*entity.ProjectFlockKandang, error) UpdateClosedAt(ctx context.Context, id uint, t *time.Time) error + UpdatePeriodByProjectFlockID(ctx context.Context, projectFlockID uint, period int) (int64, error) CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error GetAll(ctx context.Context, offset int, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandang, int64, error) @@ -525,6 +526,19 @@ func (r *projectFlockKandangRepositoryImpl) UpdateClosedAt(ctx context.Context, Update("closed_at", t).Error } +// UpdatePeriodByProjectFlockID updates the period column on every pivot row that +// belongs to the given project flock. Returns the number of rows affected. +func (r *projectFlockKandangRepositoryImpl) UpdatePeriodByProjectFlockID(ctx context.Context, projectFlockID uint, period int) (int64, error) { + result := r.db.WithContext(ctx). + Model(&entity.ProjectFlockKandang{}). + Where("project_flock_id = ?", projectFlockID). + Update("period", period) + if result.Error != nil { + return 0, result.Error + } + return result.RowsAffected, nil +} + func (r *projectFlockKandangRepositoryImpl) HasOpenNewerPeriod(ctx context.Context, kandangID uint, currentPeriod int, excludeID *uint) (bool, error) { if kandangID == 0 { return false, nil diff --git a/internal/modules/production/project_flocks/route.go b/internal/modules/production/project_flocks/route.go index c0eb8657..06791b20 100644 --- a/internal/modules/production/project_flocks/route.go +++ b/internal/modules/production/project_flocks/route.go @@ -15,13 +15,14 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj route := v1.Group("/project-flocks") route.Use(m.Auth(u)) - route.Get("/",m.RequirePermissions(m.P_ProjectFlockGetAll),ctrl.GetAll) - route.Post("/",m.RequirePermissions(m.P_ProjectFlockCreate), ctrl.CreateOne) - route.Get("/:id",m.RequirePermissions(m.P_ProjectFlockGetOne), ctrl.GetOne) - route.Delete("/:id",m.RequirePermissions(m.P_ProjectFlockGetAll), ctrl.DeleteOne) - route.Get("/kandangs/lookup",m.RequirePermissions(m.P_ProjectFlockLookup), ctrl.LookupProjectFlockKandang) - route.Post("/approvals",m.RequirePermissions(m.P_ProjectFlockApprove), ctrl.Approval) - route.Get("/locations/:location_id/periods",m.RequirePermissions(m.P_ProjectFlockNextPeriod), ctrl.GetPeriodSummary) - route.Put("/:id/resubmit",m.RequirePermissions(m.P_ProjectFlockResubmit), ctrl.Resubmit) + route.Get("/", m.RequirePermissions(m.P_ProjectFlockGetAll), ctrl.GetAll) + route.Post("/", m.RequirePermissions(m.P_ProjectFlockCreate), ctrl.CreateOne) + route.Get("/:id", m.RequirePermissions(m.P_ProjectFlockGetOne), ctrl.GetOne) + route.Patch("/:id", m.RequirePermissions(m.P_ProjectFlockCreate), ctrl.UpdateOne) + route.Delete("/:id", m.RequirePermissions(m.P_ProjectFlockGetAll), ctrl.DeleteOne) + route.Get("/kandangs/lookup", m.RequirePermissions(m.P_ProjectFlockLookup), ctrl.LookupProjectFlockKandang) + route.Post("/approvals", m.RequirePermissions(m.P_ProjectFlockApprove), ctrl.Approval) + route.Get("/locations/:location_id/periods", m.RequirePermissions(m.P_ProjectFlockNextPeriod), ctrl.GetPeriodSummary) + route.Put("/:id/resubmit", m.RequirePermissions(m.P_ProjectFlockResubmit), ctrl.Resubmit) } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index beaa0899..909a3b8a 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -51,6 +51,7 @@ type ProjectflockService interface { GetProjectPeriods(ctx *fiber.Ctx, projectIDs []uint) (map[uint]int, error) Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) Resubmit(ctx *fiber.Ctx, req *validation.Resubmit, id uint) (*entity.ProjectFlock, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) EnsureProjectFlockApproved(ctx context.Context, projectFlockID uint) error } @@ -1273,6 +1274,52 @@ func (s projectflockService) Resubmit(c *fiber.Ctx, req *validation.Resubmit, id return s.getOneEntityOnly(c, id) } +// UpdateOne updates mutable fields of a project flock. +// Currently only the `period` is updatable; the value is applied to every +// project_flock_kandang pivot row belonging to the project flock so it stays +// consistent with how periods are provisioned in CreateOne/Resubmit. +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 + } + + if req.Period == nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "period is required") + } + + existing, err := s.Repository.GetByID(c.Context(), id, nil) + 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") + } + + err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + affected, err := s.pivotRepoWithTx(dbTransaction).UpdatePeriodByProjectFlockID(c.Context(), existing.Id, *req.Period) + if err != nil { + return err + } + if affected == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Project flock tidak memiliki kandang yang dapat diperbarui periodenya") + } + return nil + }) + 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 update projectflock %d period: %+v", id, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update project flock") + } + + return s.getOneEntityOnly(c, id) +} + func (s projectflockService) UpsertProjectBudget(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, budgets []validation.ProjectBudget) error { if len(budgets) == 0 { diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index c6370133..c4b4eec8 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -41,3 +41,7 @@ type Resubmit struct { KandangIds []uint `json:"kandang_ids" validate:"required_strict,min=1,dive,gt=0"` ProjectBudgets []ProjectBudget `json:"project_budgets" validate:"required_strict,min=1,dive"` } + +type Update struct { + Period *int `json:"period" validate:"required,number,gt=0"` +}