surat keluar approval

This commit is contained in:
efrilm 2025-09-21 14:39:02 +07:00
parent acb9f75a18
commit 646af36795
3 changed files with 125 additions and 2 deletions

View File

@ -401,6 +401,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
processors.letterApprovalProcessor, processors.letterApprovalProcessor,
processors.letterAttachmentProcessor, processors.letterAttachmentProcessor,
processors.letterOutgoingRecipientProcessor, processors.letterOutgoingRecipientProcessor,
processors.notificationProcessor,
processors.letterActivityProcessor, processors.letterActivityProcessor,
) )

View File

@ -23,6 +23,7 @@ type NotificationProcessor interface {
// Letter notifications // Letter notifications
SendIncomingLetterNotification(ctx context.Context, letterID uuid.UUID, recipientUserID uuid.UUID, subject string, body string) error SendIncomingLetterNotification(ctx context.Context, letterID uuid.UUID, recipientUserID uuid.UUID, subject string, body string) error
SendOutgoingLetterNotification(ctx context.Context, letterID uuid.UUID, recipientUserID uuid.UUID, subject string, body string) error
} }
type NotificationProcessorImpl struct { type NotificationProcessorImpl struct {
@ -359,3 +360,30 @@ func (p *NovuProvider) SendNotification(ctx context.Context, payload Notificatio
return nil return nil
} }
func (p *NotificationProcessorImpl) SendOutgoingLetterNotification(ctx context.Context, letterID uuid.UUID, recipientUserID uuid.UUID, subject string, body string) error {
// Ensure subscriber exists
if err := p.provider.EnsureSubscriberExists(ctx, recipientUserID); err != nil {
return fmt.Errorf("failed to ensure subscriber exists: %w", err)
}
// Build notification URL for outgoing letters
url := fmt.Sprintf("/en/apps/surat-menyurat/keluar-detail/%s", letterID.String())
// Use workflow ID from config (defaults to "notification-dashbpard")
workflowID := p.workflowID
if workflowID == "" {
workflowID = "notification-dashbpard"
}
// Send notification
return p.provider.SendNotification(ctx, NotificationPayload{
RecipientID: recipientUserID,
EventName: workflowID,
Data: map[string]interface{}{
"subject": subject,
"body": body,
"url": url,
},
})
}

View File

@ -3,6 +3,7 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"sort" "sort"
"time" "time"
@ -60,6 +61,7 @@ type LetterOutgoingServiceImpl struct {
approvalProcessor processor.LetterApprovalProcessor approvalProcessor processor.LetterApprovalProcessor
attachmentProcessor processor.LetterAttachmentProcessor attachmentProcessor processor.LetterAttachmentProcessor
recipientProcessor processor.LetterOutgoingRecipientProcessor recipientProcessor processor.LetterOutgoingRecipientProcessor
notificationProcessor processor.NotificationProcessor
activityProcessor processor.LetterActivityProcessor activityProcessor processor.LetterActivityProcessor
} }
@ -71,6 +73,7 @@ func NewLetterOutgoingService(
approvalProcessor processor.LetterApprovalProcessor, approvalProcessor processor.LetterApprovalProcessor,
attachmentProcessor processor.LetterAttachmentProcessor, attachmentProcessor processor.LetterAttachmentProcessor,
recipientProcessor processor.LetterOutgoingRecipientProcessor, recipientProcessor processor.LetterOutgoingRecipientProcessor,
notificationProcessor processor.NotificationProcessor,
activityProcessor processor.LetterActivityProcessor, activityProcessor processor.LetterActivityProcessor,
) *LetterOutgoingServiceImpl { ) *LetterOutgoingServiceImpl {
return &LetterOutgoingServiceImpl{ return &LetterOutgoingServiceImpl{
@ -81,6 +84,7 @@ func NewLetterOutgoingService(
approvalProcessor: approvalProcessor, approvalProcessor: approvalProcessor,
attachmentProcessor: attachmentProcessor, attachmentProcessor: attachmentProcessor,
recipientProcessor: recipientProcessor, recipientProcessor: recipientProcessor,
notificationProcessor: notificationProcessor,
activityProcessor: activityProcessor, activityProcessor: activityProcessor,
} }
} }
@ -168,6 +172,15 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
return nil, err return nil, err
} }
// Send notifications if letter needs approval
log.Printf("[DEBUG] createOutgoingLetter Finsig")
log.Printf("[DEBUG] NotificationProcessor is nil: %v", s.notificationProcessor == nil)
if s.notificationProcessor != nil && len(result.Approvals) > 0 {
log.Printf("[DEBUG] sendFirstStepApprovalNotifications start")
go s.sendStepApprovalNotifications(context.Background(), result.ID, result.Subject, 1)
}
return transformLetterToResponse(result), nil return transformLetterToResponse(result), nil
} }
@ -457,7 +470,24 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
} }
} }
return s.processor.ProcessApproval(ctx, letterID, currentApproval, userID, allApproved) err = s.processor.ProcessApproval(ctx, letterID, currentApproval, userID, allApproved)
if err != nil {
return err
}
// Send notifications after successful approval
if s.notificationProcessor != nil {
// Step approved but not final - notify creator about step completion AND next approvers
creatorMessage := fmt.Sprintf("Surat keluar '%s' telah disetujui pada tahap %d, menunggu persetujuan tahap berikutnya", letter.Subject, currentApproval.StepOrder)
go s.sendApprovalNotificationToCreator(context.Background(), letterID, letter.CreatedBy, "Surat Keluar Disetujui Tahap " + fmt.Sprintf("%d", currentApproval.StepOrder), creatorMessage)
// Notify next step approvers
nextStepOrder := currentApproval.StepOrder + 1
go s.sendStepApprovalNotifications(context.Background(), letterID, letter.Subject, nextStepOrder)
}
return nil
} }
func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error { func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error {
@ -495,7 +525,18 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
currentApproval.Remarks = &req.Reason currentApproval.Remarks = &req.Reason
return s.processor.ProcessRejection(ctx, letterID, currentApproval, userID) err = s.processor.ProcessRejection(ctx, letterID, currentApproval, userID)
if err != nil {
return err
}
// Send notification to letter creator (rejection always notifies creator)
if s.notificationProcessor != nil {
message := fmt.Sprintf("Surat keluar '%s' ditolak pada tahap %d dengan alasan: %s", letter.Subject, currentApproval.StepOrder, req.Reason)
go s.sendApprovalNotificationToCreator(context.Background(), letterID, letter.CreatedBy, "Surat Keluar Ditolak", message)
}
return nil
} }
func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error { func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
@ -1539,3 +1580,56 @@ func (s *LetterOutgoingServiceImpl) BulkArchiveOutgoingLetters(ctx context.Conte
ArchivedCount: int(archivedCount), ArchivedCount: int(archivedCount),
}, nil }, nil
} }
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)
if err != nil {
log.Printf("[ERROR] Failed to get approvals: %v", err)
return
}
log.Printf("[DEBUG] Found %d approvals", len(approvals))
// Find approvers for the specified step
for _, approval := range approvals {
log.Printf("[DEBUG] Checking approval: Step=%d, Status=%s, ApproverID=%v",
approval.StepOrder, approval.Status, approval.ApproverID)
if approval.StepOrder == stepOrder {
log.Printf("[DEBUG] Sending notification to approver %s for step %d", approval.ApproverID.String(), stepOrder)
err := s.notificationProcessor.SendOutgoingLetterNotification(
ctx,
letterID,
*approval.ApproverID,
"Surat Keluar Perlu Persetujuan",
fmt.Sprintf("Surat keluar '%s' memerlukan persetujuan Anda pada tahap %d", subject, stepOrder))
if err != nil {
log.Printf("[ERROR] Failed to send notification to approver %s: %v", approval.ApproverID.String(), err)
} else {
log.Printf("[DEBUG] Successfully sent notification to approver %s", approval.ApproverID.String())
}
}
}
}
// Kirim notifikasi ke creator
func (s *LetterOutgoingServiceImpl) sendApprovalNotificationToCreator(ctx context.Context, letterID uuid.UUID, creatorID uuid.UUID, title string, message string) {
log.Printf("[DEBUG] sendApprovalNotificationToCreator START - LetterID: %s, CreatorID: %s", letterID.String(), creatorID.String())
err := s.notificationProcessor.SendOutgoingLetterNotification(
ctx,
letterID,
creatorID,
title,
message)
if err != nil {
log.Printf("[ERROR] Failed to send notification to creator %s: %v", creatorID.String(), err)
} else {
log.Printf("[DEBUG] Successfully sent notification to creator %s", creatorID.String())
}
}