adding bandwidth and status on odp customer assigntment

This commit is contained in:
areeqakbr 2025-06-23 16:35:13 +07:00
parent 0c926d722b
commit a2c71ca81e
8 changed files with 218 additions and 93 deletions

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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
}
}

View File

@ -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