package repository import ( "errors" "fmt" "time" "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) error RemoveCustomerFromPort(deviceID uuid.UUID, customerName string) error } type deviceDetailsRepo struct { db *gorm.DB } func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo { return &deviceDetailsRepo{ db: db, } } 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 errors.New("cannot reduce port amount below current usage") } // Update port available newPortAvailable := newPortAmount - devicePort.PortUsed return tx.Model(&entity.DevicePort{}). Where("device_id = ?", deviceID). Updates(map[string]interface{}{ "port_available": newPortAvailable, "updated_at": gorm.Expr("NOW()"), }).Error } func (r *deviceDetailsRepo) UpdateDevicePortUsage(deviceID uuid.UUID) error { return r.db.Transaction(func(tx *gorm.DB) error { // Lock the device record to prevent race conditions var device entity.Device if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ?", deviceID).First(&device).Error; err != nil { return err } var portUsed int var customerCount int var customerNames []string switch device.DeviceType { case "OTB": // For OTB: count backbones (each backbone uses 1 port) var backboneCount int64 if err := tx.Model(&entity.Backbone{}). Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID). Count(&backboneCount).Error; err != nil { return err } portUsed = int(backboneCount) customerCount = 0 // OTB doesn't serve customers directly case "closure": // For closure: count fishbones where this device is the start device var fishboneCount int64 if err := tx.Model(&entity.Fishbone{}). Where("dev_start_id = ?", deviceID). Count(&fishboneCount).Error; err != nil { return err } portUsed = int(fishboneCount) customerCount = 0 // Closure doesn't serve customers directly case "ODP": // For ODP: sum fishbone core amounts where this device is the end device var totalCores int64 if err := tx.Model(&entity.Fishbone{}). Where("dev_end_id = ?", deviceID). Select("COALESCE(SUM(core_amount), 0)"). Scan(&totalCores).Error; err != nil { return err } portUsed = int(totalCores) // For ODP: customer count should equal port_used // Get existing customer assignments var existingDevicePort entity.DevicePort if err := tx.Where("device_id = ?", deviceID).First(&existingDevicePort).Error; err == nil { customerNames = existingDevicePort.CustomerNames } // Ensure customer count matches port_used for ODP customerCount = portUsed // If we have more customers than ports used, trim the list if len(customerNames) > portUsed { customerNames = customerNames[:portUsed] } default: portUsed = 0 customerCount = 0 } // Calculate port available portAvailable := device.PortAmount - portUsed if portAvailable < 0 { portAvailable = 0 } // Update or create DevicePort record with locking result := tx.Set("gorm:query_option", "FOR UPDATE"). Model(&entity.DevicePort{}). Where("device_id = ?", deviceID). Updates(map[string]interface{}{ "port_used": portUsed, "port_available": portAvailable, "customer_count": customerCount, "customer_names": customerNames, "updated_at": gorm.Expr("NOW()"), }) if result.Error != nil { return result.Error } // If no record was updated, create a new one if result.RowsAffected == 0 { devicePort := entity.DevicePort{ ID: uuid.New(), DeviceID: deviceID, PortUsed: portUsed, PortAvailable: portAvailable, CustomerCount: customerCount, CustomerNames: customerNames, CreatedAt: time.Now(), UpdatedAt: time.Now(), } return tx.Create(&devicePort).Error } 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) 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) } // Check if there are available ports if devicePort.PortAvailable <= 0 { return fmt.Errorf("no available ports for customer assignment") } // Check if customer is already assigned for _, existing := range devicePort.CustomerNames { if existing == customerName { return fmt.Errorf("customer %s is already assigned to this device", customerName) } } // Add customer to the list devicePort.CustomerNames = append(devicePort.CustomerNames, customerName) devicePort.CustomerCount = len(devicePort.CustomerNames) devicePort.PortAvailable = devicePort.PortAvailable - 1 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 }) }