From c2a6cf7b1ecc6ae3ae11f7d1c3ef6daf3b610008 Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Tue, 24 Feb 2026 16:51:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8D=E5=95=86?= =?UTF-8?q?=E5=93=81=E4=BF=9D=E5=AD=98=E4=BA=8B=E5=8A=A1=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E5=B9=B6=E5=85=BC=E5=AE=B9kind=E7=BC=BA?= =?UTF-8?q?=E7=9C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contracts/Product/ProductContracts.cs | 2 +- .../Controllers/ProductController.cs | 36 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Product/ProductContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Product/ProductContracts.cs index 647d910..a9d9684 100644 --- a/src/Api/TakeoutSaaS.TenantApi/Contracts/Product/ProductContracts.cs +++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Product/ProductContracts.cs @@ -80,7 +80,7 @@ public sealed class SaveProductRequest /// /// 商品类型(single/combo)。 /// - public string Kind { get; set; } = "single"; + public string? Kind { get; set; } /// /// 商品名称。 diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/ProductController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/ProductController.cs index ddfdc14..260db9a 100644 --- a/src/Api/TakeoutSaaS.TenantApi/Controllers/ProductController.cs +++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/ProductController.cs @@ -126,12 +126,10 @@ public sealed class ProductController( await EnsureStoreAccessibleAsync(storeId, cancellationToken); var categoryId = StoreApiHelpers.ParseRequiredSnowflake(request.CategoryId, nameof(request.CategoryId)); - var kind = ParseKind(request.Kind); var productId = StoreApiHelpers.ParseSnowflakeOrNull(request.Id); var timedOnShelfAt = ParseTimedOnShelfAt(request.ShelfMode, request.TimedOnShelfAt); var normalizedStatus = ResolveStatusByShelfMode(request.ShelfMode, request.Status); var imageUrls = NormalizeImageUrls(request.ImageUrls); - var comboGroups = NormalizeComboGroups(request.ComboGroups, kind); ProductDto? existing = null; if (productId.HasValue) @@ -147,6 +145,9 @@ public sealed class ProductController( } } + var kind = ResolveKindForSave(request.Kind, existing); + var comboGroups = NormalizeComboGroups(request.ComboGroups, kind); + var shouldReplaceSpecAddon = existing is null || request.SpecTemplateIds is not null || request.AddonGroupIds is not null; var shouldReplaceLabels = existing is null || request.LabelIds is not null; var shouldReplaceSkus = existing is null || request.Skus is not null; @@ -187,9 +188,16 @@ public sealed class ProductController( int? remainStock = soldoutMode.HasValue ? Math.Max(0, existing?.RemainStock ?? request.Stock) : null; var soldoutReason = soldoutMode.HasValue ? existing?.SoldoutReason : null; - ProductDto? saved; - await using (var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken)) + ProductDto? saved = null; + var notFoundInTransaction = false; + var executionStrategy = dbContext.Database.CreateExecutionStrategy(); + await executionStrategy.ExecuteAsync(async () => { + saved = null; + notFoundInTransaction = false; + dbContext.ChangeTracker.Clear(); + + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); if (existing is null) { saved = await mediator.Send(new CreateProductCommand @@ -261,7 +269,8 @@ public sealed class ProductController( if (saved is null) { - return ApiResponse.Error(ErrorCodes.NotFound, "商品不存在"); + notFoundInTransaction = true; + return; } if (shouldReplaceSpecAddon) @@ -281,9 +290,14 @@ public sealed class ProductController( await ReplaceComboGroupsAsync(saved.Id, storeId, kind, comboGroups, cancellationToken); await transaction.CommitAsync(cancellationToken); + }); + + if (notFoundInTransaction || saved is null) + { + return ApiResponse.Error(ErrorCodes.NotFound, "商品不存在"); } - var savedProduct = saved!; + var savedProduct = saved; var detailComboGroups = await BuildComboGroupResponsesAsync(savedProduct.Id, storeId, cancellationToken); var categoryNameLookup = await BuildCategoryNameLookupAsync(storeId, cancellationToken); var savedRelationState = await LoadProductRelationStateAsync(savedProduct.Id, storeId, cancellationToken); @@ -727,6 +741,16 @@ public sealed class ProductController( }; } + private static ProductKind ResolveKindForSave(string? kind, ProductDto? existing) + { + if (string.IsNullOrWhiteSpace(kind)) + { + return existing?.Kind ?? ProductKind.Single; + } + + return ParseKind(kind); + } + private static ProductKind? ParseKindOrNull(string? kind) { if (string.IsNullOrWhiteSpace(kind))