NAM-APJATEL-BACKEND/model/entity/device_port.go

319 lines
8.3 KiB
Go

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
IsOccupied bool `json:"is_occupied"` // Explicitly store occupation status
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 - USE EXPLICIT IsOccupied FIELD
occupiedPorts := make(map[int]bool)
// For modern approach: use the explicitly stored IsOccupied field
for portNum, assignment := range portMap {
// Use the explicitly stored IsOccupied value
// If not set, fallback to checking customer name presence
isOccupied := assignment.IsOccupied
if !isOccupied && assignment.CustomerName != nil && *assignment.CustomerName != "" {
isOccupied = true
}
occupiedPorts[portNum] = isOccupied
}
// Legacy fallback: if we don't have enough explicit port assignments,
// use the old logic only for ports that don't have explicit assignment data
currentOccupiedCount := 0
for _, isOccupied := range occupiedPorts {
if isOccupied {
currentOccupiedCount++
}
}
// Only apply legacy logic if we have fewer occupied ports than port_used
// and only for ports that don't have explicit assignment data
if currentOccupiedCount < dp.PortUsed {
for i := 1; i <= maxPort && currentOccupiedCount < dp.PortUsed; i++ {
if _, hasExplicitData := portMap[i]; !hasExplicitData {
if !occupiedPorts[i] {
occupiedPorts[i] = true
currentOccupiedCount++
}
}
}
}
// 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
}
// Override status for legacy occupied ports (those without explicit assignment)
if occupiedPorts[i] && portMap[i] == nil {
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"
}