300 lines
10 KiB
Go
300 lines
10 KiB
Go
package usecase
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
"users_management/m/model/dto/req"
|
|
"users_management/m/model/dto/res"
|
|
"users_management/m/model/entity"
|
|
"users_management/m/repository"
|
|
"users_management/m/utils/helper"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type BackboneUseCase interface {
|
|
CreateBackbone(backbone req.BackboneDTO) error
|
|
GetAllBackbone() ([]res.BackboneResponse, error)
|
|
|
|
GetByID(id uuid.UUID) (res.BackboneResponse, error)
|
|
UpdateBackbone(id uuid.UUID, backbone req.UpdateBackboneDTO) error
|
|
|
|
}
|
|
|
|
type backboneUseCase struct {
|
|
backboneRepo repository.BackboneRepo
|
|
fishboneRepo repository.FishboneRepo
|
|
validate *validator.Validate
|
|
deviceDetailsRepo repository.DeviceDetailsRepo // Add this field
|
|
}
|
|
|
|
func NewBackboneUseCase(backboneRepo repository.BackboneRepo, fishboneRepo repository.FishboneRepo, deviceDetailsRepo repository.DeviceDetailsRepo) BackboneUseCase {
|
|
return &backboneUseCase{
|
|
backboneRepo: backboneRepo,
|
|
fishboneRepo: fishboneRepo,
|
|
deviceDetailsRepo: deviceDetailsRepo, // Initialize the field
|
|
validate: validator.New(),
|
|
}
|
|
}
|
|
|
|
func (u *backboneUseCase) CreateBackbone(backbone req.BackboneDTO) error {
|
|
err := u.validate.Struct(backbone)
|
|
if err != nil {
|
|
return fmt.Errorf("validation error: %w", err)
|
|
}
|
|
|
|
return u.backboneRepo.WithTransaction(func(tx *gorm.DB) error {
|
|
// Lock device records to prevent race conditions
|
|
var startDevice entity.Device
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("id = ?", backbone.DeviceStartID).First(&startDevice).Error; err != nil {
|
|
return fmt.Errorf("start device not found: %w", err)
|
|
}
|
|
|
|
var endDevice entity.Device
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("id = ?", backbone.DeviceEndID).First(&endDevice).Error; err != nil {
|
|
return fmt.Errorf("end device not found: %w", err)
|
|
}
|
|
|
|
// Validate device types - both devices must be OTB for backbones
|
|
if startDevice.DeviceType != "OTB" {
|
|
return fmt.Errorf("start device must be of type OTB, got %s", startDevice.DeviceType)
|
|
}
|
|
|
|
if endDevice.DeviceType != "OTB" {
|
|
return fmt.Errorf("end device must be of type OTB, got %s", endDevice.DeviceType)
|
|
}
|
|
|
|
// Check port availability with locking
|
|
var startDevicePort entity.DevicePort
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("device_id = ?", backbone.DeviceStartID).First(&startDevicePort).Error; err != nil {
|
|
return fmt.Errorf("start device port record not found: %w", err)
|
|
}
|
|
|
|
var endDevicePort entity.DevicePort
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("device_id = ?", backbone.DeviceEndID).First(&endDevicePort).Error; err != nil {
|
|
return fmt.Errorf("end device port record not found: %w", err)
|
|
}
|
|
|
|
// Validate port availability - each backbone uses 1 port regardless of core amount
|
|
if startDevicePort.PortAvailable < 1 {
|
|
return fmt.Errorf("start device has no available ports (available: %d, required: 1)",
|
|
startDevicePort.PortAvailable)
|
|
}
|
|
|
|
if endDevicePort.PortAvailable < 1 {
|
|
return fmt.Errorf("end device has no available ports (available: %d, required: 1)",
|
|
endDevicePort.PortAvailable)
|
|
}
|
|
|
|
newBackbone := entity.Backbone{
|
|
ID: uuid.New(),
|
|
BackboneCode: backbone.BackboneCode,
|
|
DeviceStartID: backbone.DeviceStartID,
|
|
DeviceEndID: backbone.DeviceEndID,
|
|
CoreAmount: backbone.CoreAmount,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
// Create backbone
|
|
if err := tx.Create(&newBackbone).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update port usage for both devices
|
|
if err := u.updateDevicePortUsageInTx(tx, backbone.DeviceStartID); err != nil {
|
|
return fmt.Errorf("failed to update start device port usage: %w", err)
|
|
}
|
|
|
|
if err := u.updateDevicePortUsageInTx(tx, backbone.DeviceEndID); err != nil {
|
|
return fmt.Errorf("failed to update end device port usage: %w", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
|
|
func (u *backboneUseCase) GetAllBackbone() ([]res.BackboneResponse, error) {
|
|
backbones, err := u.backboneRepo.GetAll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
totalFishbone,err := u.fishboneRepo.CountFishbone()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
backboneResp, err := helper.ConvertToBackboneResponses(backbones,totalFishbone)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return backboneResp, nil
|
|
}
|
|
|
|
func (u *backboneUseCase) GetByID(id uuid.UUID) (res.BackboneResponse, error) {
|
|
backbone, err := u.backboneRepo.GetByID(id)
|
|
if err != nil {
|
|
return res.BackboneResponse{}, err
|
|
}
|
|
|
|
fishboneCount, err := u.fishboneRepo.CountFishboneByBackboneID(backbone.ID)
|
|
|
|
if err != nil {
|
|
return res.BackboneResponse{}, err
|
|
}
|
|
|
|
backboneResp, err := helper.ConvertToBackboneRespId(backbone,fishboneCount)
|
|
|
|
if err != nil {
|
|
return res.BackboneResponse{}, err
|
|
}
|
|
|
|
return backboneResp, nil
|
|
}
|
|
|
|
func (u *backboneUseCase) UpdateBackbone(id uuid.UUID, backbone req.UpdateBackboneDTO) error {
|
|
err := u.validate.Struct(backbone)
|
|
if err != nil {
|
|
return fmt.Errorf("validation error: %w", err)
|
|
}
|
|
|
|
return u.backboneRepo.WithTransaction(func(tx *gorm.DB) error {
|
|
// Get original backbone for comparison with lock
|
|
var originalBackbone entity.Backbone
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("id = ?", id).First(&originalBackbone).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return fmt.Errorf("backbone not found")
|
|
}
|
|
return err
|
|
}
|
|
|
|
updates := make(map[string]interface{})
|
|
devicesToUpdate := make(map[uuid.UUID]bool)
|
|
devicesToUpdate[originalBackbone.DeviceStartID] = true
|
|
devicesToUpdate[originalBackbone.DeviceEndID] = true
|
|
|
|
// Validate device type changes if devices are being changed
|
|
if backbone.DeviceStartID != nil && *backbone.DeviceStartID != originalBackbone.DeviceStartID {
|
|
var newStartDevice entity.Device
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("id = ?", *backbone.DeviceStartID).First(&newStartDevice).Error; err != nil {
|
|
return fmt.Errorf("new start device not found: %w", err)
|
|
}
|
|
if newStartDevice.DeviceType != "OTB" {
|
|
return fmt.Errorf("new start device must be of type OTB, got %s", newStartDevice.DeviceType)
|
|
}
|
|
updates["dev_start_id"] = *backbone.DeviceStartID
|
|
devicesToUpdate[*backbone.DeviceStartID] = true
|
|
}
|
|
|
|
if backbone.DeviceEndID != nil && *backbone.DeviceEndID != originalBackbone.DeviceEndID {
|
|
var newEndDevice entity.Device
|
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
|
Where("id = ?", *backbone.DeviceEndID).First(&newEndDevice).Error; err != nil {
|
|
return fmt.Errorf("new end device not found: %w", err)
|
|
}
|
|
if newEndDevice.DeviceType != "OTB" {
|
|
return fmt.Errorf("new end device must be of type OTB, got %s", newEndDevice.DeviceType)
|
|
}
|
|
updates["dev_end_id"] = *backbone.DeviceEndID
|
|
devicesToUpdate[*backbone.DeviceEndID] = true
|
|
}
|
|
|
|
// Handle core amount changes
|
|
if backbone.CoreAmount != nil && *backbone.CoreAmount != originalBackbone.CoreAmount {
|
|
updates["core_amount"] = *backbone.CoreAmount
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
return fmt.Errorf("no fields to update")
|
|
}
|
|
|
|
updates["updated_at"] = time.Now()
|
|
|
|
// Update backbone
|
|
if err := tx.Model(&entity.Backbone{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update port usage for all affected devices
|
|
for deviceID := range devicesToUpdate {
|
|
if err := u.updateDevicePortUsageInTx(tx, deviceID); err != nil {
|
|
return fmt.Errorf("failed to update device port usage for device %s: %w", deviceID, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (u *backboneUseCase) updateDevicePortUsageInTx(tx *gorm.DB, deviceID uuid.UUID) error {
|
|
// Get device with lock
|
|
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
|
|
|
|
switch device.DeviceType {
|
|
case "OTB":
|
|
// For OTB: count backbones (each backbone uses 1 port regardless of core amount)
|
|
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)
|
|
customerCount = portUsed // For ODP, customer count equals port_used
|
|
}
|
|
|
|
portAvailable := device.PortAmount - portUsed
|
|
if portAvailable < 0 {
|
|
portAvailable = 0
|
|
}
|
|
|
|
return tx.Model(&entity.DevicePort{}).
|
|
Where("device_id = ?", deviceID).
|
|
Updates(map[string]interface{}{
|
|
"port_used": portUsed,
|
|
"port_available": portAvailable,
|
|
"customer_count": customerCount,
|
|
"updated_at": gorm.Expr("NOW()"),
|
|
}).Error
|
|
}
|