让换货选择不受新资产状态与原归属阻碍
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m13s

新资产仍需通过有效客户绑定和进行中换货占用校验,并返回明确的绑定冲突提示。

Constraint: 运营明确要求移除新资产在库和店铺一致限制

Rejected: 保留在库条件仅放宽店铺归属 | 无法支持已销售资产参与换货

Confidence: high

Scope-risk: moderate

Directive: 不得移除客户绑定和换货占用校验

Tested: git diff --check

Not-tested: 按用户要求未运行自动化测试
This commit is contained in:
2026-07-27 18:43:16 +08:00
parent 2f0ecffdf2
commit a8ca00e5f2

View File

@@ -801,14 +801,6 @@ func (s *Service) validateExchangeAssetsWithTx(ctx context.Context, tx *gorm.DB,
if !isExchangeableAssetStatus(oldAsset.AssetStatus) {
return oldAssetStatusError(oldAsset.AssetStatus)
}
if newAsset.AssetStatus != constants.AssetStatusInStock {
return errors.New(errors.CodeExchangeNewAssetNotInStock)
}
// 平台库存资产没有店铺归属,换货完成时会继承旧资产店铺;
// 已归属其他店铺的资产仍必须拒绝,避免跨店铺覆盖资产归属。
if newAsset.ShopID != nil && !sameShopID(oldAsset.ShopID, newAsset.ShopID) {
return errors.New(errors.CodeForbidden, "新旧资产归属不一致")
}
if err := s.ensureNewAssetBindingAvailableWithTx(ctx, tx, oldAsset, newAsset); err != nil {
return err
}
@@ -835,7 +827,7 @@ func (s *Service) ensureNewAssetBindingAvailableWithTx(ctx context.Context, tx *
return errors.Wrap(errors.CodeDatabaseError, err, "查询新资产客户绑定失败")
}
if newBindCount > 0 {
return errors.New(errors.CodeExchangeStatusInvalid, "新资产存在有效客户绑定")
return errors.New(errors.CodeExchangeStatusInvalid, "新资产存在有效客户绑定,不可用于换货")
}
return nil
}
@@ -862,7 +854,7 @@ func (s *Service) syncNewAssetOwnershipWithTx(ctx context.Context, tx *gorm.DB,
}
result := tx.WithContext(ctx).Model(modelValue).
Where("id = ? AND asset_status = ?", newAsset.AssetID, constants.AssetStatusInStock).
Where("id = ?", newAsset.AssetID).
Updates(map[string]any{
"shop_id": oldAsset.ShopID,
"status": ownershipStatus,
@@ -872,7 +864,7 @@ func (s *Service) syncNewAssetOwnershipWithTx(ctx context.Context, tx *gorm.DB,
return errors.Wrap(errors.CodeDatabaseError, result.Error, "同步新资产店铺归属失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeExchangeNewAssetNotInStock)
return errors.New(errors.CodeAssetNotFound)
}
shopIDTag := uint(0)
@@ -910,13 +902,13 @@ func (s *Service) updateAssetStatusesForCompletion(ctx context.Context, tx *gorm
return errors.New(errors.CodeExchangeStatusInvalid, "旧资产状态已被修改,请重试")
}
result = tx.WithContext(ctx).Model(&model.IotCard{}).
Where("id = ? AND asset_status = ?", newAsset.AssetID, constants.AssetStatusInStock).
Where("id = ?", newAsset.AssetID).
Updates(map[string]any{"asset_status": constants.AssetStatusSold, "updated_at": now})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新新卡状态失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeExchangeNewAssetNotInStock)
return errors.New(errors.CodeAssetNotFound)
}
return nil
}
@@ -930,13 +922,13 @@ func (s *Service) updateAssetStatusesForCompletion(ctx context.Context, tx *gorm
return errors.New(errors.CodeExchangeStatusInvalid, "旧资产状态已被修改,请重试")
}
result = tx.WithContext(ctx).Model(&model.Device{}).
Where("id = ? AND asset_status = ?", newAsset.AssetID, constants.AssetStatusInStock).
Where("id = ?", newAsset.AssetID).
Updates(map[string]any{"asset_status": constants.AssetStatusSold, "updated_at": now})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新新设备状态失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeExchangeNewAssetNotInStock)
return errors.New(errors.CodeAssetNotFound)
}
return nil
}
@@ -1007,13 +999,6 @@ func preferredDeviceIdentifier(device *model.Device) string {
return device.SN
}
func sameShopID(left, right *uint) bool {
if left == nil || right == nil {
return left == nil && right == nil
}
return *left == *right
}
func (s *Service) toExchangeOrderResponse(order *model.ExchangeOrder) *dto.ExchangeOrderResponse {
if order == nil {
return nil