package processor import ( "context" "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 // new repos for dispositions dispositionRepo *repository.LetterDispositionRepository dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository dispositionNoteRepo *repository.DispositionNoteRepository // discussion repo discussionRepo *repository.LetterDiscussionRepository } func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachRepo *repository.LetterIncomingAttachmentRepository, txManager *repository.TxManager, activity *ActivityLogProcessorImpl, dispRepo *repository.LetterDispositionRepository, dispSelRepo *repository.LetterDispositionActionSelectionRepository, noteRepo *repository.DispositionNoteRepository, discussionRepo *repository.LetterDiscussionRepository) *LetterProcessorImpl { return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager, activity: activity, dispositionRepo: dispRepo, dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo, discussionRepo: discussionRepo} } func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) { var result *contract.IncomingLetterResponse err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { userID := appcontext.FromGinContext(txCtx).UserID entity := &entities.LetterIncoming{ ReferenceNumber: req.ReferenceNumber, Subject: req.Subject, Description: req.Description, PriorityID: req.PriorityID, SenderInstitutionID: req.SenderInstitutionID, ReceivedDate: req.ReceivedDate, DueDate: req.DueDate, Status: entities.LetterIncomingStatusNew, CreatedBy: userID, } if err := p.letterRepo.Create(txCtx, entity); err != nil { return err } if p.activity != nil { action := "letter.created" if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{}); err != nil { return err } } attachments := make([]entities.LetterIncomingAttachment, 0, len(req.Attachments)) for _, a := range req.Attachments { attachments = append(attachments, entities.LetterIncomingAttachment{ LetterID: entity.ID, FileURL: a.FileURL, FileName: a.FileName, FileType: a.FileType, UploadedBy: &userID, }) } if len(attachments) > 0 { if err := p.attachRepo.CreateBulk(txCtx, attachments); err != nil { return err } if p.activity != nil { action := "attachment.uploaded" for _, a := range attachments { ctxMap := map[string]interface{}{"file_name": a.FileName, "file_type": a.FileType} if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, ctxMap); err != nil { return err } } } } savedAttachments, _ := p.attachRepo.ListByLetter(txCtx, entity.ID) result = transformer.LetterEntityToContract(entity, savedAttachments) return nil }) if err != nil { return nil, err } return result, nil } func (p *LetterProcessorImpl) GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error) { entity, err := p.letterRepo.Get(ctx, id) if err != nil { return nil, err } atts, _ := p.attachRepo.ListByLetter(ctx, id) return transformer.LetterEntityToContract(entity, atts), nil } func (p *LetterProcessorImpl) ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error) { page, limit := req.Page, req.Limit if page <= 0 { page = 1 } if limit <= 0 { limit = 10 } filter := repository.ListIncomingLettersFilter{Status: req.Status, Query: req.Query} list, total, err := p.letterRepo.List(ctx, filter, limit, (page-1)*limit) if err != nil { return nil, err } respList := make([]contract.IncomingLetterResponse, 0, len(list)) for _, e := range list { atts, _ := p.attachRepo.ListByLetter(ctx, e.ID) resp := transformer.LetterEntityToContract(&e, atts) respList = append(respList, *resp) } return &contract.ListIncomingLettersResponse{Letters: respList, Pagination: transformer.CreatePaginationResponse(int(total), page, limit)}, nil } 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.ReceivedDate != nil { entity.ReceivedDate = *req.ReceivedDate } if req.DueDate != nil { entity.DueDate = req.DueDate } 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) out = transformer.LetterEntityToContract(entity, atts) 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 }) } func (p *LetterProcessorImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) { var out *contract.ListDispositionsResponse err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { userID := appcontext.FromGinContext(txCtx).UserID created := make([]entities.LetterDisposition, 0, len(req.ToDepartmentIDs)) for _, toDept := range req.ToDepartmentIDs { disp := entities.LetterDisposition{ LetterID: req.LetterID, FromDepartmentID: nil, ToDepartmentID: &toDept, Notes: req.Notes, Status: entities.DispositionPending, CreatedBy: userID, } if err := p.dispositionRepo.Create(txCtx, &disp); err != nil { return err } created = append(created, disp) if len(req.SelectedActions) > 0 { selections := make([]entities.LetterDispositionActionSelection, 0, len(req.SelectedActions)) for _, sel := range req.SelectedActions { selections = append(selections, entities.LetterDispositionActionSelection{ DispositionID: disp.ID, ActionID: sel.ActionID, Note: sel.Note, CreatedBy: userID, }) } if err := p.dispositionActionSelRepo.CreateBulk(txCtx, selections); err != nil { return err } } if p.activity != nil { action := "disposition.created" for _, d := range created { ctxMap := map[string]interface{}{"to_department_id": d.ToDepartmentID} if err := p.activity.Log(txCtx, req.LetterID, action, &userID, nil, nil, &d.ID, nil, nil, ctxMap); err != nil { return err } } } } out = &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(created)} return nil }) if err != nil { return nil, err } return out, nil } 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) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) { var out *contract.LetterDiscussionResponse err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { userID := appcontext.FromGinContext(txCtx).UserID mentions := entities.JSONB(nil) if req.Mentions != nil { mentions = entities.JSONB(req.Mentions) } disc := &entities.LetterDiscussion{LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, Mentions: mentions} if err := p.discussionRepo.Create(txCtx, disc); err != nil { return err } if p.activity != nil { action := "discussion.created" tgt := "discussion" ctxMap := map[string]interface{}{"message": req.Message, "parent_id": req.ParentID} if err := p.activity.Log(txCtx, letterID, action, &userID, nil, &tgt, &disc.ID, nil, nil, ctxMap); err != nil { return err } } out = transformer.DiscussionEntityToContract(disc) return nil }) if err != nil { return nil, err } return out, nil } func (p *LetterProcessorImpl) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) { var out *contract.LetterDiscussionResponse err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { disc, err := p.discussionRepo.Get(txCtx, discussionID) if err != nil { return err } oldMessage := disc.Message 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(txCtx, disc); err != nil { return err } if p.activity != nil { userID := appcontext.FromGinContext(txCtx).UserID action := "discussion.updated" tgt := "discussion" ctxMap := map[string]interface{}{"old_message": oldMessage, "new_message": req.Message} if err := p.activity.Log(txCtx, letterID, action, &userID, nil, &tgt, &disc.ID, nil, nil, ctxMap); err != nil { return err } } out = transformer.DiscussionEntityToContract(disc) return nil }) if err != nil { return nil, err } return out, nil }