From 1aee7e2bdad8db88c48f2b154c5a91a21a46e653 Mon Sep 17 00:00:00 2001 From: areeqakbr Date: Sun, 15 Jun 2025 08:19:29 +0700 Subject: [PATCH] updated for bulk update --- delivery/controller/device_details.go | 152 +++++++++-- model/dto/req/deviceDetails.go | 18 ++ repository/device_details.go | 371 +++++++++++++++++++++++++- usecase/device_details.go | 82 ++++++ 4 files changed, 604 insertions(+), 19 deletions(-) diff --git a/delivery/controller/device_details.go b/delivery/controller/device_details.go index ce424b0..9962cb5 100644 --- a/delivery/controller/device_details.go +++ b/delivery/controller/device_details.go @@ -1,13 +1,18 @@ package controller import ( - "net/http" - "users_management/m/middleware" - "users_management/m/model/dto/req" - "users_management/m/usecase" - "users_management/m/utils/common" - "github.com/gin-gonic/gin" - "github.com/google/uuid" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "users_management/m/middleware" + "users_management/m/model/dto/req" + "users_management/m/usecase" + "users_management/m/utils/common" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" ) type DeviceDetailsController struct { @@ -35,9 +40,87 @@ func (c *DeviceDetailsController) Route() { deviceDetails.POST("/:id/assign-customer", c.assignCustomer) deviceDetails.PUT("/:id/port-usage", c.updatePortUsage) // New endpoint deviceDetails.PUT("/:id/port-assignments", c.updatePortAssignments) // New endpoint + + deviceDetails.PUT("/:id/update-customer-by-port", c.updateCustomerByPort) // Updated endpoint name + deviceDetails.PUT("/:id/bulk-update-customers-by-port", c.bulkUpdateCustomersByPort) // Updated endpoint name + deviceDetails.DELETE("/:id/remove-customer-by-port", c.removeCustomerByPort) + } } +func (c *DeviceDetailsController) updateCustomerByPort(ctx *gin.Context) { + id := ctx.Param("id") + deviceID, err := uuid.Parse(id) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") + return + } + + var request req.UpdateCustomerByPortDTO + if err := ctx.ShouldBindJSON(&request); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + err = c.deviceDetailsUC.UpdateCustomerByPort(deviceID, request) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + if request.NewCustomerName == nil { + common.SingleResponses(ctx, fmt.Sprintf("Customer removed from port %d successfully", request.PortNumber), nil) + } else { + common.SingleResponses(ctx, fmt.Sprintf("Customer assigned to port %d successfully", request.PortNumber), nil) + } +} + +func (c *DeviceDetailsController) bulkUpdateCustomersByPort(ctx *gin.Context) { + id := ctx.Param("id") + deviceID, err := uuid.Parse(id) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") + return + } + + var request req.BulkUpdateCustomersByPortDTO + if err := ctx.ShouldBindJSON(&request); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + err = c.deviceDetailsUC.BulkUpdateCustomersByPort(deviceID, request.Updates) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + common.SingleResponses(ctx, fmt.Sprintf("%d port assignments updated successfully", len(request.Updates)), nil) +} + +func (c *DeviceDetailsController) removeCustomerByPort(ctx *gin.Context) { + id := ctx.Param("id") + deviceID, err := uuid.Parse(id) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") + return + } + + var request req.RemoveCustomerByPortDTO + if err := ctx.ShouldBindJSON(&request); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + err = c.deviceDetailsUC.RemoveCustomerByPort(deviceID, request.PortNumber) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + common.SingleResponses(ctx, fmt.Sprintf("Customer removed from port %d successfully", request.PortNumber), nil) +} + func (c *DeviceDetailsController) assignCustomer(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) @@ -46,23 +129,58 @@ func (c *DeviceDetailsController) assignCustomer(ctx *gin.Context) { return } - var request struct { - CustomerName string `json:"customer_name"` - PortNumber *int `json:"port_number"` - } + // Try to parse as array first + var arrayRequest []req.AssignMultipleCustomersDTO - if err := ctx.ShouldBindJSON(&request); err != nil { - common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) - return + // Try to parse as single object + var singleRequest struct { + CustomerName string `json:"customer_name" binding:"required"` + PortNumber *int `json:"port_number,omitempty"` } - err = c.deviceDetailsUC.AssignCustomerToPort(deviceID, request.CustomerName, request.PortNumber) + // Get raw JSON to determine the structure + rawData, err := ctx.GetRawData() if err != nil { - common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid JSON data") return } - common.SingleResponses(ctx, "Customer assigned successfully", nil) + // Reset the request body for subsequent parsing + ctx.Request.Body = io.NopCloser(bytes.NewBuffer(rawData)) + + // Check if it's an array by looking at the first character + trimmed := bytes.TrimSpace(rawData) + if len(trimmed) > 0 && trimmed[0] == '[' { + // It's an array + if err := json.Unmarshal(rawData, &arrayRequest); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid array format: "+err.Error()) + return + } + + // Process array of assignments + err = c.deviceDetailsUC.AssignMultipleCustomersToPort(deviceID, arrayRequest) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + common.SingleResponses(ctx, fmt.Sprintf("%d customers assigned successfully", len(arrayRequest)), nil) + } else { + // It's a single object + if err := json.Unmarshal(rawData, &singleRequest); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid object format: "+err.Error()) + return + } + + // Process single assignment + err = c.deviceDetailsUC.AssignCustomerToPort(deviceID, singleRequest.CustomerName, singleRequest.PortNumber) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + common.SingleResponses(ctx, "Customer assigned successfully", nil) + } } diff --git a/model/dto/req/deviceDetails.go b/model/dto/req/deviceDetails.go index bb71e0e..ee30eec 100644 --- a/model/dto/req/deviceDetails.go +++ b/model/dto/req/deviceDetails.go @@ -25,4 +25,22 @@ type UpdateDeviceDetailsDTO struct { 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"` +} + +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 +} + +type BulkUpdateCustomersByPortDTO struct { + Updates []UpdateCustomerByPortDTO `json:"updates" binding:"required"` +} + +type RemoveCustomerByPortDTO struct { + PortNumber int `json:"port_number" binding:"required,min=1"` } \ No newline at end of file diff --git a/repository/device_details.go b/repository/device_details.go index 39db4ed..8a9cc37 100644 --- a/repository/device_details.go +++ b/repository/device_details.go @@ -33,6 +33,10 @@ type DeviceDetailsRepo interface { 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 } @@ -47,6 +51,214 @@ func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo { } } +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 @@ -536,17 +748,172 @@ func (r *deviceDetailsRepo) UpdateDevicePortUsage(deviceID uuid.UUID) error { } func (r *deviceDetailsRepo) updateDevicePortCounters(devicePort *entity.DevicePort) { - // Count actual customers and create customer names list + // 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) + 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 + }) +} \ No newline at end of file diff --git a/usecase/device_details.go b/usecase/device_details.go index 2a81867..37b3809 100644 --- a/usecase/device_details.go +++ b/usecase/device_details.go @@ -28,6 +28,10 @@ type DeviceDetailsUseCase interface { AssignCustomerToPort(deviceID uuid.UUID, customerName string,portNumber *int) error UpdatePortUsage(deviceID uuid.UUID, portUsed int) error UpdatePortAssignments(deviceID uuid.UUID, portAssignments []req.PortAssignmentDTO) 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 deviceDetailsUseCase struct { @@ -44,6 +48,84 @@ func NewDeviceDetailsUseCase(deviceDetailsRepo repository.DeviceDetailsRepo, geo } } +func (u *deviceDetailsUseCase) UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error { + // Validate port number + if update.PortNumber < 1 { + return errors.New("port number must be greater than 0") + } + + return u.deviceDetailsRepo.UpdateCustomerByPort(deviceID, update) +} + +func (u *deviceDetailsUseCase) BulkUpdateCustomersByPort(deviceID uuid.UUID, updates []req.UpdateCustomerByPortDTO) error { + if len(updates) == 0 { + return errors.New("no updates provided") + } + + // Validate all updates + portNumbers := make(map[int]bool) + newCustomerNames := make(map[string]bool) + + for _, update := range updates { + // Check for duplicate port numbers in request + if portNumbers[update.PortNumber] { + return fmt.Errorf("duplicate port number in request: %d", update.PortNumber) + } + portNumbers[update.PortNumber] = true + + // Check for duplicate new customer names in request (ignore nulls) + if update.NewCustomerName != nil && *update.NewCustomerName != "" { + if newCustomerNames[*update.NewCustomerName] { + return fmt.Errorf("duplicate new customer name in request: %s", *update.NewCustomerName) + } + newCustomerNames[*update.NewCustomerName] = true + } + + // Validate port number + if update.PortNumber < 1 { + return fmt.Errorf("port number must be greater than 0: %d", update.PortNumber) + } + } + + return u.deviceDetailsRepo.BulkUpdateCustomersByPort(deviceID, updates) +} + +func (u *deviceDetailsUseCase) RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error { + if portNumber < 1 { + return errors.New("port number must be greater than 0") + } + + return u.deviceDetailsRepo.RemoveCustomerByPort(deviceID, portNumber) +} + +func (u *deviceDetailsUseCase) AssignMultipleCustomersToPort(deviceID uuid.UUID, assignments []req.AssignMultipleCustomersDTO) error { + if len(assignments) == 0 { + return errors.New("no assignments provided") + } + + // Validate all assignments first + customerNames := make(map[string]bool) + portNumbers := make(map[int]bool) + + for _, assignment := range assignments { + // Check for duplicate customer names in the request + if customerNames[assignment.CustomerName] { + return fmt.Errorf("duplicate customer name in request: %s", assignment.CustomerName) + } + customerNames[assignment.CustomerName] = true + + // Check for duplicate port numbers in the request + if assignment.PortNumber != nil { + if portNumbers[*assignment.PortNumber] { + return fmt.Errorf("duplicate port number in request: %d", *assignment.PortNumber) + } + portNumbers[*assignment.PortNumber] = true + } + } + + return u.deviceDetailsRepo.AssignMultipleCustomersToPort(deviceID, assignments) +} + func (u *deviceDetailsUseCase) UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error { // Validate the assignments