Update group approvals

This commit is contained in:
Aditya Siregar
2025-10-20 01:10:48 +07:00
parent 92aba06d67
commit e082d4fce5
3 changed files with 218 additions and 96 deletions
+113 -83
View File
@@ -634,15 +634,14 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
return err
}
// Find user's pending approval
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 approvals[i].Status == entities.ApprovalStatusPending &&
approvals[i].ApproverID != nil &&
*approvals[i].ApproverID == userID {
currentApproval = &approvals[i]
break
}
}
@@ -659,14 +658,21 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
// 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)
// Get next parallel group to determine notification message
nextParallelGroup := s.getNextParallelGroup(approvals, currentApproval.ParallelGroup)
if nextParallelGroup > 0 {
// Notify creator about group completion AND next group approvers
creatorMessage := fmt.Sprintf("Surat keluar '%s' telah disetujui pada grup %d, menunggu persetujuan grup berikutnya", letter.Subject, currentApproval.ParallelGroup)
go s.sendApprovalNotificationToCreator(context.Background(), letterID, letter.CreatedBy, "Surat Keluar Disetujui Grup "+fmt.Sprintf("%d", currentApproval.ParallelGroup), creatorMessage)
// Notify next parallel group approvers
go s.sendParallelGroupApprovalNotifications(context.Background(), letterID, letter.Subject, nextParallelGroup)
} else {
// All groups completed
creatorMessage := fmt.Sprintf("Surat keluar '%s' telah selesai disetujui", letter.Subject)
go s.sendApprovalNotificationToCreator(context.Background(), letterID, letter.CreatedBy, "Surat Keluar Disetujui", creatorMessage)
}
}
return nil
@@ -690,15 +696,14 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
return err
}
// Find user's pending approval
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 approvals[i].Status == entities.ApprovalStatusPending &&
approvals[i].ApproverID != nil &&
*approvals[i].ApproverID == userID {
currentApproval = &approvals[i]
break
}
}
@@ -715,7 +720,7 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
// 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)
message := fmt.Sprintf("Surat keluar '%s' ditolak pada grup %d dengan alasan: %s", letter.Subject, currentApproval.ParallelGroup, req.Reason)
go s.sendApprovalNotificationToCreator(context.Background(), letterID, letter.CreatedBy, "Surat Keluar Ditolak", message)
}
@@ -997,69 +1002,20 @@ func (s *LetterOutgoingServiceImpl) GetLetterApprovalInfo(ctx context.Context, l
return nil, err
}
// Group approvals by step order and parallel group to understand the workflow
approvalsByStepAndGroup := make(map[int]map[int][]entities.LetterOutgoingApproval)
for _, approval := range approvals {
if approvalsByStepAndGroup[approval.StepOrder] == nil {
approvalsByStepAndGroup[approval.StepOrder] = make(map[int][]entities.LetterOutgoingApproval)
}
approvalsByStepAndGroup[approval.StepOrder][approval.ParallelGroup] = append(
approvalsByStepAndGroup[approval.StepOrder][approval.ParallelGroup],
approval,
)
}
// Find the current active step (lowest step order with pending approvals)
var currentStepOrder int = -1
var userApproval *entities.LetterOutgoingApproval
// Simple check: can user approve if they have a pending approval
var canApprove bool
var userApproval *entities.LetterOutgoingApproval
// Find the minimum step order that has pending approvals
for stepOrder, groupApprovals := range approvalsByStepAndGroup {
stepHasPending := false
// Check each parallel group in this step
for _, groupMembers := range groupApprovals {
groupHasPending := false
var userApprovalInGroup *entities.LetterOutgoingApproval
// Check if this group has pending approvals and if user is in this group
for i := range groupMembers {
approval := groupMembers[i]
if approval.Status == entities.ApprovalStatusPending {
groupHasPending = true
stepHasPending = true
// Check if this user is an approver in this group
if approval.ApproverID != nil && *approval.ApproverID == userID {
userApprovalInGroup = &approval
}
}
}
// If this is the earliest step with pending approvals and user is in a pending group
if groupHasPending && userApprovalInGroup != nil {
if currentStepOrder == -1 || stepOrder < currentStepOrder {
currentStepOrder = stepOrder
userApproval = userApprovalInGroup
// User can approve if they're in a parallel group with pending approvals at the current active step
canApprove = true
} else if stepOrder == currentStepOrder {
// Same step order - user can still approve if in parallel group
userApproval = userApprovalInGroup
canApprove = true
}
}
}
// Track the lowest step with pending approvals
if stepHasPending && (currentStepOrder == -1 || stepOrder < currentStepOrder) {
currentStepOrder = stepOrder
// Reset canApprove if we found a lower step and user is not in it
if userApproval == nil || userApproval.StepOrder != stepOrder {
canApprove = false
userApproval = nil
}
for i := range approvals {
approval := approvals[i]
// Check if this approval is pending and belongs to the current user
if approval.Status == entities.ApprovalStatusPending &&
approval.ApproverID != nil &&
*approval.ApproverID == userID {
canApprove = true
userApproval = &approval
break
}
}
@@ -1933,6 +1889,80 @@ func (s *LetterOutgoingServiceImpl) sendApprovalNotificationToCreator(ctx contex
}
}
// Helper function to get the next parallel group number (handles non-sequential groups)
func (s *LetterOutgoingServiceImpl) getNextParallelGroup(approvals []entities.LetterOutgoingApproval, currentGroup int) int {
// Collect all unique parallel groups
groupsMap := make(map[int]bool)
for _, approval := range approvals {
groupsMap[approval.ParallelGroup] = true
}
// Convert to sorted slice
var groups []int
for group := range groupsMap {
groups = append(groups, group)
}
sort.Ints(groups)
// Find the next group after current
for _, group := range groups {
if group > currentGroup {
// Check if this group has any pending approvals
for _, approval := range approvals {
if approval.ParallelGroup == group && approval.Status == entities.ApprovalStatusNotStarted {
return group
}
}
}
}
return 0 // No next group
}
// Send notifications to approvers in a specific parallel group
func (s *LetterOutgoingServiceImpl) sendParallelGroupApprovalNotifications(ctx context.Context, letterID uuid.UUID, subject string, parallelGroup int) {
log.Printf("[DEBUG] sendParallelGroupApprovalNotifications START - LetterID: %s, ParallelGroup: %d", letterID.String(), parallelGroup)
// 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
}
log.Printf("[DEBUG] Found %d approvals", len(approvals))
// Find approvers for the specified parallel group
for _, approval := range approvals {
log.Printf("[DEBUG] Checking approval: ParallelGroup=%d, Status=%s, ApproverID=%v",
approval.ParallelGroup, approval.Status, approval.ApproverID)
if approval.ParallelGroup == parallelGroup && approval.ApproverID != nil {
log.Printf("[DEBUG] Sending notification to approver %s for parallel group %d", approval.ApproverID.String(), parallelGroup)
err := s.notificationProcessor.SendOutgoingLetterNotification(
ctx,
letterID,
*approval.ApproverID,
"Surat Keluar Perlu Persetujuan",
fmt.Sprintf("Surat keluar '%s' memerlukan persetujuan Anda pada grup %d", subject, parallelGroup))
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())
}
}
}
}
func (s *LetterOutgoingServiceImpl) sendOutgoingDiscussionMentionNotifications(ctx context.Context, letterID uuid.UUID, senderUserID uuid.UUID, mentions map[string]interface{}, message string) {
log.Printf("[DEBUG] sendOutgoingDiscussionMentionNotifications START - LetterID: %s", letterID.String())