diff --git a/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveService.cs b/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveService.cs index 16911b8..a5dc240 100644 --- a/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveService.cs +++ b/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveService.cs @@ -1,6 +1,7 @@ using System.Security.Cryptography; using System.Text.Json; using Microsoft.EntityFrameworkCore; +using Npgsql; using TakeoutSaaS.Domain.Products.Entities; using TakeoutSaaS.Domain.Products.Enums; using TakeoutSaaS.Infrastructure.App.Persistence; @@ -53,21 +54,30 @@ public sealed class ProductSkuSaveService(TakeoutAppDbContext dbContext) if (explicitSkuCodes.Count > 0) { - var codeConflict = await dbContext.ProductSkus - .AsNoTracking() - .Where(item => item.ProductId != productId && explicitSkuCodes.Contains(item.SkuCode)) - .Select(item => item.SkuCode) - .FirstOrDefaultAsync(cancellationToken); + string? codeConflict; + using (dbContext.DisableSoftDeleteFilter()) + { + codeConflict = await dbContext.ProductSkus + .AsNoTracking() + .Where(item => item.ProductId != productId && explicitSkuCodes.Contains(item.SkuCode)) + .Select(item => item.SkuCode) + .FirstOrDefaultAsync(cancellationToken); + } + if (!string.IsNullOrWhiteSpace(codeConflict)) { throw new BusinessException(ErrorCodes.BadRequest, $"SKU 编码已存在: {codeConflict}"); } } - var existingSkus = await dbContext.ProductSkus - .Where(item => item.ProductId == productId) - .OrderBy(item => item.Id) - .ToListAsync(cancellationToken); + List existingSkus; + using (dbContext.DisableSoftDeleteFilter()) + { + existingSkus = await dbContext.ProductSkus + .Where(item => item.ProductId == productId) + .OrderBy(item => item.Id) + .ToListAsync(cancellationToken); + } var existingBySkuCode = existingSkus .Where(item => !string.IsNullOrWhiteSpace(item.SkuCode)) @@ -156,7 +166,14 @@ public sealed class ProductSkuSaveService(TakeoutAppDbContext dbContext) await dbContext.ProductSkus.AddRangeAsync(createdSkus, cancellationToken); } - await dbContext.SaveChangesAsync(cancellationToken); + try + { + await dbContext.SaveChangesAsync(cancellationToken); + } + catch (DbUpdateException ex) when (IsSkuCodeUniqueViolation(ex)) + { + throw new BusinessException(ErrorCodes.BadRequest, "SKU 编码冲突,请刷新后重试"); + } } private async Task ValidateSkuTemplateRefsAsync( @@ -344,6 +361,20 @@ public sealed class ProductSkuSaveService(TakeoutAppDbContext dbContext) return new string(buffer[pos..]); } + private static bool IsSkuCodeUniqueViolation(DbUpdateException exception) + { + if (exception.InnerException is not PostgresException postgresException) + { + return false; + } + + return postgresException.SqlState == PostgresErrorCodes.UniqueViolation && + string.Equals( + postgresException.ConstraintName, + "IX_product_skus_TenantId_SkuCode", + StringComparison.Ordinal); + } + private sealed record SkuAttributePayload(long TemplateId, long OptionId); }