refactor(store): remove fallback config behavior and expose isConfigured
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 43s
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 43s
This commit is contained in:
@@ -107,9 +107,13 @@ public sealed class StoreDeliverySettingsDto
|
||||
/// </summary>
|
||||
public string StoreId { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// IsConfigured。
|
||||
/// </summary>
|
||||
public bool IsConfigured { get; set; }
|
||||
/// <summary>
|
||||
/// Mode。
|
||||
/// </summary>
|
||||
public string Mode { get; set; } = "radius";
|
||||
public string? Mode { get; set; }
|
||||
/// <summary>
|
||||
/// RadiusCenterLatitude。
|
||||
/// </summary>
|
||||
@@ -129,7 +133,7 @@ public sealed class StoreDeliverySettingsDto
|
||||
/// <summary>
|
||||
/// GeneralSettings。
|
||||
/// </summary>
|
||||
public DeliveryGeneralSettingsDto GeneralSettings { get; set; } = new();
|
||||
public DeliveryGeneralSettingsDto? GeneralSettings { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -83,9 +83,13 @@ public sealed class StoreDineInSettingsDto
|
||||
/// </summary>
|
||||
public string StoreId { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// IsConfigured。
|
||||
/// </summary>
|
||||
public bool IsConfigured { get; set; }
|
||||
/// <summary>
|
||||
/// BasicSettings。
|
||||
/// </summary>
|
||||
public DineInBasicSettingsDto BasicSettings { get; set; } = new();
|
||||
public DineInBasicSettingsDto? BasicSettings { get; set; }
|
||||
/// <summary>
|
||||
/// Areas。
|
||||
/// </summary>
|
||||
|
||||
@@ -67,6 +67,10 @@ public sealed class StoreFeesSettingsDto
|
||||
/// </summary>
|
||||
public string StoreId { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// IsConfigured。
|
||||
/// </summary>
|
||||
public bool IsConfigured { get; set; }
|
||||
/// <summary>
|
||||
/// MinimumOrderAmount。
|
||||
/// </summary>
|
||||
public decimal MinimumOrderAmount { get; set; }
|
||||
|
||||
@@ -145,13 +145,17 @@ public sealed class StorePickupSettingsDto
|
||||
/// </summary>
|
||||
public string StoreId { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// IsConfigured。
|
||||
/// </summary>
|
||||
public bool IsConfigured { get; set; }
|
||||
/// <summary>
|
||||
/// Mode。
|
||||
/// </summary>
|
||||
public string Mode { get; set; } = "big";
|
||||
public string? Mode { get; set; }
|
||||
/// <summary>
|
||||
/// BasicSettings。
|
||||
/// </summary>
|
||||
public PickupBasicSettingsDto BasicSettings { get; set; } = new();
|
||||
public PickupBasicSettingsDto? BasicSettings { get; set; }
|
||||
/// <summary>
|
||||
/// BigSlots。
|
||||
/// </summary>
|
||||
@@ -159,7 +163,7 @@ public sealed class StorePickupSettingsDto
|
||||
/// <summary>
|
||||
/// FineRule。
|
||||
/// </summary>
|
||||
public PickupFineRuleDto FineRule { get; set; } = new();
|
||||
public PickupFineRuleDto? FineRule { get; set; }
|
||||
/// <summary>
|
||||
/// PreviewDays。
|
||||
/// </summary>
|
||||
|
||||
@@ -48,21 +48,25 @@ public sealed class StoreDeliveryController(
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var radiusTiers = ParseRadiusTiers(setting?.RadiusTiersJson);
|
||||
var isConfigured = setting is not null || polygonZones.Count > 0 || radiusTiers.Count > 0;
|
||||
return ApiResponse<StoreDeliverySettingsDto>.Ok(new StoreDeliverySettingsDto
|
||||
{
|
||||
StoreId = parsedStoreId.ToString(),
|
||||
Mode = StoreApiHelpers.ToDeliveryModeText(setting?.Mode ?? StoreDeliveryMode.Radius),
|
||||
IsConfigured = isConfigured,
|
||||
Mode = setting is null ? null : StoreApiHelpers.ToDeliveryModeText(setting.Mode),
|
||||
RadiusCenterLatitude = setting?.RadiusCenterLatitude,
|
||||
RadiusCenterLongitude = setting?.RadiusCenterLongitude,
|
||||
RadiusTiers = radiusTiers,
|
||||
PolygonZones = polygonZones.Select(MapPolygonZone).ToList(),
|
||||
GeneralSettings = new DeliveryGeneralSettingsDto
|
||||
{
|
||||
EtaAdjustmentMinutes = setting?.EtaAdjustmentMinutes ?? 10,
|
||||
FreeDeliveryThreshold = setting?.FreeDeliveryThreshold ?? 30m,
|
||||
HourlyCapacityLimit = setting?.HourlyCapacityLimit ?? 50,
|
||||
MaxDeliveryDistance = setting?.MaxDeliveryDistance ?? 5m
|
||||
}
|
||||
GeneralSettings = setting is null
|
||||
? null
|
||||
: new DeliveryGeneralSettingsDto
|
||||
{
|
||||
EtaAdjustmentMinutes = setting.EtaAdjustmentMinutes,
|
||||
FreeDeliveryThreshold = setting.FreeDeliveryThreshold,
|
||||
HourlyCapacityLimit = setting.HourlyCapacityLimit,
|
||||
MaxDeliveryDistance = setting.MaxDeliveryDistance
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,6 +102,16 @@ public sealed class StoreDeliveryController(
|
||||
public async Task<ApiResponse<object>> Save([FromBody] StoreDeliverySettingsDto request, CancellationToken cancellationToken)
|
||||
{
|
||||
var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
|
||||
if (string.IsNullOrWhiteSpace(request.Mode))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "mode 不能为空");
|
||||
}
|
||||
|
||||
if (request.GeneralSettings is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "generalSettings 不能为空");
|
||||
}
|
||||
|
||||
var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
|
||||
await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);
|
||||
|
||||
@@ -203,6 +217,10 @@ public sealed class StoreDeliveryController(
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (sourceSetting is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置配送设置,无法复制");
|
||||
}
|
||||
|
||||
var targetSettings = await dbContext.StoreDeliverySettings
|
||||
.Where(x => x.TenantId == tenantId && accessibleTargetIds.Contains(x.StoreId))
|
||||
@@ -220,14 +238,14 @@ public sealed class StoreDeliveryController(
|
||||
await dbContext.StoreDeliverySettings.AddAsync(targetSetting, cancellationToken);
|
||||
}
|
||||
|
||||
targetSetting.Mode = sourceSetting?.Mode ?? StoreDeliveryMode.Radius;
|
||||
targetSetting.EtaAdjustmentMinutes = sourceSetting?.EtaAdjustmentMinutes ?? 10;
|
||||
targetSetting.FreeDeliveryThreshold = sourceSetting?.FreeDeliveryThreshold ?? 30m;
|
||||
targetSetting.HourlyCapacityLimit = sourceSetting?.HourlyCapacityLimit ?? 50;
|
||||
targetSetting.MaxDeliveryDistance = sourceSetting?.MaxDeliveryDistance ?? 5m;
|
||||
targetSetting.RadiusCenterLatitude = sourceSetting?.RadiusCenterLatitude;
|
||||
targetSetting.RadiusCenterLongitude = sourceSetting?.RadiusCenterLongitude;
|
||||
targetSetting.RadiusTiersJson = sourceSetting?.RadiusTiersJson;
|
||||
targetSetting.Mode = sourceSetting.Mode;
|
||||
targetSetting.EtaAdjustmentMinutes = sourceSetting.EtaAdjustmentMinutes;
|
||||
targetSetting.FreeDeliveryThreshold = sourceSetting.FreeDeliveryThreshold;
|
||||
targetSetting.HourlyCapacityLimit = sourceSetting.HourlyCapacityLimit;
|
||||
targetSetting.MaxDeliveryDistance = sourceSetting.MaxDeliveryDistance;
|
||||
targetSetting.RadiusCenterLatitude = sourceSetting.RadiusCenterLatitude;
|
||||
targetSetting.RadiusCenterLongitude = sourceSetting.RadiusCenterLongitude;
|
||||
targetSetting.RadiusTiersJson = sourceSetting.RadiusTiersJson;
|
||||
}
|
||||
|
||||
var targetZones = await dbContext.StoreDeliveryZones
|
||||
@@ -264,55 +282,20 @@ public sealed class StoreDeliveryController(
|
||||
|
||||
private static List<RadiusTierDto> ParseRadiusTiers(string? raw)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(raw))
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize<List<RadiusTierDto>>(raw, StoreApiHelpers.JsonOptions);
|
||||
if (parsed is not null && parsed.Count > 0)
|
||||
{
|
||||
return NormalizeRadiusTiers(parsed);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略配置反序列化异常并回落默认值
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return NormalizeRadiusTiers(new List<RadiusTierDto>
|
||||
try
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = "tier-1",
|
||||
MinDistance = 0m,
|
||||
MaxDistance = 1m,
|
||||
DeliveryFee = 3m,
|
||||
EtaMinutes = 20,
|
||||
MinOrderAmount = 15m,
|
||||
Color = "#52c41a"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = "tier-2",
|
||||
MinDistance = 1m,
|
||||
MaxDistance = 3m,
|
||||
DeliveryFee = 5m,
|
||||
EtaMinutes = 35,
|
||||
MinOrderAmount = 20m,
|
||||
Color = "#faad14"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = "tier-3",
|
||||
MinDistance = 3m,
|
||||
MaxDistance = 5m,
|
||||
DeliveryFee = 8m,
|
||||
EtaMinutes = 50,
|
||||
MinOrderAmount = 25m,
|
||||
Color = "#ff4d4f"
|
||||
}
|
||||
});
|
||||
var parsed = JsonSerializer.Deserialize<List<RadiusTierDto>>(raw, StoreApiHelpers.JsonOptions);
|
||||
return NormalizeRadiusTiers(parsed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static List<RadiusTierDto> NormalizeRadiusTiers(IEnumerable<RadiusTierDto>? source)
|
||||
@@ -343,9 +326,9 @@ public sealed class StoreDeliveryController(
|
||||
{
|
||||
Id = source.Id.ToString(),
|
||||
Name = source.ZoneName,
|
||||
Color = source.Color ?? "#1677ff",
|
||||
Color = source.Color ?? string.Empty,
|
||||
DeliveryFee = source.DeliveryFee ?? 0m,
|
||||
EtaMinutes = source.EstimatedMinutes ?? 20,
|
||||
EtaMinutes = source.EstimatedMinutes ?? 0,
|
||||
MinOrderAmount = source.MinimumOrderAmount ?? 0m,
|
||||
Priority = source.Priority,
|
||||
PolygonGeoJson = source.PolygonGeoJson
|
||||
|
||||
@@ -47,16 +47,20 @@ public sealed class StoreDineInController(
|
||||
.Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
|
||||
.OrderBy(x => x.TableCode)
|
||||
.ToListAsync(cancellationToken);
|
||||
var isConfigured = basic is not null || areas.Count > 0 || tables.Count > 0;
|
||||
|
||||
return ApiResponse<StoreDineInSettingsDto>.Ok(new StoreDineInSettingsDto
|
||||
{
|
||||
StoreId = parsedStoreId.ToString(),
|
||||
BasicSettings = new DineInBasicSettingsDto
|
||||
{
|
||||
Enabled = basic?.Enabled ?? true,
|
||||
DefaultDiningMinutes = basic?.DefaultDiningMinutes ?? 90,
|
||||
OvertimeReminderMinutes = basic?.OvertimeReminderMinutes ?? 10
|
||||
},
|
||||
IsConfigured = isConfigured,
|
||||
BasicSettings = basic is null
|
||||
? null
|
||||
: new DineInBasicSettingsDto
|
||||
{
|
||||
Enabled = basic.Enabled,
|
||||
DefaultDiningMinutes = basic.DefaultDiningMinutes,
|
||||
OvertimeReminderMinutes = basic.OvertimeReminderMinutes
|
||||
},
|
||||
Areas = areas.Select(MapArea).ToList(),
|
||||
Tables = tables.Select(MapTable).ToList()
|
||||
});
|
||||
@@ -369,6 +373,10 @@ public sealed class StoreDineInController(
|
||||
.Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
|
||||
.OrderBy(x => x.TableCode)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (sourceBasic is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置堂食基础设置,无法复制");
|
||||
}
|
||||
|
||||
foreach (var targetStoreId in accessibleTargetIds)
|
||||
{
|
||||
@@ -383,9 +391,9 @@ public sealed class StoreDineInController(
|
||||
await dbContext.StoreDineInSettings.AddAsync(targetBasic, cancellationToken);
|
||||
}
|
||||
|
||||
targetBasic.Enabled = sourceBasic?.Enabled ?? true;
|
||||
targetBasic.DefaultDiningMinutes = sourceBasic?.DefaultDiningMinutes ?? 90;
|
||||
targetBasic.OvertimeReminderMinutes = sourceBasic?.OvertimeReminderMinutes ?? 10;
|
||||
targetBasic.Enabled = sourceBasic.Enabled;
|
||||
targetBasic.DefaultDiningMinutes = sourceBasic.DefaultDiningMinutes;
|
||||
targetBasic.OvertimeReminderMinutes = sourceBasic.OvertimeReminderMinutes;
|
||||
|
||||
var targetTables = await dbContext.StoreTables
|
||||
.Where(x => x.TenantId == tenantId && x.StoreId == targetStoreId)
|
||||
|
||||
@@ -9,6 +9,8 @@ using TakeoutSaaS.Application.App.Stores.Services;
|
||||
using TakeoutSaaS.Domain.Stores.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Enums;
|
||||
using TakeoutSaaS.Infrastructure.App.Persistence;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
using TakeoutSaaS.TenantApi.Contracts.Store;
|
||||
@@ -116,6 +118,10 @@ public sealed class StoreFeesController(
|
||||
var sourceFee = await dbContext.StoreFees
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == sourceStoreId, cancellationToken);
|
||||
if (sourceFee is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置费用设置,无法复制");
|
||||
}
|
||||
|
||||
var targetFees = await dbContext.StoreFees
|
||||
.Where(x => x.TenantId == tenantId && accessibleTargetIds.Contains(x.StoreId))
|
||||
@@ -133,17 +139,17 @@ public sealed class StoreFeesController(
|
||||
await dbContext.StoreFees.AddAsync(targetFee, cancellationToken);
|
||||
}
|
||||
|
||||
targetFee.MinimumOrderAmount = sourceFee?.MinimumOrderAmount ?? 0m;
|
||||
targetFee.BaseDeliveryFee = sourceFee?.BaseDeliveryFee ?? 0m;
|
||||
targetFee.FreeDeliveryThreshold = sourceFee?.FreeDeliveryThreshold;
|
||||
targetFee.PackagingFeeMode = sourceFee?.PackagingFeeMode ?? PackagingFeeMode.Fixed;
|
||||
targetFee.OrderPackagingFeeMode = sourceFee?.OrderPackagingFeeMode ?? OrderPackagingFeeMode.Fixed;
|
||||
targetFee.FixedPackagingFee = sourceFee?.FixedPackagingFee ?? 0m;
|
||||
targetFee.PackagingFeeTiersJson = sourceFee?.PackagingFeeTiersJson;
|
||||
targetFee.CutleryFeeEnabled = sourceFee?.CutleryFeeEnabled ?? false;
|
||||
targetFee.CutleryFeeAmount = sourceFee?.CutleryFeeAmount ?? 0m;
|
||||
targetFee.RushFeeEnabled = sourceFee?.RushFeeEnabled ?? false;
|
||||
targetFee.RushFeeAmount = sourceFee?.RushFeeAmount ?? 0m;
|
||||
targetFee.MinimumOrderAmount = sourceFee.MinimumOrderAmount;
|
||||
targetFee.BaseDeliveryFee = sourceFee.BaseDeliveryFee;
|
||||
targetFee.FreeDeliveryThreshold = sourceFee.FreeDeliveryThreshold;
|
||||
targetFee.PackagingFeeMode = sourceFee.PackagingFeeMode;
|
||||
targetFee.OrderPackagingFeeMode = sourceFee.OrderPackagingFeeMode;
|
||||
targetFee.FixedPackagingFee = sourceFee.FixedPackagingFee;
|
||||
targetFee.PackagingFeeTiersJson = sourceFee.PackagingFeeTiersJson;
|
||||
targetFee.CutleryFeeEnabled = sourceFee.CutleryFeeEnabled;
|
||||
targetFee.CutleryFeeAmount = sourceFee.CutleryFeeAmount;
|
||||
targetFee.RushFeeEnabled = sourceFee.RushFeeEnabled;
|
||||
targetFee.RushFeeAmount = sourceFee.RushFeeAmount;
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
@@ -171,6 +177,7 @@ public sealed class StoreFeesController(
|
||||
return new StoreFeesSettingsDto
|
||||
{
|
||||
StoreId = storeId.ToString(),
|
||||
IsConfigured = source is not null,
|
||||
MinimumOrderAmount = source?.MinimumOrderAmount ?? 0m,
|
||||
BaseDeliveryFee = source?.DeliveryFee ?? 0m,
|
||||
FreeDeliveryThreshold = source?.FreeDeliveryThreshold,
|
||||
@@ -196,9 +203,17 @@ public sealed class StoreFeesController(
|
||||
|
||||
private static PackagingFeeMode ParsePackagingFeeMode(string? value)
|
||||
{
|
||||
return string.Equals(value, "item", StringComparison.OrdinalIgnoreCase)
|
||||
? PackagingFeeMode.PerItem
|
||||
: PackagingFeeMode.Fixed;
|
||||
if (string.Equals(value, "item", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return PackagingFeeMode.PerItem;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "order", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return PackagingFeeMode.Fixed;
|
||||
}
|
||||
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "packagingFeeMode 非法");
|
||||
}
|
||||
|
||||
private static string ToPackagingFeeModeText(PackagingFeeMode value)
|
||||
@@ -208,9 +223,17 @@ public sealed class StoreFeesController(
|
||||
|
||||
private static OrderPackagingFeeMode ParseOrderPackagingFeeMode(string? value)
|
||||
{
|
||||
return string.Equals(value, "tiered", StringComparison.OrdinalIgnoreCase)
|
||||
? OrderPackagingFeeMode.Tiered
|
||||
: OrderPackagingFeeMode.Fixed;
|
||||
if (string.Equals(value, "tiered", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return OrderPackagingFeeMode.Tiered;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "fixed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return OrderPackagingFeeMode.Fixed;
|
||||
}
|
||||
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "orderPackagingFeeMode 非法");
|
||||
}
|
||||
|
||||
private static string ToOrderPackagingFeeModeText(OrderPackagingFeeMode value)
|
||||
|
||||
@@ -7,6 +7,8 @@ using TakeoutSaaS.Application.App.Stores.Services;
|
||||
using TakeoutSaaS.Domain.Stores.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Enums;
|
||||
using TakeoutSaaS.Infrastructure.App.Persistence;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
using TakeoutSaaS.TenantApi.Contracts.Store;
|
||||
@@ -46,18 +48,22 @@ public sealed class StorePickupController(
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var fineRule = ParseFineRule(setting?.FineRuleJson);
|
||||
var previewDays = BuildPreviewDays(fineRule);
|
||||
var previewDays = fineRule is null ? [] : BuildPreviewDays(fineRule);
|
||||
var isConfigured = setting is not null || slots.Count > 0;
|
||||
|
||||
var response = new StorePickupSettingsDto
|
||||
{
|
||||
StoreId = parsedStoreId.ToString(),
|
||||
Mode = StoreApiHelpers.ToPickupModeText(setting?.Mode ?? StorePickupMode.Big),
|
||||
BasicSettings = new PickupBasicSettingsDto
|
||||
{
|
||||
AllowSameDayPickup = setting?.AllowToday ?? true,
|
||||
BookingDays = setting?.AllowDaysAhead ?? 3,
|
||||
MaxItemsPerOrder = setting?.MaxQuantityPerOrder ?? 20
|
||||
},
|
||||
IsConfigured = isConfigured,
|
||||
Mode = setting is null ? null : StoreApiHelpers.ToPickupModeText(setting.Mode),
|
||||
BasicSettings = setting is null
|
||||
? null
|
||||
: new PickupBasicSettingsDto
|
||||
{
|
||||
AllowSameDayPickup = setting.AllowToday,
|
||||
BookingDays = setting.AllowDaysAhead,
|
||||
MaxItemsPerOrder = setting.MaxQuantityPerOrder
|
||||
},
|
||||
BigSlots = slots.Select(slot => new PickupSlotDto
|
||||
{
|
||||
Id = slot.Id.ToString(),
|
||||
@@ -211,6 +217,10 @@ public sealed class StorePickupController(
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (sourceSetting is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置自提设置,无法复制");
|
||||
}
|
||||
|
||||
var targetSettings = await dbContext.StorePickupSettings
|
||||
.Where(x => x.TenantId == tenantId && accessibleTargetIds.Contains(x.StoreId))
|
||||
@@ -229,12 +239,12 @@ public sealed class StorePickupController(
|
||||
await dbContext.StorePickupSettings.AddAsync(targetSetting, cancellationToken);
|
||||
}
|
||||
|
||||
targetSetting.AllowToday = sourceSetting?.AllowToday ?? true;
|
||||
targetSetting.AllowDaysAhead = sourceSetting?.AllowDaysAhead ?? 3;
|
||||
targetSetting.DefaultCutoffMinutes = sourceSetting?.DefaultCutoffMinutes ?? 30;
|
||||
targetSetting.MaxQuantityPerOrder = sourceSetting?.MaxQuantityPerOrder ?? 20;
|
||||
targetSetting.Mode = sourceSetting?.Mode ?? StorePickupMode.Big;
|
||||
targetSetting.FineRuleJson = sourceSetting?.FineRuleJson;
|
||||
targetSetting.AllowToday = sourceSetting.AllowToday;
|
||||
targetSetting.AllowDaysAhead = sourceSetting.AllowDaysAhead;
|
||||
targetSetting.DefaultCutoffMinutes = sourceSetting.DefaultCutoffMinutes;
|
||||
targetSetting.MaxQuantityPerOrder = sourceSetting.MaxQuantityPerOrder;
|
||||
targetSetting.Mode = sourceSetting.Mode;
|
||||
targetSetting.FineRuleJson = sourceSetting.FineRuleJson;
|
||||
targetSetting.RowVersion = CreateRowVersion();
|
||||
}
|
||||
|
||||
@@ -295,46 +305,35 @@ public sealed class StorePickupController(
|
||||
return RandomNumberGenerator.GetBytes(16);
|
||||
}
|
||||
|
||||
private static PickupFineRuleDto ParseFineRule(string? raw)
|
||||
private static PickupFineRuleDto? ParseFineRule(string? raw)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(raw))
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize<PickupFineRuleDto>(raw, StoreApiHelpers.JsonOptions);
|
||||
if (parsed is not null)
|
||||
{
|
||||
return NormalizeFineRule(parsed);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略反序列化异常,回落默认配置
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PickupFineRuleDto
|
||||
try
|
||||
{
|
||||
IntervalMinutes = 30,
|
||||
SlotCapacity = 5,
|
||||
DayStartTime = "09:00",
|
||||
DayEndTime = "20:30",
|
||||
MinAdvanceHours = 2,
|
||||
DayOfWeeks = [0, 1, 2, 3, 4, 5, 6]
|
||||
};
|
||||
var parsed = JsonSerializer.Deserialize<PickupFineRuleDto>(raw, StoreApiHelpers.JsonOptions);
|
||||
return parsed is null ? null : NormalizeFineRule(parsed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static PickupFineRuleDto NormalizeFineRule(PickupFineRuleDto source)
|
||||
{
|
||||
var start = string.IsNullOrWhiteSpace(source.DayStartTime) ? "09:00" : source.DayStartTime;
|
||||
var end = string.IsNullOrWhiteSpace(source.DayEndTime) ? "20:30" : source.DayEndTime;
|
||||
var start = StoreApiHelpers.ParseRequiredTime(source.DayStartTime, "fineRule.dayStartTime");
|
||||
var end = StoreApiHelpers.ParseRequiredTime(source.DayEndTime, "fineRule.dayEndTime");
|
||||
|
||||
return new PickupFineRuleDto
|
||||
{
|
||||
IntervalMinutes = Math.Clamp(source.IntervalMinutes, 5, 180),
|
||||
SlotCapacity = Math.Clamp(source.SlotCapacity, 1, 999),
|
||||
DayStartTime = StoreApiHelpers.ToHHmm(StoreApiHelpers.ParseRequiredTime(start, "fineRule.dayStartTime")),
|
||||
DayEndTime = StoreApiHelpers.ToHHmm(StoreApiHelpers.ParseRequiredTime(end, "fineRule.dayEndTime")),
|
||||
DayStartTime = StoreApiHelpers.ToHHmm(start),
|
||||
DayEndTime = StoreApiHelpers.ToHHmm(end),
|
||||
MinAdvanceHours = Math.Clamp(source.MinAdvanceHours, 0, 72),
|
||||
DayOfWeeks = (source.DayOfWeeks ?? [])
|
||||
.Distinct()
|
||||
|
||||
@@ -2,7 +2,6 @@ using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||
using TakeoutSaaS.Domain.Stores.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
@@ -31,25 +30,7 @@ public sealed class GetStoreFeeQueryHandler(
|
||||
|
||||
// 2. (空行后) 查询费用配置
|
||||
var fee = await storeRepository.GetStoreFeeAsync(request.StoreId, tenantId, cancellationToken);
|
||||
if (fee is null)
|
||||
{
|
||||
var fallback = new StoreFee
|
||||
{
|
||||
StoreId = request.StoreId,
|
||||
MinimumOrderAmount = 0m,
|
||||
BaseDeliveryFee = 0m,
|
||||
PackagingFeeMode = Domain.Stores.Enums.PackagingFeeMode.Fixed,
|
||||
OrderPackagingFeeMode = Domain.Stores.Enums.OrderPackagingFeeMode.Fixed,
|
||||
FixedPackagingFee = 0m,
|
||||
CutleryFeeEnabled = false,
|
||||
CutleryFeeAmount = 0m,
|
||||
RushFeeEnabled = false,
|
||||
RushFeeAmount = 0m
|
||||
};
|
||||
return StoreMapping.ToDto(fallback);
|
||||
}
|
||||
|
||||
// 3. (空行后) 返回结果
|
||||
return StoreMapping.ToDto(fee);
|
||||
return fee is null ? null : StoreMapping.ToDto(fee);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,29 +101,12 @@ public static class StoreListMapping
|
||||
result.Add(ServiceType.DineIn);
|
||||
}
|
||||
|
||||
// 2. 兜底默认展示配送服务
|
||||
if (result.Count == 0)
|
||||
{
|
||||
result.Add(ServiceType.Delivery);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ResolveAddress(Store store)
|
||||
{
|
||||
// 1. 地址优先返回业务地址字段
|
||||
if (!string.IsNullOrWhiteSpace(store.Address))
|
||||
{
|
||||
return store.Address;
|
||||
}
|
||||
|
||||
// 2. 兜底拼接省市区
|
||||
var parts = new[] { store.Province, store.City, store.District }
|
||||
.Where(static part => !string.IsNullOrWhiteSpace(part))
|
||||
.ToArray();
|
||||
|
||||
return parts.Length == 0 ? string.Empty : string.Join(string.Empty, parts);
|
||||
return string.IsNullOrWhiteSpace(store.Address) ? string.Empty : store.Address;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user