feat(store): 扩展临时时段配置
This commit is contained in:
@@ -18,6 +18,7 @@ using TakeoutSaaS.Domain.Products.Entities;
|
||||
using TakeoutSaaS.Domain.Queues.Entities;
|
||||
using TakeoutSaaS.Domain.Reservations.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Infrastructure.Common.Persistence;
|
||||
@@ -959,8 +960,15 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.ToTable("store_holidays");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.Reason).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Date }).IsUnique();
|
||||
builder.Property(x => x.Date).IsRequired();
|
||||
builder.Property(x => x.EndDate);
|
||||
builder.Property(x => x.IsAllDay).HasDefaultValue(true);
|
||||
builder.Property(x => x.StartTime);
|
||||
builder.Property(x => x.EndTime);
|
||||
builder.Property(x => x.OverrideType).HasDefaultValue(OverrideType.Closed);
|
||||
builder.Property(x => x.IsClosed).HasDefaultValue(true);
|
||||
builder.Property(x => x.Reason).HasMaxLength(200);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Date });
|
||||
}
|
||||
|
||||
private static void ConfigureStoreDeliveryZone(EntityTypeBuilder<StoreDeliveryZone> builder)
|
||||
|
||||
@@ -39,15 +39,17 @@ public sealed class StoreSchedulerService(
|
||||
var holidays = await context.StoreHolidays
|
||||
.AsNoTracking()
|
||||
.Where(holiday => storeIds.Contains(holiday.StoreId)
|
||||
&& holiday.IsClosed
|
||||
&& holiday.Date.Date == today)
|
||||
&& holiday.Date <= today
|
||||
&& (holiday.EndDate == null || holiday.EndDate >= today))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// 3. (空行后) 构造查找表
|
||||
var hoursLookup = hours
|
||||
.GroupBy(hour => hour.StoreId)
|
||||
.ToDictionary(group => group.Key, group => (IReadOnlyList<StoreBusinessHour>)group.ToList());
|
||||
var holidaySet = holidays.Select(holiday => holiday.StoreId).ToHashSet();
|
||||
var holidayLookup = holidays
|
||||
.GroupBy(holiday => holiday.StoreId)
|
||||
.ToDictionary(group => group.Key, group => (IReadOnlyList<StoreHoliday>)group.ToList());
|
||||
|
||||
// 4. (空行后) 判定状态并更新
|
||||
var updated = 0;
|
||||
@@ -66,9 +68,33 @@ public sealed class StoreSchedulerService(
|
||||
}
|
||||
|
||||
// 4.3 (空行后) 计算营业状态
|
||||
var isHolidayClosed = holidaySet.Contains(store.Id);
|
||||
var storeHolidays = holidayLookup.TryGetValue(store.Id, out var matched) ? matched : [];
|
||||
var nowTime = now.TimeOfDay;
|
||||
var isHolidayClosed = storeHolidays.Any(holiday =>
|
||||
holiday.OverrideType == OverrideType.Closed && IsWithinHolidayTime(holiday, nowTime));
|
||||
var hasModifiedHours = storeHolidays.Any(holiday => holiday.OverrideType == OverrideType.ModifiedHours);
|
||||
var isModifiedOpen = hasModifiedHours && storeHolidays.Any(holiday =>
|
||||
holiday.OverrideType == OverrideType.ModifiedHours && IsWithinHolidayTime(holiday, nowTime));
|
||||
var isTemporaryOpen = storeHolidays.Any(holiday =>
|
||||
holiday.OverrideType == OverrideType.TemporaryOpen && IsWithinHolidayTime(holiday, nowTime));
|
||||
var hasHours = hoursLookup.TryGetValue(store.Id, out var storeHours) && storeHours.Count > 0;
|
||||
var isOpen = !isHolidayClosed && hasHours && IsWithinBusinessHours(storeHours ?? [], now);
|
||||
var isOpen = false;
|
||||
if (isHolidayClosed)
|
||||
{
|
||||
isOpen = false;
|
||||
}
|
||||
else if (hasModifiedHours)
|
||||
{
|
||||
isOpen = isModifiedOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
isOpen = hasHours && IsWithinBusinessHours(storeHours ?? [], now);
|
||||
if (!isOpen && isTemporaryOpen)
|
||||
{
|
||||
isOpen = true;
|
||||
}
|
||||
}
|
||||
if (isOpen)
|
||||
{
|
||||
if (store.BusinessStatus != StoreBusinessStatus.Open)
|
||||
@@ -191,6 +217,21 @@ public sealed class StoreSchedulerService(
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsWithinHolidayTime(StoreHoliday holiday, TimeSpan time)
|
||||
{
|
||||
if (holiday.IsAllDay)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!holiday.StartTime.HasValue || !holiday.EndTime.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return time >= holiday.StartTime.Value && time < holiday.EndTime.Value;
|
||||
}
|
||||
|
||||
private static DayOfWeek NextDay(DayOfWeek day)
|
||||
{
|
||||
var next = (int)day + 1;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ExtendStoreHolidayForTemporaryHours : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_store_holidays_TenantId_StoreId_Date",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.AlterTable(
|
||||
name: "store_holidays",
|
||||
comment: "门店临时时段配置(节假日/歇业/调整营业时间)。",
|
||||
oldComment: "门店休息日或特殊营业日。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Reason",
|
||||
table: "store_holidays",
|
||||
type: "character varying(200)",
|
||||
maxLength: 200,
|
||||
nullable: true,
|
||||
comment: "说明内容。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true,
|
||||
oldComment: "说明内容。");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsClosed",
|
||||
table: "store_holidays",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
comment: "是否闭店(兼容旧数据,新逻辑请用 OverrideType)。",
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldComment: "是否全天闭店。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "Date",
|
||||
table: "store_holidays",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
comment: "开始日期(原 Date 字段)。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldComment: "日期。");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "EndDate",
|
||||
table: "store_holidays",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "结束日期(可选,用于日期范围,如春节 1.28~2.4)。");
|
||||
|
||||
migrationBuilder.AddColumn<TimeSpan>(
|
||||
name: "EndTime",
|
||||
table: "store_holidays",
|
||||
type: "interval",
|
||||
nullable: true,
|
||||
comment: "结束时间(IsAllDay=false 时使用)。");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsAllDay",
|
||||
table: "store_holidays",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
comment: "是否全天生效。true=全天;false=仅 StartTime~EndTime 时段。");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "OverrideType",
|
||||
table: "store_holidays",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
comment: "覆盖类型(闭店/临时营业/调整时间)。");
|
||||
|
||||
migrationBuilder.AddColumn<TimeSpan>(
|
||||
name: "StartTime",
|
||||
table: "store_holidays",
|
||||
type: "interval",
|
||||
nullable: true,
|
||||
comment: "开始时间(IsAllDay=false 时使用)。");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_store_holidays_TenantId_StoreId_Date",
|
||||
table: "store_holidays",
|
||||
columns: new[] { "TenantId", "StoreId", "Date" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_store_holidays_TenantId_StoreId_Date",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EndDate",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EndTime",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsAllDay",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OverrideType",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StartTime",
|
||||
table: "store_holidays");
|
||||
|
||||
migrationBuilder.AlterTable(
|
||||
name: "store_holidays",
|
||||
comment: "门店休息日或特殊营业日。",
|
||||
oldComment: "门店临时时段配置(节假日/歇业/调整营业时间)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Reason",
|
||||
table: "store_holidays",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
comment: "说明内容。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(200)",
|
||||
oldMaxLength: 200,
|
||||
oldNullable: true,
|
||||
oldComment: "说明内容。");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsClosed",
|
||||
table: "store_holidays",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
comment: "是否全天闭店。",
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldDefaultValue: true,
|
||||
oldComment: "是否闭店(兼容旧数据,新逻辑请用 OverrideType)。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "Date",
|
||||
table: "store_holidays",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
comment: "日期。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldComment: "开始日期(原 Date 字段)。");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_store_holidays_TenantId_StoreId_Date",
|
||||
table: "store_holidays",
|
||||
columns: new[] { "TenantId", "StoreId", "Date" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5482,7 +5482,7 @@ namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("日期。");
|
||||
.HasComment("开始日期(原 Date 字段)。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -5492,15 +5492,41 @@ namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<bool>("IsClosed")
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("结束日期(可选,用于日期范围,如春节 1.28~2.4)。");
|
||||
|
||||
b.Property<TimeSpan?>("EndTime")
|
||||
.HasColumnType("interval")
|
||||
.HasComment("结束时间(IsAllDay=false 时使用)。");
|
||||
|
||||
b.Property<bool>("IsAllDay")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否全天闭店。");
|
||||
.HasDefaultValue(true)
|
||||
.HasComment("是否全天生效。true=全天;false=仅 StartTime~EndTime 时段。");
|
||||
|
||||
b.Property<bool>("IsClosed")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasComment("是否闭店(兼容旧数据,新逻辑请用 OverrideType)。");
|
||||
|
||||
b.Property<int>("OverrideType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasComment("覆盖类型(闭店/临时营业/调整时间)。");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasComment("说明内容。");
|
||||
|
||||
b.Property<TimeSpan?>("StartTime")
|
||||
.HasColumnType("interval")
|
||||
.HasComment("开始时间(IsAllDay=false 时使用)。");
|
||||
|
||||
b.Property<long>("StoreId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("门店标识。");
|
||||
@@ -5519,12 +5545,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId", "StoreId", "Date")
|
||||
.IsUnique();
|
||||
b.HasIndex("TenantId", "StoreId", "Date");
|
||||
|
||||
b.ToTable("store_holidays", null, t =>
|
||||
{
|
||||
t.HasComment("门店休息日或特殊营业日。");
|
||||
t.HasComment("门店临时时段配置(节假日/歇业/调整营业时间)。");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user