948 lines
28 KiB
Go
948 lines
28 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"eslogad-be/internal/appcontext"
|
|
"eslogad-be/internal/contract"
|
|
"eslogad-be/internal/entities"
|
|
"eslogad-be/internal/processor"
|
|
"eslogad-be/internal/repository"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type LetterOutgoingService interface {
|
|
CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
|
|
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error)
|
|
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
|
|
UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
|
|
DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error
|
|
|
|
SubmitForApproval(ctx context.Context, letterID uuid.UUID) error
|
|
ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error
|
|
RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error
|
|
SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error
|
|
ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error
|
|
|
|
AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error
|
|
UpdateRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, req *contract.UpdateRecipientRequest) error
|
|
RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error
|
|
|
|
AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error
|
|
RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error
|
|
|
|
CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error)
|
|
UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error
|
|
DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error
|
|
|
|
GetLetterApprovalInfo(ctx context.Context, letterID uuid.UUID) (*contract.LetterApprovalInfoResponse, error)
|
|
|
|
// GetApprovalDiscussions returns both approvals and discussions for an outgoing letter
|
|
GetApprovalDiscussions(ctx context.Context, letterID uuid.UUID) (*contract.OutgoingLetterApprovalDiscussionsResponse, error)
|
|
}
|
|
|
|
type LetterOutgoingServiceImpl struct {
|
|
processor processor.LetterOutgoingProcessor
|
|
}
|
|
|
|
func NewLetterOutgoingService(processor processor.LetterOutgoingProcessor) *LetterOutgoingServiceImpl {
|
|
return &LetterOutgoingServiceImpl{
|
|
processor: processor,
|
|
}
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) {
|
|
departmentID := getDepartmentIDFromContext(ctx)
|
|
|
|
letter := &entities.LetterOutgoing{
|
|
Subject: req.Subject,
|
|
Description: req.Description,
|
|
PriorityID: req.PriorityID,
|
|
ReceiverInstitutionID: req.ReceiverInstitutionID,
|
|
IssueDate: req.IssueDate,
|
|
CreatedBy: req.UserID,
|
|
}
|
|
|
|
if req.ReferenceNumber != nil {
|
|
letter.ReferenceNumber = req.ReferenceNumber
|
|
}
|
|
|
|
var attachments []entities.LetterOutgoingAttachment
|
|
if len(req.Attachments) > 0 {
|
|
attachments = make([]entities.LetterOutgoingAttachment, len(req.Attachments))
|
|
for i, a := range req.Attachments {
|
|
attachments[i] = entities.LetterOutgoingAttachment{
|
|
FileURL: a.FileURL,
|
|
FileName: a.FileName,
|
|
FileType: a.FileType,
|
|
UploadedBy: &req.UserID,
|
|
}
|
|
}
|
|
}
|
|
|
|
err := s.processor.CreateOutgoingLetter(ctx, letter, attachments, req.UserID, departmentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := s.processor.GetOutgoingLetterByID(ctx, letter.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return transformLetterToResponse(result), nil
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error) {
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return transformLetterToResponse(letter), nil
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error) {
|
|
filter := repository.ListOutgoingLettersFilter{
|
|
Status: req.Status,
|
|
Query: req.Query,
|
|
CreatedBy: req.CreatedBy,
|
|
ReceiverInstitutionID: req.ReceiverInstitutionID,
|
|
FromDate: req.FromDate,
|
|
ToDate: req.ToDate,
|
|
}
|
|
|
|
letters, total, err := s.processor.ListOutgoingLetters(ctx, filter, req.Limit, req.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]*contract.OutgoingLetterResponse, len(letters))
|
|
for i, letter := range letters {
|
|
items[i] = transformLetterToResponse(&letter)
|
|
}
|
|
|
|
return &contract.ListOutgoingLettersResponse{
|
|
Items: items,
|
|
Total: total,
|
|
}, nil
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return nil, gorm.ErrInvalidData
|
|
}
|
|
|
|
if req.Subject != nil {
|
|
letter.Subject = *req.Subject
|
|
}
|
|
if req.Description != nil {
|
|
letter.Description = req.Description
|
|
}
|
|
if req.PriorityID != nil {
|
|
letter.PriorityID = req.PriorityID
|
|
}
|
|
if req.ReceiverInstitutionID != nil {
|
|
letter.ReceiverInstitutionID = req.ReceiverInstitutionID
|
|
}
|
|
if req.IssueDate != nil {
|
|
letter.IssueDate = *req.IssueDate
|
|
}
|
|
if req.ReferenceNumber != nil {
|
|
letter.ReferenceNumber = req.ReferenceNumber
|
|
}
|
|
|
|
err = s.processor.UpdateOutgoingLetter(ctx, letter, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := s.processor.GetOutgoingLetterByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return transformLetterToResponse(result), nil
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
return s.processor.DeleteOutgoingLetter(ctx, id, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, letterID uuid.UUID) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
if letter.ApprovalFlowID == nil {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
return s.processor.ProcessApprovalSubmission(ctx, letterID, *letter.ApprovalFlowID, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusPendingApproval {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var currentApproval *entities.LetterOutgoingApproval
|
|
for i := range approvals {
|
|
if approvals[i].Status == entities.ApprovalStatusPending {
|
|
step := approvals[i].Step
|
|
if (step.ApproverUserID != nil && *step.ApproverUserID == userID) ||
|
|
(step.ApproverRoleID != nil && userHasRole(ctx, *step.ApproverRoleID)) {
|
|
currentApproval = &approvals[i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if currentApproval == nil {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
currentApproval.Remarks = req.Remarks
|
|
|
|
allApproved := true
|
|
for _, approval := range approvals {
|
|
if approval.ID != currentApproval.ID && approval.Status == entities.ApprovalStatusPending {
|
|
allApproved = false
|
|
break
|
|
}
|
|
}
|
|
|
|
return s.processor.ProcessApproval(ctx, letterID, currentApproval, userID, allApproved)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusPendingApproval {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var currentApproval *entities.LetterOutgoingApproval
|
|
for i := range approvals {
|
|
if approvals[i].Status == entities.ApprovalStatusPending {
|
|
step := approvals[i].Step
|
|
if (step.ApproverUserID != nil && *step.ApproverUserID == userID) ||
|
|
(step.ApproverRoleID != nil && userHasRole(ctx, *step.ApproverRoleID)) {
|
|
currentApproval = &approvals[i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if currentApproval == nil {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
currentApproval.Remarks = &req.Reason
|
|
|
|
return s.processor.ProcessRejection(ctx, letterID, currentApproval, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusApproved {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
fromStatus := string(entities.LetterOutgoingStatusApproved)
|
|
toStatus := string(entities.LetterOutgoingStatusSent)
|
|
return s.processor.UpdateLetterStatus(ctx, letterID, entities.LetterOutgoingStatusSent, userID, &fromStatus, &toStatus)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusSent {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
fromStatus := string(entities.LetterOutgoingStatusSent)
|
|
toStatus := string(entities.LetterOutgoingStatusArchived)
|
|
return s.processor.UpdateLetterStatus(ctx, letterID, entities.LetterOutgoingStatusArchived, userID, &fromStatus, &toStatus)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients))
|
|
for i, r := range req.Recipients {
|
|
recipients[i] = entities.LetterOutgoingRecipient{
|
|
LetterID: letterID,
|
|
UserID: r.UserID,
|
|
DepartmentID: r.DepartmentID,
|
|
IsPrimary: r.IsPrimary,
|
|
Status: r.Status,
|
|
Flag: r.Flag,
|
|
IsArchived: r.IsArchived,
|
|
}
|
|
}
|
|
|
|
return s.processor.AddRecipients(ctx, letterID, recipients, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) UpdateRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, req *contract.UpdateRecipientRequest) error {
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
recipient := &entities.LetterOutgoingRecipient{
|
|
ID: recipientID,
|
|
IsPrimary: req.IsPrimary,
|
|
}
|
|
|
|
if req.UserID != nil {
|
|
recipient.UserID = req.UserID
|
|
}
|
|
if req.DepartmentID != nil {
|
|
recipient.DepartmentID = req.DepartmentID
|
|
}
|
|
if req.Status != nil {
|
|
recipient.Status = *req.Status
|
|
}
|
|
if req.Flag != nil {
|
|
recipient.Flag = req.Flag
|
|
}
|
|
if req.IsArchived != nil {
|
|
recipient.IsArchived = *req.IsArchived
|
|
}
|
|
|
|
return s.processor.UpdateRecipient(ctx, recipient)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
return s.processor.RemoveRecipient(ctx, letterID, recipientID, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments))
|
|
for i, a := range req.Attachments {
|
|
attachments[i] = entities.LetterOutgoingAttachment{
|
|
LetterID: letterID,
|
|
FileURL: a.FileURL,
|
|
FileName: a.FileName,
|
|
FileType: a.FileType,
|
|
UploadedBy: &userID,
|
|
}
|
|
}
|
|
|
|
return s.processor.AddAttachments(ctx, letterID, attachments, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if letter.Status != entities.LetterOutgoingStatusDraft {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
return s.processor.RemoveAttachment(ctx, letterID, attachmentID, userID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
discussion := &entities.LetterOutgoingDiscussion{
|
|
LetterID: letterID,
|
|
ParentID: req.ParentID,
|
|
UserID: userID,
|
|
Message: req.Message,
|
|
}
|
|
|
|
if req.Mentions != nil {
|
|
discussion.Mentions = req.Mentions
|
|
}
|
|
|
|
var attachments []entities.LetterOutgoingDiscussionAttachment
|
|
if len(req.Attachments) > 0 {
|
|
attachments = make([]entities.LetterOutgoingDiscussionAttachment, len(req.Attachments))
|
|
for i, a := range req.Attachments {
|
|
attachments[i] = entities.LetterOutgoingDiscussionAttachment{
|
|
DiscussionID: discussion.ID,
|
|
FileURL: a.FileURL,
|
|
FileName: a.FileName,
|
|
FileType: a.FileType,
|
|
UploadedBy: &userID,
|
|
}
|
|
}
|
|
}
|
|
|
|
err := s.processor.CreateDiscussion(ctx, discussion, attachments, userID)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := s.processor.GetDiscussionByID(ctx, discussion.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return transformDiscussionToResponse(result), nil
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error {
|
|
discussion, err := s.processor.GetDiscussionByID(ctx, discussionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userID := getUserIDFromContext(ctx)
|
|
if discussion.UserID != userID {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
discussion.Message = req.Message
|
|
if req.Mentions != nil {
|
|
discussion.Mentions = req.Mentions
|
|
}
|
|
|
|
return s.processor.UpdateDiscussion(ctx, discussion)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error {
|
|
discussion, err := s.processor.GetDiscussionByID(ctx, discussionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userID := getUserIDFromContext(ctx)
|
|
if discussion.UserID != userID {
|
|
return gorm.ErrInvalidData
|
|
}
|
|
|
|
return s.processor.DeleteDiscussion(ctx, discussionID)
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) GetLetterApprovalInfo(ctx context.Context, letterID uuid.UUID) (*contract.LetterApprovalInfoResponse, error) {
|
|
userID := getUserIDFromContext(ctx)
|
|
|
|
_, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var currentApproval *entities.LetterOutgoingApproval
|
|
var isApproverOnActiveStep bool
|
|
var canApprove bool
|
|
|
|
for _, approval := range approvals {
|
|
if approval.Status == entities.ApprovalStatusPending {
|
|
currentApproval = &approval
|
|
break
|
|
}
|
|
}
|
|
|
|
// Check if current user is the approver for the active step
|
|
if currentApproval != nil && currentApproval.Step != nil {
|
|
step := currentApproval.Step
|
|
|
|
// Check if user is the specific approver
|
|
if step.ApproverUserID != nil && *step.ApproverUserID == userID {
|
|
isApproverOnActiveStep = true
|
|
canApprove = true
|
|
}
|
|
// Note: Role-based approval check would require additional implementation
|
|
// For now, we only support user-specific approvers
|
|
}
|
|
|
|
// Build actions based on current status
|
|
var actions []contract.ApprovalAction
|
|
if canApprove && currentApproval != nil {
|
|
actions = []contract.ApprovalAction{
|
|
{
|
|
Type: "APPROVE",
|
|
Href: fmt.Sprintf("/v1/letters/%s/approvals/%s/decision", letterID, currentApproval.ID),
|
|
Method: "POST",
|
|
},
|
|
{
|
|
Type: "REJECT",
|
|
Href: fmt.Sprintf("/v1/letters/%s/approvals/%s/decision", letterID, currentApproval.ID),
|
|
Method: "POST",
|
|
},
|
|
}
|
|
}
|
|
|
|
// Determine decision status
|
|
decisionStatus := "PENDING"
|
|
if currentApproval == nil {
|
|
decisionStatus = "COMPLETED"
|
|
}
|
|
|
|
// Determine notes visibility
|
|
notesVisibility := "FULL"
|
|
if !isApproverOnActiveStep {
|
|
notesVisibility = "READONLY"
|
|
}
|
|
|
|
info := &contract.LetterApprovalInfoResponse{
|
|
IsApproverOnActiveStep: isApproverOnActiveStep,
|
|
DecisionStatus: decisionStatus,
|
|
CanApprove: canApprove,
|
|
Actions: actions,
|
|
NotesVisibility: notesVisibility,
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func getUserIDFromContext(ctx context.Context) uuid.UUID {
|
|
appCtx := appcontext.FromGinContext(ctx)
|
|
if appCtx != nil {
|
|
return appCtx.UserID
|
|
}
|
|
return uuid.New()
|
|
}
|
|
|
|
func getDepartmentIDFromContext(ctx context.Context) uuid.UUID {
|
|
appCtx := appcontext.FromGinContext(ctx)
|
|
if appCtx != nil {
|
|
return appCtx.DepartmentID
|
|
}
|
|
return uuid.Nil
|
|
}
|
|
|
|
func userHasRole(ctx context.Context, roleID uuid.UUID) bool {
|
|
return false
|
|
}
|
|
|
|
func (s *LetterOutgoingServiceImpl) GetApprovalDiscussions(ctx context.Context, letterID uuid.UUID) (*contract.OutgoingLetterApprovalDiscussionsResponse, error) {
|
|
// Get the letter with all related data
|
|
letter, err := s.processor.GetOutgoingLetterWithDetails(ctx, letterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Transform approvals
|
|
approvals := make([]contract.EnhancedOutgoingLetterApprovalResponse, 0, len(letter.Approvals))
|
|
for _, approval := range letter.Approvals {
|
|
approvalResp := contract.EnhancedOutgoingLetterApprovalResponse{
|
|
ID: approval.ID,
|
|
LetterID: approval.LetterID,
|
|
StepID: approval.StepID,
|
|
ApproverID: approval.ApproverID,
|
|
Status: string(approval.Status),
|
|
Remarks: approval.Remarks,
|
|
ActedAt: approval.ActedAt,
|
|
CreatedAt: approval.CreatedAt,
|
|
}
|
|
|
|
// Add step details if available
|
|
if approval.Step != nil {
|
|
approvalResp.Step = &contract.ApprovalFlowStepResponse{
|
|
ID: approval.Step.ID,
|
|
StepOrder: approval.Step.StepOrder,
|
|
ParallelGroup: approval.Step.ParallelGroup,
|
|
Required: approval.Step.Required,
|
|
CreatedAt: approval.Step.CreatedAt,
|
|
UpdatedAt: approval.Step.UpdatedAt,
|
|
}
|
|
|
|
if approval.Step.ApproverRoleID != nil {
|
|
approvalResp.Step.ApproverRoleID = approval.Step.ApproverRoleID
|
|
}
|
|
if approval.Step.ApproverUserID != nil {
|
|
approvalResp.Step.ApproverUserID = approval.Step.ApproverUserID
|
|
}
|
|
|
|
// Add role information if available
|
|
if approval.Step.ApproverRole != nil {
|
|
approvalResp.Step.ApproverRole = &contract.RoleResponse{
|
|
ID: approval.Step.ApproverRole.ID,
|
|
Name: approval.Step.ApproverRole.Name,
|
|
Code: approval.Step.ApproverRole.Code,
|
|
}
|
|
}
|
|
|
|
// Add user information if available
|
|
if approval.Step.ApproverUser != nil {
|
|
approvalResp.Step.ApproverUser = &contract.UserResponse{
|
|
ID: approval.Step.ApproverUser.ID,
|
|
Name: approval.Step.ApproverUser.Name,
|
|
Email: approval.Step.ApproverUser.Email,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add approver details if available
|
|
if approval.Approver != nil {
|
|
approvalResp.Approver = &contract.UserResponse{
|
|
ID: approval.Approver.ID,
|
|
Name: approval.Approver.Name,
|
|
Email: approval.Approver.Email,
|
|
}
|
|
|
|
// Add profile if available
|
|
if approval.Approver.Profile != nil {
|
|
approvalResp.Approver.Profile = &contract.UserProfileResponse{
|
|
UserID: approval.Approver.Profile.UserID,
|
|
FullName: approval.Approver.Profile.FullName,
|
|
DisplayName: approval.Approver.Profile.DisplayName,
|
|
Phone: approval.Approver.Profile.Phone,
|
|
AvatarURL: approval.Approver.Profile.AvatarURL,
|
|
JobTitle: approval.Approver.Profile.JobTitle,
|
|
EmployeeNo: approval.Approver.Profile.EmployeeNo,
|
|
Bio: approval.Approver.Profile.Bio,
|
|
Timezone: approval.Approver.Profile.Timezone,
|
|
Locale: approval.Approver.Profile.Locale,
|
|
}
|
|
}
|
|
}
|
|
|
|
approvals = append(approvals, approvalResp)
|
|
}
|
|
|
|
// Transform discussions
|
|
discussions := make([]contract.OutgoingLetterDiscussionResponse, 0, len(letter.Discussions))
|
|
for _, discussion := range letter.Discussions {
|
|
// Extract mentioned user IDs from mentions
|
|
mentionedUserIDs := extractMentionedUserIDs(discussion.Mentions)
|
|
|
|
discussionResp := contract.OutgoingLetterDiscussionResponse{
|
|
ID: discussion.ID,
|
|
LetterID: discussion.LetterID,
|
|
ParentID: discussion.ParentID,
|
|
UserID: discussion.UserID,
|
|
Message: discussion.Message,
|
|
Mentions: discussion.Mentions,
|
|
CreatedAt: discussion.CreatedAt,
|
|
UpdatedAt: discussion.UpdatedAt,
|
|
EditedAt: discussion.EditedAt,
|
|
}
|
|
|
|
// Add user details if available
|
|
if discussion.User != nil {
|
|
discussionResp.User = &contract.UserResponse{
|
|
ID: discussion.User.ID,
|
|
Name: discussion.User.Name,
|
|
Email: discussion.User.Email,
|
|
IsActive: discussion.User.IsActive,
|
|
CreatedAt: discussion.User.CreatedAt,
|
|
UpdatedAt: discussion.User.UpdatedAt,
|
|
}
|
|
|
|
// Add profile if available
|
|
if discussion.User.Profile != nil {
|
|
discussionResp.User.Profile = &contract.UserProfileResponse{
|
|
UserID: discussion.User.Profile.UserID,
|
|
FullName: discussion.User.Profile.FullName,
|
|
DisplayName: discussion.User.Profile.DisplayName,
|
|
Phone: discussion.User.Profile.Phone,
|
|
AvatarURL: discussion.User.Profile.AvatarURL,
|
|
JobTitle: discussion.User.Profile.JobTitle,
|
|
EmployeeNo: discussion.User.Profile.EmployeeNo,
|
|
Bio: discussion.User.Profile.Bio,
|
|
Timezone: discussion.User.Profile.Timezone,
|
|
Locale: discussion.User.Profile.Locale,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get mentioned users details
|
|
if len(mentionedUserIDs) > 0 {
|
|
mentionedUsers, _ := s.processor.GetUsersByIDs(ctx, mentionedUserIDs)
|
|
for _, user := range mentionedUsers {
|
|
mentionedUserResp := contract.UserResponse{
|
|
ID: user.ID,
|
|
Name: user.Name,
|
|
Email: user.Email,
|
|
IsActive: user.IsActive,
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
}
|
|
|
|
if user.Profile != nil {
|
|
mentionedUserResp.Profile = &contract.UserProfileResponse{
|
|
UserID: user.Profile.UserID,
|
|
FullName: user.Profile.FullName,
|
|
DisplayName: user.Profile.DisplayName,
|
|
Timezone: user.Profile.Timezone,
|
|
Locale: user.Profile.Locale,
|
|
}
|
|
}
|
|
|
|
discussionResp.MentionedUsers = append(discussionResp.MentionedUsers, mentionedUserResp)
|
|
}
|
|
}
|
|
|
|
// Add attachments if available
|
|
for _, attachment := range discussion.Attachments {
|
|
attachmentResp := contract.OutgoingLetterDiscussionAttachmentResponse{
|
|
ID: attachment.ID,
|
|
DiscussionID: attachment.DiscussionID,
|
|
FileURL: attachment.FileURL,
|
|
FileName: attachment.FileName,
|
|
FileType: attachment.FileType,
|
|
UploadedBy: attachment.UploadedBy,
|
|
UploadedAt: attachment.UploadedAt,
|
|
}
|
|
discussionResp.Attachments = append(discussionResp.Attachments, attachmentResp)
|
|
}
|
|
|
|
discussions = append(discussions, discussionResp)
|
|
}
|
|
|
|
return &contract.OutgoingLetterApprovalDiscussionsResponse{
|
|
Approvals: approvals,
|
|
Discussions: discussions,
|
|
}, nil
|
|
}
|
|
|
|
// Helper function to extract user IDs from mentions
|
|
func extractMentionedUserIDs(mentions map[string]interface{}) []uuid.UUID {
|
|
var userIDs []uuid.UUID
|
|
|
|
if mentions == nil {
|
|
return userIDs
|
|
}
|
|
|
|
if userIDsInterface, ok := mentions["user_ids"]; ok {
|
|
if userIDsList, ok := userIDsInterface.([]interface{}); ok {
|
|
for _, id := range userIDsList {
|
|
if idStr, ok := id.(string); ok {
|
|
if userID, err := uuid.Parse(idStr); err == nil {
|
|
userIDs = append(userIDs, userID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return userIDs
|
|
}
|
|
|
|
func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.OutgoingLetterResponse {
|
|
resp := &contract.OutgoingLetterResponse{
|
|
ID: letter.ID,
|
|
LetterNumber: letter.LetterNumber,
|
|
ReferenceNumber: letter.ReferenceNumber,
|
|
Subject: letter.Subject,
|
|
Description: letter.Description,
|
|
PriorityID: letter.PriorityID,
|
|
ReceiverInstitutionID: letter.ReceiverInstitutionID,
|
|
IssueDate: letter.IssueDate,
|
|
Status: string(letter.Status),
|
|
ApprovalFlowID: letter.ApprovalFlowID,
|
|
CreatedBy: letter.CreatedBy,
|
|
CreatedAt: letter.CreatedAt,
|
|
UpdatedAt: letter.UpdatedAt,
|
|
}
|
|
|
|
if letter.Priority != nil {
|
|
resp.Priority = &contract.PriorityResponse{
|
|
ID: letter.Priority.ID.String(),
|
|
Name: letter.Priority.Name,
|
|
Level: letter.Priority.Level,
|
|
CreatedAt: letter.Priority.CreatedAt,
|
|
UpdatedAt: letter.Priority.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
if letter.ReceiverInstitution != nil {
|
|
resp.ReceiverInstitution = &contract.InstitutionResponse{
|
|
ID: letter.ReceiverInstitution.ID.String(),
|
|
Name: letter.ReceiverInstitution.Name,
|
|
Type: string(letter.ReceiverInstitution.Type),
|
|
Address: letter.ReceiverInstitution.Address,
|
|
ContactPerson: letter.ReceiverInstitution.ContactPerson,
|
|
Phone: letter.ReceiverInstitution.Phone,
|
|
Email: letter.ReceiverInstitution.Email,
|
|
CreatedAt: letter.ReceiverInstitution.CreatedAt,
|
|
UpdatedAt: letter.ReceiverInstitution.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
if len(letter.Recipients) > 0 {
|
|
resp.Recipients = make([]contract.OutgoingLetterRecipientResponse, len(letter.Recipients))
|
|
for i, recipient := range letter.Recipients {
|
|
recipResp := contract.OutgoingLetterRecipientResponse{
|
|
ID: recipient.ID,
|
|
LetterID: recipient.LetterID,
|
|
UserID: recipient.UserID,
|
|
DepartmentID: recipient.DepartmentID,
|
|
IsPrimary: recipient.IsPrimary,
|
|
Status: recipient.Status,
|
|
ReadAt: recipient.ReadAt,
|
|
Flag: recipient.Flag,
|
|
IsArchived: recipient.IsArchived,
|
|
CreatedAt: recipient.CreatedAt,
|
|
}
|
|
|
|
if recipient.User != nil {
|
|
recipResp.User = &contract.UserResponse{
|
|
ID: recipient.User.ID,
|
|
Name: recipient.User.Name,
|
|
Email: recipient.User.Email,
|
|
}
|
|
}
|
|
|
|
if recipient.Department != nil {
|
|
recipResp.Department = &contract.DepartmentResponse{
|
|
ID: recipient.Department.ID,
|
|
Name: recipient.Department.Name,
|
|
Code: recipient.Department.Code,
|
|
}
|
|
}
|
|
|
|
resp.Recipients[i] = recipResp
|
|
}
|
|
}
|
|
|
|
if len(letter.Attachments) > 0 {
|
|
resp.Attachments = make([]contract.OutgoingLetterAttachmentResponse, len(letter.Attachments))
|
|
for i, attachment := range letter.Attachments {
|
|
resp.Attachments[i] = contract.OutgoingLetterAttachmentResponse{
|
|
ID: attachment.ID,
|
|
FileURL: attachment.FileURL,
|
|
FileName: attachment.FileName,
|
|
FileType: attachment.FileType,
|
|
UploadedAt: attachment.UploadedAt,
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(letter.Approvals) > 0 {
|
|
resp.Approvals = make([]contract.OutgoingLetterApprovalResponse, len(letter.Approvals))
|
|
for i, approval := range letter.Approvals {
|
|
approvalResp := contract.OutgoingLetterApprovalResponse{
|
|
ID: approval.ID,
|
|
ApproverID: approval.ApproverID,
|
|
Status: string(approval.Status),
|
|
Remarks: approval.Remarks,
|
|
ActedAt: approval.ActedAt,
|
|
CreatedAt: approval.CreatedAt,
|
|
}
|
|
|
|
// Include step order if step is loaded
|
|
if approval.Step != nil {
|
|
approvalResp.StepOrder = approval.Step.StepOrder
|
|
}
|
|
|
|
resp.Approvals[i] = approvalResp
|
|
}
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
func transformDiscussionToResponse(discussion *entities.LetterOutgoingDiscussion) *contract.DiscussionResponse {
|
|
return &contract.DiscussionResponse{
|
|
ID: discussion.ID,
|
|
UserID: discussion.UserID,
|
|
Message: discussion.Message,
|
|
CreatedAt: discussion.CreatedAt,
|
|
UpdatedAt: discussion.UpdatedAt,
|
|
}
|
|
}
|