dukcapil/internal/processor/letter_outgoing_recipient_processor.go
2025-09-20 16:31:22 +07:00

221 lines
6.7 KiB
Go

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
}