diff --git a/model/dto/req/deviceDetails.go b/model/dto/req/deviceDetails.go index ade3702..76127c9 100644 --- a/model/dto/req/deviceDetails.go +++ b/model/dto/req/deviceDetails.go @@ -1,51 +1,55 @@ package req +import "users_management/m/model/entity" type DeviceDetailsDTO struct { - DeviceCode string `json:"device_code" validate:"required"` - DeviceType string `json:"device_type" validate:"required,oneof=OTB ODP"` - Longitude float64 `json:"longitude" validate:"required"` - Latitude float64 `json:"latitude" validate:"required"` - PortAmount int `json:"port_amount" validate:"required,min=1,max=100"` - Status string `json:"status" validate:"required,oneof=active inactive maintenance"` - Region *string `json:"region,omitempty" validate:"omitempty,min=3"` - Province *string `json:"province,omitempty" validate:"omitempty,min=3"` - City *string `json:"city,omitempty" validate:"omitempty,min=3"` - District *string `json:"district,omitempty" validate:"omitempty,min=3"` + DeviceCode string `json:"device_code" validate:"required"` + DeviceType string `json:"device_type" validate:"required,oneof=OTB ODP"` + Longitude float64 `json:"longitude" validate:"required"` + Latitude float64 `json:"latitude" validate:"required"` + PortAmount int `json:"port_amount" validate:"required,min=1,max=100"` + Status string `json:"status" validate:"required,oneof=active inactive maintenance"` + Region *string `json:"region,omitempty" validate:"omitempty,min=3"` + Province *string `json:"province,omitempty" validate:"omitempty,min=3"` + City *string `json:"city,omitempty" validate:"omitempty,min=3"` + District *string `json:"district,omitempty" validate:"omitempty,min=3"` } type UpdateDeviceDetailsDTO struct { - DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"` - DeviceType *string `json:"device_type,omitempty" validate:"omitempty,oneof=OTB ODP CLOSURE"` - Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"` - Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"` - PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=0,max=100"` - Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"` - Region *string `json:"region,omitempty" validate:"omitempty,min=3"` - Province *string `json:"province,omitempty" validate:"omitempty,min=3"` - City *string `json:"city,omitempty" validate:"omitempty,min=3"` - District *string `json:"district,omitempty" validate:"omitempty,min=3"` - + DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"` + DeviceType *string `json:"device_type,omitempty" validate:"omitempty,oneof=OTB ODP CLOSURE"` + Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"` + Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"` + PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=0,max=100"` + Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"` + Region *string `json:"region,omitempty" validate:"omitempty,min=3"` + Province *string `json:"province,omitempty" validate:"omitempty,min=3"` + City *string `json:"city,omitempty" validate:"omitempty,min=3"` + District *string `json:"district,omitempty" validate:"omitempty,min=3"` } type AssignMultipleCustomersDTO struct { - CustomerName string `json:"customer_name" binding:"required"` - PortNumber *int `json:"port_number,omitempty"` + CustomerName string `json:"customer_name" binding:"required"` + PortNumber *int `json:"port_number,omitempty"` + Status *entity.PortStatus `json:"status,omitempty"` // Add status field + Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field } type UpdateCustomerByPortDTO struct { - PortNumber int `json:"port_number" binding:"required,min=1"` - NewCustomerName *string `json:"new_customer_name,omitempty"` // null to remove, string to update/assign + PortNumber int `json:"port_number" binding:"required,min=1"` + NewCustomerName *string `json:"new_customer_name,omitempty"` + Status *entity.PortStatus `json:"status,omitempty"` // Add status field + Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field } type BulkUpdateCustomersByPortDTO struct { - Updates []UpdateCustomerByPortDTO `json:"updates" binding:"required"` + Updates []UpdateCustomerByPortDTO `json:"updates" binding:"required"` } type RemoveCustomerByPortDTO struct { - PortNumber int `json:"port_number" binding:"required,min=1"` + PortNumber int `json:"port_number" binding:"required,min=1"` } type DeleteImageDTO struct { - Filename string `json:"filename" validate:"required"` + Filename string `json:"filename" validate:"required"` } \ No newline at end of file diff --git a/model/dto/req/devicePort_dto.go b/model/dto/req/devicePort_dto.go index d13801f..edf0ada 100644 --- a/model/dto/req/devicePort_dto.go +++ b/model/dto/req/devicePort_dto.go @@ -1,6 +1,10 @@ package req -import "github.com/google/uuid" +import ( + "users_management/m/model/entity" + + "github.com/google/uuid" +) type DevicePort struct { DeviceID uuid.UUID `json:"device_id"` @@ -14,8 +18,10 @@ type UpdateDevicePort struct { type AssignCustomerToPortDTO struct { - CustomerName string `json:"customer_name" binding:"required"` - PortNumber *int `json:"port_number,omitempty"` // Optional, will auto-assign if not provided + CustomerName string `json:"customer_name" binding:"required"` + PortNumber *int `json:"port_number,omitempty"` // Optional, will auto-assign if not provided + Status *entity.PortStatus `json:"status,omitempty"` // Add status field + Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field } type UpdatePortUsageDTO struct { @@ -23,8 +29,10 @@ type UpdatePortUsageDTO struct { } type PortAssignmentDTO struct { - PortNumber int `json:"port_number" binding:"required,min=1"` - CustomerName *string `json:"customer_name,omitempty"` + PortNumber int `json:"port_number" binding:"required,min=1"` + CustomerName *string `json:"customer_name,omitempty"` + Status *entity.PortStatus `json:"status,omitempty" validate:"omitempty,portstatus"` + Bandwidth *string `json:"bandwidth,omitempty" validate:"omitempty,min=1"` } type UpdatePortAssignmentsDTO struct { diff --git a/model/dto/res/device_details.go b/model/dto/res/device_details.go index c444e3e..a00dcbe 100644 --- a/model/dto/res/device_details.go +++ b/model/dto/res/device_details.go @@ -1,8 +1,10 @@ package res import ( - "time" - "github.com/google/uuid" + "time" + "users_management/m/model/entity" + + "github.com/google/uuid" ) type DeviceDetailsResponse struct { @@ -44,7 +46,9 @@ type TowerConnectionDetail struct { } type PortAssignmentResponse struct { - PortNumber int `json:"port_number"` - CustomerName *string `json:"customer_name"` - IsOccupied bool `json:"is_occupied"` + PortNumber int `json:"port_number"` + CustomerName *string `json:"customer_name"` + IsOccupied bool `json:"is_occupied"` + Status entity.PortStatus `json:"status"` // Add status field + Bandwidth *string `json:"bandwidth"` // Add bandwidth field } \ No newline at end of file diff --git a/model/entity/device_port.go b/model/entity/device_port.go index fd66c38..4aed908 100644 --- a/model/entity/device_port.go +++ b/model/entity/device_port.go @@ -10,6 +10,15 @@ import ( "github.com/google/uuid" ) +type PortStatus string + +const ( + PortStatusOn PortStatus = "on" + PortStatusOff PortStatus = "off" + PortStatusDyingGasp PortStatus = "dyingGasp" + PortStatusLOS PortStatus = "los" +) + type StringSlice []string func (s StringSlice) Value() (driver.Value, error) { @@ -45,14 +54,18 @@ func (s *StringSlice) Scan(value interface{}) error { } type PortAssignment struct { - PortNumber int `json:"port_number"` - CustomerName *string `json:"customer_name"` // Nullable for empty ports + PortNumber int `json:"port_number"` + CustomerName *string `json:"customer_name"` // Nullable for empty ports + Status PortStatus `json:"status"` // Add status field + Bandwidth *string `json:"bandwidth"` // Add bandwidth field (nullable) } type PortAssignmentResponse struct { - PortNumber int `json:"port_number"` - CustomerName *string `json:"customer_name"` - IsOccupied bool `json:"is_occupied"` + PortNumber int `json:"port_number"` + CustomerName *string `json:"customer_name"` + IsOccupied bool `json:"is_occupied"` + Status PortStatus `json:"status"` // Add status field + Bandwidth *string `json:"bandwidth"` // Add bandwidth field } type PortAssignments []PortAssignment @@ -105,6 +118,9 @@ type DevicePort struct { // Helper method to get customer names with port numbers func (dp *DevicePort) GetCustomerNamesWithPorts() []string { + if dp.Device.DeviceType == "OTB" { + return []string{} // OTB devices do not have port assignments + } var result []string for _, assignment := range dp.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { @@ -124,6 +140,9 @@ func (dp *DevicePort) GetCustomerNamesWithPorts() []string { // Helper method to get only customer names func (dp *DevicePort) GetCustomerNames() []string { + if dp.Device.DeviceType == "OTB" { + return []string{} // OTB devices do not have port assignments + } var result []string for _, assignment := range dp.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { @@ -143,6 +162,11 @@ func (dp *DevicePort) GetCustomerNames() []string { func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { // If port_used is 0, return empty array + + if dp.Device.DeviceType == "OTB" { + return []PortAssignmentResponse{} + } + if dp.PortUsed == 0 { return []PortAssignmentResponse{} } @@ -150,12 +174,12 @@ func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { var result []PortAssignmentResponse // Step 1: Create a map of all port assignments - portMap := make(map[int]*string) + portMap := make(map[int]*PortAssignment) maxPort := 0 if len(dp.PortAssignments) > 0 { for _, assignment := range dp.PortAssignments { - portMap[assignment.PortNumber] = assignment.CustomerName + portMap[assignment.PortNumber] = &assignment if assignment.PortNumber > maxPort { maxPort = assignment.PortNumber } @@ -164,8 +188,18 @@ func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { // Fallback: use CustomerNames array if PortAssignments is empty for i, name := range dp.CustomerNames { portNum := i + 1 - if name != "" { - portMap[portNum] = &name + if name != "" || portNum <= dp.PortUsed { + var customerName *string + if name != "" { + customerName = &name + } + assignment := PortAssignment{ + PortNumber: portNum, + CustomerName: customerName, + Status: PortStatusOn, // Default status + Bandwidth: nil, // Default bandwidth + } + portMap[portNum] = &assignment if portNum > maxPort { maxPort = portNum } @@ -179,10 +213,9 @@ func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { } // Step 2: Determine which ports should be occupied - // First, collect all ports that have customers portsWithCustomers := make([]int, 0) - for portNum, customerName := range portMap { - if customerName != nil && *customerName != "" { + for portNum, assignment := range portMap { + if assignment.CustomerName != nil && *assignment.CustomerName != "" { portsWithCustomers = append(portsWithCustomers, portNum) } } @@ -213,20 +246,33 @@ func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { // Step 4: Create the result for ALL ports up to maxPort for i := 1; i <= maxPort; i++ { var customerName *string - if portMap[i] != nil { - customerName = portMap[i] + var status PortStatus = PortStatusOff // Default status for unoccupied ports + var bandwidth *string + + if assignment := portMap[i]; assignment != nil { + customerName = assignment.CustomerName + status = assignment.Status + bandwidth = assignment.Bandwidth + } else if occupiedPorts[i] { + // Port is occupied but no assignment data - use defaults + status = PortStatusOn } result = append(result, PortAssignmentResponse{ PortNumber: i, CustomerName: customerName, IsOccupied: occupiedPorts[i], + Status: status, + Bandwidth: bandwidth, }) } return result } func (dp *DevicePort) GetCustomerNamesOnly() []string { + if dp.Device.DeviceType == "OTB" { + return []string{} // OTB devices do not have port assignments + } var result []string // If we have PortAssignments data, use it diff --git a/repository/device_details.go b/repository/device_details.go index e4ce6e0..8a38e5c 100644 --- a/repository/device_details.go +++ b/repository/device_details.go @@ -108,6 +108,8 @@ func (r *deviceDetailsRepo) UpdateCustomerByPort(deviceID uuid.UUID, update req. devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, + Status: entity.PortStatusOff, + Bandwidth: nil, } } } @@ -129,13 +131,26 @@ func (r *deviceDetailsRepo) UpdateCustomerByPort(deviceID uuid.UUID, update req. } } - // Update the port assignment if update.NewCustomerName == nil { // Remove customer from port devicePort.PortAssignments[portIndex].CustomerName = nil + devicePort.PortAssignments[portIndex].Status = entity.PortStatusOff + devicePort.PortAssignments[portIndex].Bandwidth = nil } else { // Assign/update customer on port devicePort.PortAssignments[portIndex].CustomerName = update.NewCustomerName + + // Update status if provided, otherwise default to "on" + if update.Status != nil { + devicePort.PortAssignments[portIndex].Status = *update.Status + } else { + devicePort.PortAssignments[portIndex].Status = entity.PortStatusOn + } + + // Update bandwidth if provided + if update.Bandwidth != nil { + devicePort.PortAssignments[portIndex].Bandwidth = update.Bandwidth + } } // Update counters and backward compatibility fields @@ -323,6 +338,8 @@ func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignment devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, + Status: entity.PortStatusOff, + Bandwidth: nil, } } } @@ -345,6 +362,20 @@ func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignment if portIndex < len(devicePort.PortAssignments) { devicePort.PortAssignments[portIndex].PortNumber = assignment.PortNumber devicePort.PortAssignments[portIndex].CustomerName = assignment.CustomerName + + // Update status if provided, otherwise default based on customer presence + if assignment.Status != nil { + devicePort.PortAssignments[portIndex].Status = *assignment.Status + } else if assignment.CustomerName != nil && *assignment.CustomerName != "" { + devicePort.PortAssignments[portIndex].Status = entity.PortStatusOn + } else { + devicePort.PortAssignments[portIndex].Status = entity.PortStatusOff + } + + // Update bandwidth if provided + if assignment.Bandwidth != nil { + devicePort.PortAssignments[portIndex].Bandwidth = assignment.Bandwidth + } } } @@ -693,6 +724,8 @@ func (r *deviceDetailsRepo) AssignCustomerToPort(deviceID uuid.UUID, customerNam devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, + Status: entity.PortStatusOff, // Default status + Bandwidth: nil, } } } @@ -734,6 +767,7 @@ func (r *deviceDetailsRepo) AssignCustomerToPort(deviceID uuid.UUID, customerNam // Assign customer to port devicePort.PortAssignments[portIndex].CustomerName = &customerName + devicePort.PortAssignments[portIndex].Status = entity.PortStatusOn // Default to "on" when assigning // Update both PortAssignments and CustomerNames for backward compatibility r.updateDevicePortCounters(&devicePort) diff --git a/utils/helper/device_details.go b/utils/helper/device_details.go index 6eb53b2..87cbfe9 100644 --- a/utils/helper/device_details.go +++ b/utils/helper/device_details.go @@ -112,47 +112,52 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic towerInfos = append(towerInfos, info) } - // Convert port assignments - entityPortAssignments := device.DevicePort.GetPortAssignmentsWithDetails() - resPortAssignments := make([]res.PortAssignmentResponse, len(entityPortAssignments)) - for i, pa := range entityPortAssignments { - resPortAssignments[i] = res.PortAssignmentResponse{ - PortNumber: pa.PortNumber, - CustomerName: pa.CustomerName, - IsOccupied: pa.IsOccupied, - } - } + // REPLACE ALL THE EXISTING PORT ASSIGNMENT CODE WITH THIS: + // Convert port assignments - only for ODP devices + var finalPortAssignments []res.PortAssignmentResponse + var customerNames []string - portAssignments := device.DevicePort.GetPortAssignmentsWithDetails() - - // Ensure we show all ports up to PortAmount - portAssignmentMap := make(map[int]res.PortAssignmentResponse) - for _, pa := range portAssignments { - portAssignmentMap[pa.PortNumber] = res.PortAssignmentResponse{ - PortNumber: pa.PortNumber, - CustomerName: pa.CustomerName, - IsOccupied: pa.IsOccupied, + if device.DeviceType == "ODP" { + // Only ODP devices have port assignments and customers + entityPortAssignments := device.DevicePort.GetPortAssignmentsWithDetails() + + // Ensure we show all ports up to PortAmount + portAssignmentMap := make(map[int]res.PortAssignmentResponse) + for _, pa := range entityPortAssignments { + portAssignmentMap[pa.PortNumber] = res.PortAssignmentResponse{ + PortNumber: pa.PortNumber, + CustomerName: pa.CustomerName, + IsOccupied: pa.IsOccupied, + Status: pa.Status, + Bandwidth: pa.Bandwidth, + } } - } - - // Fill in missing ports - finalPortAssignments := make([]res.PortAssignmentResponse, 0) - for i := 1; i <= device.PortAmount; i++ { - if assignment, exists := portAssignmentMap[i]; exists { - finalPortAssignments = append(finalPortAssignments, assignment) - } else { - finalPortAssignments = append(finalPortAssignments, res.PortAssignmentResponse{ - PortNumber: i, - CustomerName: nil, - IsOccupied: false, - }) + + // Fill in missing ports + finalPortAssignments = make([]res.PortAssignmentResponse, 0) + for i := 1; i <= device.PortAmount; i++ { + if assignment, exists := portAssignmentMap[i]; exists { + finalPortAssignments = append(finalPortAssignments, assignment) + } else { + finalPortAssignments = append(finalPortAssignments, res.PortAssignmentResponse{ + PortNumber: i, + CustomerName: nil, + IsOccupied: false, + Status: entity.PortStatusOff, + Bandwidth: nil, + }) + } } + + // Get customer names for ODP devices + customerNames = device.DevicePort.GetCustomerNamesWithPorts() + } else { + // OTB and other device types don't have customers or detailed port assignments + finalPortAssignments = []res.PortAssignmentResponse{} + customerNames = []string{} } - // Get customer names - customerNames := device.DevicePort.GetCustomerNamesWithPorts() - - // DIRECT FIX: Use device.GetAllImageURLs() method directly + // Get all image URLs allImageURLs := device.GetAllImageURLs() // Handle empty slice vs nil for JSON response @@ -171,13 +176,13 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic PortAmount: device.PortAmount, PortUsed: device.DevicePort.PortUsed, PortAvailable: device.DevicePort.PortAvailable, - CustomerNames: customerNames, - PortAssignments: finalPortAssignments, + CustomerNames: customerNames, // Empty for OTB + PortAssignments: finalPortAssignments, // Empty for OTB Province: device.Province, City: device.City, District: device.District, - ImageURL: device.ImageURL, // Primary image - ImageURLs: allImageURLs, // All images using device.GetAllImageURLs() + ImageURL: device.ImageURL, + ImageURLs: allImageURLs, Backbones: backboneInfos, Fishbones: fishboneInfos, Towers: towerInfos, diff --git a/utils/validation/validation_ports.go b/utils/validation/validation_ports.go new file mode 100644 index 0000000..9eece5d --- /dev/null +++ b/utils/validation/validation_ports.go @@ -0,0 +1,22 @@ +package validation + +import ( + "users_management/m/model/entity" + "github.com/go-playground/validator/v10" +) + +// RegisterPortValidators registers port-related validation rules +func RegisterPortValidators(validate *validator.Validate) { + validate.RegisterValidation("portstatus", validatePortStatus) +} + +// validatePortStatus checks if the value is a valid PortStatus +func validatePortStatus(fl validator.FieldLevel) bool { + value := fl.Field().String() + switch entity.PortStatus(value) { + case entity.PortStatusOn, entity.PortStatusOff, entity.PortStatusDyingGasp, entity.PortStatusLOS: + return true + default: + return false + } +} \ No newline at end of file diff --git a/utils/validation/validation_username.go b/utils/validation/validation_username.go index 8ebffe3..88b3d44 100644 --- a/utils/validation/validation_username.go +++ b/utils/validation/validation_username.go @@ -14,6 +14,8 @@ func RegisterCustomValidators(validate *validator.Validate) { validate.RegisterValidation("alphanumunderscore", validateAlphaNumUnderscore) validate.RegisterValidation("alphaspace", validateAlphaSpace) // Add this validate.RegisterValidation("alphanum", validateAlphaNum) // Add this + + RegisterPortValidators(validate) // Ensure port validators are registered } // validateNoSpace checks that the field contains no spaces