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