outgoing letter is read

This commit is contained in:
efrilm 2025-10-08 11:52:11 +07:00
parent d8942fa918
commit e7455f107a
5 changed files with 80 additions and 4 deletions

View File

@ -110,6 +110,7 @@ type OutgoingLetterResponse struct {
CreatedBy uuid.UUID `json:"created_by"` CreatedBy uuid.UUID `json:"created_by"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
IsRead bool `json:"is_read"`
Recipients []OutgoingLetterRecipientResponse `json:"recipients,omitempty"` Recipients []OutgoingLetterRecipientResponse `json:"recipients,omitempty"`
Attachments []OutgoingLetterAttachmentResponse `json:"attachments,omitempty"` Attachments []OutgoingLetterAttachmentResponse `json:"attachments,omitempty"`
Approvals []OutgoingLetterApprovalResponse `json:"approvals,omitempty"` Approvals []OutgoingLetterApprovalResponse `json:"approvals,omitempty"`
@ -139,6 +140,7 @@ type ListOutgoingLettersRequest struct {
SortBy string `form:"sort_by" json:"sort_by,omitempty"` SortBy string `form:"sort_by" json:"sort_by,omitempty"`
SortOrder string `form:"sort_order" json:"sort_order,omitempty"` SortOrder string `form:"sort_order" json:"sort_order,omitempty"`
IsArchived *bool `form:"is_archived" json:"is_archived,omitempty"` IsArchived *bool `form:"is_archived" json:"is_archived,omitempty"`
IsRead *bool `form:"is_read,omitempty"`
} }
type ListOutgoingLettersResponse struct { type ListOutgoingLettersResponse struct {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"eslogad-be/internal/appcontext" "eslogad-be/internal/appcontext"
"eslogad-be/internal/contract" "eslogad-be/internal/contract"
"fmt"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -101,6 +102,8 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) {
req.Limit = 10 req.Limit = 10
} }
fmt.Printf("[DEBUG] request: %v\n", req)
resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), &req) resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), &req)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})

View File

@ -51,6 +51,8 @@ type LetterOutgoingProcessor interface {
GetBatchRecipients(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingRecipient, error) 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) 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) 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 { type LetterOutgoingProcessorImpl struct {
@ -806,6 +808,13 @@ func (p *LetterOutgoingProcessorImpl) GetBatchRecipients(ctx context.Context, le
return p.recipientRepo.ListByLetterIDs(ctx, letterIDs) 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 // 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) { func (p *LetterOutgoingProcessorImpl) GetBatchPriorities(ctx context.Context, priorityIDs []uuid.UUID) (map[uuid.UUID]*entities.Priority, error) {
if p.priorityRepo == nil || len(priorityIDs) == 0 { if p.priorityRepo == nil || len(priorityIDs) == 0 {

View File

@ -2,6 +2,7 @@ package repository
import ( import (
"context" "context"
"fmt"
"time" "time"
"eslogad-be/internal/entities" "eslogad-be/internal/entities"
@ -93,6 +94,7 @@ type ListOutgoingLettersFilter struct {
SortBy *string SortBy *string
SortOrder *string SortOrder *string
IsArchived *bool IsArchived *bool
IsRead *bool
} }
func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error) { 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 // Filter by UserID through recipients
if filter.UserID != nil { if filter.UserID != nil {
query = query.Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id"). 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). query = query.Where("letter_outgoing_recipients.user_id = ?", *filter.UserID)
Distinct()
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 { if filter.ReceiverInstitutionID != nil {
query = query.Where("receiver_institution_id = ?", *filter.ReceiverInstitutionID) query = query.Where("receiver_institution_id = ?", *filter.ReceiverInstitutionID)
} }
@ -485,6 +502,27 @@ func (r *LetterOutgoingDiscussionRepository) ListByLetter(ctx context.Context, l
return list, nil 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 { func (r *LetterOutgoingDiscussionRepository) Update(ctx context.Context, e *entities.LetterOutgoingDiscussion) error {
db := DBFromContext(ctx, r.db) db := DBFromContext(ctx, r.db)
now := time.Now() now := time.Now()

View File

@ -212,6 +212,7 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req
ReceiverInstitutionID: req.ReceiverInstitutionID, ReceiverInstitutionID: req.ReceiverInstitutionID,
PriorityID: req.PriorityID, PriorityID: req.PriorityID,
UserID: &userID, UserID: &userID,
IsRead: req.IsRead,
} }
if departmentID != uuid.Nil { if departmentID != uuid.Nil {
@ -250,6 +251,12 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req
filter.IsArchived = req.IsArchived filter.IsArchived = req.IsArchived
} }
if filter.IsRead != nil {
filter.IsRead = req.IsRead
}
fmt.Printf("[DEBUG] filter: %v\n", filter)
// Get raw letters data // Get raw letters data
letters, total, err := s.processor.ListOutgoingLetters(ctx, filter, req.Limit, offset) letters, total, err := s.processor.ListOutgoingLetters(ctx, filter, req.Limit, offset)
if err != nil { if err != nil {
@ -294,6 +301,7 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req
result := batchResult{} result := batchResult{}
errChan := make(chan error, 4) errChan := make(chan error, 4)
// Load attachments // Load attachments
go func() { go func() {
result.attachments, err = s.processor.GetBatchAttachments(ctx, letterIDs) result.attachments, err = s.processor.GetBatchAttachments(ctx, letterIDs)
@ -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{ return &contract.ListOutgoingLettersResponse{