506 lines
15 KiB
Go
506 lines
15 KiB
Go
package repository
|
|
|
|
import (
|
|
"users_management/m/model/dto/req"
|
|
"users_management/m/model/dto/res"
|
|
"users_management/m/model/entity"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CableConnectionRepo interface {
|
|
// Basic CRUD operations
|
|
Create(connection entity.CableConnection) error
|
|
GetByID(id uuid.UUID) (entity.CableConnection, error)
|
|
GetByIDWithDevices(id uuid.UUID) (entity.CableConnection, error)
|
|
Update(id uuid.UUID, request req.UpdateCableConnectionDTO) error
|
|
Delete(id uuid.UUID) error
|
|
|
|
// Bulk operations
|
|
BulkCreate(connections []entity.CableConnection) ([]entity.CableConnection, []error)
|
|
BulkUpdate(ids []uuid.UUID, updates req.UpdateCableConnectionDTO) (int64, error)
|
|
BulkDelete(ids []uuid.UUID) (int64, error)
|
|
|
|
// Search and filtering
|
|
SearchWithPagination(request req.CableConnectionSearchDTO) ([]entity.CableConnection, int, error)
|
|
GetByDeviceID(deviceID uuid.UUID) ([]entity.CableConnection, error)
|
|
|
|
// Analytics operations
|
|
GetLengthDistribution(cableType string) (res.CableLengthDistributionResponse, error)
|
|
GetTypeAnalytics() (res.CableTypeAnalyticsResponse, error)
|
|
GetStatusSummary() (res.CableStatusSummaryResponse, error)
|
|
|
|
// Route analysis
|
|
FindPossibleRoutes(fromDeviceID, toDeviceID uuid.UUID) ([]res.RouteConnectionResponse, error)
|
|
TracePath(fromDeviceID, toDeviceID uuid.UUID, maxHops int) (res.CablePathResponse, error)
|
|
GetNetworkMap(deviceType, cableType, region string) (res.NetworkMapResponse, error)
|
|
|
|
// Maintenance operations
|
|
UpdateStatus(id uuid.UUID, status string, notes *string) error
|
|
GetMaintenanceDue(days int) ([]res.MaintenanceItemResponse, error)
|
|
}
|
|
|
|
type cableConnectionRepo struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewCableConnectionRepo(db *gorm.DB) CableConnectionRepo {
|
|
return &cableConnectionRepo{db: db}
|
|
}
|
|
|
|
func (r *cableConnectionRepo) Create(connection entity.CableConnection) error {
|
|
return r.db.Create(&connection).Error
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetByID(id uuid.UUID) (entity.CableConnection, error) {
|
|
var connection entity.CableConnection
|
|
err := r.db.First(&connection, "id = ?", id).Error
|
|
return connection, err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetByIDWithDevices(id uuid.UUID) (entity.CableConnection, error) {
|
|
var connection entity.CableConnection
|
|
err := r.db.Preload("FromDevice").Preload("ToDevice").First(&connection, "id = ?", id).Error
|
|
return connection, err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) Update(id uuid.UUID, request req.UpdateCableConnectionDTO) error {
|
|
updates := make(map[string]interface{})
|
|
|
|
if request.FromDeviceID != nil {
|
|
updates["from_device_id"] = *request.FromDeviceID
|
|
}
|
|
if request.ToDeviceID != nil {
|
|
updates["to_device_id"] = *request.ToDeviceID
|
|
}
|
|
if request.CableLength != nil {
|
|
updates["cable_length"] = *request.CableLength
|
|
}
|
|
if request.CableType != nil {
|
|
updates["cable_type"] = *request.CableType
|
|
}
|
|
if request.BranchingType != nil {
|
|
updates["branching_type"] = *request.BranchingType
|
|
}
|
|
if request.InstallationDate != nil {
|
|
updates["installation_date"] = *request.InstallationDate
|
|
}
|
|
if request.Status != nil {
|
|
updates["status"] = *request.Status
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return r.db.Model(&entity.CableConnection{}).Where("id = ?", id).Updates(updates).Error
|
|
}
|
|
|
|
func (r *cableConnectionRepo) Delete(id uuid.UUID) error {
|
|
return r.db.Delete(&entity.CableConnection{}, "id = ?", id).Error
|
|
}
|
|
|
|
// BulkCreate creates multiple cable connections in a single transaction
|
|
func (r *cableConnectionRepo) BulkCreate(connections []entity.CableConnection) ([]entity.CableConnection, []error) {
|
|
var errors []error
|
|
|
|
// Use transaction for bulk insert
|
|
err := r.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.CreateInBatches(connections, 50).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
errors = append(errors, err)
|
|
return nil, errors
|
|
}
|
|
|
|
return connections, nil
|
|
}
|
|
|
|
// BulkUpdate updates multiple cable connections with the same values
|
|
func (r *cableConnectionRepo) BulkUpdate(ids []uuid.UUID, updates req.UpdateCableConnectionDTO) (int64, error) {
|
|
updateMap := make(map[string]interface{})
|
|
|
|
if updates.FromDeviceID != nil {
|
|
updateMap["from_device_id"] = *updates.FromDeviceID
|
|
}
|
|
if updates.ToDeviceID != nil {
|
|
updateMap["to_device_id"] = *updates.ToDeviceID
|
|
}
|
|
if updates.CableLength != nil {
|
|
updateMap["cable_length"] = *updates.CableLength
|
|
}
|
|
if updates.CableType != nil {
|
|
updateMap["cable_type"] = *updates.CableType
|
|
}
|
|
if updates.BranchingType != nil {
|
|
updateMap["branching_type"] = *updates.BranchingType
|
|
}
|
|
if updates.InstallationDate != nil {
|
|
updateMap["installation_date"] = *updates.InstallationDate
|
|
}
|
|
if updates.Status != nil {
|
|
updateMap["status"] = *updates.Status
|
|
}
|
|
|
|
if len(updateMap) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
result := r.db.Model(&entity.CableConnection{}).Where("id IN ?", ids).Updates(updateMap)
|
|
return result.RowsAffected, result.Error
|
|
}
|
|
|
|
// BulkDelete deletes multiple cable connections
|
|
func (r *cableConnectionRepo) BulkDelete(ids []uuid.UUID) (int64, error) {
|
|
result := r.db.Delete(&entity.CableConnection{}, "id IN ?", ids)
|
|
return result.RowsAffected, result.Error
|
|
}
|
|
|
|
func (r *cableConnectionRepo) SearchWithPagination(request req.CableConnectionSearchDTO) ([]entity.CableConnection, int, error) {
|
|
query := r.db.Model(&entity.CableConnection{}).Preload("FromDevice").Preload("ToDevice")
|
|
|
|
// Apply filters
|
|
if request.CableType != nil && *request.CableType != "" {
|
|
query = query.Where("cable_type = ?", *request.CableType)
|
|
}
|
|
if request.Status != nil && *request.Status != "" {
|
|
query = query.Where("status = ?", *request.Status)
|
|
}
|
|
if request.DeviceID != nil {
|
|
query = query.Where("from_device_id = ? OR to_device_id = ?", *request.DeviceID, *request.DeviceID)
|
|
}
|
|
if request.MinLength != nil {
|
|
query = query.Where("cable_length >= ?", *request.MinLength)
|
|
}
|
|
if request.MaxLength != nil {
|
|
query = query.Where("cable_length <= ?", *request.MaxLength)
|
|
}
|
|
if request.BranchingType != nil && *request.BranchingType != "" {
|
|
query = query.Where("branching_type = ?", *request.BranchingType)
|
|
}
|
|
if request.InstallationDateFrom != nil {
|
|
query = query.Where("installation_date >= ?", *request.InstallationDateFrom)
|
|
}
|
|
if request.InstallationDateTo != nil {
|
|
query = query.Where("installation_date <= ?", *request.InstallationDateTo)
|
|
}
|
|
|
|
// Get total count
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// Apply pagination
|
|
offset := (request.Page - 1) * request.PerPage
|
|
query = query.Offset(offset).Limit(request.PerPage)
|
|
|
|
// Apply sorting
|
|
orderBy := "created_at DESC"
|
|
if request.SortBy != nil && *request.SortBy != "" {
|
|
direction := "ASC"
|
|
if request.SortDirection != nil && *request.SortDirection == "desc" {
|
|
direction = "DESC"
|
|
}
|
|
orderBy = *request.SortBy + " " + direction
|
|
}
|
|
query = query.Order(orderBy)
|
|
|
|
var connections []entity.CableConnection
|
|
err := query.Find(&connections).Error
|
|
|
|
return connections, int(total), err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetByDeviceID(deviceID uuid.UUID) ([]entity.CableConnection, error) {
|
|
var connections []entity.CableConnection
|
|
err := r.db.Preload("FromDevice").Preload("ToDevice").
|
|
Where("from_device_id = ? OR to_device_id = ?", deviceID, deviceID).
|
|
Find(&connections).Error
|
|
return connections, err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetLengthDistribution(cableType string) (res.CableLengthDistributionResponse, error) {
|
|
query := r.db.Model(&entity.CableConnection{})
|
|
|
|
if cableType != "" {
|
|
query = query.Where("cable_type = ?", cableType)
|
|
}
|
|
|
|
var distribution res.CableLengthDistributionResponse
|
|
|
|
// Get length ranges
|
|
err := query.Select(`
|
|
COUNT(CASE WHEN cable_length < 100 THEN 1 END) as under_100m,
|
|
COUNT(CASE WHEN cable_length >= 100 AND cable_length < 500 THEN 1 END) as from_100_500m,
|
|
COUNT(CASE WHEN cable_length >= 500 AND cable_length < 1000 THEN 1 END) as from_500_1000m,
|
|
COUNT(CASE WHEN cable_length >= 1000 AND cable_length < 5000 THEN 1 END) as from_1_5km,
|
|
COUNT(CASE WHEN cable_length >= 5000 THEN 1 END) as above_5km,
|
|
AVG(cable_length) as average_length,
|
|
MIN(cable_length) as min_length,
|
|
MAX(cable_length) as max_length,
|
|
COUNT(*) as total_connections
|
|
`).Scan(&distribution).Error
|
|
|
|
return distribution, err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetTypeAnalytics() (res.CableTypeAnalyticsResponse, error) {
|
|
var typeStats []res.CableTypeStats
|
|
|
|
err := r.db.Model(&entity.CableConnection{}).
|
|
Select("cable_type, COUNT(*) as count, AVG(cable_length) as average_length, SUM(cable_length) as total_length").
|
|
Group("cable_type").
|
|
Scan(&typeStats).Error
|
|
|
|
if err != nil {
|
|
return res.CableTypeAnalyticsResponse{}, err
|
|
}
|
|
|
|
// Get total metrics
|
|
var totalConnections int64
|
|
var totalLength float64
|
|
|
|
r.db.Model(&entity.CableConnection{}).Count(&totalConnections)
|
|
r.db.Model(&entity.CableConnection{}).Select("SUM(cable_length)").Scan(&totalLength)
|
|
|
|
response := res.CableTypeAnalyticsResponse{
|
|
TypeStats: typeStats,
|
|
TotalConnections: int(totalConnections),
|
|
TotalLength: totalLength,
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetStatusSummary() (res.CableStatusSummaryResponse, error) {
|
|
var statusStats []res.CableStatusStats
|
|
|
|
err := r.db.Model(&entity.CableConnection{}).
|
|
Select("status, COUNT(*) as count").
|
|
Group("status").
|
|
Scan(&statusStats).Error
|
|
|
|
if err != nil {
|
|
return res.CableStatusSummaryResponse{}, err
|
|
}
|
|
|
|
var totalConnections int64
|
|
r.db.Model(&entity.CableConnection{}).Count(&totalConnections)
|
|
|
|
response := res.CableStatusSummaryResponse{
|
|
StatusStats: statusStats,
|
|
TotalConnections: int(totalConnections),
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (r *cableConnectionRepo) FindPossibleRoutes(fromDeviceID, toDeviceID uuid.UUID) ([]res.RouteConnectionResponse, error) {
|
|
var routes []res.RouteConnectionResponse
|
|
|
|
// Find intermediate devices that could be used for routing
|
|
query := `
|
|
WITH RECURSIVE route_finder AS (
|
|
-- Start with direct connections from source device
|
|
SELECT
|
|
cc.id,
|
|
cc.from_device_id,
|
|
cc.to_device_id,
|
|
cc.cable_length,
|
|
cc.cable_type,
|
|
1 as hop_count,
|
|
ARRAY[cc.from_device_id, cc.to_device_id] as path
|
|
FROM cable_connections cc
|
|
WHERE cc.from_device_id = ? AND cc.status = 'active'
|
|
|
|
UNION ALL
|
|
|
|
-- Recursively find connections through intermediate devices
|
|
SELECT
|
|
cc.id,
|
|
cc.from_device_id,
|
|
cc.to_device_id,
|
|
rf.cable_length + cc.cable_length,
|
|
cc.cable_type,
|
|
rf.hop_count + 1,
|
|
rf.path || cc.to_device_id
|
|
FROM cable_connections cc
|
|
JOIN route_finder rf ON cc.from_device_id = rf.to_device_id
|
|
WHERE cc.status = 'active'
|
|
AND rf.hop_count < 5 -- Limit to 5 hops
|
|
AND NOT (cc.to_device_id = ANY(rf.path)) -- Avoid loops
|
|
)
|
|
SELECT DISTINCT
|
|
rf.from_device_id,
|
|
rf.to_device_id,
|
|
rf.cable_length as total_length,
|
|
rf.hop_count,
|
|
rf.path
|
|
FROM route_finder rf
|
|
WHERE rf.to_device_id = ?
|
|
ORDER BY rf.hop_count, rf.cable_length
|
|
LIMIT 10
|
|
`
|
|
|
|
err := r.db.Raw(query, fromDeviceID, toDeviceID).Scan(&routes).Error
|
|
return routes, err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) TracePath(fromDeviceID, toDeviceID uuid.UUID, maxHops int) (res.CablePathResponse, error) {
|
|
if maxHops <= 0 {
|
|
maxHops = 10
|
|
}
|
|
|
|
var pathResponse res.CablePathResponse
|
|
|
|
// Find the shortest path using a recursive CTE
|
|
query := `
|
|
WITH RECURSIVE path_trace AS (
|
|
SELECT
|
|
cc.id,
|
|
cc.from_device_id,
|
|
cc.to_device_id,
|
|
cc.cable_length,
|
|
cc.cable_type,
|
|
1 as hop_count,
|
|
ARRAY[cc.from_device_id] as visited_devices,
|
|
ARRAY[cc.id] as connection_path
|
|
FROM cable_connections cc
|
|
WHERE cc.from_device_id = ? AND cc.status = 'active'
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
cc.id,
|
|
cc.from_device_id,
|
|
cc.to_device_id,
|
|
pt.cable_length + cc.cable_length,
|
|
cc.cable_type,
|
|
pt.hop_count + 1,
|
|
pt.visited_devices || cc.from_device_id,
|
|
pt.connection_path || cc.id
|
|
FROM cable_connections cc
|
|
JOIN path_trace pt ON cc.from_device_id = pt.to_device_id
|
|
WHERE cc.status = 'active'
|
|
AND pt.hop_count < ?
|
|
AND NOT (cc.from_device_id = ANY(pt.visited_devices))
|
|
)
|
|
SELECT
|
|
from_device_id,
|
|
to_device_id,
|
|
cable_length as total_length,
|
|
hop_count,
|
|
connection_path
|
|
FROM path_trace
|
|
WHERE to_device_id = ?
|
|
ORDER BY hop_count, cable_length
|
|
LIMIT 1
|
|
`
|
|
|
|
err := r.db.Raw(query, fromDeviceID, maxHops, toDeviceID).Scan(&pathResponse).Error
|
|
return pathResponse, err
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetNetworkMap(deviceType, cableType, region string) (res.NetworkMapResponse, error) {
|
|
query := r.db.Model(&entity.CableConnection{}).
|
|
Select(`
|
|
cc.id,
|
|
cc.from_device_id,
|
|
cc.to_device_id,
|
|
cc.cable_length,
|
|
cc.cable_type,
|
|
cc.status,
|
|
fd.device_code as from_device_code,
|
|
fd.device_type as from_device_type,
|
|
fd.latitude as from_latitude,
|
|
fd.longitude as from_longitude,
|
|
td.device_code as to_device_code,
|
|
td.device_type as to_device_type,
|
|
td.latitude as to_latitude,
|
|
td.longitude as to_longitude
|
|
`).
|
|
Joins("cc").
|
|
Joins("LEFT JOIN devices fd ON cc.from_device_id = fd.id").
|
|
Joins("LEFT JOIN devices td ON cc.to_device_id = td.id").
|
|
Where("cc.status = ?", "active")
|
|
|
|
if deviceType != "" {
|
|
query = query.Where("fd.device_type = ? OR td.device_type = ?", deviceType, deviceType)
|
|
}
|
|
if cableType != "" {
|
|
query = query.Where("cc.cable_type = ?", cableType)
|
|
}
|
|
if region != "" {
|
|
query = query.Where("fd.province = ? OR fd.city = ? OR td.province = ? OR td.city = ?",
|
|
region, region, region, region)
|
|
}
|
|
|
|
var mapData []res.NetworkMapConnection
|
|
err := query.Scan(&mapData).Error
|
|
|
|
if err != nil {
|
|
return res.NetworkMapResponse{}, err
|
|
}
|
|
|
|
// Calculate summary statistics
|
|
var stats res.NetworkMapStats
|
|
r.db.Model(&entity.CableConnection{}).
|
|
Select("COUNT(*) as total_connections, AVG(cable_length) as average_length").
|
|
Where("status = ?", "active").
|
|
Scan(&stats)
|
|
|
|
response := res.NetworkMapResponse{
|
|
Connections: mapData,
|
|
Stats: stats,
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (r *cableConnectionRepo) UpdateStatus(id uuid.UUID, status string, notes *string) error {
|
|
updates := map[string]interface{}{
|
|
"status": status,
|
|
}
|
|
|
|
if notes != nil {
|
|
updates["notes"] = *notes
|
|
}
|
|
|
|
return r.db.Model(&entity.CableConnection{}).Where("id = ?", id).Updates(updates).Error
|
|
}
|
|
|
|
func (r *cableConnectionRepo) GetMaintenanceDue(days int) ([]res.MaintenanceItemResponse, error) {
|
|
var maintenanceItems []res.MaintenanceItemResponse
|
|
|
|
// Find connections that need maintenance based on installation date
|
|
query := `
|
|
SELECT
|
|
cc.id,
|
|
cc.from_device_id,
|
|
cc.to_device_id,
|
|
cc.cable_length,
|
|
cc.cable_type,
|
|
cc.installation_date,
|
|
cc.status,
|
|
fd.device_code as from_device_code,
|
|
td.device_code as to_device_code,
|
|
EXTRACT(DAYS FROM (NOW() - cc.installation_date)) as days_since_installation
|
|
FROM cable_connections cc
|
|
LEFT JOIN devices fd ON cc.from_device_id = fd.id
|
|
LEFT JOIN devices td ON cc.to_device_id = td.id
|
|
WHERE cc.installation_date IS NOT NULL
|
|
AND cc.installation_date < (NOW() - INTERVAL ? || ' days')
|
|
AND cc.status = 'active'
|
|
ORDER BY cc.installation_date ASC
|
|
`
|
|
|
|
err := r.db.Raw(query, days).Scan(&maintenanceItems).Error
|
|
return maintenanceItems, err
|
|
}
|