add archive
This commit is contained in:
@@ -21,6 +21,7 @@ type LetterOutgoingProcessor interface {
|
||||
DeleteOutgoingLetter(ctx context.Context, id uuid.UUID, userID uuid.UUID) error
|
||||
|
||||
UpdateLetterStatus(ctx context.Context, letterID uuid.UUID, status entities.LetterOutgoingStatus, userID uuid.UUID, fromStatus, toStatus *string) error
|
||||
ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID, userID uuid.UUID) error
|
||||
|
||||
ProcessApprovalSubmission(ctx context.Context, letterID uuid.UUID, approvalFlowID uuid.UUID, userID uuid.UUID) error
|
||||
ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID, allApproved bool) error
|
||||
@@ -45,14 +46,13 @@ type LetterOutgoingProcessor interface {
|
||||
GetOutgoingLetterWithDetails(ctx context.Context, letterID uuid.UUID) (*entities.LetterOutgoing, error)
|
||||
GetUsersByIDs(ctx context.Context, userIDs []uuid.UUID) ([]entities.User, error)
|
||||
BulkArchiveOutgoingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error)
|
||||
|
||||
|
||||
// Batch loading methods for efficient querying
|
||||
GetBatchAttachments(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingAttachment, error)
|
||||
GetBatchRecipients(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingRecipient, error)
|
||||
GetBatchPriorities(ctx context.Context, priorityIDs []uuid.UUID) (map[uuid.UUID]*entities.Priority, error)
|
||||
GetBatchInstitutions(ctx context.Context, institutionIDs []uuid.UUID) (map[uuid.UUID]*entities.Institution, error)
|
||||
GetBatchOutgoingRecipientsByUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterOutgoingRecipient, error)
|
||||
|
||||
}
|
||||
|
||||
type LetterOutgoingProcessorImpl struct {
|
||||
@@ -356,8 +356,6 @@ func (p *LetterOutgoingProcessorImpl) UpdateLetterStatus(ctx context.Context, le
|
||||
activityLog.ActionType = entities.LetterOutgoingActionApproved
|
||||
case entities.LetterOutgoingStatusSent:
|
||||
activityLog.ActionType = entities.LetterOutgoingActionSent
|
||||
case entities.LetterOutgoingStatusArchived:
|
||||
activityLog.ActionType = entities.LetterOutgoingActionArchived
|
||||
default:
|
||||
activityLog.ActionType = entities.LetterOutgoingActionUpdated
|
||||
}
|
||||
@@ -370,6 +368,28 @@ func (p *LetterOutgoingProcessorImpl) UpdateLetterStatus(ctx context.Context, le
|
||||
})
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID, userID uuid.UUID) error {
|
||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
// Archive the letter using the new flag
|
||||
if err := p.letterRepo.Archive(txCtx, letterID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Log the activity
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActorUserID: &userID,
|
||||
ActionType: entities.LetterOutgoingActionArchived,
|
||||
}
|
||||
|
||||
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Context, letterID uuid.UUID, approvalFlowID uuid.UUID, userID uuid.UUID) error {
|
||||
flow, err := p.approvalFlowRepo.Get(ctx, approvalFlowID)
|
||||
if err != nil {
|
||||
@@ -465,104 +485,173 @@ func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Cont
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID, allApproved bool) error {
|
||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
now := time.Now()
|
||||
approval.Status = entities.ApprovalStatusApproved
|
||||
approval.ApproverID = &userID
|
||||
approval.ActedAt = &now
|
||||
|
||||
if err := p.approvalRepo.Update(txCtx, approval); err != nil {
|
||||
// Step 1: Update the approval record
|
||||
if err := p.updateApprovalRecord(txCtx, approval, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allApprovals, err := p.approvalRepo.ListByLetter(txCtx, letterID)
|
||||
// Step 2: Get all approvals and organize by step
|
||||
approvalsByStep, err := p.getApprovalsByStep(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
|
||||
// Step 3: Check if current step is completed
|
||||
if p.isStepCompleted(approvalsByStep[approval.StepOrder]) {
|
||||
// Step 4: Activate next step if exists
|
||||
if err := p.activateNextStep(txCtx, letterID, approval.StepOrder, approvalsByStep); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Step 5: Check if all required approvals are completed
|
||||
if p.areAllRequiredApprovalsCompleted(approvalsByStep) {
|
||||
// Step 6: Update letter status to approved
|
||||
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionApproved,
|
||||
ActorUserID: &userID,
|
||||
TargetID: &approval.ID,
|
||||
// Step 7: Log the activity
|
||||
return p.logApprovalActivity(txCtx, letterID, approval.ID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
// updateApprovalRecord updates the approval with approver info and timestamp
|
||||
func (p *LetterOutgoingProcessorImpl) updateApprovalRecord(ctx context.Context, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error {
|
||||
now := time.Now()
|
||||
approval.Status = entities.ApprovalStatusApproved
|
||||
approval.ApproverID = &userID
|
||||
approval.ActedAt = &now
|
||||
|
||||
return p.approvalRepo.Update(ctx, approval)
|
||||
}
|
||||
|
||||
// getApprovalsByStep fetches all approvals and organizes them by step order
|
||||
func (p *LetterOutgoingProcessorImpl) getApprovalsByStep(ctx context.Context, letterID uuid.UUID) (map[int][]entities.LetterOutgoingApproval, error) {
|
||||
allApprovals, err := p.approvalRepo.ListByLetter(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
approvalsByStep := make(map[int][]entities.LetterOutgoingApproval)
|
||||
for _, approval := range allApprovals {
|
||||
approvalsByStep[approval.StepOrder] = append(approvalsByStep[approval.StepOrder], approval)
|
||||
}
|
||||
|
||||
return approvalsByStep, nil
|
||||
}
|
||||
|
||||
// isStepCompleted checks if all required approvals in a step are approved
|
||||
func (p *LetterOutgoingProcessorImpl) isStepCompleted(stepApprovals []entities.LetterOutgoingApproval) bool {
|
||||
for _, approval := range stepApprovals {
|
||||
if approval.IsRequired && approval.Status != entities.ApprovalStatusApproved {
|
||||
return false
|
||||
}
|
||||
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// activateNextStep activates the next approval step and adds approvers as recipients
|
||||
func (p *LetterOutgoingProcessorImpl) activateNextStep(ctx context.Context, letterID uuid.UUID, currentStepOrder int, approvalsByStep map[int][]entities.LetterOutgoingApproval) error {
|
||||
nextStepOrder := currentStepOrder + 1
|
||||
nextStepApprovals, exists := approvalsByStep[nextStepOrder]
|
||||
if !exists {
|
||||
return nil // No next step
|
||||
}
|
||||
|
||||
// Get existing recipients to avoid duplicates
|
||||
existingUserIDs, err := p.getExistingRecipientUserIDs(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process each approval in the next step
|
||||
for _, nextApproval := range nextStepApprovals {
|
||||
// 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
|
||||
}
|
||||
|
||||
// getExistingRecipientUserIDs gets a set of existing recipient user IDs
|
||||
func (p *LetterOutgoingProcessorImpl) getExistingRecipientUserIDs(ctx context.Context, letterID uuid.UUID) (map[uuid.UUID]bool, error) {
|
||||
currentRecipients, err := p.recipientRepo.ListByLetter(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingUserIDs := make(map[uuid.UUID]bool)
|
||||
for _, recipient := range currentRecipients {
|
||||
if recipient.UserID != nil {
|
||||
existingUserIDs[*recipient.UserID] = true
|
||||
}
|
||||
}
|
||||
|
||||
return existingUserIDs, nil
|
||||
}
|
||||
|
||||
// activateApprovalIfNotStarted changes approval status from not_started to pending
|
||||
func (p *LetterOutgoingProcessorImpl) activateApprovalIfNotStarted(ctx context.Context, approval *entities.LetterOutgoingApproval) error {
|
||||
if approval.Status != entities.ApprovalStatusNotStarted {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
approval.Status = entities.ApprovalStatusPending
|
||||
return p.approvalRepo.Update(ctx, approval)
|
||||
}
|
||||
|
||||
// addApproverAsRecipientIfNeeded adds an approver as a recipient if they don't exist
|
||||
func (p *LetterOutgoingProcessorImpl) addApproverAsRecipientIfNeeded(ctx context.Context, letterID uuid.UUID, approverID *uuid.UUID, existingUserIDs map[uuid.UUID]bool) error {
|
||||
if approverID == nil || existingUserIDs[*approverID] {
|
||||
return nil
|
||||
}
|
||||
|
||||
newRecipient := entities.LetterOutgoingRecipient{
|
||||
LetterID: letterID,
|
||||
UserID: approverID,
|
||||
IsPrimary: false,
|
||||
Status: "unread",
|
||||
IsArchived: false,
|
||||
}
|
||||
|
||||
if err := p.recipientRepo.Create(ctx, &newRecipient); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingUserIDs[*approverID] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// areAllRequiredApprovalsCompleted checks if all required approvals are completed
|
||||
func (p *LetterOutgoingProcessorImpl) areAllRequiredApprovalsCompleted(approvalsByStep map[int][]entities.LetterOutgoingApproval) bool {
|
||||
for _, stepApprovals := range approvalsByStep {
|
||||
for _, approval := range stepApprovals {
|
||||
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{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionApproved,
|
||||
ActorUserID: &userID,
|
||||
TargetID: &approvalID,
|
||||
}
|
||||
return p.activityLogRepo.Create(ctx, activityLog)
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) ProcessRejection(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error {
|
||||
@@ -576,12 +665,12 @@ func (p *LetterOutgoingProcessorImpl) ProcessRejection(ctx context.Context, lett
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusDraft); err != nil {
|
||||
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusRejected); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromStatus := string(entities.LetterOutgoingStatusPendingApproval)
|
||||
toStatus := string(entities.LetterOutgoingStatusDraft)
|
||||
toStatus := string(entities.LetterOutgoingStatusRejected)
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionRejected,
|
||||
|
||||
@@ -623,6 +623,10 @@ func (p *LetterProcessorImpl) BulkArchiveIncomingLetters(ctx context.Context, le
|
||||
return p.letterRepo.BulkArchive(ctx, letterIDs)
|
||||
}
|
||||
|
||||
func (p *LetterProcessorImpl) ArchiveIncomingLetter(ctx context.Context, letterID uuid.UUID) error {
|
||||
return p.letterRepo.Archive(ctx, letterID)
|
||||
}
|
||||
|
||||
// BulkArchiveIncomingLettersForUser archives letters for a specific user only
|
||||
func (p *LetterProcessorImpl) BulkArchiveIncomingLettersForUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (int64, error) {
|
||||
return p.letterRepo.BulkArchiveForUser(ctx, letterIDs, userID)
|
||||
|
||||
Reference in New Issue
Block a user