package processor import ( "context" "eslogad-be/internal/entities" "eslogad-be/internal/repository" "github.com/google/uuid" ) // LetterOutgoingRecipientProcessor handles all recipient-related operations for outgoing letters type LetterOutgoingRecipientProcessor interface { // CreateRecipients creates multiple recipients for a letter CreateRecipients(ctx context.Context, letterID uuid.UUID, recipients []entities.LetterOutgoingRecipient) error // CreateInitialRecipients creates recipients from both approval workflow and department members CreateInitialRecipients(ctx context.Context, letter *entities.LetterOutgoing, creatorDepartmentID uuid.UUID) error // UpdateRecipient updates a single recipient's information UpdateRecipient(ctx context.Context, recipient *entities.LetterOutgoingRecipient) error // RemoveRecipient removes a recipient from a letter RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error // GetRecipientsByLetterID retrieves all recipients for a specific letter GetRecipientsByLetterID(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingRecipient, error) } type LetterOutgoingRecipientProcessorImpl struct { recipientRepo *repository.LetterOutgoingRecipientRepository approvalFlowRepo *repository.ApprovalFlowRepository userDeptRepo *repository.UserDepartmentRepository } func NewLetterOutgoingRecipientProcessor( recipientRepo *repository.LetterOutgoingRecipientRepository, approvalFlowRepo *repository.ApprovalFlowRepository, userDeptRepo *repository.UserDepartmentRepository, ) *LetterOutgoingRecipientProcessorImpl { return &LetterOutgoingRecipientProcessorImpl{ recipientRepo: recipientRepo, approvalFlowRepo: approvalFlowRepo, userDeptRepo: userDeptRepo, } } // CreateRecipients creates multiple recipients for a letter func (p *LetterOutgoingRecipientProcessorImpl) CreateRecipients( ctx context.Context, letterID uuid.UUID, recipients []entities.LetterOutgoingRecipient, ) error { if len(recipients) == 0 { return nil } // Ensure all recipients have the correct letter ID and default status for i := range recipients { recipients[i].LetterID = letterID if recipients[i].Status == "" { recipients[i].Status = "pending" } } return p.recipientRepo.CreateBulk(ctx, recipients) } // CreateInitialRecipients creates the initial set of recipients for an outgoing letter // It combines: // 1. Approvers from the approval workflow (if exists) // 2. All active users from the letter creator's department func (p *LetterOutgoingRecipientProcessorImpl) CreateInitialRecipients( ctx context.Context, letter *entities.LetterOutgoing, creatorDepartmentID uuid.UUID, ) error { // Track unique users to avoid duplicates uniqueUsers := make(map[uuid.UUID]bool) var allRecipients []entities.LetterOutgoingRecipient // Step 1: Add recipients from approval workflow approvalRecipients := p.collectApprovalWorkflowRecipients(ctx, letter, uniqueUsers) allRecipients = append(allRecipients, approvalRecipients...) // Step 2: Add all users from the creator's department departmentRecipients := p.collectDepartmentRecipients(ctx, letter.ID, creatorDepartmentID, uniqueUsers) allRecipients = append(allRecipients, departmentRecipients...) // Step 3: Mark the first recipient as primary and save all if len(allRecipients) > 0 { allRecipients[0].IsPrimary = true return p.recipientRepo.CreateBulk(ctx, allRecipients) } return nil } // collectApprovalWorkflowRecipients gathers all users who are approvers in the workflow func (p *LetterOutgoingRecipientProcessorImpl) collectApprovalWorkflowRecipients( ctx context.Context, letter *entities.LetterOutgoing, existingUsers map[uuid.UUID]bool, ) []entities.LetterOutgoingRecipient { var recipients []entities.LetterOutgoingRecipient // If no approval workflow is assigned, return empty if letter.ApprovalFlowID == nil { return recipients } // Fetch the approval workflow approvalFlow, err := p.approvalFlowRepo.Get(ctx, *letter.ApprovalFlowID) if err != nil || approvalFlow == nil || len(approvalFlow.Steps) == 0 { return recipients } // Add each approver as a recipient for _, step := range approvalFlow.Steps { if step.ApproverUserID == nil { continue } userID := *step.ApproverUserID // Skip if user is already added if existingUsers[userID] { continue } existingUsers[userID] = true recipient := entities.LetterOutgoingRecipient{ LetterID: letter.ID, UserID: &userID, IsPrimary: false, Status: "pending", } recipients = append(recipients, recipient) } return recipients } // collectDepartmentRecipients gathers all active users from a specific department func (p *LetterOutgoingRecipientProcessorImpl) collectDepartmentRecipients( ctx context.Context, letterID uuid.UUID, departmentID uuid.UUID, existingUsers map[uuid.UUID]bool, ) []entities.LetterOutgoingRecipient { var recipients []entities.LetterOutgoingRecipient // If no department specified, return empty if departmentID == uuid.Nil { return recipients } // Fetch all active users in the department userDepartmentMappings, err := p.userDeptRepo.ListActiveByDepartmentIDs(ctx, []uuid.UUID{departmentID}) if err != nil { return recipients } // Add each department user as a recipient for _, mapping := range userDepartmentMappings { // Skip if user is already added (e.g., from approval workflow) if existingUsers[mapping.UserID] { continue } existingUsers[mapping.UserID] = true recipient := entities.LetterOutgoingRecipient{ LetterID: letterID, UserID: &mapping.UserID, DepartmentID: &mapping.DepartmentID, IsPrimary: false, Status: "pending", } recipients = append(recipients, recipient) } return recipients } // UpdateRecipient updates an existing recipient's information func (p *LetterOutgoingRecipientProcessorImpl) UpdateRecipient( ctx context.Context, recipient *entities.LetterOutgoingRecipient, ) error { return p.recipientRepo.Update(ctx, recipient) } // RemoveRecipient removes a recipient from a letter func (p *LetterOutgoingRecipientProcessorImpl) RemoveRecipient( ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, ) error { return p.recipientRepo.Delete(ctx, recipientID) } // GetRecipientsByLetterID retrieves all recipients for a specific letter func (p *LetterOutgoingRecipientProcessorImpl) GetRecipientsByLetterID( ctx context.Context, letterID uuid.UUID, ) ([]entities.LetterOutgoingRecipient, error) { recipientMap, err := p.recipientRepo.ListByLetterIDs(ctx, []uuid.UUID{letterID}) if err != nil { return nil, err } if recipients, ok := recipientMap[letterID]; ok { return recipients, nil } return []entities.LetterOutgoingRecipient{}, nil }