add restrict for expense,purchase,adjustment transfer: unfinished

This commit is contained in:
ragilap
2025-12-09 15:16:01 +07:00
parent 7094d90034
commit 0fbf04fc1d
18 changed files with 681 additions and 395 deletions
-1
View File
@@ -11,7 +11,6 @@ import (
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
rExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
sExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -79,7 +79,7 @@ func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context
if err := r.DB().WithContext(ctx).
Table("expenses").
Scopes(r.WithProjectFlockKandangFilter(pfkID, kandangID)).
Group("expenses.id").
Group("expenses.id").Where("expenses.deleted_at IS NULL").
Pluck("expenses.id", &ids).Error; err != nil {
return 0, err
}
@@ -360,6 +360,9 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense")
}
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), currentExpense); err != nil {
return err
}
categoryChanged := false
var newCategory string
if req.Category != nil && *req.Category != currentExpense.Category {
@@ -404,6 +407,9 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
if ens.KandangId != nil {
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(*ens.KandangId))
if err != nil {
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), currentExpense); err != nil {
return err
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
}
@@ -543,7 +549,21 @@ func (s expenseService) DeleteOne(c *fiber.Ctx, id uint) error {
); err != nil {
return err
}
expense, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
return db.Preload("Nonstocks")
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Expense not found for ID %d: %+v", id, err)
return fiber.NewError(fiber.StatusNotFound, "Expense not found")
}
s.Log.Errorf("Failed to get expense for ID %d: %+v", id, err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense")
}
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil {
return err
}
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Expense not found for ID %d: %+v", id, err)
@@ -572,6 +592,20 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid realization_date format")
}
expense, err := s.Repository.GetByID(c.Context(), expenseID, func(db *gorm.DB) *gorm.DB {
return db.Preload("Nonstocks")
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Expense not found")
}
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense")
}
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil {
return nil, err
}
if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
@@ -712,7 +746,19 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
); err != nil {
return nil, err
}
expense, err := s.Repository.GetByID(c.Context(), expenseID, func(db *gorm.DB) *gorm.DB {
return db.Preload("Nonstocks")
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Expense not found")
}
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense")
}
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil {
return nil, err
}
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowExpense, expenseID, nil)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate workflow")
@@ -1010,6 +1056,21 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
} else {
return fiber.NewError(fiber.StatusBadRequest, "Invalid approval action")
}
if approvalAction == entity.ApprovalActionApproved {
expense, err := expenseRepoTx.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
return db.Preload("Nonstocks")
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "Expense not found")
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to load expense")
}
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil {
return err
}
}
if _, err := approvalSvcTx.CreateApproval(
c.Context(),
@@ -1079,13 +1140,45 @@ func (s *expenseService) validateExpenseNonstockRelation(ctx *fiber.Ctx, expense
return nil
}
// 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
// }
func (s *expenseService) ensureProjectFlockNotClosedForExpense(
ctx context.Context,
expense *entity.Expense,
) error {
// Kalau repo belum di-wire atau expense kosong → gak usah ngecek apa-apa
if s.ProjectFlockKandangRepo == nil || expense == nil {
return nil
}
// return user.Id, nil
// }
seen := make(map[uint]struct{})
for _, ens := range expense.Nonstocks {
// Field ini pointer, bisa nil
if ens.ProjectFlockKandangId == nil || *ens.ProjectFlockKandangId == 0 {
continue
}
pfkID := uint(*ens.ProjectFlockKandangId)
if _, ok := seen[pfkID]; ok {
continue
}
seen[pfkID] = struct{}{}
pfk, err := s.ProjectFlockKandangRepo.GetByID(ctx, pfkID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Project flock %d tidak ditemukan", pfkID),
)
}
s.Log.Errorf("Failed to validate project flock %d for expense %d: %+v", pfkID, expense.Id, err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to validate project flock")
}
// ❗ RULE: kalau ClosedAt tidak nil → project sudah closing
if pfk.ClosedAt != nil {
return fiber.NewError(fiber.StatusBadRequest, "Project sudah closing")
}
}
return nil
}