NAM-APJATEL-BACKEND/repository/device_details.go

553 lines
21 KiB
Go

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
}
type deviceDetailsRepo struct {
db *gorm.DB
}
func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo {
return &deviceDetailsRepo{
db: db,
}
}
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) {
// Count actual customers and create customer names list
customerCount := 0
customerNames := make([]string, 0)
for _, assignment := range devicePort.PortAssignments {
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
customerCount++
customerNames = append(customerNames, *assignment.CustomerName)
}
}
devicePort.CustomerCount = customerCount
devicePort.CustomerNames = customerNames
}