updated for bulk update
This commit is contained in:
parent
8afca3054a
commit
1aee7e2bda
|
|
@ -1,13 +1,18 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"bytes"
|
||||||
"users_management/m/middleware"
|
"encoding/json"
|
||||||
"users_management/m/model/dto/req"
|
"fmt"
|
||||||
"users_management/m/usecase"
|
"io"
|
||||||
"users_management/m/utils/common"
|
"net/http"
|
||||||
"github.com/gin-gonic/gin"
|
"users_management/m/middleware"
|
||||||
"github.com/google/uuid"
|
"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 {
|
type DeviceDetailsController struct {
|
||||||
|
|
@ -35,9 +40,87 @@ func (c *DeviceDetailsController) Route() {
|
||||||
deviceDetails.POST("/:id/assign-customer", c.assignCustomer)
|
deviceDetails.POST("/:id/assign-customer", c.assignCustomer)
|
||||||
deviceDetails.PUT("/:id/port-usage", c.updatePortUsage) // New endpoint
|
deviceDetails.PUT("/:id/port-usage", c.updatePortUsage) // New endpoint
|
||||||
deviceDetails.PUT("/:id/port-assignments", c.updatePortAssignments) // 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) {
|
func (c *DeviceDetailsController) assignCustomer(ctx *gin.Context) {
|
||||||
id := ctx.Param("id")
|
id := ctx.Param("id")
|
||||||
deviceID, err := uuid.Parse(id)
|
deviceID, err := uuid.Parse(id)
|
||||||
|
|
@ -46,23 +129,58 @@ func (c *DeviceDetailsController) assignCustomer(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var request struct {
|
// Try to parse as array first
|
||||||
CustomerName string `json:"customer_name"`
|
var arrayRequest []req.AssignMultipleCustomersDTO
|
||||||
PortNumber *int `json:"port_number"`
|
|
||||||
|
// Try to parse as single object
|
||||||
|
var singleRequest struct {
|
||||||
|
CustomerName string `json:"customer_name" binding:"required"`
|
||||||
|
PortNumber *int `json:"port_number,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
// Get raw JSON to determine the structure
|
||||||
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
rawData, err := ctx.GetRawData()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.deviceDetailsUC.AssignCustomerToPort(deviceID, request.CustomerName, request.PortNumber)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid JSON data")
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,21 @@ type UpdateDeviceDetailsDTO struct {
|
||||||
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
||||||
District *string `json:"district,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"`
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,10 @@ type DeviceDetailsRepo interface {
|
||||||
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
||||||
UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error
|
UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error
|
||||||
MigrateCustomerNamesToPortAssignments(devicePort *entity.DevicePort, devicePortAmount int) 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 {
|
func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error {
|
||||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
// Lock both device and device_port records
|
// 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) {
|
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
|
customerCount := 0
|
||||||
customerNames := make([]string, 0)
|
customerNames := make([]string, 0)
|
||||||
|
highestCustomerPort := 0
|
||||||
|
|
||||||
for _, assignment := range devicePort.PortAssignments {
|
for _, assignment := range devicePort.PortAssignments {
|
||||||
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
customerCount++
|
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.CustomerCount = customerCount
|
||||||
devicePort.CustomerNames = customerNames
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,10 @@ type DeviceDetailsUseCase interface {
|
||||||
AssignCustomerToPort(deviceID uuid.UUID, customerName string,portNumber *int) error
|
AssignCustomerToPort(deviceID uuid.UUID, customerName string,portNumber *int) error
|
||||||
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
||||||
UpdatePortAssignments(deviceID uuid.UUID, portAssignments []req.PortAssignmentDTO) 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 {
|
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 {
|
func (u *deviceDetailsUseCase) UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error {
|
||||||
// Validate the assignments
|
// Validate the assignments
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue