Init All Docs

This commit is contained in:
Aditya Siregar
2025-09-08 12:24:37 +07:00
parent aa662a321f
commit 2319019eb2
68 changed files with 5417 additions and 437 deletions
+6 -2
View File
@@ -177,14 +177,18 @@ func (r *AnalyticsRepository) GetLetterSummaryStats(ctx context.Context, startDa
stats["total_archived"] = archivedCount
// Calculate average processing time
var avgProcessingTime float64
var avgProcessingTime *float64
db.Table("letters_outgoing").
Select("AVG(EXTRACT(EPOCH FROM (letters_outgoing.updated_at - letters_outgoing.created_at))/3600) as avg_hours").
Where("letters_outgoing.status IN ('approved', 'sent', 'archived')").
Where("letters_outgoing.deleted_at IS NULL").
Scan(&avgProcessingTime)
stats["avg_processing_time"] = avgProcessingTime
if avgProcessingTime != nil {
stats["avg_processing_time"] = *avgProcessingTime
} else {
stats["avg_processing_time"] = float64(0)
}
// Calculate completion rate
var completedCount int64
@@ -2,7 +2,11 @@ package repository
import (
"context"
"encoding/json"
"eslogad-be/internal/contract"
"eslogad-be/internal/entities"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -26,3 +30,33 @@ func (r *AppSettingRepository) Upsert(ctx context.Context, key string, value ent
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Exec("INSERT INTO app_settings(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP", key, value).Error
}
func (r *AppSettingRepository) GetDepartmentRecipients(ctx context.Context) ([]uuid.UUID, error) {
setting, err := r.Get(ctx, contract.SettingIncomingLetterDepartmentRecipients)
if err != nil {
if err == gorm.ErrRecordNotFound {
return []uuid.UUID{}, nil
}
return nil, err
}
jsonBytes, err := json.Marshal(setting.Value)
if err != nil {
return []uuid.UUID{}, nil
}
// Try to unmarshal as the structured format first
var recipientSetting entities.DepartmentRecipientsSetting
if err := json.Unmarshal(jsonBytes, &recipientSetting); err == nil {
return recipientSetting.DepartmentIDs, nil
}
// If that fails, try to unmarshal as a direct array of UUIDs
var departmentIDs []uuid.UUID
if err := json.Unmarshal(jsonBytes, &departmentIDs); err == nil {
return departmentIDs, nil
}
// If both fail, return empty array
return []uuid.UUID{}, nil
}
@@ -19,10 +19,106 @@ func (r *DispositionRouteRepository) Create(ctx context.Context, e *entities.Dis
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Create(e).Error
}
// Upsert creates or updates a disposition route based on from_department_id and to_department_id
func (r *DispositionRouteRepository) Upsert(ctx context.Context, e *entities.DispositionRoute) error {
db := DBFromContext(ctx, r.db)
// Check if route exists
var existing entities.DispositionRoute
err := db.WithContext(ctx).
Where("from_department_id = ? AND to_department_id = ?", e.FromDepartmentID, e.ToDepartmentID).
First(&existing).Error
if err == gorm.ErrRecordNotFound {
// Create new route
return db.WithContext(ctx).Create(e).Error
} else if err != nil {
return err
}
// Update existing route
e.ID = existing.ID
return db.WithContext(ctx).Model(&entities.DispositionRoute{}).
Where("id = ?", existing.ID).
Updates(e).Error
}
// BulkUpsert performs bulk create or update for multiple routes
func (r *DispositionRouteRepository) BulkUpsert(ctx context.Context, fromDeptID uuid.UUID, toDeptIDs []uuid.UUID, isActive bool, allowedActions entities.JSONB) (created int, updated int, err error) {
db := DBFromContext(ctx, r.db)
// Start transaction
tx := db.WithContext(ctx).Begin()
defer func() {
if err != nil {
tx.Rollback()
}
}()
// Get existing routes for this from_department_id
var existingRoutes []entities.DispositionRoute
if err = tx.Where("from_department_id = ?", fromDeptID).Find(&existingRoutes).Error; err != nil {
return 0, 0, err
}
// Create map of existing routes
existingMap := make(map[uuid.UUID]entities.DispositionRoute)
for _, route := range existingRoutes {
existingMap[route.ToDepartmentID] = route
}
// Process each to_department_id
for _, toDeptID := range toDeptIDs {
route := entities.DispositionRoute{
FromDepartmentID: fromDeptID,
ToDepartmentID: toDeptID,
IsActive: isActive,
AllowedActions: allowedActions,
}
if existing, exists := existingMap[toDeptID]; exists {
// Update existing route
route.ID = existing.ID
if err = tx.Model(&entities.DispositionRoute{}).
Where("id = ?", existing.ID).
Updates(&route).Error; err != nil {
return created, updated, err
}
updated++
// Remove from map to track which routes to delete
delete(existingMap, toDeptID)
} else {
// Create new route
if err = tx.Create(&route).Error; err != nil {
return created, updated, err
}
created++
}
}
// Optionally deactivate routes that are no longer in the list
// (routes that exist in DB but not in the new list)
for _, oldRoute := range existingMap {
if err = tx.Model(&entities.DispositionRoute{}).
Where("id = ?", oldRoute.ID).
Update("is_active", false).Error; err != nil {
return created, updated, err
}
}
// Commit transaction
if err = tx.Commit().Error; err != nil {
return 0, 0, err
}
return created, updated, nil
}
func (r *DispositionRouteRepository) Update(ctx context.Context, e *entities.DispositionRoute) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Model(&entities.DispositionRoute{}).Where("id = ?", e.ID).Updates(e).Error
}
func (r *DispositionRouteRepository) Get(ctx context.Context, id uuid.UUID) (*entities.DispositionRoute, error) {
db := DBFromContext(ctx, r.db)
var e entities.DispositionRoute
@@ -46,7 +142,71 @@ func (r *DispositionRouteRepository) ListByFromDept(ctx context.Context, fromDep
return list, nil
}
func (r *DispositionRouteRepository) IsEligibleForDisposition(ctx context.Context, fromDept uuid.UUID) (bool, error) {
db := DBFromContext(ctx, r.db)
var list []entities.DispositionRoute
if err := db.WithContext(ctx).Where("from_department_id = ?", fromDept).Find(&list).Error; err != nil {
return false, err
}
return len(list) > 0, nil
}
func (r *DispositionRouteRepository) SetActive(ctx context.Context, id uuid.UUID, isActive bool) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Model(&entities.DispositionRoute{}).Where("id = ?", id).Update("is_active", isActive).Error
}
// ListAllGrouped returns all disposition routes grouped by from_department_id
func (r *DispositionRouteRepository) ListAllGrouped(ctx context.Context) (map[uuid.UUID][]uuid.UUID, error) {
db := DBFromContext(ctx, r.db)
var routes []entities.DispositionRoute
if err := db.WithContext(ctx).
Where("is_active = ?", true).
Order("from_department_id, to_department_id").
Find(&routes).Error; err != nil {
return nil, err
}
// Group by from_department_id
grouped := make(map[uuid.UUID][]uuid.UUID)
for _, route := range routes {
grouped[route.FromDepartmentID] = append(grouped[route.FromDepartmentID], route.ToDepartmentID)
}
return grouped, nil
}
// ListAllGroupedWithDepartments returns all disposition routes grouped by from_department_id with department details
func (r *DispositionRouteRepository) ListAllGroupedWithDepartments(ctx context.Context) ([]entities.DispositionRoute, error) {
db := DBFromContext(ctx, r.db)
var routes []entities.DispositionRoute
if err := db.WithContext(ctx).
Preload("FromDepartment").
Preload("ToDepartment").
Where("is_active = ?", true).
Order("from_department_id, to_department_id").
Find(&routes).Error; err != nil {
return nil, err
}
return routes, nil
}
// ListAll returns all disposition routes with department details
func (r *DispositionRouteRepository) ListAll(ctx context.Context) ([]entities.DispositionRoute, error) {
db := DBFromContext(ctx, r.db)
var routes []entities.DispositionRoute
if err := db.WithContext(ctx).
Preload("FromDepartment").
Preload("ToDepartment").
Where("is_active = ?", true).
Order("from_department_id, to_department_id").
Find(&routes).Error; err != nil {
return nil, err
}
return routes, nil
}
@@ -51,6 +51,15 @@ func (r *LetterOutgoingRepository) SoftDelete(ctx context.Context, id uuid.UUID)
return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("deleted_at", now).Error
}
func (r *LetterOutgoingRepository) BulkArchive(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
db := DBFromContext(ctx, r.db)
result := db.WithContext(ctx).
Model(&entities.LetterOutgoing{}).
Where("id IN ? AND deleted_at IS NULL", letterIDs).
Update("status", "archived")
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)
@@ -83,12 +92,22 @@ type ListOutgoingLettersFilter struct {
PriorityID *uuid.UUID
SortBy *string
SortOrder *string
IsArchived *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("status = 'archived'")
} else {
query = query.Where("status != 'archived'")
}
}
if filter.Status != nil {
query = query.Where("status = ?", *filter.Status)
}
@@ -207,6 +226,27 @@ func (r *LetterOutgoingAttachmentRepository) Delete(ctx context.Context, id uuid
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 LetterOutgoingRecipientRepository struct{ db *gorm.DB }
func NewLetterOutgoingRecipientRepository(db *gorm.DB) *LetterOutgoingRecipientRepository {
@@ -226,6 +266,27 @@ func (r *LetterOutgoingRecipientRepository) CreateBulk(ctx context.Context, list
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
if err := db.WithContext(ctx).
Model(&entities.LetterOutgoingRecipient{}).
Where("user_id = ? AND read_at IS NULL", userID).
Count(&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
@@ -255,6 +316,33 @@ func (r *LetterOutgoingRecipientRepository) DeleteByLetter(ctx context.Context,
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 {
+425 -6
View File
@@ -29,6 +29,10 @@ func (r *LetterIncomingRepository) Get(ctx context.Context, id uuid.UUID) (*enti
return &e, nil
}
func (r *LetterIncomingRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.LetterIncoming, error) {
return r.Get(ctx, id)
}
func (r *LetterIncomingRepository) Update(ctx context.Context, e *entities.LetterIncoming) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("id = ? AND deleted_at IS NULL", e.ID).Updates(e).Error
@@ -39,21 +43,97 @@ func (r *LetterIncomingRepository) SoftDelete(ctx context.Context, id uuid.UUID)
return db.WithContext(ctx).Exec("UPDATE letters_incoming SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL", id).Error
}
func (r *LetterIncomingRepository) BulkArchive(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
db := DBFromContext(ctx, r.db)
// For incoming letters, we archive the recipients, not the letter itself
// The letter status remains as is (new, in_progress, or completed)
result := db.WithContext(ctx).
Model(&entities.LetterIncomingRecipient{}).
Where("letter_id IN ?", letterIDs).
Update("is_archived", true)
return result.RowsAffected, result.Error
}
// BulkArchiveForUser archives letters for a specific user only
func (r *LetterIncomingRepository) 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.LetterIncomingRecipient{}).
Where("letter_id IN ? AND recipient_user_id = ?", letterIDs, userID).
Update("is_archived", true)
return result.RowsAffected, result.Error
}
type ListIncomingLettersFilter struct {
Status *string
Query *string
DepartmentID *uuid.UUID
Status *string
Query *string
DepartmentID *uuid.UUID
UserID *uuid.UUID
IsRead *bool
PriorityIDs []uuid.UUID
IsDispositioned *bool
IsArchived *bool
}
func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncomingLettersFilter, limit, offset int) ([]entities.LetterIncoming, int64, error) {
db := DBFromContext(ctx, r.db)
query := db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("deleted_at IS NULL")
joinedRecipients := false
needsGroupBy := false
if filter.DepartmentID != nil {
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
Where("letter_incoming_recipients.recipient_department_id = ?", *filter.DepartmentID)
joinedRecipients = true
needsGroupBy = true
}
// Apply is_read filter if UserID is provided
if filter.UserID != nil && filter.IsRead != nil {
if !joinedRecipients {
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id")
joinedRecipients = true
needsGroupBy = true
}
query = query.Where("letter_incoming_recipients.recipient_user_id = ?", *filter.UserID)
if *filter.IsRead {
query = query.Where("letter_incoming_recipients.read_at IS NOT NULL")
} else {
query = query.Where("letter_incoming_recipients.read_at IS NULL")
}
}
// Apply is_dispositioned filter if DepartmentID is provided
if filter.DepartmentID != nil && filter.IsDispositioned != nil {
query = query.Joins("LEFT JOIN letter_incoming_dispositions_department lidd ON lidd.letter_incoming_id = letters_incoming.id AND lidd.department_id = ?", *filter.DepartmentID)
if *filter.IsDispositioned {
// Has been dispositioned (status is not 'pending' or record exists with non-pending status)
query = query.Where("lidd.id IS NOT NULL AND lidd.status != 'pending'")
} else {
// Not yet dispositioned (no record or status is 'pending')
query = query.Where("lidd.id IS NULL OR lidd.status = 'pending'")
}
}
// Apply priority filter
if len(filter.PriorityIDs) > 0 {
query = query.Where("letters_incoming.priority_id IN ?", filter.PriorityIDs)
}
// Apply is_archived filter based on recipient's is_archived field
if filter.IsArchived != nil {
if *filter.IsArchived {
query = query.Where("letter_incoming_recipients.is_archived = ?", true)
} else {
query = query.Where("letter_incoming_recipients.is_archived = ? OR letter_incoming_recipients.is_archived IS NULL", false)
}
}
if filter.Status != nil {
query = query.Where("letters_incoming.status = ?", *filter.Status)
}
@@ -61,12 +141,76 @@ func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncoming
q := "%" + *filter.Query + "%"
query = query.Where("letters_incoming.subject ILIKE ? OR letters_incoming.reference_number ILIKE ?", q, q)
}
// Use GROUP BY instead of DISTINCT to handle joins properly
if needsGroupBy {
query = query.Group("letters_incoming.id")
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
// For the actual data fetch, we need to select all columns
var list []entities.LetterIncoming
if err := query.Order("letters_incoming.created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil {
dataQuery := db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("deleted_at IS NULL")
if filter.DepartmentID != nil {
dataQuery = dataQuery.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
Where("letter_incoming_recipients.recipient_department_id = ?", *filter.DepartmentID)
}
if filter.UserID != nil && filter.IsRead != nil {
if filter.DepartmentID == nil {
dataQuery = dataQuery.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id")
}
dataQuery = dataQuery.Where("letter_incoming_recipients.recipient_user_id = ?", *filter.UserID)
if *filter.IsRead {
dataQuery = dataQuery.Where("letter_incoming_recipients.read_at IS NOT NULL")
} else {
dataQuery = dataQuery.Where("letter_incoming_recipients.read_at IS NULL")
}
}
if filter.DepartmentID != nil && filter.IsDispositioned != nil {
dataQuery = dataQuery.Joins("LEFT JOIN letter_incoming_dispositions_department lidd ON lidd.letter_incoming_id = letters_incoming.id AND lidd.department_id = ?", *filter.DepartmentID)
if *filter.IsDispositioned {
dataQuery = dataQuery.Where("lidd.id IS NOT NULL AND lidd.status != 'pending'")
} else {
dataQuery = dataQuery.Where("lidd.id IS NULL OR lidd.status = 'pending'")
}
}
if len(filter.PriorityIDs) > 0 {
dataQuery = dataQuery.Where("letters_incoming.priority_id IN ?", filter.PriorityIDs)
}
// Apply is_archived filter based on recipient's is_archived field
if filter.IsArchived != nil {
if *filter.IsArchived {
dataQuery = dataQuery.Where("letter_incoming_recipients.is_archived = ?", true)
} else {
dataQuery = dataQuery.Where("letter_incoming_recipients.is_archived = ? OR letter_incoming_recipients.is_archived IS NULL", false)
}
}
if filter.Status != nil {
dataQuery = dataQuery.Where("letters_incoming.status = ?", *filter.Status)
}
if filter.Query != nil {
q := "%" + *filter.Query + "%"
dataQuery = dataQuery.Where("letters_incoming.subject ILIKE ? OR letters_incoming.reference_number ILIKE ?", q, q)
}
if needsGroupBy {
dataQuery = dataQuery.Group("letters_incoming.id, letters_incoming.letter_number, letters_incoming.reference_number, letters_incoming.subject, letters_incoming.description, letters_incoming.priority_id, letters_incoming.sender_institution_id, letters_incoming.received_date, letters_incoming.due_date, letters_incoming.status, letters_incoming.created_by, letters_incoming.created_at, letters_incoming.updated_at, letters_incoming.deleted_at")
}
if err := dataQuery.Order("letters_incoming.created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil {
return nil, 0, err
}
return list, total, nil
@@ -91,6 +235,26 @@ func (r *LetterIncomingAttachmentRepository) ListByLetter(ctx context.Context, l
return list, nil
}
func (r *LetterIncomingAttachmentRepository) ListByLetterIDs(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterIncomingAttachment, error) {
if len(letterIDs) == 0 {
return make(map[uuid.UUID][]entities.LetterIncomingAttachment), nil
}
db := DBFromContext(ctx, r.db)
var attachments []entities.LetterIncomingAttachment
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.LetterIncomingAttachment)
for _, att := range attachments {
result[att.LetterID] = append(result[att.LetterID], att)
}
return result, nil
}
type LetterIncomingActivityLogRepository struct{ db *gorm.DB }
func NewLetterIncomingActivityLogRepository(db *gorm.DB) *LetterIncomingActivityLogRepository {
@@ -120,6 +284,21 @@ func (r *LetterIncomingDispositionRepository) Create(ctx context.Context, e *ent
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Create(e).Error
}
func (r *LetterIncomingDispositionRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.LetterIncomingDisposition, error) {
db := DBFromContext(ctx, r.db)
var e entities.LetterIncomingDisposition
if err := db.WithContext(ctx).
Preload("Department").
Preload("Departments.Department").
Preload("ActionSelections.Action").
Preload("DispositionNotes.User").
First(&e, "id = ?", id).Error; err != nil {
return nil, err
}
return &e, nil
}
func (r *LetterIncomingDispositionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingDisposition, error) {
db := DBFromContext(ctx, r.db)
var list []entities.LetterIncomingDisposition
@@ -136,15 +315,149 @@ func (r *LetterIncomingDispositionRepository) ListByLetter(ctx context.Context,
return list, nil
}
func (r *LetterIncomingDispositionRepository) GetByLetterIncomingID(ctx context.Context, letterIncomingID uuid.UUID) ([]entities.LetterIncomingDisposition, error) {
db := DBFromContext(ctx, r.db)
var list []entities.LetterIncomingDisposition
if err := db.WithContext(ctx).
Where("letter_id = ?", letterIncomingID).
Preload("Department").
Preload("Departments.Department").
Order("created_at ASC").
Find(&list).Error; err != nil {
return nil, err
}
return list, nil
}
type LetterIncomingDispositionDepartmentRepository struct{ db *gorm.DB }
func NewLetterIncomingDispositionDepartmentRepository(db *gorm.DB) *LetterIncomingDispositionDepartmentRepository {
return &LetterIncomingDispositionDepartmentRepository{db: db}
}
func (r *LetterIncomingDispositionDepartmentRepository) DB(ctx context.Context) *gorm.DB {
return DBFromContext(ctx, r.db)
}
func (r *LetterIncomingDispositionDepartmentRepository) Create(ctx context.Context, e *entities.LetterIncomingDispositionDepartment) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Create(e).Error
}
func (r *LetterIncomingDispositionDepartmentRepository) CreateBulk(ctx context.Context, list []entities.LetterIncomingDispositionDepartment) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Create(&list).Error
}
func (r *LetterIncomingDispositionDepartmentRepository) Update(ctx context.Context, e *entities.LetterIncomingDispositionDepartment) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Save(e).Error
}
func (r *LetterIncomingDispositionDepartmentRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.LetterIncomingDispositionDepartment, error) {
db := DBFromContext(ctx, r.db)
var e entities.LetterIncomingDispositionDepartment
if err := db.WithContext(ctx).
Preload("Department").
Preload("LetterIncoming").
Preload("LetterIncomingDisposition").
First(&e, "id = ?", id).Error; err != nil {
return nil, err
}
return &e, nil
}
func (r *LetterIncomingDispositionDepartmentRepository) GetByDispositionAndDepartment(ctx context.Context, letterIncomingID, departmentID uuid.UUID) (*entities.LetterIncomingDispositionDepartment, error) {
db := DBFromContext(ctx, r.db)
var e entities.LetterIncomingDispositionDepartment
if err := db.WithContext(ctx).
Where("letter_incoming_id = ? AND department_id = ?", letterIncomingID, departmentID).
Preload("Department").
Preload("LetterIncoming").
Preload("LetterIncomingDisposition").
First(&e).Error; err != nil {
return nil, err
}
return &e, nil
}
func (r *LetterIncomingDispositionDepartmentRepository) GetByLetterAndDepartment(ctx context.Context, letterID, departmentID uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
db := DBFromContext(ctx, r.db)
var list []entities.LetterIncomingDispositionDepartment
if err := db.WithContext(ctx).
Where("letter_incoming_id = ? AND department_id = ?", letterID, departmentID).
Preload("Department").
Preload("LetterIncoming").
Preload("LetterIncomingDisposition").
Find(&list).Error; err != nil {
return nil, err
}
return list, nil
}
func (r *LetterIncomingDispositionDepartmentRepository) ListByDepartmentWithPagination(ctx context.Context, departmentID uuid.UUID, status *string, offset, limit int) ([]entities.LetterIncomingDispositionDepartment, int64, error) {
db := DBFromContext(ctx, r.db)
query := db.WithContext(ctx).Where("department_id = ?", departmentID)
if status != nil && *status != "" {
query = query.Where("status = ?", *status)
}
var total int64
if err := query.Model(&entities.LetterIncomingDispositionDepartment{}).Count(&total).Error; err != nil {
return nil, 0, err
}
var list []entities.LetterIncomingDispositionDepartment
if err := query.
Preload("Department").
Preload("LetterIncoming").
Preload("LetterIncomingDisposition.Department").
Offset(offset).
Limit(limit).
Order("created_at DESC").
Find(&list).Error; err != nil {
return nil, 0, err
}
return list, total, nil
}
func (r *LetterIncomingDispositionDepartmentRepository) GetByLetterIncomingID(ctx context.Context, letterIncomingID uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
db := DBFromContext(ctx, r.db)
var list []entities.LetterIncomingDispositionDepartment
if err := db.WithContext(ctx).
Where("letter_incoming_id = ?", letterIncomingID).
Preload("Department").
Preload("LetterIncoming").
Preload("LetterIncomingDisposition.Department").
Order("created_at DESC").
Find(&list).Error; err != nil {
return nil, err
}
return list, nil
}
func (r *LetterIncomingDispositionDepartmentRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.LetterIncomingDispositionDepartmentStatus, notes string, readAt, completedAt *time.Time) error {
db := DBFromContext(ctx, r.db)
updates := map[string]interface{}{
"status": status,
}
if readAt != nil {
updates["read_at"] = readAt
}
if completedAt != nil {
updates["completed_at"] = completedAt
}
if notes != "" {
updates["notes"] = notes
}
return db.WithContext(ctx).Model(&entities.LetterIncomingDispositionDepartment{}).
Where("id = ?", id).
Updates(updates).Error
}
func (r *LetterIncomingDispositionDepartmentRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
db := DBFromContext(ctx, r.db)
var list []entities.LetterIncomingDispositionDepartment
@@ -292,11 +605,63 @@ type LetterIncomingRecipientRepository struct{ db *gorm.DB }
func NewLetterIncomingRecipientRepository(db *gorm.DB) *LetterIncomingRecipientRepository {
return &LetterIncomingRecipientRepository{db: db}
}
func (r *LetterIncomingRecipientRepository) DB(ctx context.Context) *gorm.DB {
return DBFromContext(ctx, r.db)
}
func (r *LetterIncomingRecipientRepository) CreateBulk(ctx context.Context, recs []entities.LetterIncomingRecipient) error {
db := DBFromContext(ctx, r.db)
return db.WithContext(ctx).Create(&recs).Error
}
func (r *LetterIncomingRecipientRepository) Create(ctx context.Context, recipient *entities.LetterIncomingRecipient) error {
return r.DB(ctx).Create(recipient).Error
}
func (r *LetterIncomingRecipientRepository) Update(ctx context.Context, recipient *entities.LetterIncomingRecipient) error {
return r.DB(ctx).Save(recipient).Error
}
func (r *LetterIncomingRecipientRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.LetterIncomingRecipient, error) {
var recipient entities.LetterIncomingRecipient
if err := r.DB(ctx).Where("id = ?", id).First(&recipient).Error; err != nil {
return nil, err
}
return &recipient, nil
}
func (r *LetterIncomingRecipientRepository) GetByLetterAndDepartment(ctx context.Context, letterID uuid.UUID, departmentID uuid.UUID) (*entities.LetterIncomingRecipient, error) {
var recipient entities.LetterIncomingRecipient
if err := r.DB(ctx).Where("letter_id = ? AND recipient_department_id = ?", letterID, departmentID).First(&recipient).Error; err != nil {
return nil, err
}
return &recipient, nil
}
func (r *LetterIncomingRecipientRepository) GetByLetterAndUser(ctx context.Context, letterID uuid.UUID, userID uuid.UUID) (*entities.LetterIncomingRecipient, error) {
var recipient entities.LetterIncomingRecipient
if err := r.DB(ctx).Where("letter_id = ? AND recipient_user_id = ?", letterID, userID).First(&recipient).Error; err != nil {
return nil, err
}
return &recipient, nil
}
func (r *LetterIncomingRecipientRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingRecipient, error) {
var recipients []entities.LetterIncomingRecipient
if err := r.DB(ctx).Where("letter_id = ?", letterID).Find(&recipients).Error; err != nil {
return nil, err
}
return recipients, nil
}
func (r *LetterIncomingRecipientRepository) ListByDepartment(ctx context.Context, departmentID uuid.UUID) ([]entities.LetterIncomingRecipient, error) {
var recipients []entities.LetterIncomingRecipient
if err := r.DB(ctx).Where("recipient_department_id = ?", departmentID).Find(&recipients).Error; err != nil {
return nil, err
}
return recipients, nil
}
func (r *LetterIncomingRecipientRepository) GetLetterIDsByDepartment(ctx context.Context, departmentID uuid.UUID) ([]uuid.UUID, error) {
db := DBFromContext(ctx, r.db)
var letterIDs []uuid.UUID
@@ -310,6 +675,60 @@ func (r *LetterIncomingRecipientRepository) GetLetterIDsByDepartment(ctx context
return letterIDs, nil
}
func (r *LetterIncomingRecipientRepository) CountReadByLetter(ctx context.Context, letterID uuid.UUID) (int, error) {
db := DBFromContext(ctx, r.db)
var count int64
if err := db.WithContext(ctx).
Model(&entities.LetterIncomingRecipient{}).
Where("letter_id = ? AND read_at IS NOT NULL", letterID).
Count(&count).Error; err != nil {
return 0, err
}
return int(count), nil
}
func (r *LetterIncomingRecipientRepository) CountUnreadByUser(ctx context.Context, userID uuid.UUID) (int, error) {
db := DBFromContext(ctx, r.db)
var count int64
if err := db.WithContext(ctx).
Model(&entities.LetterIncomingRecipient{}).
Where("recipient_user_id = ? AND read_at IS NULL", userID).
Count(&count).Error; err != nil {
return 0, err
}
return int(count), nil
}
func (r *LetterIncomingRecipientRepository) MarkAsRead(ctx context.Context, letterID, userID uuid.UUID) error {
db := DBFromContext(ctx, r.db)
now := time.Now()
return db.WithContext(ctx).
Model(&entities.LetterIncomingRecipient{}).
Where("letter_id = ? AND recipient_user_id = ?", letterID, userID).
Update("read_at", now).Error
}
func (r *LetterIncomingRecipientRepository) GetByLetterIDsAndUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (map[uuid.UUID]*entities.LetterIncomingRecipient, error) {
if len(letterIDs) == 0 {
return make(map[uuid.UUID]*entities.LetterIncomingRecipient), nil
}
db := DBFromContext(ctx, r.db)
var recipients []entities.LetterIncomingRecipient
if err := db.WithContext(ctx).
Where("letter_id IN ? AND recipient_user_id = ?", letterIDs, userID).
Find(&recipients).Error; err != nil {
return nil, err
}
result := make(map[uuid.UUID]*entities.LetterIncomingRecipient)
for i := range recipients {
result[recipients[i].LetterID] = &recipients[i]
}
return result, nil
}
func (r *LetterIncomingRecipientRepository) HasDepartmentAccess(ctx context.Context, letterID uuid.UUID, departmentID uuid.UUID) (bool, error) {
db := DBFromContext(ctx, r.db)
var count int64
+45
View File
@@ -59,6 +59,24 @@ func (r *PriorityRepository) Get(ctx context.Context, id uuid.UUID) (*entities.P
return &e, nil
}
func (r *PriorityRepository) GetByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]*entities.Priority, error) {
if len(ids) == 0 {
return make(map[uuid.UUID]*entities.Priority), nil
}
var priorities []entities.Priority
if err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&priorities).Error; err != nil {
return nil, err
}
result := make(map[uuid.UUID]*entities.Priority)
for i := range priorities {
result[priorities[i].ID] = &priorities[i]
}
return result, nil
}
type InstitutionRepository struct{ db *gorm.DB }
func NewInstitutionRepository(db *gorm.DB) *InstitutionRepository {
@@ -100,6 +118,24 @@ func (r *InstitutionRepository) Get(ctx context.Context, id uuid.UUID) (*entitie
return &e, nil
}
func (r *InstitutionRepository) GetByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]*entities.Institution, error) {
if len(ids) == 0 {
return make(map[uuid.UUID]*entities.Institution), nil
}
var institutions []entities.Institution
if err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&institutions).Error; err != nil {
return nil, err
}
result := make(map[uuid.UUID]*entities.Institution)
for i := range institutions {
result[institutions[i].ID] = &institutions[i]
}
return result, nil
}
type DispositionActionRepository struct{ db *gorm.DB }
func NewDispositionActionRepository(db *gorm.DB) *DispositionActionRepository {
@@ -188,3 +224,12 @@ func (r *DepartmentRepository) List(ctx context.Context, search string, limit, o
return list, total, nil
}
func (r *DepartmentRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.Department, error) {
db := DBFromContext(ctx, r.db)
var e entities.Department
if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
return nil, err
}
return &e, nil
}
@@ -13,15 +13,14 @@ func NewUserDepartmentRepository(db *gorm.DB) *UserDepartmentRepository {
return &UserDepartmentRepository{db: db}
}
type userDepartmentRow struct {
type UserDepartmentRow struct {
UserID uuid.UUID `gorm:"column:user_id"`
DepartmentID uuid.UUID `gorm:"column:department_id"`
}
// ListActiveByDepartmentIDs returns active user-department memberships for given department IDs.
func (r *UserDepartmentRepository) ListActiveByDepartmentIDs(ctx context.Context, departmentIDs []uuid.UUID) ([]userDepartmentRow, error) {
func (r *UserDepartmentRepository) ListActiveByDepartmentIDs(ctx context.Context, departmentIDs []uuid.UUID) ([]UserDepartmentRow, error) {
db := DBFromContext(ctx, r.db)
rows := make([]userDepartmentRow, 0)
rows := make([]UserDepartmentRow, 0)
if len(departmentIDs) == 0 {
return rows, nil
}
+12
View File
@@ -35,6 +35,18 @@ func (r *UserRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entiti
return &user, nil
}
// GetByIDLight retrieves user without preloading relationships - for faster auth checks
func (r *UserRepositoryImpl) GetByIDLight(ctx context.Context, id uuid.UUID) (*entities.User, error) {
var user entities.User
err := r.b.WithContext(ctx).
Where("id = ?", id).
First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *UserRepositoryImpl) GetByEmail(ctx context.Context, email string) (*entities.User, error) {
var user entities.User
err := r.b.WithContext(ctx).