package repository import ( "fmt" "time" "users_management/m/model/dto/req" "users_management/m/model/entity" "github.com/google/uuid" "gorm.io/gorm" ) type DeviceDetailsRepo interface { Create(device entity.Device) error GetAll() ([]entity.DeviceDetails, error) GetByID(id uuid.UUID) (entity.DeviceDetails, error) Update(id uuid.UUID, updates map[string]interface{}) error Delete(id uuid.UUID) error // Port management UpdateDevicePortUsage(deviceID uuid.UUID) error // ValidatePortAvailability(deviceID uuid.UUID, requiredPorts int) error // Connection management GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error) GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error) GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error) // Validation helpers GetPortUsageByDevice(deviceID uuid.UUID) (portUsed, portAvailable int, err error) AssignCustomerToPort(deviceID uuid.UUID, customerName string, portNumber *int) error 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 AssignMultipleCustomersToPort(deviceID uuid.UUID, assignments []req.AssignMultipleCustomersDTO) error UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error BulkUpdateCustomersByPort(deviceID uuid.UUID, updates []req.UpdateCustomerByPortDTO) error RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error } type deviceDetailsRepo struct { db *gorm.DB } func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo { return &deviceDetailsRepo{ db: db, } } func (r *deviceDetailsRepo) UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error { return r.db.Transaction(func(tx *gorm.DB) error { // Lock both device and device_port records var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } // Only ODP devices can have customer assignments if device.DeviceType != "ODP" { return fmt.Errorf("customer assignments can only be updated for ODP devices") } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Initialize port assignments if empty if len(devicePort.PortAssignments) == 0 { devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount) for i := 0; i < device.PortAmount; i++ { devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, } } } // Validate port number if update.PortNumber > device.PortAmount { return fmt.Errorf("port number %d exceeds device capacity (%d)", update.PortNumber, device.PortAmount) } portIndex := update.PortNumber - 1 // If assigning a new customer name if update.NewCustomerName != nil && *update.NewCustomerName != "" { // Check if customer name already exists on another port for i, assignment := range devicePort.PortAssignments { if i != portIndex && assignment.CustomerName != nil && *assignment.CustomerName == *update.NewCustomerName { return fmt.Errorf("customer %s is already assigned to port %d", *update.NewCustomerName, i+1) } } } // Update the port assignment if update.NewCustomerName == nil { // Remove customer from port devicePort.PortAssignments[portIndex].CustomerName = nil } else { // Assign/update customer on port devicePort.PortAssignments[portIndex].CustomerName = update.NewCustomerName } // Update counters and backward compatibility fields r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) BulkUpdateCustomersByPort(deviceID uuid.UUID, updates []req.UpdateCustomerByPortDTO) error { return r.db.Transaction(func(tx *gorm.DB) error { // Lock both device and device_port records var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } // Only ODP devices can have customer assignments if device.DeviceType != "ODP" { return fmt.Errorf("customer assignments can only be updated for ODP devices") } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Initialize port assignments if empty if len(devicePort.PortAssignments) == 0 { devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount) for i := 0; i < device.PortAmount; i++ { devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, } } } // Validate all port numbers first for _, update := range updates { if update.PortNumber > device.PortAmount { return fmt.Errorf("port number %d exceeds device capacity (%d)", update.PortNumber, device.PortAmount) } } // Create a map of final assignments to validate for duplicates finalAssignments := make(map[int]*string) // Start with current assignments for i, assignment := range devicePort.PortAssignments { finalAssignments[i+1] = assignment.CustomerName } // Apply updates for _, update := range updates { finalAssignments[update.PortNumber] = update.NewCustomerName } // Check for duplicate customer names customerNames := make(map[string]int) // customer name -> port number for portNum, customerName := range finalAssignments { if customerName != nil && *customerName != "" { if existingPort, exists := customerNames[*customerName]; exists { return fmt.Errorf("customer %s would be assigned to both port %d and port %d", *customerName, existingPort, portNum) } customerNames[*customerName] = portNum } } // Apply all updates for _, update := range updates { portIndex := update.PortNumber - 1 devicePort.PortAssignments[portIndex].CustomerName = update.NewCustomerName } // Recalculate port_used based on actual assignments highestUsedPort := 0 customerCount := 0 for _, assignment := range devicePort.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { customerCount++ if assignment.PortNumber > highestUsedPort { highestUsedPort = assignment.PortNumber } } } // Update port_used to reflect actual usage if customerCount == 0 { devicePort.PortUsed = 0 } else { // Set port_used to the highest port with a customer // or keep current port_used if it's higher (for reserved ports) if highestUsedPort > devicePort.PortUsed { devicePort.PortUsed = highestUsedPort } } // Recalculate port_available devicePort.PortAvailable = device.PortAmount - devicePort.PortUsed // Update counters and backward compatibility fields r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error { return r.db.Transaction(func(tx *gorm.DB) error { var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Validate port number if portNumber > device.PortAmount { return fmt.Errorf("port number %d exceeds device capacity (%d)", portNumber, device.PortAmount) } portIndex := portNumber - 1 // Check if port has a customer if len(devicePort.PortAssignments) <= portIndex || devicePort.PortAssignments[portIndex].CustomerName == nil || *devicePort.PortAssignments[portIndex].CustomerName == "" { return fmt.Errorf("port %d does not have a customer assigned", portNumber) } // Remove customer from port devicePort.PortAssignments[portIndex].CustomerName = nil // Update counters and backward compatibility fields r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error { return r.db.Transaction(func(tx *gorm.DB) error { // Lock both device and device_port records var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } // Only ODP devices can have port assignments if device.DeviceType != "ODP" { return fmt.Errorf("port assignments can only be updated for ODP devices") } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Validate port numbers are within device capacity for _, assignment := range assignments { if assignment.PortNumber < 1 || assignment.PortNumber > device.PortAmount { return fmt.Errorf("port number %d is out of range (1-%d)", assignment.PortNumber, device.PortAmount) } } // Initialize port assignments array if needed if len(devicePort.PortAssignments) == 0 { devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount) for i := 0; i < device.PortAmount; i++ { devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, } } } // Check for duplicate customer names (if not null) customerNames := make(map[string]int) // map customer name to port number for _, assignment := range assignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { if existingPort, exists := customerNames[*assignment.CustomerName]; exists { return fmt.Errorf("customer %s is assigned to multiple ports (%d and %d)", *assignment.CustomerName, existingPort, assignment.PortNumber) } customerNames[*assignment.CustomerName] = assignment.PortNumber } } // Update port assignments for _, assignment := range assignments { portIndex := assignment.PortNumber - 1 if portIndex < len(devicePort.PortAssignments) { devicePort.PortAssignments[portIndex].PortNumber = assignment.PortNumber devicePort.PortAssignments[portIndex].CustomerName = assignment.CustomerName } } // Update counters and backward compatibility fields r.updateDevicePortCounters(&devicePort) // Calculate port usage based on assignments portUsed := 0 for _, assignment := range devicePort.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { portUsed++ } } devicePort.PortUsed = portUsed devicePort.PortAvailable = device.PortAmount - portUsed devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) Create(device entity.Device) error { return r.db.Transaction(func(tx *gorm.DB) error { // Create device if err := tx.Create(&device).Error; err != nil { return err } // Create corresponding device port devicePort := entity.DevicePort{ ID: uuid.New(), DeviceID: device.ID, PortUsed: 0, PortAvailable: device.PortAmount, CreatedAt: device.CreatedAt, UpdatedAt: device.UpdatedAt, } return tx.Create(&devicePort).Error }) } func (r *deviceDetailsRepo) GetAll() ([]entity.DeviceDetails, error) { var devices []entity.DeviceDetails err := r.db. Preload("DevicePort"). Preload("BackbonesStart"). Preload("BackbonesStart.DeviceStart"). Preload("BackbonesStart.DeviceEnd"). Preload("BackbonesEnd"). Preload("BackbonesEnd.DeviceStart"). Preload("BackbonesEnd.DeviceEnd"). Preload("FishbonesStart"). Preload("FishbonesStart.DeviceStart"). Preload("FishbonesStart.DeviceEnd"). Preload("FishbonesStart.Backbone"). Preload("FishbonesEnd"). Preload("FishbonesEnd.DeviceStart"). Preload("FishbonesEnd.DeviceEnd"). Preload("FishbonesEnd.Backbone"). Preload("Towers"). Preload("Towers.Device"). Find(&devices).Error return devices, err } func (r *deviceDetailsRepo) GetByID(id uuid.UUID) (entity.DeviceDetails, error) { var device entity.DeviceDetails err := r.db. Preload("DevicePort"). Preload("BackbonesStart"). Preload("BackbonesStart.DeviceStart"). Preload("BackbonesStart.DeviceEnd"). Preload("BackbonesEnd"). Preload("BackbonesEnd.DeviceStart"). Preload("BackbonesEnd.DeviceEnd"). Preload("FishbonesStart"). Preload("FishbonesStart.DeviceStart"). Preload("FishbonesStart.DeviceEnd"). Preload("FishbonesStart.Backbone"). Preload("FishbonesEnd"). Preload("FishbonesEnd.DeviceStart"). Preload("FishbonesEnd.DeviceEnd"). Preload("FishbonesEnd.Backbone"). Preload("Towers"). Preload("Towers.Device"). Where("id = ?", id). First(&device).Error return device, err } func (r *deviceDetailsRepo) Update(id uuid.UUID, updates map[string]interface{}) error { return r.db.Transaction(func(tx *gorm.DB) error { // Update device if err := tx.Model(&entity.Device{}).Where("id = ?", id).Updates(updates).Error; err != nil { return err } // If port_amount is updated, update device_port if portAmount, exists := updates["port_amount"]; exists { if err := r.updatePortAmountCascade(tx, id, portAmount.(int)); err != nil { return err } } return nil }) } func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.UUID, newPortAmount int) error { // Get current port usage var devicePort entity.DevicePort if err := tx.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return err } // Check if new port amount is sufficient for current usage if newPortAmount < devicePort.PortUsed { return fmt.Errorf("cannot reduce port amount to %d, currently using %d ports", newPortAmount, devicePort.PortUsed) } // 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 { return r.db.Transaction(func(tx *gorm.DB) error { var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } if portUsed > device.PortAmount { return fmt.Errorf("port_used (%d) cannot exceed port_amount (%d)", portUsed, device.PortAmount) } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // 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: customerName, } } } // Update port assignments based on new port_used value currentCustomerCount := 0 for _, assignment := range devicePort.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { currentCustomerCount++ } } if portUsed < currentCustomerCount { // Need to remove some customers (keep first N customers) customersKept := 0 for i := range devicePort.PortAssignments { if devicePort.PortAssignments[i].CustomerName != nil && *devicePort.PortAssignments[i].CustomerName != "" { if customersKept < portUsed { customersKept++ } else { devicePort.PortAssignments[i].CustomerName = nil } } } } // Update counters devicePort.PortUsed = portUsed devicePort.PortAvailable = device.PortAmount - portUsed r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } 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 { // return err // } // if devicePort.Portvailable < requiredPorts { // return errors.New("insufficient available ports") // } // return nil // } func (r *deviceDetailsRepo) GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error) { var backbones []entity.Backbone err := r.db.Preload("DeviceStart").Preload("DeviceEnd"). Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID). Find(&backbones).Error return backbones, err } func (r *deviceDetailsRepo) GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error) { var fishbones []entity.Fishbone err := r.db.Preload("Backbone").Preload("DeviceStart").Preload("DeviceEnd"). Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID). Find(&fishbones).Error return fishbones, err } func (r *deviceDetailsRepo) GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error) { var towers []entity.Tower err := r.db.Where("dev_id = ?", deviceID).Find(&towers).Error return towers, err } func (r *deviceDetailsRepo) GetPortUsageByDevice(deviceID uuid.UUID) (portUsed, portAvailable int, err error) { var devicePort entity.DevicePort err = r.db.Where("device_id = ?", deviceID).First(&devicePort).Error if err != nil { return 0, 0, err } return devicePort.PortUsed, devicePort.PortAvailable, nil } func (r *deviceDetailsRepo) Delete(id uuid.UUID) error { return r.db.Transaction(func(tx *gorm.DB) error { // Delete device port first if err := tx.Where("device_id = ?", id).Delete(&entity.DevicePort{}).Error; err != nil { return err } // Delete device return tx.Delete(&entity.Device{}, id).Error }) } func (r *deviceDetailsRepo) AssignCustomerToPort(deviceID uuid.UUID, customerName string, portNumber *int) error { return r.db.Transaction(func(tx *gorm.DB) error { // Lock both device and device_port records var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } // Only ODP devices can have customers assigned if device.DeviceType != "ODP" { return fmt.Errorf("customers can only be assigned to ODP devices") } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Initialize port assignments if empty if len(devicePort.PortAssignments) == 0 { devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount) for i := 0; i < device.PortAmount; i++ { devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, } } } // Determine port number to use var targetPortNumber int if portNumber != nil { targetPortNumber = *portNumber if targetPortNumber < 1 || targetPortNumber > device.PortAmount { return fmt.Errorf("port number must be between 1 and %d", device.PortAmount) } } else { // Auto-assign to first available port targetPortNumber = -1 for i, assignment := range devicePort.PortAssignments { if assignment.CustomerName == nil || *assignment.CustomerName == "" { targetPortNumber = i + 1 break } } if targetPortNumber == -1 { return fmt.Errorf("no available ports for customer assignment") } } // Check if the specified port is already occupied portIndex := targetPortNumber - 1 if devicePort.PortAssignments[portIndex].CustomerName != nil && *devicePort.PortAssignments[portIndex].CustomerName != "" { return fmt.Errorf("port %d is already occupied by %s", targetPortNumber, *devicePort.PortAssignments[portIndex].CustomerName) } // Check if customer is already assigned to another port for i, assignment := range devicePort.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName == customerName { return fmt.Errorf("customer %s is already assigned to port %d", customerName, i+1) } } // Assign customer to port devicePort.PortAssignments[portIndex].CustomerName = &customerName // Update both PortAssignments and CustomerNames for backward compatibility r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) RemoveCustomerFromPort(deviceID uuid.UUID, customerName string) error { return r.db.Transaction(func(tx *gorm.DB) error { var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Find and remove customer newCustomerNames := make([]string, 0) found := false for _, existing := range devicePort.CustomerNames { if existing != customerName { newCustomerNames = append(newCustomerNames, existing) } else { found = true } } if !found { return fmt.Errorf("customer %s is not assigned to this device", customerName) } devicePort.CustomerNames = newCustomerNames devicePort.CustomerCount = len(devicePort.CustomerNames) devicePort.PortAvailable = devicePort.PortAvailable + 1 devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) UpdateDevicePortUsage(deviceID uuid.UUID) error { // This method is required by the DeviceDetailsRepo interface return r.db.Transaction(func(tx *gorm.DB) error { var devicePort entity.DevicePort if err := tx.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Update based on port assignments r.updateDevicePortCounters(&devicePort) devicePort.PortUsed = devicePort.CustomerCount devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) updateDevicePortCounters(devicePort *entity.DevicePort) { // Get device port amount (need to load device if not loaded) var device entity.Device if devicePort.Device.ID == uuid.Nil { r.db.Where("id = ?", devicePort.DeviceID).First(&device) devicePort.Device = device } else { device = devicePort.Device } // Count actual customers and find highest used port customerCount := 0 customerNames := make([]string, 0) highestCustomerPort := 0 for _, assignment := range devicePort.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { customerCount++ customerNames = append(customerNames, *assignment.CustomerName) // Just customer names, not with port numbers if assignment.PortNumber > highestCustomerPort { highestCustomerPort = assignment.PortNumber } } } // Set port_used to the highest port number that has a customer // This ensures we don't have gaps in port usage if customerCount == 0 { devicePort.PortUsed = 0 } else { devicePort.PortUsed = highestCustomerPort } // Calculate port_available devicePort.PortAvailable = device.PortAmount - devicePort.PortUsed // Update customer fields devicePort.CustomerCount = customerCount devicePort.CustomerNames = customerNames } func (r *deviceDetailsRepo) AssignMultipleCustomersToPort(deviceID uuid.UUID, assignments []req.AssignMultipleCustomersDTO) error { return r.db.Transaction(func(tx *gorm.DB) error { // Lock both device and device_port records var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } // Only ODP devices can have customers assigned if device.DeviceType != "ODP" { return fmt.Errorf("customers can only be assigned to ODP devices") } var devicePort entity.DevicePort if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return fmt.Errorf("device port record not found: %w", err) } // Initialize port assignments if empty if len(devicePort.PortAssignments) == 0 { devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount) for i := 0; i < device.PortAmount; i++ { devicePort.PortAssignments[i] = entity.PortAssignment{ PortNumber: i + 1, CustomerName: nil, } } } // Check if there are enough available ports availablePorts := 0 for _, assignment := range devicePort.PortAssignments { if assignment.CustomerName == nil || *assignment.CustomerName == "" { availablePorts++ } } portsNeeded := len(assignments) if portsNeeded > availablePorts { return fmt.Errorf("not enough available ports: need %d, available %d", portsNeeded, availablePorts) } // Process each assignment for _, assignment := range assignments { var targetPortNumber int if assignment.PortNumber != nil { targetPortNumber = *assignment.PortNumber if targetPortNumber < 1 || targetPortNumber > device.PortAmount { return fmt.Errorf("port number %d must be between 1 and %d", targetPortNumber, device.PortAmount) } } else { // Auto-assign to first available port targetPortNumber = -1 for i, portAssignment := range devicePort.PortAssignments { if portAssignment.CustomerName == nil || *portAssignment.CustomerName == "" { targetPortNumber = i + 1 break } } if targetPortNumber == -1 { return fmt.Errorf("no available ports for customer assignment") } } // Check if the specified port is already occupied portIndex := targetPortNumber - 1 if devicePort.PortAssignments[portIndex].CustomerName != nil && *devicePort.PortAssignments[portIndex].CustomerName != "" { return fmt.Errorf("port %d is already occupied by %s", targetPortNumber, *devicePort.PortAssignments[portIndex].CustomerName) } // Check if customer is already assigned to another port for i, portAssignment := range devicePort.PortAssignments { if portAssignment.CustomerName != nil && *portAssignment.CustomerName == assignment.CustomerName { return fmt.Errorf("customer %s is already assigned to port %d", assignment.CustomerName, i+1) } } // Assign customer to port devicePort.PortAssignments[portIndex].CustomerName = &assignment.CustomerName } // Update counters and backward compatibility fields r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) } func (r *deviceDetailsRepo) RecalculatePortUsageAfterBulkUpdate(deviceID uuid.UUID) error { return r.db.Transaction(func(tx *gorm.DB) error { var device entity.Device if err := tx.Where("id = ?", deviceID).First(&device).Error; err != nil { return err } var devicePort entity.DevicePort if err := tx.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil { return err } // Find the highest port number with a customer highestUsedPort := 0 customerCount := 0 for _, assignment := range devicePort.PortAssignments { if assignment.CustomerName != nil && *assignment.CustomerName != "" { customerCount++ if assignment.PortNumber > highestUsedPort { highestUsedPort = assignment.PortNumber } } } // Update port usage devicePort.PortUsed = highestUsedPort devicePort.PortAvailable = device.PortAmount - devicePort.PortUsed // Update other counters r.updateDevicePortCounters(&devicePort) devicePort.UpdatedAt = time.Now() return tx.Save(&devicePort).Error }) }