From e7455f107ad03f73b1faae18f030d012cdd5b7ee Mon Sep 17 00:00:00 2001 From: efrilm Date: Wed, 8 Oct 2025 11:52:11 +0700 Subject: [PATCH] outgoing letter is read --- internal/contract/letter_outgoing_contract.go | 2 + internal/handler/letter_outgoing_handler.go | 3 ++ .../processor/letter_outgoing_processor.go | 9 ++++ .../repository/letter_outgoing_repository.go | 44 +++++++++++++++++-- internal/service/letter_outgoing_service.go | 26 ++++++++++- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/internal/contract/letter_outgoing_contract.go b/internal/contract/letter_outgoing_contract.go index 00f5bf7..d50dd8f 100644 --- a/internal/contract/letter_outgoing_contract.go +++ b/internal/contract/letter_outgoing_contract.go @@ -110,6 +110,7 @@ type OutgoingLetterResponse struct { CreatedBy uuid.UUID `json:"created_by"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` + IsRead bool `json:"is_read"` Recipients []OutgoingLetterRecipientResponse `json:"recipients,omitempty"` Attachments []OutgoingLetterAttachmentResponse `json:"attachments,omitempty"` Approvals []OutgoingLetterApprovalResponse `json:"approvals,omitempty"` @@ -139,6 +140,7 @@ type ListOutgoingLettersRequest struct { SortBy string `form:"sort_by" json:"sort_by,omitempty"` SortOrder string `form:"sort_order" json:"sort_order,omitempty"` IsArchived *bool `form:"is_archived" json:"is_archived,omitempty"` + IsRead *bool `form:"is_read,omitempty"` } type ListOutgoingLettersResponse struct { diff --git a/internal/handler/letter_outgoing_handler.go b/internal/handler/letter_outgoing_handler.go index d3e61bd..3015a61 100644 --- a/internal/handler/letter_outgoing_handler.go +++ b/internal/handler/letter_outgoing_handler.go @@ -4,6 +4,7 @@ import ( "context" "eslogad-be/internal/appcontext" "eslogad-be/internal/contract" + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -101,6 +102,8 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) { req.Limit = 10 } + fmt.Printf("[DEBUG] request: %v\n", req) + resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) diff --git a/internal/processor/letter_outgoing_processor.go b/internal/processor/letter_outgoing_processor.go index 469dede..c11755a 100644 --- a/internal/processor/letter_outgoing_processor.go +++ b/internal/processor/letter_outgoing_processor.go @@ -51,6 +51,8 @@ type LetterOutgoingProcessor interface { GetBatchRecipients(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingRecipient, error) GetBatchPriorities(ctx context.Context, priorityIDs []uuid.UUID) (map[uuid.UUID]*entities.Priority, error) GetBatchInstitutions(ctx context.Context, institutionIDs []uuid.UUID) (map[uuid.UUID]*entities.Institution, error) + GetBatchOutgoingRecipientsByUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterOutgoingRecipient, error) + } type LetterOutgoingProcessorImpl struct { @@ -806,6 +808,13 @@ func (p *LetterOutgoingProcessorImpl) GetBatchRecipients(ctx context.Context, le return p.recipientRepo.ListByLetterIDs(ctx, letterIDs) } +func (p *LetterOutgoingProcessorImpl) GetBatchOutgoingRecipientsByUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterOutgoingRecipient, error) { + if p.recipientRepo == nil || len(letterIDs) == 0 { + return make(map[uuid.UUID]*entities.LetterOutgoingRecipient), nil + } + return p.recipientRepo.GetByLetterIDsAndUser(ctx, letterIDs, userID) +} + // GetBatchPriorities fetches priorities by IDs in a single query func (p *LetterOutgoingProcessorImpl) GetBatchPriorities(ctx context.Context, priorityIDs []uuid.UUID) (map[uuid.UUID]*entities.Priority, error) { if p.priorityRepo == nil || len(priorityIDs) == 0 { diff --git a/internal/repository/letter_outgoing_repository.go b/internal/repository/letter_outgoing_repository.go index a974998..6813893 100644 --- a/internal/repository/letter_outgoing_repository.go +++ b/internal/repository/letter_outgoing_repository.go @@ -2,6 +2,7 @@ package repository import ( "context" + "fmt" "time" "eslogad-be/internal/entities" @@ -93,6 +94,7 @@ type ListOutgoingLettersFilter struct { SortBy *string SortOrder *string IsArchived *bool + IsRead *bool } func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error) { @@ -120,10 +122,25 @@ func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoing } // Filter by UserID through recipients if filter.UserID != nil { - query = query.Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id"). - Where("letter_outgoing_recipients.user_id = ?", *filter.UserID). - Distinct() + query = query.Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id") + query = query.Where("letter_outgoing_recipients.user_id = ?", *filter.UserID) + + fmt.Printf("[DEBUG] filter.UserID: %v\n", filter.UserID) + fmt.Printf("[DEBUG] filter.isRead: %v\n", filter.IsRead) + + // Tambahkan filter IsRead + if filter.IsRead != nil { + if *filter.IsRead { + query = query.Where("letter_outgoing_recipients.read_at IS NOT NULL") + } else { + query = query.Where("letter_outgoing_recipients.read_at IS NULL") + } + } + + query = query.Distinct() } + + if filter.ReceiverInstitutionID != nil { query = query.Where("receiver_institution_id = ?", *filter.ReceiverInstitutionID) } @@ -485,6 +502,27 @@ func (r *LetterOutgoingDiscussionRepository) ListByLetter(ctx context.Context, l return list, nil } +func (r *LetterOutgoingRecipientRepository) GetByLetterIDsAndUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterOutgoingRecipient, error) { + if len(letterIDs) == 0 { + return make(map[uuid.UUID]*entities.LetterOutgoingRecipient), nil + } + + db := DBFromContext(ctx, r.db) + var recipients []entities.LetterOutgoingRecipient + if err := db.WithContext(ctx). + Where("letter_id IN ? AND user_id = ?", letterIDs, userID). + Find(&recipients).Error; err != nil { + return nil, err + } + + result := make(map[uuid.UUID]*entities.LetterOutgoingRecipient) + for i := range recipients { + result[recipients[i].LetterID] = &recipients[i] + } + + return result, nil +} + func (r *LetterOutgoingDiscussionRepository) Update(ctx context.Context, e *entities.LetterOutgoingDiscussion) error { db := DBFromContext(ctx, r.db) now := time.Now() diff --git a/internal/service/letter_outgoing_service.go b/internal/service/letter_outgoing_service.go index c964a16..c5e01c6 100644 --- a/internal/service/letter_outgoing_service.go +++ b/internal/service/letter_outgoing_service.go @@ -212,6 +212,7 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req ReceiverInstitutionID: req.ReceiverInstitutionID, PriorityID: req.PriorityID, UserID: &userID, + IsRead: req.IsRead, } if departmentID != uuid.Nil { @@ -250,6 +251,12 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req filter.IsArchived = req.IsArchived } + if filter.IsRead != nil { + filter.IsRead = req.IsRead + } + + fmt.Printf("[DEBUG] filter: %v\n", filter) + // Get raw letters data letters, total, err := s.processor.ListOutgoingLetters(ctx, filter, req.Limit, offset) if err != nil { @@ -293,6 +300,7 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req result := batchResult{} errChan := make(chan error, 4) + // Load attachments go func() { @@ -346,7 +354,23 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req } } - items[i] = transformLetterToResponse(&letter) + isRead := false + recipientByUser := make(map[uuid.UUID]*entities.LetterOutgoingRecipient) + recipientByUser, err = s.processor.GetBatchOutgoingRecipientsByUser(ctx, letterIDs, userID) + if err != nil { + // Handle error + return nil, err + } + + // Ambil isRead dari recipientByUser berdasarkan letter.ID + if recipient, exists := recipientByUser[letter.ID]; exists && recipient != nil { + isRead = recipient.ReadAt != nil + } + + + response := transformLetterToResponse(&letter) + response.IsRead = isRead + items[i] = response } return &contract.ListOutgoingLettersResponse{