From 5d6e23f1a52a3084097d328769007490220fd611 Mon Sep 17 00:00:00 2001 From: break Date: Fri, 24 Jul 2026 11:31:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=81=E8=AE=B8=E5=B9=B3=E5=8F=B0=E5=BA=93?= =?UTF-8?q?=E5=AD=98=E8=B5=84=E4=BA=A7=E7=9B=B4=E6=8E=A5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=8D=A2=E8=B4=A7=E5=BD=92=E5=B1=9E=E7=BB=A7=E6=89=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 换货完成事务允许平台库存新资产换入,并同步店铺归属、分销状态及钱包租户标签;其他店铺资产继续拒绝。 Constraint: 七月迭代要求归属继承不受 migrate_data 控制 Rejected: 先将新资产人工分配到旧资产店铺 | 会保留冲突校验并增加多余操作 Confidence: high Scope-risk: narrow Directive: 后续换货资料迁移不得覆盖归属继承规则 Tested: gofmt;git diff --check Not-tested: 按用户要求未运行自动化测试 --- internal/service/exchange/service.go | 60 +++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/internal/service/exchange/service.go b/internal/service/exchange/service.go index c567b5d..37ec5f0 100644 --- a/internal/service/exchange/service.go +++ b/internal/service/exchange/service.go @@ -583,6 +583,9 @@ func (s *Service) completeExchangeWithTx(ctx context.Context, tx *gorm.DB, order if err = s.validateExchangeAssetsWithTx(ctx, tx, order.ID, oldAsset, newAsset); err != nil { return err } + if err = s.syncNewAssetOwnershipWithTx(ctx, tx, oldAsset, newAsset); err != nil { + return err + } if err = s.switchCustomerBindingWithTx(ctx, tx, oldAsset, newAsset); err != nil { return err } @@ -755,7 +758,9 @@ func (s *Service) validateExchangeAssetsWithTx(ctx context.Context, tx *gorm.DB, if newAsset.AssetStatus != constants.AssetStatusInStock { return errors.New(errors.CodeExchangeNewAssetNotInStock) } - if !sameShopID(oldAsset.ShopID, newAsset.ShopID) { + // 平台库存资产没有店铺归属,换货完成时会继承旧资产店铺; + // 已归属其他店铺的资产仍必须拒绝,避免跨店铺覆盖资产归属。 + if newAsset.ShopID != nil && !sameShopID(oldAsset.ShopID, newAsset.ShopID) { return errors.New(errors.CodeForbidden, "新旧资产归属不一致") } if err := s.ensureNewAssetBindingAvailableWithTx(ctx, tx, oldAsset, newAsset); err != nil { @@ -793,6 +798,59 @@ func (s *Service) switchCustomerBindingWithTx(ctx context.Context, tx *gorm.DB, return s.customerBinding.Migrate(ctx, tx, oldAsset.AssetType, oldAsset.AssetID, newAsset.AssetType, newAsset.AssetID) } +// syncNewAssetOwnershipWithTx 将新资产归属同步为旧资产当前归属。 +// 归属继承不受 migrate_data 控制,避免平台库存资产换入店铺后仍处于平台租户范围。 +func (s *Service) syncNewAssetOwnershipWithTx(ctx context.Context, tx *gorm.DB, oldAsset, newAsset *resolvedExchangeAsset) error { + ownershipStatus := constants.IotCardStatusInStock + if oldAsset.ShopID != nil { + ownershipStatus = constants.IotCardStatusDistributed + } + + modelValue := any(&model.IotCard{}) + if newAsset.AssetType == constants.ExchangeAssetTypeDevice { + modelValue = &model.Device{} + ownershipStatus = constants.DeviceStatusInStock + if oldAsset.ShopID != nil { + ownershipStatus = constants.DeviceStatusDistributed + } + } + + result := tx.WithContext(ctx).Model(modelValue). + Where("id = ? AND asset_status = ?", newAsset.AssetID, constants.AssetStatusInStock). + Updates(map[string]any{ + "shop_id": oldAsset.ShopID, + "status": ownershipStatus, + "updated_at": time.Now(), + }) + if result.Error != nil { + return errors.Wrap(errors.CodeDatabaseError, result.Error, "同步新资产店铺归属失败") + } + if result.RowsAffected == 0 { + return errors.New(errors.CodeExchangeNewAssetNotInStock) + } + + shopIDTag := uint(0) + if oldAsset.ShopID != nil { + shopIDTag = *oldAsset.ShopID + } + if err := tx.WithContext(ctx).Model(&model.AssetWallet{}). + Where("resource_type = ? AND resource_id = ?", newAsset.AssetType, newAsset.AssetID). + Updates(map[string]any{"shop_id_tag": shopIDTag, "updated_at": time.Now()}).Error; err != nil { + return errors.Wrap(errors.CodeDatabaseError, err, "同步新资产钱包店铺标签失败") + } + + newAsset.ShopID = cloneShopID(oldAsset.ShopID) + return nil +} + +func cloneShopID(shopID *uint) *uint { + if shopID == nil { + return nil + } + value := *shopID + return &value +} + func (s *Service) updateAssetStatusesForCompletion(ctx context.Context, tx *gorm.DB, oldAsset, newAsset *resolvedExchangeAsset) error { now := time.Now() if oldAsset.AssetType == constants.ExchangeAssetTypeIotCard {