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"`
|
||||
BackboneID uuid.UUID `json:"bb_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"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ type FishboneResponse struct {
|
|||
DeviceStart string `json:"device_start"`
|
||||
DeviceEnd string `json:"device_end"`
|
||||
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"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
|
@ -23,7 +23,7 @@ type FishboneDetailResponse struct {
|
|||
ID uuid.UUID `json:"id"`
|
||||
FishboneCode string `json:"fishbone_code"`
|
||||
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"`
|
||||
CoreAmount int `json:"core_amount"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ type Fishbone struct {
|
|||
FishboneCode string `json:"fishbone_code" gorm:"unique"`
|
||||
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"`
|
||||
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"`
|
||||
CreatedAt time.Time `json:"created_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)
|
||||
}
|
||||
|
||||
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
|
||||
// Check port availability for start device
|
||||
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 && startDevice.DeviceType == "ODP" {
|
||||
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,
|
||||
|
|
@ -127,14 +105,34 @@ func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Update port usage for both devices
|
||||
// Update port usage for start device
|
||||
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 {
|
||||
// Only validate and update end device if provided (it is optional)
|
||||
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
|
||||
})
|
||||
|
|
@ -228,7 +226,9 @@ func (u *fishboneUseCase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishbo
|
|||
updates := make(map[string]interface{})
|
||||
devicesToUpdate := make(map[uuid.UUID]bool)
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
if fishbone.DeviceEndID != nil && *fishbone.DeviceEndID != originalFishbone.DeviceEndID {
|
||||
if fishbone.DeviceEndID != nil && (originalFishbone.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 {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func ConvertToFishboneResponses(fishbones []entity.Fishbone, totalFishbone map[u
|
|||
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
||||
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
||||
DeviceStartID: fishbone.DeviceStart.ID,
|
||||
DeviceEndID: fishbone.DeviceEnd.ID,
|
||||
DeviceEndID: fishbone.DeviceEndID,
|
||||
CoreAmount: fishbone.CoreAmount,
|
||||
CreatedAt: fishbone.CreatedAt,
|
||||
UpdatedAt: fishbone.UpdatedAt,
|
||||
|
|
@ -36,7 +36,7 @@ func ConvertToFishboneDetailResponse(fishbone entity.Fishbone) res.FishboneDetai
|
|||
ID: fishbone.ID,
|
||||
FishboneCode: fishbone.FishboneCode,
|
||||
DeviceStartID: fishbone.DeviceStart.ID,
|
||||
DeviceEndID: fishbone.DeviceEnd.ID,
|
||||
DeviceEndID: fishbone.DeviceEndID,
|
||||
BackboneCode: fishbone.Backbone.BackboneCode,
|
||||
CoreAmount: fishbone.CoreAmount,
|
||||
CreatedAt: fishbone.CreatedAt,
|
||||
|
|
@ -56,7 +56,7 @@ func ConvertToSimpleFishboneResponses(fishbones []entity.Fishbone) []res.Fishbon
|
|||
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
||||
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
||||
DeviceStartID: fishbone.DeviceStart.ID,
|
||||
DeviceEndID: fishbone.DeviceEnd.ID,
|
||||
DeviceEndID: fishbone.DeviceEndID,
|
||||
CoreAmount: fishbone.CoreAmount,
|
||||
CreatedAt: fishbone.CreatedAt,
|
||||
UpdatedAt: fishbone.UpdatedAt,
|
||||
|
|
|
|||
Loading…
Reference in New Issue