diff --git a/model/dto/req/deviceDetails.go b/model/dto/req/deviceDetails.go index 8c29d94..bb71e0e 100644 --- a/model/dto/req/deviceDetails.go +++ b/model/dto/req/deviceDetails.go @@ -16,10 +16,10 @@ type DeviceDetailsDTO struct { type UpdateDeviceDetailsDTO struct { DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"` - DeviceType *string `json:"device_type,omitempty" validate:"omitempty,oneof=OTB ODP"` + 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=1,max=100"` + 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"` diff --git a/model/dto/req/device_dto.go b/model/dto/req/device_dto.go index e8e04ce..cc35165 100644 --- a/model/dto/req/device_dto.go +++ b/model/dto/req/device_dto.go @@ -5,7 +5,7 @@ type DeviceDTO struct { DeviceType string `json:"device_type" validate:"required"` Longitude float64 `json:"longitude" validate:"required"` Latitude float64 `json:"latitude" validate:"required"` - PortAmount int `json:"port_amount" validate:"required"` + PortAmount int `json:"port_amount"` Status string `json:"status" validate:"required,oneof=active inactive maintenance"` Province *string `json:"province,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"` @@ -17,7 +17,7 @@ type UpdateDeviceDTO struct { DeviceType *string `json:"device_type,omitempty" validate:"omitempty,min=3"` 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=1"` + PortAmount *int `json:"port_amount,omitempty" validate:"omitempty"` Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"` Province *string `json:"province,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"` diff --git a/model/entity/device_port.go b/model/entity/device_port.go index e4b9a42..0e99684 100644 --- a/model/entity/device_port.go +++ b/model/entity/device_port.go @@ -141,13 +141,7 @@ func (dp *DevicePort) GetCustomerNames() []string { } func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { - // If both port_used and port_available are 0, return empty array - if dp.PortUsed == 0 && dp.PortAvailable == 0 { - return []PortAssignmentResponse{} - } - - // If port_used is 0 but port_available > 0, still return empty array - // This means the device has capacity but no ports are currently used + // If port_used is 0, return empty array if dp.PortUsed == 0 { return []PortAssignmentResponse{} } @@ -166,19 +160,20 @@ func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { } } } else { - // Fallback: use CustomerNames array + // Fallback: use CustomerNames array if PortAssignments is empty for i, name := range dp.CustomerNames { + portNum := i + 1 if name != "" { - portMap[i+1] = &name - } - if i+1 > maxPort { - maxPort = i + 1 + portMap[portNum] = &name + if portNum > maxPort { + maxPort = portNum + } } } } - // If no ports have assignments and port_used > 0, create empty assignments up to port_used - if maxPort == 0 && dp.PortUsed > 0 { + // Ensure maxPort is at least equal to port_used + if maxPort < dp.PortUsed { maxPort = dp.PortUsed } @@ -212,30 +207,20 @@ func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse { occupiedCount++ } } - - // If we still haven't reached port_used, extend maxPort - for i := maxPort + 1; occupiedCount < dp.PortUsed; i++ { - occupiedPorts[i] = true - occupiedCount++ - maxPort = i - } } - // Step 4: Create the result only for ports that are either occupied or have customers + // Step 4: Create the result for ALL ports up to maxPort for i := 1; i <= maxPort; i++ { - // Only include ports that are occupied or have customers - if occupiedPorts[i] || (portMap[i] != nil && *portMap[i] != "") { - var customerName *string - if portMap[i] != nil { - customerName = portMap[i] - } - - result = append(result, PortAssignmentResponse{ - PortNumber: i, - CustomerName: customerName, - IsOccupied: occupiedPorts[i], - }) + var customerName *string + if portMap[i] != nil { + customerName = portMap[i] } + + result = append(result, PortAssignmentResponse{ + PortNumber: i, + CustomerName: customerName, + IsOccupied: occupiedPorts[i], + }) } return result diff --git a/repository/device_details.go b/repository/device_details.go index 6b145e5..39db4ed 100644 --- a/repository/device_details.go +++ b/repository/device_details.go @@ -1,7 +1,6 @@ package repository import ( - "errors" "fmt" "time" "users_management/m/model/dto/req" @@ -33,6 +32,8 @@ type DeviceDetailsRepo interface { RemoveCustomerFromPort(deviceID uuid.UUID, customerName string) error UpdatePortUsage(deviceID uuid.UUID, portUsed int) error UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error + MigrateCustomerNamesToPortAssignments(devicePort *entity.DevicePort, devicePortAmount int) error + } @@ -222,17 +223,45 @@ func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.U // Check if new port amount is sufficient for current usage if newPortAmount < devicePort.PortUsed { - return errors.New("cannot reduce port amount below current usage") + return fmt.Errorf("cannot reduce port amount to %d, currently using %d ports", newPortAmount, devicePort.PortUsed) } - // Update port available - newPortAvailable := newPortAmount - devicePort.PortUsed - return tx.Model(&entity.DevicePort{}). - Where("device_id = ?", deviceID). - Updates(map[string]interface{}{ - "port_available": newPortAvailable, - "updated_at": gorm.Expr("NOW()"), - }).Error + // Special case: if port_amount is set to 0, clear all assignments + if newPortAmount == 0 { + devicePort.PortUsed = 0 + devicePort.PortAvailable = 0 + devicePort.CustomerCount = 0 + devicePort.CustomerNames = []string{} + devicePort.PortAssignments = entity.PortAssignments{} + devicePort.UpdatedAt = time.Now() + } else { + // Calculate new port available + newPortAvailable := newPortAmount - devicePort.PortUsed + + // Update port assignments to match new port amount + if len(devicePort.PortAssignments) > 0 { + // Resize port assignments array + newPortAssignments := make(entity.PortAssignments, newPortAmount) + + // Copy existing assignments up to the new port amount + for i := 0; i < newPortAmount; i++ { + if i < len(devicePort.PortAssignments) { + newPortAssignments[i] = devicePort.PortAssignments[i] + } else { + newPortAssignments[i] = entity.PortAssignment{ + PortNumber: i + 1, + CustomerName: nil, + } + } + } + devicePort.PortAssignments = newPortAssignments + } + + devicePort.PortAvailable = newPortAvailable + devicePort.UpdatedAt = time.Now() + } + + return tx.Save(&devicePort).Error } func (r *deviceDetailsRepo) UpdatePortUsage(deviceID uuid.UUID, portUsed int) error { @@ -253,13 +282,19 @@ func (r *deviceDetailsRepo) UpdatePortUsage(deviceID uuid.UUID, portUsed int) er return fmt.Errorf("device port record not found: %w", err) } - // Initialize port assignments if empty - if len(devicePort.PortAssignments) == 0 { + // Initialize port assignments if empty and we have customers or port_used > 0 + if len(devicePort.PortAssignments) == 0 && (len(devicePort.CustomerNames) > 0 || portUsed > 0) { devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount) for i := 0; i < device.PortAmount; i++ { + var customerName *string + // Map existing customer names to ports + if i < len(devicePort.CustomerNames) && devicePort.CustomerNames[i] != "" { + customerName = &devicePort.CustomerNames[i] + } + devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, - CustomerName: nil, + CustomerName: customerName, } } } @@ -296,6 +331,26 @@ func (r *deviceDetailsRepo) UpdatePortUsage(deviceID uuid.UUID, portUsed int) er }) } +func (r *deviceDetailsRepo) MigrateCustomerNamesToPortAssignments(devicePort *entity.DevicePort, devicePortAmount int) error { + // Only migrate if PortAssignments is empty but CustomerNames has data + if len(devicePort.PortAssignments) == 0 && len(devicePort.CustomerNames) > 0 { + devicePort.PortAssignments = make(entity.PortAssignments, devicePortAmount) + + for i := 0; i < devicePortAmount; i++ { + var customerName *string + if i < len(devicePort.CustomerNames) && devicePort.CustomerNames[i] != "" { + customerName = &devicePort.CustomerNames[i] + } + + devicePort.PortAssignments[i] = entity.PortAssignment{ + PortNumber: i + 1, + CustomerName: customerName, + } + } + } + return nil +} + // func (r *deviceDetailsRepo) ValidatePortAvailability(deviceID uuid.UUID, requiredPorts int) error { // var devicePort entity.DevicePort // if err := r.db.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { diff --git a/usecase/device_details.go b/usecase/device_details.go index 064feef..2a81867 100644 --- a/usecase/device_details.go +++ b/usecase/device_details.go @@ -118,8 +118,6 @@ func (u *deviceDetailsUseCase) UpdateDeviceDetails(id uuid.UUID, deviceDTO req.U return fmt.Errorf("validation error: %w", err) } - // Check if device exists - updates := map[string]interface{}{} if deviceDTO.DeviceCode != nil { @@ -136,13 +134,21 @@ func (u *deviceDetailsUseCase) UpdateDeviceDetails(id uuid.UUID, deviceDTO req.U } if deviceDTO.PortAmount != nil { // Validate port amount change - currentUsed, _, err := u.deviceDetailsRepo.GetPortUsageByDevice(id) - if err != nil { - return err + if *deviceDTO.PortAmount < 0 { + return fmt.Errorf("port amount cannot be negative") } - if *deviceDTO.PortAmount < currentUsed { - return fmt.Errorf("cannot reduce port amount to %d, currently using %d ports", *deviceDTO.PortAmount, currentUsed) + + // If not setting to 0, check current usage + if *deviceDTO.PortAmount > 0 { + currentUsed, _, err := u.deviceDetailsRepo.GetPortUsageByDevice(id) + if err != nil { + return err + } + if *deviceDTO.PortAmount < currentUsed { + return fmt.Errorf("cannot reduce port amount to %d, currently using %d ports", *deviceDTO.PortAmount, currentUsed) + } } + updates["port_amount"] = *deviceDTO.PortAmount } if deviceDTO.Status != nil { diff --git a/usecase/device_usecase.go b/usecase/device_usecase.go index a400043..63c7d0c 100644 --- a/usecase/device_usecase.go +++ b/usecase/device_usecase.go @@ -43,6 +43,10 @@ func (u *deviceUseCase) CreateDevice(device req.DeviceDTO) error { return fmt.Errorf("validation error: %w", err) } + if device.DeviceType == "OTB" || device.DeviceType == "ODP" && device.PortAmount <= 0 { + return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices") + } + newDevice := entity.Device{ ID: uuid.New(), DeviceCode: device.DeviceCode, @@ -92,7 +96,6 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e if err != nil { return fmt.Errorf("validation error: %w", err) } - updates := map[string]interface{}{} if device.DeviceCode != nil { @@ -122,6 +125,9 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e if device.District != nil { updates["District"] = *device.District } + if device.DeviceType != nil && (*device.DeviceType == "OTB" || *device.DeviceType == "ODP") && device.PortAmount != nil && *device.PortAmount <= 0 { + return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices") + } if len(updates) == 0 { return fmt.Errorf("no update data") @@ -129,6 +135,8 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e updates["UpdatedAt"] = time.Now() + + return u.deviceRepo.Update(id, updates) } diff --git a/utils/helper/device_details.go b/utils/helper/device_details.go index 8d4f89f..201c28d 100644 --- a/utils/helper/device_details.go +++ b/utils/helper/device_details.go @@ -168,10 +168,17 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic }) } } + + var customerNames []string // If port_used is 0, return empty port assignments - if device.DevicePort.PortUsed == 0 { + if device.PortAmount == 0 { finalPortAssignments = []res.PortAssignmentResponse{} + customerNames = []string{} + } else if device.DevicePort.PortUsed == 0 { + // If port_used is 0, return empty port assignments but keep port_amount structure + finalPortAssignments = []res.PortAssignmentResponse{} + customerNames = []string{} } else { // Get port assignments with details portAssignments := device.DevicePort.GetPortAssignmentsWithDetails() @@ -185,6 +192,9 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic IsOccupied: pa.IsOccupied, } } + + // Get customer names + customerNames = device.DevicePort.GetCustomerNamesOnly() } response := res.DeviceDetailsResponse{ @@ -198,7 +208,7 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic PortAmount: device.PortAmount, PortUsed: device.DevicePort.PortUsed, PortAvailable: device.DevicePort.PortAvailable, - CustomerNames: device.DevicePort.GetCustomerNamesOnly(), // Just customer names + CustomerNames: customerNames, // Just customer names PortAssignments: finalPortAssignments, // Use finalPortAssignments instead of resPortAssignments Region: device.Region, Province: device.Province,