package repository import ( "context" "fmt" "time" "eslogad-be/internal/entities" "github.com/google/uuid" "gorm.io/gorm" ) type LetterOutgoingRepository struct{ db *gorm.DB } func NewLetterOutgoingRepository(db *gorm.DB) *LetterOutgoingRepository { return &LetterOutgoingRepository{db: db} } func (r *LetterOutgoingRepository) Create(ctx context.Context, e *entities.LetterOutgoing) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Create(e).Error } func (r *LetterOutgoingRepository) Get(ctx context.Context, id uuid.UUID) (*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("id = ? AND deleted_at IS NULL", id). First(&e).Error; err != nil { return nil, err } 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 { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", e.ID).Updates(e).Error } func (r *LetterOutgoingRepository) SoftDelete(ctx context.Context, id uuid.UUID) error { db := DBFromContext(ctx, r.db) now := time.Now() return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("deleted_at", now).Error } func (r *LetterOutgoingRepository) BulkSoftDelete(ctx context.Context, ids []uuid.UUID) error { if len(ids) == 0 { return nil } db := DBFromContext(ctx, r.db) now := time.Now() return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id IN ? AND deleted_at IS NULL", ids).Update("deleted_at", now).Error } func (r *LetterOutgoingRepository) BulkArchive(ctx context.Context, letterIDs []uuid.UUID) (int64, error) { db := DBFromContext(ctx, r.db) now := time.Now() result := db.WithContext(ctx). Model(&entities.LetterOutgoing{}). Where("id IN ? AND deleted_at IS NULL", letterIDs). Updates(map[string]interface{}{ "is_archived": true, "archived_at": now, }) return result.RowsAffected, result.Error } func (r *LetterOutgoingRepository) Archive(ctx context.Context, letterID uuid.UUID) error { db := DBFromContext(ctx, r.db) now := time.Now() return db.WithContext(ctx). Model(&entities.LetterOutgoing{}). Where("id = ? AND deleted_at IS NULL", letterID). Updates(map[string]interface{}{ "is_archived": true, "archived_at": now, }).Error } func (r *LetterOutgoingRepository) BulkArchiveForUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (int64, error) { db := DBFromContext(ctx, r.db) // Archive only the recipient records for the specific user // Note: letter_incoming_recipients uses recipient_user_id column result := db.WithContext(ctx). Model(&entities.LetterOutgoingRecipient{}). Where("letter_id IN ? AND user_id = ?", letterIDs, userID). Update("is_archived", true) return result.RowsAffected, result.Error } func (r *LetterOutgoingRepository) GetWithRelations(ctx context.Context, id uuid.UUID, relations []string) (*entities.LetterOutgoing, error) { db := DBFromContext(ctx, r.db) query := db.WithContext(ctx).Where("id = ? AND deleted_at IS NULL", id) // Preload all specified relations for _, relation := range relations { query = query.Preload(relation) } var e entities.LetterOutgoing if err := query.First(&e).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, gorm.ErrRecordNotFound } return nil, err } return &e, nil } type ListOutgoingLettersFilter struct { Status *string Query *string CreatedBy *uuid.UUID DepartmentID *uuid.UUID UserID *uuid.UUID ReceiverInstitutionID *uuid.UUID FromDate *time.Time ToDate *time.Time PriorityID *uuid.UUID PriorityIDs []uuid.UUID SortBy *string SortOrder *string IsArchived *bool IsRead *bool } func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error) { db := DBFromContext(ctx, r.db) query := db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("deleted_at IS NULL") // Apply is_archived filter if filter.IsArchived != nil { if *filter.IsArchived { query = query.Where("letter_outgoing_recipients.is_archived = ?", true) } else { query = query.Where("letter_outgoing_recipients.is_archived = ? OR letter_outgoing_recipients.is_archived IS NULL", false) } } if filter.Query != nil { q := "%" + *filter.Query + "%" query = query.Where("subject ILIKE ? OR reference_number ILIKE ? OR letter_number ILIKE ?", q, q, q) } if filter.CreatedBy != nil { query = query.Where("created_by = ?", *filter.CreatedBy) } // 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") 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") } } } if filter.Status != nil { query = query.Joins("LEFT JOIN letter_outgoing_approvals ON letter_outgoing_approvals.letter_id = letters_outgoing.id") query = query.Where("letter_outgoing_approvals.approver_id = ?", *filter.UserID) query = query.Where("letter_outgoing_approvals.status = ?", *filter.Status) query = query.Distinct() } if filter.ReceiverInstitutionID != nil { query = query.Where("receiver_institution_id = ?", *filter.ReceiverInstitutionID) } if filter.PriorityID != nil { query = query.Where("priority_id = ?", *filter.PriorityID) } if len(filter.PriorityIDs) > 0 { query = query.Where("priority_id IN ?", filter.PriorityIDs) } fmt.Printf("Priority %s", filter.PriorityIDs) if filter.FromDate != nil { query = query.Where("issue_date >= ?", *filter.FromDate) } if filter.ToDate != nil { query = query.Where("issue_date <= ?", *filter.ToDate) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } orderBy := "created_at DESC" // default if filter.SortBy != nil { sortField := *filter.SortBy sortDirection := "ASC" if filter.SortOrder != nil && (*filter.SortOrder == "desc" || *filter.SortOrder == "DESC") { sortDirection = "DESC" } switch sortField { case "letter_number": orderBy = "letter_number " + sortDirection case "subject": orderBy = "subject " + sortDirection case "issue_date": orderBy = "issue_date " + sortDirection case "status": orderBy = "status " + sortDirection case "created_at": orderBy = "created_at " + sortDirection default: orderBy = "created_at " + sortDirection } } var list []entities.LetterOutgoing if err := query. Preload("Priority"). Preload("ReceiverInstitution"). Preload("Creator"). Preload("Creator.Profile"). Preload("Creator.Departments"). Preload("Recipients"). Preload("Recipients.User"). Preload("Recipients.Department"). Preload("Attachments"). Preload("Approvals.Step"). Preload("Approvals.Approver"). Order(orderBy). Limit(limit). Offset(offset). Find(&list).Error; err != nil { return nil, 0, err } return list, total, nil } func (r *LetterOutgoingRepository) ListAll(ctx context.Context, filter ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error) { db := DBFromContext(ctx, r.db) query := db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("deleted_at IS NULL") // Apply search query filter if filter.Query != nil { q := "%" + *filter.Query + "%" query = query.Where("subject ILIKE ? OR reference_number ILIKE ? OR letter_number ILIKE ?", q, q, q) } // Filter by creator (if admin wants to see letters from specific creator) if filter.CreatedBy != nil { query = query.Where("created_by = ?", *filter.CreatedBy) } // Filter by receiver institution if filter.ReceiverInstitutionID != nil { query = query.Where("receiver_institution_id = ?", *filter.ReceiverInstitutionID) } // Filter by priority if filter.PriorityID != nil { query = query.Where("priority_id = ?", *filter.PriorityID) } // Filter by multiple priorities if len(filter.PriorityIDs) > 0 { query = query.Where("priority_id IN ?", filter.PriorityIDs) } // Date range filters if filter.FromDate != nil { query = query.Where("issue_date >= ?", *filter.FromDate) } if filter.ToDate != nil { query = query.Where("issue_date <= ?", *filter.ToDate) } // Filter by approval status (if admin wants to see letters with specific approval status) // Note: This is different from user-specific approval status if filter.Status != nil { query = query.Joins("LEFT JOIN letter_outgoing_approvals ON letter_outgoing_approvals.letter_id = letters_outgoing.id"). Where("letter_outgoing_approvals.status = ?", *filter.Status). Distinct() } // Get total count var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } // Prepare sorting orderBy := "created_at DESC" // default if filter.SortBy != nil { sortField := *filter.SortBy sortDirection := "ASC" if filter.SortOrder != nil && (*filter.SortOrder == "desc" || *filter.SortOrder == "DESC") { sortDirection = "DESC" } switch sortField { case "letter_number": orderBy = "letter_number " + sortDirection case "subject": orderBy = "subject " + sortDirection case "issue_date": orderBy = "issue_date " + sortDirection case "status": orderBy = "status " + sortDirection case "created_at": orderBy = "created_at " + sortDirection default: orderBy = "created_at " + sortDirection } } // Get paginated data with all relations var list []entities.LetterOutgoing if err := query. Preload("Priority"). Preload("ReceiverInstitution"). Preload("Creator"). Preload("Creator.Profile"). Preload("Creator.Departments"). Preload("Recipients"). Preload("Recipients.User"). Preload("Recipients.Department"). Preload("Attachments"). Preload("Approvals"). Preload("Approvals.Step"). Preload("Approvals.Approver"). Order(orderBy). Limit(limit). Offset(offset). Find(&list).Error; err != nil { return nil, 0, err } return list, total, nil } func (r *LetterOutgoingRepository) Search(ctx context.Context, filters map[string]interface{}, limit, offset int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error) { db := DBFromContext(ctx, r.db) query := db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("deleted_at IS NULL") // Apply search filters if q, ok := filters["query"]; ok && q != "" { searchTerm := "%" + q.(string) + "%" query = query.Where("subject ILIKE ? OR reference_number ILIKE ? OR letter_number ILIKE ? OR description ILIKE ? OR receiver_name ILIKE ?", searchTerm, searchTerm, searchTerm, searchTerm, searchTerm) } if letterNumber, ok := filters["letter_number"]; ok && letterNumber != "" { query = query.Where("letter_number ILIKE ?", "%"+letterNumber.(string)+"%") } if subject, ok := filters["subject"]; ok && subject != "" { query = query.Where("subject ILIKE ?", "%"+subject.(string)+"%") } if status, ok := filters["status"]; ok && status != "" { query = query.Where("status = ?", status) } if priorityID, ok := filters["priority_id"]; ok { query = query.Where("priority_id = ?", priorityID) } if institutionID, ok := filters["receiver_institution_id"]; ok { query = query.Where("receiver_institution_id = ?", institutionID) } if createdBy, ok := filters["created_by"]; ok { query = query.Where("created_by = ?", createdBy) } if dateFrom, ok := filters["date_from"]; ok { query = query.Where("issue_date >= ?", dateFrom) } if dateTo, ok := filters["date_to"]; ok { query = query.Where("issue_date <= ?", dateTo) } // Apply user context filters if present if userContext, ok := filters["user_context"]; ok { if ctx, ok := userContext.(map[string]interface{}); ok { if userID, ok := ctx["user_id"]; ok { // User can see: letters created by them OR letters where they are recipients subQuery := db.Model(&entities.LetterOutgoingRecipient{}).Select("letter_id").Where("user_id = ?", userID) query = query.Where("created_by = ? OR id IN (?)", userID, subQuery) } } } // Count total results var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } // Apply sorting if sortBy == "" { sortBy = "created_at" } if sortOrder == "" { sortOrder = "desc" } validSortFields := map[string]bool{ "letter_number": true, "subject": true, "issue_date": true, "status": true, "created_at": true, "updated_at": true, } if !validSortFields[sortBy] { sortBy = "created_at" } if sortOrder != "asc" && sortOrder != "desc" { sortOrder = "desc" } orderBy := sortBy + " " + sortOrder // Execute query with preloads var letters []entities.LetterOutgoing if err := query. Preload("Priority"). Preload("ReceiverInstitution"). Preload("Creator"). Preload("Creator.Profile"). Order(orderBy). Limit(limit). Offset(offset). Find(&letters).Error; err != nil { return nil, 0, err } return letters, total, nil } func (r *LetterOutgoingRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.LetterOutgoingStatus) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("status", status).Error } type LetterOutgoingAttachmentRepository struct{ db *gorm.DB } func NewLetterOutgoingAttachmentRepository(db *gorm.DB) *LetterOutgoingAttachmentRepository { return &LetterOutgoingAttachmentRepository{db: db} } func (r *LetterOutgoingAttachmentRepository) Create(ctx context.Context, e *entities.LetterOutgoingAttachment) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Create(e).Error } func (r *LetterOutgoingAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingAttachment) error { db := DBFromContext(ctx, r.db) if len(list) == 0 { return nil } return db.WithContext(ctx).Create(&list).Error } func (r *LetterOutgoingAttachmentRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingAttachment, error) { db := DBFromContext(ctx, r.db) var list []entities.LetterOutgoingAttachment if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("uploaded_at ASC").Find(&list).Error; err != nil { return nil, err } return list, nil } func (r *LetterOutgoingAttachmentRepository) Delete(ctx context.Context, id uuid.UUID) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingAttachment{}).Error } // ListByLetterIDs fetches attachments for multiple letters in a single query func (r *LetterOutgoingAttachmentRepository) ListByLetterIDs(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingAttachment, error) { if len(letterIDs) == 0 { return make(map[uuid.UUID][]entities.LetterOutgoingAttachment), nil } db := DBFromContext(ctx, r.db) var attachments []entities.LetterOutgoingAttachment if err := db.WithContext(ctx).Where("letter_id IN ?", letterIDs).Order("uploaded_at ASC").Find(&attachments).Error; err != nil { return nil, err } // Group attachments by letter ID result := make(map[uuid.UUID][]entities.LetterOutgoingAttachment) for _, att := range attachments { result[att.LetterID] = append(result[att.LetterID], att) } return result, nil } type LetterOutgoingFinalAttachmentRepository struct{ db *gorm.DB } func NewLetterOutgoingFinalAttachmentRepository(db *gorm.DB) *LetterOutgoingFinalAttachmentRepository { return &LetterOutgoingFinalAttachmentRepository{db: db} } func (r *LetterOutgoingFinalAttachmentRepository) Create(ctx context.Context, e *entities.LetterOutgoingFinalAttachment) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Create(e).Error } func (r *LetterOutgoingFinalAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingFinalAttachment) error { db := DBFromContext(ctx, r.db) if len(list) == 0 { return nil } return db.WithContext(ctx).Create(&list).Error } func (r *LetterOutgoingFinalAttachmentRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingFinalAttachment, error) { db := DBFromContext(ctx, r.db) var list []entities.LetterOutgoingFinalAttachment if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("uploaded_at ASC").Find(&list).Error; err != nil { return nil, err } return list, nil } func (r *LetterOutgoingFinalAttachmentRepository) Delete(ctx context.Context, id uuid.UUID) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingAttachment{}).Error } // ListByLetterIDs fetches attachments for multiple letters in a single query func (r *LetterOutgoingFinalAttachmentRepository) ListByLetterIDs(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingFinalAttachment, error) { if len(letterIDs) == 0 { return make(map[uuid.UUID][]entities.LetterOutgoingFinalAttachment), nil } db := DBFromContext(ctx, r.db) var attachments []entities.LetterOutgoingFinalAttachment if err := db.WithContext(ctx).Where("letter_id IN ?", letterIDs).Order("uploaded_at ASC").Find(&attachments).Error; err != nil { return nil, err } // Group attachments by letter ID result := make(map[uuid.UUID][]entities.LetterOutgoingFinalAttachment) for _, att := range attachments { result[att.LetterID] = append(result[att.LetterID], att) } return result, nil } type LetterOutgoingRecipientRepository struct{ db *gorm.DB } func NewLetterOutgoingRecipientRepository(db *gorm.DB) *LetterOutgoingRecipientRepository { return &LetterOutgoingRecipientRepository{db: db} } func (r *LetterOutgoingRecipientRepository) Create(ctx context.Context, e *entities.LetterOutgoingRecipient) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Create(e).Error } func (r *LetterOutgoingRecipientRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingRecipient) error { db := DBFromContext(ctx, r.db) if len(list) == 0 { return nil } return db.WithContext(ctx).Create(&list).Error } func (r *LetterOutgoingRecipientRepository) CountUnreadByUser(ctx context.Context, userID uuid.UUID) (int, error) { db := DBFromContext(ctx, r.db) var count int64 sql := ` WITH valid_recipients AS ( SELECT lor.id, lor.letter_id, lor.read_at, lor.created_at, ROW_NUMBER() OVER (PARTITION BY lor.letter_id ORDER BY lor.created_at DESC) as rn FROM letter_outgoing_recipients lor INNER JOIN letters_outgoing l ON l.id = lor.letter_id AND l.deleted_at IS NULL WHERE lor.user_id = ? ) SELECT COUNT(*) FROM valid_recipients WHERE rn = 1 AND read_at IS NULL ` if err := db.WithContext(ctx).Raw(sql, userID).Scan(&count).Error; err != nil { return 0, err } return int(count), nil } func (r *LetterOutgoingRecipientRepository) MarkAsRead(ctx context.Context, letterID, userID uuid.UUID) error { db := DBFromContext(ctx, r.db) now := time.Now() return db.WithContext(ctx). Model(&entities.LetterOutgoingRecipient{}). Where("letter_id = ? AND user_id = ?", letterID, userID). Update("read_at", now).Error } func (r *LetterOutgoingRecipientRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingRecipient, error) { db := DBFromContext(ctx, r.db) var list []entities.LetterOutgoingRecipient if err := db.WithContext(ctx). Preload("User"). Preload("Department"). Where("letter_id = ?", letterID). Order("is_primary DESC, created_at ASC"). Find(&list).Error; err != nil { return nil, err } return list, nil } func (r *LetterOutgoingRecipientRepository) Update(ctx context.Context, e *entities.LetterOutgoingRecipient) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Model(&entities.LetterOutgoingRecipient{}).Where("id = ?", e.ID).Updates(e).Error } func (r *LetterOutgoingRecipientRepository) Delete(ctx context.Context, id uuid.UUID) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingRecipient{}).Error } func (r *LetterOutgoingRecipientRepository) DeleteByLetter(ctx context.Context, letterID uuid.UUID) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Where("letter_id = ?", letterID).Delete(&entities.LetterOutgoingRecipient{}).Error } // ListByLetterIDs fetches recipients for multiple letters in a single query func (r *LetterOutgoingRecipientRepository) ListByLetterIDs(ctx context.Context, letterIDs []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). Preload("User"). Preload("User.Profile"). Preload("Department"). Where("letter_id IN ?", letterIDs). Order("is_primary DESC, created_at ASC"). Find(&recipients).Error; err != nil { return nil, err } // Group recipients by letter ID result := make(map[uuid.UUID][]entities.LetterOutgoingRecipient) for _, rec := range recipients { result[rec.LetterID] = append(result[rec.LetterID], rec) } return result, nil } type LetterOutgoingDiscussionRepository struct{ db *gorm.DB } func NewLetterOutgoingDiscussionRepository(db *gorm.DB) *LetterOutgoingDiscussionRepository { return &LetterOutgoingDiscussionRepository{db: db} } func (r *LetterOutgoingDiscussionRepository) Create(ctx context.Context, e *entities.LetterOutgoingDiscussion) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Create(e).Error } func (r *LetterOutgoingDiscussionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoingDiscussion, error) { db := DBFromContext(ctx, r.db) var e entities.LetterOutgoingDiscussion if err := db.WithContext(ctx). Preload("User"). Preload("Attachments"). Where("id = ?", id). First(&e).Error; err != nil { return nil, err } return &e, nil } func (r *LetterOutgoingDiscussionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingDiscussion, error) { db := DBFromContext(ctx, r.db) var list []entities.LetterOutgoingDiscussion if err := db.WithContext(ctx). Preload("User"). Preload("Attachments"). Preload("Replies.User"). Where("letter_id = ? AND parent_id IS NULL", letterID). Order("created_at DESC"). Find(&list).Error; err != nil { return nil, err } 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(`id IN ( SELECT id FROM ( SELECT id, ROW_NUMBER() OVER (PARTITION BY letter_id ORDER BY created_at DESC) as rn FROM letter_outgoing_recipients WHERE letter_id IN ? AND user_id = ? ) t WHERE rn = 1 )`, 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() e.EditedAt = &now return db.WithContext(ctx).Model(&entities.LetterOutgoingDiscussion{}).Where("id = ?", e.ID).Updates(e).Error } func (r *LetterOutgoingDiscussionRepository) Delete(ctx context.Context, id uuid.UUID) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingDiscussion{}).Error } type LetterOutgoingDiscussionAttachmentRepository struct{ db *gorm.DB } func NewLetterOutgoingDiscussionAttachmentRepository(db *gorm.DB) *LetterOutgoingDiscussionAttachmentRepository { return &LetterOutgoingDiscussionAttachmentRepository{db: db} } func (r *LetterOutgoingDiscussionAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingDiscussionAttachment) error { db := DBFromContext(ctx, r.db) if len(list) == 0 { return nil } return db.WithContext(ctx).Create(&list).Error } type LetterOutgoingActivityLogRepository struct{ db *gorm.DB } func NewLetterOutgoingActivityLogRepository(db *gorm.DB) *LetterOutgoingActivityLogRepository { return &LetterOutgoingActivityLogRepository{db: db} } func (r *LetterOutgoingActivityLogRepository) Create(ctx context.Context, e *entities.LetterOutgoingActivityLog) error { db := DBFromContext(ctx, r.db) return db.WithContext(ctx).Create(e).Error } func (r *LetterOutgoingActivityLogRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingActivityLog, error) { db := DBFromContext(ctx, r.db) var list []entities.LetterOutgoingActivityLog if err := db.WithContext(ctx). Preload("ActorUser"). Preload("ActorDepartment"). Where("letter_id = ?", letterID). Order("occurred_at DESC"). Find(&list).Error; err != nil { return nil, err } return list, nil }