add dukcapil
This commit is contained in:
@@ -1,959 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AnalyticsRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAnalyticsRepository(db *gorm.DB) *AnalyticsRepository {
|
||||
return &AnalyticsRepository{db: db}
|
||||
}
|
||||
|
||||
// GetLetterSummaryStats gets overall summary statistics using summary tables for better performance
|
||||
func (r *AnalyticsRepository) GetLetterSummaryStats(ctx context.Context, startDate, endDate time.Time, userID, departmentID *uuid.UUID) (map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
// Use summary tables for better performance when possible
|
||||
if userID == nil && departmentID != nil {
|
||||
// Use department_letter_summary for department-specific stats
|
||||
query := db.Table("department_letter_summary").
|
||||
Where("department_id = ?", *departmentID)
|
||||
|
||||
if !startDate.IsZero() {
|
||||
query = query.Where("summary_date >= ?", startDate)
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
query = query.Where("summary_date <= ?", endDate)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
TotalIncoming int64 `gorm:"column:total_incoming"`
|
||||
TotalOutgoing int64 `gorm:"column:total_outgoing"`
|
||||
PendingOutgoing int64 `gorm:"column:pending_outgoing"`
|
||||
ApprovedOutgoing int64 `gorm:"column:approved_outgoing"`
|
||||
RejectedOutgoing int64 `gorm:"column:rejected_outgoing"`
|
||||
AvgResponseHours float64 `gorm:"column:avg_response_hours"`
|
||||
CompletionRate float64 `gorm:"column:completion_rate"`
|
||||
}
|
||||
|
||||
query.Select(`
|
||||
COALESCE(SUM(incoming_count), 0) as total_incoming,
|
||||
COALESCE(SUM(outgoing_count), 0) as total_outgoing,
|
||||
COALESCE(SUM(pending_outgoing), 0) as pending_outgoing,
|
||||
COALESCE(SUM(approved_outgoing), 0) as approved_outgoing,
|
||||
COALESCE(SUM(rejected_outgoing), 0) as rejected_outgoing,
|
||||
COALESCE(AVG(avg_response_hours), 0) as avg_response_hours,
|
||||
COALESCE(AVG(completion_rate), 0) as completion_rate
|
||||
`).Scan(&result)
|
||||
|
||||
stats["total_incoming"] = result.TotalIncoming
|
||||
stats["total_outgoing"] = result.TotalOutgoing
|
||||
stats["total_pending"] = result.PendingOutgoing
|
||||
stats["total_approved"] = result.ApprovedOutgoing
|
||||
stats["total_rejected"] = result.RejectedOutgoing
|
||||
stats["total_archived"] = int64(0) // Calculate separately if needed
|
||||
stats["avg_processing_time"] = result.AvgResponseHours
|
||||
stats["completion_rate"] = result.CompletionRate
|
||||
} else if userID == nil && departmentID == nil {
|
||||
// Use letter_summary for overall stats
|
||||
query := db.Table("letter_summary")
|
||||
|
||||
if !startDate.IsZero() {
|
||||
query = query.Where("summary_date >= ?", startDate)
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
query = query.Where("summary_date <= ?", endDate)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
TotalIncoming int64 `gorm:"column:total_incoming"`
|
||||
TotalOutgoing int64 `gorm:"column:total_outgoing"`
|
||||
TotalPending int64 `gorm:"column:total_pending"`
|
||||
TotalApproved int64 `gorm:"column:total_approved"`
|
||||
TotalRejected int64 `gorm:"column:total_rejected"`
|
||||
TotalArchived int64 `gorm:"column:total_archived"`
|
||||
TotalSent int64 `gorm:"column:total_sent"`
|
||||
AvgProcessing float64 `gorm:"column:avg_processing"`
|
||||
}
|
||||
|
||||
query.Select(`
|
||||
COALESCE(SUM(CASE WHEN letter_type = 'incoming' THEN total_count ELSE 0 END), 0) as total_incoming,
|
||||
COALESCE(SUM(CASE WHEN letter_type = 'outgoing' THEN total_count ELSE 0 END), 0) as total_outgoing,
|
||||
COALESCE(SUM(pending_count), 0) as total_pending,
|
||||
COALESCE(SUM(approved_count), 0) as total_approved,
|
||||
COALESCE(SUM(rejected_count), 0) as total_rejected,
|
||||
COALESCE(SUM(archived_count), 0) as total_archived,
|
||||
COALESCE(SUM(sent_count), 0) as total_sent,
|
||||
COALESCE(AVG(avg_processing_hours), 0) as avg_processing
|
||||
`).Scan(&result)
|
||||
|
||||
stats["total_incoming"] = result.TotalIncoming
|
||||
stats["total_outgoing"] = result.TotalOutgoing
|
||||
stats["total_pending"] = result.TotalPending
|
||||
stats["total_approved"] = result.TotalApproved
|
||||
stats["total_rejected"] = result.TotalRejected
|
||||
stats["total_archived"] = result.TotalArchived
|
||||
stats["avg_processing_time"] = result.AvgProcessing
|
||||
|
||||
// Calculate completion rate
|
||||
completionRate := float64(0)
|
||||
if result.TotalOutgoing > 0 {
|
||||
completedCount := result.TotalSent + result.TotalArchived
|
||||
completionRate = float64(completedCount) / float64(result.TotalOutgoing) * 100
|
||||
}
|
||||
stats["completion_rate"] = completionRate
|
||||
} else {
|
||||
// Fall back to original implementation for user-specific queries
|
||||
// Base query builders
|
||||
incomingQuery := db.Table("letters_incoming").Where("letters_incoming.deleted_at IS NULL")
|
||||
outgoingQuery := db.Table("letters_outgoing").Where("letters_outgoing.deleted_at IS NULL")
|
||||
|
||||
// Apply date filters
|
||||
if !startDate.IsZero() {
|
||||
incomingQuery = incomingQuery.Where("letters_incoming.created_at >= ?", startDate)
|
||||
outgoingQuery = outgoingQuery.Where("letters_outgoing.created_at >= ?", startDate)
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
incomingQuery = incomingQuery.Where("letters_incoming.created_at <= ?", endDate)
|
||||
outgoingQuery = outgoingQuery.Where("letters_outgoing.created_at <= ?", endDate)
|
||||
}
|
||||
|
||||
// Apply user/department filters for outgoing letters
|
||||
if userID != nil {
|
||||
outgoingQuery = outgoingQuery.
|
||||
Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
|
||||
Where("letter_outgoing_recipients.user_id = ?", *userID)
|
||||
incomingQuery = incomingQuery.
|
||||
Joins("LEFT JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
|
||||
Where("letter_incoming_recipients.recipient_user_id = ?", *userID)
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] userId analitycs: %v\n", userID)
|
||||
|
||||
// Count incoming letters
|
||||
var totalIncoming int64
|
||||
incomingQuery.Distinct("letters_incoming.id").Count(&totalIncoming)
|
||||
stats["total_incoming"] = totalIncoming
|
||||
|
||||
// Count outgoing letters
|
||||
var totalOutgoing int64
|
||||
outgoingQuery.Distinct("letters_outgoing.id").Count(&totalOutgoing)
|
||||
stats["total_outgoing"] = totalOutgoing
|
||||
|
||||
// Count by status - need to clone query for each count
|
||||
var pendingCount, approvedCount, rejectedCount, archivedCount int64
|
||||
|
||||
db.Table("letters_outgoing").Where("letters_outgoing.deleted_at IS NULL").
|
||||
Where("letters_outgoing.status = ?", "pending_approval").
|
||||
Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
|
||||
Where("letter_outgoing_recipients.user_id = ?", *userID).
|
||||
Count(&pendingCount)
|
||||
|
||||
db.Table("letters_outgoing").Where("letters_outgoing.deleted_at IS NULL").
|
||||
Where("letters_outgoing.status = ?", "approved").
|
||||
Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
|
||||
Where("letter_outgoing_recipients.user_id = ?", *userID).
|
||||
Count(&approvedCount)
|
||||
|
||||
db.Table("letters_outgoing").Where("letters_outgoing.deleted_at IS NULL").
|
||||
Where("letters_outgoing.status = ?", "rejected").
|
||||
Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
|
||||
Where("letter_outgoing_recipients.user_id = ?", *userID).
|
||||
Count(&rejectedCount)
|
||||
|
||||
db.Table("letters_outgoing").Where("letters_outgoing.deleted_at IS NULL").
|
||||
Where("letters_outgoing.status = ?", "archived").
|
||||
Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
|
||||
Where("letter_outgoing_recipients.user_id = ?", *userID).
|
||||
Count(&archivedCount)
|
||||
|
||||
stats["total_pending"] = pendingCount
|
||||
stats["total_approved"] = approvedCount
|
||||
stats["total_rejected"] = rejectedCount
|
||||
stats["total_archived"] = archivedCount
|
||||
|
||||
// Calculate average processing time
|
||||
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)
|
||||
|
||||
if avgProcessingTime != nil {
|
||||
stats["avg_processing_time"] = *avgProcessingTime
|
||||
} else {
|
||||
stats["avg_processing_time"] = float64(0)
|
||||
}
|
||||
|
||||
// Calculate completion rate
|
||||
var completedCount int64
|
||||
db.Table("letters_outgoing").Where("letters_outgoing.deleted_at IS NULL").
|
||||
Where("letters_outgoing.status IN ('sent', 'archived')").
|
||||
Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
|
||||
Where("letter_outgoing_recipients.user_id = ?", *userID).
|
||||
Count(&completedCount)
|
||||
|
||||
completionRate := float64(0)
|
||||
if totalOutgoing > 0 {
|
||||
completionRate = float64(completedCount) / float64(totalOutgoing) * 100
|
||||
}
|
||||
stats["completion_rate"] = completionRate
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetStatusDistribution gets letter distribution by status
|
||||
func (r *AnalyticsRepository) GetStatusDistribution(ctx context.Context, startDate, endDate time.Time, userID *uuid.UUID) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
query := `
|
||||
WITH combined_letters AS (
|
||||
SELECT
|
||||
status,
|
||||
'incoming' as type,
|
||||
COUNT(*) as count
|
||||
FROM letters_incoming
|
||||
WHERE deleted_at IS NULL
|
||||
%s
|
||||
GROUP BY status
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
lo.status,
|
||||
'outgoing' as type,
|
||||
COUNT(DISTINCT lo.id) as count
|
||||
FROM letters_outgoing lo
|
||||
%s
|
||||
WHERE lo.deleted_at IS NULL
|
||||
%s
|
||||
GROUP BY lo.status
|
||||
)
|
||||
SELECT
|
||||
status,
|
||||
type,
|
||||
count,
|
||||
ROUND(count * 100.0 / SUM(count) OVER (PARTITION BY type), 2) as percentage
|
||||
FROM combined_letters
|
||||
ORDER BY type, count DESC
|
||||
`
|
||||
|
||||
incomingDateFilter := ""
|
||||
outgoingDateFilter := ""
|
||||
if !startDate.IsZero() {
|
||||
incomingDateFilter += fmt.Sprintf(" AND created_at >= '%s'", startDate.Format("2006-01-02"))
|
||||
outgoingDateFilter += fmt.Sprintf(" AND lo.created_at >= '%s'", startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
incomingDateFilter += fmt.Sprintf(" AND created_at <= '%s'", endDate.Format("2006-01-02"))
|
||||
outgoingDateFilter += fmt.Sprintf(" AND lo.created_at <= '%s'", endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
joinClause := ""
|
||||
userFilter := ""
|
||||
if userID != nil {
|
||||
joinClause = "LEFT JOIN letter_outgoing_recipients lor ON lor.letter_id = lo.id"
|
||||
userFilter = fmt.Sprintf(" AND lor.user_id = '%s'", userID.String())
|
||||
}
|
||||
|
||||
query = fmt.Sprintf(query, incomingDateFilter, joinClause, outgoingDateFilter+userFilter)
|
||||
|
||||
if err := db.Raw(query).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetPriorityDistribution gets letter distribution by priority
|
||||
func (r *AnalyticsRepository) GetPriorityDistribution(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
p.id as priority_id,
|
||||
p.name as priority_name,
|
||||
p.level,
|
||||
COUNT(lo.id) as count,
|
||||
ROUND(COUNT(lo.id) * 100.0 / SUM(COUNT(lo.id)) OVER (), 2) as percentage,
|
||||
AVG(EXTRACT(EPOCH FROM (lo.updated_at - lo.created_at))/3600) as avg_response_time
|
||||
FROM priorities p
|
||||
LEFT JOIN letters_outgoing lo ON lo.priority_id = p.id AND lo.deleted_at IS NULL
|
||||
WHERE 1=1
|
||||
%s
|
||||
GROUP BY p.id, p.name, p.level
|
||||
ORDER BY p.level ASC
|
||||
`
|
||||
|
||||
dateFilter := ""
|
||||
if !startDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND lo.created_at >= '%s'", startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND lo.created_at <= '%s'", endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
query = fmt.Sprintf(query, dateFilter)
|
||||
|
||||
if err := db.Raw(query).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetDepartmentStats gets statistics per department using summary tables
|
||||
func (r *AnalyticsRepository) GetDepartmentStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
// Format tanggal untuk logging
|
||||
log.Printf("GetDepartmentStats called with startDate: %v, endDate: %v", startDate, endDate)
|
||||
|
||||
// Try summary table first
|
||||
query := `
|
||||
SELECT
|
||||
d.id as department_id,
|
||||
d.name as department_name,
|
||||
d.code as department_code,
|
||||
COALESCE(SUM(dls.incoming_count), 0) as incoming_count,
|
||||
COALESCE(SUM(dls.outgoing_count), 0) as outgoing_count,
|
||||
COALESCE(SUM(dls.pending_outgoing), 0) as pending_count,
|
||||
COALESCE(AVG(dls.avg_response_hours), 0) as avg_response_time,
|
||||
COALESCE(AVG(dls.completion_rate), 0) as completion_rate
|
||||
FROM departments d
|
||||
LEFT JOIN department_letter_summary dls ON dls.department_id = d.id`
|
||||
|
||||
var conditions []string
|
||||
var args []interface{}
|
||||
|
||||
if !startDate.IsZero() {
|
||||
conditions = append(conditions, "dls.summary_date >= ?")
|
||||
args = append(args, startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
conditions = append(conditions, "dls.summary_date <= ?")
|
||||
args = append(args, endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
if len(conditions) > 0 {
|
||||
query += " WHERE " + strings.Join(conditions, " AND ")
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY d.id, d.name, d.code
|
||||
ORDER BY (COALESCE(SUM(dls.incoming_count), 0) + COALESCE(SUM(dls.outgoing_count), 0)) DESC`
|
||||
|
||||
log.Printf("Summary query: %s, args: %v", query, args)
|
||||
|
||||
if err := db.Raw(query, args...).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if summary table has data for this period
|
||||
var summaryCount int64
|
||||
checkQuery := "SELECT COUNT(*) FROM department_letter_summary WHERE 1=1"
|
||||
checkArgs := []interface{}{}
|
||||
|
||||
if !startDate.IsZero() {
|
||||
checkQuery += " AND summary_date >= ?"
|
||||
checkArgs = append(checkArgs, startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
checkQuery += " AND summary_date <= ?"
|
||||
checkArgs = append(checkArgs, endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
db.Raw(checkQuery, checkArgs...).Scan(&summaryCount)
|
||||
log.Printf("Summary count: %d", summaryCount)
|
||||
|
||||
// If no summary data exists for this period, fall back to direct query
|
||||
if summaryCount == 0 {
|
||||
log.Println("Using fallback query (no summary data)")
|
||||
|
||||
// Use CTE for better performance
|
||||
fallbackQuery := `
|
||||
WITH filtered_incoming AS (
|
||||
SELECT
|
||||
li.id,
|
||||
li.created_at,
|
||||
li.updated_at,
|
||||
lir.recipient_department_id
|
||||
FROM letters_incoming li
|
||||
INNER JOIN letter_incoming_recipients lir ON lir.letter_id = li.id
|
||||
WHERE li.deleted_at IS NULL`
|
||||
|
||||
var fallbackArgs []interface{}
|
||||
|
||||
if !startDate.IsZero() {
|
||||
fallbackQuery += " AND li.created_at >= ?"
|
||||
fallbackArgs = append(fallbackArgs, startDate)
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
fallbackQuery += " AND li.created_at <= ?"
|
||||
fallbackArgs = append(fallbackArgs, endDate)
|
||||
}
|
||||
|
||||
fallbackQuery += `
|
||||
),
|
||||
filtered_outgoing AS (
|
||||
SELECT
|
||||
lo.id,
|
||||
lo.status,
|
||||
lo.created_at,
|
||||
lo.updated_at,
|
||||
lor.department_id
|
||||
FROM letters_outgoing lo
|
||||
INNER JOIN letter_outgoing_recipients lor ON lor.letter_id = lo.id
|
||||
WHERE lo.deleted_at IS NULL`
|
||||
|
||||
if !startDate.IsZero() {
|
||||
fallbackQuery += " AND lo.created_at >= ?"
|
||||
fallbackArgs = append(fallbackArgs, startDate)
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
fallbackQuery += " AND lo.created_at <= ?"
|
||||
fallbackArgs = append(fallbackArgs, endDate)
|
||||
}
|
||||
|
||||
fallbackQuery += `
|
||||
)
|
||||
SELECT
|
||||
d.id as department_id,
|
||||
d.name as department_name,
|
||||
d.code as department_code,
|
||||
COUNT(DISTINCT fi.id) as incoming_count,
|
||||
COUNT(DISTINCT fo.id) as outgoing_count,
|
||||
COUNT(DISTINCT CASE WHEN fo.status = 'pending_approval' THEN fo.id END) as pending_count,
|
||||
COALESCE(AVG(CASE
|
||||
WHEN fo.status IN ('approved', 'sent', 'archived')
|
||||
THEN EXTRACT(EPOCH FROM (fo.updated_at - fo.created_at))/3600
|
||||
END), 0) as avg_response_time,
|
||||
CASE
|
||||
WHEN COUNT(DISTINCT fo.id) > 0
|
||||
THEN ROUND(COUNT(DISTINCT CASE WHEN fo.status IN ('sent', 'archived') THEN fo.id END) * 100.0 / COUNT(DISTINCT fo.id), 2)
|
||||
ELSE 0
|
||||
END as completion_rate
|
||||
FROM departments d
|
||||
LEFT JOIN filtered_incoming fi ON fi.recipient_department_id = d.id
|
||||
LEFT JOIN filtered_outgoing fo ON fo.department_id = d.id
|
||||
GROUP BY d.id, d.name, d.code
|
||||
ORDER BY (COUNT(DISTINCT fi.id) + COUNT(DISTINCT fo.id)) DESC`
|
||||
|
||||
log.Printf("Fallback query: %s", fallbackQuery)
|
||||
log.Printf("Fallback args: %v", fallbackArgs)
|
||||
|
||||
if err := db.Raw(fallbackQuery, fallbackArgs...).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetMonthlyTrend gets monthly trend data using summary tables for better performance
|
||||
func (r *AnalyticsRepository) GetMonthlyTrend(ctx context.Context, months int) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
// Use summary table for better performance
|
||||
query := `
|
||||
WITH monthly_aggregated AS (
|
||||
SELECT
|
||||
TO_CHAR(summary_date, 'Month') as month,
|
||||
EXTRACT(YEAR FROM summary_date) as year,
|
||||
EXTRACT(MONTH FROM summary_date) as month_num,
|
||||
SUM(CASE WHEN letter_type = 'incoming' THEN total_count ELSE 0 END) as incoming_count,
|
||||
SUM(CASE WHEN letter_type = 'outgoing' THEN total_count ELSE 0 END) as outgoing_count,
|
||||
SUM(total_count) as total_count
|
||||
FROM letter_summary
|
||||
WHERE summary_date >= NOW() - INTERVAL '%d months'
|
||||
GROUP BY TO_CHAR(summary_date, 'Month'),
|
||||
EXTRACT(YEAR FROM summary_date),
|
||||
EXTRACT(MONTH FROM summary_date)
|
||||
)
|
||||
SELECT
|
||||
month,
|
||||
year,
|
||||
incoming_count,
|
||||
outgoing_count,
|
||||
total_count,
|
||||
LAG(total_count) OVER (ORDER BY year, month_num) as prev_total
|
||||
FROM monthly_aggregated
|
||||
ORDER BY year DESC, month_num DESC
|
||||
LIMIT %d
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, months, months)
|
||||
|
||||
if err := db.Raw(query).Scan(&results).Error; err != nil {
|
||||
// If summary table is empty, fall back to direct query
|
||||
if len(results) == 0 {
|
||||
fallbackQuery := `
|
||||
WITH monthly_data AS (
|
||||
SELECT
|
||||
TO_CHAR(date_trunc('month', created_at), 'Month') as month,
|
||||
EXTRACT(YEAR FROM created_at) as year,
|
||||
EXTRACT(MONTH FROM created_at) as month_num,
|
||||
COUNT(*) as incoming_count,
|
||||
0 as outgoing_count
|
||||
FROM letters_incoming
|
||||
WHERE deleted_at IS NULL
|
||||
AND created_at >= NOW() - INTERVAL '%d months'
|
||||
GROUP BY date_trunc('month', created_at), EXTRACT(YEAR FROM created_at), EXTRACT(MONTH FROM created_at)
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
TO_CHAR(date_trunc('month', created_at), 'Month') as month,
|
||||
EXTRACT(YEAR FROM created_at) as year,
|
||||
EXTRACT(MONTH FROM created_at) as month_num,
|
||||
0 as incoming_count,
|
||||
COUNT(*) as outgoing_count
|
||||
FROM letters_outgoing
|
||||
WHERE deleted_at IS NULL
|
||||
AND created_at >= NOW() - INTERVAL '%d months'
|
||||
GROUP BY date_trunc('month', created_at), EXTRACT(YEAR FROM created_at), EXTRACT(MONTH FROM created_at)
|
||||
)
|
||||
SELECT
|
||||
month,
|
||||
year,
|
||||
SUM(incoming_count) as incoming_count,
|
||||
SUM(outgoing_count) as outgoing_count,
|
||||
SUM(incoming_count + outgoing_count) as total_count,
|
||||
LAG(SUM(incoming_count + outgoing_count)) OVER (ORDER BY year, month_num) as prev_total
|
||||
FROM monthly_data
|
||||
GROUP BY month, year, month_num
|
||||
ORDER BY year DESC, month_num DESC
|
||||
LIMIT %d
|
||||
`
|
||||
|
||||
fallbackQuery = fmt.Sprintf(fallbackQuery, months, months, months)
|
||||
|
||||
if err := db.Raw(fallbackQuery).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate growth rate
|
||||
for i := range results {
|
||||
if results[i]["prev_total"] != nil {
|
||||
prevVal, ok := results[i]["prev_total"].(float64)
|
||||
if ok && prevVal > 0 {
|
||||
current := getFloat64FromInterface(results[i]["total_count"])
|
||||
results[i]["growth_rate"] = ((current - prevVal) / prevVal) * 100
|
||||
} else {
|
||||
results[i]["growth_rate"] = float64(0)
|
||||
}
|
||||
} else {
|
||||
results[i]["growth_rate"] = float64(0)
|
||||
}
|
||||
delete(results[i], "prev_total")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepository) GetMonthlyTrendByUserID(ctx context.Context, userID *uuid.UUID, months int) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
// Direct query (since we need to filter by user)
|
||||
fallbackQuery := `
|
||||
WITH monthly_data AS (
|
||||
SELECT
|
||||
TO_CHAR(date_trunc('month', li.created_at), 'Month') as month,
|
||||
EXTRACT(YEAR FROM li.created_at) as year,
|
||||
EXTRACT(MONTH FROM li.created_at) as month_num,
|
||||
COUNT(DISTINCT li.id) as incoming_count,
|
||||
0 as outgoing_count
|
||||
FROM letters_incoming li
|
||||
INNER JOIN letter_incoming_recipients lir ON lir.letter_id = li.id
|
||||
WHERE li.deleted_at IS NULL
|
||||
AND lir.recipient_user_id = ?
|
||||
AND li.created_at >= NOW() - INTERVAL '%d months'
|
||||
GROUP BY date_trunc('month', li.created_at), EXTRACT(YEAR FROM li.created_at), EXTRACT(MONTH FROM li.created_at)
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
TO_CHAR(date_trunc('month', lo.created_at), 'Month') as month,
|
||||
EXTRACT(YEAR FROM lo.created_at) as year,
|
||||
EXTRACT(MONTH FROM lo.created_at) as month_num,
|
||||
0 as incoming_count,
|
||||
COUNT(DISTINCT lo.id) as outgoing_count
|
||||
FROM letters_outgoing lo
|
||||
INNER JOIN letter_outgoing_recipients lor ON lor.letter_id = lo.id
|
||||
WHERE lo.deleted_at IS NULL
|
||||
AND lor.user_id = ?
|
||||
AND lo.created_at >= NOW() - INTERVAL '%d months'
|
||||
GROUP BY date_trunc('month', lo.created_at), EXTRACT(YEAR FROM lo.created_at), EXTRACT(MONTH FROM lo.created_at)
|
||||
)
|
||||
SELECT
|
||||
month,
|
||||
year,
|
||||
SUM(incoming_count) as incoming_count,
|
||||
SUM(outgoing_count) as outgoing_count,
|
||||
SUM(incoming_count + outgoing_count) as total_count,
|
||||
LAG(SUM(incoming_count + outgoing_count)) OVER (ORDER BY year, month_num) as prev_total
|
||||
FROM monthly_data
|
||||
GROUP BY month, year, month_num
|
||||
ORDER BY year DESC, month_num DESC
|
||||
LIMIT %d
|
||||
`
|
||||
|
||||
fallbackQuery = fmt.Sprintf(fallbackQuery, months, months, months)
|
||||
|
||||
if err := db.Raw(fallbackQuery, userID, userID).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Calculate growth rate
|
||||
for i := range results {
|
||||
if results[i]["prev_total"] != nil {
|
||||
prevVal, ok := results[i]["prev_total"].(float64)
|
||||
if ok && prevVal > 0 {
|
||||
current := getFloat64FromInterface(results[i]["total_count"])
|
||||
results[i]["growth_rate"] = ((current - prevVal) / prevVal) * 100
|
||||
} else {
|
||||
results[i]["growth_rate"] = float64(0)
|
||||
}
|
||||
} else {
|
||||
results[i]["growth_rate"] = float64(0)
|
||||
}
|
||||
delete(results[i], "prev_total")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Helper function to safely convert interface{} to float64
|
||||
func getFloat64FromInterface(v interface{}) float64 {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
return val
|
||||
case int64:
|
||||
return float64(val)
|
||||
case int:
|
||||
return float64(val)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// GetTopSenders gets top letter senders
|
||||
func (r *AnalyticsRepository) GetTopSenders(ctx context.Context, limit int, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
u.id as user_id,
|
||||
u.name as user_name,
|
||||
u.email as user_email,
|
||||
COALESCE(d.name, 'No Department') as department,
|
||||
COUNT(lo.id) as letter_count,
|
||||
AVG(EXTRACT(EPOCH FROM (lo.updated_at - lo.created_at))/3600) as avg_response_time
|
||||
FROM users u
|
||||
LEFT JOIN letters_outgoing lo ON lo.created_by = u.id
|
||||
LEFT JOIN user_department ud ON ud.user_id = u.id
|
||||
LEFT JOIN departments d ON d.id = ud.department_id
|
||||
WHERE lo.deleted_at IS NULL
|
||||
%s
|
||||
GROUP BY u.id, u.name, u.email, d.name
|
||||
ORDER BY letter_count DESC
|
||||
LIMIT %d
|
||||
`
|
||||
|
||||
dateFilter := ""
|
||||
if !startDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND lo.created_at >= '%s'", startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND lo.created_at <= '%s'", endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
query = fmt.Sprintf(query, dateFilter, limit)
|
||||
|
||||
if err := db.Raw(query).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetInstitutionStats gets statistics per institution using summary tables
|
||||
func (r *AnalyticsRepository) GetInstitutionStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
// Use summary table for better performance
|
||||
query := `
|
||||
SELECT
|
||||
i.id as institution_id,
|
||||
i.name as institution_name,
|
||||
i.type as institution_type,
|
||||
COALESCE(SUM(ils.incoming_sent), 0) as incoming_count,
|
||||
COALESCE(SUM(ils.outgoing_received), 0) as outgoing_count,
|
||||
COALESCE(SUM(ils.total_correspondence), 0) as total_count,
|
||||
MAX(ils.last_activity_at) as last_activity
|
||||
FROM institutions i
|
||||
LEFT JOIN institution_letter_summary ils ON ils.institution_id = i.id
|
||||
WHERE 1=1
|
||||
%s
|
||||
GROUP BY i.id, i.name, i.type
|
||||
HAVING COALESCE(SUM(ils.total_correspondence), 0) > 0
|
||||
ORDER BY total_count DESC
|
||||
`
|
||||
|
||||
dateFilter := ""
|
||||
if !startDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND ils.summary_date >= '%s'", startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND ils.summary_date <= '%s'", endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
query = fmt.Sprintf(query, dateFilter)
|
||||
|
||||
if err := db.Raw(query).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetApprovalMetrics gets approval-related metrics using summary tables
|
||||
func (r *AnalyticsRepository) GetApprovalMetrics(ctx context.Context, startDate, endDate time.Time) (map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
metrics := make(map[string]interface{})
|
||||
|
||||
// Use summary table for better performance
|
||||
query := db.Table("approval_sla_summary")
|
||||
|
||||
if !startDate.IsZero() {
|
||||
query = query.Where("summary_date >= ?", startDate)
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
query = query.Where("summary_date <= ?", endDate)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
TotalApprovals int64 `gorm:"column:total_approvals"`
|
||||
ApprovedCount int64 `gorm:"column:approved_count"`
|
||||
RejectedCount int64 `gorm:"column:rejected_count"`
|
||||
PendingCount int64 `gorm:"column:pending_count"`
|
||||
AvgApprovalHours float64 `gorm:"column:avg_approval_hours"`
|
||||
AvgApprovalSteps float64 `gorm:"column:avg_approval_steps"`
|
||||
SLACompliance float64 `gorm:"column:sla_compliance"`
|
||||
WithinSLA int64 `gorm:"column:within_sla"`
|
||||
ExceededSLA int64 `gorm:"column:exceeded_sla"`
|
||||
}
|
||||
|
||||
query.Select(`
|
||||
COALESCE(SUM(total_approvals), 0) as total_approvals,
|
||||
COALESCE(SUM(approved_count), 0) as approved_count,
|
||||
COALESCE(SUM(rejected_count), 0) as rejected_count,
|
||||
COALESCE(SUM(pending_count), 0) as pending_count,
|
||||
COALESCE(AVG(avg_approval_hours), 0) as avg_approval_hours,
|
||||
COALESCE(AVG(avg_approval_steps), 0) as avg_approval_steps,
|
||||
COALESCE(AVG(sla_compliance_rate), 0) as sla_compliance,
|
||||
COALESCE(SUM(within_sla_count), 0) as within_sla,
|
||||
COALESCE(SUM(exceeded_sla_count), 0) as exceeded_sla
|
||||
`).Scan(&result)
|
||||
|
||||
metrics["total_submitted"] = result.TotalApprovals
|
||||
metrics["total_approved"] = result.ApprovedCount
|
||||
metrics["total_rejected"] = result.RejectedCount
|
||||
metrics["total_pending"] = result.PendingCount
|
||||
metrics["avg_approval_time"] = result.AvgApprovalHours
|
||||
metrics["avg_approval_steps"] = result.AvgApprovalSteps
|
||||
metrics["sla_compliance_rate"] = result.SLACompliance
|
||||
metrics["within_sla_count"] = result.WithinSLA
|
||||
metrics["exceeded_sla_count"] = result.ExceededSLA
|
||||
|
||||
// Calculate rates
|
||||
if result.TotalApprovals > 0 {
|
||||
metrics["approval_rate"] = float64(result.ApprovedCount) / float64(result.TotalApprovals) * 100
|
||||
metrics["rejection_rate"] = float64(result.RejectedCount) / float64(result.TotalApprovals) * 100
|
||||
} else {
|
||||
metrics["approval_rate"] = float64(0)
|
||||
metrics["rejection_rate"] = float64(0)
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// GetDailyActivity gets daily activity data
|
||||
func (r *AnalyticsRepository) GetDailyActivity(ctx context.Context, days int) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
query := `
|
||||
WITH daily_data AS (
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
TO_CHAR(created_at, 'Day') as day_of_week,
|
||||
COUNT(CASE WHEN type = 'incoming' THEN 1 END) as incoming_count,
|
||||
COUNT(CASE WHEN type = 'outgoing' THEN 1 END) as outgoing_count,
|
||||
0 as approved_count,
|
||||
0 as rejected_count
|
||||
FROM (
|
||||
SELECT created_at, 'incoming' as type FROM letters_incoming WHERE deleted_at IS NULL
|
||||
UNION ALL
|
||||
SELECT created_at, 'outgoing' as type FROM letters_outgoing WHERE deleted_at IS NULL
|
||||
) combined
|
||||
WHERE created_at >= CURRENT_DATE - INTERVAL '%d days'
|
||||
GROUP BY DATE(created_at), TO_CHAR(created_at, 'Day')
|
||||
),
|
||||
approval_data AS (
|
||||
SELECT
|
||||
DATE(acted_at) as date,
|
||||
COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved_count,
|
||||
COUNT(CASE WHEN status = 'rejected' THEN 1 END) as rejected_count
|
||||
FROM letter_outgoing_approvals
|
||||
WHERE acted_at IS NOT NULL
|
||||
AND acted_at >= CURRENT_DATE - INTERVAL '%d days'
|
||||
GROUP BY DATE(acted_at)
|
||||
)
|
||||
SELECT
|
||||
d.date,
|
||||
d.day_of_week,
|
||||
d.incoming_count,
|
||||
d.outgoing_count,
|
||||
COALESCE(a.approved_count, 0) as approved_count,
|
||||
COALESCE(a.rejected_count, 0) as rejected_count
|
||||
FROM daily_data d
|
||||
LEFT JOIN approval_data a ON a.date = d.date
|
||||
ORDER BY d.date DESC
|
||||
LIMIT %d
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, days, days, days)
|
||||
|
||||
if err := db.Raw(query).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepository) GetDailyActivityByUserID(ctx context.Context, userID *uuid.UUID, days int) ([]map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var results []map[string]interface{}
|
||||
|
||||
query := `
|
||||
WITH daily_data AS (
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
TO_CHAR(created_at, 'Day') as day_of_week,
|
||||
COUNT(DISTINCT CASE WHEN type = 'incoming' THEN letter_id END) as incoming_count,
|
||||
COUNT(DISTINCT CASE WHEN type = 'outgoing' THEN letter_id END) as outgoing_count,
|
||||
0 as approved_count,
|
||||
0 as rejected_count
|
||||
FROM (
|
||||
SELECT li.id as letter_id, li.created_at, 'incoming' as type
|
||||
FROM letters_incoming li
|
||||
INNER JOIN letter_incoming_recipients lir ON lir.letter_id = li.id
|
||||
WHERE li.deleted_at IS NULL AND lir.recipient_user_id = ?
|
||||
UNION ALL
|
||||
SELECT lo.id as letter_id, lo.created_at, 'outgoing' as type
|
||||
FROM letters_outgoing lo
|
||||
INNER JOIN letter_outgoing_recipients lor ON lor.letter_id = lo.id
|
||||
WHERE lo.deleted_at IS NULL AND lor.user_id = ?
|
||||
) combined
|
||||
WHERE created_at >= CURRENT_DATE - INTERVAL '%d days'
|
||||
GROUP BY DATE(created_at), TO_CHAR(created_at, 'Day')
|
||||
),
|
||||
approval_data AS (
|
||||
SELECT
|
||||
DATE(acted_at) as date,
|
||||
COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved_count,
|
||||
COUNT(CASE WHEN status = 'rejected' THEN 1 END) as rejected_count
|
||||
FROM letter_outgoing_approvals
|
||||
WHERE acted_at IS NOT NULL
|
||||
AND acted_at >= CURRENT_DATE - INTERVAL '%d days'
|
||||
AND approver_id = ?
|
||||
GROUP BY DATE(acted_at)
|
||||
)
|
||||
SELECT
|
||||
d.date,
|
||||
d.day_of_week,
|
||||
d.incoming_count,
|
||||
d.outgoing_count,
|
||||
COALESCE(a.approved_count, 0) as approved_count,
|
||||
COALESCE(a.rejected_count, 0) as rejected_count
|
||||
FROM daily_data d
|
||||
LEFT JOIN approval_data a ON a.date = d.date
|
||||
ORDER BY d.date DESC
|
||||
LIMIT %d
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, days, days, days)
|
||||
|
||||
if err := db.Raw(query, userID, userID, userID).Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetResponseTimeStats gets response time statistics
|
||||
func (r *AnalyticsRepository) GetResponseTimeStats(ctx context.Context, startDate, endDate time.Time) (map[string]interface{}, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
query := `
|
||||
WITH response_times AS (
|
||||
SELECT
|
||||
EXTRACT(EPOCH FROM (updated_at - created_at))/3600 as response_time_hours
|
||||
FROM letters_outgoing
|
||||
WHERE status IN ('approved', 'sent', 'archived')
|
||||
AND deleted_at IS NULL
|
||||
%s
|
||||
)
|
||||
SELECT
|
||||
MIN(response_time_hours) as min_response_time,
|
||||
MAX(response_time_hours) as max_response_time,
|
||||
AVG(response_time_hours) as avg_response_time,
|
||||
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY response_time_hours) as median_response_time,
|
||||
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY response_time_hours) as p95_response_time,
|
||||
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_time_hours) as p99_response_time
|
||||
FROM response_times
|
||||
`
|
||||
|
||||
dateFilter := ""
|
||||
if !startDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND created_at >= '%s'", startDate.Format("2006-01-02"))
|
||||
}
|
||||
if !endDate.IsZero() {
|
||||
dateFilter += fmt.Sprintf(" AND created_at <= '%s'", endDate.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
query = fmt.Sprintf(query, dateFilter)
|
||||
|
||||
if err := db.Raw(query).Scan(&stats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"eslogad-be/internal/contract"
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AppSettingRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewAppSettingRepository(db *gorm.DB) *AppSettingRepository {
|
||||
return &AppSettingRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AppSettingRepository) Get(ctx context.Context, key string) (*entities.AppSetting, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.AppSetting
|
||||
if err := db.WithContext(ctx).First(&e, "key = ?", key).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
func (r *AppSettingRepository) Upsert(ctx context.Context, key string, value entities.JSONB) error {
|
||||
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
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ApprovalFlowRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewApprovalFlowRepository(db *gorm.DB) *ApprovalFlowRepository {
|
||||
return &ApprovalFlowRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowRepository) Create(ctx context.Context, e *entities.ApprovalFlow) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowRepository) Get(ctx context.Context, id uuid.UUID) (*entities.ApprovalFlow, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.ApprovalFlow
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("Department").
|
||||
Preload("Steps", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("step_order ASC, parallel_group ASC")
|
||||
}).
|
||||
Preload("Steps.ApproverRole").
|
||||
Preload("Steps.ApproverUser").
|
||||
Where("id = ?", id).
|
||||
First(&e).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowRepository) GetByDepartment(ctx context.Context, departmentID uuid.UUID) (*entities.ApprovalFlow, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.ApprovalFlow
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("Department").
|
||||
Preload("Steps", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("step_order ASC, parallel_group ASC")
|
||||
}).
|
||||
Preload("Steps.ApproverRole").
|
||||
Preload("Steps.ApproverUser").
|
||||
Where("department_id = ? AND is_active = true", departmentID).
|
||||
First(&e).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowRepository) Update(ctx context.Context, e *entities.ApprovalFlow) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Model(&entities.ApprovalFlow{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.ApprovalFlow{}).Error
|
||||
}
|
||||
|
||||
type ListApprovalFlowsFilter struct {
|
||||
DepartmentID *uuid.UUID
|
||||
Search *string
|
||||
IsActive *bool
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowRepository) List(ctx context.Context, filter ListApprovalFlowsFilter, limit, offset int) ([]entities.ApprovalFlow, int64, error) {
|
||||
var list []entities.ApprovalFlow
|
||||
var total int64
|
||||
|
||||
// Build base query for counting
|
||||
countQuery := r.db.WithContext(ctx).Model(&entities.ApprovalFlow{})
|
||||
|
||||
if filter.DepartmentID != nil {
|
||||
countQuery = countQuery.Where("department_id = ?", *filter.DepartmentID)
|
||||
}
|
||||
|
||||
if filter.IsActive != nil {
|
||||
countQuery = countQuery.Where("is_active = ?", *filter.IsActive)
|
||||
}
|
||||
|
||||
if filter.Search != nil && *filter.Search != "" {
|
||||
like := "%" + *filter.Search + "%"
|
||||
countQuery = countQuery.Where("name ILIKE ? OR description ILIKE ?", like, like)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
if err := countQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Build query for fetching data - BUAT QUERY BARU DARI AWAL
|
||||
dataQuery := r.db.WithContext(ctx).Model(&entities.ApprovalFlow{})
|
||||
|
||||
if filter.DepartmentID != nil {
|
||||
dataQuery = dataQuery.Where("department_id = ?", *filter.DepartmentID)
|
||||
}
|
||||
|
||||
if filter.IsActive != nil {
|
||||
dataQuery = dataQuery.Where("is_active = ?", *filter.IsActive)
|
||||
}
|
||||
|
||||
if filter.Search != nil && *filter.Search != "" {
|
||||
like := "%" + *filter.Search + "%"
|
||||
dataQuery = dataQuery.Where("name ILIKE ? OR description ILIKE ?", like, like)
|
||||
}
|
||||
|
||||
// Fetch data with pagination and preloads
|
||||
if err := dataQuery.
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Preload("Department").
|
||||
Preload("Steps", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("step_order ASC, parallel_group ASC")
|
||||
}).
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
type ApprovalFlowStepRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewApprovalFlowStepRepository(db *gorm.DB) *ApprovalFlowStepRepository {
|
||||
return &ApprovalFlowStepRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowStepRepository) Create(ctx context.Context, e *entities.ApprovalFlowStep) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowStepRepository) CreateBulk(ctx context.Context, list []entities.ApprovalFlowStep) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
return db.WithContext(ctx).Create(&list).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowStepRepository) Update(ctx context.Context, e *entities.ApprovalFlowStep) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Model(&entities.ApprovalFlowStep{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowStepRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.ApprovalFlowStep{}).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowStepRepository) DeleteByFlow(ctx context.Context, flowID uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Where("flow_id = ?", flowID).Delete(&entities.ApprovalFlowStep{}).Error
|
||||
}
|
||||
|
||||
func (r *ApprovalFlowStepRepository) ListByFlow(ctx context.Context, flowID uuid.UUID) ([]entities.ApprovalFlowStep, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.ApprovalFlowStep
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("ApproverRole").
|
||||
Preload("ApproverUser").
|
||||
Where("flow_id = ?", flowID).
|
||||
Order("step_order ASC, parallel_group ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type LetterOutgoingApprovalRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLetterOutgoingApprovalRepository(db *gorm.DB) *LetterOutgoingApprovalRepository {
|
||||
return &LetterOutgoingApprovalRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) Create(ctx context.Context, e *entities.LetterOutgoingApproval) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingApproval) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
return db.WithContext(ctx).Create(&list).Error
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoingApproval, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.LetterOutgoingApproval
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("Letter").
|
||||
Preload("Step").
|
||||
Preload("Approver").
|
||||
Where("id = ?", id).
|
||||
First(&e).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) GetByLetterAndStep(ctx context.Context, letterID, stepID uuid.UUID) (*entities.LetterOutgoingApproval, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.LetterOutgoingApproval
|
||||
if err := db.WithContext(ctx).
|
||||
Where("letter_id = ? AND step_id = ?", letterID, stepID).
|
||||
First(&e).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) Update(ctx context.Context, e *entities.LetterOutgoingApproval) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Model(&entities.LetterOutgoingApproval{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingApproval, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterOutgoingApproval
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("Step.ApproverRole").
|
||||
Preload("Step.ApproverUser").
|
||||
Preload("Approver").
|
||||
Where("letter_id = ?", letterID).
|
||||
Order("created_at ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) ListByLetterAndLasRevisionNumber(ctx context.Context, letterID uuid.UUID, revisionNumber int) ([]entities.LetterOutgoingApproval, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterOutgoingApproval
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("Step.ApproverRole").
|
||||
Preload("Step.ApproverUser").
|
||||
Preload("Approver").
|
||||
Where("letter_id = ? AND revision_number = ?", letterID, revisionNumber).
|
||||
Order("created_at ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *LetterOutgoingApprovalRepository) GetPendingApprovals(ctx context.Context, userID uuid.UUID) ([]entities.LetterOutgoingApproval, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterOutgoingApproval
|
||||
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("Letter").
|
||||
Preload("Step").
|
||||
Joins("JOIN approval_flow_steps afs ON afs.id = letter_outgoing_approvals.step_id").
|
||||
Where("letter_outgoing_approvals.status = ? AND (afs.approver_user_id = ? OR afs.approver_role_id IN (SELECT role_id FROM user_roles WHERE user_id = ?))",
|
||||
entities.ApprovalStatusPending, userID, userID).
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DispositionRouteRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewDispositionRouteRepository(db *gorm.DB) *DispositionRouteRepository {
|
||||
return &DispositionRouteRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *DispositionRouteRepository) Create(ctx context.Context, e *entities.DispositionRoute) error {
|
||||
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
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("FromDepartment").
|
||||
Preload("ToDepartment").
|
||||
First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
func (r *DispositionRouteRepository) ListByFromDept(ctx context.Context, fromDept uuid.UUID) ([]entities.DispositionRoute, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.DispositionRoute
|
||||
if err := db.WithContext(ctx).Where("from_department_id = ? and is_active=true", fromDept).
|
||||
Preload("FromDepartment").
|
||||
Preload("ToDepartment").
|
||||
Order("to_department_id").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DocumentSessionRepository handles document session operations
|
||||
type DocumentSessionRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDocumentSessionRepository(db *gorm.DB) *DocumentSessionRepository {
|
||||
return &DocumentSessionRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *DocumentSessionRepository) Create(ctx context.Context, session *entities.DocumentSession) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(session).Error
|
||||
}
|
||||
|
||||
func (r *DocumentSessionRepository) GetByKey(ctx context.Context, documentKey string) (*entities.DocumentSession, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var session entities.DocumentSession
|
||||
err := db.WithContext(ctx).
|
||||
Preload("User").
|
||||
Where("document_key = ?", documentKey).
|
||||
First(&session).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func (r *DocumentSessionRepository) GetActiveByDocument(ctx context.Context, documentID uuid.UUID) (*entities.DocumentSession, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var session entities.DocumentSession
|
||||
err := db.WithContext(ctx).
|
||||
Preload("User").
|
||||
Where("document_id = ? AND status != 4", documentID). // Status 4 = closed
|
||||
Order("created_at DESC").
|
||||
First(&session).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func (r *DocumentSessionRepository) Update(ctx context.Context, session *entities.DocumentSession) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
// Only update specific fields to avoid association issues
|
||||
updates := map[string]interface{}{
|
||||
"status": session.Status,
|
||||
"is_locked": session.IsLocked,
|
||||
"locked_by": session.LockedBy,
|
||||
"locked_at": session.LockedAt,
|
||||
"last_saved_at": session.LastSavedAt,
|
||||
"version": session.Version,
|
||||
"updated_at": session.UpdatedAt,
|
||||
}
|
||||
return db.WithContext(ctx).Model(&entities.DocumentSession{}).Where("id = ?", session.ID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *DocumentSessionRepository) ListByDocument(ctx context.Context, documentID uuid.UUID) ([]entities.DocumentSession, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var sessions []entities.DocumentSession
|
||||
err := db.WithContext(ctx).
|
||||
Preload("User").
|
||||
Where("document_id = ?", documentID).
|
||||
Order("created_at DESC").
|
||||
Find(&sessions).Error
|
||||
return sessions, err
|
||||
}
|
||||
|
||||
// DocumentVersionRepository handles document version operations
|
||||
type DocumentVersionRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDocumentVersionRepository(db *gorm.DB) *DocumentVersionRepository {
|
||||
return &DocumentVersionRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *DocumentVersionRepository) Create(ctx context.Context, version *entities.DocumentVersion) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(version).Error
|
||||
}
|
||||
|
||||
func (r *DocumentVersionRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.DocumentVersion, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var version entities.DocumentVersion
|
||||
err := db.WithContext(ctx).
|
||||
Preload("User").
|
||||
Where("id = ?", id).
|
||||
First(&version).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
func (r *DocumentVersionRepository) GetActiveVersion(ctx context.Context, documentID uuid.UUID) (*entities.DocumentVersion, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var version entities.DocumentVersion
|
||||
err := db.WithContext(ctx).
|
||||
Preload("User").
|
||||
Where("document_id = ? AND is_active = ?", documentID, true).
|
||||
First(&version).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
func (r *DocumentVersionRepository) ListByDocument(ctx context.Context, documentID uuid.UUID) ([]entities.DocumentVersion, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var versions []entities.DocumentVersion
|
||||
err := db.WithContext(ctx).
|
||||
Preload("User").
|
||||
Where("document_id = ?", documentID).
|
||||
Order("version DESC").
|
||||
Find(&versions).Error
|
||||
return versions, err
|
||||
}
|
||||
|
||||
func (r *DocumentVersionRepository) DeactivateAllVersions(ctx context.Context, documentID uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).
|
||||
Model(&entities.DocumentVersion{}).
|
||||
Where("document_id = ?", documentID).
|
||||
Update("is_active", false).Error
|
||||
}
|
||||
|
||||
// DocumentMetadataRepository handles document metadata operations
|
||||
type DocumentMetadataRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDocumentMetadataRepository(db *gorm.DB) *DocumentMetadataRepository {
|
||||
return &DocumentMetadataRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *DocumentMetadataRepository) Create(ctx context.Context, metadata *entities.DocumentMetadata) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(metadata).Error
|
||||
}
|
||||
|
||||
func (r *DocumentMetadataRepository) GetByDocumentID(ctx context.Context, documentID uuid.UUID) (*entities.DocumentMetadata, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var metadata entities.DocumentMetadata
|
||||
err := db.WithContext(ctx).
|
||||
Where("document_id = ?", documentID).
|
||||
First(&metadata).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &metadata, nil
|
||||
}
|
||||
|
||||
func (r *DocumentMetadataRepository) Update(ctx context.Context, metadata *entities.DocumentMetadata) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
// Only update specific fields to avoid association issues
|
||||
updates := map[string]interface{}{
|
||||
"document_type": metadata.DocumentType,
|
||||
"reference_id": metadata.ReferenceID,
|
||||
"file_name": metadata.FileName,
|
||||
"file_type": metadata.FileType,
|
||||
"file_size": metadata.FileSize,
|
||||
"mime_type": metadata.MimeType,
|
||||
"updated_at": metadata.UpdatedAt,
|
||||
}
|
||||
return db.WithContext(ctx).Model(&entities.DocumentMetadata{}).Where("id = ?", metadata.ID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *DocumentMetadataRepository) Delete(ctx context.Context, documentID uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).
|
||||
Where("document_id = ?", documentID).
|
||||
Delete(&entities.DocumentMetadata{}).Error
|
||||
}
|
||||
|
||||
// DocumentErrorRepository handles document error logging
|
||||
type DocumentErrorRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDocumentErrorRepository(db *gorm.DB) *DocumentErrorRepository {
|
||||
return &DocumentErrorRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *DocumentErrorRepository) Create(ctx context.Context, docError *entities.DocumentError) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(docError).Error
|
||||
}
|
||||
|
||||
func (r *DocumentErrorRepository) ListByDocument(ctx context.Context, documentID uuid.UUID) ([]entities.DocumentError, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var errors []entities.DocumentError
|
||||
err := db.WithContext(ctx).
|
||||
Preload("Session").
|
||||
Where("document_id = ?", documentID).
|
||||
Order("created_at DESC").
|
||||
Find(&errors).Error
|
||||
return errors, err
|
||||
}
|
||||
|
||||
func (r *DocumentErrorRepository) ListBySession(ctx context.Context, sessionID uuid.UUID) ([]entities.DocumentError, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var errors []entities.DocumentError
|
||||
err := db.WithContext(ctx).
|
||||
Where("session_id = ?", sessionID).
|
||||
Order("created_at DESC").
|
||||
Find(&errors).Error
|
||||
return errors, err
|
||||
}
|
||||
@@ -1,818 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,991 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LetterIncomingRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLetterIncomingRepository(db *gorm.DB) *LetterIncomingRepository {
|
||||
return &LetterIncomingRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *LetterIncomingRepository) Create(ctx context.Context, e *entities.LetterIncoming) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
func (r *LetterIncomingRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterIncoming, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.LetterIncoming
|
||||
if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *LetterIncomingRepository) GetByReferenceNumber(ctx context.Context, refNumber *string) (*entities.LetterIncoming, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.LetterIncoming
|
||||
if err := db.WithContext(ctx).
|
||||
Where("reference_number = ? AND deleted_at IS NULL", refNumber).
|
||||
First(&e).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (r *LetterIncomingRepository) SoftDelete(ctx context.Context, id uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
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) BulkSoftDelete(ctx context.Context, ids []uuid.UUID) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Exec("UPDATE letters_incoming SET deleted_at = CURRENT_TIMESTAMP WHERE id IN ? AND deleted_at IS NULL", ids).Error
|
||||
}
|
||||
|
||||
func (r *LetterIncomingRepository) BulkArchive(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
now := time.Now()
|
||||
result := db.WithContext(ctx).
|
||||
Model(&entities.LetterIncoming{}).
|
||||
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 *LetterIncomingRepository) Archive(ctx context.Context, letterID uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
now := time.Now()
|
||||
return db.WithContext(ctx).
|
||||
Model(&entities.LetterIncoming{}).
|
||||
Where("id = ? AND deleted_at IS NULL", letterID).
|
||||
Updates(map[string]interface{}{
|
||||
"is_archived": true,
|
||||
"archived_at": now,
|
||||
}).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
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
query = query.Where("lidd.id IS NOT NULL AND lidd.status != 'pending'")
|
||||
} else {
|
||||
query = query.Where("lidd.id IS NULL OR lidd.status = 'pending'")
|
||||
}
|
||||
}
|
||||
|
||||
if len(filter.PriorityIDs) > 0 {
|
||||
query = query.Where("letters_incoming.priority_id IN ?", filter.PriorityIDs)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if filter.Query != nil {
|
||||
q := "%" + *filter.Query + "%"
|
||||
query = query.Where("letters_incoming.subject ILIKE ? OR letters_incoming.reference_number ILIKE ?", q, q)
|
||||
}
|
||||
|
||||
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
|
||||
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.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
|
||||
}
|
||||
|
||||
func (r *LetterIncomingRepository) ListAll(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")
|
||||
|
||||
// Apply filters (same as ListAll)
|
||||
if len(filter.PriorityIDs) > 0 {
|
||||
query = query.Where("letters_incoming.priority_id IN ?", filter.PriorityIDs)
|
||||
}
|
||||
|
||||
if filter.Status != nil {
|
||||
query = query.Where("letters_incoming.status = ?", *filter.Status)
|
||||
}
|
||||
|
||||
if filter.Query != nil {
|
||||
q := "%" + *filter.Query + "%"
|
||||
query = query.Where("letters_incoming.subject ILIKE ? OR letters_incoming.reference_number ILIKE ? OR letters_incoming.letter_number ILIKE ?", q, q, q)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated data with preloaded relations
|
||||
var list []entities.LetterIncoming
|
||||
if err := query.
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (r *LetterIncomingRepository) Search(ctx context.Context, filters map[string]interface{}, limit, offset int, sortBy, sortOrder string) ([]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
|
||||
|
||||
// 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 sender_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["sender_institution_id"]; ok {
|
||||
query = query.Where("sender_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("received_date >= ?", dateFrom)
|
||||
}
|
||||
|
||||
if dateTo, ok := filters["date_to"]; ok {
|
||||
query = query.Where("received_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 where they are recipients
|
||||
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
|
||||
Where("letter_incoming_recipients.recipient_user_id = ?", userID)
|
||||
joinedRecipients = true
|
||||
needsGroupBy = true
|
||||
}
|
||||
if departmentID, ok := ctx["department_id"]; ok {
|
||||
// Also include letters for user's department
|
||||
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_department_id = ?", departmentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count total results
|
||||
var total int64
|
||||
if needsGroupBy {
|
||||
// For grouped queries, count distinct letter IDs
|
||||
if err := db.WithContext(ctx).Model(&entities.LetterIncoming{}).
|
||||
Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
|
||||
Where("letters_incoming.deleted_at IS NULL").
|
||||
Distinct("letters_incoming.id").
|
||||
Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
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,
|
||||
"received_date": true,
|
||||
"status": true,
|
||||
"created_at": true,
|
||||
"updated_at": true,
|
||||
}
|
||||
|
||||
if !validSortFields[sortBy] {
|
||||
sortBy = "created_at"
|
||||
}
|
||||
|
||||
if sortOrder != "asc" && sortOrder != "desc" {
|
||||
sortOrder = "desc"
|
||||
}
|
||||
|
||||
orderBy := "letters_incoming." + sortBy + " " + sortOrder
|
||||
|
||||
// Apply grouping if necessary
|
||||
if needsGroupBy {
|
||||
query = query.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")
|
||||
}
|
||||
|
||||
// Execute query
|
||||
var letters []entities.LetterIncoming
|
||||
if err := query.
|
||||
Order(orderBy).
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&letters).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return letters, total, nil
|
||||
}
|
||||
|
||||
type LetterIncomingAttachmentRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLetterIncomingAttachmentRepository(db *gorm.DB) *LetterIncomingAttachmentRepository {
|
||||
return &LetterIncomingAttachmentRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *LetterIncomingAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterIncomingAttachment) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(&list).Error
|
||||
}
|
||||
func (r *LetterIncomingAttachmentRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingAttachment, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterIncomingAttachment
|
||||
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 *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 {
|
||||
return &LetterIncomingActivityLogRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *LetterIncomingActivityLogRepository) Create(ctx context.Context, e *entities.LetterIncomingActivityLog) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
|
||||
func (r *LetterIncomingActivityLogRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingActivityLog, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterIncomingActivityLog
|
||||
if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("occurred_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type LetterIncomingDispositionRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLetterIncomingDispositionRepository(db *gorm.DB) *LetterIncomingDispositionRepository {
|
||||
return &LetterIncomingDispositionRepository{db: db}
|
||||
}
|
||||
func (r *LetterIncomingDispositionRepository) Create(ctx context.Context, e *entities.LetterIncomingDisposition) error {
|
||||
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
|
||||
if err := db.WithContext(ctx).
|
||||
Where("letter_id = ?", letterID).
|
||||
Preload("Department").
|
||||
Preload("Departments.Department").
|
||||
Preload("ActionSelections.Action").
|
||||
Preload("DispositionNotes.User").
|
||||
Order("created_at ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *LetterIncomingDispositionRepository) ListByLetterIDs(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterIncomingDisposition, error) {
|
||||
if len(letterIDs) == 0 {
|
||||
return make(map[uuid.UUID][]entities.LetterIncomingDisposition), nil
|
||||
}
|
||||
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var dispositions []entities.LetterIncomingDisposition
|
||||
if err := db.WithContext(ctx).Where("letter_id IN ?", letterIDs).
|
||||
Preload("Department").
|
||||
Preload("Departments.Department").
|
||||
Preload("ActionSelections.Action").
|
||||
Preload("DispositionNotes.User").
|
||||
Order("created_at ASC").
|
||||
Find(&dispositions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Group by letter ID
|
||||
result := make(map[uuid.UUID][]entities.LetterIncomingDisposition)
|
||||
for i := range dispositions { // Gunakan index, bukan value
|
||||
letterID := dispositions[i].LetterID
|
||||
result[letterID] = append(result[letterID], dispositions[i])
|
||||
}
|
||||
|
||||
return result, 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
|
||||
if err := db.WithContext(ctx).Where("letter_incoming_disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *LetterIncomingDispositionDepartmentRepository) ListByDispositions(ctx context.Context, dispositionIDs []uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterIncomingDispositionDepartment
|
||||
if len(dispositionIDs) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := db.WithContext(ctx).Where("letter_incoming_disposition_id IN ?", dispositionIDs).Order("letter_incoming_disposition_id, created_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type DispositionNoteRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewDispositionNoteRepository(db *gorm.DB) *DispositionNoteRepository {
|
||||
return &DispositionNoteRepository{db: db}
|
||||
}
|
||||
func (r *DispositionNoteRepository) Create(ctx context.Context, e *entities.DispositionNote) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
|
||||
func (r *DispositionNoteRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.DispositionNote, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.DispositionNote
|
||||
if err := db.WithContext(ctx).Where("disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *DispositionNoteRepository) ListByDispositions(ctx context.Context, dispositionIDs []uuid.UUID) ([]entities.DispositionNote, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.DispositionNote
|
||||
if len(dispositionIDs) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := db.WithContext(ctx).Where("disposition_id IN ?", dispositionIDs).Order("disposition_id, created_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type LetterDispositionActionSelectionRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLetterDispositionActionSelectionRepository(db *gorm.DB) *LetterDispositionActionSelectionRepository {
|
||||
return &LetterDispositionActionSelectionRepository{db: db}
|
||||
}
|
||||
func (r *LetterDispositionActionSelectionRepository) CreateBulk(ctx context.Context, list []entities.LetterDispositionActionSelection) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(&list).Error
|
||||
}
|
||||
func (r *LetterDispositionActionSelectionRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.LetterDispositionActionSelection, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterDispositionActionSelection
|
||||
if err := db.WithContext(ctx).Where("disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *LetterDispositionActionSelectionRepository) ListByDispositions(ctx context.Context, dispositionIDs []uuid.UUID) ([]entities.LetterDispositionActionSelection, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterDispositionActionSelection
|
||||
if len(dispositionIDs) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := db.WithContext(ctx).Where("disposition_id IN ?", dispositionIDs).Order("disposition_id, created_at ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
type LetterDiscussionRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLetterDiscussionRepository(db *gorm.DB) *LetterDiscussionRepository {
|
||||
return &LetterDiscussionRepository{db: db}
|
||||
}
|
||||
func (r *LetterDiscussionRepository) Create(ctx context.Context, e *entities.LetterDiscussion) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
func (r *LetterDiscussionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterDiscussion, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var e entities.LetterDiscussion
|
||||
if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
func (r *LetterDiscussionRepository) Update(ctx context.Context, e *entities.LetterDiscussion) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
// ensure edited_at is set when updating
|
||||
if e.EditedAt == nil {
|
||||
now := time.Now()
|
||||
e.EditedAt = &now
|
||||
}
|
||||
return db.WithContext(ctx).Model(&entities.LetterDiscussion{}).
|
||||
Where("id = ?", e.ID).
|
||||
Updates(map[string]interface{}{"message": e.Message, "mentions": e.Mentions, "edited_at": e.EditedAt}).Error
|
||||
}
|
||||
|
||||
func (r *LetterDiscussionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterDiscussion, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var list []entities.LetterDiscussion
|
||||
if err := db.WithContext(ctx).
|
||||
Where("letter_id = ?", letterID).
|
||||
Preload("User.Profile").
|
||||
Order("created_at ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *LetterDiscussionRepository) GetUsersByIDs(ctx context.Context, userIDs []uuid.UUID) ([]entities.User, error) {
|
||||
if len(userIDs) == 0 {
|
||||
return []entities.User{}, nil
|
||||
}
|
||||
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var users []entities.User
|
||||
if err := db.WithContext(ctx).
|
||||
Where("id IN ?", userIDs).
|
||||
Preload("Profile").
|
||||
Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// recipients
|
||||
|
||||
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
|
||||
if err := db.WithContext(ctx).
|
||||
Model(&entities.LetterIncomingRecipient{}).
|
||||
Where("recipient_department_id = ?", departmentID).
|
||||
Distinct("letter_id").
|
||||
Pluck("letter_id", &letterIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
|
||||
sql := `
|
||||
WITH valid_recipients AS (
|
||||
SELECT lir.id, lir.letter_id, lir.read_at, lir.created_at,
|
||||
ROW_NUMBER() OVER (PARTITION BY lir.letter_id ORDER BY lir.created_at DESC) as rn
|
||||
FROM letter_incoming_recipients lir
|
||||
INNER JOIN letters_incoming l ON l.id = lir.letter_id AND l.deleted_at IS NULL
|
||||
WHERE lir.recipient_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 *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(`id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (PARTITION BY letter_id ORDER BY created_at DESC) as rn
|
||||
FROM letter_incoming_recipients
|
||||
WHERE letter_id IN ? AND recipient_user_id = ?
|
||||
) t WHERE rn = 1
|
||||
)`, 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
|
||||
if err := db.WithContext(ctx).
|
||||
Model(&entities.LetterIncomingRecipient{}).
|
||||
Where("letter_id = ? AND recipient_department_id = ?", letterID, departmentID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LabelRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewLabelRepository(db *gorm.DB) *LabelRepository { return &LabelRepository{db: db} }
|
||||
func (r *LabelRepository) Create(ctx context.Context, e *entities.Label) error {
|
||||
return r.db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
func (r *LabelRepository) Update(ctx context.Context, e *entities.Label) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Label{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
func (r *LabelRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Label{}, "id = ?", id).Error
|
||||
}
|
||||
func (r *LabelRepository) List(ctx context.Context) ([]entities.Label, error) {
|
||||
var list []entities.Label
|
||||
err := r.db.WithContext(ctx).Order("name ASC").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
func (r *LabelRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Label, error) {
|
||||
var e entities.Label
|
||||
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
type PriorityRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewPriorityRepository(db *gorm.DB) *PriorityRepository { return &PriorityRepository{db: db} }
|
||||
func (r *PriorityRepository) Create(ctx context.Context, e *entities.Priority) error {
|
||||
return r.db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
func (r *PriorityRepository) Update(ctx context.Context, e *entities.Priority) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Priority{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
func (r *PriorityRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Priority{}, "id = ?", id).Error
|
||||
}
|
||||
func (r *PriorityRepository) List(ctx context.Context) ([]entities.Priority, error) {
|
||||
var list []entities.Priority
|
||||
err := r.db.WithContext(ctx).Order("level ASC").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
func (r *PriorityRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Priority, error) {
|
||||
var e entities.Priority
|
||||
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
return &InstitutionRepository{db: db}
|
||||
}
|
||||
func (r *InstitutionRepository) Create(ctx context.Context, e *entities.Institution) error {
|
||||
return r.db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
func (r *InstitutionRepository) Update(ctx context.Context, e *entities.Institution) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Institution{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
func (r *InstitutionRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Institution{}, "id = ?", id).Error
|
||||
}
|
||||
func (r *InstitutionRepository) List(ctx context.Context) ([]entities.Institution, error) {
|
||||
var list []entities.Institution
|
||||
err := r.db.WithContext(ctx).Order("name ASC").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (r *InstitutionRepository) ListWithSearch(ctx context.Context, search *string) ([]entities.Institution, error) {
|
||||
var list []entities.Institution
|
||||
q := r.db.WithContext(ctx).Model(&entities.Institution{})
|
||||
|
||||
if search != nil && *search != "" {
|
||||
like := "%" + *search + "%"
|
||||
q = q.Where("name ILIKE ? OR type ILIKE ? OR address ILIKE ? OR contact_person ILIKE ?", like, like, like, like)
|
||||
}
|
||||
|
||||
err := q.Order("name ASC").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (r *InstitutionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Institution, error) {
|
||||
var e entities.Institution
|
||||
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
return &DispositionActionRepository{db: db}
|
||||
}
|
||||
func (r *DispositionActionRepository) Create(ctx context.Context, e *entities.DispositionAction) error {
|
||||
return r.db.WithContext(ctx).Create(e).Error
|
||||
}
|
||||
func (r *DispositionActionRepository) Update(ctx context.Context, e *entities.DispositionAction) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.DispositionAction{}).Where("id = ?", e.ID).Updates(e).Error
|
||||
}
|
||||
func (r *DispositionActionRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.DispositionAction{}, "id = ?", id).Error
|
||||
}
|
||||
func (r *DispositionActionRepository) List(ctx context.Context) ([]entities.DispositionAction, error) {
|
||||
var list []entities.DispositionAction
|
||||
err := r.db.WithContext(ctx).Order("sort_order NULLS LAST, label ASC").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
func (r *DispositionActionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.DispositionAction, error) {
|
||||
var e entities.DispositionAction
|
||||
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *DispositionActionRepository) GetByIDs(ctx context.Context, ids []uuid.UUID) ([]entities.DispositionAction, error) {
|
||||
var actions []entities.DispositionAction
|
||||
if len(ids) == 0 {
|
||||
return actions, nil
|
||||
}
|
||||
if err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&actions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
type DepartmentRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewDepartmentRepository(db *gorm.DB) *DepartmentRepository { return &DepartmentRepository{db: db} }
|
||||
|
||||
func (r *DepartmentRepository) GetByCode(ctx context.Context, code string) (*entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var dep entities.Department
|
||||
if err := db.WithContext(ctx).Where("code = ?", code).First(&dep).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dep, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var dep entities.Department
|
||||
if err := db.WithContext(ctx).First(&dep, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dep, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) List(ctx context.Context, search string, limit, offset int) ([]entities.Department, int64, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
|
||||
query := db.WithContext(ctx).Model(&entities.Department{})
|
||||
|
||||
// Add search filter if provided
|
||||
if search != "" {
|
||||
query = query.Where("name ILIKE ?", "%"+search+"%")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated results
|
||||
var list []entities.Department
|
||||
if err := query.
|
||||
Order("name ASC").
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) ListWithParentFilter(ctx context.Context, search string, limit, offset int, parentPath string, excludedPaths []string) ([]entities.Department, int64, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
|
||||
query := db.WithContext(ctx).Model(&entities.Department{})
|
||||
|
||||
// Filter by parent path if provided - include the parent itself and all descendants
|
||||
if parentPath != "" {
|
||||
query = query.Where("path = ? OR path <@ ?", parentPath, parentPath)
|
||||
}
|
||||
|
||||
// Exclude specific paths
|
||||
for _, excludedPath := range excludedPaths {
|
||||
query = query.Where("NOT (path ~ ?)", excludedPath)
|
||||
}
|
||||
|
||||
// Add search filter if provided
|
||||
if search != "" {
|
||||
query = query.Where("name ILIKE ? OR code ILIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated results
|
||||
var list []entities.Department
|
||||
if err := query.
|
||||
Order("name ASC").
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) Create(ctx context.Context, department *entities.Department) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Create(department).Error
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) Update(ctx context.Context, department *entities.Department) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Save(department).Error
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
return db.WithContext(ctx).Delete(&entities.Department{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) GetByPath(ctx context.Context, path string) (*entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var department entities.Department
|
||||
if err := db.WithContext(ctx).Where("path = ?", path).First(&department).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &department, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) GetAll(ctx context.Context) ([]entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var departments []entities.Department
|
||||
if err := db.WithContext(ctx).Order("path ASC").Find(&departments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return departments, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) GetAllWithParentFilter(ctx context.Context, parentPath string, excludedPaths []string) ([]entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var departments []entities.Department
|
||||
|
||||
query := db.WithContext(ctx)
|
||||
|
||||
// Filter by parent path if provided - include the parent itself and all descendants
|
||||
if parentPath != "" {
|
||||
query = query.Where("path = ? OR path <@ ?", parentPath, parentPath)
|
||||
}
|
||||
|
||||
// Exclude specific paths
|
||||
for _, excludedPath := range excludedPaths {
|
||||
query = query.Where("NOT (path ~ ?)", excludedPath)
|
||||
}
|
||||
|
||||
if err := query.Order("path ASC").Find(&departments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return departments, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) GetByPathPrefix(ctx context.Context, pathPrefix string) ([]entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var departments []entities.Department
|
||||
// Using ltree operators for hierarchical queries
|
||||
query := db.WithContext(ctx).Order("path ASC")
|
||||
if pathPrefix != "" {
|
||||
// Get all descendants of a path
|
||||
query = query.Where("path <@ ?", pathPrefix)
|
||||
}
|
||||
if err := query.Find(&departments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return departments, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) GetChildren(ctx context.Context, parentPath string) ([]entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
var departments []entities.Department
|
||||
// Get direct children and all descendants
|
||||
if err := db.WithContext(ctx).
|
||||
Where("path <@ ? AND path != ?", parentPath, parentPath).
|
||||
Order("path ASC").
|
||||
Find(&departments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return departments, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) GetAllDescendants(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
|
||||
// First get the parent department to get its path
|
||||
var parent entities.Department
|
||||
if err := db.WithContext(ctx).First(&parent, "id = ?", parentID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var departments []entities.Department
|
||||
// Get all descendants using ltree
|
||||
if err := db.WithContext(ctx).
|
||||
Where("path <@ ? AND path != ?", parent.Path, parent.Path).
|
||||
Order("path ASC").
|
||||
Find(&departments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return departments, nil
|
||||
}
|
||||
|
||||
func (r *DepartmentRepository) UpdateChildrenPaths(ctx context.Context, oldPath, newPath string) error {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
// Use raw SQL for ltree path update
|
||||
// This will update all children paths by replacing the old prefix with the new one
|
||||
query := `
|
||||
UPDATE departments
|
||||
SET path = ? || subpath(path, nlevel(?))
|
||||
WHERE path <@ ? AND path != ?
|
||||
`
|
||||
return db.WithContext(ctx).Exec(query, newPath, oldPath, oldPath, oldPath).Error
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RBACRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRBACRepository(db *gorm.DB) *RBACRepository { return &RBACRepository{db: db} }
|
||||
|
||||
// Permissions
|
||||
func (r *RBACRepository) CreatePermission(ctx context.Context, p *entities.Permission) error {
|
||||
return r.db.WithContext(ctx).Create(p).Error
|
||||
}
|
||||
func (r *RBACRepository) UpdatePermission(ctx context.Context, p *entities.Permission) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Permission{}).Where("id = ?", p.ID).Updates(p).Error
|
||||
}
|
||||
func (r *RBACRepository) DeletePermission(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Permission{}, "id = ?", id).Error
|
||||
}
|
||||
func (r *RBACRepository) ListPermissions(ctx context.Context) ([]entities.Permission, error) {
|
||||
var perms []entities.Permission
|
||||
if err := r.db.WithContext(ctx).Preload("Module").Order("code ASC").Find(&perms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
func (r *RBACRepository) GetPermissionByCode(ctx context.Context, code string) (*entities.Permission, error) {
|
||||
var p entities.Permission
|
||||
if err := r.db.WithContext(ctx).First(&p, "code = ?", code).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Roles
|
||||
func (r *RBACRepository) CreateRole(ctx context.Context, role *entities.Role) error {
|
||||
return r.db.WithContext(ctx).Create(role).Error
|
||||
}
|
||||
func (r *RBACRepository) UpdateRole(ctx context.Context, role *entities.Role) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Role{}).Where("id = ?", role.ID).Updates(role).Error
|
||||
}
|
||||
func (r *RBACRepository) DeleteRole(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Role{}, "id = ?", id).Error
|
||||
}
|
||||
func (r *RBACRepository) ListRoles(ctx context.Context) ([]entities.Role, error) {
|
||||
var roles []entities.Role
|
||||
if err := r.db.WithContext(ctx).Order("name ASC").Find(&roles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
func (r *RBACRepository) GetRoleByCode(ctx context.Context, code string) (*entities.Role, error) {
|
||||
var role entities.Role
|
||||
if err := r.db.WithContext(ctx).First(&role, "code = ?", code).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
func (r *RBACRepository) SetRolePermissionsByCodes(ctx context.Context, roleID uuid.UUID, permCodes []string) error {
|
||||
if err := r.db.WithContext(ctx).Where("role_id = ?", roleID).Delete(&entities.RolePermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(permCodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
var perms []entities.Permission
|
||||
if err := r.db.WithContext(ctx).Where("code IN ?", permCodes).Find(&perms).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
pairs := make([]entities.RolePermission, 0, len(perms))
|
||||
for _, p := range perms {
|
||||
pairs = append(pairs, entities.RolePermission{RoleID: roleID, PermissionID: p.ID})
|
||||
}
|
||||
return r.db.WithContext(ctx).Create(&pairs).Error
|
||||
}
|
||||
|
||||
func (r *RBACRepository) GetPermissionsByRoleID(ctx context.Context, roleID uuid.UUID) ([]entities.Permission, error) {
|
||||
var perms []entities.Permission
|
||||
if err := r.db.WithContext(ctx).
|
||||
Preload("Module").
|
||||
Table("permissions p").
|
||||
Select("p.*").
|
||||
Joins("JOIN role_permissions rp ON rp.permission_id = p.id").
|
||||
Where("rp.role_id = ?", roleID).
|
||||
Find(&perms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
// Modules
|
||||
func (r *RBACRepository) CreateModule(ctx context.Context, m *entities.Module) error {
|
||||
return r.db.WithContext(ctx).Create(m).Error
|
||||
}
|
||||
|
||||
func (r *RBACRepository) UpdateModule(ctx context.Context, m *entities.Module) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Module{}).Where("id = ?", m.ID).Updates(m).Error
|
||||
}
|
||||
|
||||
func (r *RBACRepository) DeleteModule(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Module{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *RBACRepository) ListModules(ctx context.Context) ([]entities.Module, error) {
|
||||
var modules []entities.Module
|
||||
if err := r.db.WithContext(ctx).Order("name ASC").Find(&modules).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return modules, nil
|
||||
}
|
||||
|
||||
func (r *RBACRepository) GetModuleByCode(ctx context.Context, code string) (*entities.Module, error) {
|
||||
var m entities.Module
|
||||
if err := r.db.WithContext(ctx).First(&m, "code = ?", code).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (r *RBACRepository) GetModuleByID(ctx context.Context, id uuid.UUID) (*entities.Module, error) {
|
||||
var m entities.Module
|
||||
if err := r.db.WithContext(ctx).First(&m, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (r *RBACRepository) GetPermissionsGroupedByModule(ctx context.Context) ([]entities.Module, error) {
|
||||
var modules []entities.Module
|
||||
if err := r.db.WithContext(ctx).
|
||||
Preload("Permissions", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("action ASC")
|
||||
}).
|
||||
Find(&modules).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return modules, nil
|
||||
}
|
||||
|
||||
func (r *RBACRepository) GetRoleByID(ctx context.Context, id uuid.UUID) (*entities.Role, error) {
|
||||
var role entities.Role
|
||||
if err := r.db.WithContext(ctx).First(&role, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
func (r *RBACRepository) SetRolePermissionsByIDs(ctx context.Context, roleID uuid.UUID, permissionIDs []uuid.UUID) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Delete existing permissions
|
||||
if err := tx.WithContext(ctx).Where("role_id = ?", roleID).Delete(&entities.RolePermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add new permissions
|
||||
if len(permissionIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pairs := make([]entities.RolePermission, 0, len(permissionIDs))
|
||||
for _, permID := range permissionIDs {
|
||||
pairs = append(pairs, entities.RolePermission{RoleID: roleID, PermissionID: permID})
|
||||
}
|
||||
return tx.WithContext(ctx).Create(&pairs).Error
|
||||
})
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RepositoryAttachmentRepositoryImpl struct {
|
||||
b *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepositoryAttachmentRepositoryImpl(db *gorm.DB) *RepositoryAttachmentRepositoryImpl {
|
||||
return &RepositoryAttachmentRepositoryImpl{
|
||||
b: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RepositoryAttachmentRepositoryImpl) Create(ctx context.Context, user *entities.RepositoryAttachment) error {
|
||||
return r.b.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
|
||||
func (r *RepositoryAttachmentRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.RepositoryAttachment, error) {
|
||||
var attachment entities.RepositoryAttachment
|
||||
err := r.b.WithContext(ctx).
|
||||
First(&attachment, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &attachment, nil
|
||||
}
|
||||
|
||||
func (r *RepositoryAttachmentRepositoryImpl) Update(ctx context.Context, user *entities.RepositoryAttachment) error {
|
||||
return r.b.WithContext(ctx).Save(user).Error
|
||||
}
|
||||
|
||||
func (r *RepositoryAttachmentRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.b.WithContext(ctx).Delete(&entities.RepositoryAttachment{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *RepositoryAttachmentRepositoryImpl) List(ctx context.Context, search *string, limit, offset int) ([]*entities.RepositoryAttachment, int64, error) {
|
||||
var attachments []*entities.RepositoryAttachment
|
||||
var total int64
|
||||
|
||||
baseQuery := r.b.WithContext(ctx).Model(&entities.RepositoryAttachment{})
|
||||
|
||||
if search != nil && *search != "" {
|
||||
like := "%" + *search + "%"
|
||||
baseQuery = baseQuery.Where("name ILIKE ? OR email ILIKE ?", like, like)
|
||||
}
|
||||
|
||||
countQuery := baseQuery
|
||||
if err := countQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
dataQuery := r.b.WithContext(ctx).Model(&entities.RepositoryAttachment{})
|
||||
|
||||
if search != nil && *search != "" {
|
||||
like := "%" + *search + "%"
|
||||
dataQuery = dataQuery.Where("name ILIKE ? OR category ILIKE ?", like, like)
|
||||
}
|
||||
|
||||
if err := dataQuery.
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&attachments).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return attachments, total, nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TitleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewTitleRepository(db *gorm.DB) *TitleRepository {
|
||||
return &TitleRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *TitleRepository) ListAll(ctx context.Context) ([]entities.Title, error) {
|
||||
var titles []entities.Title
|
||||
if err := r.db.WithContext(ctx).Order("name ASC").Find(&titles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return titles, nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserDepartmentRepository struct{ db *gorm.DB }
|
||||
|
||||
func NewUserDepartmentRepository(db *gorm.DB) *UserDepartmentRepository {
|
||||
return &UserDepartmentRepository{db: db}
|
||||
}
|
||||
|
||||
type UserDepartmentRow struct {
|
||||
UserID uuid.UUID `gorm:"column:user_id"`
|
||||
DepartmentID uuid.UUID `gorm:"column:department_id"`
|
||||
}
|
||||
|
||||
func (r *UserDepartmentRepository) ListActiveByDepartmentIDs(ctx context.Context, departmentIDs []uuid.UUID) ([]UserDepartmentRow, error) {
|
||||
db := DBFromContext(ctx, r.db)
|
||||
rows := make([]UserDepartmentRow, 0)
|
||||
if len(departmentIDs) == 0 {
|
||||
return rows, nil
|
||||
}
|
||||
err := db.WithContext(ctx).
|
||||
Table("user_department").
|
||||
Select("user_id, department_id").
|
||||
Where("department_id IN ? AND removed_at IS NULL", departmentIDs).
|
||||
Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
"go-backend-template/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
|
||||
"eslogad-be/internal/entities"
|
||||
"go-backend-template/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
@@ -27,7 +27,6 @@ func (r *UserRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entiti
|
||||
var user entities.User
|
||||
err := r.b.WithContext(ctx).
|
||||
Preload("Profile").
|
||||
Preload("Departments").
|
||||
First(&user, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,7 +50,6 @@ func (r *UserRepositoryImpl) GetByEmail(ctx context.Context, email string) (*ent
|
||||
var user entities.User
|
||||
err := r.b.WithContext(ctx).
|
||||
Preload("Profile").
|
||||
Preload("Departments").
|
||||
Where("email = ?", email).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -59,18 +57,28 @@ func (r *UserRepositoryImpl) GetByEmail(ctx context.Context, email string) (*ent
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetByUsername(ctx context.Context, username string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
err := r.b.WithContext(ctx).
|
||||
Preload("Profile").
|
||||
Where("username = ?", username).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetByRole(ctx context.Context, role entities.UserRole) ([]*entities.User, error) {
|
||||
var users []*entities.User
|
||||
err := r.b.WithContext(ctx).Preload("Profile").Preload("Departments").Where("role = ?", role).Find(&users).Error
|
||||
err := r.b.WithContext(ctx).Preload("Profile").Where("role = ?", role).Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetActiveUsers(ctx context.Context, organizationID uuid.UUID) ([]*entities.User, error) {
|
||||
func (r *UserRepositoryImpl) GetActiveUsers(ctx context.Context) ([]*entities.User, error) {
|
||||
var users []*entities.User
|
||||
err := r.b.WithContext(ctx).
|
||||
Where(" is_active = ?", organizationID, true).
|
||||
Where("is_active = ?", true).
|
||||
Preload("Profile").
|
||||
Preload("Departments").
|
||||
Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
@@ -109,7 +117,7 @@ func (r *UserRepositoryImpl) List(ctx context.Context, filters map[string]interf
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err := query.Limit(limit).Offset(offset).Preload("Profile").Preload("Departments").Find(&users).Error
|
||||
err := query.Limit(limit).Offset(offset).Preload("Profile").Find(&users).Error
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
@@ -125,155 +133,50 @@ func (r *UserRepositoryImpl) Count(ctx context.Context, filters map[string]inter
|
||||
return count, err
|
||||
}
|
||||
|
||||
// RBAC helpers
|
||||
func (r *UserRepositoryImpl) GetRolesByUserID(ctx context.Context, userID uuid.UUID) ([]entities.Role, error) {
|
||||
var roles []entities.Role
|
||||
err := r.b.WithContext(ctx).
|
||||
Table("roles as r").
|
||||
Select("r.*").
|
||||
Joins("JOIN user_role ur ON ur.role_id = r.id AND ur.removed_at IS NULL").
|
||||
Where("ur.user_id = ?", userID).
|
||||
Find(&roles).Error
|
||||
return roles, err
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetPermissionsByUserID(ctx context.Context, userID uuid.UUID) ([]entities.Permission, error) {
|
||||
var perms []entities.Permission
|
||||
err := r.b.WithContext(ctx).
|
||||
Table("permissions as p").
|
||||
Select("DISTINCT p.*").
|
||||
Joins("JOIN role_permissions rp ON rp.permission_id = p.id").
|
||||
Joins("JOIN user_role ur ON ur.role_id = rp.role_id AND ur.removed_at IS NULL").
|
||||
Where("ur.user_id = ?", userID).
|
||||
Find(&perms).Error
|
||||
return perms, err
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetDepartmentsByUserID(ctx context.Context, userID uuid.UUID) ([]entities.Department, error) {
|
||||
var departments []entities.Department
|
||||
err := r.b.WithContext(ctx).
|
||||
Table("departments as d").
|
||||
Select("d.*").
|
||||
Joins("JOIN user_department ud ON ud.department_id = d.id AND ud.removed_at IS NULL").
|
||||
Where("ud.user_id = ?", userID).
|
||||
Find(&departments).Error
|
||||
return departments, err
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetRolesByUserIDs(ctx context.Context, userIDs []uuid.UUID) (map[uuid.UUID][]entities.Role, error) {
|
||||
result := make(map[uuid.UUID][]entities.Role)
|
||||
if len(userIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type row struct {
|
||||
UserID uuid.UUID
|
||||
RoleID uuid.UUID
|
||||
Name string
|
||||
Code string
|
||||
}
|
||||
|
||||
var rows []row
|
||||
err := r.b.WithContext(ctx).
|
||||
Table("user_role as ur").
|
||||
Select("ur.user_id, r.id as role_id, r.name, r.code").
|
||||
Joins("JOIN roles r ON r.id = ur.role_id").
|
||||
Where("ur.removed_at IS NULL AND ur.user_id IN ?", userIDs).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rw := range rows {
|
||||
role := entities.Role{ID: rw.RoleID, Name: rw.Name, Code: rw.Code}
|
||||
result[rw.UserID] = append(result[rw.UserID], role)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) ListWithFilters(ctx context.Context, search *string, roleCode *string, isActive *bool, limit, offset int) ([]*entities.User, int64, error) {
|
||||
func (r *UserRepositoryImpl) GetAll(ctx context.Context, page, limit int) ([]*entities.User, int64, error) {
|
||||
var users []*entities.User
|
||||
var total int64
|
||||
|
||||
// Build base query - use Model directly without Table for proper field mapping
|
||||
baseQuery := r.b.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
if search != nil && *search != "" {
|
||||
like := "%" + *search + "%"
|
||||
baseQuery = baseQuery.Where("name ILIKE ? OR email ILIKE ?", like, like)
|
||||
}
|
||||
offset := (page - 1) * limit
|
||||
|
||||
if isActive != nil {
|
||||
baseQuery = baseQuery.Where("is_active = ?", *isActive)
|
||||
}
|
||||
query := r.b.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
// For counting with role filter, we need to use a subquery or join
|
||||
countQuery := baseQuery
|
||||
if roleCode != nil && *roleCode != "" {
|
||||
countQuery = countQuery.
|
||||
Joins("JOIN user_role ur ON ur.user_id = users.id AND ur.removed_at IS NULL").
|
||||
Joins("JOIN roles r ON r.id = ur.role_id").
|
||||
Where("r.code = ?", *roleCode)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
if err := countQuery.Count(&total).Error; err != nil {
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Build query for fetching data
|
||||
dataQuery := r.b.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
err := query.Limit(limit).Offset(offset).Preload("Profile").Find(&users).Error
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) ListWithFilters(ctx context.Context, search *string, isActive *bool, limit, offset int) ([]*entities.User, int64, error) {
|
||||
var users []*entities.User
|
||||
var total int64
|
||||
|
||||
query := r.b.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
if search != nil && *search != "" {
|
||||
like := "%" + *search + "%"
|
||||
dataQuery = dataQuery.Where("name ILIKE ? OR email ILIKE ?", like, like)
|
||||
query = query.Where("name ILIKE ? OR email ILIKE ? OR username ILIKE ?", like, like, like)
|
||||
}
|
||||
|
||||
if isActive != nil {
|
||||
dataQuery = dataQuery.Where("is_active = ?", *isActive)
|
||||
query = query.Where("is_active = ?", *isActive)
|
||||
}
|
||||
|
||||
if roleCode != nil && *roleCode != "" {
|
||||
dataQuery = dataQuery.
|
||||
Joins("JOIN user_role ur ON ur.user_id = users.id AND ur.removed_at IS NULL").
|
||||
Joins("JOIN roles r ON r.id = ur.role_id").
|
||||
Where("r.code = ?", *roleCode)
|
||||
// Get total count
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Fetch users with preloads
|
||||
if err := dataQuery.
|
||||
if err := query.
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Preload("Profile").
|
||||
Preload("Departments").
|
||||
Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) GetByIDWithDepartments(ctx context.Context, id uuid.UUID) (*entities.User, error) {
|
||||
var user entities.User
|
||||
err := r.b.WithContext(ctx).
|
||||
Preload("Profile").
|
||||
Preload("Departments").
|
||||
First(&user, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *UserRepositoryImpl) UpdateDepartments(ctx context.Context, userID uuid.UUID, departments []entities.Department) error {
|
||||
// First, clear existing associations
|
||||
if err := r.b.WithContext(ctx).Model(&entities.User{ID: userID}).Association("Departments").Clear(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Then add new associations
|
||||
if len(departments) > 0 {
|
||||
return r.b.WithContext(ctx).Model(&entities.User{ID: userID}).Association("Departments").Append(&departments)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user