diff --git a/src/Oms.Application/Services/Ai/AiSkillService.cs b/src/Oms.Application/Services/Ai/AiSkillService.cs
index 775d83f..ce85056 100644
--- a/src/Oms.Application/Services/Ai/AiSkillService.cs
+++ b/src/Oms.Application/Services/Ai/AiSkillService.cs
@@ -93,6 +93,7 @@ public class AiSkillService : ApplicationService
}
entity ??= await AiSkillRepository.GetBySkillKeyAsync(skillKey);
+ entity ??= await AiSkillRepository.GetBySkillKeyIncludingDeletedAsync(skillKey);
if (entity == null)
{
@@ -106,9 +107,17 @@ public class AiSkillService : ApplicationService
throw new BusinessException($"SkillKey 已存在:{skillKey}");
}
+ var softDeleted = await AiSkillRepository.GetBySkillKeyIncludingDeletedAsync(skillKey);
+ if (softDeleted is { IsDeleted: true } && softDeleted.Id != entity.Id)
+ {
+ softDeleted.SkillKey = $"{skillKey}__deleted_{softDeleted.Id}";
+ await AiSkillRepository.UpdateAsync(softDeleted);
+ }
+
entity.SkillKey = skillKey;
}
+ entity.IsDeleted = false;
entity.Scene = scene;
entity.Name = input.Name.Trim();
entity.Description = NormalizeOptional(input.Description);
diff --git a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
index 5cf568d..5c3203d 100644
--- a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
@@ -23,6 +23,21 @@ public partial class SalesProductService
}
}
+ ///
+ /// 更新时若目标编码被软删记录占用,改名释放 uk_sp_code。
+ ///
+ private async Task FreeSoftDeletedProductCodeIfNeededAsync(string productCode, int currentId)
+ {
+ var conflict = await SalesProductRepository.GetByProductCodeIncludingDeletedAsync(productCode);
+ if (conflict == null || !conflict.IsDeleted || conflict.Id == currentId)
+ {
+ return;
+ }
+
+ conflict.ProductCode = $"{productCode}__deleted_{conflict.Id}";
+ await SalesProductRepository.UpdateAsync(conflict);
+ }
+
///
/// 获取销售产品,不存在时直接抛异常。
///
diff --git a/src/Oms.Application/Services/Channels/SalesProductService.cs b/src/Oms.Application/Services/Channels/SalesProductService.cs
index 29ddaf2..9ebf67f 100644
--- a/src/Oms.Application/Services/Channels/SalesProductService.cs
+++ b/src/Oms.Application/Services/Channels/SalesProductService.cs
@@ -75,6 +75,16 @@ public partial class SalesProductService : ApplicationService
public async Task CreateAsync(SalesProductSaveInput input)
{
await ValidateProductCodeAsync(input.ProductCode, null);
+ var code = input.ProductCode.Trim();
+ var deleted = await SalesProductRepository.GetByProductCodeIncludingDeletedAsync(code);
+ if (deleted != null && deleted.IsDeleted)
+ {
+ deleted.IsDeleted = false;
+ ApplyProductInput(deleted, input);
+ await SalesProductRepository.UpdateAsync(deleted);
+ return;
+ }
+
var entity = new SalesProduct();
ApplyProductInput(entity, input);
await SalesProductRepository.InsertAsync(entity);
@@ -90,6 +100,7 @@ public partial class SalesProductService : ApplicationService
{
var entity = await GetProductOrThrowAsync(input.Id);
await ValidateProductCodeAsync(input.ProductCode, entity.Id);
+ await FreeSoftDeletedProductCodeIfNeededAsync(input.ProductCode.Trim(), entity.Id);
ApplyProductInput(entity, input);
await SalesProductRepository.UpdateAsync(entity);
}
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
index 0f8c182..eb303e8 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
@@ -91,6 +91,21 @@ public partial class ServiceChannelService
}
}
+ ///
+ /// 更新时若目标编码被软删记录占用,改名释放 uk_sc_code。
+ ///
+ private async Task FreeSoftDeletedChannelCodeIfNeededAsync(string channelCode, int currentId)
+ {
+ var conflict = await ServiceChannelRepository.GetByChannelCodeIncludingDeletedAsync(channelCode);
+ if (conflict == null || !conflict.IsDeleted || conflict.Id == currentId)
+ {
+ return;
+ }
+
+ conflict.ChannelCode = $"{channelCode}__deleted_{conflict.Id}";
+ await ServiceChannelRepository.UpdateAsync(conflict);
+ }
+
///
/// 获取服务渠道,不存在时直接抛异常。
///
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
index 137cd20..061ab28 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
@@ -73,6 +73,16 @@ public partial class ServiceChannelService : ApplicationService
public async Task CreateAsync(ServiceChannelSaveInput input)
{
await ValidateChannelCodeAsync(input.ChannelCode, null);
+ var code = input.ChannelCode.Trim();
+ var deleted = await ServiceChannelRepository.GetByChannelCodeIncludingDeletedAsync(code);
+ if (deleted != null && deleted.IsDeleted)
+ {
+ deleted.IsDeleted = false;
+ ApplyInput(deleted, input);
+ await ServiceChannelRepository.UpdateAsync(deleted);
+ return;
+ }
+
var entity = new ServiceChannel();
ApplyInput(entity, input);
await ServiceChannelRepository.InsertAsync(entity);
@@ -88,6 +98,7 @@ public partial class ServiceChannelService : ApplicationService
{
var entity = await GetEntityOrThrowAsync(input.Id);
await ValidateChannelCodeAsync(input.ChannelCode, entity.Id);
+ await FreeSoftDeletedChannelCodeIfNeededAsync(input.ChannelCode.Trim(), entity.Id);
ApplyInput(entity, input);
await ServiceChannelRepository.UpdateAsync(entity);
}
diff --git a/src/Oms.Application/Services/Customers/CustomerService.cs b/src/Oms.Application/Services/Customers/CustomerService.cs
index 21550cd..8e6bed7 100644
--- a/src/Oms.Application/Services/Customers/CustomerService.cs
+++ b/src/Oms.Application/Services/Customers/CustomerService.cs
@@ -178,6 +178,12 @@ public class CustomerService : ApplicationService
NormalizeRequiredText(input.Email, "邮箱"),
WeChatRegistrationFieldNormalizer.EmailMaxLength);
+ await FreeSoftDeletedCustomerIdentityIfNeededAsync(
+ customer.CustomerCode,
+ phone,
+ email,
+ excludeId: customer.Id);
+
var duplicatedPhone = await CustomerRepository.GetByPhoneAsync(phone, cancellationToken);
if (duplicatedPhone != null && duplicatedPhone.Id != customer.Id)
{
@@ -216,6 +222,10 @@ public class CustomerService : ApplicationService
{
var normalized = await NormalizeCreateCustomerInputAsync(input, "客户编号", "客户名称");
+ await FreeSoftDeletedCustomerIdentityIfNeededAsync(
+ normalized.CustomerCode,
+ normalized.Phone,
+ normalized.Email);
await EnsureCustomerIdentityUniqueAsync(normalized.CustomerCode, normalized.Phone, normalized.Email);
var customerRoleId = await GetCustomerRoleIdOrThrowAsync();
@@ -268,6 +278,10 @@ public class CustomerService : ApplicationService
var normalized = await NormalizeCreateCustomerInputAsync(input, "子客户编号", "子客户名称");
var permissions = await NormalizeAndValidateSubCustomerPermissionsAsync(input.Permissions);
+ await FreeSoftDeletedCustomerIdentityIfNeededAsync(
+ normalized.CustomerCode,
+ normalized.Phone,
+ normalized.Email);
await EnsureCustomerIdentityUniqueAsync(normalized.CustomerCode, normalized.Phone, normalized.Email);
var user = await CreateRbacUserAsync(input, normalized);
@@ -550,8 +564,46 @@ public class CustomerService : ApplicationService
}
}
+ ///
+ /// 客户创建会新建 RBAC 用户,不适合直接恢复软删行;释放软删占用的唯一键即可。
+ ///
+ private async Task FreeSoftDeletedCustomerIdentityIfNeededAsync(
+ string customerCode,
+ string phone,
+ string? email,
+ int? excludeId = null)
+ {
+ var byCode = await CustomerRepository.GetByCustomerCodeIncludingDeletedAsync(customerCode);
+ if (byCode != null && byCode.IsDeleted && byCode.Id != excludeId)
+ {
+ byCode.CustomerCode = $"{customerCode}__deleted_{byCode.Id}";
+ await CustomerRepository.UpdateAsync(byCode);
+ }
+
+ var byPhone = await CustomerRepository.GetByPhoneIncludingDeletedAsync(phone);
+ if (byPhone != null && byPhone.IsDeleted && byPhone.Id != excludeId)
+ {
+ byPhone.Phone = $"{phone}__deleted_{byPhone.Id}";
+ await CustomerRepository.UpdateAsync(byPhone);
+ }
+
+ if (string.IsNullOrWhiteSpace(email))
+ {
+ return;
+ }
+
+ var byEmail = await CustomerRepository.GetByEmailIncludingDeletedAsync(email);
+ if (byEmail != null && byEmail.IsDeleted && byEmail.Id != excludeId)
+ {
+ byEmail.Email = $"{email}__deleted_{byEmail.Id}";
+ await CustomerRepository.UpdateAsync(byEmail);
+ }
+ }
+
private async Task EnsureCustomerIdentityUniqueForUpdateAsync(int id, string customerCode, string phone, string? email)
{
+ await FreeSoftDeletedCustomerIdentityIfNeededAsync(customerCode, phone, email, excludeId: id);
+
var duplicatedCode = await CustomerRepository.GetByCustomerCodeAsync(customerCode);
if (duplicatedCode != null && duplicatedCode.Id != id)
{
diff --git a/src/Oms.Application/Services/Kb/KbCategoryService.cs b/src/Oms.Application/Services/Kb/KbCategoryService.cs
index a42186e..492845c 100644
--- a/src/Oms.Application/Services/Kb/KbCategoryService.cs
+++ b/src/Oms.Application/Services/Kb/KbCategoryService.cs
@@ -64,7 +64,22 @@ public class KbCategoryService : ApplicationService
throw new BusinessException($"分类编码已存在:{code}");
}
- entity ??= new KbCategory();
+ if (entity == null)
+ {
+ var deleted = await CategoryRepository.GetByCodeIncludingDeletedAsync(code);
+ entity = deleted is { IsDeleted: true } ? deleted : new KbCategory();
+ }
+ else
+ {
+ var softDeleted = await CategoryRepository.GetByCodeIncludingDeletedAsync(code);
+ if (softDeleted is { IsDeleted: true } && softDeleted.Id != entity.Id)
+ {
+ softDeleted.Code = $"{code}__deleted_{softDeleted.Id}";
+ await CategoryRepository.UpdateAsync(softDeleted);
+ }
+ }
+
+ entity.IsDeleted = false;
entity.Code = code;
entity.Name = name;
entity.SortOrder = input.SortOrder;
diff --git a/src/Oms.Application/Services/System/DictService.cs b/src/Oms.Application/Services/System/DictService.cs
index 2bd2737..7329353 100644
--- a/src/Oms.Application/Services/System/DictService.cs
+++ b/src/Oms.Application/Services/System/DictService.cs
@@ -132,14 +132,10 @@ public class DictService : ApplicationService
}
entity ??= await DictTypeRepository.GetByDictTypeAsync(dictType);
+ // 软删后列表查不到,但 uk_dict_type_code 仍占坑;重建时恢复原行
+ entity ??= await DictTypeRepository.GetByDictTypeIncludingDeletedAsync(dictType);
if (entity == null)
{
- var existedType = await DictTypeRepository.GetByDictTypeAsync(dictType);
- if (existedType != null)
- {
- throw new BusinessException("字典类型已存在");
- }
-
await DictTypeRepository.InsertAsync(new DictTypeMeta
{
DictType = dictType,
@@ -151,13 +147,19 @@ public class DictService : ApplicationService
if (!string.Equals(entity.DictType, dictType, StringComparison.OrdinalIgnoreCase))
{
- var duplicate = await DictTypeRepository.GetByDictTypeAsync(dictType);
- if (duplicate != null && duplicate.Id != entity.Id)
+ var duplicate = await DictTypeRepository.GetByDictTypeIncludingDeletedAsync(dictType);
+ if (duplicate != null && duplicate.Id != entity.Id && !duplicate.IsDeleted)
{
throw new BusinessException("字典类型已存在");
}
+
+ if (duplicate != null && duplicate.Id != entity.Id && duplicate.IsDeleted)
+ {
+ throw new BusinessException("字典类型编码与已删除记录冲突,请换编码或联系管理员恢复");
+ }
}
+ entity.IsDeleted = false;
entity.DictType = dictType;
entity.Remark = remark;
entity.TypeSort = input.TypeSort;
@@ -317,6 +319,14 @@ public class DictService : ApplicationService
return;
}
+ var deleted = await DictTypeRepository.GetByDictTypeIncludingDeletedAsync(dictType);
+ if (deleted != null)
+ {
+ deleted.IsDeleted = false;
+ await DictTypeRepository.UpdateAsync(deleted);
+ return;
+ }
+
await DictTypeRepository.InsertAsync(new DictTypeMeta
{
DictType = dictType,
diff --git a/src/Oms.Application/Services/System/SystemOtherConfigService.cs b/src/Oms.Application/Services/System/SystemOtherConfigService.cs
index b213f87..7c52138 100644
--- a/src/Oms.Application/Services/System/SystemOtherConfigService.cs
+++ b/src/Oms.Application/Services/System/SystemOtherConfigService.cs
@@ -61,9 +61,26 @@ public class SystemOtherConfigService : ApplicationService
public async Task CreateAsync(SystemOtherConfigCreateInput input)
{
await ValidateInputAsync(input, null);
+ var configCode = input.ConfigCode.Trim();
+ var deleted = await SystemOtherConfigRepository.GetByConfigCodeIncludingDeletedAsync(configCode);
+ if (deleted != null && deleted.IsDeleted)
+ {
+ deleted.IsDeleted = false;
+ deleted.ConfigCode = configCode;
+ deleted.ConfigName = input.ConfigName.Trim();
+ deleted.Enabled = input.Enabled;
+ deleted.BaseUrl = NormalizeOptional(input.BaseUrl);
+ deleted.AppKey = NormalizeOptional(input.AppKey);
+ deleted.AppSecret = NormalizeOptional(input.AppSecret);
+ deleted.TimeoutSeconds = NormalizeTimeoutSeconds(input.TimeoutSeconds);
+ deleted.Remark = NormalizeOptional(input.Remark);
+ await SystemOtherConfigRepository.UpdateAsync(deleted);
+ return;
+ }
+
await SystemOtherConfigRepository.InsertAsync(new SystemOtherConfig
{
- ConfigCode = input.ConfigCode.Trim(),
+ ConfigCode = configCode,
ConfigName = input.ConfigName.Trim(),
Enabled = input.Enabled,
BaseUrl = NormalizeOptional(input.BaseUrl),
@@ -82,8 +99,16 @@ public class SystemOtherConfigService : ApplicationService
{
var entity = await GetConfigOrThrowAsync(input.Id);
await ValidateInputAsync(input, entity.Id);
+ var configCode = input.ConfigCode.Trim();
+ var softDeleted = await SystemOtherConfigRepository.GetByConfigCodeIncludingDeletedAsync(configCode);
+ if (softDeleted != null && softDeleted.IsDeleted && softDeleted.Id != entity.Id)
+ {
+ softDeleted.ConfigCode = $"{configCode}__deleted_{softDeleted.Id}";
+ await SystemOtherConfigRepository.UpdateAsync(softDeleted);
+ }
+
var clearWeChatAccessToken = ShouldClearWeChatAccessToken(entity, input);
- entity.ConfigCode = input.ConfigCode.Trim();
+ entity.ConfigCode = configCode;
entity.ConfigName = input.ConfigName.Trim();
entity.Enabled = input.Enabled;
entity.BaseUrl = NormalizeOptional(input.BaseUrl);
diff --git a/src/Oms.Application/Services/System/WarehouseService.cs b/src/Oms.Application/Services/System/WarehouseService.cs
index 2e27f6c..8532769 100644
--- a/src/Oms.Application/Services/System/WarehouseService.cs
+++ b/src/Oms.Application/Services/System/WarehouseService.cs
@@ -81,15 +81,29 @@ public class WarehouseService : ApplicationService
[HttpPost("/system/warehouse/create")]
public async Task CreateAsync(CreateWarehouseInput input)
{
- var duplicatedWarehouse = await WarehouseAddressRepository.GetByWarehouseNameAsync(input.WarehouseName);
+ var warehouseName = input.WarehouseName.Trim();
+ var duplicatedWarehouse = await WarehouseAddressRepository.GetByWarehouseNameAsync(warehouseName);
if (duplicatedWarehouse != null)
{
throw new BusinessException("仓库名称已存在");
}
+ var deleted = await WarehouseAddressRepository.GetByWarehouseNameIncludingDeletedAsync(warehouseName);
+ if (deleted != null && deleted.IsDeleted)
+ {
+ deleted.IsDeleted = false;
+ deleted.WarehouseName = warehouseName;
+ deleted.WarehouseType = input.WarehouseType;
+ deleted.Recipient = input.Recipient;
+ deleted.ContactPhone = input.ContactPhone;
+ deleted.ShippingAddress = input.ShippingAddress;
+ await WarehouseAddressRepository.UpdateAsync(deleted);
+ return;
+ }
+
var entity = new WarehouseAddress
{
- WarehouseName = input.WarehouseName,
+ WarehouseName = warehouseName,
WarehouseType = input.WarehouseType,
Recipient = input.Recipient,
ContactPhone = input.ContactPhone,
@@ -108,13 +122,21 @@ public class WarehouseService : ApplicationService
public async Task UpdateAsync(UpdateWarehouseInput input)
{
var entity = await GetWarehouseOrThrowAsync(input.Id);
- var duplicatedWarehouse = await WarehouseAddressRepository.GetByWarehouseNameAsync(input.WarehouseName);
+ var warehouseName = input.WarehouseName.Trim();
+ var duplicatedWarehouse = await WarehouseAddressRepository.GetByWarehouseNameAsync(warehouseName);
if (duplicatedWarehouse != null && duplicatedWarehouse.Id != entity.Id)
{
throw new BusinessException("仓库名称已存在");
}
- entity.WarehouseName = input.WarehouseName;
+ var softDeleted = await WarehouseAddressRepository.GetByWarehouseNameIncludingDeletedAsync(warehouseName);
+ if (softDeleted != null && softDeleted.IsDeleted && softDeleted.Id != entity.Id)
+ {
+ softDeleted.WarehouseName = $"{warehouseName}__deleted_{softDeleted.Id}";
+ await WarehouseAddressRepository.UpdateAsync(softDeleted);
+ }
+
+ entity.WarehouseName = warehouseName;
entity.WarehouseType = input.WarehouseType;
entity.Recipient = input.Recipient;
entity.ContactPhone = input.ContactPhone;
diff --git a/src/Oms.Application/Services/Warehouse/WarehouseService.BinLocation.cs b/src/Oms.Application/Services/Warehouse/WarehouseService.BinLocation.cs
index 0c1327e..3fe21b9 100644
--- a/src/Oms.Application/Services/Warehouse/WarehouseService.BinLocation.cs
+++ b/src/Oms.Application/Services/Warehouse/WarehouseService.BinLocation.cs
@@ -63,7 +63,12 @@ public partial class WarehouseService
}
else
{
- entity = new CBinLocation { WarehouseId = input.WarehouseId };
+ var deleted = await BinLocationRepository.GetByWarehouseAndBinCodeIncludingDeletedAsync(
+ input.WarehouseId,
+ binCode);
+ entity = deleted is { IsDeleted: true }
+ ? deleted
+ : new CBinLocation { WarehouseId = input.WarehouseId };
}
var duplicate = await BinLocationRepository.FirstOrDefaultAsync(x =>
@@ -73,13 +78,26 @@ public partial class WarehouseService
throw new BusinessException("库位编码已存在");
}
+ if (input.Id > 0)
+ {
+ var softDeleted = await BinLocationRepository.GetByWarehouseAndBinCodeIncludingDeletedAsync(
+ input.WarehouseId,
+ binCode);
+ if (softDeleted is { IsDeleted: true } && softDeleted.Id != entity.Id)
+ {
+ softDeleted.BinCode = $"{binCode}__deleted_{softDeleted.Id}";
+ await BinLocationRepository.UpdateAsync(softDeleted);
+ }
+ }
+
+ entity.IsDeleted = false;
entity.BinCode = binCode;
entity.BinName = string.IsNullOrWhiteSpace(input.BinName) ? null : input.BinName.Trim();
entity.ZoneCode = string.IsNullOrWhiteSpace(input.ZoneCode) ? null : input.ZoneCode.Trim();
entity.IsEnabled = input.IsEnabled;
entity.Remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
- if (input.Id > 0)
+ if (entity.Id > 0)
{
await BinLocationRepository.UpdateAsync(entity);
}
diff --git a/src/Oms.Application/Services/Warehouse/WarehouseService.Sku.cs b/src/Oms.Application/Services/Warehouse/WarehouseService.Sku.cs
index 646e35f..c0b0eaa 100644
--- a/src/Oms.Application/Services/Warehouse/WarehouseService.Sku.cs
+++ b/src/Oms.Application/Services/Warehouse/WarehouseService.Sku.cs
@@ -105,7 +105,10 @@ public partial class WarehouseService
}
else
{
- entity = new Domain.WarehouseAggregate.Entities.CSku { CustomerId = customerId };
+ var deleted = await SkuRepository.GetByCustomerAndSkuCodeIncludingDeletedAsync(customerId, skuCode);
+ entity = deleted is { IsDeleted: true }
+ ? deleted
+ : new Domain.WarehouseAggregate.Entities.CSku { CustomerId = customerId };
}
var duplicate = await SkuRepository.FirstOrDefaultAsync(x =>
@@ -115,6 +118,17 @@ public partial class WarehouseService
throw new BusinessException("SKU 编码已存在");
}
+ if (input.Id > 0)
+ {
+ var softDeleted = await SkuRepository.GetByCustomerAndSkuCodeIncludingDeletedAsync(customerId, skuCode);
+ if (softDeleted is { IsDeleted: true } && softDeleted.Id != entity.Id)
+ {
+ softDeleted.SkuCode = $"{skuCode}__deleted_{softDeleted.Id}";
+ await SkuRepository.UpdateAsync(softDeleted);
+ }
+ }
+
+ entity.IsDeleted = false;
entity.SkuCode = skuCode;
entity.SkuName = skuName;
entity.SkuNameEn = string.IsNullOrWhiteSpace(input.SkuNameEn) ? null : input.SkuNameEn.Trim();
@@ -129,7 +143,7 @@ public partial class WarehouseService
entity.HeightCm = input.HeightCm;
entity.Remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
- if (input.Id > 0)
+ if (entity.Id > 0)
{
await SkuRepository.UpdateAsync(entity);
}
diff --git a/src/Oms.Domain/AiAggregate/Repositories/IAiSkillRepository.cs b/src/Oms.Domain/AiAggregate/Repositories/IAiSkillRepository.cs
index 8dfae78..bf79a04 100644
--- a/src/Oms.Domain/AiAggregate/Repositories/IAiSkillRepository.cs
+++ b/src/Oms.Domain/AiAggregate/Repositories/IAiSkillRepository.cs
@@ -6,4 +6,8 @@ namespace Oms.Domain.AiAggregate.Repositories;
public interface IAiSkillRepository : IRepository
{
Task GetBySkillKeyAsync(string skillKey, CancellationToken cancellationToken = default);
+
+ Task GetBySkillKeyIncludingDeletedAsync(
+ string skillKey,
+ CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.Domain/ChannelsAggregate/Repositories/ISalesProductRepository.cs b/src/Oms.Domain/ChannelsAggregate/Repositories/ISalesProductRepository.cs
index ea41dad..5ba942a 100644
--- a/src/Oms.Domain/ChannelsAggregate/Repositories/ISalesProductRepository.cs
+++ b/src/Oms.Domain/ChannelsAggregate/Repositories/ISalesProductRepository.cs
@@ -7,6 +7,10 @@ public interface ISalesProductRepository : IRepository
{
Task GetByProductCodeAsync(string productCode, CancellationToken cancellationToken = default);
+ Task GetByProductCodeIncludingDeletedAsync(
+ string productCode,
+ CancellationToken cancellationToken = default);
+
Task HasProductCodeAsync(string productCode, CancellationToken cancellationToken = default);
Task> GetPricesAsync(int salesProductId, CancellationToken cancellationToken = default);
diff --git a/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelRepository.cs b/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelRepository.cs
index 0ae8a9f..bb090e5 100644
--- a/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelRepository.cs
+++ b/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelRepository.cs
@@ -7,6 +7,10 @@ public interface IServiceChannelRepository : IRepository
{
Task GetByChannelCodeAsync(string channelCode, CancellationToken cancellationToken = default);
+ Task GetByChannelCodeIncludingDeletedAsync(
+ string channelCode,
+ CancellationToken cancellationToken = default);
+
Task HasChannelCodeAsync(string channelCode, CancellationToken cancellationToken = default);
Task> GetGroupsAsync(CancellationToken cancellationToken = default);
diff --git a/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs b/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs
index 7292f30..a46ba16 100644
--- a/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs
+++ b/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs
@@ -7,10 +7,18 @@ public interface ICustomerRepository : IRepository
{
Task GetByCustomerCodeAsync(string customerCode, CancellationToken cancellationToken = default);
+ Task GetByCustomerCodeIncludingDeletedAsync(
+ string customerCode,
+ CancellationToken cancellationToken = default);
+
Task GetByPhoneAsync(string phone, CancellationToken cancellationToken = default);
+ Task GetByPhoneIncludingDeletedAsync(string phone, CancellationToken cancellationToken = default);
+
Task GetByEmailAsync(string email, CancellationToken cancellationToken = default);
+ Task GetByEmailIncludingDeletedAsync(string email, CancellationToken cancellationToken = default);
+
Task GetByWechatOpenidAsync(string wechatOpenid, CancellationToken cancellationToken = default);
Task ExistsPhoneAsync(string phone, CancellationToken cancellationToken = default);
diff --git a/src/Oms.Domain/KbAggregate/Repositories/IKbCategoryRepository.cs b/src/Oms.Domain/KbAggregate/Repositories/IKbCategoryRepository.cs
index 4fe86d9..f10c44f 100644
--- a/src/Oms.Domain/KbAggregate/Repositories/IKbCategoryRepository.cs
+++ b/src/Oms.Domain/KbAggregate/Repositories/IKbCategoryRepository.cs
@@ -7,5 +7,7 @@ public interface IKbCategoryRepository : IRepository
{
Task GetByCodeAsync(string code, CancellationToken cancellationToken = default);
+ Task GetByCodeIncludingDeletedAsync(string code, CancellationToken cancellationToken = default);
+
Task ExistsCodeAsync(string code, int? excludeId = null, CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.Domain/SystemAggregate/Repositories/IDictTypeRepository.cs b/src/Oms.Domain/SystemAggregate/Repositories/IDictTypeRepository.cs
index fb0e07f..3b1a4aa 100644
--- a/src/Oms.Domain/SystemAggregate/Repositories/IDictTypeRepository.cs
+++ b/src/Oms.Domain/SystemAggregate/Repositories/IDictTypeRepository.cs
@@ -6,4 +6,11 @@ namespace Oms.Domain.SystemAggregate.Repositories;
public interface IDictTypeRepository : IRepository
{
Task GetByDictTypeAsync(string dictType, CancellationToken cancellationToken = default);
+
+ ///
+ /// 按类型编码查询(含软删除),用于唯一键冲突时恢复。
+ ///
+ Task GetByDictTypeIncludingDeletedAsync(
+ string dictType,
+ CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.Domain/SystemAggregate/Repositories/ISystemOtherConfigRepository.cs b/src/Oms.Domain/SystemAggregate/Repositories/ISystemOtherConfigRepository.cs
index b1b3ffe..3b92cd8 100644
--- a/src/Oms.Domain/SystemAggregate/Repositories/ISystemOtherConfigRepository.cs
+++ b/src/Oms.Domain/SystemAggregate/Repositories/ISystemOtherConfigRepository.cs
@@ -6,4 +6,8 @@ namespace Oms.Domain.SystemAggregate.Repositories;
public interface ISystemOtherConfigRepository : IRepository
{
Task GetByConfigCodeAsync(string configCode, CancellationToken cancellationToken = default);
+
+ Task GetByConfigCodeIncludingDeletedAsync(
+ string configCode,
+ CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.Domain/SystemAggregate/Repositories/IWarehouseAddressRepository.cs b/src/Oms.Domain/SystemAggregate/Repositories/IWarehouseAddressRepository.cs
index 16bd587..131abb9 100644
--- a/src/Oms.Domain/SystemAggregate/Repositories/IWarehouseAddressRepository.cs
+++ b/src/Oms.Domain/SystemAggregate/Repositories/IWarehouseAddressRepository.cs
@@ -7,5 +7,9 @@ public interface IWarehouseAddressRepository : IRepository
{
Task GetByWarehouseNameAsync(string warehouseName, CancellationToken cancellationToken = default);
+ Task GetByWarehouseNameIncludingDeletedAsync(
+ string warehouseName,
+ CancellationToken cancellationToken = default);
+
Task ExistsWarehouseNameAsync(string warehouseName, CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.Domain/WarehouseAggregate/Repositories/IWarehouseRepositories.cs b/src/Oms.Domain/WarehouseAggregate/Repositories/IWarehouseRepositories.cs
index f2c0fb8..26c7073 100644
--- a/src/Oms.Domain/WarehouseAggregate/Repositories/IWarehouseRepositories.cs
+++ b/src/Oms.Domain/WarehouseAggregate/Repositories/IWarehouseRepositories.cs
@@ -3,9 +3,21 @@ using Oms.Domain.WarehouseAggregate.Entities;
namespace Oms.Domain.WarehouseAggregate.Repositories;
-public interface ICSkuRepository : IRepository;
+public interface ICSkuRepository : IRepository
+{
+ Task GetByCustomerAndSkuCodeIncludingDeletedAsync(
+ int customerId,
+ string skuCode,
+ CancellationToken cancellationToken = default);
+}
-public interface ICBinLocationRepository : IRepository;
+public interface ICBinLocationRepository : IRepository
+{
+ Task GetByWarehouseAndBinCodeIncludingDeletedAsync(
+ int warehouseId,
+ string binCode,
+ CancellationToken cancellationToken = default);
+}
public interface ICInventoryRepository : IRepository;
diff --git a/src/Oms.SqlSugarCore/Ai/AiSkillRepository.cs b/src/Oms.SqlSugarCore/Ai/AiSkillRepository.cs
index cac80c7..4204742 100644
--- a/src/Oms.SqlSugarCore/Ai/AiSkillRepository.cs
+++ b/src/Oms.SqlSugarCore/Ai/AiSkillRepository.cs
@@ -12,4 +12,15 @@ public class AiSkillRepository
string skillKey,
CancellationToken cancellationToken = default) =>
FirstOrDefaultAsync(x => x.SkillKey == skillKey, cancellationToken);
+
+ public async Task GetBySkillKeyIncludingDeletedAsync(
+ string skillKey,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.SkillKey == skillKey,
+ cancellationToken);
+ }
}
diff --git a/src/Oms.SqlSugarCore/Channels/SalesProductRepository.cs b/src/Oms.SqlSugarCore/Channels/SalesProductRepository.cs
index b6a1aab..fdc3af9 100644
--- a/src/Oms.SqlSugarCore/Channels/SalesProductRepository.cs
+++ b/src/Oms.SqlSugarCore/Channels/SalesProductRepository.cs
@@ -13,6 +13,17 @@ public class SalesProductRepository : OmsSqlSugarRepository, ISale
return FirstOrDefaultAsync(x => x.ProductCode == productCode, cancellationToken);
}
+ public async Task GetByProductCodeIncludingDeletedAsync(
+ string productCode,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.ProductCode == productCode,
+ cancellationToken);
+ }
+
public Task HasProductCodeAsync(string productCode, CancellationToken cancellationToken = default)
{
return AnyAsync(x => x.ProductCode == productCode, cancellationToken);
diff --git a/src/Oms.SqlSugarCore/Channels/ServiceChannelRepository.cs b/src/Oms.SqlSugarCore/Channels/ServiceChannelRepository.cs
index e7e80f2..c0c6e49 100644
--- a/src/Oms.SqlSugarCore/Channels/ServiceChannelRepository.cs
+++ b/src/Oms.SqlSugarCore/Channels/ServiceChannelRepository.cs
@@ -13,6 +13,17 @@ public class ServiceChannelRepository : OmsSqlSugarRepository, I
return FirstOrDefaultAsync(x => x.ChannelCode == channelCode, cancellationToken);
}
+ public async Task GetByChannelCodeIncludingDeletedAsync(
+ string channelCode,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.ChannelCode == channelCode,
+ cancellationToken);
+ }
+
public Task HasChannelCodeAsync(string channelCode, CancellationToken cancellationToken = default)
{
return AnyAsync(x => x.ChannelCode == channelCode, cancellationToken);
diff --git a/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs b/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs
index 22ed7bc..6215bd9 100644
--- a/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs
+++ b/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs
@@ -12,16 +12,49 @@ public class CustomerRepository : OmsSqlSugarRepository, ICustomerRepo
return FirstOrDefaultAsync(x => x.CustomerCode == customerCode, cancellationToken);
}
+ public async Task GetByCustomerCodeIncludingDeletedAsync(
+ string customerCode,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.CustomerCode == customerCode,
+ cancellationToken);
+ }
+
public Task GetByPhoneAsync(string phone, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.Phone == phone, cancellationToken);
}
+ public async Task GetByPhoneIncludingDeletedAsync(
+ string phone,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.Phone == phone,
+ cancellationToken);
+ }
+
public Task GetByEmailAsync(string email, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.Email == email, cancellationToken);
}
+ public async Task GetByEmailIncludingDeletedAsync(
+ string email,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.Email == email,
+ cancellationToken);
+ }
+
public Task GetByWechatOpenidAsync(string wechatOpenid, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.WechatOpenid == wechatOpenid, cancellationToken);
diff --git a/src/Oms.SqlSugarCore/Kb/KbCategoryRepository.cs b/src/Oms.SqlSugarCore/Kb/KbCategoryRepository.cs
index f6fdc90..0bd8fce 100644
--- a/src/Oms.SqlSugarCore/Kb/KbCategoryRepository.cs
+++ b/src/Oms.SqlSugarCore/Kb/KbCategoryRepository.cs
@@ -11,6 +11,17 @@ public class KbCategoryRepository
public Task GetByCodeAsync(string code, CancellationToken cancellationToken = default) =>
FirstOrDefaultAsync(x => x.Code == code, cancellationToken);
+ public async Task GetByCodeIncludingDeletedAsync(
+ string code,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.Code == code,
+ cancellationToken);
+ }
+
public async Task ExistsCodeAsync(
string code,
int? excludeId = null,
diff --git a/src/Oms.SqlSugarCore/Repositories/SoftDeleteQuery.cs b/src/Oms.SqlSugarCore/Repositories/SoftDeleteQuery.cs
new file mode 100644
index 0000000..f77ab9a
--- /dev/null
+++ b/src/Oms.SqlSugarCore/Repositories/SoftDeleteQuery.cs
@@ -0,0 +1,24 @@
+using System.Linq.Expressions;
+using SqlSugar;
+
+namespace Oms.SqlSugarCore.Repositories;
+
+///
+/// 软删场景下按业务唯一键查询(ClearFilter),供新增恢复 / 释放唯一键使用。
+///
+public static class SoftDeleteQuery
+{
+ public static async Task FirstIncludingDeletedAsync(
+ ISqlSugarClient db,
+ Expression> predicate,
+ CancellationToken cancellationToken = default)
+ where TEntity : class, new()
+ {
+ var rows = await db.Queryable()
+ .ClearFilter()
+ .Where(predicate)
+ .Take(1)
+ .ToListAsync(cancellationToken);
+ return rows.FirstOrDefault();
+ }
+}
diff --git a/src/Oms.SqlSugarCore/System/DictTypeRepository.cs b/src/Oms.SqlSugarCore/System/DictTypeRepository.cs
index b52693e..e76441f 100644
--- a/src/Oms.SqlSugarCore/System/DictTypeRepository.cs
+++ b/src/Oms.SqlSugarCore/System/DictTypeRepository.cs
@@ -11,4 +11,17 @@ public class DictTypeRepository : OmsSqlSugarRepository, IDictType
{
return FirstOrDefaultAsync(x => x.DictType == dictType, cancellationToken);
}
+
+ public async Task GetByDictTypeIncludingDeletedAsync(
+ string dictType,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ var rows = await db.Queryable()
+ .ClearFilter()
+ .Where(x => x.DictType == dictType)
+ .Take(1)
+ .ToListAsync(cancellationToken);
+ return rows.FirstOrDefault();
+ }
}
diff --git a/src/Oms.SqlSugarCore/System/SystemOtherConfigRepository.cs b/src/Oms.SqlSugarCore/System/SystemOtherConfigRepository.cs
index e71892b..40bcfeb 100644
--- a/src/Oms.SqlSugarCore/System/SystemOtherConfigRepository.cs
+++ b/src/Oms.SqlSugarCore/System/SystemOtherConfigRepository.cs
@@ -11,4 +11,15 @@ public class SystemOtherConfigRepository : OmsSqlSugarRepository x.ConfigCode == configCode, cancellationToken);
}
+
+ public async Task GetByConfigCodeIncludingDeletedAsync(
+ string configCode,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.ConfigCode == configCode,
+ cancellationToken);
+ }
}
diff --git a/src/Oms.SqlSugarCore/System/WarehouseAddressRepository.cs b/src/Oms.SqlSugarCore/System/WarehouseAddressRepository.cs
index e6233c5..40a7eb1 100644
--- a/src/Oms.SqlSugarCore/System/WarehouseAddressRepository.cs
+++ b/src/Oms.SqlSugarCore/System/WarehouseAddressRepository.cs
@@ -12,6 +12,17 @@ public class WarehouseAddressRepository : OmsSqlSugarRepository x.WarehouseName == warehouseName, cancellationToken);
}
+ public async Task GetByWarehouseNameIncludingDeletedAsync(
+ string warehouseName,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.WarehouseName == warehouseName,
+ cancellationToken);
+ }
+
public Task ExistsWarehouseNameAsync(string warehouseName, CancellationToken cancellationToken = default)
{
return AnyAsync(x => x.WarehouseName == warehouseName, cancellationToken);
diff --git a/src/Oms.SqlSugarCore/Warehouse/WarehouseRepositories.cs b/src/Oms.SqlSugarCore/Warehouse/WarehouseRepositories.cs
index c9e6193..08ecd97 100644
--- a/src/Oms.SqlSugarCore/Warehouse/WarehouseRepositories.cs
+++ b/src/Oms.SqlSugarCore/Warehouse/WarehouseRepositories.cs
@@ -5,9 +5,35 @@ using Oms.SqlSugarCore.Repositories;
namespace Oms.SqlSugarCore.Warehouse;
-public class CSkuRepository : OmsSqlSugarRepository, ICSkuRepository, IScopedDependency;
+public class CSkuRepository : OmsSqlSugarRepository, ICSkuRepository, IScopedDependency
+{
+ public async Task GetByCustomerAndSkuCodeIncludingDeletedAsync(
+ int customerId,
+ string skuCode,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.CustomerId == customerId && x.SkuCode == skuCode,
+ cancellationToken);
+ }
+}
-public class CBinLocationRepository : OmsSqlSugarRepository, ICBinLocationRepository, IScopedDependency;
+public class CBinLocationRepository : OmsSqlSugarRepository, ICBinLocationRepository, IScopedDependency
+{
+ public async Task GetByWarehouseAndBinCodeIncludingDeletedAsync(
+ int warehouseId,
+ string binCode,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ return await SoftDeleteQuery.FirstIncludingDeletedAsync(
+ db,
+ x => x.WarehouseId == warehouseId && x.BinCode == binCode,
+ cancellationToken);
+ }
+}
public class CInventoryRepository : OmsSqlSugarRepository, ICInventoryRepository, IScopedDependency;