diff --git a/internal/contract/letter_outgoing_contract.go b/internal/contract/letter_outgoing_contract.go index 702a993..1df1961 100644 --- a/internal/contract/letter_outgoing_contract.go +++ b/internal/contract/letter_outgoing_contract.go @@ -287,29 +287,36 @@ type OutgoingLetterApprovalDiscussionsResponse struct { // EnhancedOutgoingLetterApprovalResponse includes approval details with related data type EnhancedOutgoingLetterApprovalResponse struct { - ID uuid.UUID `json:"id"` - LetterID uuid.UUID `json:"letter_id"` - StepID uuid.UUID `json:"step_id"` - StepOrder int `json:"step_order"` - ParallelGroup int `json:"parallel_group"` - IsRequired bool `json:"is_required"` - ApproverID *uuid.UUID `json:"approver_id,omitempty"` - Status string `json:"status"` - Remarks *string `json:"remarks,omitempty"` - ActedAt *time.Time `json:"acted_at,omitempty"` - CreatedAt time.Time `json:"created_at"` - Step *ApprovalFlowStepResponse `json:"step,omitempty"` - Approver *UserResponse `json:"approver,omitempty"` + ID uuid.UUID `json:"id"` + LetterID uuid.UUID `json:"letter_id"` + StepID uuid.UUID `json:"step_id"` + StepOrder int `json:"step_order"` + ParallelGroup int `json:"parallel_group"` + IsRequired bool `json:"is_required"` + ApproverID *uuid.UUID `json:"approver_id,omitempty"` + RevisionNumber int `json:"revision_number"` + Status string `json:"status"` + Remarks *string `json:"remarks,omitempty"` + ActedAt *time.Time `json:"acted_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + Step *ApprovalFlowStepResponse `json:"step,omitempty"` + Approver *UserResponse `json:"approver,omitempty"` +} + +type OutgoingLetterApprovalRevisionNumberResponse struct { + RevisionNumber int `json:"revision_number"` + Approvals []EnhancedOutgoingLetterApprovalResponse `json:"approvals"` } // GetLetterApprovalsResponse represents the list of approvals for a letter type GetLetterApprovalsResponse struct { - LetterID uuid.UUID `json:"letter_id"` - LetterNumber string `json:"letter_number"` - LetterStatus string `json:"letter_status"` - TotalSteps int `json:"total_steps"` - CurrentStep int `json:"current_step"` - Approvals []EnhancedOutgoingLetterApprovalResponse `json:"approvals"` + LetterID uuid.UUID `json:"letter_id"` + LetterNumber string `json:"letter_number"` + LetterStatus string `json:"letter_status"` + TotalSteps int `json:"total_steps"` + CurrentStep int `json:"current_step"` + CurrentRevisionNumber int `json:"current_revision_number"` + Approvals []OutgoingLetterApprovalRevisionNumberResponse `json:"approvals"` } // OutgoingLetterDiscussionResponse represents a discussion on an outgoing letter diff --git a/internal/processor/letter_outgoing_processor.go b/internal/processor/letter_outgoing_processor.go index 89fa551..6102ce2 100644 --- a/internal/processor/letter_outgoing_processor.go +++ b/internal/processor/letter_outgoing_processor.go @@ -41,6 +41,7 @@ type LetterOutgoingProcessor interface { DeleteDiscussion(ctx context.Context, id uuid.UUID) error GetApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error) + GetAllApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error) GetApprovalsByLetterAndRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) ([]entities.LetterOutgoingApproval, error) GetApprovalFlow(ctx context.Context, flowID uuid.UUID) (*entities.ApprovalFlow, error) @@ -989,6 +990,15 @@ func (p *LetterOutgoingProcessorImpl) GetApprovalsByLetter(ctx context.Context, return p.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber) } +func (p *LetterOutgoingProcessorImpl) GetAllApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error) { + approvals, err := p.approvalRepo.ListByLetter(ctx, letterID) + if err != nil { + return nil, err + } + + return approvals, nil +} + func (p *LetterOutgoingProcessorImpl) GetApprovalsByLetterAndRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) ([]entities.LetterOutgoingApproval, error) { // Get all approvals for this letter approvals, err := p.approvalRepo.ListByLetter(ctx, letterID) diff --git a/internal/service/letter_outgoing_service.go b/internal/service/letter_outgoing_service.go index 4e86b20..8ca1876 100644 --- a/internal/service/letter_outgoing_service.go +++ b/internal/service/letter_outgoing_service.go @@ -1112,120 +1112,154 @@ func (s *LetterOutgoingServiceImpl) GetLetterApprovals(ctx context.Context, lett return nil, err } - // Get all approvals for this letter's current revision - approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber) + // Get all approvals for this letter (all revisions) + approvals, err := s.processor.GetAllApprovalsByLetter(ctx, letterID) if err != nil { return nil, err } - // Sort approvals by step order and parallel group - sort.Slice(approvals, func(i, j int) bool { - if approvals[i].StepOrder != approvals[j].StepOrder { - return approvals[i].StepOrder < approvals[j].StepOrder - } - return approvals[i].ParallelGroup < approvals[j].ParallelGroup - }) - - // Transform to response format - approvalResponses := make([]contract.EnhancedOutgoingLetterApprovalResponse, 0, len(approvals)) - totalSteps := 0 - currentStep := 0 - stepOrdersSeen := make(map[int]bool) - + // Group approvals by revision number from approval itself + revisionMap := make(map[int][]entities.LetterOutgoingApproval) for _, approval := range approvals { - // Count unique step orders for total steps - if !stepOrdersSeen[approval.StepOrder] { - stepOrdersSeen[approval.StepOrder] = true - totalSteps++ - } - - // Determine current step (lowest step with pending/not_started status) - if approval.Status == entities.ApprovalStatusPending && (currentStep == 0 || approval.StepOrder < currentStep) { - currentStep = approval.StepOrder - } - - approvalResp := contract.EnhancedOutgoingLetterApprovalResponse{ - ID: approval.ID, - LetterID: approval.LetterID, - StepID: approval.StepID, - StepOrder: approval.StepOrder, - ParallelGroup: approval.ParallelGroup, - IsRequired: approval.IsRequired, - ApproverID: approval.ApproverID, - Status: string(approval.Status), - Remarks: approval.Remarks, - ActedAt: approval.ActedAt, - CreatedAt: approval.CreatedAt, - } - - // Add step details if available - if approval.Step != nil { - approvalResp.Step = &contract.ApprovalFlowStepResponse{ - ID: approval.Step.ID, - StepOrder: approval.Step.StepOrder, - ParallelGroup: approval.Step.ParallelGroup, - Required: approval.Step.Required, - CreatedAt: approval.Step.CreatedAt, - UpdatedAt: approval.Step.UpdatedAt, - } - - // Add approver role if available - if approval.Step.ApproverRole != nil { - approvalResp.Step.ApproverRole = &contract.RoleResponse{ - ID: approval.Step.ApproverRole.ID, - Name: approval.Step.ApproverRole.Name, - Code: approval.Step.ApproverRole.Code, - } - } - - // Add approver user if available - if approval.Step.ApproverUser != nil { - approvalResp.Step.ApproverUser = &contract.UserResponse{ - ID: approval.Step.ApproverUser.ID, - Name: approval.Step.ApproverUser.Name, - Email: approval.Step.ApproverUser.Email, - } - } - } - - // Add approver details if available - if approval.Approver != nil { - approvalResp.Approver = &contract.UserResponse{ - ID: approval.Approver.ID, - Name: approval.Approver.Name, - Email: approval.Approver.Email, - } - } - - approvalResponses = append(approvalResponses, approvalResp) + revisionMap[approval.RevisionNumber] = append(revisionMap[approval.RevisionNumber], approval) } - // If no current step found but there are approvals, check if all are completed - if currentStep == 0 && len(approvals) > 0 { - allCompleted := true - for _, approval := range approvals { - if approval.IsRequired && approval.Status != entities.ApprovalStatusApproved { - allCompleted = false - break + // Get sorted revision numbers + revisionNumbers := make([]int, 0, len(revisionMap)) + for revNum := range revisionMap { + revisionNumbers = append(revisionNumbers, revNum) + } + sort.Sort(sort.Reverse(sort.IntSlice(revisionNumbers))) + + // Process each revision + revisionResponses := make([]contract.OutgoingLetterApprovalRevisionNumberResponse, 0, len(revisionNumbers)) + + totalSteps := 0 + currentStep := 0 + + for _, revNum := range revisionNumbers { + revisionApprovals := revisionMap[revNum] + + // Sort approvals within this revision by step order and parallel group + sort.Slice(revisionApprovals, func(i, j int) bool { + if revisionApprovals[i].StepOrder != revisionApprovals[j].StepOrder { + return revisionApprovals[i].StepOrder < revisionApprovals[j].StepOrder + } + return revisionApprovals[i].ParallelGroup < revisionApprovals[j].ParallelGroup + }) + + // Transform to response format + approvalResponses := make([]contract.EnhancedOutgoingLetterApprovalResponse, 0, len(revisionApprovals)) + + // Only calculate totalSteps and currentStep for the current letter's revision + if revNum == letter.RevisionNumber { + stepOrdersSeen := make(map[int]bool) + + for _, approval := range revisionApprovals { + // Count unique step orders for total steps + if !stepOrdersSeen[approval.StepOrder] { + stepOrdersSeen[approval.StepOrder] = true + totalSteps++ + } + + // Determine current step (lowest step with pending status) + if approval.Status == entities.ApprovalStatusPending && (currentStep == 0 || approval.StepOrder < currentStep) { + currentStep = approval.StepOrder + } + } + + // If no current step found but there are approvals, check if all are completed + if currentStep == 0 && len(revisionApprovals) > 0 { + allCompleted := true + for _, approval := range revisionApprovals { + if approval.IsRequired && approval.Status != entities.ApprovalStatusApproved { + allCompleted = false + break + } + } + if allCompleted { + currentStep = totalSteps // All steps completed + } } } - if allCompleted { - currentStep = totalSteps // All steps completed + + for _, approval := range revisionApprovals { + approvalResp := contract.EnhancedOutgoingLetterApprovalResponse{ + ID: approval.ID, + LetterID: approval.LetterID, + StepID: approval.StepID, + StepOrder: approval.StepOrder, + ParallelGroup: approval.ParallelGroup, + IsRequired: approval.IsRequired, + ApproverID: approval.ApproverID, + RevisionNumber: approval.RevisionNumber, + Status: string(approval.Status), + Remarks: approval.Remarks, + ActedAt: approval.ActedAt, + CreatedAt: approval.CreatedAt, + } + + // Add step details if available + if approval.Step != nil { + approvalResp.Step = &contract.ApprovalFlowStepResponse{ + ID: approval.Step.ID, + StepOrder: approval.Step.StepOrder, + ParallelGroup: approval.Step.ParallelGroup, + Required: approval.Step.Required, + CreatedAt: approval.Step.CreatedAt, + UpdatedAt: approval.Step.UpdatedAt, + } + + // Add approver role if available + if approval.Step.ApproverRole != nil { + approvalResp.Step.ApproverRole = &contract.RoleResponse{ + ID: approval.Step.ApproverRole.ID, + Name: approval.Step.ApproverRole.Name, + Code: approval.Step.ApproverRole.Code, + } + } + + // Add approver user if available + if approval.Step.ApproverUser != nil { + approvalResp.Step.ApproverUser = &contract.UserResponse{ + ID: approval.Step.ApproverUser.ID, + Name: approval.Step.ApproverUser.Name, + Email: approval.Step.ApproverUser.Email, + } + } + } + + // Add approver details if available + if approval.Approver != nil { + approvalResp.Approver = &contract.UserResponse{ + ID: approval.Approver.ID, + Name: approval.Approver.Name, + Email: approval.Approver.Email, + } + } + + approvalResponses = append(approvalResponses, approvalResp) } + + // Add revision response + revisionResponses = append(revisionResponses, contract.OutgoingLetterApprovalRevisionNumberResponse{ + RevisionNumber: revNum, + Approvals: approvalResponses, + }) } response := &contract.GetLetterApprovalsResponse{ - LetterID: letter.ID, - LetterNumber: letter.LetterNumber, - LetterStatus: string(letter.Status), - TotalSteps: totalSteps, - CurrentStep: currentStep, - Approvals: approvalResponses, + LetterID: letter.ID, + LetterNumber: letter.LetterNumber, + LetterStatus: string(letter.Status), + TotalSteps: totalSteps, + CurrentStep: currentStep, + CurrentRevisionNumber: letter.RevisionNumber, + Approvals: revisionResponses, } return response, nil } - func getUserIDFromContext(ctx context.Context) uuid.UUID { appCtx := appcontext.FromGinContext(ctx) if appCtx != nil { @@ -1257,17 +1291,18 @@ func (s *LetterOutgoingServiceImpl) GetApprovalDiscussions(ctx context.Context, approvals := make([]contract.EnhancedOutgoingLetterApprovalResponse, 0, len(letter.Approvals)) for _, approval := range letter.Approvals { approvalResp := contract.EnhancedOutgoingLetterApprovalResponse{ - ID: approval.ID, - LetterID: approval.LetterID, - StepID: approval.StepID, - StepOrder: approval.StepOrder, - ParallelGroup: approval.ParallelGroup, - IsRequired: approval.IsRequired, - ApproverID: approval.ApproverID, - Status: string(approval.Status), - Remarks: approval.Remarks, - ActedAt: approval.ActedAt, - CreatedAt: approval.CreatedAt, + ID: approval.ID, + LetterID: approval.LetterID, + StepID: approval.StepID, + StepOrder: approval.StepOrder, + ParallelGroup: approval.ParallelGroup, + IsRequired: approval.IsRequired, + ApproverID: approval.ApproverID, + RevisionNumber: approval.RevisionNumber, + Status: string(approval.Status), + Remarks: approval.Remarks, + ActedAt: approval.ActedAt, + CreatedAt: approval.CreatedAt, } // Add step details if available