This commit is contained in:
Aditya Siregar
2025-09-01 12:06:14 +07:00
parent 2bdce63852
commit aa662a321f
25 changed files with 2923 additions and 189 deletions
+169 -16
View File
@@ -39,7 +39,7 @@ type LetterOutgoingProcessor interface {
GetApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error)
GetApprovalFlow(ctx context.Context, flowID uuid.UUID) (*entities.ApprovalFlow, error)
// GetOutgoingLetterWithDetails fetches letter with all related data
GetOutgoingLetterWithDetails(ctx context.Context, letterID uuid.UUID) (*entities.LetterOutgoing, error)
GetUsersByIDs(ctx context.Context, userIDs []uuid.UUID) ([]entities.User, error)
@@ -178,7 +178,38 @@ func (p *LetterOutgoingProcessorImpl) createRecipientsFromApprovalFlow(ctx conte
}
}
// Collect all recipients from the first step (can be multiple if parallel)
// Create all approval steps in letter_outgoing_approvals
var approvals []entities.LetterOutgoingApproval
for _, step := range flow.Steps {
approval := entities.LetterOutgoingApproval{
LetterID: letter.ID,
StepID: step.ID,
StepOrder: step.StepOrder,
ParallelGroup: step.ParallelGroup,
IsRequired: step.Required,
ApproverID: step.ApproverUserID,
}
// Set status based on step order
if step.StepOrder == minStepOrder {
// First step(s) are set to pending
approval.Status = entities.ApprovalStatusPending
} else {
// All other steps are set to not_started
approval.Status = entities.ApprovalStatusNotStarted
}
approvals = append(approvals, approval)
}
// Bulk create all approvals
if len(approvals) > 0 {
if err := p.approvalRepo.CreateBulk(ctx, approvals); err != nil {
return err
}
}
// Also create recipients from the first step (for backward compatibility)
var recipients []entities.LetterOutgoingRecipient
for i, step := range flow.Steps {
// Only process steps with the minimum step order (first step)
@@ -198,13 +229,12 @@ func (p *LetterOutgoingProcessorImpl) createRecipientsFromApprovalFlow(ctx conte
}
}
// If no recipients were created, return without error
if len(recipients) == 0 {
return nil
// Bulk create all recipients if any
if len(recipients) > 0 {
return p.recipientRepo.CreateBulk(ctx, recipients)
}
// Bulk create all recipients
return p.recipientRepo.CreateBulk(ctx, recipients)
return nil
}
// createRecipientFromApprovalStep creates a recipient from an approval flow step
@@ -331,12 +361,31 @@ func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Cont
}
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
// Find the minimum step order (first step)
minStepOrder := flow.Steps[0].StepOrder
for _, step := range flow.Steps {
if step.StepOrder < minStepOrder {
minStepOrder = step.StepOrder
}
}
approvals := make([]entities.LetterOutgoingApproval, len(flow.Steps))
for i, step := range flow.Steps {
approvals[i] = entities.LetterOutgoingApproval{
LetterID: letterID,
StepID: step.ID,
Status: entities.ApprovalStatusPending,
LetterID: letterID,
StepID: step.ID,
StepOrder: step.StepOrder,
ParallelGroup: step.ParallelGroup,
IsRequired: step.Required,
ApproverID: step.ApproverUserID,
Status: entities.ApprovalStatusPending,
}
// Set status based on step order
if step.StepOrder == minStepOrder {
approvals[i].Status = entities.ApprovalStatusPending
} else {
approvals[i].Status = entities.ApprovalStatusNotStarted
}
}
@@ -344,6 +393,39 @@ func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Cont
return err
}
// Add first step approvers as recipients
existingRecipients, err := p.recipientRepo.ListByLetter(txCtx, letterID)
if err != nil {
return err
}
// Create a map of existing user IDs for quick lookup
existingUserIDs := make(map[uuid.UUID]bool)
for _, recipient := range existingRecipients {
if recipient.UserID != nil {
existingUserIDs[*recipient.UserID] = true
}
}
// Add approvers from the first step as recipients
for _, approval := range approvals {
if approval.StepOrder == minStepOrder && approval.ApproverID != nil {
if !existingUserIDs[*approval.ApproverID] {
newRecipient := entities.LetterOutgoingRecipient{
LetterID: letterID,
UserID: approval.ApproverID,
IsPrimary: false,
Status: "unread",
IsArchived: false,
}
if err := p.recipientRepo.Create(txCtx, &newRecipient); err != nil {
return err
}
existingUserIDs[*approval.ApproverID] = true
}
}
}
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil {
return err
}
@@ -376,7 +458,78 @@ func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, lette
return err
}
if allApproved {
allApprovals, err := p.approvalRepo.ListByLetter(txCtx, letterID)
if err != nil {
return err
}
approvalsByStep := make(map[int][]entities.LetterOutgoingApproval)
for _, a := range allApprovals {
approvalsByStep[a.StepOrder] = append(approvalsByStep[a.StepOrder], a)
}
currentStepCompleted := true
for _, a := range approvalsByStep[approval.StepOrder] {
if a.IsRequired && a.Status != entities.ApprovalStatusApproved {
currentStepCompleted = false
break
}
}
// If current step is completed, activate the next step and add approvers as recipients
if currentStepCompleted {
nextStepOrder := approval.StepOrder + 1
if nextStepApprovals, exists := approvalsByStep[nextStepOrder]; exists {
currentRecipients, err := p.recipientRepo.ListByLetter(txCtx, letterID)
if err != nil {
return err
}
existingUserIDs := make(map[uuid.UUID]bool)
for _, recipient := range currentRecipients {
if recipient.UserID != nil {
existingUserIDs[*recipient.UserID] = true
}
}
for _, nextApproval := range nextStepApprovals {
if nextApproval.Status == entities.ApprovalStatusNotStarted {
nextApproval.Status = entities.ApprovalStatusPending
if err := p.approvalRepo.Update(txCtx, &nextApproval); err != nil {
return err
}
}
if nextApproval.ApproverID != nil && !existingUserIDs[*nextApproval.ApproverID] {
newRecipient := entities.LetterOutgoingRecipient{
LetterID: letterID,
UserID: nextApproval.ApproverID,
IsPrimary: false,
Status: "unread",
IsArchived: false,
}
if err := p.recipientRepo.Create(txCtx, &newRecipient); err != nil {
return err
}
existingUserIDs[*nextApproval.ApproverID] = true
}
}
}
}
// Check if all required approvals are completed
allRequiredApproved := true
for _, a := range allApprovals {
if a.IsRequired && a.Status != entities.ApprovalStatusApproved {
allRequiredApproved = false
break
}
}
// Update letter status if all required approvals are done
if allRequiredApproved {
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
return err
}
@@ -587,11 +740,11 @@ func (p *LetterOutgoingProcessorImpl) GetOutgoingLetterWithDetails(ctx context.C
"Discussions.Attachments",
"ActivityLogs",
})
if err != nil {
return nil, err
}
return letter, nil
}
@@ -600,16 +753,16 @@ func (p *LetterOutgoingProcessorImpl) GetUsersByIDs(ctx context.Context, userIDs
if len(userIDs) == 0 {
return []entities.User{}, nil
}
var users []entities.User
err := p.db.WithContext(ctx).
Preload("Profile").
Where("id IN ?", userIDs).
Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}