diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs
index 896c090..660eab2 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs
@@ -1,10 +1,11 @@
using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
+using TakeoutSaaS.Domain.Stores.Enums;
namespace TakeoutSaaS.Application.App.Stores.Commands;
///
-/// 创建节假日配置命令。
+/// 创建临时时段配置命令。
///
public sealed record CreateStoreHolidayCommand : IRequest
{
@@ -14,14 +15,34 @@ public sealed record CreateStoreHolidayCommand : IRequest
public long StoreId { get; init; }
///
- /// 日期。
+ /// 开始日期。
///
public DateTime Date { get; init; }
///
- /// 是否闭店。
+ /// 结束日期(可选)。
///
- public bool IsClosed { get; init; } = true;
+ public DateTime? EndDate { get; init; }
+
+ ///
+ /// 是否全天。
+ ///
+ public bool IsAllDay { get; init; } = true;
+
+ ///
+ /// 开始时间(IsAllDay=false 时必填)。
+ ///
+ public TimeSpan? StartTime { get; init; }
+
+ ///
+ /// 结束时间(IsAllDay=false 时必填)。
+ ///
+ public TimeSpan? EndTime { get; init; }
+
+ ///
+ /// 覆盖类型。
+ ///
+ public OverrideType OverrideType { get; init; } = OverrideType.Closed;
///
/// 说明。
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs
index c228bb1..4552a67 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs
@@ -1,10 +1,11 @@
using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
+using TakeoutSaaS.Domain.Stores.Enums;
namespace TakeoutSaaS.Application.App.Stores.Commands;
///
-/// 更新节假日配置命令。
+/// 更新临时时段配置命令。
///
public sealed record UpdateStoreHolidayCommand : IRequest
{
@@ -19,14 +20,34 @@ public sealed record UpdateStoreHolidayCommand : IRequest
public long StoreId { get; init; }
///
- /// 日期。
+ /// 开始日期。
///
public DateTime Date { get; init; }
///
- /// 是否闭店。
+ /// 结束日期(可选)。
///
- public bool IsClosed { get; init; } = true;
+ public DateTime? EndDate { get; init; }
+
+ ///
+ /// 是否全天。
+ ///
+ public bool IsAllDay { get; init; } = true;
+
+ ///
+ /// 开始时间(IsAllDay=false 时必填)。
+ ///
+ public TimeSpan? StartTime { get; init; }
+
+ ///
+ /// 结束时间(IsAllDay=false 时必填)。
+ ///
+ public TimeSpan? EndTime { get; init; }
+
+ ///
+ /// 覆盖类型。
+ ///
+ public OverrideType OverrideType { get; init; } = OverrideType.Closed;
///
/// 说明。
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs
index a861b85..44ede7c 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs
@@ -1,10 +1,11 @@
using System.Text.Json.Serialization;
+using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Shared.Abstractions.Serialization;
namespace TakeoutSaaS.Application.App.Stores.Dto;
///
-/// 门店节假日 DTO。
+/// 门店临时时段 DTO。
///
public sealed record StoreHolidayDto
{
@@ -27,12 +28,37 @@ public sealed record StoreHolidayDto
public long StoreId { get; init; }
///
- /// 日期。
+ /// 开始日期。
///
public DateTime Date { get; init; }
///
- /// 是否闭店。
+ /// 结束日期(可选)。
+ ///
+ public DateTime? EndDate { get; init; }
+
+ ///
+ /// 是否全天。
+ ///
+ public bool IsAllDay { get; init; }
+
+ ///
+ /// 开始时间。
+ ///
+ public TimeSpan? StartTime { get; init; }
+
+ ///
+ /// 结束时间。
+ ///
+ public TimeSpan? EndTime { get; init; }
+
+ ///
+ /// 覆盖类型。
+ ///
+ public OverrideType OverrideType { get; init; }
+
+ ///
+ /// 是否闭店(兼容)。
///
public bool IsClosed { get; init; }
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs
index f5f5692..09fa43a 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Domain.Stores.Entities;
+using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
@@ -39,7 +40,12 @@ public sealed class CreateStoreHolidayCommandHandler(
{
StoreId = request.StoreId,
Date = request.Date,
- IsClosed = request.IsClosed,
+ EndDate = request.EndDate,
+ IsAllDay = request.IsAllDay,
+ StartTime = request.StartTime,
+ EndTime = request.EndTime,
+ OverrideType = request.OverrideType,
+ IsClosed = request.OverrideType == OverrideType.Closed,
Reason = request.Reason?.Trim()
};
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs
index 878eaeb..558180d 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Domain.Stores.Entities;
+using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
@@ -42,7 +43,12 @@ public sealed class UpdateStoreHolidayCommandHandler(
// 3. 更新字段
existing.Date = request.Date;
- existing.IsClosed = request.IsClosed;
+ existing.EndDate = request.EndDate;
+ existing.IsAllDay = request.IsAllDay;
+ existing.StartTime = request.StartTime;
+ existing.EndTime = request.EndTime;
+ existing.OverrideType = request.OverrideType;
+ existing.IsClosed = request.OverrideType == OverrideType.Closed;
existing.Reason = request.Reason?.Trim();
// 4. 持久化
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs b/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs
index fbd09c3..29b77df 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs
@@ -150,6 +150,11 @@ public static class StoreMapping
TenantId = holiday.TenantId,
StoreId = holiday.StoreId,
Date = holiday.Date,
+ EndDate = holiday.EndDate,
+ IsAllDay = holiday.IsAllDay,
+ StartTime = holiday.StartTime,
+ EndTime = holiday.EndTime,
+ OverrideType = holiday.OverrideType,
IsClosed = holiday.IsClosed,
Reason = holiday.Reason,
CreatedAt = holiday.CreatedAt
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs
index b6c140f..6bb463e 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs
@@ -15,6 +15,27 @@ public sealed class CreateStoreHolidayCommandValidator : AbstractValidator x.StoreId).GreaterThan(0);
RuleFor(x => x.Date).NotEmpty();
- RuleFor(x => x.Reason).MaximumLength(256);
+
+ RuleFor(x => x.EndDate)
+ .GreaterThanOrEqualTo(x => x.Date)
+ .When(x => x.EndDate.HasValue)
+ .WithMessage("结束日期不能早于开始日期");
+
+ RuleFor(x => x.StartTime)
+ .NotNull()
+ .When(x => !x.IsAllDay)
+ .WithMessage("非全天模式下必须填写开始时间");
+
+ RuleFor(x => x.EndTime)
+ .NotNull()
+ .When(x => !x.IsAllDay)
+ .WithMessage("非全天模式下必须填写结束时间");
+
+ RuleFor(x => x.EndTime)
+ .GreaterThan(x => x.StartTime)
+ .When(x => !x.IsAllDay && x.StartTime.HasValue && x.EndTime.HasValue)
+ .WithMessage("结束时间必须晚于开始时间");
+
+ RuleFor(x => x.Reason).MaximumLength(200);
}
}
diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs
index 4567bef..cc36913 100644
--- a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs
+++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs
@@ -16,6 +16,27 @@ public sealed class UpdateStoreHolidayCommandValidator : AbstractValidator x.HolidayId).GreaterThan(0);
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.Date).NotEmpty();
- RuleFor(x => x.Reason).MaximumLength(256);
+
+ RuleFor(x => x.EndDate)
+ .GreaterThanOrEqualTo(x => x.Date)
+ .When(x => x.EndDate.HasValue)
+ .WithMessage("结束日期不能早于开始日期");
+
+ RuleFor(x => x.StartTime)
+ .NotNull()
+ .When(x => !x.IsAllDay)
+ .WithMessage("非全天模式下必须填写开始时间");
+
+ RuleFor(x => x.EndTime)
+ .NotNull()
+ .When(x => !x.IsAllDay)
+ .WithMessage("非全天模式下必须填写结束时间");
+
+ RuleFor(x => x.EndTime)
+ .GreaterThan(x => x.StartTime)
+ .When(x => !x.IsAllDay && x.StartTime.HasValue && x.EndTime.HasValue)
+ .WithMessage("结束时间必须晚于开始时间");
+
+ RuleFor(x => x.Reason).MaximumLength(200);
}
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs
index e10edce..d694c23 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs
@@ -1,9 +1,10 @@
+using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Stores.Entities;
///
-/// 门店休息日或特殊营业日。
+/// 门店临时时段配置(节假日/歇业/调整营业时间)。
///
public sealed class StoreHoliday : MultiTenantEntityBase
{
@@ -13,12 +14,37 @@ public sealed class StoreHoliday : MultiTenantEntityBase
public long StoreId { get; set; }
///
- /// 日期。
+ /// 开始日期(原 Date 字段)。
///
public DateTime Date { get; set; }
///
- /// 是否全天闭店。
+ /// 结束日期(可选,用于日期范围,如春节 1.28~2.4)。
+ ///
+ public DateTime? EndDate { get; set; }
+
+ ///
+ /// 是否全天生效。true=全天;false=仅 StartTime~EndTime 时段。
+ ///
+ public bool IsAllDay { get; set; } = true;
+
+ ///
+ /// 开始时间(IsAllDay=false 时使用)。
+ ///
+ public TimeSpan? StartTime { get; set; }
+
+ ///
+ /// 结束时间(IsAllDay=false 时使用)。
+ ///
+ public TimeSpan? EndTime { get; set; }
+
+ ///
+ /// 覆盖类型(闭店/临时营业/调整时间)。
+ ///
+ public OverrideType OverrideType { get; set; } = OverrideType.Closed;
+
+ ///
+ /// 是否闭店(兼容旧数据,新逻辑请用 OverrideType)。
///
public bool IsClosed { get; set; } = true;
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Enums/OverrideType.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/OverrideType.cs
new file mode 100644
index 0000000..1af8665
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/OverrideType.cs
@@ -0,0 +1,22 @@
+namespace TakeoutSaaS.Domain.Stores.Enums;
+
+///
+/// 临时时段覆盖类型。
+///
+public enum OverrideType
+{
+ ///
+ /// 闭店(歇业)——覆盖常规营业时段,该时间段不营业。
+ ///
+ Closed = 0,
+
+ ///
+ /// 临时营业——覆盖常规休息日,使门店临时营业。
+ ///
+ TemporaryOpen = 1,
+
+ ///
+ /// 调整营业时间——覆盖常规时段,使用自定义营业时间。
+ ///
+ ModifiedHours = 2
+}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs
index 98bba1b..1e9722c 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs
@@ -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 builder)
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/StoreSchedulerService.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/StoreSchedulerService.cs
index 8ca2432..7a183ff 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/StoreSchedulerService.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/StoreSchedulerService.cs
@@ -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)group.ToList());
- var holidaySet = holidays.Select(holiday => holiday.StoreId).ToHashSet();
+ var holidayLookup = holidays
+ .GroupBy(holiday => holiday.StoreId)
+ .ToDictionary(group => group.Key, group => (IReadOnlyList)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;
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20260120090508_ExtendStoreHolidayForTemporaryHours.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20260120090508_ExtendStoreHolidayForTemporaryHours.Designer.cs
new file mode 100644
index 0000000..1301696
--- /dev/null
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20260120090508_ExtendStoreHolidayForTemporaryHours.Designer.cs
@@ -0,0 +1,7416 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using TakeoutSaaS.Infrastructure.App.Persistence;
+
+#nullable disable
+
+namespace TakeoutSaaS.Infrastructure.Migrations
+{
+ [DbContext(typeof(TakeoutAppDbContext))]
+ [Migration("20260120090508_ExtendStoreHolidayForTemporaryHours")]
+ partial class ExtendStoreHolidayForTemporaryHours
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricAlertRule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ConditionJson")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasComment("触发条件 JSON。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean")
+ .HasComment("是否启用。");
+
+ b.Property("MetricDefinitionId")
+ .HasColumnType("bigint")
+ .HasComment("关联指标。");
+
+ b.Property("NotificationChannels")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("通知渠道。");
+
+ b.Property("Severity")
+ .HasColumnType("integer")
+ .HasComment("告警级别。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "MetricDefinitionId", "Severity");
+
+ b.ToTable("metric_alert_rules", null, t =>
+ {
+ t.HasComment("指标告警规则。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("指标编码。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DefaultAggregation")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("默认聚合方式。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Description")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasComment("说明。");
+
+ b.Property("DimensionsJson")
+ .HasColumnType("text")
+ .HasComment("维度描述 JSON。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("指标名称。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "Code")
+ .IsUnique();
+
+ b.ToTable("metric_definitions", null, t =>
+ {
+ t.HasComment("指标定义,描述可观测的数据点。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricSnapshot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("DimensionKey")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("维度键(JSON)。");
+
+ b.Property("MetricDefinitionId")
+ .HasColumnType("bigint")
+ .HasComment("指标定义 ID。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("Value")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)")
+ .HasComment("数值。");
+
+ b.Property("WindowEnd")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("统计时间窗口结束。");
+
+ b.Property("WindowStart")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("统计时间窗口开始。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "MetricDefinitionId", "DimensionKey", "WindowStart", "WindowEnd")
+ .IsUnique();
+
+ b.ToTable("metric_snapshots", null, t =>
+ {
+ t.HasComment("指标快照,用于大盘展示。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.Coupon", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("券码或序列号。");
+
+ b.Property("CouponTemplateId")
+ .HasColumnType("bigint")
+ .HasComment("模板标识。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("ExpireAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("到期时间。");
+
+ b.Property("IssuedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("发放时间。");
+
+ b.Property("OrderId")
+ .HasColumnType("bigint")
+ .HasComment("订单 ID(已使用时记录)。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("状态。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("UsedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("使用时间。");
+
+ b.Property("UserId")
+ .HasColumnType("bigint")
+ .HasComment("归属用户。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "Code")
+ .IsUnique();
+
+ b.ToTable("coupons", null, t =>
+ {
+ t.HasComment("用户领取的券。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.CouponTemplate", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AllowStack")
+ .HasColumnType("boolean")
+ .HasComment("是否允许叠加其他优惠。");
+
+ b.Property("ChannelsJson")
+ .HasColumnType("text")
+ .HasComment("发放渠道(JSON)。");
+
+ b.Property("ClaimedQuantity")
+ .HasColumnType("integer")
+ .HasComment("已领取数量。");
+
+ b.Property("CouponType")
+ .HasColumnType("integer")
+ .HasComment("券类型。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Description")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasComment("备注。");
+
+ b.Property("DiscountCap")
+ .HasColumnType("numeric")
+ .HasComment("折扣上限(针对折扣券)。");
+
+ b.Property("MinimumSpend")
+ .HasColumnType("numeric")
+ .HasComment("最低消费门槛。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("模板名称。");
+
+ b.Property("ProductScopeJson")
+ .HasColumnType("text")
+ .HasComment("适用品类或商品范围(JSON)。");
+
+ b.Property("RelativeValidDays")
+ .HasColumnType("integer")
+ .HasComment("有效天数(相对发放时间)。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("状态。");
+
+ b.Property("StoreScopeJson")
+ .HasColumnType("text")
+ .HasComment("适用门店 ID 集合(JSON)。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("TotalQuantity")
+ .HasColumnType("integer")
+ .HasComment("总发放数量上限。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("ValidFrom")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("可用开始时间。");
+
+ b.Property("ValidTo")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("可用结束时间。");
+
+ b.Property("Value")
+ .HasColumnType("numeric")
+ .HasComment("面值或折扣额度。");
+
+ b.HasKey("Id");
+
+ b.ToTable("coupon_templates", null, t =>
+ {
+ t.HasComment("优惠券模板。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.PromotionCampaign", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AudienceDescription")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasComment("目标人群描述。");
+
+ b.Property("BannerUrl")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasComment("营销素材(如 banner)。");
+
+ b.Property("Budget")
+ .HasColumnType("numeric")
+ .HasComment("预算金额。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("EndAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("结束时间。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("活动名称。");
+
+ b.Property("PromotionType")
+ .HasColumnType("integer")
+ .HasComment("活动类型。");
+
+ b.Property("RulesJson")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasComment("活动规则 JSON。");
+
+ b.Property("StartAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("开始时间。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("活动状态。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.ToTable("promotion_campaigns", null, t =>
+ {
+ t.HasComment("营销活动配置。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.ChatMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChatSessionId")
+ .HasColumnType("bigint")
+ .HasComment("会话标识。");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasComment("消息内容。");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("消息类型(文字/图片/语音等)。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("IsRead")
+ .HasColumnType("boolean")
+ .HasComment("是否已读。");
+
+ b.Property("ReadAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("读取时间。");
+
+ b.Property("SenderType")
+ .HasColumnType("integer")
+ .HasComment("发送方类型。");
+
+ b.Property("SenderUserId")
+ .HasColumnType("bigint")
+ .HasComment("发送方用户 ID。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "ChatSessionId", "CreatedAt");
+
+ b.ToTable("chat_messages", null, t =>
+ {
+ t.HasComment("会话消息。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.ChatSession", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AgentUserId")
+ .HasColumnType("bigint")
+ .HasComment("当前客服员工 ID。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("CustomerUserId")
+ .HasColumnType("bigint")
+ .HasComment("顾客用户 ID。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("EndedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("结束时间。");
+
+ b.Property("IsBotActive")
+ .HasColumnType("boolean")
+ .HasComment("是否机器人接待中。");
+
+ b.Property("SessionCode")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("会话编号。");
+
+ b.Property("StartedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("开始时间。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("会话状态。");
+
+ b.Property("StoreId")
+ .HasColumnType("bigint")
+ .HasComment("所属门店(可空为平台)。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "SessionCode")
+ .IsUnique();
+
+ b.ToTable("chat_sessions", null, t =>
+ {
+ t.HasComment("客服会话。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.SupportTicket", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AssignedAgentId")
+ .HasColumnType("bigint")
+ .HasComment("指派的客服。");
+
+ b.Property("ClosedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("关闭时间。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("CustomerUserId")
+ .HasColumnType("bigint")
+ .HasComment("客户用户 ID。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasComment("工单详情。");
+
+ b.Property("OrderId")
+ .HasColumnType("bigint")
+ .HasComment("关联订单(如有)。");
+
+ b.Property("Priority")
+ .HasColumnType("integer")
+ .HasComment("优先级。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("状态。");
+
+ b.Property("Subject")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("工单主题。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("TicketNo")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("工单编号。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "TicketNo")
+ .IsUnique();
+
+ b.ToTable("support_tickets", null, t =>
+ {
+ t.HasComment("客服工单。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.TicketComment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AttachmentsJson")
+ .HasColumnType("text")
+ .HasComment("附件 JSON。");
+
+ b.Property("AuthorUserId")
+ .HasColumnType("bigint")
+ .HasComment("评论人 ID。");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasComment("评论内容。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("IsInternal")
+ .HasColumnType("boolean")
+ .HasComment("是否内部备注。");
+
+ b.Property("SupportTicketId")
+ .HasColumnType("bigint")
+ .HasComment("工单标识。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "SupportTicketId");
+
+ b.ToTable("ticket_comments", null, t =>
+ {
+ t.HasComment("工单评论/流转记录。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryEvent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("DeliveryOrderId")
+ .HasColumnType("bigint")
+ .HasComment("配送单标识。");
+
+ b.Property("EventType")
+ .HasColumnType("integer")
+ .HasComment("事件类型。");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("事件描述。");
+
+ b.Property("OccurredAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("发生时间。");
+
+ b.Property("Payload")
+ .HasColumnType("text")
+ .HasComment("原始数据 JSON。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "DeliveryOrderId", "EventType");
+
+ b.ToTable("delivery_events", null, t =>
+ {
+ t.HasComment("配送状态事件流水。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CourierName")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("骑手姓名。");
+
+ b.Property("CourierPhone")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("骑手电话。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("DeliveredAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("完成时间。");
+
+ b.Property("DeliveryFee")
+ .HasPrecision(18, 2)
+ .HasColumnType("numeric(18,2)")
+ .HasComment("配送费。");
+
+ b.Property("DispatchedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("下发时间。");
+
+ b.Property("FailureReason")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("异常原因。");
+
+ b.Property("OrderId")
+ .HasColumnType("bigint")
+ .HasComment("获取或设置关联订单 ID。");
+
+ b.Property("PickedUpAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("取餐时间。");
+
+ b.Property("Provider")
+ .HasColumnType("integer")
+ .HasComment("配送服务商。");
+
+ b.Property("ProviderOrderId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("第三方配送单号。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("状态。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "OrderId")
+ .IsUnique();
+
+ b.ToTable("delivery_orders", null, t =>
+ {
+ t.HasComment("配送单。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Distribution.Entities.AffiliateOrder", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AffiliatePartnerId")
+ .HasColumnType("bigint")
+ .HasComment("推广人标识。");
+
+ b.Property("BuyerUserId")
+ .HasColumnType("bigint")
+ .HasComment("用户 ID。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("EstimatedCommission")
+ .HasPrecision(18, 2)
+ .HasColumnType("numeric(18,2)")
+ .HasComment("预计佣金。");
+
+ b.Property("OrderAmount")
+ .HasPrecision(18, 2)
+ .HasColumnType("numeric(18,2)")
+ .HasComment("订单金额。");
+
+ b.Property("OrderId")
+ .HasColumnType("bigint")
+ .HasComment("关联订单。");
+
+ b.Property("SettledAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("结算完成时间。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("当前状态。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "AffiliatePartnerId", "OrderId")
+ .IsUnique();
+
+ b.ToTable("affiliate_orders", null, t =>
+ {
+ t.HasComment("分销订单记录。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Distribution.Entities.AffiliatePartner", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChannelType")
+ .HasColumnType("integer")
+ .HasComment("渠道类型。");
+
+ b.Property("CommissionRate")
+ .HasColumnType("numeric")
+ .HasComment("分成比例(0-1)。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("昵称或渠道名称。");
+
+ b.Property("Phone")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("联系电话。");
+
+ b.Property("Remarks")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("审核备注。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("当前状态。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("UserId")
+ .HasColumnType("bigint")
+ .HasComment("用户 ID(如绑定平台账号)。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId", "DisplayName");
+
+ b.ToTable("affiliate_partners", null, t =>
+ {
+ t.HasComment("分销/推广合作伙伴。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Distribution.Entities.AffiliatePayout", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AffiliatePartnerId")
+ .HasColumnType("bigint")
+ .HasComment("合作伙伴标识。");
+
+ b.Property("Amount")
+ .HasPrecision(18, 2)
+ .HasColumnType("numeric(18,2)")
+ .HasComment("结算金额。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("PaidAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("打款时间。");
+
+ b.Property("Period")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("结算周期描述。");
+
+ b.Property