package entity import ( "database/sql/driver" "encoding/json" "fmt" "sort" "time" "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) { if len(s) == 0 { return "[]", nil } return json.Marshal(s) } func (s *StringSlice) Scan(value interface{}) error { if value == nil { *s = StringSlice{} return nil } var bytes []byte switch v := value.(type) { case []byte: // []byte and []uint8 are the same type in Go bytes = v case string: bytes = []byte(v) default: return fmt.Errorf("cannot scan %T into StringSlice", value) } // Handle empty string case if len(bytes) == 0 { *s = StringSlice{} return nil } return json.Unmarshal(bytes, s) } type PortAssignment struct { 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"` Status PortStatus `json:"status"` // Add status field Bandwidth *string `json:"bandwidth"` // Add bandwidth field } type PortAssignments []PortAssignment func (p PortAssignments) Value() (driver.Value, error) { if len(p) == 0 { return "[]", nil } return json.Marshal(p) } func (p *PortAssignments) Scan(value interface{}) error { if value == nil { *p = PortAssignments{} return nil } var bytes []byte switch v := value.(type) { case []byte: bytes = v case string: bytes = []byte(v) default: return fmt.Errorf("cannot scan %T into PortAssignments", value) } if len(bytes) == 0 { *p = PortAssignments{} return nil } return json.Unmarshal(bytes, p) } type DevicePort struct { ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"` PortUsed int `json:"port_used"` PortAvailable int `json:"port_available"` CustomerCount int `json:"customer_count"` CustomerNames StringSlice `json:"customer_names" gorm:"type:jsonb"` PortAssignments PortAssignments `json:"port_assignments" gorm:"type:jsonb"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` // Relationships Device Device `json:"device" gorm:"foreignKey:DeviceID"` } // 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 != "" { result = append(result, fmt.Sprintf("%d: %s", assignment.PortNumber, *assignment.CustomerName)) } } // If no port assignments, fall back to customer names if len(result) == 0 { for i, name := range dp.CustomerNames { if name != "" { result = append(result, fmt.Sprintf("%d: %s", i+1, name)) } } } return result } // 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 != "" { result = append(result, *assignment.CustomerName) } } // If no port assignments, fall back to customer names if len(result) == 0 { for _, name := range dp.CustomerNames { if name != "" { result = append(result, name) } } } return result } 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{} } var result []PortAssignmentResponse // Step 1: Create a map of all port assignments portMap := make(map[int]*PortAssignment) maxPort := 0 if len(dp.PortAssignments) > 0 { for _, assignment := range dp.PortAssignments { portMap[assignment.PortNumber] = &assignment if assignment.PortNumber > maxPort { maxPort = assignment.PortNumber } } } else { // Fallback: use CustomerNames array if PortAssignments is empty for i, name := range dp.CustomerNames { portNum := i + 1 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 } } } } // Ensure maxPort is at least equal to port_used if maxPort < dp.PortUsed { maxPort = dp.PortUsed } // Step 2: Determine which ports should be occupied portsWithCustomers := make([]int, 0) for portNum, assignment := range portMap { if assignment.CustomerName != nil && *assignment.CustomerName != "" { portsWithCustomers = append(portsWithCustomers, portNum) } } // Sort to get them in order sort.Ints(portsWithCustomers) // Step 3: Determine occupied ports occupiedPorts := make(map[int]bool) // All ports with customers are occupied for _, portNum := range portsWithCustomers { occupiedPorts[portNum] = true } // Fill remaining slots to reach port_used occupiedCount := len(portsWithCustomers) if occupiedCount < dp.PortUsed { // Need to occupy more ports (ports without customer names but still occupied) for i := 1; i <= maxPort && occupiedCount < dp.PortUsed; i++ { if !occupiedPorts[i] { occupiedPorts[i] = true occupiedCount++ } } } // Step 4: Create the result for ALL ports up to maxPort for i := 1; i <= maxPort; i++ { var customerName *string 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 if len(dp.PortAssignments) > 0 { for _, assignment := range dp.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { result = append(result, *assignment.CustomerName) } } } else { // Fallback: use CustomerNames directly for _, name := range dp.CustomerNames { if name != "" { result = append(result, name) } } } return result } func (DevicePort) TableName() string { return "device_ports" }