add revision number
This commit is contained in:
parent
4e58ce95fb
commit
e58472c963
@ -107,6 +107,7 @@ type OutgoingLetterResponse struct {
|
||||
IssueDate time.Time `json:"issue_date"`
|
||||
Status string `json:"status"`
|
||||
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
|
||||
RevisionNumber int `json:"revision_number"`
|
||||
CreatedBy uuid.UUID `json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@ -157,6 +158,12 @@ type RejectLetterRequest struct {
|
||||
Reason string `json:"reason" validate:"required"`
|
||||
}
|
||||
|
||||
type ReviseLetterRequest struct {
|
||||
FileURL string `json:"file_url" validate:"required"`
|
||||
FileName string `json:"file_name" validate:"required"`
|
||||
FileType string `json:"file_type" validate:"required"`
|
||||
}
|
||||
|
||||
type AddRecipientsRequest struct {
|
||||
Recipients []CreateOutgoingLetterRecipient `json:"recipients" validate:"required,dive"`
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ type LetterOutgoingApproval struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
LetterID uuid.UUID `gorm:"type:uuid;not null" json:"letter_id"`
|
||||
StepID uuid.UUID `gorm:"type:uuid;not null" json:"step_id"`
|
||||
RevisionNumber int `gorm:"not null;default:0" json:"revision_number"`
|
||||
StepOrder int `gorm:"not null" json:"step_order"`
|
||||
ParallelGroup int `gorm:"default:1" json:"parallel_group"`
|
||||
IsRequired bool `gorm:"default:true" json:"is_required"`
|
||||
|
||||
@ -28,6 +28,7 @@ type LetterOutgoing struct {
|
||||
IssueDate time.Time `json:"issue_date"`
|
||||
Status LetterOutgoingStatus `gorm:"not null;default:'draft'" json:"status"`
|
||||
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
|
||||
RevisionNumber int `gorm:"not null;default:0" json:"revision_number"`
|
||||
CreatedBy uuid.UUID `gorm:"not null" json:"created_by"`
|
||||
IsArchived bool `gorm:"not null;default:false" json:"is_archived"`
|
||||
ArchivedAt *time.Time `json:"archived_at,omitempty"`
|
||||
@ -69,6 +70,7 @@ func (LetterOutgoingRecipient) TableName() string { return "letter_outgoing_reci
|
||||
type LetterOutgoingAttachment struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
LetterID uuid.UUID `gorm:"type:uuid;not null" json:"letter_id"`
|
||||
RevisionNumber int `gorm:"not null;default:0" json:"revision_number"`
|
||||
FileURL string `gorm:"not null" json:"file_url"`
|
||||
FileName string `gorm:"not null" json:"file_name"`
|
||||
FileType string `gorm:"not null" json:"file_type"`
|
||||
|
||||
@ -36,6 +36,7 @@ const (
|
||||
LetterOutgoingActionSubmittedApproval = "submitted_for_approval"
|
||||
LetterOutgoingActionApproved = "approved"
|
||||
LetterOutgoingActionRejected = "rejected"
|
||||
LetterOutgoingActionRevised = "revised"
|
||||
LetterOutgoingActionSent = "sent"
|
||||
LetterOutgoingActionArchived = "archived"
|
||||
LetterOutgoingActionAttachmentAdded = "attachment_added"
|
||||
|
||||
@ -23,6 +23,7 @@ type LetterOutgoingService interface {
|
||||
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
|
||||
ReviseOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ReviseLetterRequest) error
|
||||
SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error
|
||||
ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error
|
||||
|
||||
@ -217,6 +218,27 @@ func (h *LetterOutgoingHandler) RejectOutgoingLetter(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{Message: "rejected"}))
|
||||
}
|
||||
|
||||
func (h *LetterOutgoingHandler) ReviseOutgoingLetter(c *gin.Context) {
|
||||
id, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
|
||||
return
|
||||
}
|
||||
|
||||
var req contract.ReviseLetterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.ReviseOutgoingLetter(c.Request.Context(), id, &req); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{Message: "revised"}))
|
||||
}
|
||||
|
||||
func (h *LetterOutgoingHandler) SendOutgoingLetter(c *gin.Context) {
|
||||
id, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
|
||||
@ -24,8 +24,9 @@ type LetterOutgoingProcessor interface {
|
||||
ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID, userID uuid.UUID) error
|
||||
|
||||
ProcessApprovalSubmission(ctx context.Context, letterID uuid.UUID, approvalFlowID uuid.UUID, userID uuid.UUID) error
|
||||
ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID, allApproved bool) error
|
||||
ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error
|
||||
ProcessRejection(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error
|
||||
ProcessRevision(ctx context.Context, letterID uuid.UUID, attachment entities.LetterOutgoingAttachment, userID uuid.UUID) error
|
||||
|
||||
AddRecipients(ctx context.Context, letterID uuid.UUID, recipients []entities.LetterOutgoingRecipient, userID uuid.UUID) error
|
||||
UpdateRecipient(ctx context.Context, recipient *entities.LetterOutgoingRecipient) error
|
||||
@ -40,6 +41,7 @@ type LetterOutgoingProcessor interface {
|
||||
DeleteDiscussion(ctx context.Context, id uuid.UUID) error
|
||||
|
||||
GetApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error)
|
||||
GetApprovalsByLetterAndRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) ([]entities.LetterOutgoingApproval, error)
|
||||
GetApprovalFlow(ctx context.Context, flowID uuid.UUID) (*entities.ApprovalFlow, error)
|
||||
|
||||
// GetOutgoingLetterWithDetails fetches letter with all related data
|
||||
@ -397,6 +399,12 @@ func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Cont
|
||||
}
|
||||
|
||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
// Get the letter to get the current revision number
|
||||
letter, err := p.letterRepo.Get(txCtx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the minimum step order (first step)
|
||||
minStepOrder := flow.Steps[0].StepOrder
|
||||
for _, step := range flow.Steps {
|
||||
@ -410,6 +418,7 @@ func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Cont
|
||||
approvals[i] = entities.LetterOutgoingApproval{
|
||||
LetterID: letterID,
|
||||
StepID: step.ID,
|
||||
RevisionNumber: letter.RevisionNumber,
|
||||
StepOrder: step.StepOrder,
|
||||
ParallelGroup: step.ParallelGroup,
|
||||
IsRequired: step.Required,
|
||||
@ -483,15 +492,15 @@ func (p *LetterOutgoingProcessorImpl) ProcessApprovalSubmission(ctx context.Cont
|
||||
})
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID, allApproved bool) error {
|
||||
func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, letterID uuid.UUID, approval *entities.LetterOutgoingApproval, userID uuid.UUID) error {
|
||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
// Step 1: Update the approval record
|
||||
if err := p.updateApprovalRecord(txCtx, approval, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 2: Get all approvals and organize by step
|
||||
approvalsByStep, err := p.getApprovalsByStep(txCtx, letterID)
|
||||
// Step 2: Get all approvals FOR THE SAME REVISION and organize by step
|
||||
approvalsByStep, err := p.getApprovalsByStepForRevision(txCtx, letterID, approval.RevisionNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -499,12 +508,12 @@ func (p *LetterOutgoingProcessorImpl) ProcessApproval(ctx context.Context, lette
|
||||
// Step 3: Check if current step is completed
|
||||
if p.isStepCompleted(approvalsByStep[approval.StepOrder]) {
|
||||
// Step 4: Activate next step if exists
|
||||
if err := p.activateNextStep(txCtx, letterID, approval.StepOrder, approvalsByStep); err != nil {
|
||||
if err := p.activateNextStepForRevision(txCtx, letterID, approval.StepOrder, approval.RevisionNumber, approvalsByStep); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Check if all required approvals are completed
|
||||
// Step 5: Check if all required approvals are completed FOR THIS REVISION
|
||||
if p.areAllRequiredApprovalsCompleted(approvalsByStep) {
|
||||
// Step 6: Update letter status to approved
|
||||
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
|
||||
@ -542,6 +551,24 @@ func (p *LetterOutgoingProcessorImpl) getApprovalsByStep(ctx context.Context, le
|
||||
return approvalsByStep, nil
|
||||
}
|
||||
|
||||
// getApprovalsByStepForRevision fetches approvals for a specific revision and organizes them by step order
|
||||
func (p *LetterOutgoingProcessorImpl) getApprovalsByStepForRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) (map[int][]entities.LetterOutgoingApproval, error) {
|
||||
allApprovals, err := p.approvalRepo.ListByLetter(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
approvalsByStep := make(map[int][]entities.LetterOutgoingApproval)
|
||||
for _, approval := range allApprovals {
|
||||
// Only include approvals from the same revision
|
||||
if approval.RevisionNumber == revisionNumber {
|
||||
approvalsByStep[approval.StepOrder] = append(approvalsByStep[approval.StepOrder], approval)
|
||||
}
|
||||
}
|
||||
|
||||
return approvalsByStep, nil
|
||||
}
|
||||
|
||||
// isStepCompleted checks if all required approvals in a step are approved
|
||||
func (p *LetterOutgoingProcessorImpl) isStepCompleted(stepApprovals []entities.LetterOutgoingApproval) bool {
|
||||
for _, approval := range stepApprovals {
|
||||
@ -582,6 +609,39 @@ func (p *LetterOutgoingProcessorImpl) activateNextStep(ctx context.Context, lett
|
||||
return nil
|
||||
}
|
||||
|
||||
// activateNextStepForRevision activates the next approval step for a specific revision
|
||||
func (p *LetterOutgoingProcessorImpl) activateNextStepForRevision(ctx context.Context, letterID uuid.UUID, currentStepOrder int, revisionNumber int, approvalsByStep map[int][]entities.LetterOutgoingApproval) error {
|
||||
nextStepOrder := currentStepOrder + 1
|
||||
nextStepApprovals, exists := approvalsByStep[nextStepOrder]
|
||||
if !exists {
|
||||
return nil // No next step
|
||||
}
|
||||
|
||||
// Get existing recipients to avoid duplicates
|
||||
existingUserIDs, err := p.getExistingRecipientUserIDs(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process each approval in the next step (already filtered by revision in approvalsByStep)
|
||||
for _, nextApproval := range nextStepApprovals {
|
||||
// Only process if it's the same revision
|
||||
if nextApproval.RevisionNumber == revisionNumber {
|
||||
// Activate approval if not started
|
||||
if err := p.activateApprovalIfNotStarted(ctx, &nextApproval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add approver as recipient if not already exists
|
||||
if err := p.addApproverAsRecipientIfNeeded(ctx, letterID, nextApproval.ApproverID, existingUserIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getExistingRecipientUserIDs gets a set of existing recipient user IDs
|
||||
func (p *LetterOutgoingProcessorImpl) getExistingRecipientUserIDs(ctx context.Context, letterID uuid.UUID) (map[uuid.UUID]bool, error) {
|
||||
currentRecipients, err := p.recipientRepo.ListByLetter(ctx, letterID)
|
||||
@ -665,12 +725,39 @@ func (p *LetterOutgoingProcessorImpl) ProcessRejection(ctx context.Context, lett
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark all other pending approvals in the same revision as rejected
|
||||
allApprovals, err := p.approvalRepo.ListByLetter(txCtx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range allApprovals {
|
||||
// Only update other pending approvals from the same revision
|
||||
if allApprovals[i].RevisionNumber == approval.RevisionNumber &&
|
||||
allApprovals[i].ID != approval.ID &&
|
||||
allApprovals[i].Status == entities.ApprovalStatusPending {
|
||||
allApprovals[i].Status = entities.ApprovalStatusRejected
|
||||
if err := p.approvalRepo.Update(txCtx, &allApprovals[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusRejected); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromStatus := string(entities.LetterOutgoingStatusPendingApproval)
|
||||
toStatus := string(entities.LetterOutgoingStatusRejected)
|
||||
|
||||
// Include rejection remarks in activity log context
|
||||
var context entities.JSONB
|
||||
if approval.Remarks != nil && *approval.Remarks != "" {
|
||||
context = entities.JSONB{"remarks": *approval.Remarks, "revision_number": approval.RevisionNumber}
|
||||
} else {
|
||||
context = entities.JSONB{"revision_number": approval.RevisionNumber}
|
||||
}
|
||||
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionRejected,
|
||||
@ -678,6 +765,79 @@ func (p *LetterOutgoingProcessorImpl) ProcessRejection(ctx context.Context, lett
|
||||
TargetID: &approval.ID,
|
||||
FromStatus: &fromStatus,
|
||||
ToStatus: &toStatus,
|
||||
Context: context,
|
||||
}
|
||||
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) ProcessRevision(ctx context.Context, letterID uuid.UUID, attachment entities.LetterOutgoingAttachment, userID uuid.UUID) error {
|
||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
// Get the current letter
|
||||
letter, err := p.letterRepo.Get(txCtx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Increment revision number
|
||||
letter.RevisionNumber++
|
||||
|
||||
// Set revision number on the new attachment
|
||||
attachment.RevisionNumber = letter.RevisionNumber
|
||||
|
||||
// Add the new attachment
|
||||
if err := p.attachmentRepo.Create(txCtx, &attachment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update letter with new revision number
|
||||
if err := p.letterRepo.Update(txCtx, letter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update status to pending approval (ready for re-submission)
|
||||
if err := p.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get existing approvals for the current revision
|
||||
approvals, err := p.approvalRepo.ListByLetter(txCtx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create new approval records for the new revision
|
||||
for _, approval := range approvals {
|
||||
// Create a new approval for the new revision
|
||||
newApproval := entities.LetterOutgoingApproval{
|
||||
LetterID: approval.LetterID,
|
||||
StepID: approval.StepID,
|
||||
RevisionNumber: letter.RevisionNumber,
|
||||
StepOrder: approval.StepOrder,
|
||||
ParallelGroup: approval.ParallelGroup,
|
||||
IsRequired: approval.IsRequired,
|
||||
Status: entities.ApprovalStatusPending,
|
||||
}
|
||||
if err := p.approvalRepo.Create(txCtx, &newApproval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Log activity
|
||||
fromStatus := string(entities.LetterOutgoingStatusRejected)
|
||||
toStatus := string(entities.LetterOutgoingStatusPendingApproval)
|
||||
activityLog := &entities.LetterOutgoingActivityLog{
|
||||
LetterID: letterID,
|
||||
ActionType: entities.LetterOutgoingActionRevised,
|
||||
ActorUserID: &userID,
|
||||
TargetID: &attachment.ID,
|
||||
FromStatus: &fromStatus,
|
||||
ToStatus: &toStatus,
|
||||
Context: entities.JSONB{"attachment": attachment.FileName, "revision_number": letter.RevisionNumber},
|
||||
}
|
||||
if err := p.activityLogRepo.Create(txCtx, activityLog); err != nil {
|
||||
return err
|
||||
@ -732,6 +892,17 @@ func (p *LetterOutgoingProcessorImpl) RemoveRecipient(ctx context.Context, lette
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, attachments []entities.LetterOutgoingAttachment, userID uuid.UUID) error {
|
||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
// Get the letter to get the current revision number
|
||||
letter, err := p.letterRepo.Get(txCtx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set revision number on all attachments
|
||||
for i := range attachments {
|
||||
attachments[i].RevisionNumber = letter.RevisionNumber
|
||||
}
|
||||
|
||||
if err := p.attachmentRepo.CreateBulk(txCtx, attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -808,7 +979,31 @@ func (p *LetterOutgoingProcessorImpl) DeleteDiscussion(ctx context.Context, id u
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) GetApprovalsByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error) {
|
||||
return p.approvalRepo.ListByLetter(ctx, letterID)
|
||||
// Get the letter first to know the current revision
|
||||
letter, err := p.letterRepo.Get(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) GetApprovalsByLetterAndRevision(ctx context.Context, letterID uuid.UUID, revisionNumber int) ([]entities.LetterOutgoingApproval, error) {
|
||||
// Get all approvals for this letter
|
||||
approvals, err := p.approvalRepo.ListByLetter(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter to only return approvals for the specified revision
|
||||
var currentRevisionApprovals []entities.LetterOutgoingApproval
|
||||
for _, approval := range approvals {
|
||||
if approval.RevisionNumber == revisionNumber {
|
||||
currentRevisionApprovals = append(currentRevisionApprovals, approval)
|
||||
}
|
||||
}
|
||||
|
||||
return currentRevisionApprovals, nil
|
||||
}
|
||||
|
||||
func (p *LetterOutgoingProcessorImpl) GetApprovalFlow(ctx context.Context, flowID uuid.UUID) (*entities.ApprovalFlow, error) {
|
||||
|
||||
@ -106,6 +106,7 @@ type LetterOutgoingHandler interface {
|
||||
SubmitForApproval(c *gin.Context)
|
||||
ApproveOutgoingLetter(c *gin.Context)
|
||||
RejectOutgoingLetter(c *gin.Context)
|
||||
ReviseOutgoingLetter(c *gin.Context)
|
||||
SendOutgoingLetter(c *gin.Context)
|
||||
ArchiveOutgoingLetter(c *gin.Context)
|
||||
GetLetterApprovalInfo(c *gin.Context)
|
||||
|
||||
@ -193,6 +193,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
lettersch.POST("/outgoing/:id/submit", r.letterOutgoingHandler.SubmitForApproval)
|
||||
lettersch.POST("/outgoing/:id/approve", r.letterOutgoingHandler.ApproveOutgoingLetter)
|
||||
lettersch.POST("/outgoing/:id/reject", r.letterOutgoingHandler.RejectOutgoingLetter)
|
||||
lettersch.POST("/outgoing/:id/revise", r.letterOutgoingHandler.ReviseOutgoingLetter)
|
||||
lettersch.POST("/outgoing/:id/send", r.letterOutgoingHandler.SendOutgoingLetter)
|
||||
lettersch.POST("/outgoing/:id/archive", r.letterOutgoingHandler.ArchiveOutgoingLetter)
|
||||
lettersch.POST("/outgoing/archive", r.letterOutgoingHandler.BulkArchiveOutgoingLetters)
|
||||
|
||||
@ -628,7 +628,8 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
||||
// Get approvals for the current revision only
|
||||
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -651,15 +652,7 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
|
||||
|
||||
currentApproval.Remarks = req.Remarks
|
||||
|
||||
allApproved := true
|
||||
for _, approval := range approvals {
|
||||
if approval.ID != currentApproval.ID && approval.Status == entities.ApprovalStatusPending {
|
||||
allApproved = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = s.processor.ProcessApproval(ctx, letterID, currentApproval, userID, allApproved)
|
||||
err = s.processor.ProcessApproval(ctx, letterID, currentApproval, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -691,7 +684,8 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
||||
// Get approvals for the current revision only
|
||||
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -728,6 +722,35 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) ReviseOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ReviseLetterRequest) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Can only revise rejected letters
|
||||
if letter.Status != entities.LetterOutgoingStatusRejected {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
attachment := entities.LetterOutgoingAttachment{
|
||||
LetterID: letterID,
|
||||
FileURL: req.FileURL,
|
||||
FileName: req.FileName,
|
||||
FileType: req.FileType,
|
||||
UploadedBy: &userID,
|
||||
}
|
||||
|
||||
err = s.processor.ProcessRevision(ctx, letterID, attachment, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
|
||||
userID := getUserIDFromContext(ctx)
|
||||
|
||||
@ -968,8 +991,8 @@ func (s *LetterOutgoingServiceImpl) GetLetterApprovalInfo(ctx context.Context, l
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all approvals for this letter
|
||||
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
||||
// Get all approvals for this letter's current revision
|
||||
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1030,6 +1053,15 @@ func (s *LetterOutgoingServiceImpl) GetLetterApprovalInfo(ctx context.Context, l
|
||||
}
|
||||
}
|
||||
|
||||
// Add REVISE action if letter is rejected and user is the creator
|
||||
if letter.Status == entities.LetterOutgoingStatusRejected && letter.CreatedBy == userID {
|
||||
actions = append(actions, contract.ApprovalAction{
|
||||
Type: "REVISE",
|
||||
Href: fmt.Sprintf("/api/v1/letters/outgoing/%s/revise", letterID),
|
||||
Method: "POST",
|
||||
})
|
||||
}
|
||||
|
||||
// Determine overall decision status
|
||||
decisionStatus := "PENDING"
|
||||
|
||||
@ -1080,8 +1112,8 @@ func (s *LetterOutgoingServiceImpl) GetLetterApprovals(ctx context.Context, lett
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all approvals for this letter
|
||||
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
||||
// Get all approvals for this letter's current revision
|
||||
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1435,6 +1467,7 @@ func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.Outgoi
|
||||
IssueDate: letter.IssueDate,
|
||||
Status: string(letter.Status),
|
||||
ApprovalFlowID: letter.ApprovalFlowID,
|
||||
RevisionNumber: letter.RevisionNumber,
|
||||
CreatedBy: letter.CreatedBy,
|
||||
CreatedAt: letter.CreatedAt,
|
||||
UpdatedAt: letter.UpdatedAt,
|
||||
@ -1778,7 +1811,15 @@ func (s *LetterOutgoingServiceImpl) BulkArchiveOutgoingLetters(ctx context.Conte
|
||||
func (s *LetterOutgoingServiceImpl) sendStepApprovalNotifications(ctx context.Context, letterID uuid.UUID, subject string, stepOrder int) {
|
||||
log.Printf("[DEBUG] sendStepApprovalNotifications START - LetterID: %s, StepOrder: %d", letterID.String(), stepOrder)
|
||||
|
||||
approvals, err := s.processor.GetApprovalsByLetter(ctx, letterID)
|
||||
// Get the letter to know the current revision
|
||||
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to get letter: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get approvals for the current revision only
|
||||
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to get approvals: %v", err)
|
||||
return
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
DROP INDEX IF EXISTS idx_letters_outgoing_revision_number;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
DROP COLUMN IF EXISTS revision_number;
|
||||
|
||||
COMMIT;
|
||||
@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE letters_outgoing
|
||||
ADD COLUMN IF NOT EXISTS revision_number INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_revision_number ON letters_outgoing(revision_number);
|
||||
|
||||
COMMIT;
|
||||
@ -0,0 +1,14 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_attachments_revision;
|
||||
DROP INDEX IF EXISTS idx_letter_outgoing_approvals_revision;
|
||||
|
||||
-- Remove revision_number from tables
|
||||
ALTER TABLE letter_outgoing_attachments
|
||||
DROP COLUMN IF EXISTS revision_number;
|
||||
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
DROP COLUMN IF EXISTS revision_number;
|
||||
|
||||
COMMIT;
|
||||
@ -0,0 +1,15 @@
|
||||
BEGIN;
|
||||
|
||||
-- Add revision_number to letter_outgoing_attachments
|
||||
ALTER TABLE letter_outgoing_attachments
|
||||
ADD COLUMN IF NOT EXISTS revision_number INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Add revision_number to letter_outgoing_approvals
|
||||
ALTER TABLE letter_outgoing_approvals
|
||||
ADD COLUMN IF NOT EXISTS revision_number INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Create indexes for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_attachments_revision ON letter_outgoing_attachments(letter_id, revision_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_approvals_revision ON letter_outgoing_approvals(letter_id, revision_number);
|
||||
|
||||
COMMIT;
|
||||
Loading…
x
Reference in New Issue
Block a user