1133 lines
40 KiB
Go
1133 lines
40 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"eslogad-be/internal/contract"
|
|
"eslogad-be/internal/entities"
|
|
"eslogad-be/internal/repository"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type LetterOutgoingProcessor interface {
|
|
CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error
|
|
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error)
|
|
ListOutgoingLetters(ctx context.Context, filter repository.ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error)
|
|
SearchOutgoingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error)
|
|
UpdateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, userID uuid.UUID) error
|
|
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) error
|
|
ProcessRejection(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error
|
|
ProcessRevision(ctx context.Context, letterID uuid.UUID, attachment entities.LetterOutgoingAttachment, userID uuid.UUID) error
|
|
|
|
AddRecipients(ctx context.Context, letterID uuid.UUID, recipients []entities.LetterOutgoingRecipient, userID uuid.UUID) error
|
|
UpdateRecipient(ctx context.Context, recipient *entities.LetterOutgoingRecipient) error
|
|
RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, userID uuid.UUID) error
|
|
|
|
AddAttachments(ctx context.Context, letterID uuid.UUID, attachments []entities.LetterOutgoingAttachment, userID uuid.UUID) error
|
|
RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID, userID uuid.UUID) error
|
|
|
|
CreateDiscussion(ctx context.Context, discussion *entities.LetterOutgoingDiscussion, attachments []entities.LetterOutgoingDiscussionAttachment, userID uuid.UUID) error
|
|
GetDiscussionByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoingDiscussion, error)
|
|
UpdateDiscussion(ctx context.Context, discussion *entities.LetterOutgoingDiscussion) error
|
|
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)
|
|
|
|
// 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)
|
|
BulkArchiveOutgoingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error)
|
|
BulkArchiveIncomingLettersForUser(ctx context.Context, letterIDs []uuid.UUID, userId 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 {
|
|
db *gorm.DB
|
|
letterRepo *repository.LetterOutgoingRepository
|
|
attachmentRepo *repository.LetterOutgoingAttachmentRepository
|
|
recipientRepo *repository.LetterOutgoingRecipientRepository
|
|
discussionRepo *repository.LetterOutgoingDiscussionRepository
|
|
discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository
|
|
activityLogRepo *repository.LetterOutgoingActivityLogRepository
|
|
approvalFlowRepo *repository.ApprovalFlowRepository
|
|
approvalRepo *repository.LetterOutgoingApprovalRepository
|
|
numberGenerator *LetterNumberGeneratorImpl
|
|
txManager *repository.TxManager
|
|
priorityRepo *repository.PriorityRepository
|
|
institutionRepo *repository.InstitutionRepository
|
|
}
|
|
|
|
func NewLetterOutgoingProcessor(
|
|
db *gorm.DB,
|
|
letterRepo *repository.LetterOutgoingRepository,
|
|
attachmentRepo *repository.LetterOutgoingAttachmentRepository,
|
|
recipientRepo *repository.LetterOutgoingRecipientRepository,
|
|
discussionRepo *repository.LetterOutgoingDiscussionRepository,
|
|
discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository,
|
|
activityLogRepo *repository.LetterOutgoingActivityLogRepository,
|
|
approvalFlowRepo *repository.ApprovalFlowRepository,
|
|
approvalRepo *repository.LetterOutgoingApprovalRepository,
|
|
numberGenerator *LetterNumberGeneratorImpl,
|
|
txManager *repository.TxManager,
|
|
priorityRepo *repository.PriorityRepository,
|
|
institutionRepo *repository.InstitutionRepository,
|
|
) *LetterOutgoingProcessorImpl {
|
|
return &LetterOutgoingProcessorImpl{
|
|
db: db,
|
|
letterRepo: letterRepo,
|
|
attachmentRepo: attachmentRepo,
|
|
recipientRepo: recipientRepo,
|
|
discussionRepo: discussionRepo,
|
|
discussionAttachmentRepo: discussionAttachmentRepo,
|
|
activityLogRepo: activityLogRepo,
|
|
approvalFlowRepo: approvalFlowRepo,
|
|
approvalRepo: approvalRepo,
|
|
numberGenerator: numberGenerator,
|
|
txManager: txManager,
|
|
priorityRepo: priorityRepo,
|
|
institutionRepo: institutionRepo,
|
|
}
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
// Step 1: Assign approval flow from department if not provided
|
|
if err := p.assignApprovalFlowFromDepartment(txCtx, letter, departmentID); err != nil {
|
|
// Log error but continue - approval flow is optional
|
|
}
|
|
|
|
// Step 2: Set status based on approval flow
|
|
if letter.ApprovalFlowID != nil {
|
|
letter.Status = entities.LetterOutgoingStatusPendingApproval
|
|
} else {
|
|
letter.Status = entities.LetterOutgoingStatusApproved
|
|
}
|
|
|
|
// Step 3: Generate and assign letter number
|
|
if err := p.assignLetterNumber(txCtx, letter); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 4: Create the letter
|
|
if err := p.letterRepo.Create(txCtx, letter); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 5: Create recipients from approval flow
|
|
if err := p.createRecipientsFromApprovalFlow(txCtx, letter); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 6: Create attachments
|
|
if err := p.createAttachments(txCtx, letter.ID, attachments); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 7: Log the activity
|
|
return p.logActivity(txCtx, letter.ID, entities.LetterOutgoingActionCreated, userID)
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) assignApprovalFlowFromDepartment(ctx context.Context, letter *entities.LetterOutgoing, departmentID uuid.UUID) error {
|
|
if letter.ApprovalFlowID != nil || departmentID == uuid.Nil {
|
|
return nil
|
|
}
|
|
|
|
flow, err := p.approvalFlowRepo.GetByDepartment(ctx, departmentID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if flow != nil {
|
|
letter.ApprovalFlowID = &flow.ID
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) assignLetterNumber(ctx context.Context, letter *entities.LetterOutgoing) error {
|
|
letterNumber, err := p.numberGenerator.GenerateNumber(
|
|
ctx,
|
|
contract.SettingOutgoingLetterPrefix,
|
|
contract.SettingOutgoingLetterSequence,
|
|
"ESLO",
|
|
)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
letter.LetterNumber = letterNumber
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) createRecipientsFromApprovalFlow(ctx context.Context, letter *entities.LetterOutgoing) error {
|
|
if letter.ApprovalFlowID == nil {
|
|
return nil
|
|
}
|
|
|
|
flow, err := p.approvalFlowRepo.Get(ctx, *letter.ApprovalFlowID)
|
|
if err != nil || flow == nil || len(flow.Steps) == 0 {
|
|
return err
|
|
}
|
|
|
|
// Find the minimum step order (first step)
|
|
minStepOrder := flow.Steps[0].StepOrder
|
|
for _, step := range flow.Steps {
|
|
if step.StepOrder < minStepOrder {
|
|
minStepOrder = step.StepOrder
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
if step.StepOrder != minStepOrder {
|
|
continue
|
|
}
|
|
|
|
recipient := p.createRecipientFromApprovalStep(step, letter.ID)
|
|
if recipient != nil {
|
|
// Mark the first recipient as primary
|
|
if i == 0 {
|
|
recipient.IsPrimary = true
|
|
} else {
|
|
recipient.IsPrimary = false
|
|
}
|
|
recipients = append(recipients, *recipient)
|
|
}
|
|
}
|
|
|
|
// Bulk create all recipients if any
|
|
if len(recipients) > 0 {
|
|
return p.recipientRepo.CreateBulk(ctx, recipients)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createRecipientFromApprovalStep creates a recipient from an approval flow step
|
|
func (p *LetterOutgoingProcessorImpl) createRecipientFromApprovalStep(step entities.ApprovalFlowStep, letterID uuid.UUID) *entities.LetterOutgoingRecipient {
|
|
recipient := &entities.LetterOutgoingRecipient{
|
|
LetterID: letterID,
|
|
Status: "pending",
|
|
IsArchived: false,
|
|
UserID: &step.ApproverUser.ID,
|
|
}
|
|
|
|
return recipient
|
|
}
|
|
|
|
// createAttachments creates letter attachments
|
|
func (p *LetterOutgoingProcessorImpl) createAttachments(ctx context.Context, letterID uuid.UUID, attachments []entities.LetterOutgoingAttachment) error {
|
|
if len(attachments) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Update letter IDs for all attachments
|
|
for i := range attachments {
|
|
attachments[i].LetterID = letterID
|
|
}
|
|
|
|
return p.attachmentRepo.CreateBulk(ctx, attachments)
|
|
}
|
|
|
|
// logActivity logs an activity for the letter
|
|
func (p *LetterOutgoingProcessorImpl) logActivity(ctx context.Context, letterID uuid.UUID, actionType string, userID uuid.UUID) error {
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: actionType,
|
|
ActorUserID: &userID,
|
|
}
|
|
return p.activityLogRepo.Create(ctx, activityLog)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error) {
|
|
return p.letterRepo.Get(ctx, id)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) ListOutgoingLetters(ctx context.Context, filter repository.ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error) {
|
|
return p.letterRepo.List(ctx, filter, limit, offset)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) UpdateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.letterRepo.Update(txCtx, letter); err != nil {
|
|
return err
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letter.ID,
|
|
ActionType: entities.LetterOutgoingActionUpdated,
|
|
ActorUserID: &userID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.letterRepo.SoftDelete(txCtx, id); err != nil {
|
|
return err
|
|
}
|
|
|
|
letter, _ := p.letterRepo.Get(txCtx, id)
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letter.ID,
|
|
ActionType: entities.LetterOutgoingActionDeleted,
|
|
ActorUserID: &userID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) UpdateLetterStatus(ctx context.Context, letterID uuid.UUID, status entities.LetterOutgoingStatus, userID uuid.UUID, fromStatus, toStatus *string) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.letterRepo.UpdateStatus(txCtx, letterID, status); err != nil {
|
|
return err
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActorUserID: &userID,
|
|
FromStatus: fromStatus,
|
|
ToStatus: toStatus,
|
|
}
|
|
|
|
switch status {
|
|
case entities.LetterOutgoingStatusPendingApproval:
|
|
activityLog.ActionType = entities.LetterOutgoingActionSubmittedApproval
|
|
case entities.LetterOutgoingStatusApproved:
|
|
activityLog.ActionType = entities.LetterOutgoingActionApproved
|
|
case entities.LetterOutgoingStatusSent:
|
|
activityLog.ActionType = entities.LetterOutgoingActionSent
|
|
default:
|
|
activityLog.ActionType = entities.LetterOutgoingActionUpdated
|
|
}
|
|
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
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 {
|
|
return err
|
|
}
|
|
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
// Get the letter to get the current revision number
|
|
letter, err := p.letterRepo.Get(txCtx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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,
|
|
RevisionNumber: letter.RevisionNumber,
|
|
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
|
|
}
|
|
}
|
|
|
|
if err := p.approvalRepo.CreateBulk(txCtx, approvals); err != nil {
|
|
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
|
|
}
|
|
|
|
fromStatus := string(entities.LetterOutgoingStatusDraft)
|
|
toStatus := string(entities.LetterOutgoingStatusPendingApproval)
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionSubmittedApproval,
|
|
ActorUserID: &userID,
|
|
FromStatus: &fromStatus,
|
|
ToStatus: &toStatus,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
// Step 1: Update the approval record
|
|
if err := p.updateApprovalRecord(txCtx, approval, userID); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 2: Get all approvals FOR THE SAME REVISION and organize by step
|
|
approvalsByStep, err := p.getApprovalsByStepForRevision(txCtx, letterID, approval.RevisionNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 3: Check if current step is completed
|
|
if p.isStepCompleted(approvalsByStep[approval.StepOrder]) {
|
|
// Step 4: Activate next step if exists
|
|
if err := p.activateNextStepForRevision(txCtx, letterID, approval.StepOrder, approval.RevisionNumber, approvalsByStep); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Step 5: Check if all required approvals are completed FOR THIS REVISION
|
|
if p.areAllRequiredApprovalsCompleted(approvalsByStep) {
|
|
// Step 6: Update letter status to approved
|
|
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// getApprovalsByStepForRevision fetches approvals for a specific revision and organizes them by step order
|
|
func (p *LetterOutgoingProcessorImpl) getApprovalsByStepForRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) (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 {
|
|
// Only include approvals from the same revision
|
|
if approval.RevisionNumber == revisionNumber {
|
|
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
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
// activateNextStepForRevision activates the next approval step for a specific revision
|
|
func (p *LetterOutgoingProcessorImpl) activateNextStepForRevision(ctx context.Context, letterID uuid.UUID, currentStepOrder int, revisionNumber 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 (already filtered by revision in approvalsByStep)
|
|
for _, nextApproval := range nextStepApprovals {
|
|
// Only process if it's the same revision
|
|
if nextApproval.RevisionNumber == revisionNumber {
|
|
// 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 {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
now := time.Now()
|
|
approval.Status = entities.ApprovalStatusRejected
|
|
approval.ApproverID = &userID
|
|
approval.ActedAt = &now
|
|
|
|
if err := p.approvalRepo.Update(txCtx, approval); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Mark all other pending approvals in the same revision as rejected
|
|
allApprovals, err := p.approvalRepo.ListByLetter(txCtx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range allApprovals {
|
|
// Only update other pending approvals from the same revision
|
|
if allApprovals[i].RevisionNumber == approval.RevisionNumber &&
|
|
allApprovals[i].ID != approval.ID &&
|
|
allApprovals[i].Status == entities.ApprovalStatusPending {
|
|
allApprovals[i].Status = entities.ApprovalStatusRejected
|
|
if err := p.approvalRepo.Update(txCtx, &allApprovals[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusRejected); err != nil {
|
|
return err
|
|
}
|
|
|
|
fromStatus := string(entities.LetterOutgoingStatusPendingApproval)
|
|
toStatus := string(entities.LetterOutgoingStatusRejected)
|
|
|
|
// Include rejection remarks in activity log context
|
|
var context entities.JSONB
|
|
if approval.Remarks != nil && *approval.Remarks != "" {
|
|
context = entities.JSONB{"remarks": *approval.Remarks, "revision_number": approval.RevisionNumber}
|
|
} else {
|
|
context = entities.JSONB{"revision_number": approval.RevisionNumber}
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionRejected,
|
|
ActorUserID: &userID,
|
|
TargetID: &approval.ID,
|
|
FromStatus: &fromStatus,
|
|
ToStatus: &toStatus,
|
|
Context: context,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) ProcessRevision(ctx context.Context, letterID uuid.UUID, attachment entities.LetterOutgoingAttachment, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
// Get the current letter
|
|
letter, err := p.letterRepo.Get(txCtx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Increment revision number
|
|
letter.RevisionNumber++
|
|
|
|
// Set revision number on the new attachment
|
|
attachment.RevisionNumber = letter.RevisionNumber
|
|
|
|
// Add the new attachment
|
|
if err := p.attachmentRepo.Create(txCtx, &attachment); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update letter with new revision number
|
|
if err := p.letterRepo.Update(txCtx, letter); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update status to pending approval (ready for re-submission)
|
|
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get existing approvals for the current revision
|
|
approvals, err := p.approvalRepo.ListByLetter(txCtx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create new approval records for the new revision
|
|
for _, approval := range approvals {
|
|
// Create a new approval for the new revision
|
|
newApproval := entities.LetterOutgoingApproval{
|
|
LetterID: approval.LetterID,
|
|
StepID: approval.StepID,
|
|
RevisionNumber: letter.RevisionNumber,
|
|
StepOrder: approval.StepOrder,
|
|
ParallelGroup: approval.ParallelGroup,
|
|
IsRequired: approval.IsRequired,
|
|
Status: entities.ApprovalStatusPending,
|
|
ApproverID: approval.ApproverID,
|
|
}
|
|
if err := p.approvalRepo.Create(txCtx, &newApproval); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Log activity
|
|
fromStatus := string(entities.LetterOutgoingStatusRejected)
|
|
toStatus := string(entities.LetterOutgoingStatusPendingApproval)
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionRevised,
|
|
ActorUserID: &userID,
|
|
TargetID: &attachment.ID,
|
|
FromStatus: &fromStatus,
|
|
ToStatus: &toStatus,
|
|
Context: entities.JSONB{"attachment": attachment.FileName, "revision_number": letter.RevisionNumber},
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) AddRecipients(ctx context.Context, letterID uuid.UUID, recipients []entities.LetterOutgoingRecipient, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
|
|
return err
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionRecipientAdded,
|
|
ActorUserID: &userID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) UpdateRecipient(ctx context.Context, recipient *entities.LetterOutgoingRecipient) error {
|
|
return p.recipientRepo.Update(ctx, recipient)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.recipientRepo.Delete(txCtx, recipientID); err != nil {
|
|
return err
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionRecipientRemoved,
|
|
ActorUserID: &userID,
|
|
TargetID: &recipientID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, attachments []entities.LetterOutgoingAttachment, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
// Get the letter to get the current revision number
|
|
letter, err := p.letterRepo.Get(txCtx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set revision number on all attachments
|
|
for i := range attachments {
|
|
attachments[i].RevisionNumber = letter.RevisionNumber
|
|
}
|
|
|
|
if err := p.attachmentRepo.CreateBulk(txCtx, attachments); err != nil {
|
|
return err
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionAttachmentAdded,
|
|
ActorUserID: &userID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.attachmentRepo.Delete(txCtx, attachmentID); err != nil {
|
|
return err
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: letterID,
|
|
ActionType: entities.LetterOutgoingActionAttachmentRemoved,
|
|
ActorUserID: &userID,
|
|
TargetID: &attachmentID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) CreateDiscussion(ctx context.Context, discussion *entities.LetterOutgoingDiscussion, attachments []entities.LetterOutgoingDiscussionAttachment, userID uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.discussionRepo.Create(txCtx, discussion); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(attachments) > 0 {
|
|
if err := p.discussionAttachmentRepo.CreateBulk(txCtx, attachments); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
activityLog := &entities.LetterOutgoingActivityLog{
|
|
LetterID: discussion.LetterID,
|
|
ActionType: entities.LetterOutgoingActionDiscussionAdded,
|
|
ActorUserID: &userID,
|
|
TargetID: &discussion.ID,
|
|
}
|
|
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) GetDiscussionByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoingDiscussion, error) {
|
|
return p.discussionRepo.Get(ctx, id)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) UpdateDiscussion(ctx context.Context, discussion *entities.LetterOutgoingDiscussion) error {
|
|
return p.discussionRepo.Update(ctx, discussion)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) DeleteDiscussion(ctx context.Context, id uuid.UUID) error {
|
|
return p.discussionRepo.Delete(ctx, id)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) GetApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error) {
|
|
// Get the letter first to know the current revision
|
|
letter, err := p.letterRepo.Get(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Filter to only return approvals for the specified revision
|
|
var currentRevisionApprovals []entities.LetterOutgoingApproval
|
|
for _, approval := range approvals {
|
|
if approval.RevisionNumber == revisionNumber {
|
|
currentRevisionApprovals = append(currentRevisionApprovals, approval)
|
|
}
|
|
}
|
|
|
|
return currentRevisionApprovals, nil
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) GetApprovalFlow(ctx context.Context, flowID uuid.UUID) (*entities.ApprovalFlow, error) {
|
|
return p.approvalFlowRepo.Get(ctx, flowID)
|
|
}
|
|
|
|
// GetOutgoingLetterWithDetails fetches letter with all related data including approvals, discussions, and users
|
|
func (p *LetterOutgoingProcessorImpl) GetOutgoingLetterWithDetails(ctx context.Context, letterID uuid.UUID) (*entities.LetterOutgoing, error) {
|
|
letter, err := p.letterRepo.GetWithRelations(ctx, letterID, []string{
|
|
"Priority",
|
|
"ReceiverInstitution",
|
|
"Creator",
|
|
"Creator.Profile",
|
|
"ApprovalFlow",
|
|
"ApprovalFlow.Steps",
|
|
"ApprovalFlow.Steps.ApproverRole",
|
|
"ApprovalFlow.Steps.ApproverUser",
|
|
"ApprovalFlow.Steps.ApproverUser.Profile",
|
|
"Recipients",
|
|
"Recipients.User",
|
|
"Recipients.User.Profile",
|
|
"Recipients.Department",
|
|
"Attachments",
|
|
"Approvals",
|
|
"Approvals.Step",
|
|
"Approvals.Step.ApproverRole",
|
|
"Approvals.Step.ApproverUser",
|
|
"Approvals.Step.ApproverUser.Profile",
|
|
"Approvals.Approver",
|
|
"Approvals.Approver.Profile",
|
|
"Discussions",
|
|
"Discussions.User",
|
|
"Discussions.User.Profile",
|
|
"Discussions.Attachments",
|
|
"ActivityLogs",
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return letter, nil
|
|
}
|
|
|
|
// GetUsersByIDs fetches users by their IDs
|
|
func (p *LetterOutgoingProcessorImpl) GetUsersByIDs(ctx context.Context, userIDs []uuid.UUID) ([]entities.User, error) {
|
|
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
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) SearchOutgoingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error) {
|
|
offset := (page - 1) * limit
|
|
return p.letterRepo.Search(ctx, filters, limit, offset, sortBy, sortOrder)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) BulkArchiveOutgoingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
|
|
return p.letterRepo.BulkArchive(ctx, letterIDs)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) BulkArchiveIncomingLettersForUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (int64, error) {
|
|
return p.letterRepo.BulkArchiveForUser(ctx, letterIDs, userID)
|
|
}
|
|
|
|
// GetBatchAttachments fetches attachments for multiple letters in a single query
|
|
func (p *LetterOutgoingProcessorImpl) GetBatchAttachments(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingAttachment, error) {
|
|
if p.attachmentRepo == nil || len(letterIDs) == 0 {
|
|
return make(map[uuid.UUID][]entities.LetterOutgoingAttachment), nil
|
|
}
|
|
return p.attachmentRepo.ListByLetterIDs(ctx, letterIDs)
|
|
}
|
|
|
|
// GetBatchRecipients fetches recipients for multiple letters in a single query
|
|
func (p *LetterOutgoingProcessorImpl) GetBatchRecipients(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingRecipient, error) {
|
|
if p.recipientRepo == nil || len(letterIDs) == 0 {
|
|
return make(map[uuid.UUID][]entities.LetterOutgoingRecipient), nil
|
|
}
|
|
return p.recipientRepo.ListByLetterIDs(ctx, letterIDs)
|
|
}
|
|
|
|
func (p *LetterOutgoingProcessorImpl) GetBatchOutgoingRecipientsByUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterOutgoingRecipient, error) {
|
|
if p.recipientRepo == nil || len(letterIDs) == 0 {
|
|
return make(map[uuid.UUID]*entities.LetterOutgoingRecipient), nil
|
|
}
|
|
return p.recipientRepo.GetByLetterIDsAndUser(ctx, letterIDs, userID)
|
|
}
|
|
|
|
// GetBatchPriorities fetches priorities by IDs in a single query
|
|
func (p *LetterOutgoingProcessorImpl) GetBatchPriorities(ctx context.Context, priorityIDs []uuid.UUID) (map[uuid.UUID]*entities.Priority, error) {
|
|
if p.priorityRepo == nil || len(priorityIDs) == 0 {
|
|
return make(map[uuid.UUID]*entities.Priority), nil
|
|
}
|
|
return p.priorityRepo.GetByIDs(ctx, priorityIDs)
|
|
}
|
|
|
|
// GetBatchInstitutions fetches institutions by IDs in a single query
|
|
func (p *LetterOutgoingProcessorImpl) GetBatchInstitutions(ctx context.Context, institutionIDs []uuid.UUID) (map[uuid.UUID]*entities.Institution, error) {
|
|
if p.institutionRepo == nil || len(institutionIDs) == 0 {
|
|
return make(map[uuid.UUID]*entities.Institution), nil
|
|
}
|
|
return p.institutionRepo.GetByIDs(ctx, institutionIDs)
|
|
}
|