NAM-APJATEL-BACKEND/usecase/fishbone_usecase.go

319 lines
11 KiB
Go

package usecase
import (
"errors"
"fmt"
"strings"
"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 FishboneUseCase interface {
CreateFishbone(fishbone req.FishboneDTO) error
GetAllFishbone() ([]res.FishboneResponse, error)
GetByID(id uuid.UUID) (res.FishboneDetailResponse, error)
UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error
DeleteFishbone(id uuid.UUID) error
GetFishboneStats() (map[string]interface{}, error)
GetByBackboneID(backboneID uuid.UUID) ([]res.FishboneResponse, error)
}
type fishboneUseCase struct {
fishboneRepo repository.FishboneRepo
backboneRepo repository.BackboneRepo
deviceDetailsRepo repository.DeviceDetailsRepo // Add this field
validate *validator.Validate
}
func NewFishboneUseCase(fishboneRepo repository.FishboneRepo, backboneRepo repository.BackboneRepo, deviceDetailsRepo repository.DeviceDetailsRepo) FishboneUseCase {
return &fishboneUseCase{
fishboneRepo: fishboneRepo,
backboneRepo: backboneRepo,
deviceDetailsRepo: deviceDetailsRepo, // Initialize the field
validate: validator.New(),
}
}
func (u *fishboneUseCase) GetByBackboneID(backboneID uuid.UUID) ([]res.FishboneResponse, error) {
fishbones, err := u.fishboneRepo.GetByBackboneID(backboneID)
if err != nil {
return nil, err
}
fishboneResp, err := helper.ConvertToFishboneResponses(fishbones, nil)
if err != nil {
return nil, err
}
return fishboneResp, nil
}
func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
err := u.validate.Struct(fishbone)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
return u.fishboneRepo.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 = ?", fishbone.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 = ?", fishbone.DeviceEndID).First(&endDevice).Error; err != nil {
return fmt.Errorf("end device not found: %w", err)
}
// Validate device types
if strings.ToLower(string(startDevice.DeviceType)) != "closure" {
return fmt.Errorf("start device must be of type closure, got %s", startDevice.DeviceType)
}
if strings.ToUpper(string(endDevice.DeviceType)) != "ODP" {
return fmt.Errorf("end device must be of type ODP, 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 = ?", fishbone.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 = ?", fishbone.DeviceEndID).First(&endDevicePort).Error; err != nil {
return fmt.Errorf("end device port record not found: %w", err)
}
// Validate port availability
if startDevicePort.PortAvailable < 1 {
return fmt.Errorf("start device has no available ports (available: %d, required: 1)",
startDevicePort.PortAvailable)
}
if endDevicePort.PortAvailable < fishbone.CoreAmount {
return fmt.Errorf("end device has insufficient available ports (available: %d, required: %d)",
endDevicePort.PortAvailable, fishbone.CoreAmount)
}
newFishbone := entity.Fishbone{
ID: uuid.New(),
FishboneCode: fishbone.FishboneCode,
BackboneID: fishbone.BackboneID,
DeviceStartID: fishbone.DeviceStartID,
DeviceEndID: fishbone.DeviceEndID,
CoreAmount: fishbone.CoreAmount,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Create fishbone
if err := tx.Create(&newFishbone).Error; err != nil {
return err
}
// Update port usage for both devices
if err := u.updateDevicePortUsageInTx(tx, fishbone.DeviceStartID); err != nil {
return fmt.Errorf("failed to update start device port usage: %w", err)
}
if err := u.updateDevicePortUsageInTx(tx, fishbone.DeviceEndID); err != nil {
return fmt.Errorf("failed to update end device port usage: %w", err)
}
return nil
})
}
func (u *fishboneUseCase) 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 "closure":
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
case "ODP":
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
}
func (u *fishboneUseCase) GetAllFishbone() ([]res.FishboneResponse, error) {
fishbones, err := u.fishboneRepo.GetAll()
if err != nil {
return nil, err
}
return helper.ConvertToSimpleFishboneResponses(fishbones), nil
}
func (u *fishboneUseCase) GetByID(id uuid.UUID) (res.FishboneDetailResponse, error) {
fishbone, err := u.fishboneRepo.GetByIDWithRelations(id)
if err != nil {
return res.FishboneDetailResponse{}, err
}
// Convert to detailed response using helper
return helper.ConvertToFishboneDetailResponse(fishbone), nil
}
func (u *fishboneUseCase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error {
err := u.validate.Struct(fishbone)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
return u.fishboneRepo.WithTransaction(func(tx *gorm.DB) error {
// Get original fishbone for comparison with lock
var originalFishbone entity.Fishbone
if err := tx.Set("gorm:query_option", "FOR UPDATE").
Where("id = ?", id).First(&originalFishbone).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return fmt.Errorf("fishbone not found")
}
return err
}
updates := make(map[string]interface{})
devicesToUpdate := make(map[uuid.UUID]bool)
devicesToUpdate[originalFishbone.DeviceStartID] = true
devicesToUpdate[originalFishbone.DeviceEndID] = true
// Validate device type changes if devices are being changed
if fishbone.DeviceStartID != nil && *fishbone.DeviceStartID != originalFishbone.DeviceStartID {
var newStartDevice entity.Device
if err := tx.Set("gorm:query_option", "FOR UPDATE").
Where("id = ?", *fishbone.DeviceStartID).First(&newStartDevice).Error; err != nil {
return fmt.Errorf("new start device not found: %w", err)
}
if newStartDevice.DeviceType != "closure" {
return fmt.Errorf("new start device must be of type closure, got %s", newStartDevice.DeviceType)
}
updates["dev_start_id"] = *fishbone.DeviceStartID
devicesToUpdate[*fishbone.DeviceStartID] = true
}
if fishbone.DeviceEndID != nil && *fishbone.DeviceEndID != originalFishbone.DeviceEndID {
var newEndDevice entity.Device
if err := tx.Set("gorm:query_option", "FOR UPDATE").
Where("id = ?", *fishbone.DeviceEndID).First(&newEndDevice).Error; err != nil {
return fmt.Errorf("new end device not found: %w", err)
}
if newEndDevice.DeviceType != "ODP" {
return fmt.Errorf("new end device must be of type ODP, got %s", newEndDevice.DeviceType)
}
updates["dev_end_id"] = *fishbone.DeviceEndID
devicesToUpdate[*fishbone.DeviceEndID] = true
}
// Handle core amount changes
if fishbone.CoreAmount != nil && *fishbone.CoreAmount != originalFishbone.CoreAmount {
updates["core_amount"] = *fishbone.CoreAmount
}
if fishbone.BackboneID != nil {
// Validate backbone exists
var backbone entity.Backbone
if err := tx.Where("id = ?", *fishbone.BackboneID).First(&backbone).Error; err != nil {
return fmt.Errorf("backbone not found: %w", err)
}
updates["bb_id"] = *fishbone.BackboneID
}
if fishbone.FishboneCode != nil {
updates["fishbone_code"] = *fishbone.FishboneCode
}
if len(updates) == 0 {
return fmt.Errorf("no fields to update")
}
updates["updated_at"] = time.Now()
// Update fishbone
if err := tx.Model(&entity.Fishbone{}).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 *fishboneUseCase) DeleteFishbone(id uuid.UUID) error {
// Check if fishbone exists
exists, err := u.fishboneRepo.CheckFishboneExists(id)
if err != nil {
return err
}
if !exists {
return errors.New("fishbone not found")
}
return u.fishboneRepo.Delete(id)
}
func (u *fishboneUseCase) GetFishboneStats() (map[string]interface{}, error) {
fishbones, err := u.fishboneRepo.GetAll()
if err != nil {
return nil, err
}
return helper.GetFishboneStats(fishbones), nil
}