Add letter and user id
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/contract"
|
||||
"eslogad-be/internal/entities"
|
||||
"eslogad-be/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ApprovalFlowService interface {
|
||||
CreateApprovalFlow(ctx context.Context, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error)
|
||||
GetApprovalFlow(ctx context.Context, id uuid.UUID) (*contract.ApprovalFlowResponse, error)
|
||||
GetApprovalFlowByDepartment(ctx context.Context, departmentID uuid.UUID) (*contract.ApprovalFlowResponse, error)
|
||||
UpdateApprovalFlow(ctx context.Context, id uuid.UUID, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error)
|
||||
DeleteApprovalFlow(ctx context.Context, id uuid.UUID) error
|
||||
ListApprovalFlows(ctx context.Context, req *contract.ListApprovalFlowsRequest) (*contract.ListApprovalFlowsResponse, error)
|
||||
}
|
||||
|
||||
type ApprovalFlowServiceImpl struct {
|
||||
db *gorm.DB
|
||||
flowRepo *repository.ApprovalFlowRepository
|
||||
stepRepo *repository.ApprovalFlowStepRepository
|
||||
txManager *repository.TxManager
|
||||
}
|
||||
|
||||
func NewApprovalFlowService(
|
||||
db *gorm.DB,
|
||||
flowRepo *repository.ApprovalFlowRepository,
|
||||
stepRepo *repository.ApprovalFlowStepRepository,
|
||||
txManager *repository.TxManager,
|
||||
) *ApprovalFlowServiceImpl {
|
||||
return &ApprovalFlowServiceImpl{
|
||||
db: db,
|
||||
flowRepo: flowRepo,
|
||||
stepRepo: stepRepo,
|
||||
txManager: txManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ApprovalFlowServiceImpl) CreateApprovalFlow(ctx context.Context, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) {
|
||||
flow := &entities.ApprovalFlow{
|
||||
DepartmentID: req.DepartmentID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
|
||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.flowRepo.Create(txCtx, flow); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(req.Steps) > 0 {
|
||||
steps := make([]entities.ApprovalFlowStep, len(req.Steps))
|
||||
for i, stepReq := range req.Steps {
|
||||
if stepReq.ApproverRoleID == nil && stepReq.ApproverUserID == nil {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
steps[i] = entities.ApprovalFlowStep{
|
||||
FlowID: flow.ID,
|
||||
StepOrder: stepReq.StepOrder,
|
||||
ParallelGroup: stepReq.ParallelGroup,
|
||||
ApproverRoleID: stepReq.ApproverRoleID,
|
||||
ApproverUserID: stepReq.ApproverUserID,
|
||||
Required: stepReq.Required,
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.stepRepo.CreateBulk(txCtx, steps); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := s.flowRepo.Get(ctx, flow.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transformApprovalFlowToResponse(result), nil
|
||||
}
|
||||
|
||||
func (s *ApprovalFlowServiceImpl) GetApprovalFlow(ctx context.Context, id uuid.UUID) (*contract.ApprovalFlowResponse, error) {
|
||||
flow, err := s.flowRepo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transformApprovalFlowToResponse(flow), nil
|
||||
}
|
||||
|
||||
func (s *ApprovalFlowServiceImpl) GetApprovalFlowByDepartment(ctx context.Context, departmentID uuid.UUID) (*contract.ApprovalFlowResponse, error) {
|
||||
flow, err := s.flowRepo.GetByDepartment(ctx, departmentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transformApprovalFlowToResponse(flow), nil
|
||||
}
|
||||
|
||||
func (s *ApprovalFlowServiceImpl) UpdateApprovalFlow(ctx context.Context, id uuid.UUID, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) {
|
||||
flow, err := s.flowRepo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flow.DepartmentID = req.DepartmentID
|
||||
flow.Name = req.Name
|
||||
flow.Description = req.Description
|
||||
flow.IsActive = req.IsActive
|
||||
|
||||
err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.flowRepo.Update(txCtx, flow); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.stepRepo.DeleteByFlow(txCtx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(req.Steps) > 0 {
|
||||
steps := make([]entities.ApprovalFlowStep, len(req.Steps))
|
||||
for i, stepReq := range req.Steps {
|
||||
if stepReq.ApproverRoleID == nil && stepReq.ApproverUserID == nil {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
steps[i] = entities.ApprovalFlowStep{
|
||||
FlowID: flow.ID,
|
||||
StepOrder: stepReq.StepOrder,
|
||||
ParallelGroup: stepReq.ParallelGroup,
|
||||
ApproverRoleID: stepReq.ApproverRoleID,
|
||||
ApproverUserID: stepReq.ApproverUserID,
|
||||
Required: stepReq.Required,
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.stepRepo.CreateBulk(txCtx, steps); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := s.flowRepo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transformApprovalFlowToResponse(result), nil
|
||||
}
|
||||
|
||||
func (s *ApprovalFlowServiceImpl) DeleteApprovalFlow(ctx context.Context, id uuid.UUID) error {
|
||||
return s.flowRepo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (s *ApprovalFlowServiceImpl) ListApprovalFlows(ctx context.Context, req *contract.ListApprovalFlowsRequest) (*contract.ListApprovalFlowsResponse, error) {
|
||||
filter := repository.ListApprovalFlowsFilter{
|
||||
DepartmentID: req.DepartmentID,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
|
||||
flows, total, err := s.flowRepo.List(ctx, filter, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]*contract.ApprovalFlowResponse, len(flows))
|
||||
for i, flow := range flows {
|
||||
items[i] = transformApprovalFlowToResponse(&flow)
|
||||
}
|
||||
|
||||
return &contract.ListApprovalFlowsResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transformApprovalFlowToResponse(flow *entities.ApprovalFlow) *contract.ApprovalFlowResponse {
|
||||
resp := &contract.ApprovalFlowResponse{
|
||||
ID: flow.ID,
|
||||
DepartmentID: flow.DepartmentID,
|
||||
Name: flow.Name,
|
||||
Description: flow.Description,
|
||||
IsActive: flow.IsActive,
|
||||
CreatedAt: flow.CreatedAt,
|
||||
UpdatedAt: flow.UpdatedAt,
|
||||
}
|
||||
|
||||
if flow.Department != nil {
|
||||
resp.Department = &contract.DepartmentResponse{
|
||||
ID: flow.Department.ID,
|
||||
Name: flow.Department.Name,
|
||||
}
|
||||
}
|
||||
|
||||
if len(flow.Steps) > 0 {
|
||||
resp.Steps = make([]contract.ApprovalFlowStepResponse, len(flow.Steps))
|
||||
for i, step := range flow.Steps {
|
||||
stepResp := contract.ApprovalFlowStepResponse{
|
||||
ID: step.ID,
|
||||
StepOrder: step.StepOrder,
|
||||
ParallelGroup: step.ParallelGroup,
|
||||
ApproverRoleID: step.ApproverRoleID,
|
||||
ApproverUserID: step.ApproverUserID,
|
||||
Required: step.Required,
|
||||
CreatedAt: step.CreatedAt,
|
||||
UpdatedAt: step.UpdatedAt,
|
||||
}
|
||||
|
||||
if step.ApproverRole != nil {
|
||||
stepResp.ApproverRole = &contract.RoleResponse{
|
||||
ID: step.ApproverRole.ID,
|
||||
Name: step.ApproverRole.Name,
|
||||
}
|
||||
}
|
||||
|
||||
if step.ApproverUser != nil {
|
||||
stepResp.ApproverUser = &contract.UserResponse{
|
||||
ID: step.ApproverUser.ID,
|
||||
Name: step.ApproverUser.Name,
|
||||
Email: step.ApproverUser.Email,
|
||||
}
|
||||
}
|
||||
|
||||
resp.Steps[i] = stepResp
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"eslogad-be/internal/contract"
|
||||
"eslogad-be/internal/entities"
|
||||
"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
|
||||
}
|
||||
|
||||
type LetterOutgoingServiceImpl struct {
|
||||
db *gorm.DB
|
||||
letterRepo *repository.LetterOutgoingRepository
|
||||
attachmentRepo *repository.LetterOutgoingAttachmentRepository
|
||||
recipientRepo *repository.LetterOutgoingRecipientRepository
|
||||
discussionRepo *repository.LetterOutgoingDiscussionRepository
|
||||
discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository
|
||||
activityLogRepo *repository.LetterOutgoingActivityLogRepository
|
||||
approvalFlowRepo *repository.ApprovalFlowRepository
|
||||
approvalRepo *repository.LetterOutgoingApprovalRepository
|
||||
txManager *repository.TxManager
|
||||
}
|
||||
|
||||
func NewLetterOutgoingService(
|
||||
db *gorm.DB,
|
||||
letterRepo *repository.LetterOutgoingRepository,
|
||||
attachmentRepo *repository.LetterOutgoingAttachmentRepository,
|
||||
recipientRepo *repository.LetterOutgoingRecipientRepository,
|
||||
discussionRepo *repository.LetterOutgoingDiscussionRepository,
|
||||
discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository,
|
||||
activityLogRepo *repository.LetterOutgoingActivityLogRepository,
|
||||
approvalFlowRepo *repository.ApprovalFlowRepository,
|
||||
approvalRepo *repository.LetterOutgoingApprovalRepository,
|
||||
txManager *repository.TxManager,
|
||||
) *LetterOutgoingServiceImpl {
|
||||
return &LetterOutgoingServiceImpl{
|
||||
db: db,
|
||||
letterRepo: letterRepo,
|
||||
attachmentRepo: attachmentRepo,
|
||||
recipientRepo: recipientRepo,
|
||||
discussionRepo: discussionRepo,
|
||||
discussionAttachmentRepo: discussionAttachmentRepo,
|
||||
activityLogRepo: activityLogRepo,
|
||||
approvalFlowRepo: approvalFlowRepo,
|
||||
approvalRepo: approvalRepo,
|
||||
txManager: txManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter := &entities.LetterOutgoing{
|
||||
Subject: req.Subject,
|
||||
Description: req.Description,
|
||||
PriorityID: req.PriorityID,
|
||||
ReceiverInstitutionID: req.ReceiverInstitutionID,
|
||||
IssueDate: req.IssueDate,
|
||||
Status: entities.LetterOutgoingStatusDraft,
|
||||
ApprovalFlowID: req.ApprovalFlowID,
|
||||
CreatedBy: userID,
|
||||
}
|
||||
|
||||
if req.ReferenceNumber != nil {
|
||||
letter.ReferenceNumber = req.ReferenceNumber
|
||||
}
|
||||
|
||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.letterRepo.Create(txCtx, letter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(req.Recipients) > 0 {
|
||||
recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients))
|
||||
for i, r := range req.Recipients {
|
||||
recipients[i] = entities.LetterOutgoingRecipient{
|
||||
LetterID: letter.ID,
|
||||
RecipientName: r.Name,
|
||||
RecipientEmail: r.Email,
|
||||
RecipientPosition: r.Position,
|
||||
RecipientInstitution: r.Institution,
|
||||
IsPrimary: r.IsPrimary,
|
||||
}
|
||||
}
|
||||
if err := s.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(req.Attachments) > 0 {
|
||||
attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments))
|
||||
for i, a := range req.Attachments {
|
||||
attachments[i] = entities.LetterOutgoingAttachment{
|
||||
LetterID: letter.ID,
|
||||
FileURL: a.FileURL,
|
||||
FileName: a.FileName,
|
||||
FileType: a.FileType,
|
||||
UploadedBy: &userID,
|
||||
}
|
||||
}
|
||||
if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letter.ID,
|
||||
ActionType: entities.LetterOutgoingActionCreated,
|
||||
ActorUserID: &userID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := s.letterRepo.Get(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.letterRepo.Get(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.letterRepo.List(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.letterRepo.Get(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.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.letterRepo.Update(txCtx, letter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letter.ID,
|
||||
ActionType: entities.LetterOutgoingActionUpdated,
|
||||
ActorUserID: &userID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := s.letterRepo.Get(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.letterRepo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusDraft {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.letterRepo.SoftDelete(txCtx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letter.ID,
|
||||
ActionType: entities.LetterOutgoingActionDeleted,
|
||||
ActorUserID: &userID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, letterID uuid.UUID) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusDraft {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
if letter.ApprovalFlowID == nil {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
flow, err := s.approvalFlowRepo.Get(ctx, *letter.ApprovalFlowID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
approvals := make([]entities.LetterOutgoingApproval, len(flow.Steps))
|
||||
for i, step := range flow.Steps {
|
||||
approvals[i] = entities.LetterOutgoingApproval{
|
||||
LetterID: letterID,
|
||||
StepID: step.ID,
|
||||
Status: entities.ApprovalStatusPending,
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.approvalRepo.CreateBulk(txCtx, approvals); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionSubmittedApproval,
|
||||
ActorUserID: &userID,
|
||||
FromStatus: ptr(string(entities.LetterOutgoingStatusDraft)),
|
||||
ToStatus: ptr(string(entities.LetterOutgoingStatusPendingApproval)),
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusPendingApproval {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
approvals, err := s.approvalRepo.ListByLetter(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
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
now := time.Now()
|
||||
currentApproval.Status = entities.ApprovalStatusApproved
|
||||
currentApproval.ApproverID = &userID
|
||||
currentApproval.ActedAt = &now
|
||||
currentApproval.Remarks = req.Remarks
|
||||
|
||||
if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allApproved := true
|
||||
for _, approval := range approvals {
|
||||
if approval.ID != currentApproval.ID && approval.Status == entities.ApprovalStatusPending {
|
||||
allApproved = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allApproved {
|
||||
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionApproved,
|
||||
ActorUserID: &userID,
|
||||
TargetID: ¤tApproval.ID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusPendingApproval {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
approvals, err := s.approvalRepo.ListByLetter(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
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
now := time.Now()
|
||||
currentApproval.Status = entities.ApprovalStatusRejected
|
||||
currentApproval.ApproverID = &userID
|
||||
currentApproval.ActedAt = &now
|
||||
currentApproval.Remarks = &req.Reason
|
||||
|
||||
if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusDraft); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionRejected,
|
||||
ActorUserID: &userID,
|
||||
TargetID: ¤tApproval.ID,
|
||||
FromStatus: ptr(string(entities.LetterOutgoingStatusPendingApproval)),
|
||||
ToStatus: ptr(string(entities.LetterOutgoingStatusDraft)),
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusApproved {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusSent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionSent,
|
||||
ActorUserID: &userID,
|
||||
FromStatus: ptr(string(entities.LetterOutgoingStatusApproved)),
|
||||
ToStatus: ptr(string(entities.LetterOutgoingStatusSent)),
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusSent {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusArchived); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionArchived,
|
||||
ActorUserID: &userID,
|
||||
FromStatus: ptr(string(entities.LetterOutgoingStatusSent)),
|
||||
ToStatus: ptr(string(entities.LetterOutgoingStatusArchived)),
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(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,
|
||||
RecipientName: r.Name,
|
||||
RecipientEmail: r.Email,
|
||||
RecipientPosition: r.Position,
|
||||
RecipientInstitution: r.Institution,
|
||||
IsPrimary: r.IsPrimary,
|
||||
}
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionRecipientAdded,
|
||||
ActorUserID: &userID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) UpdateRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, req *contract.UpdateRecipientRequest) error {
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusDraft {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
recipient := &entities.LetterOutgoingRecipient{
|
||||
ID: recipientID,
|
||||
RecipientName: req.Name,
|
||||
RecipientEmail: req.Email,
|
||||
RecipientPosition: req.Position,
|
||||
RecipientInstitution: req.Institution,
|
||||
IsPrimary: req.IsPrimary,
|
||||
}
|
||||
|
||||
return s.recipientRepo.Update(ctx, recipient)
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusDraft {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.recipientRepo.Delete(txCtx, recipientID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionRecipientRemoved,
|
||||
ActorUserID: &userID,
|
||||
TargetID: &recipientID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(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.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionAttachmentAdded,
|
||||
ActorUserID: &userID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if letter.Status != entities.LetterOutgoingStatusDraft {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.attachmentRepo.Delete(txCtx, attachmentID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionAttachmentRemoved,
|
||||
ActorUserID: &userID,
|
||||
TargetID: &attachmentID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
if err := s.discussionRepo.Create(txCtx, discussion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
if err := s.discussionAttachmentRepo.CreateBulk(txCtx, attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionDiscussionAdded,
|
||||
ActorUserID: &userID,
|
||||
TargetID: &discussion.ID,
|
||||
}
|
||||
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := s.discussionRepo.Get(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.discussionRepo.Get(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.discussionRepo.Update(ctx, discussion)
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error {
|
||||
discussion, err := s.discussionRepo.Get(ctx, discussionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userID := getUserIDFromContext(ctx)
|
||||
if discussion.UserID != userID {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
return s.discussionRepo.Delete(ctx, discussionID)
|
||||
}
|
||||
|
||||
func getUserIDFromContext(ctx context.Context) uuid.UUID {
|
||||
return uuid.New()
|
||||
}
|
||||
|
||||
func userHasRole(ctx context.Context, roleID uuid.UUID) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func ptr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.OutgoingLetterResponse {
|
||||
return &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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user