NAM-APJATEL-BACKEND/usecase/cable_connections_usecase.go

598 lines
19 KiB
Go

package usecase
import (
"fmt"
"math"
"time"
"users_management/m/model/dto/req"
"users_management/m/model/dto/res"
"users_management/m/model/entity"
"users_management/m/repository"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
type CableConnectionUseCase interface {
// Basic CRUD operations
SearchCableConnections(request req.CableConnectionSearchDTO) ([]res.CableConnectionResponse, int, error)
GetCableConnectionByID(id uuid.UUID) (res.CableConnectionResponse, error)
CreateCableConnection(request req.CreateCableConnectionDTO) (res.CableConnectionResponse, error)
UpdateCableConnection(id uuid.UUID, request req.UpdateCableConnectionDTO) error
DeleteCableConnection(id uuid.UUID) error
// Bulk operations
BulkCreateCableConnections(request req.BulkCreateCableConnectionDTO) (res.BulkOperationResponse, error)
BulkUpdateCableConnections(request req.BulkUpdateCableConnectionDTO) (res.BulkOperationResponse, error)
BulkDeleteCableConnections(request req.BulkDeleteCableConnectionDTO) (res.BulkOperationResponse, error)
// Device-related operations
GetCableConnectionsByDevice(deviceID uuid.UUID) ([]res.CableConnectionResponse, error)
// Analytics operations
GetCableLengthDistribution(cableType string) (res.CableLengthDistributionResponse, error)
GetCableTypeAnalytics() (res.CableTypeAnalyticsResponse, error)
CalculateOptimalRoute(request req.OptimalRouteRequestDTO) (res.OptimalRouteResponse, error)
// Status and maintenance operations
GetCableStatusSummary() (res.CableStatusSummaryResponse, error)
UpdateCableStatus(id uuid.UUID, request req.UpdateCableStatusDTO) error
GetMaintenanceDue(days int) ([]res.MaintenanceItemResponse, error)
// Network analysis operations
TraceCablePath(request req.TraceCablePathDTO) (res.CablePathResponse, error)
GetNetworkMap(deviceType, cableType, region string) (res.NetworkMapResponse, error)
}
type cableConnectionUseCase struct {
cableConnectionRepo repository.CableConnectionRepo
deviceRepo repository.DevicesRepo
validate *validator.Validate
}
func NewCableConnectionUseCase(
cableConnectionRepo repository.CableConnectionRepo,
deviceRepo repository.DevicesRepo,
) CableConnectionUseCase {
return &cableConnectionUseCase{
cableConnectionRepo: cableConnectionRepo,
deviceRepo: deviceRepo,
validate: validator.New(),
}
}
func (u *cableConnectionUseCase) SearchCableConnections(request req.CableConnectionSearchDTO) ([]res.CableConnectionResponse, int, error) {
// Set default pagination
if request.Page <= 0 {
request.Page = 1
}
if request.PerPage <= 0 {
request.PerPage = 10
}
connections, total, err := u.cableConnectionRepo.SearchWithPagination(request)
if err != nil {
return nil, 0, fmt.Errorf("failed to search cable connections: %w", err)
}
var responses []res.CableConnectionResponse
for _, connection := range connections {
response := u.mapToResponse(connection)
responses = append(responses, response)
}
return responses, total, nil
}
func (u *cableConnectionUseCase) GetCableConnectionByID(id uuid.UUID) (res.CableConnectionResponse, error) {
connection, err := u.cableConnectionRepo.GetByIDWithDevices(id)
if err != nil {
return res.CableConnectionResponse{}, fmt.Errorf("cable connection not found: %w", err)
}
return u.mapToResponse(connection), nil
}
func (u *cableConnectionUseCase) CreateCableConnection(request req.CreateCableConnectionDTO) (res.CableConnectionResponse, error) {
err := u.validate.Struct(request)
if err != nil {
return res.CableConnectionResponse{}, fmt.Errorf("validation error: %w", err)
}
// Validate devices exist
fromDevice, err := u.deviceRepo.GetByID(request.FromDeviceID)
if err != nil {
return res.CableConnectionResponse{}, fmt.Errorf("from device not found: %w", err)
}
toDevice, err := u.deviceRepo.GetByID(request.ToDeviceID)
if err != nil {
return res.CableConnectionResponse{}, fmt.Errorf("to device not found: %w", err)
}
// Calculate estimated distance if coordinates are available
estimatedDistance := u.calculateDistance(fromDevice.Latitude, fromDevice.Longitude, toDevice.Latitude, toDevice.Longitude)
// Validate cable length is reasonable
if request.CableLength > 0 && estimatedDistance > 0 {
ratio := request.CableLength / estimatedDistance
if ratio > 3.0 { // Cable length shouldn't be more than 3x the straight-line distance
return res.CableConnectionResponse{}, fmt.Errorf("cable length seems unrealistic compared to device distance (ratio: %.2f)", ratio)
}
}
connection := entity.CableConnection{
ID: uuid.New(),
FromDeviceID: request.FromDeviceID,
ToDeviceID: request.ToDeviceID,
CableLength: request.CableLength,
CableType: &request.CableType,
BranchingType: request.BranchingType,
InstallationDate: request.InstallationDate,
Status: entity.DeviceStatus(request.Status),
}
err = u.cableConnectionRepo.Create(connection)
if err != nil {
return res.CableConnectionResponse{}, fmt.Errorf("failed to create cable connection: %w", err)
}
// Fetch with devices for response
createdConnection, err := u.cableConnectionRepo.GetByIDWithDevices(connection.ID)
if err != nil {
return res.CableConnectionResponse{}, fmt.Errorf("failed to fetch created connection: %w", err)
}
return u.mapToResponse(createdConnection), nil
}
func (u *cableConnectionUseCase) UpdateCableConnection(id uuid.UUID, request req.UpdateCableConnectionDTO) error {
err := u.validate.Struct(request)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
// Check if connection exists
_, err = u.cableConnectionRepo.GetByID(id)
if err != nil {
return fmt.Errorf("cable connection not found: %w", err)
}
// Validate devices if they are being updated
if request.FromDeviceID != nil {
_, err = u.deviceRepo.GetByID(*request.FromDeviceID)
if err != nil {
return fmt.Errorf("from device not found: %w", err)
}
}
if request.ToDeviceID != nil {
_, err = u.deviceRepo.GetByID(*request.ToDeviceID)
if err != nil {
return fmt.Errorf("to device not found: %w", err)
}
}
return u.cableConnectionRepo.Update(id, request)
}
func (u *cableConnectionUseCase) DeleteCableConnection(id uuid.UUID) error {
// Check if connection exists
_, err := u.cableConnectionRepo.GetByID(id)
if err != nil {
return fmt.Errorf("cable connection not found: %w", err)
}
return u.cableConnectionRepo.Delete(id)
}
// BulkCreateCableConnections creates multiple cable connections at once
func (u *cableConnectionUseCase) BulkCreateCableConnections(request req.BulkCreateCableConnectionDTO) (res.BulkOperationResponse, error) {
startTime := time.Now()
err := u.validate.Struct(request)
if err != nil {
return res.BulkOperationResponse{}, fmt.Errorf("validation error: %w", err)
}
var connections []entity.CableConnection
var errors []res.BulkOperationError
// Validate each connection
for i, connReq := range request.Connections {
if err := u.validate.Struct(connReq); err != nil {
errors = append(errors, res.BulkOperationError{
Index: i,
Error: "Validation failed",
Details: err.Error(),
})
continue
}
// Validate devices exist
_, err := u.deviceRepo.GetByID(connReq.FromDeviceID)
if err != nil {
errors = append(errors, res.BulkOperationError{
Index: i,
Error: "From device not found",
Details: err.Error(),
})
continue
}
_, err = u.deviceRepo.GetByID(connReq.ToDeviceID)
if err != nil {
errors = append(errors, res.BulkOperationError{
Index: i,
Error: "To device not found",
Details: err.Error(),
})
continue
}
connection := entity.CableConnection{
ID: uuid.New(),
FromDeviceID: connReq.FromDeviceID,
ToDeviceID: connReq.ToDeviceID,
CableLength: connReq.CableLength,
CableType: &connReq.CableType,
BranchingType: connReq.BranchingType,
InstallationDate: connReq.InstallationDate,
Status: entity.DeviceStatus(connReq.Status),
}
connections = append(connections, connection)
}
// Bulk insert valid connections
var createdConnections []entity.CableConnection
if len(connections) > 0 {
createdConnections, _ = u.cableConnectionRepo.BulkCreate(connections)
}
// Fetch created connections with device info
var responses []res.CableConnectionResponse
for _, conn := range createdConnections {
fetchedConn, err := u.cableConnectionRepo.GetByIDWithDevices(conn.ID)
if err == nil {
responses = append(responses, u.mapToResponse(fetchedConn))
}
}
executionTime := time.Since(startTime).String()
return res.BulkOperationResponse{
TotalRequested: len(request.Connections),
Successful: len(createdConnections),
Failed: len(errors),
Errors: errors,
Results: responses,
ExecutionTime: executionTime,
}, nil
}
// BulkUpdateCableConnections updates multiple cable connections with the same values
func (u *cableConnectionUseCase) BulkUpdateCableConnections(request req.BulkUpdateCableConnectionDTO) (res.BulkOperationResponse, error) {
startTime := time.Now()
err := u.validate.Struct(request)
if err != nil {
return res.BulkOperationResponse{}, fmt.Errorf("validation error: %w", err)
}
// Validate that connections exist
var validIDs []uuid.UUID
var errors []res.BulkOperationError
for i, id := range request.ConnectionIDs {
_, err := u.cableConnectionRepo.GetByID(id)
if err != nil {
errors = append(errors, res.BulkOperationError{
Index: i,
Error: "Connection not found",
Details: id.String(),
})
continue
}
validIDs = append(validIDs, id)
}
// Perform bulk update
var rowsAffected int64
if len(validIDs) > 0 {
rowsAffected, err = u.cableConnectionRepo.BulkUpdate(validIDs, request.Updates)
if err != nil {
return res.BulkOperationResponse{}, fmt.Errorf("bulk update failed: %w", err)
}
}
// Fetch updated connections
var responses []res.CableConnectionResponse
for _, id := range validIDs {
conn, err := u.cableConnectionRepo.GetByIDWithDevices(id)
if err == nil {
responses = append(responses, u.mapToResponse(conn))
}
}
executionTime := time.Since(startTime).String()
return res.BulkOperationResponse{
TotalRequested: len(request.ConnectionIDs),
Successful: int(rowsAffected),
Failed: len(errors),
Errors: errors,
Results: responses,
ExecutionTime: executionTime,
}, nil
}
// BulkDeleteCableConnections deletes multiple cable connections
func (u *cableConnectionUseCase) BulkDeleteCableConnections(request req.BulkDeleteCableConnectionDTO) (res.BulkOperationResponse, error) {
startTime := time.Now()
err := u.validate.Struct(request)
if err != nil {
return res.BulkOperationResponse{}, fmt.Errorf("validation error: %w", err)
}
// Validate that connections exist
var validIDs []uuid.UUID
var errors []res.BulkOperationError
for i, id := range request.ConnectionIDs {
_, err := u.cableConnectionRepo.GetByID(id)
if err != nil {
errors = append(errors, res.BulkOperationError{
Index: i,
Error: "Connection not found",
Details: id.String(),
})
continue
}
validIDs = append(validIDs, id)
}
// Perform bulk delete
var rowsAffected int64
if len(validIDs) > 0 {
rowsAffected, err = u.cableConnectionRepo.BulkDelete(validIDs)
if err != nil {
return res.BulkOperationResponse{}, fmt.Errorf("bulk delete failed: %w", err)
}
}
executionTime := time.Since(startTime).String()
return res.BulkOperationResponse{
TotalRequested: len(request.ConnectionIDs),
Successful: int(rowsAffected),
Failed: len(errors),
Errors: errors,
ExecutionTime: executionTime,
}, nil
}
func (u *cableConnectionUseCase) GetCableConnectionsByDevice(deviceID uuid.UUID) ([]res.CableConnectionResponse, error) {
// Validate device exists
_, err := u.deviceRepo.GetByID(deviceID)
if err != nil {
return nil, fmt.Errorf("device not found: %w", err)
}
connections, err := u.cableConnectionRepo.GetByDeviceID(deviceID)
if err != nil {
return nil, fmt.Errorf("failed to get cable connections: %w", err)
}
var responses []res.CableConnectionResponse
for _, connection := range connections {
response := u.mapToResponse(connection)
responses = append(responses, response)
}
return responses, nil
}
func (u *cableConnectionUseCase) GetCableLengthDistribution(cableType string) (res.CableLengthDistributionResponse, error) {
distribution, err := u.cableConnectionRepo.GetLengthDistribution(cableType)
if err != nil {
return res.CableLengthDistributionResponse{}, fmt.Errorf("failed to get length distribution: %w", err)
}
return distribution, nil
}
func (u *cableConnectionUseCase) GetCableTypeAnalytics() (res.CableTypeAnalyticsResponse, error) {
analytics, err := u.cableConnectionRepo.GetTypeAnalytics()
if err != nil {
return res.CableTypeAnalyticsResponse{}, fmt.Errorf("failed to get type analytics: %w", err)
}
return analytics, nil
}
func (u *cableConnectionUseCase) CalculateOptimalRoute(request req.OptimalRouteRequestDTO) (res.OptimalRouteResponse, error) {
err := u.validate.Struct(request)
if err != nil {
return res.OptimalRouteResponse{}, fmt.Errorf("validation error: %w", err)
}
// Validate devices exist
fromDevice, err := u.deviceRepo.GetByID(request.FromDeviceID)
if err != nil {
return res.OptimalRouteResponse{}, fmt.Errorf("from device not found: %w", err)
}
toDevice, err := u.deviceRepo.GetByID(request.ToDeviceID)
if err != nil {
return res.OptimalRouteResponse{}, fmt.Errorf("to device not found: %w", err)
}
// Calculate direct distance
directDistance := u.calculateDistance(fromDevice.Latitude, fromDevice.Longitude, toDevice.Latitude, toDevice.Longitude)
// Find existing connections that could be used for routing
existingConnections, err := u.cableConnectionRepo.FindPossibleRoutes(request.FromDeviceID, request.ToDeviceID)
if err != nil {
return res.OptimalRouteResponse{}, fmt.Errorf("failed to find possible routes: %w", err)
}
response := res.OptimalRouteResponse{
FromDeviceID: request.FromDeviceID,
ToDeviceID: request.ToDeviceID,
DirectDistance: directDistance,
RecommendedCableLength: directDistance * 1.2, // Add 20% for routing overhead
ExistingConnections: existingConnections,
Recommendations: u.generateRouteRecommendations(fromDevice, toDevice, directDistance),
}
return response, nil
}
func (u *cableConnectionUseCase) GetCableStatusSummary() (res.CableStatusSummaryResponse, error) {
summary, err := u.cableConnectionRepo.GetStatusSummary()
if err != nil {
return res.CableStatusSummaryResponse{}, fmt.Errorf("failed to get status summary: %w", err)
}
return summary, nil
}
func (u *cableConnectionUseCase) UpdateCableStatus(id uuid.UUID, request req.UpdateCableStatusDTO) error {
err := u.validate.Struct(request)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
// Check if connection exists
_, err = u.cableConnectionRepo.GetByID(id)
if err != nil {
return fmt.Errorf("cable connection not found: %w", err)
}
return u.cableConnectionRepo.UpdateStatus(id, request.Status, request.Notes)
}
func (u *cableConnectionUseCase) GetMaintenanceDue(days int) ([]res.MaintenanceItemResponse, error) {
maintenanceItems, err := u.cableConnectionRepo.GetMaintenanceDue(days)
if err != nil {
return nil, fmt.Errorf("failed to get maintenance due: %w", err)
}
return maintenanceItems, nil
}
func (u *cableConnectionUseCase) TraceCablePath(request req.TraceCablePathDTO) (res.CablePathResponse, error) {
err := u.validate.Struct(request)
if err != nil {
return res.CablePathResponse{}, fmt.Errorf("validation error: %w", err)
}
path, err := u.cableConnectionRepo.TracePath(request.FromDeviceID, request.ToDeviceID, request.MaxHops)
if err != nil {
return res.CablePathResponse{}, fmt.Errorf("failed to trace path: %w", err)
}
return path, nil
}
func (u *cableConnectionUseCase) GetNetworkMap(deviceType, cableType, region string) (res.NetworkMapResponse, error) {
networkMap, err := u.cableConnectionRepo.GetNetworkMap(deviceType, cableType, region)
if err != nil {
return res.NetworkMapResponse{}, fmt.Errorf("failed to get network map: %w", err)
}
return networkMap, nil
}
// Helper methods
func (u *cableConnectionUseCase) mapToResponse(connection entity.CableConnection) res.CableConnectionResponse {
response := res.CableConnectionResponse{
ID: connection.ID,
FromDeviceID: connection.FromDeviceID,
ToDeviceID: connection.ToDeviceID,
CableLength: connection.CableLength,
CableType: *connection.CableType,
BranchingType: connection.BranchingType,
InstallationDate: connection.InstallationDate,
Status: string(connection.Status),
CreatedAt: connection.CreatedAt,
UpdatedAt: connection.UpdatedAt,
}
if connection.FromDevice != nil {
response.FromDevice = &res.CableDeviceInfo{
ID: connection.FromDevice.ID,
DeviceCode: connection.FromDevice.DeviceCode,
DeviceType: string(connection.FromDevice.DeviceType),
Latitude: connection.FromDevice.Latitude,
Longitude: connection.FromDevice.Longitude,
}
}
if connection.ToDevice != nil {
response.ToDevice = &res.CableDeviceInfo{
ID: connection.ToDevice.ID,
DeviceCode: connection.ToDevice.DeviceCode,
DeviceType: string(connection.ToDevice.DeviceType),
Latitude: connection.ToDevice.Latitude,
Longitude: connection.ToDevice.Longitude,
}
}
// Calculate efficiency metrics if devices are available
if connection.FromDevice != nil && connection.ToDevice != nil {
directDistance := u.calculateDistance(
connection.FromDevice.Latitude, connection.FromDevice.Longitude,
connection.ToDevice.Latitude, connection.ToDevice.Longitude,
)
if directDistance > 0 {
efficiency := directDistance / connection.CableLength * 100
response.RouteEfficiency = &efficiency
}
}
return response
}
func (u *cableConnectionUseCase) calculateDistance(lat1, lon1, lat2, lon2 float64) float64 {
// Haversine formula to calculate distance between two points
const R = 6371000 // Earth's radius in meters
φ1 := lat1 * math.Pi / 180
φ2 := lat2 * math.Pi / 180
Δφ := (lat2 - lat1) * math.Pi / 180
Δλ := (lon2 - lon1) * math.Pi / 180
a := math.Sin(Δφ/2)*math.Sin(Δφ/2) + math.Cos(φ1)*math.Cos(φ2)*math.Sin(Δλ/2)*math.Sin(Δλ/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return R * c // Distance in meters
}
func (u *cableConnectionUseCase) generateRouteRecommendations(fromDevice, toDevice entity.Device, directDistance float64) []string {
recommendations := []string{}
// Basic recommendations based on distance
if directDistance < 100 {
recommendations = append(recommendations, "Direct connection recommended for short distance")
} else if directDistance < 1000 {
recommendations = append(recommendations, "Consider intermediate splice points for cable management")
} else {
recommendations = append(recommendations, "Long distance connection - consider signal amplification")
}
// Device type specific recommendations
if fromDevice.DeviceType != toDevice.DeviceType {
recommendations = append(recommendations, "Different device types detected - verify compatibility")
}
// Environmental recommendations
if fromDevice.Province != toDevice.Province {
recommendations = append(recommendations, "Inter-province connection - check regulatory requirements")
}
return recommendations
}