Update group approvals

This commit is contained in:
Aditya Siregar
2025-10-20 01:10:48 +07:00
parent 92aba06d67
commit e082d4fce5
3 changed files with 218 additions and 96 deletions
@@ -45,11 +45,11 @@ func (p *LetterApprovalProcessorImpl) CreateApprovalSteps(ctx context.Context, l
return err
}
// Find the minimum step order (first step)
minStepOrder := flow.Steps[0].StepOrder
// Find the minimum parallel group
minParallelGroup := flow.Steps[0].ParallelGroup
for _, step := range flow.Steps {
if step.StepOrder < minStepOrder {
minStepOrder = step.StepOrder
if step.ParallelGroup < minParallelGroup {
minParallelGroup = step.ParallelGroup
}
}
@@ -64,8 +64,8 @@ func (p *LetterApprovalProcessorImpl) CreateApprovalSteps(ctx context.Context, l
ApproverID: step.ApproverUserID,
}
// Set initial status
if step.StepOrder == minStepOrder {
// Set initial status - all approvals in first parallel group are pending
if step.ParallelGroup == minParallelGroup {
approval.Status = entities.ApprovalStatusPending
} else {
approval.Status = entities.ApprovalStatusNotStarted
@@ -3,6 +3,7 @@ package processor
import (
"context"
"fmt"
"sort"
"time"
"eslogad-be/internal/contract"
@@ -502,22 +503,22 @@ func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, lette
return err
}
// Step 2: Get all approvals FOR THE SAME REVISION and organize by step
approvalsByStep, err := p.getApprovalsByStepForRevision(txCtx, letterID, approval.RevisionNumber)
// Step 2: Get all approvals FOR THE SAME REVISION and organize by parallel group
approvalsByGroup, err := p.getApprovalsByParallelGroupForRevision(txCtx, letterID, approval.RevisionNumber)
if err != nil {
return err
}
// Step 3: Check if current step is completed
if p.isStepCompleted(approvalsByStep[approval.StepOrder]) {
// Step 4: Activate next step if exists
if err := p.activateNextStepForRevision(txCtx, letterID, approval.StepOrder, approval.RevisionNumber, approvalsByStep); err != nil {
// Step 3: Check if current parallel group is completed
if p.isParallelGroupCompleted(approvalsByGroup[approval.ParallelGroup]) {
// Step 4: Activate next parallel group if exists
if err := p.activateNextParallelGroupForRevision(txCtx, letterID, approval.ParallelGroup, approval.RevisionNumber, approvalsByGroup); err != nil {
return err
}
}
// Step 5: Check if all required approvals are completed FOR THIS REVISION
if p.areAllRequiredApprovalsCompleted(approvalsByStep) {
if p.areAllRequiredApprovalsCompletedByGroup(approvalsByGroup) {
// Step 6: Update letter status to approved
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
return err
@@ -572,6 +573,24 @@ func (p *LetterOutgoingProcessorImpl) getApprovalsByStepForRevision(ctx context.
return approvalsByStep, nil
}
// getApprovalsByParallelGroupForRevision fetches approvals for a specific revision and organizes them by parallel group
func (p *LetterOutgoingProcessorImpl) getApprovalsByParallelGroupForRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) (map[int][]entities.LetterOutgoingApproval, error) {
allApprovals, err := p.approvalRepo.ListByLetter(ctx, letterID)
if err != nil {
return nil, err
}
approvalsByGroup := make(map[int][]entities.LetterOutgoingApproval)
for _, approval := range allApprovals {
// Only include approvals from the same revision
if approval.RevisionNumber == revisionNumber {
approvalsByGroup[approval.ParallelGroup] = append(approvalsByGroup[approval.ParallelGroup], approval)
}
}
return approvalsByGroup, nil
}
// isStepCompleted checks if all required approvals in a step are approved
// For parallel groups, at least one approval per group must be completed
func (p *LetterOutgoingProcessorImpl) isStepCompleted(stepApprovals []entities.LetterOutgoingApproval) bool {
@@ -741,6 +760,79 @@ func (p *LetterOutgoingProcessorImpl) areAllRequiredApprovalsCompleted(approvals
return true
}
// isParallelGroupCompleted checks if all required approvals in a parallel group are approved
func (p *LetterOutgoingProcessorImpl) isParallelGroupCompleted(groupApprovals []entities.LetterOutgoingApproval) bool {
for _, approval := range groupApprovals {
if approval.IsRequired && approval.Status != entities.ApprovalStatusApproved {
return false
}
}
return true
}
// activateNextParallelGroupForRevision activates the next parallel group for a specific revision
func (p *LetterOutgoingProcessorImpl) activateNextParallelGroupForRevision(ctx context.Context, letterID uuid.UUID, currentGroup int, revisionNumber int, approvalsByGroup map[int][]entities.LetterOutgoingApproval) error {
// Find the next parallel group (handles non-sequential group numbers)
var groups []int
for group := range approvalsByGroup {
groups = append(groups, group)
}
sort.Ints(groups)
var nextGroup int = -1
for _, group := range groups {
if group > currentGroup {
nextGroup = group
break
}
}
if nextGroup == -1 {
return nil // No next group
}
nextGroupApprovals, exists := approvalsByGroup[nextGroup]
if !exists {
return nil
}
// Get existing recipients to avoid duplicates
existingUserIDs, err := p.getExistingRecipientUserIDs(ctx, letterID)
if err != nil {
return err
}
// Process each approval in the next group
for _, nextApproval := range nextGroupApprovals {
// Only process if it's the same revision
if nextApproval.RevisionNumber == revisionNumber {
// Activate approval if not started
if err := p.activateApprovalIfNotStarted(ctx, &nextApproval); err != nil {
return err
}
// Add approver as recipient if not already exists
if err := p.addApproverAsRecipientIfNeeded(ctx, letterID, nextApproval.ApproverID, existingUserIDs); err != nil {
return err
}
}
}
return nil
}
// areAllRequiredApprovalsCompletedByGroup checks if all required approvals are completed (organized by parallel group)
func (p *LetterOutgoingProcessorImpl) areAllRequiredApprovalsCompletedByGroup(approvalsByGroup map[int][]entities.LetterOutgoingApproval) bool {
for _, groupApprovals := range approvalsByGroup {
for _, approval := range groupApprovals {
if approval.IsRequired && approval.Status != entities.ApprovalStatusApproved {
return false
}
}
}
return true
}
// logApprovalActivity creates an activity log for the approval action
func (p *LetterOutgoingProcessorImpl) logApprovalActivity(ctx context.Context, letterID, approvalID uuid.UUID, userID uuid.UUID) error {
activityLog := &entities.LetterOutgoingActivityLog{