fix: make fishbone dev_end_id optional (nullable)
DeviceEndID was uuid.UUID (non-nullable) in entity, DTO, and response. Sending empty string from the frontend caused "invalid uuid" parse error. Changed to *uuid.UUID throughout. CreateFishbone now skips end device lookup, type validation, port check, and port-usage update when DeviceEndID is nil. UpdateFishbone pointer comparisons fixed accordingly. Run once on DB: ALTER TABLE fishbone ALTER COLUMN dev_end_id DROP NOT NULL; Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
128bc30680
commit
9ae9de471d
|
|
@ -6,7 +6,7 @@ type FishboneDTO struct {
|
||||||
FishboneCode string `json:"fishbone_code" validate:"required,min=3"`
|
FishboneCode string `json:"fishbone_code" validate:"required,min=3"`
|
||||||
BackboneID uuid.UUID `json:"bb_id" validate:"required"`
|
BackboneID uuid.UUID `json:"bb_id" validate:"required"`
|
||||||
DeviceStartID uuid.UUID `json:"dev_start_id" validate:"required"`
|
DeviceStartID uuid.UUID `json:"dev_start_id" validate:"required"`
|
||||||
DeviceEndID uuid.UUID `json:"dev_end_id" validate:"required"`
|
DeviceEndID *uuid.UUID `json:"dev_end_id" validate:"omitempty"`
|
||||||
CoreAmount int `json:"core_amount" validate:"required,min=1"`
|
CoreAmount int `json:"core_amount" validate:"required,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ type FishboneResponse struct {
|
||||||
DeviceStart string `json:"device_start"`
|
DeviceStart string `json:"device_start"`
|
||||||
DeviceEnd string `json:"device_end"`
|
DeviceEnd string `json:"device_end"`
|
||||||
DeviceStartID uuid.UUID `json:"device_start_id"`
|
DeviceStartID uuid.UUID `json:"device_start_id"`
|
||||||
DeviceEndID uuid.UUID `json:"device_end_id"`
|
DeviceEndID *uuid.UUID `json:"device_end_id"`
|
||||||
CoreAmount int `json:"core_amount"`
|
CoreAmount int `json:"core_amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
@ -23,7 +23,7 @@ type FishboneDetailResponse struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
FishboneCode string `json:"fishbone_code"`
|
FishboneCode string `json:"fishbone_code"`
|
||||||
DeviceStartID uuid.UUID `json:"device_start_id"`
|
DeviceStartID uuid.UUID `json:"device_start_id"`
|
||||||
DeviceEndID uuid.UUID `json:"device_end_id"`
|
DeviceEndID *uuid.UUID `json:"device_end_id"`
|
||||||
BackboneCode string `json:"backbone_code"`
|
BackboneCode string `json:"backbone_code"`
|
||||||
CoreAmount int `json:"core_amount"`
|
CoreAmount int `json:"core_amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ type Fishbone struct {
|
||||||
FishboneCode string `json:"fishbone_code" gorm:"unique"`
|
FishboneCode string `json:"fishbone_code" gorm:"unique"`
|
||||||
BackboneID uuid.UUID `json:"bb_id" gorm:"type:uuid;column:bb_id"`
|
BackboneID uuid.UUID `json:"bb_id" gorm:"type:uuid;column:bb_id"`
|
||||||
DeviceStartID uuid.UUID `json:"dev_start_id" gorm:"type:uuid;column:dev_start_id"`
|
DeviceStartID uuid.UUID `json:"dev_start_id" gorm:"type:uuid;column:dev_start_id"`
|
||||||
DeviceEndID uuid.UUID `json:"dev_end_id" gorm:"type:uuid;column:dev_end_id"`
|
DeviceEndID *uuid.UUID `json:"dev_end_id" gorm:"type:uuid;column:dev_end_id"`
|
||||||
CoreAmount int `json:"core_amount" gorm:"column:core_amount"`
|
CoreAmount int `json:"core_amount" gorm:"column:core_amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
|
||||||
|
|
@ -72,45 +72,23 @@ func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
|
||||||
return fmt.Errorf("start device not found: %w", err)
|
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
|
// Validate device types
|
||||||
if strings.ToLower(string(startDevice.DeviceType)) != "closure" {
|
if strings.ToLower(string(startDevice.DeviceType)) != "closure" {
|
||||||
return fmt.Errorf("start device must be of type closure, got %s", startDevice.DeviceType)
|
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
|
// Check port availability for start device
|
||||||
var startDevicePort entity.DevicePort
|
var startDevicePort entity.DevicePort
|
||||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||||
Where("device_id = ?", fishbone.DeviceStartID).First(&startDevicePort).Error; err != nil {
|
Where("device_id = ?", fishbone.DeviceStartID).First(&startDevicePort).Error; err != nil {
|
||||||
return fmt.Errorf("start device port record not found: %w", err)
|
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 && startDevice.DeviceType == "ODP" {
|
if startDevicePort.PortAvailable < 1 && startDevice.DeviceType == "ODP" {
|
||||||
return fmt.Errorf("start device has no available ports (available: %d, required: 1)",
|
return fmt.Errorf("start device has no available ports (available: %d, required: 1)",
|
||||||
startDevicePort.PortAvailable)
|
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{
|
newFishbone := entity.Fishbone{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
FishboneCode: fishbone.FishboneCode,
|
FishboneCode: fishbone.FishboneCode,
|
||||||
|
|
@ -127,13 +105,33 @@ func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update port usage for both devices
|
// Update port usage for start device
|
||||||
if err := u.updateDevicePortUsageInTx(tx, fishbone.DeviceStartID); err != nil {
|
if err := u.updateDevicePortUsageInTx(tx, fishbone.DeviceStartID); err != nil {
|
||||||
return fmt.Errorf("failed to update start device port usage: %w", err)
|
return fmt.Errorf("failed to update start device port usage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.updateDevicePortUsageInTx(tx, fishbone.DeviceEndID); err != nil {
|
// Only validate and update end device if provided (it is optional)
|
||||||
return fmt.Errorf("failed to update end device port usage: %w", err)
|
if fishbone.DeviceEndID != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if strings.ToUpper(string(endDevice.DeviceType)) != "ODP" {
|
||||||
|
return fmt.Errorf("end device must be of type ODP, got %s", endDevice.DeviceType)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if endDevicePort.PortAvailable < fishbone.CoreAmount {
|
||||||
|
return fmt.Errorf("end device has insufficient available ports (available: %d, required: %d)",
|
||||||
|
endDevicePort.PortAvailable, fishbone.CoreAmount)
|
||||||
|
}
|
||||||
|
if err := u.updateDevicePortUsageInTx(tx, *fishbone.DeviceEndID); err != nil {
|
||||||
|
return fmt.Errorf("failed to update end device port usage: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -228,7 +226,9 @@ func (u *fishboneUseCase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishbo
|
||||||
updates := make(map[string]interface{})
|
updates := make(map[string]interface{})
|
||||||
devicesToUpdate := make(map[uuid.UUID]bool)
|
devicesToUpdate := make(map[uuid.UUID]bool)
|
||||||
devicesToUpdate[originalFishbone.DeviceStartID] = true
|
devicesToUpdate[originalFishbone.DeviceStartID] = true
|
||||||
devicesToUpdate[originalFishbone.DeviceEndID] = true
|
if originalFishbone.DeviceEndID != nil {
|
||||||
|
devicesToUpdate[*originalFishbone.DeviceEndID] = true
|
||||||
|
}
|
||||||
|
|
||||||
// Validate device type changes if devices are being changed
|
// Validate device type changes if devices are being changed
|
||||||
if fishbone.DeviceStartID != nil && *fishbone.DeviceStartID != originalFishbone.DeviceStartID {
|
if fishbone.DeviceStartID != nil && *fishbone.DeviceStartID != originalFishbone.DeviceStartID {
|
||||||
|
|
@ -244,7 +244,7 @@ func (u *fishboneUseCase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishbo
|
||||||
devicesToUpdate[*fishbone.DeviceStartID] = true
|
devicesToUpdate[*fishbone.DeviceStartID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if fishbone.DeviceEndID != nil && *fishbone.DeviceEndID != originalFishbone.DeviceEndID {
|
if fishbone.DeviceEndID != nil && (originalFishbone.DeviceEndID == nil || *fishbone.DeviceEndID != *originalFishbone.DeviceEndID) {
|
||||||
var newEndDevice entity.Device
|
var newEndDevice entity.Device
|
||||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||||
Where("id = ?", *fishbone.DeviceEndID).First(&newEndDevice).Error; err != nil {
|
Where("id = ?", *fishbone.DeviceEndID).First(&newEndDevice).Error; err != nil {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func ConvertToFishboneResponses(fishbones []entity.Fishbone, totalFishbone map[u
|
||||||
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
||||||
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
||||||
DeviceStartID: fishbone.DeviceStart.ID,
|
DeviceStartID: fishbone.DeviceStart.ID,
|
||||||
DeviceEndID: fishbone.DeviceEnd.ID,
|
DeviceEndID: fishbone.DeviceEndID,
|
||||||
CoreAmount: fishbone.CoreAmount,
|
CoreAmount: fishbone.CoreAmount,
|
||||||
CreatedAt: fishbone.CreatedAt,
|
CreatedAt: fishbone.CreatedAt,
|
||||||
UpdatedAt: fishbone.UpdatedAt,
|
UpdatedAt: fishbone.UpdatedAt,
|
||||||
|
|
@ -36,7 +36,7 @@ func ConvertToFishboneDetailResponse(fishbone entity.Fishbone) res.FishboneDetai
|
||||||
ID: fishbone.ID,
|
ID: fishbone.ID,
|
||||||
FishboneCode: fishbone.FishboneCode,
|
FishboneCode: fishbone.FishboneCode,
|
||||||
DeviceStartID: fishbone.DeviceStart.ID,
|
DeviceStartID: fishbone.DeviceStart.ID,
|
||||||
DeviceEndID: fishbone.DeviceEnd.ID,
|
DeviceEndID: fishbone.DeviceEndID,
|
||||||
BackboneCode: fishbone.Backbone.BackboneCode,
|
BackboneCode: fishbone.Backbone.BackboneCode,
|
||||||
CoreAmount: fishbone.CoreAmount,
|
CoreAmount: fishbone.CoreAmount,
|
||||||
CreatedAt: fishbone.CreatedAt,
|
CreatedAt: fishbone.CreatedAt,
|
||||||
|
|
@ -56,7 +56,7 @@ func ConvertToSimpleFishboneResponses(fishbones []entity.Fishbone) []res.Fishbon
|
||||||
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
||||||
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
||||||
DeviceStartID: fishbone.DeviceStart.ID,
|
DeviceStartID: fishbone.DeviceStart.ID,
|
||||||
DeviceEndID: fishbone.DeviceEnd.ID,
|
DeviceEndID: fishbone.DeviceEndID,
|
||||||
CoreAmount: fishbone.CoreAmount,
|
CoreAmount: fishbone.CoreAmount,
|
||||||
CreatedAt: fishbone.CreatedAt,
|
CreatedAt: fishbone.CreatedAt,
|
||||||
UpdatedAt: fishbone.UpdatedAt,
|
UpdatedAt: fishbone.UpdatedAt,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue