dukcapil/internal/repository/letter_outgoing_repository.go
2025-12-06 11:20:33 +07:00

819 lines
26 KiB
Go

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("FinalAttachments").
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("FinalAttachments").
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("FinalAttachments").
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("FinalAttachments").
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
}