2025-08-09 18:58:22 +07:00
|
|
|
package processor
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-08-09 23:44:03 +07:00
|
|
|
"fmt"
|
2025-08-09 18:58:22 +07:00
|
|
|
"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 {
|
2025-08-15 21:17:19 +07:00
|
|
|
letterRepo *repository.LetterIncomingRepository
|
|
|
|
|
attachRepo *repository.LetterIncomingAttachmentRepository
|
|
|
|
|
txManager *repository.TxManager
|
|
|
|
|
activity *ActivityLogProcessorImpl
|
|
|
|
|
dispositionRepo *repository.LetterIncomingDispositionRepository
|
|
|
|
|
dispositionDeptRepo *repository.LetterIncomingDispositionDepartmentRepository
|
2025-08-09 18:58:22 +07:00
|
|
|
dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository
|
|
|
|
|
dispositionNoteRepo *repository.DispositionNoteRepository
|
2025-08-15 21:17:19 +07:00
|
|
|
discussionRepo *repository.LetterDiscussionRepository
|
|
|
|
|
settingRepo *repository.AppSettingRepository
|
|
|
|
|
recipientRepo *repository.LetterIncomingRecipientRepository
|
|
|
|
|
departmentRepo *repository.DepartmentRepository
|
|
|
|
|
userDeptRepo *repository.UserDepartmentRepository
|
|
|
|
|
priorityRepo *repository.PriorityRepository
|
|
|
|
|
institutionRepo *repository.InstitutionRepository
|
|
|
|
|
dispActionRepo *repository.DispositionActionRepository
|
2025-08-09 18:58:22 +07:00
|
|
|
}
|
|
|
|
|
|
2025-08-15 21:17:19 +07:00
|
|
|
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, departmentRepo *repository.DepartmentRepository, userDeptRepo *repository.UserDepartmentRepository, priorityRepo *repository.PriorityRepository, institutionRepo *repository.InstitutionRepository, dispActionRepo *repository.DispositionActionRepository) *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, departmentRepo: departmentRepo, userDeptRepo: userDeptRepo, priorityRepo: priorityRepo, institutionRepo: institutionRepo, dispActionRepo: dispActionRepo}
|
2025-08-09 18:58:22 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2025-08-09 23:44:03 +07:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2025-08-09 18:58:22 +07:00
|
|
|
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,
|
|
|
|
|
}
|
2025-08-09 23:44:03 +07:00
|
|
|
entity.LetterNumber = letterNumber
|
2025-08-09 18:58:22 +07:00
|
|
|
if err := p.letterRepo.Create(txCtx, entity); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 23:44:03 +07:00
|
|
|
_ = 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
2025-08-15 21:17:19 +07:00
|
|
|
|
2025-08-09 23:44:03 +07:00
|
|
|
userMemberships, _ := p.userDeptRepo.ListActiveByDepartmentIDs(txCtx, depIDs)
|
2025-08-15 21:17:19 +07:00
|
|
|
var recipients []entities.LetterIncomingRecipient
|
|
|
|
|
|
|
|
|
|
mapsUsers := map[string]bool{}
|
2025-08-09 23:44:03 +07:00
|
|
|
for _, row := range userMemberships {
|
|
|
|
|
uid := row.UserID
|
2025-08-15 21:17:19 +07:00
|
|
|
if _, ok := mapsUsers[uid.String()]; !ok {
|
|
|
|
|
recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientUserID: &uid, RecipientDepartmentID: &row.DepartmentID, Status: entities.RecipientStatusNew})
|
|
|
|
|
}
|
|
|
|
|
mapsUsers[uid.String()] = true
|
2025-08-09 23:44:03 +07:00
|
|
|
}
|
2025-08-15 21:17:19 +07:00
|
|
|
|
2025-08-09 23:44:03 +07:00
|
|
|
if len(recipients) > 0 {
|
|
|
|
|
if err := p.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 18:58:22 +07:00
|
|
|
if p.activity != nil {
|
|
|
|
|
action := "letter.created"
|
2025-08-09 23:44:03 +07:00
|
|
|
if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{"letter_number": letterNumber}); err != nil {
|
2025-08-09 18:58:22 +07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attachments := make([]entities.LetterIncomingAttachment, 0, len(req.Attachments))
|
|
|
|
|
for _, a := range req.Attachments {
|
2025-08-09 23:44:03 +07:00
|
|
|
attachments = append(attachments, entities.LetterIncomingAttachment{LetterID: entity.ID, FileURL: a.FileURL, FileName: a.FileName, FileType: a.FileType, UploadedBy: &userID})
|
2025-08-09 18:58:22 +07:00
|
|
|
}
|
|
|
|
|
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)
|
2025-08-15 21:17:19 +07:00
|
|
|
var pr *entities.Priority
|
|
|
|
|
if entity.PriorityID != nil {
|
|
|
|
|
if p.priorityRepo != nil {
|
|
|
|
|
if got, err := p.priorityRepo.Get(txCtx, *entity.PriorityID); err == nil {
|
|
|
|
|
pr = got
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var inst *entities.Institution
|
|
|
|
|
if entity.SenderInstitutionID != nil {
|
|
|
|
|
if p.institutionRepo != nil {
|
|
|
|
|
if got, err := p.institutionRepo.Get(txCtx, *entity.SenderInstitutionID); err == nil {
|
|
|
|
|
inst = got
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result = transformer.LetterEntityToContract(entity, savedAttachments, pr, inst)
|
2025-08-09 18:58:22 +07:00
|
|
|
return nil
|
|
|
|
|
})
|
2025-08-15 21:17:19 +07:00
|
|
|
|
2025-08-09 18:58:22 +07:00
|
|
|
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)
|
2025-08-15 21:17:19 +07:00
|
|
|
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, atts, pr, inst), nil
|
2025-08-09 18:58:22 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2025-08-15 21:17:19 +07:00
|
|
|
var pr *entities.Priority
|
|
|
|
|
if e.PriorityID != nil && p.priorityRepo != nil {
|
|
|
|
|
if got, err := p.priorityRepo.Get(ctx, *e.PriorityID); err == nil {
|
|
|
|
|
pr = got
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var inst *entities.Institution
|
|
|
|
|
if e.SenderInstitutionID != nil && p.institutionRepo != nil {
|
|
|
|
|
if got, err := p.institutionRepo.Get(ctx, *e.SenderInstitutionID); err == nil {
|
|
|
|
|
inst = got
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
resp := transformer.LetterEntityToContract(&e, atts, pr, inst)
|
2025-08-09 18:58:22 +07:00
|
|
|
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)
|
2025-08-15 21:17:19 +07:00
|
|
|
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)
|
2025-08-09 18:58:22 +07:00
|
|
|
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
|
2025-08-15 21:17:19 +07:00
|
|
|
|
|
|
|
|
disp := entities.LetterIncomingDisposition{
|
|
|
|
|
LetterID: req.LetterID,
|
|
|
|
|
DepartmentID: &req.FromDepartment,
|
|
|
|
|
Notes: req.Notes,
|
|
|
|
|
CreatedBy: userID,
|
|
|
|
|
}
|
|
|
|
|
if err := p.dispositionRepo.Create(txCtx, &disp); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dispDepartments []entities.LetterIncomingDispositionDepartment
|
2025-08-09 18:58:22 +07:00
|
|
|
for _, toDept := range req.ToDepartmentIDs {
|
2025-08-15 21:17:19 +07:00
|
|
|
dispDepartments = append(dispDepartments, entities.LetterIncomingDispositionDepartment{
|
|
|
|
|
LetterIncomingDispositionID: disp.ID,
|
|
|
|
|
DepartmentID: toDept,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := p.dispositionDeptRepo.CreateBulk(txCtx, dispDepartments); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
})
|
2025-08-09 18:58:22 +07:00
|
|
|
}
|
2025-08-15 21:17:19 +07:00
|
|
|
if err := p.dispositionActionSelRepo.CreateBulk(txCtx, selections); err != nil {
|
2025-08-09 18:58:22 +07:00
|
|
|
return err
|
|
|
|
|
}
|
2025-08-15 21:17:19 +07:00
|
|
|
}
|
2025-08-09 18:58:22 +07:00
|
|
|
|
2025-08-15 21:17:19 +07:00
|
|
|
if p.activity != nil {
|
|
|
|
|
action := "disposition.created"
|
|
|
|
|
ctxMap := map[string]interface{}{"to_department_id": dispDepartments}
|
|
|
|
|
if err := p.activity.Log(txCtx, req.LetterID, action, &userID, nil, nil, &disp.ID, nil, nil, ctxMap); err != nil {
|
|
|
|
|
return err
|
2025-08-09 18:58:22 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-15 21:17:19 +07:00
|
|
|
out = &contract.ListDispositionsResponse{Dispositions: []contract.DispositionResponse{transformer.DispoToContract(disp)}}
|
2025-08-09 18:58:22 +07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-15 21:17:19 +07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 18:58:22 +07:00
|
|
|
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)
|
|
|
|
|
}
|
2025-08-15 21:17:19 +07:00
|
|
|
disc := &entities.LetterDiscussion{ID: uuid.New(), LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, Mentions: mentions}
|
2025-08-09 18:58:22 +07:00
|
|
|
if err := p.discussionRepo.Create(txCtx, disc); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if p.activity != nil {
|
2025-08-09 23:44:03 +07:00
|
|
|
action := "reference_numberdiscussion.created"
|
2025-08-09 18:58:22 +07:00
|
|
|
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
|
|
|
|
|
}
|