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); + } }