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 }