package service import ( "context" "strings" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" "gorm.io/gorm" ) type ApprovalService interface { RegisterWorkflowSteps(workflow approvalutils.ApprovalWorkflowKey, steps map[approvalutils.ApprovalStep]string) error WorkflowSteps(workflow approvalutils.ApprovalWorkflowKey) map[approvalutils.ApprovalStep]string WorkflowStepName(workflow approvalutils.ApprovalWorkflowKey, step approvalutils.ApprovalStep) (string, bool) CreateApproval(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, step approvalutils.ApprovalStep, action *entity.ApprovalAction, actorID uint, note *string) (*entity.Approval, error) List(ctx context.Context, module string, approvableID *uint, page, limit int, search string, orderByDate string) ([]entity.Approval, int64, error) ListByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.Approval, error) LatestByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) (*entity.Approval, error) LatestByTargets(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableIDs []uint, modifier func(*gorm.DB) *gorm.DB) (map[uint]*entity.Approval, error) } type approvalService struct { repo commonRepo.ApprovalRepository } func NewApprovalService(repo commonRepo.ApprovalRepository) ApprovalService { return &approvalService{repo: repo} } func (s *approvalService) RegisterWorkflowSteps(workflow approvalutils.ApprovalWorkflowKey, steps map[approvalutils.ApprovalStep]string) error { return approvalutils.RegisterWorkflowSteps(workflow, steps) } func (s *approvalService) WorkflowSteps(workflow approvalutils.ApprovalWorkflowKey) map[approvalutils.ApprovalStep]string { return approvalutils.WorkflowSteps(workflow) } func (s *approvalService) WorkflowStepName(workflow approvalutils.ApprovalWorkflowKey, step approvalutils.ApprovalStep) (string, bool) { return approvalutils.ApprovalStepName(workflow, step) } func (s *approvalService) CreateApproval( ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, step approvalutils.ApprovalStep, action *entity.ApprovalAction, actorID uint, note *string, ) (*entity.Approval, error) { record, err := approvalutils.NewApproval(workflow, approvableID, step, action, actorID, note) if err != nil { return nil, err } if err := s.repo.CreateOne(ctx, record, nil); err != nil { return nil, err } s.decorateApproval(workflow, record) return record, nil } func (s *approvalService) List( ctx context.Context, module string, approvableID *uint, page, limit int, search string, orderByDate string, ) ([]entity.Approval, int64, error) { module = strings.TrimSpace(strings.ToUpper(module)) search = strings.TrimSpace(search) orderByDate = strings.TrimSpace(strings.ToUpper(orderByDate)) if orderByDate != "ASC" && orderByDate != "DESC" { orderByDate = "DESC" } if limit <= 0 { limit = 10 } if page <= 0 { page = 1 } offset := (page - 1) * limit records, total, err := s.repo.GetAll( ctx, offset, limit, func(db *gorm.DB) *gorm.DB { query := db. Where("approvable_type = ?", module). Order("action_at " + orderByDate). Preload("ActionUser") if approvableID != nil { query = query.Where("approvable_id = ?", *approvableID) } if search != "" { like := "%" + strings.ToLower(search) + "%" query = query.Where("(LOWER(step_name) LIKE ? OR LOWER(action) LIKE ? OR LOWER(notes) LIKE ?)", like, like, like) } return query }, ) if err != nil { if s.isApprovalTableMissing(err) { return nil, 0, nil } return nil, 0, err } if len(records) == 0 { return nil, total, nil } workflow := approvalutils.ApprovalWorkflowKey(module) for i := range records { s.decorateApproval(workflow, &records[i]) } return records, total, nil } func (s *approvalService) ListByTarget( ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB, ) ([]entity.Approval, error) { records, err := s.repo.FindByTarget(ctx, workflow.String(), approvableID, modifier) if err != nil { if s.isApprovalTableMissing(err) { return nil, nil } return nil, err } for i := range records { s.decorateApproval(workflow, &records[i]) } return records, nil } func (s *approvalService) LatestByTarget( ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB, ) (*entity.Approval, error) { record, err := s.repo.LatestByTarget(ctx, workflow.String(), approvableID, modifier) if err != nil { if s.isApprovalTableMissing(err) { return nil, nil } return nil, err } if record == nil { return nil, nil } s.decorateApproval(workflow, record) return record, nil } func (s *approvalService) LatestByTargets( ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableIDs []uint, modifier func(*gorm.DB) *gorm.DB, ) (map[uint]*entity.Approval, error) { records, err := s.repo.LatestByTargets(ctx, workflow.String(), approvableIDs, modifier) if err != nil { if s.isApprovalTableMissing(err) { return nil, nil } return nil, err } if len(records) == 0 { return nil, nil } result := make(map[uint]*entity.Approval, len(records)) for approvableID, approval := range records { approvalCopy := approval s.decorateApproval(workflow, &approvalCopy) result[approvableID] = &approvalCopy } return result, nil } func (s *approvalService) decorateApproval(workflow approvalutils.ApprovalWorkflowKey, approval *entity.Approval) { if approval == nil { return } currentName := strings.TrimSpace(approval.StepName) if currentName == "" { if name, ok := approvalutils.ApprovalStepName(workflow, approvalutils.ApprovalStep(approval.StepNumber)); ok { approval.StepName = name } } else { approval.StepName = currentName } } func (s *approvalService) isApprovalTableMissing(err error) bool { if err == nil { return false } errMsg := strings.ToLower(err.Error()) if strings.Contains(errMsg, "no such table: approvals") { return true } schemaIssues := []string{ `relation "approvals" does not exist`, `column "step_name" does not exist`, `column "step_number" does not exist`, `column "action" does not exist`, `column "status" does not exist`, `column "step" does not exist`, } for _, issue := range schemaIssues { if strings.Contains(errMsg, issue) { return true } } return false }