630 lines
23 KiB
Go
630 lines
23 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"eslogad-be/internal/appcontext"
|
|
"eslogad-be/internal/contract"
|
|
"eslogad-be/internal/entities"
|
|
"eslogad-be/internal/repository"
|
|
"eslogad-be/internal/transformer"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type LetterProcessorImpl struct {
|
|
letterRepo *repository.LetterIncomingRepository
|
|
attachRepo *repository.LetterIncomingAttachmentRepository
|
|
txManager *repository.TxManager
|
|
activity *ActivityLogProcessorImpl
|
|
dispositionRepo *repository.LetterIncomingDispositionRepository
|
|
dispositionDeptRepo *repository.LetterIncomingDispositionDepartmentRepository
|
|
dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository
|
|
dispositionNoteRepo *repository.DispositionNoteRepository
|
|
discussionRepo *repository.LetterDiscussionRepository
|
|
settingRepo *repository.AppSettingRepository
|
|
recipientRepo *repository.LetterIncomingRecipientRepository
|
|
outgoingRecipientRepo *repository.LetterOutgoingRecipientRepository
|
|
departmentRepo *repository.DepartmentRepository
|
|
userDeptRepo *repository.UserDepartmentRepository
|
|
priorityRepo *repository.PriorityRepository
|
|
institutionRepo *repository.InstitutionRepository
|
|
dispActionRepo *repository.DispositionActionRepository
|
|
dispoRoutes *repository.DispositionRouteRepository
|
|
numberGenerator *LetterNumberGeneratorImpl
|
|
}
|
|
|
|
func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachRepo *repository.LetterIncomingAttachmentRepository, txManager *repository.TxManager, activity *ActivityLogProcessorImpl, dispRepo *repository.LetterIncomingDispositionRepository, dispDeptRepo *repository.LetterIncomingDispositionDepartmentRepository, dispSelRepo *repository.LetterDispositionActionSelectionRepository, noteRepo *repository.DispositionNoteRepository, discussionRepo *repository.LetterDiscussionRepository,
|
|
settingRepo *repository.AppSettingRepository,
|
|
recipientRepo *repository.LetterIncomingRecipientRepository,
|
|
outgoingRecipientRepo *repository.LetterOutgoingRecipientRepository,
|
|
departmentRepo *repository.DepartmentRepository,
|
|
userDeptRepo *repository.UserDepartmentRepository,
|
|
priorityRepo *repository.PriorityRepository,
|
|
institutionRepo *repository.InstitutionRepository,
|
|
dispActionRepo *repository.DispositionActionRepository,
|
|
numberGenerator *LetterNumberGeneratorImpl,
|
|
dispoRoutes *repository.DispositionRouteRepository) *LetterProcessorImpl {
|
|
return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager,
|
|
activity: activity, dispositionRepo: dispRepo, dispositionDeptRepo: dispDeptRepo,
|
|
dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo,
|
|
discussionRepo: discussionRepo, settingRepo: settingRepo, recipientRepo: recipientRepo,
|
|
outgoingRecipientRepo: outgoingRecipientRepo,
|
|
departmentRepo: departmentRepo, userDeptRepo: userDeptRepo, priorityRepo: priorityRepo,
|
|
institutionRepo: institutionRepo, dispActionRepo: dispActionRepo, numberGenerator: numberGenerator,
|
|
dispoRoutes: dispoRoutes}
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
|
userID := appcontext.FromGinContext(ctx).UserID
|
|
|
|
letterType := entities.LetterIncomingTypeUtama
|
|
if req.Type == "TEMBUSAN" {
|
|
letterType = entities.LetterIncomingTypeTembusan
|
|
}
|
|
|
|
entity := &entities.LetterIncoming{
|
|
LetterNumber: req.LetterNumber,
|
|
ReferenceNumber: req.ReferenceNumber,
|
|
Subject: req.Subject,
|
|
Description: req.Description,
|
|
PriorityID: req.PriorityID,
|
|
SenderInstitutionID: req.SenderInstitutionID,
|
|
SenderName: req.SenderName,
|
|
Addressee: req.Addressee,
|
|
ReceivedDate: req.ReceivedDate,
|
|
DueDate: req.DueDate,
|
|
Type: letterType,
|
|
Status: entities.LetterIncomingStatusNew,
|
|
CreatedBy: userID,
|
|
}
|
|
|
|
if err := p.letterRepo.Create(ctx, entity); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := p.createAttachments(ctx, entity.ID, req.Attachments, userID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return p.buildLetterResponse(ctx, entity)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error) {
|
|
// Get current user ID from context
|
|
userID := appcontext.FromGinContext(ctx).UserID
|
|
|
|
entity, err := p.letterRepo.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
atts, _ := p.attachRepo.ListByLetter(ctx, id)
|
|
var pr *entities.Priority
|
|
if entity.PriorityID != nil && p.priorityRepo != nil {
|
|
if got, err := p.priorityRepo.Get(ctx, *entity.PriorityID); err == nil {
|
|
pr = got
|
|
}
|
|
}
|
|
var inst *entities.Institution
|
|
if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
|
|
if got, err := p.institutionRepo.Get(ctx, *entity.SenderInstitutionID); err == nil {
|
|
inst = got
|
|
}
|
|
}
|
|
|
|
// Check if letter is read by current user
|
|
isRead := false
|
|
if p.recipientRepo != nil {
|
|
if recipient, err := p.recipientRepo.GetByLetterAndUser(ctx, id, userID); err == nil {
|
|
isRead = recipient.ReadAt != nil
|
|
}
|
|
}
|
|
|
|
resp := transformer.LetterEntityToContract(entity, atts, pr, inst)
|
|
resp.IsRead = isRead
|
|
|
|
// Include created_by if the current user is the creator
|
|
if entity.CreatedBy == userID {
|
|
resp.CreatedBy = entity.CreatedBy
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) GetLetterUnreadCounts(ctx context.Context) (*contract.LetterUnreadCountResponse, error) {
|
|
userID := appcontext.FromGinContext(ctx).UserID
|
|
|
|
incomingUnread := 0
|
|
if p.recipientRepo != nil {
|
|
if count, err := p.recipientRepo.CountUnreadByUser(ctx, userID); err == nil {
|
|
incomingUnread = count
|
|
}
|
|
}
|
|
|
|
outgoingUnread := 0
|
|
if p.outgoingRecipientRepo != nil {
|
|
if count, err := p.outgoingRecipientRepo.CountUnreadByUser(ctx, userID); err == nil {
|
|
outgoingUnread = count
|
|
}
|
|
}
|
|
|
|
response := &contract.LetterUnreadCountResponse{}
|
|
response.IncomingLetter.Unread = incomingUnread
|
|
response.OutgoingLetter.Unread = outgoingUnread
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) MarkIncomingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error) {
|
|
// Get current user ID from context
|
|
userID := appcontext.FromGinContext(ctx).UserID
|
|
|
|
// Mark the letter as read for the current user
|
|
if p.recipientRepo != nil {
|
|
if err := p.recipientRepo.MarkAsRead(ctx, letterID, userID); err != nil {
|
|
return &contract.MarkLetterReadResponse{
|
|
Success: false,
|
|
Message: "Failed to mark letter as read",
|
|
}, err
|
|
}
|
|
}
|
|
|
|
return &contract.MarkLetterReadResponse{
|
|
Success: true,
|
|
Message: "Letter marked as read successfully",
|
|
}, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error) {
|
|
// Get current user ID from context
|
|
userID := appcontext.FromGinContext(ctx).UserID
|
|
|
|
// Mark the letter as read for the current user
|
|
if p.outgoingRecipientRepo != nil {
|
|
if err := p.outgoingRecipientRepo.MarkAsRead(ctx, letterID, userID); err != nil {
|
|
return &contract.MarkLetterReadResponse{
|
|
Success: false,
|
|
Message: "Failed to mark letter as read",
|
|
}, err
|
|
}
|
|
}
|
|
|
|
return &contract.MarkLetterReadResponse{
|
|
Success: true,
|
|
Message: "Letter marked as read successfully",
|
|
}, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) ListIncomingLetters(ctx context.Context, filter repository.ListIncomingLettersFilter, page, limit int) ([]entities.LetterIncoming, int64, error) {
|
|
// Just fetch the raw data
|
|
return p.letterRepo.List(ctx, filter, limit, (page-1)*limit)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) GetBatchAttachments(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterIncomingAttachment, error) {
|
|
if p.attachRepo == nil || len(letterIDs) == 0 {
|
|
return make(map[uuid.UUID][]entities.LetterIncomingAttachment), nil
|
|
}
|
|
return p.attachRepo.ListByLetterIDs(ctx, letterIDs)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) 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)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) 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)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) GetBatchRecipientsByUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterIncomingRecipient, error) {
|
|
if p.recipientRepo == nil || len(letterIDs) == 0 {
|
|
return make(map[uuid.UUID]*entities.LetterIncomingRecipient), nil
|
|
}
|
|
return p.recipientRepo.GetByLetterIDsAndUser(ctx, letterIDs, userID)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) CountUnreadByUser(ctx context.Context, userID uuid.UUID) (int, error) {
|
|
if p.recipientRepo == nil {
|
|
return 0, nil
|
|
}
|
|
return p.recipientRepo.CountUnreadByUser(ctx, userID)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
|
var out *contract.IncomingLetterResponse
|
|
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
entity, err := p.letterRepo.Get(txCtx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fromStatus := string(entity.Status)
|
|
if req.ReferenceNumber != nil {
|
|
entity.ReferenceNumber = req.ReferenceNumber
|
|
}
|
|
if req.Subject != nil {
|
|
entity.Subject = *req.Subject
|
|
}
|
|
if req.Description != nil {
|
|
entity.Description = req.Description
|
|
}
|
|
if req.PriorityID != nil {
|
|
entity.PriorityID = req.PriorityID
|
|
}
|
|
if req.SenderInstitutionID != nil {
|
|
entity.SenderInstitutionID = req.SenderInstitutionID
|
|
}
|
|
if req.SenderName != nil {
|
|
entity.SenderName = req.SenderName
|
|
}
|
|
if req.Addressee != nil {
|
|
entity.Addressee = req.Addressee
|
|
}
|
|
if req.ReceivedDate != nil {
|
|
entity.ReceivedDate = *req.ReceivedDate
|
|
}
|
|
if req.DueDate != nil {
|
|
entity.DueDate = req.DueDate
|
|
}
|
|
if req.Type != nil {
|
|
entity.Type = entities.LetterIncomingType(*req.Type)
|
|
}
|
|
if req.Status != nil {
|
|
entity.Status = entities.LetterIncomingStatus(*req.Status)
|
|
}
|
|
if err := p.letterRepo.Update(txCtx, entity); err != nil {
|
|
return err
|
|
}
|
|
toStatus := string(entity.Status)
|
|
if p.activity != nil && fromStatus != toStatus {
|
|
userID := appcontext.FromGinContext(txCtx).UserID
|
|
action := "status.changed"
|
|
if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, &fromStatus, &toStatus, map[string]interface{}{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
atts, _ := p.attachRepo.ListByLetter(txCtx, id)
|
|
var pr *entities.Priority
|
|
if entity.PriorityID != nil && p.priorityRepo != nil {
|
|
if got, err := p.priorityRepo.Get(txCtx, *entity.PriorityID); err == nil {
|
|
pr = got
|
|
}
|
|
}
|
|
var inst *entities.Institution
|
|
if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
|
|
if got, err := p.institutionRepo.Get(txCtx, *entity.SenderInstitutionID); err == nil {
|
|
inst = got
|
|
}
|
|
}
|
|
out = transformer.LetterEntityToContract(entity, atts, pr, inst)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error {
|
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
if err := p.letterRepo.SoftDelete(txCtx, id); err != nil {
|
|
return err
|
|
}
|
|
if p.activity != nil {
|
|
userID := appcontext.FromGinContext(txCtx).UserID
|
|
action := "letter.deleted"
|
|
if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// CreateDispositions creates a new disposition with modular helper functions
|
|
func (p *LetterProcessorImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) {
|
|
// Transaction should be handled at service layer
|
|
// The context passed here should already contain the transaction if needed
|
|
|
|
// Step 1: Update existing disposition departments
|
|
if err := p.updateExistingDispositionDepartments(ctx, req.LetterID, req.FromDepartment); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Step 2: Create the main disposition
|
|
disp, err := p.createMainDisposition(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Step 3: Create disposition departments for target departments
|
|
dispDepartments, err := p.createDispositionDepartments(ctx, disp.ID, req.LetterID, req.ToDepartmentIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Step 4: Create action selections if provided
|
|
if err := p.createActionSelections(ctx, disp.ID, req.SelectedActions, req.CreatedBy); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Step 5: Build and return the response
|
|
return p.buildDispositionResponse(disp, dispDepartments, req.ToDepartmentIDs), nil
|
|
}
|
|
|
|
// updateExistingDispositionDepartments updates the status of existing disposition departments
|
|
func (p *LetterProcessorImpl) updateExistingDispositionDepartments(ctx context.Context, letterID uuid.UUID, fromDepartment uuid.UUID) error {
|
|
existingDispDepts, err := p.dispositionDeptRepo.GetByLetterAndDepartment(ctx, letterID, fromDepartment)
|
|
if err != nil {
|
|
// If no existing departments found, that's ok
|
|
return nil
|
|
}
|
|
|
|
for _, existingDispDept := range existingDispDepts {
|
|
if existingDispDept.Status == entities.DispositionDepartmentStatusPending {
|
|
existingDispDept.Status = entities.DispositionDepartmentStatusDispositioned
|
|
if err := p.dispositionDeptRepo.Update(ctx, &existingDispDept); err != nil {
|
|
return fmt.Errorf("failed to update existing disposition department: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createMainDisposition creates the primary disposition record
|
|
func (p *LetterProcessorImpl) createMainDisposition(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*entities.LetterIncomingDisposition, error) {
|
|
disp := &entities.LetterIncomingDisposition{
|
|
LetterID: req.LetterID,
|
|
DepartmentID: &req.FromDepartment,
|
|
Notes: req.Notes,
|
|
CreatedBy: req.CreatedBy, // Should be set by service layer
|
|
}
|
|
|
|
if err := p.dispositionRepo.Create(ctx, disp); err != nil {
|
|
return nil, fmt.Errorf("failed to create disposition: %w", err)
|
|
}
|
|
|
|
return disp, nil
|
|
}
|
|
|
|
// createDispositionDepartments creates disposition department records for target departments
|
|
func (p *LetterProcessorImpl) createDispositionDepartments(ctx context.Context, dispositionID, letterID uuid.UUID, toDepartmentIDs []uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
|
|
if len(toDepartmentIDs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
dispDepartments := make([]entities.LetterIncomingDispositionDepartment, 0, len(toDepartmentIDs))
|
|
for _, toDept := range toDepartmentIDs {
|
|
dispDepartments = append(dispDepartments, entities.LetterIncomingDispositionDepartment{
|
|
LetterIncomingDispositionID: dispositionID,
|
|
LetterIncomingID: letterID,
|
|
DepartmentID: toDept,
|
|
Status: entities.DispositionDepartmentStatusPending,
|
|
})
|
|
}
|
|
|
|
if err := p.dispositionDeptRepo.CreateBulk(ctx, dispDepartments); err != nil {
|
|
return nil, fmt.Errorf("failed to create disposition departments: %w", err)
|
|
}
|
|
|
|
return dispDepartments, nil
|
|
}
|
|
|
|
// createActionSelections creates action selection records for the disposition
|
|
func (p *LetterProcessorImpl) createActionSelections(ctx context.Context, dispositionID uuid.UUID, selectedActions []contract.CreateDispositionActionSelection, createdBy uuid.UUID) error {
|
|
if len(selectedActions) == 0 {
|
|
return nil
|
|
}
|
|
|
|
selections := make([]entities.LetterDispositionActionSelection, 0, len(selectedActions))
|
|
for _, sel := range selectedActions {
|
|
selections = append(selections, entities.LetterDispositionActionSelection{
|
|
DispositionID: dispositionID,
|
|
ActionID: sel.ActionID,
|
|
Note: sel.Note,
|
|
CreatedBy: createdBy,
|
|
})
|
|
}
|
|
|
|
if err := p.dispositionActionSelRepo.CreateBulk(ctx, selections); err != nil {
|
|
return fmt.Errorf("failed to create action selections: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// buildDispositionResponse builds the response for the created disposition
|
|
func (p *LetterProcessorImpl) buildDispositionResponse(disp *entities.LetterIncomingDisposition, dispDepartments []entities.LetterIncomingDispositionDepartment, toDepartmentIDs []uuid.UUID) *contract.ListDispositionsResponse {
|
|
response := &contract.ListDispositionsResponse{
|
|
Dispositions: []contract.DispositionResponse{transformer.DispoToContract(*disp)},
|
|
}
|
|
|
|
// The toDepartmentIDs are available in the dispDepartments for service layer logging
|
|
// No need to store them in the response as DispositionResponse doesn't have this field
|
|
|
|
return response
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error) {
|
|
list, err := p.dispositionRepo.ListByLetter(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(list)}, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) GetEnhancedDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListEnhancedDispositionsResponse, error) {
|
|
// Get dispositions with all related data preloaded in a single query
|
|
dispositions, err := p.dispositionRepo.ListByLetter(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get discussions with preloaded user profiles
|
|
discussions, err := p.discussionRepo.ListByLetter(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Extract all mentioned user IDs from discussions for efficient batch fetching
|
|
var mentionedUserIDs []uuid.UUID
|
|
mentionedUserIDsMap := make(map[uuid.UUID]bool)
|
|
|
|
for _, discussion := range discussions {
|
|
if discussion.Mentions != nil {
|
|
mentions := map[string]interface{}(discussion.Mentions)
|
|
if userIDs, ok := mentions["user_ids"]; ok {
|
|
if userIDList, ok := userIDs.([]interface{}); ok {
|
|
for _, userID := range userIDList {
|
|
if userIDStr, ok := userID.(string); ok {
|
|
if userUUID, err := uuid.Parse(userIDStr); err == nil {
|
|
if !mentionedUserIDsMap[userUUID] {
|
|
mentionedUserIDsMap[userUUID] = true
|
|
mentionedUserIDs = append(mentionedUserIDs, userUUID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fetch all mentioned users in a single batch query
|
|
var mentionedUsers []entities.User
|
|
if len(mentionedUserIDs) > 0 {
|
|
mentionedUsers, err = p.discussionRepo.GetUsersByIDs(ctx, mentionedUserIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Transform dispositions
|
|
enhancedDispositions := transformer.EnhancedDispositionsWithPreloadedDataToContract(dispositions)
|
|
|
|
// Transform discussions with mentioned users
|
|
enhancedDiscussions := transformer.DiscussionsWithPreloadedDataToContract(discussions, mentionedUsers)
|
|
|
|
return &contract.ListEnhancedDispositionsResponse{
|
|
Dispositions: enhancedDispositions,
|
|
Discussions: enhancedDiscussions,
|
|
}, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
|
userID := appcontext.FromGinContext(ctx).UserID
|
|
|
|
mentions := entities.JSONB(nil)
|
|
if req.Mentions != nil {
|
|
mentions = entities.JSONB(req.Mentions)
|
|
}
|
|
|
|
disc := &entities.LetterDiscussion{
|
|
ID: uuid.New(),
|
|
LetterID: letterID,
|
|
ParentID: req.ParentID,
|
|
UserID: userID,
|
|
Message: req.Message,
|
|
Mentions: mentions,
|
|
}
|
|
|
|
if err := p.discussionRepo.Create(ctx, disc); err != nil {
|
|
return nil, fmt.Errorf("failed to create discussion: %w", err)
|
|
}
|
|
|
|
// Activity logging should be handled at service layer
|
|
return transformer.DiscussionEntityToContract(disc), nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, string, error) {
|
|
// Transaction should be handled at service layer
|
|
disc, err := p.discussionRepo.Get(ctx, discussionID)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to get discussion: %w", err)
|
|
}
|
|
|
|
// Store old message for activity logging
|
|
oldMessage := disc.Message
|
|
|
|
// Update discussion fields
|
|
disc.Message = req.Message
|
|
if req.Mentions != nil {
|
|
disc.Mentions = entities.JSONB(req.Mentions)
|
|
}
|
|
now := time.Now()
|
|
disc.EditedAt = &now
|
|
|
|
if err := p.discussionRepo.Update(ctx, disc); err != nil {
|
|
return nil, "", fmt.Errorf("failed to update discussion: %w", err)
|
|
}
|
|
|
|
// Return both the updated discussion and old message for service layer logging
|
|
return transformer.DiscussionEntityToContract(disc), oldMessage, nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) createAttachments(ctx context.Context, letterID uuid.UUID, attachments []contract.CreateIncomingLetterAttachment, userID uuid.UUID) error {
|
|
if len(attachments) == 0 {
|
|
return nil
|
|
}
|
|
|
|
attachmentEntities := make([]entities.LetterIncomingAttachment, 0, len(attachments))
|
|
for _, a := range attachments {
|
|
attachmentEntities = append(attachmentEntities, entities.LetterIncomingAttachment{
|
|
LetterID: letterID,
|
|
FileURL: a.FileURL,
|
|
FileName: a.FileName,
|
|
FileType: a.FileType,
|
|
UploadedBy: &userID,
|
|
})
|
|
}
|
|
|
|
if err := p.attachRepo.CreateBulk(ctx, attachmentEntities); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Attachment logging will be handled by service layer
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) SearchIncomingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterIncoming, int64, error) {
|
|
offset := (page - 1) * limit
|
|
return p.letterRepo.Search(ctx, filters, limit, offset, sortBy, sortOrder)
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) buildLetterResponse(ctx context.Context, entity *entities.LetterIncoming) (*contract.IncomingLetterResponse, error) {
|
|
savedAttachments, _ := p.attachRepo.ListByLetter(ctx, entity.ID)
|
|
|
|
var pr *entities.Priority
|
|
if entity.PriorityID != nil && p.priorityRepo != nil {
|
|
if got, err := p.priorityRepo.Get(ctx, *entity.PriorityID); err == nil {
|
|
pr = got
|
|
}
|
|
}
|
|
|
|
var inst *entities.Institution
|
|
if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
|
|
if got, err := p.institutionRepo.Get(ctx, *entity.SenderInstitutionID); err == nil {
|
|
inst = got
|
|
}
|
|
}
|
|
|
|
return transformer.LetterEntityToContract(entity, savedAttachments, pr, inst), nil
|
|
}
|
|
|
|
func (p *LetterProcessorImpl) BulkArchiveIncomingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
|
|
return p.letterRepo.BulkArchive(ctx, letterIDs)
|
|
}
|
|
|
|
// 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)
|
|
}
|