378 lines
14 KiB
Go
378 lines
14 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
|
|
// new repos for dispositions
|
|
dispositionRepo *repository.LetterDispositionRepository
|
|
dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository
|
|
dispositionNoteRepo *repository.DispositionNoteRepository
|
|
// discussion repo
|
|
discussionRepo *repository.LetterDiscussionRepository
|
|
// settings and recipients
|
|
settingRepo *repository.AppSettingRepository
|
|
recipientRepo *repository.LetterIncomingRecipientRepository
|
|
departmentRepo *repository.DepartmentRepository
|
|
userDeptRepo *repository.UserDepartmentRepository
|
|
}
|
|
|
|
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, settingRepo *repository.AppSettingRepository, recipientRepo *repository.LetterIncomingRecipientRepository, departmentRepo *repository.DepartmentRepository, userDeptRepo *repository.UserDepartmentRepository) *LetterProcessorImpl {
|
|
return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager, activity: activity, dispositionRepo: dispRepo, dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo, discussionRepo: discussionRepo, settingRepo: settingRepo, recipientRepo: recipientRepo, departmentRepo: departmentRepo, userDeptRepo: userDeptRepo}
|
|
}
|
|
|
|
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
|
|
|
|
prefix := "ESLI"
|
|
seq := 0
|
|
if s, err := p.settingRepo.Get(txCtx, contract.SettingIncomingLetterPrefix); err == nil {
|
|
if v, ok := s.Value["value"].(string); ok && v != "" {
|
|
prefix = v
|
|
}
|
|
}
|
|
if s, err := p.settingRepo.Get(txCtx, contract.SettingIncomingLetterSequence); err == nil {
|
|
if v, ok := s.Value["value"].(float64); ok {
|
|
seq = int(v)
|
|
}
|
|
}
|
|
seq = seq + 1
|
|
letterNumber := fmt.Sprintf("%s%04d", prefix, seq)
|
|
|
|
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,
|
|
}
|
|
entity.LetterNumber = letterNumber
|
|
if err := p.letterRepo.Create(txCtx, entity); err != nil {
|
|
return err
|
|
}
|
|
|
|
_ = p.settingRepo.Upsert(txCtx, contract.SettingIncomingLetterSequence, entities.JSONB{"value": seq})
|
|
|
|
defaultDeptCodes := []string{}
|
|
if s, err := p.settingRepo.Get(txCtx, contract.SettingIncomingLetterRecipients); err == nil {
|
|
if arr, ok := s.Value["department_codes"].([]interface{}); ok {
|
|
for _, it := range arr {
|
|
if str, ok := it.(string); ok {
|
|
defaultDeptCodes = append(defaultDeptCodes, str)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// resolve department codes to ids using repository
|
|
depIDs := make([]uuid.UUID, 0, len(defaultDeptCodes))
|
|
for _, code := range defaultDeptCodes {
|
|
dep, err := p.departmentRepo.GetByCode(txCtx, code)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
depIDs = append(depIDs, dep.ID)
|
|
}
|
|
// query user memberships for all departments at once
|
|
userMemberships, _ := p.userDeptRepo.ListActiveByDepartmentIDs(txCtx, depIDs)
|
|
// build recipients: one department recipient per department + one user recipient per membership
|
|
recipients := make([]entities.LetterIncomingRecipient, 0, len(depIDs)+len(userMemberships))
|
|
// department recipients
|
|
for _, depID := range depIDs {
|
|
id := depID
|
|
recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientDepartmentID: &id, Status: entities.RecipientStatusNew})
|
|
}
|
|
// user recipients
|
|
for _, row := range userMemberships {
|
|
uid := row.UserID
|
|
recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientUserID: &uid, Status: entities.RecipientStatusNew})
|
|
}
|
|
if len(recipients) > 0 {
|
|
if err := p.recipientRepo.CreateBulk(txCtx, recipients); 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{}{"letter_number": letterNumber}); 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 := "reference_numberdiscussion.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
|
|
}
|