diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json
index 4a20620..3079e81 100644
--- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json
+++ b/src/Api/TakeoutSaaS.AdminApi/appsettings.Seed.Development.json
@@ -30,6 +30,24 @@
{ "Key": "new", "Value": "新店", "SortOrder": 20 }
]
}
+ ],
+ "SystemParameters": [
+ { "Key": "site_name", "Value": "外卖SaaS Demo", "Description": "演示环境站点名称", "SortOrder": 10, "IsEnabled": true },
+ { "Key": "order_auto_cancel_minutes", "Value": "30", "Description": "待支付自动取消时间(分钟)", "SortOrder": 20, "IsEnabled": true }
+ ]
+ }
+ },
+ "Identity": {
+ "AdminSeed": {
+ "Users": [
+ {
+ "Account": "admin",
+ "DisplayName": "平台管理员",
+ "Password": "Admin@123456",
+ "TenantId": 1000000000001,
+ "Roles": [ "PlatformAdmin" ],
+ "Permissions": [ "merchant:*", "store:*", "product:*", "order:*", "payment:*", "delivery:*" ]
+ }
]
}
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs
index 010793d..d0a2132 100644
--- a/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs
@@ -39,4 +39,14 @@ public interface IDeliveryRepository
/// 持久化变更。
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新配送单。
+ ///
+ Task UpdateDeliveryOrderAsync(DeliveryOrder deliveryOrder, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除配送单及事件。
+ ///
+ Task DeleteDeliveryOrderAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs
index a735ff7..9e8710a 100644
--- a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs
@@ -60,4 +60,14 @@ public interface IMerchantRepository
/// 持久化变更。
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新商户信息。
+ ///
+ Task UpdateMerchantAsync(Merchant merchant, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商户。
+ ///
+ Task DeleteMerchantAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs
index baba30b..d48ecf3 100644
--- a/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs
@@ -66,4 +66,14 @@ public interface IOrderRepository
/// 持久化变更。
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新订单。
+ ///
+ Task UpdateOrderAsync(Order order, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除订单。
+ ///
+ Task DeleteOrderAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs
index c217ce2..ec286b5 100644
--- a/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs
@@ -39,4 +39,14 @@ public interface IPaymentRepository
/// 持久化变更。
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新支付记录。
+ ///
+ Task UpdatePaymentAsync(PaymentRecord payment, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除支付记录及关联退款。
+ ///
+ Task DeletePaymentAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs b/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs
index 093ee81..6f01804 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs
@@ -100,4 +100,49 @@ public interface IProductRepository
/// 持久化变更。
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新商品。
+ ///
+ Task UpdateProductAsync(Product product, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商品。
+ ///
+ Task DeleteProductAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新分类。
+ ///
+ Task UpdateCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除分类。
+ ///
+ Task DeleteCategoryAsync(long categoryId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商品下的 SKU。
+ ///
+ Task RemoveSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商品下的加料组及选项。
+ ///
+ Task RemoveAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商品下的规格组及选项。
+ ///
+ Task RemoveAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商品媒资。
+ ///
+ Task RemoveMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除商品定价规则。
+ ///
+ Task RemovePricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs
index dccde79..bbbf7e0 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs
@@ -90,4 +90,14 @@ public interface IStoreRepository
/// 持久化变更。
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新门店。
+ ///
+ Task UpdateStoreAsync(Store store, CancellationToken cancellationToken = default);
+
+ ///
+ /// 删除门店。
+ ///
+ Task DeleteStoreAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/AppSeedOptions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/AppSeedOptions.cs
index d5992f7..b3ec66d 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/AppSeedOptions.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/AppSeedOptions.cs
@@ -26,4 +26,9 @@ public sealed class AppSeedOptions
/// 基础字典分组。
///
public List DictionaryGroups { get; set; } = new();
+
+ ///
+ /// 系统参数配置。
+ ///
+ public List SystemParameters { get; set; } = new();
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/SystemParameterSeedOptions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/SystemParameterSeedOptions.cs
new file mode 100644
index 0000000..a9df90b
--- /dev/null
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/SystemParameterSeedOptions.cs
@@ -0,0 +1,37 @@
+namespace TakeoutSaaS.Infrastructure.App.Options;
+
+///
+/// 系统参数种子配置项。
+///
+public sealed class SystemParameterSeedOptions
+{
+ ///
+ /// 目标租户,null 时使用默认租户或 0。
+ ///
+ public long? TenantId { get; set; }
+
+ ///
+ /// 参数键。
+ ///
+ public string Key { get; set; } = string.Empty;
+
+ ///
+ /// 参数值。
+ ///
+ public string Value { get; set; } = string.Empty;
+
+ ///
+ /// 说明。
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 排序。
+ ///
+ public int SortOrder { get; set; }
+
+ ///
+ /// 是否启用。
+ ///
+ public bool IsEnabled { get; set; } = true;
+}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/AppDataSeeder.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/AppDataSeeder.cs
index eede6a4..e9a70d7 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/AppDataSeeder.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/AppDataSeeder.cs
@@ -1,9 +1,11 @@
+using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TakeoutSaaS.Domain.Dictionary.Entities;
+using TakeoutSaaS.Domain.Dictionary.Enums;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Infrastructure.App.Options;
@@ -142,81 +144,147 @@ public sealed class AppDataSeeder : IHostedService
///
private async Task EnsureDictionarySeedsAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken)
{
- if (_options.DictionaryGroups == null || _options.DictionaryGroups.Count == 0)
+ var dictionaryGroups = _options.DictionaryGroups ?? new List();
+ var hasDictionaryGroups = dictionaryGroups.Count > 0;
+
+ if (!hasDictionaryGroups)
{
_logger.LogInformation("AppSeed 未配置基础字典,跳过字典种子");
+ }
+
+ if (hasDictionaryGroups)
+ {
+ foreach (var groupOptions in dictionaryGroups)
+ {
+ if (string.IsNullOrWhiteSpace(groupOptions.Code) || string.IsNullOrWhiteSpace(groupOptions.Name))
+ {
+ _logger.LogWarning("AppSeed 跳过字典分组,Code 或 Name 为空");
+ continue;
+ }
+
+ var tenantId = groupOptions.TenantId ?? defaultTenantId ?? 0;
+ var code = groupOptions.Code.Trim();
+
+ var group = await dbContext.DictionaryGroups
+ .IgnoreQueryFilters()
+ .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Code == code, cancellationToken);
+
+ if (group == null)
+ {
+ group = new DictionaryGroup
+ {
+ Id = 0,
+ TenantId = tenantId,
+ Code = code,
+ Name = groupOptions.Name.Trim(),
+ Scope = groupOptions.Scope,
+ Description = groupOptions.Description?.Trim(),
+ IsEnabled = groupOptions.IsEnabled
+ };
+
+ await dbContext.DictionaryGroups.AddAsync(group, cancellationToken);
+ _logger.LogInformation("AppSeed 创建字典分组 {GroupCode} (Tenant: {TenantId})", code, tenantId);
+ }
+ else
+ {
+ var groupUpdated = false;
+
+ if (!string.Equals(group.Name, groupOptions.Name, StringComparison.Ordinal))
+ {
+ group.Name = groupOptions.Name.Trim();
+ groupUpdated = true;
+ }
+
+ if (!string.Equals(group.Description, groupOptions.Description, StringComparison.Ordinal))
+ {
+ group.Description = groupOptions.Description?.Trim();
+ groupUpdated = true;
+ }
+
+ if (group.Scope != groupOptions.Scope)
+ {
+ group.Scope = groupOptions.Scope;
+ groupUpdated = true;
+ }
+
+ if (group.IsEnabled != groupOptions.IsEnabled)
+ {
+ group.IsEnabled = groupOptions.IsEnabled;
+ groupUpdated = true;
+ }
+
+ if (groupUpdated)
+ {
+ dbContext.DictionaryGroups.Update(group);
+ }
+ }
+
+ await UpsertDictionaryItemsAsync(dbContext, group, groupOptions.Items, tenantId, cancellationToken);
+ }
+ }
+
+ await EnsureSystemParametersAsync(dbContext, defaultTenantId, cancellationToken);
+ await dbContext.SaveChangesAsync(cancellationToken);
+ }
+
+ ///
+ /// 确保系统参数以字典形式幂等种子。
+ ///
+ private async Task EnsureSystemParametersAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken)
+ {
+ var systemParameters = _options.SystemParameters ?? new List();
+
+ if (systemParameters.Count == 0)
+ {
+ _logger.LogInformation("AppSeed 未配置系统参数,跳过系统参数种子");
return;
}
- foreach (var groupOptions in _options.DictionaryGroups)
+ var grouped = systemParameters
+ .Where(x => !string.IsNullOrWhiteSpace(x.Key) && !string.IsNullOrWhiteSpace(x.Value))
+ .GroupBy(x => x.TenantId ?? defaultTenantId ?? 0);
+
+ if (!grouped.Any())
{
- if (string.IsNullOrWhiteSpace(groupOptions.Code) || string.IsNullOrWhiteSpace(groupOptions.Name))
- {
- _logger.LogWarning("AppSeed 跳过字典分组,Code 或 Name 为空");
- continue;
- }
+ _logger.LogInformation("AppSeed 系统参数配置为空,跳过系统参数种子");
+ return;
+ }
- var tenantId = groupOptions.TenantId ?? defaultTenantId ?? 0;
- var code = groupOptions.Code.Trim();
-
- var group = await dbContext.DictionaryGroups
+ foreach (var group in grouped)
+ {
+ var tenantId = group.Key;
+ var dictionaryGroup = await dbContext.DictionaryGroups
.IgnoreQueryFilters()
- .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Code == code, cancellationToken);
+ .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Code == "system_parameters", cancellationToken);
- if (group == null)
+ if (dictionaryGroup == null)
{
- group = new DictionaryGroup
+ dictionaryGroup = new DictionaryGroup
{
Id = 0,
TenantId = tenantId,
- Code = code,
- Name = groupOptions.Name.Trim(),
- Scope = groupOptions.Scope,
- Description = groupOptions.Description?.Trim(),
- IsEnabled = groupOptions.IsEnabled
+ Code = "system_parameters",
+ Name = "系统参数",
+ Scope = tenantId == 0 ? DictionaryScope.System : DictionaryScope.Business,
+ Description = "系统参数配置",
+ IsEnabled = true
};
- await dbContext.DictionaryGroups.AddAsync(group, cancellationToken);
- _logger.LogInformation("AppSeed 创建字典分组 {GroupCode} (Tenant: {TenantId})", code, tenantId);
+ await dbContext.DictionaryGroups.AddAsync(dictionaryGroup, cancellationToken);
+ _logger.LogInformation("AppSeed 创建系统参数分组 (Tenant: {TenantId})", tenantId);
}
- else
+
+ var seedItems = group.Select(x => new DictionarySeedItemOptions
{
- var groupUpdated = false;
+ Key = x.Key.Trim(),
+ Value = x.Value.Trim(),
+ Description = x.Description?.Trim(),
+ SortOrder = x.SortOrder,
+ IsEnabled = x.IsEnabled
+ });
- if (!string.Equals(group.Name, groupOptions.Name, StringComparison.Ordinal))
- {
- group.Name = groupOptions.Name.Trim();
- groupUpdated = true;
- }
-
- if (!string.Equals(group.Description, groupOptions.Description, StringComparison.Ordinal))
- {
- group.Description = groupOptions.Description?.Trim();
- groupUpdated = true;
- }
-
- if (group.Scope != groupOptions.Scope)
- {
- group.Scope = groupOptions.Scope;
- groupUpdated = true;
- }
-
- if (group.IsEnabled != groupOptions.IsEnabled)
- {
- group.IsEnabled = groupOptions.IsEnabled;
- groupUpdated = true;
- }
-
- if (groupUpdated)
- {
- dbContext.DictionaryGroups.Update(group);
- }
- }
-
- await UpsertDictionaryItemsAsync(dbContext, group, groupOptions.Items, tenantId, cancellationToken);
+ await UpsertDictionaryItemsAsync(dbContext, dictionaryGroup, seedItems, tenantId, cancellationToken);
}
-
- await dbContext.SaveChangesAsync(cancellationToken);
}
///
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfDeliveryRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfDeliveryRepository.cs
index 45db052..5c408ef 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfDeliveryRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfDeliveryRepository.cs
@@ -68,4 +68,35 @@ public sealed class EfDeliveryRepository : IDeliveryRepository
{
return _context.SaveChangesAsync(cancellationToken);
}
+
+ ///
+ public Task UpdateDeliveryOrderAsync(DeliveryOrder deliveryOrder, CancellationToken cancellationToken = default)
+ {
+ _context.DeliveryOrders.Update(deliveryOrder);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeleteDeliveryOrderAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var events = await _context.DeliveryEvents
+ .Where(x => x.TenantId == tenantId && x.DeliveryOrderId == deliveryOrderId)
+ .ToListAsync(cancellationToken);
+
+ if (events.Count > 0)
+ {
+ _context.DeliveryEvents.RemoveRange(events);
+ }
+
+ var existing = await _context.DeliveryOrders
+ .Where(x => x.TenantId == tenantId && x.Id == deliveryOrderId)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.DeliveryOrders.Remove(existing);
+ }
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs
index a17b212..3aaa2c5 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs
@@ -113,4 +113,26 @@ public sealed class EfMerchantRepository : IMerchantRepository
{
return _context.SaveChangesAsync(cancellationToken);
}
+
+ ///
+ public Task UpdateMerchantAsync(Merchant merchant, CancellationToken cancellationToken = default)
+ {
+ _context.Merchants.Update(merchant);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeleteMerchantAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var existing = await _context.Merchants
+ .Where(x => x.TenantId == tenantId && x.Id == merchantId)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.Merchants.Remove(existing);
+ }
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs
index 9517ef9..91aa04f 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs
@@ -130,4 +130,49 @@ public sealed class EfOrderRepository : IOrderRepository
{
return _context.SaveChangesAsync(cancellationToken);
}
+
+ ///
+ public Task UpdateOrderAsync(Order order, CancellationToken cancellationToken = default)
+ {
+ _context.Orders.Update(order);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeleteOrderAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var items = await _context.OrderItems
+ .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
+ .ToListAsync(cancellationToken);
+ if (items.Count > 0)
+ {
+ _context.OrderItems.RemoveRange(items);
+ }
+
+ var histories = await _context.OrderStatusHistories
+ .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
+ .ToListAsync(cancellationToken);
+ if (histories.Count > 0)
+ {
+ _context.OrderStatusHistories.RemoveRange(histories);
+ }
+
+ var refunds = await _context.RefundRequests
+ .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
+ .ToListAsync(cancellationToken);
+ if (refunds.Count > 0)
+ {
+ _context.RefundRequests.RemoveRange(refunds);
+ }
+
+ var existing = await _context.Orders
+ .Where(x => x.TenantId == tenantId && x.Id == orderId)
+ .FirstOrDefaultAsync(cancellationToken);
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.Orders.Remove(existing);
+ }
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfPaymentRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfPaymentRepository.cs
index af5034d..8ea2124 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfPaymentRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfPaymentRepository.cs
@@ -68,4 +68,33 @@ public sealed class EfPaymentRepository : IPaymentRepository
{
return _context.SaveChangesAsync(cancellationToken);
}
+
+ ///
+ public Task UpdatePaymentAsync(PaymentRecord payment, CancellationToken cancellationToken = default)
+ {
+ _context.PaymentRecords.Update(payment);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeletePaymentAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var refunds = await _context.PaymentRefundRecords
+ .Where(x => x.TenantId == tenantId && x.PaymentRecordId == paymentId)
+ .ToListAsync(cancellationToken);
+ if (refunds.Count > 0)
+ {
+ _context.PaymentRefundRecords.RemoveRange(refunds);
+ }
+
+ var existing = await _context.PaymentRecords
+ .Where(x => x.TenantId == tenantId && x.Id == paymentId)
+ .FirstOrDefaultAsync(cancellationToken);
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.PaymentRecords.Remove(existing);
+ }
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs
index fde29fe..234a367 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs
@@ -224,4 +224,163 @@ public sealed class EfProductRepository : IProductRepository
{
return _context.SaveChangesAsync(cancellationToken);
}
+
+ ///
+ public Task UpdateProductAsync(Product product, CancellationToken cancellationToken = default)
+ {
+ _context.Products.Update(product);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeleteProductAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ await RemovePricingRulesAsync(productId, tenantId, cancellationToken);
+ await RemoveMediaAssetsAsync(productId, tenantId, cancellationToken);
+ await RemoveAttributeGroupsAsync(productId, tenantId, cancellationToken);
+ await RemoveAddonGroupsAsync(productId, tenantId, cancellationToken);
+ await RemoveSkusAsync(productId, tenantId, cancellationToken);
+
+ var existing = await _context.Products
+ .Where(x => x.TenantId == tenantId && x.Id == productId)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.Products.Remove(existing);
+ }
+
+ ///
+ public Task UpdateCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default)
+ {
+ _context.ProductCategories.Update(category);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeleteCategoryAsync(long categoryId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var existing = await _context.ProductCategories
+ .Where(x => x.TenantId == tenantId && x.Id == categoryId)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.ProductCategories.Remove(existing);
+ }
+
+ ///
+ public async Task RemoveSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var skus = await _context.ProductSkus
+ .Where(x => x.TenantId == tenantId && x.ProductId == productId)
+ .ToListAsync(cancellationToken);
+
+ if (skus.Count == 0)
+ {
+ return;
+ }
+
+ _context.ProductSkus.RemoveRange(skus);
+ }
+
+ ///
+ public async Task RemoveAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var groupIds = await _context.ProductAddonGroups
+ .Where(x => x.TenantId == tenantId && x.ProductId == productId)
+ .Select(x => x.Id)
+ .ToListAsync(cancellationToken);
+
+ if (groupIds.Count == 0)
+ {
+ return;
+ }
+
+ var options = await _context.ProductAddonOptions
+ .Where(x => x.TenantId == tenantId && groupIds.Contains(x.AddonGroupId))
+ .ToListAsync(cancellationToken);
+
+ if (options.Count > 0)
+ {
+ _context.ProductAddonOptions.RemoveRange(options);
+ }
+
+ var groups = await _context.ProductAddonGroups
+ .Where(x => groupIds.Contains(x.Id))
+ .ToListAsync(cancellationToken);
+
+ if (groups.Count > 0)
+ {
+ _context.ProductAddonGroups.RemoveRange(groups);
+ }
+ }
+
+ ///
+ public async Task RemoveAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var groupIds = await _context.ProductAttributeGroups
+ .Where(x => x.TenantId == tenantId && x.ProductId == productId)
+ .Select(x => x.Id)
+ .ToListAsync(cancellationToken);
+
+ if (groupIds.Count == 0)
+ {
+ return;
+ }
+
+ var options = await _context.ProductAttributeOptions
+ .Where(x => x.TenantId == tenantId && groupIds.Contains(x.AttributeGroupId))
+ .ToListAsync(cancellationToken);
+
+ if (options.Count > 0)
+ {
+ _context.ProductAttributeOptions.RemoveRange(options);
+ }
+
+ var groups = await _context.ProductAttributeGroups
+ .Where(x => groupIds.Contains(x.Id))
+ .ToListAsync(cancellationToken);
+
+ if (groups.Count > 0)
+ {
+ _context.ProductAttributeGroups.RemoveRange(groups);
+ }
+ }
+
+ ///
+ public async Task RemoveMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var assets = await _context.ProductMediaAssets
+ .Where(x => x.TenantId == tenantId && x.ProductId == productId)
+ .ToListAsync(cancellationToken);
+
+ if (assets.Count == 0)
+ {
+ return;
+ }
+
+ _context.ProductMediaAssets.RemoveRange(assets);
+ }
+
+ ///
+ public async Task RemovePricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var rules = await _context.ProductPricingRules
+ .Where(x => x.TenantId == tenantId && x.ProductId == productId)
+ .ToListAsync(cancellationToken);
+
+ if (rules.Count == 0)
+ {
+ return;
+ }
+
+ _context.ProductPricingRules.RemoveRange(rules);
+ }
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs
index 120f1d0..4ed2412 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs
@@ -171,4 +171,26 @@ public sealed class EfStoreRepository : IStoreRepository
{
return _context.SaveChangesAsync(cancellationToken);
}
+
+ ///
+ public Task UpdateStoreAsync(Store store, CancellationToken cancellationToken = default)
+ {
+ _context.Stores.Update(store);
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task DeleteStoreAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
+ {
+ var existing = await _context.Stores
+ .Where(x => x.TenantId == tenantId && x.Id == storeId)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ if (existing == null)
+ {
+ return;
+ }
+
+ _context.Stores.Remove(existing);
+ }
}