update unique letter number
This commit is contained in:
parent
0c3e1db502
commit
88ad35c5d0
@ -17,6 +17,7 @@ import (
|
|||||||
type LetterOutgoingProcessor interface {
|
type LetterOutgoingProcessor interface {
|
||||||
CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error
|
CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error
|
||||||
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error)
|
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error)
|
||||||
|
GetOutgoingLetterByReferenceNumber(ctx context.Context, referenceNumber *string) (*entities.LetterOutgoing, error)
|
||||||
ListOutgoingLetters(ctx context.Context, filter repository.ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error)
|
ListOutgoingLetters(ctx context.Context, filter repository.ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error)
|
||||||
SearchOutgoingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error)
|
SearchOutgoingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error)
|
||||||
UpdateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, userID uuid.UUID) error
|
UpdateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, userID uuid.UUID) error
|
||||||
@ -110,6 +111,11 @@ func NewLetterOutgoingProcessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *LetterOutgoingProcessorImpl) CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error {
|
func (p *LetterOutgoingProcessorImpl) CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error {
|
||||||
|
existingOutgoing, err := p.letterRepo.GetByReferenceNumber(ctx, letter.ReferenceNumber)
|
||||||
|
if err == nil && existingOutgoing != nil {
|
||||||
|
return fmt.Errorf("surat dengan nomor %s sudah ada", *letter.ReferenceNumber)
|
||||||
|
}
|
||||||
|
|
||||||
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
// Step 1: Assign approval flow from department if not provided
|
// Step 1: Assign approval flow from department if not provided
|
||||||
if err := p.assignApprovalFlowFromDepartment(txCtx, letter, departmentID); err != nil {
|
if err := p.assignApprovalFlowFromDepartment(txCtx, letter, departmentID); err != nil {
|
||||||
@ -148,6 +154,10 @@ func (p *LetterOutgoingProcessorImpl) CreateOutgoingLetter(ctx context.Context,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *LetterOutgoingProcessorImpl) GetOutgoingLetterByReferenceNumber(ctx context.Context, referenceNumber *string) (*entities.LetterOutgoing, error) {
|
||||||
|
return p.letterRepo.GetByReferenceNumber(ctx, referenceNumber)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *LetterOutgoingProcessorImpl) assignApprovalFlowFromDepartment(ctx context.Context, letter *entities.LetterOutgoing, departmentID uuid.UUID) error {
|
func (p *LetterOutgoingProcessorImpl) assignApprovalFlowFromDepartment(ctx context.Context, letter *entities.LetterOutgoing, departmentID uuid.UUID) error {
|
||||||
if letter.ApprovalFlowID != nil || departmentID == uuid.Nil {
|
if letter.ApprovalFlowID != nil || departmentID == uuid.Nil {
|
||||||
return nil
|
return nil
|
||||||
@ -598,17 +608,17 @@ func (p *LetterOutgoingProcessorImpl) isStepCompleted(stepApprovals []entities.L
|
|||||||
approvalsByParallelGroup := make(map[int][]entities.LetterOutgoingApproval)
|
approvalsByParallelGroup := make(map[int][]entities.LetterOutgoingApproval)
|
||||||
for _, approval := range stepApprovals {
|
for _, approval := range stepApprovals {
|
||||||
approvalsByParallelGroup[approval.ParallelGroup] = append(
|
approvalsByParallelGroup[approval.ParallelGroup] = append(
|
||||||
approvalsByParallelGroup[approval.ParallelGroup],
|
approvalsByParallelGroup[approval.ParallelGroup],
|
||||||
approval,
|
approval,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check each parallel group
|
// Check each parallel group
|
||||||
for _, groupApprovals := range approvalsByParallelGroup {
|
for _, groupApprovals := range approvalsByParallelGroup {
|
||||||
// For each parallel group, check if it has at least one approved
|
// For each parallel group, check if it has at least one approved
|
||||||
groupHasApproval := false
|
groupHasApproval := false
|
||||||
hasRequiredPending := false
|
hasRequiredPending := false
|
||||||
|
|
||||||
for _, approval := range groupApprovals {
|
for _, approval := range groupApprovals {
|
||||||
if approval.Status == entities.ApprovalStatusApproved {
|
if approval.Status == entities.ApprovalStatusApproved {
|
||||||
groupHasApproval = true
|
groupHasApproval = true
|
||||||
@ -617,12 +627,12 @@ func (p *LetterOutgoingProcessorImpl) isStepCompleted(stepApprovals []entities.L
|
|||||||
hasRequiredPending = true
|
hasRequiredPending = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this group has required approvals that are still pending, step is not complete
|
// If this group has required approvals that are still pending, step is not complete
|
||||||
if hasRequiredPending {
|
if hasRequiredPending {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this group has no approvals at all and contains required approvals, step is not complete
|
// If this group has no approvals at all and contains required approvals, step is not complete
|
||||||
if !groupHasApproval {
|
if !groupHasApproval {
|
||||||
for _, approval := range groupApprovals {
|
for _, approval := range groupApprovals {
|
||||||
@ -632,7 +642,7 @@ func (p *LetterOutgoingProcessorImpl) isStepCompleted(stepApprovals []entities.L
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -778,7 +788,7 @@ func (p *LetterOutgoingProcessorImpl) activateNextParallelGroupForRevision(ctx c
|
|||||||
groups = append(groups, group)
|
groups = append(groups, group)
|
||||||
}
|
}
|
||||||
sort.Ints(groups)
|
sort.Ints(groups)
|
||||||
|
|
||||||
var nextGroup int = -1
|
var nextGroup int = -1
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
if group > currentGroup {
|
if group > currentGroup {
|
||||||
@ -786,22 +796,22 @@ func (p *LetterOutgoingProcessorImpl) activateNextParallelGroupForRevision(ctx c
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if nextGroup == -1 {
|
if nextGroup == -1 {
|
||||||
return nil // No next group
|
return nil // No next group
|
||||||
}
|
}
|
||||||
|
|
||||||
nextGroupApprovals, exists := approvalsByGroup[nextGroup]
|
nextGroupApprovals, exists := approvalsByGroup[nextGroup]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get existing recipients to avoid duplicates
|
// Get existing recipients to avoid duplicates
|
||||||
existingUserIDs, err := p.getExistingRecipientUserIDs(ctx, letterID)
|
existingUserIDs, err := p.getExistingRecipientUserIDs(ctx, letterID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each approval in the next group
|
// Process each approval in the next group
|
||||||
for _, nextApproval := range nextGroupApprovals {
|
for _, nextApproval := range nextGroupApprovals {
|
||||||
// Only process if it's the same revision
|
// Only process if it's the same revision
|
||||||
@ -810,14 +820,14 @@ func (p *LetterOutgoingProcessorImpl) activateNextParallelGroupForRevision(ctx c
|
|||||||
if err := p.activateApprovalIfNotStarted(ctx, &nextApproval); err != nil {
|
if err := p.activateApprovalIfNotStarted(ctx, &nextApproval); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add approver as recipient if not already exists
|
// Add approver as recipient if not already exists
|
||||||
if err := p.addApproverAsRecipientIfNeeded(ctx, letterID, nextApproval.ApproverID, existingUserIDs); err != nil {
|
if err := p.addApproverAsRecipientIfNeeded(ctx, letterID, nextApproval.ApproverID, existingUserIDs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,11 @@ func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachR
|
|||||||
func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
||||||
userID := appcontext.FromGinContext(ctx).UserID
|
userID := appcontext.FromGinContext(ctx).UserID
|
||||||
|
|
||||||
|
existingIncoming, err := p.letterRepo.GetByReferenceNumber(ctx, req.ReferenceNumber)
|
||||||
|
if err == nil && existingIncoming != nil {
|
||||||
|
return nil, fmt.Errorf("surat dengan nomor %s sudah ada", *req.ReferenceNumber)
|
||||||
|
}
|
||||||
|
|
||||||
letterType := entities.LetterIncomingTypeUtama
|
letterType := entities.LetterIncomingTypeUtama
|
||||||
if req.Type == "TEMBUSAN" {
|
if req.Type == "TEMBUSAN" {
|
||||||
letterType = entities.LetterIncomingTypeTembusan
|
letterType = entities.LetterIncomingTypeTembusan
|
||||||
|
|||||||
@ -41,6 +41,25 @@ func (r *LetterOutgoingRepository) Get(ctx context.Context, id uuid.UUID) (*enti
|
|||||||
return &e, nil
|
return &e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LetterOutgoingRepository) GetByReferenceNumber(ctx context.Context, refNumber *string) (*entities.LetterOutgoing, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var e entities.LetterOutgoing
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Preload("Priority").
|
||||||
|
Preload("ReceiverInstitution").
|
||||||
|
Preload("Creator").
|
||||||
|
Preload("ApprovalFlow").
|
||||||
|
Preload("Recipients").
|
||||||
|
Preload("Attachments").
|
||||||
|
Preload("Approvals.Step").
|
||||||
|
Preload("Approvals.Approver").
|
||||||
|
Where("reference_number = ? AND deleted_at IS NULL", refNumber).
|
||||||
|
First(&e).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LetterOutgoingRepository) Update(ctx context.Context, e *entities.LetterOutgoing) error {
|
func (r *LetterOutgoingRepository) Update(ctx context.Context, e *entities.LetterOutgoing) error {
|
||||||
db := DBFromContext(ctx, r.db)
|
db := DBFromContext(ctx, r.db)
|
||||||
return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", e.ID).Updates(e).Error
|
return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", e.ID).Updates(e).Error
|
||||||
|
|||||||
@ -29,6 +29,17 @@ func (r *LetterIncomingRepository) Get(ctx context.Context, id uuid.UUID) (*enti
|
|||||||
return &e, nil
|
return &e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingRepository) GetByReferenceNumber(ctx context.Context, refNumber *string) (*entities.LetterIncoming, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var e entities.LetterIncoming
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Where("reference_number = ? AND deleted_at IS NULL", refNumber).
|
||||||
|
First(&e).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LetterIncomingRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.LetterIncoming, error) {
|
func (r *LetterIncomingRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.LetterIncoming, error) {
|
||||||
return r.Get(ctx, id)
|
return r.Get(ctx, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,6 +94,12 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
|
|||||||
departmentID := getDepartmentIDFromContext(ctx)
|
departmentID := getDepartmentIDFromContext(ctx)
|
||||||
userID := getUserIDFromContext(ctx)
|
userID := getUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
existingOutgoing, err := s.processor.GetOutgoingLetterByReferenceNumber(ctx, req.ReferenceNumber)
|
||||||
|
|
||||||
|
if err == nil && existingOutgoing != nil {
|
||||||
|
return nil, fmt.Errorf("surat dengan nomor %s sudah ada", *req.ReferenceNumber)
|
||||||
|
}
|
||||||
|
|
||||||
// Create letter entity
|
// Create letter entity
|
||||||
letter := &entities.LetterOutgoing{
|
letter := &entities.LetterOutgoing{
|
||||||
Subject: req.Subject,
|
Subject: req.Subject,
|
||||||
@ -125,7 +131,7 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute creation with transaction in service layer
|
// Execute creation with transaction in service layer
|
||||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
// Step 1: Validate letter
|
// Step 1: Validate letter
|
||||||
if err := s.validationProcessor.ValidateCreateOutgoingLetter(txCtx, letter); err != nil {
|
if err := s.validationProcessor.ValidateCreateOutgoingLetter(txCtx, letter); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -660,7 +666,7 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
|
|||||||
if s.notificationProcessor != nil {
|
if s.notificationProcessor != nil {
|
||||||
// Get next parallel group to determine notification message
|
// Get next parallel group to determine notification message
|
||||||
nextParallelGroup := s.getNextParallelGroup(approvals, currentApproval.ParallelGroup)
|
nextParallelGroup := s.getNextParallelGroup(approvals, currentApproval.ParallelGroup)
|
||||||
|
|
||||||
if nextParallelGroup > 0 {
|
if nextParallelGroup > 0 {
|
||||||
// Notify creator about group completion AND next group approvers
|
// 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)
|
creatorMessage := fmt.Sprintf("Surat keluar '%s' telah disetujui pada grup %d, menunggu persetujuan grup berikutnya", letter.Subject, currentApproval.ParallelGroup)
|
||||||
@ -1896,14 +1902,14 @@ func (s *LetterOutgoingServiceImpl) getNextParallelGroup(approvals []entities.Le
|
|||||||
for _, approval := range approvals {
|
for _, approval := range approvals {
|
||||||
groupsMap[approval.ParallelGroup] = true
|
groupsMap[approval.ParallelGroup] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to sorted slice
|
// Convert to sorted slice
|
||||||
var groups []int
|
var groups []int
|
||||||
for group := range groupsMap {
|
for group := range groupsMap {
|
||||||
groups = append(groups, group)
|
groups = append(groups, group)
|
||||||
}
|
}
|
||||||
sort.Ints(groups)
|
sort.Ints(groups)
|
||||||
|
|
||||||
// Find the next group after current
|
// Find the next group after current
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
if group > currentGroup {
|
if group > currentGroup {
|
||||||
@ -1915,45 +1921,45 @@ func (s *LetterOutgoingServiceImpl) getNextParallelGroup(approvals []entities.Le
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0 // No next group
|
return 0 // No next group
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notifications to approvers in a specific parallel group
|
// Send notifications to approvers in a specific parallel group
|
||||||
func (s *LetterOutgoingServiceImpl) sendParallelGroupApprovalNotifications(ctx context.Context, letterID uuid.UUID, subject string, parallelGroup int) {
|
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)
|
log.Printf("[DEBUG] sendParallelGroupApprovalNotifications START - LetterID: %s, ParallelGroup: %d", letterID.String(), parallelGroup)
|
||||||
|
|
||||||
// Get the letter to know the current revision
|
// Get the letter to know the current revision
|
||||||
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
letter, err := s.processor.GetOutgoingLetterByID(ctx, letterID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to get letter: %v", err)
|
log.Printf("[ERROR] Failed to get letter: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get approvals for the current revision only
|
// Get approvals for the current revision only
|
||||||
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
approvals, err := s.processor.GetApprovalsByLetterAndRevision(ctx, letterID, letter.RevisionNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to get approvals: %v", err)
|
log.Printf("[ERROR] Failed to get approvals: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Found %d approvals", len(approvals))
|
log.Printf("[DEBUG] Found %d approvals", len(approvals))
|
||||||
|
|
||||||
// Find approvers for the specified parallel group
|
// Find approvers for the specified parallel group
|
||||||
for _, approval := range approvals {
|
for _, approval := range approvals {
|
||||||
log.Printf("[DEBUG] Checking approval: ParallelGroup=%d, Status=%s, ApproverID=%v",
|
log.Printf("[DEBUG] Checking approval: ParallelGroup=%d, Status=%s, ApproverID=%v",
|
||||||
approval.ParallelGroup, approval.Status, approval.ApproverID)
|
approval.ParallelGroup, approval.Status, approval.ApproverID)
|
||||||
|
|
||||||
if approval.ParallelGroup == parallelGroup && approval.ApproverID != nil {
|
if approval.ParallelGroup == parallelGroup && approval.ApproverID != nil {
|
||||||
log.Printf("[DEBUG] Sending notification to approver %s for parallel group %d", approval.ApproverID.String(), parallelGroup)
|
log.Printf("[DEBUG] Sending notification to approver %s for parallel group %d", approval.ApproverID.String(), parallelGroup)
|
||||||
|
|
||||||
err := s.notificationProcessor.SendOutgoingLetterNotification(
|
err := s.notificationProcessor.SendOutgoingLetterNotification(
|
||||||
ctx,
|
ctx,
|
||||||
letterID,
|
letterID,
|
||||||
*approval.ApproverID,
|
*approval.ApproverID,
|
||||||
"Surat Keluar Perlu Persetujuan",
|
"Surat Keluar Perlu Persetujuan",
|
||||||
fmt.Sprintf("Surat keluar '%s' memerlukan persetujuan Anda pada grup %d", subject, parallelGroup))
|
fmt.Sprintf("Surat keluar '%s' memerlukan persetujuan Anda pada grup %d", subject, parallelGroup))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to send notification to approver %s: %v", approval.ApproverID.String(), err)
|
log.Printf("[ERROR] Failed to send notification to approver %s: %v", approval.ApproverID.String(), err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user