diff --git a/internal/capabilities/capabilities.go b/internal/capabilities/capabilities.go index 742d7acb..47f774ba 100644 --- a/internal/capabilities/capabilities.go +++ b/internal/capabilities/capabilities.go @@ -3,7 +3,7 @@ package capabilities import ( "strings" - recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings" + permission "gitlab.com/mbugroup/lti-api.git/internal/middleware" ) // FromPermissions returns a filtered map of capabilities that the frontend can use @@ -37,8 +37,8 @@ func normalizeAndAllow(perm string) (string, bool) { } var allowed = map[string]struct{}{ - recordings.PermissionRecordingRead: {}, - recordings.PermissionRecordingCreate: {}, - recordings.PermissionRecordingUpdate: {}, - recordings.PermissionRecordingDelete: {}, + permission.PermissionRecordingRead: {}, + permission.PermissionRecordingCreate: {}, + permission.PermissionRecordingUpdate: {}, + permission.PermissionRecordingDelete: {}, } diff --git a/internal/database/migrations/20251128081118_add_closing_project_flock_kandangs.down.sql b/internal/database/migrations/20251128081118_add_closing_project_flock_kandangs.down.sql new file mode 100644 index 00000000..2003bc61 --- /dev/null +++ b/internal/database/migrations/20251128081118_add_closing_project_flock_kandangs.down.sql @@ -0,0 +1,3 @@ +DROP INDEX IF EXISTS idx_project_flock_kandangs_closed_at; +ALTER TABLE project_flock_kandangs + DROP COLUMN IF EXISTS closed_at; diff --git a/internal/database/migrations/20251128081118_add_closing_project_flock_kandangs.up.sql b/internal/database/migrations/20251128081118_add_closing_project_flock_kandangs.up.sql new file mode 100644 index 00000000..dc2114b1 --- /dev/null +++ b/internal/database/migrations/20251128081118_add_closing_project_flock_kandangs.up.sql @@ -0,0 +1,5 @@ +ALTER TABLE project_flock_kandangs + ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ; + +CREATE INDEX IF NOT EXISTS idx_project_flock_kandangs_closed_at + ON project_flock_kandangs (closed_at); diff --git a/internal/entities/projectflock_kandang.go b/internal/entities/projectflock_kandang.go index d4bd7452..0ce4fc25 100644 --- a/internal/entities/projectflock_kandang.go +++ b/internal/entities/projectflock_kandang.go @@ -3,11 +3,12 @@ package entities import "time" type ProjectFlockKandang struct { - Id uint `gorm:"primaryKey"` - ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"` - KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"` - Period int `gorm:"not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` + Id uint `gorm:"primaryKey"` + ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"` + KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"` + Period int `gorm:"not null"` + ClosedAt *time.Time `gorm:"index"` + CreatedAt time.Time `gorm:"autoCreateTime"` ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"` Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"` diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 881c3a67..03c510e0 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -9,7 +9,6 @@ import ( service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" "gitlab.com/mbugroup/lti-api.git/internal/sso" "gitlab.com/mbugroup/lti-api.git/internal/utils" - "github.com/gofiber/fiber/v2" ) @@ -107,10 +106,11 @@ func AuthenticatedUser(c *fiber.Ctx) (*entity.User, bool) { func ActorIDFromContext(c *fiber.Ctx) (uint, error) { user, ok := AuthenticatedUser(c) - if !ok || user == nil || user.Id == 0 { + if !ok || user == nil || user.Id == 0 { return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") } return user.Id, nil + return 1, nil } // AuthDetails returns the full authentication context (token, claims, user). @@ -199,3 +199,72 @@ func hasAllScopes(have, required []string) bool { } return true } + + +// RequirePermissions ensures the authenticated user possesses all specified permissions. +func RequirePermissions(perms ...string) fiber.Handler { + required := canonicalPermissions(perms) + return func(c *fiber.Ctx) error { + if len(required) == 0 { + return c.Next() + } + + ctx, ok := AuthDetails(c) + if !ok || ctx == nil { + return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") + } + + userPerms := ctx.permissionSet() + if len(userPerms) == 0 { + return fiber.NewError(fiber.StatusForbidden, "Insufficient permission") + } + + for _, perm := range required { + if _, has := userPerms[perm]; !has { + return fiber.NewError(fiber.StatusForbidden, "Insufficient permission") + } + } + + return c.Next() + } +} + +// HasPermission reports whether the current request context includes the given permission. +func HasPermission(c *fiber.Ctx, perm string) bool { + ctx, ok := AuthDetails(c) + if !ok || ctx == nil { + return false + } + perm = canonicalPermission(perm) + if perm == "" { + return false + } + _, has := ctx.permissionSet()[perm] + return has +} + +func (a *AuthContext) permissionSet() map[string]struct{} { + if a == nil || a.Permissions == nil { + return nil + } + return a.Permissions +} + +func canonicalPermissions(perms []string) []string { + out := make([]string, 0, len(perms)) + seen := make(map[string]struct{}, len(perms)) + for _, perm := range perms { + if canonical := canonicalPermission(perm); canonical != "" { + if _, ok := seen[canonical]; ok { + continue + } + seen[canonical] = struct{}{} + out = append(out, canonical) + } + } + return out +} + +func canonicalPermission(perm string) string { + return strings.ToLower(strings.TrimSpace(perm)) +} diff --git a/internal/middleware/permissions.go b/internal/middleware/permissions.go index 3ebe6866..30f1b35a 100644 --- a/internal/middleware/permissions.go +++ b/internal/middleware/permissions.go @@ -1,75 +1,14 @@ package middleware -import ( - "strings" - - "github.com/gofiber/fiber/v2" +//project-flock +const ( + PermissionProjectFlockClosing = "lti:project-flock:closing" ) -// RequirePermissions ensures the authenticated user possesses all specified permissions. -func RequirePermissions(perms ...string) fiber.Handler { - required := canonicalPermissions(perms) - return func(c *fiber.Ctx) error { - if len(required) == 0 { - return c.Next() - } - - ctx, ok := AuthDetails(c) - if !ok || ctx == nil { - return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") - } - - userPerms := ctx.permissionSet() - if len(userPerms) == 0 { - return fiber.NewError(fiber.StatusForbidden, "Insufficient permission") - } - - for _, perm := range required { - if _, has := userPerms[perm]; !has { - return fiber.NewError(fiber.StatusForbidden, "Insufficient permission") - } - } - - return c.Next() - } -} - -// HasPermission reports whether the current request context includes the given permission. -func HasPermission(c *fiber.Ctx, perm string) bool { - ctx, ok := AuthDetails(c) - if !ok || ctx == nil { - return false - } - perm = canonicalPermission(perm) - if perm == "" { - return false - } - _, has := ctx.permissionSet()[perm] - return has -} - -func (a *AuthContext) permissionSet() map[string]struct{} { - if a == nil || a.Permissions == nil { - return nil - } - return a.Permissions -} - -func canonicalPermissions(perms []string) []string { - out := make([]string, 0, len(perms)) - seen := make(map[string]struct{}, len(perms)) - for _, perm := range perms { - if canonical := canonicalPermission(perm); canonical != "" { - if _, ok := seen[canonical]; ok { - continue - } - seen[canonical] = struct{}{} - out = append(out, canonical) - } - } - return out -} - -func canonicalPermission(perm string) string { - return strings.ToLower(strings.TrimSpace(perm)) -} +//recording +const ( + PermissionRecordingRead = "recording.read" + PermissionRecordingCreate = "recording.write" + PermissionRecordingUpdate = "recording.update" + PermissionRecordingDelete = "recording.delete" +) \ No newline at end of file diff --git a/internal/modules/expenses/repositories/expense.repository.go b/internal/modules/expenses/repositories/expense.repository.go index 588583da..8c1eeab1 100644 --- a/internal/modules/expenses/repositories/expense.repository.go +++ b/internal/modules/expenses/repositories/expense.repository.go @@ -2,9 +2,11 @@ package repository import ( "context" + "errors" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) @@ -13,6 +15,8 @@ type ExpenseRepository interface { IdExists(ctx context.Context, id uint64) (bool, error) GetNextSequence(ctx context.Context) (int, error) GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error) + WithProjectFlockKandangFilter(pfkID uint) func(*gorm.DB) *gorm.DB + CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID uint, isFinished func(*entity.Approval) bool) (int64, error) } type ExpenseRepositoryImpl struct { @@ -49,3 +53,51 @@ func (r *ExpenseRepositoryImpl) GetWithSupplier(ctx context.Context, id uint64) } return &expense, nil } + +func (r *ExpenseRepositoryImpl) WithProjectFlockKandangFilter(pfkID uint) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if pfkID == 0 { + return db + } + return db.Joins("JOIN expense_nonstocks ON expense_nonstocks.expense_id = expenses.id"). + Where("expense_nonstocks.project_flock_kandang_id = ?", pfkID) + } +} + +func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID uint, isFinished func(*entity.Approval) bool) (int64, error) { + if pfkID == 0 { + return 0, nil + } + + var ids []uint64 + if err := r.DB().WithContext(ctx). + Table("expenses"). + Scopes(r.WithProjectFlockKandangFilter(pfkID)). + Group("expenses.id"). + Pluck("expenses.id", &ids).Error; err != nil { + return 0, err + } + if len(ids) == 0 { + return 0, nil + } + + var unfinished int64 + for _, id := range ids { + var latest entity.Approval + err := r.DB().WithContext(ctx). + Table("approvals"). + Where("approvable_type = ? AND approvable_id = ?", utils.ApprovalWorkflowExpense.String(), id). + Order("action_at DESC"). + Limit(1). + First(&latest).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, err + } + if isFinished != nil { + if !isFinished(&latest) { + unfinished++ + } + } + } + return unfinished, nil +} diff --git a/internal/modules/production/project-flock-kandangs/controllers/project_flock_kandang.controller.go b/internal/modules/production/project-flock-kandangs/controllers/project_flock_kandang.controller.go index 4b6e605a..3b00d3b6 100644 --- a/internal/modules/production/project-flock-kandangs/controllers/project_flock_kandang.controller.go +++ b/internal/modules/production/project-flock-kandangs/controllers/project_flock_kandang.controller.go @@ -84,3 +84,29 @@ func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error { Data: dto.ToProjectFlockKandangDetailDTOWithAvailableQty(*result, availableQtys, productWarehouses), }) } + +func (u *ProjectFlockKandangController) Closing(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil || id <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + req := new(validation.Closing) + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.ProjectFlockKandangService.Closing(c, uint(id), req) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Status closing kandang diperbarui", + // Data: dto.ToProjectFlockKandangDetailDTO(*result), + Data: result, + }) +} diff --git a/internal/modules/production/project-flock-kandangs/module.go b/internal/modules/production/project-flock-kandangs/module.go index 160cec5e..becd2b61 100644 --- a/internal/modules/production/project-flock-kandangs/module.go +++ b/internal/modules/production/project-flock-kandangs/module.go @@ -12,6 +12,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" + rExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -36,7 +37,8 @@ func (ProjectFlockKandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err)) } - projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, warehouseRepo, productWarehouseRepo, projectFlockPopulationRepo, validate) + expenseRepo := rExpense.NewExpenseRepository(db) + projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, expenseRepo, warehouseRepo, productWarehouseRepo, projectFlockPopulationRepo, validate) userService := sUser.NewUserService(userRepo, validate) ProjectFlockKandangRoutes(router, userService, projectFlockKandangService) diff --git a/internal/modules/production/project-flock-kandangs/route.go b/internal/modules/production/project-flock-kandangs/route.go index 7bab770e..1105fad3 100644 --- a/internal/modules/production/project-flock-kandangs/route.go +++ b/internal/modules/production/project-flock-kandangs/route.go @@ -22,5 +22,5 @@ func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlo route.Get("/", ctrl.GetAll) route.Get("/:id", ctrl.GetOne) - + route.Post("/:id/closing", ctrl.Closing) } diff --git a/internal/modules/production/project-flock-kandangs/services/project_flock_kandang.service.go b/internal/modules/production/project-flock-kandangs/services/project_flock_kandang.service.go index 11e8b0d5..fa65d045 100644 --- a/internal/modules/production/project-flock-kandangs/services/project_flock_kandang.service.go +++ b/internal/modules/production/project-flock-kandangs/services/project_flock_kandang.service.go @@ -2,24 +2,28 @@ package service import ( "errors" + "strings" + "time" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" - - "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" - "github.com/sirupsen/logrus" "gorm.io/gorm" ) type ProjectFlockKandangService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, error) + Closing(ctx *fiber.Ctx, id uint, req *validation.Closing) (*entity.ProjectFlockKandang, error) } // Note: map[uint]float64 adalah mapping dari ProductWarehouse ID ke calculated available quantity @@ -29,17 +33,19 @@ type projectFlockKandangService struct { Validate *validator.Validate Repository repository.ProjectFlockKandangRepository ApprovalSvc commonSvc.ApprovalService + ExpenseRepo expenseRepo.ExpenseRepository WarehouseRepo rWarehouse.WarehouseRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository PopulationRepo repository.ProjectFlockPopulationRepository } -func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, validate *validator.Validate) ProjectFlockKandangService { +func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, expenseRepo expenseRepo.ExpenseRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, validate *validator.Validate) ProjectFlockKandangService { return &projectFlockKandangService{ Log: utils.Log, Validate: validate, Repository: repo, ApprovalSvc: approvalSvc, + ExpenseRepo: expenseRepo, WarehouseRepo: warehouseRepo, ProductWarehouseRepo: productWarehouseRepo, PopulationRepo: populationRepo, @@ -166,6 +172,99 @@ func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, project return result, nil } +func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validation.Closing) (*entity.ProjectFlockKandang, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + + pfk, err := s.Repository.GetByID(c.Context(), id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") + } + return nil, err + } + + if s.ApprovalSvc != nil { + latest, aerr := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlock, pfk.ProjectFlockId, nil) + if aerr != nil { + return nil, aerr + } + if latest == nil || latest.StepNumber != uint16(utils.ProjectFlockStepAktif) || latest.Action == nil || *latest.Action != entity.ApprovalActionApproved { + return nil, fiber.NewError(fiber.StatusBadRequest, "Project flock belum berstatus aktif") + } + } + + action := strings.ToLower(strings.TrimSpace(req.Action)) + now := time.Now() + + switch action { + case "close": + if pfk.ClosedAt != nil { + return nil, fiber.NewError(fiber.StatusConflict, "Kandang sudah closed") + } + if s.ExpenseRepo != nil && s.ApprovalSvc != nil { + unfinished, err := s.ExpenseRepo.CountUnfinishedByProjectFlockKandang(c.Context(), pfk.Id, func(appr *entity.Approval) bool { + return appr != nil && appr.StepNumber == uint16(utils.ExpenseStepSelesai) && appr.Action != nil && *appr.Action == entity.ApprovalActionApproved + }) + if err != nil { + return nil, err + } + if unfinished > 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Masih ada expense belum selesai untuk kandang ini") + } + } + closeTime := now + if req.ClosedDate != nil { + parsed, perr := utils.ParseDateString(strings.TrimSpace(*req.ClosedDate)) + if perr != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "closed_date tidak valid, gunakan format YYYY-MM-DD") + } + closeTime = parsed + } + if err := s.Repository.UpdateClosedAt(c.Context(), id, &closeTime); err != nil { + return nil, err + } + if s.ApprovalSvc != nil { + closeAction := entity.ApprovalActionApproved + if _, aerr := s.ApprovalSvc.CreateApproval( + c.Context(), + utils.ApprovalWorkflowProjectFlockKandang, + id, + utils.ProjectFlockKandangStepDisetujui, + &closeAction, + actorID, + nil, + ); aerr != nil { + return nil, aerr + } + } + case "unclose": + if pfk.ClosedAt == nil { + return nil, fiber.NewError(fiber.StatusConflict, "Kandang belum closed") + } + openNewer, err := s.Repository.HasOpenNewerPeriod(c.Context(), pfk.KandangId, pfk.Period, &pfk.Id) + if err != nil { + return nil, err + } + if openNewer { + return nil, fiber.NewError(fiber.StatusBadRequest, "Tidak dapat un-close: ada periode yang sedang berjalan") + } + if err := s.Repository.UpdateClosedAt(c.Context(), id, nil); err != nil { + return nil, err + } + default: + return nil, fiber.NewError(fiber.StatusBadRequest, "action harus close atau unclose") + } + + return s.Repository.GetByID(c.Context(), id) +} + func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehouse(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang, productWarehouse *entity.ProductWarehouse) (float64, error) { availableQty := productWarehouse.Quantity diff --git a/internal/modules/production/project-flock-kandangs/validations/project_flock_kandang.validation.go b/internal/modules/production/project-flock-kandangs/validations/project_flock_kandang.validation.go index 93e0256a..729d8329 100644 --- a/internal/modules/production/project-flock-kandangs/validations/project_flock_kandang.validation.go +++ b/internal/modules/production/project-flock-kandangs/validations/project_flock_kandang.validation.go @@ -22,3 +22,8 @@ type Query struct { SortOrder string `query:"sort_order" validate:"omitempty,oneof=ASC DESC"` StepName string `query:"step_name" validate:"omitempty,max=50"` } + +type Closing struct { + Action string `json:"action" validate:"required,oneof=close unclose"` + ClosedDate *string `json:"closed_date,omitempty"` +} \ No newline at end of file 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 76f23b39..b0863700 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go @@ -3,6 +3,7 @@ package repository import ( "context" "strings" + "time" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -14,6 +15,7 @@ type ProjectFlockKandangRepository interface { GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) 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 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) @@ -23,9 +25,9 @@ type ProjectFlockKandangRepository interface { FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error) MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error) ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error) + HasOpenNewerPeriod(ctx context.Context, kandangID uint, currentPeriod int, excludeID *uint) (bool, error) WithTx(tx *gorm.DB) ProjectFlockKandangRepository IdExists(ctx context.Context, id uint) (bool, error) - DB() *gorm.DB } type projectFlockKandangRepositoryImpl struct { @@ -251,6 +253,32 @@ func (r *projectFlockKandangRepositoryImpl) GetActiveByKandangID(ctx context.Con return record, nil } +func (r *projectFlockKandangRepositoryImpl) UpdateClosedAt(ctx context.Context, id uint, t *time.Time) error { + return r.db.WithContext(ctx). + Model(&entity.ProjectFlockKandang{}). + Where("id = ?", id). + Update("closed_at", t).Error +} + +func (r *projectFlockKandangRepositoryImpl) HasOpenNewerPeriod(ctx context.Context, kandangID uint, currentPeriod int, excludeID *uint) (bool, error) { + if kandangID == 0 { + return false, nil + } + q := r.db.WithContext(ctx). + Table("project_flock_kandangs"). + Where("kandang_id = ?", kandangID). + Where("period > ?", currentPeriod). + Where("closed_at IS NULL") + if excludeID != nil { + q = q.Where("id <> ?", *excludeID) + } + var count int64 + if err := q.Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} + func (r *projectFlockKandangRepositoryImpl) ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error) { if len(kandangIDs) == 0 { return nil, nil diff --git a/internal/modules/production/recordings/permissions.go b/internal/modules/production/recordings/permissions.go deleted file mode 100644 index 00f9bd48..00000000 --- a/internal/modules/production/recordings/permissions.go +++ /dev/null @@ -1,8 +0,0 @@ -package recordings - -const ( - PermissionRecordingRead = "recording.read" - PermissionRecordingCreate = "recording.write" - PermissionRecordingUpdate = "recording.update" - PermissionRecordingDelete = "recording.delete" -)