feat(marketing): add new customer gift backend module

This commit is contained in:
2026-03-02 15:58:06 +08:00
parent c9e2226b48
commit 6588c85f27
38 changed files with 12525 additions and 1 deletions

View File

@@ -46,6 +46,7 @@ public static class AppServiceCollectionExtensions
services.AddScoped<IStoreRepository, EfStoreRepository>();
services.AddScoped<IProductRepository, EfProductRepository>();
services.AddScoped<ICouponRepository, EfCouponRepository>();
services.AddScoped<INewCustomerGiftRepository, EfNewCustomerGiftRepository>();
services.AddScoped<IPromotionCampaignRepository, EfPromotionCampaignRepository>();
services.AddScoped<IOrderRepository, EfOrderRepository>();
services.AddScoped<IPaymentRepository, EfPaymentRepository>();

View File

@@ -354,6 +354,22 @@ public sealed class TakeoutAppDbContext(
/// </summary>
public DbSet<PromotionCampaign> PromotionCampaigns => Set<PromotionCampaign>();
/// <summary>
/// 新客有礼配置。
/// </summary>
public DbSet<NewCustomerGiftSetting> NewCustomerGiftSettings => Set<NewCustomerGiftSetting>();
/// <summary>
/// 新客有礼券规则。
/// </summary>
public DbSet<NewCustomerCouponRule> NewCustomerCouponRules => Set<NewCustomerCouponRule>();
/// <summary>
/// 新客邀请记录。
/// </summary>
public DbSet<NewCustomerInviteRecord> NewCustomerInviteRecords => Set<NewCustomerInviteRecord>();
/// <summary>
/// 新客成长记录。
/// </summary>
public DbSet<NewCustomerGrowthRecord> NewCustomerGrowthRecords => Set<NewCustomerGrowthRecord>();
/// <summary>
/// 会员档案。
/// </summary>
public DbSet<MemberProfile> MemberProfiles => Set<MemberProfile>();
@@ -520,6 +536,10 @@ public sealed class TakeoutAppDbContext(
ConfigureCouponTemplate(modelBuilder.Entity<CouponTemplate>());
ConfigureCoupon(modelBuilder.Entity<Coupon>());
ConfigurePromotionCampaign(modelBuilder.Entity<PromotionCampaign>());
ConfigureNewCustomerGiftSetting(modelBuilder.Entity<NewCustomerGiftSetting>());
ConfigureNewCustomerCouponRule(modelBuilder.Entity<NewCustomerCouponRule>());
ConfigureNewCustomerInviteRecord(modelBuilder.Entity<NewCustomerInviteRecord>());
ConfigureNewCustomerGrowthRecord(modelBuilder.Entity<NewCustomerGrowthRecord>());
ConfigureMemberProfile(modelBuilder.Entity<MemberProfile>());
ConfigureMemberTier(modelBuilder.Entity<MemberTier>());
ConfigureMemberPointLedger(modelBuilder.Entity<MemberPointLedger>());
@@ -1619,6 +1639,59 @@ public sealed class TakeoutAppDbContext(
builder.Property(x => x.BannerUrl).HasMaxLength(512);
}
private static void ConfigureNewCustomerGiftSetting(EntityTypeBuilder<NewCustomerGiftSetting> builder)
{
builder.ToTable("new_customer_gift_settings");
builder.HasKey(x => x.Id);
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.GiftType).HasConversion<int>();
builder.Property(x => x.DirectReduceAmount).HasPrecision(18, 2);
builder.Property(x => x.DirectMinimumSpend).HasPrecision(18, 2);
builder.Property(x => x.ShareChannelsJson).HasColumnType("text").IsRequired();
builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique();
}
private static void ConfigureNewCustomerCouponRule(EntityTypeBuilder<NewCustomerCouponRule> builder)
{
builder.ToTable("new_customer_coupon_rules");
builder.HasKey(x => x.Id);
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.Scene).HasConversion<int>();
builder.Property(x => x.CouponType).HasConversion<int>();
builder.Property(x => x.Value).HasPrecision(18, 2);
builder.Property(x => x.MinimumSpend).HasPrecision(18, 2);
builder.Property(x => x.ValidDays).IsRequired();
builder.Property(x => x.SortOrder).IsRequired();
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Scene, x.SortOrder });
}
private static void ConfigureNewCustomerInviteRecord(EntityTypeBuilder<NewCustomerInviteRecord> builder)
{
builder.ToTable("new_customer_invite_records");
builder.HasKey(x => x.Id);
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.InviterName).HasMaxLength(64).IsRequired();
builder.Property(x => x.InviteeName).HasMaxLength(64).IsRequired();
builder.Property(x => x.InviteTime).IsRequired();
builder.Property(x => x.OrderStatus).HasConversion<int>();
builder.Property(x => x.RewardStatus).HasConversion<int>();
builder.Property(x => x.SourceChannel).HasMaxLength(32);
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.InviteTime });
}
private static void ConfigureNewCustomerGrowthRecord(EntityTypeBuilder<NewCustomerGrowthRecord> builder)
{
builder.ToTable("new_customer_growth_records");
builder.HasKey(x => x.Id);
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.CustomerKey).HasMaxLength(64).IsRequired();
builder.Property(x => x.CustomerName).HasMaxLength(64);
builder.Property(x => x.RegisteredAt).IsRequired();
builder.Property(x => x.SourceChannel).HasMaxLength(32);
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.CustomerKey }).IsUnique();
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RegisteredAt });
}
private static void ConfigureMemberProfile(EntityTypeBuilder<MemberProfile> builder)
{
builder.ToTable("member_profiles");

View File

@@ -0,0 +1,200 @@
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Coupons.Entities;
using TakeoutSaaS.Domain.Coupons.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 新客有礼仓储 EF Core 实现。
/// </summary>
public sealed class EfNewCustomerGiftRepository(TakeoutAppDbContext context) : INewCustomerGiftRepository
{
/// <inheritdoc />
public Task<NewCustomerGiftSetting?> FindSettingByStoreIdAsync(
long tenantId,
long storeId,
CancellationToken cancellationToken = default)
{
return context.NewCustomerGiftSettings
.Where(item => item.TenantId == tenantId && item.StoreId == storeId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task AddSettingAsync(NewCustomerGiftSetting entity, CancellationToken cancellationToken = default)
{
return context.NewCustomerGiftSettings.AddAsync(entity, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task UpdateSettingAsync(NewCustomerGiftSetting entity, CancellationToken cancellationToken = default)
{
context.NewCustomerGiftSettings.Update(entity);
return Task.CompletedTask;
}
/// <inheritdoc />
public async Task<IReadOnlyList<NewCustomerCouponRule>> GetCouponRulesByStoreIdAsync(
long tenantId,
long storeId,
CancellationToken cancellationToken = default)
{
return await context.NewCustomerCouponRules
.AsNoTracking()
.Where(item => item.TenantId == tenantId && item.StoreId == storeId)
.OrderBy(item => item.Scene)
.ThenBy(item => item.SortOrder)
.ThenBy(item => item.Id)
.ToListAsync(cancellationToken);
}
/// <inheritdoc />
public async Task ReplaceCouponRulesAsync(
long tenantId,
long storeId,
IReadOnlyCollection<NewCustomerCouponRule> entities,
CancellationToken cancellationToken = default)
{
var existing = await context.NewCustomerCouponRules
.Where(item => item.TenantId == tenantId && item.StoreId == storeId)
.ToListAsync(cancellationToken);
if (existing.Count > 0)
{
context.NewCustomerCouponRules.RemoveRange(existing);
}
if (entities.Count > 0)
{
await context.NewCustomerCouponRules.AddRangeAsync(entities, cancellationToken);
}
}
/// <inheritdoc />
public async Task<(IReadOnlyList<NewCustomerInviteRecord> Items, int TotalCount)> GetInviteRecordsAsync(
long tenantId,
long storeId,
int page,
int pageSize,
CancellationToken cancellationToken = default)
{
var normalizedPage = Math.Max(1, page);
var normalizedPageSize = Math.Clamp(pageSize, 1, 200);
var query = context.NewCustomerInviteRecords
.AsNoTracking()
.Where(item => item.TenantId == tenantId && item.StoreId == storeId);
var totalCount = await query.CountAsync(cancellationToken);
if (totalCount == 0)
{
return ([], 0);
}
var items = await query
.OrderByDescending(item => item.InviteTime)
.ThenByDescending(item => item.Id)
.Skip((normalizedPage - 1) * normalizedPageSize)
.Take(normalizedPageSize)
.ToListAsync(cancellationToken);
return (items, totalCount);
}
/// <inheritdoc />
public Task AddInviteRecordAsync(NewCustomerInviteRecord entity, CancellationToken cancellationToken = default)
{
return context.NewCustomerInviteRecords.AddAsync(entity, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task<NewCustomerGrowthRecord?> FindGrowthRecordByCustomerKeyAsync(
long tenantId,
long storeId,
string customerKey,
CancellationToken cancellationToken = default)
{
return context.NewCustomerGrowthRecords
.Where(item =>
item.TenantId == tenantId &&
item.StoreId == storeId &&
item.CustomerKey == customerKey)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task AddGrowthRecordAsync(NewCustomerGrowthRecord entity, CancellationToken cancellationToken = default)
{
return context.NewCustomerGrowthRecords.AddAsync(entity, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task UpdateGrowthRecordAsync(NewCustomerGrowthRecord entity, CancellationToken cancellationToken = default)
{
context.NewCustomerGrowthRecords.Update(entity);
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<int> CountRegisteredCustomersAsync(
long tenantId,
long storeId,
DateTime startAt,
DateTime endAt,
CancellationToken cancellationToken = default)
{
return context.NewCustomerGrowthRecords
.AsNoTracking()
.Where(item =>
item.TenantId == tenantId &&
item.StoreId == storeId &&
item.RegisteredAt >= startAt &&
item.RegisteredAt < endAt)
.CountAsync(cancellationToken);
}
/// <inheritdoc />
public Task<int> CountGiftClaimedCustomersAsync(
long tenantId,
long storeId,
DateTime startAt,
DateTime endAt,
CancellationToken cancellationToken = default)
{
return context.NewCustomerGrowthRecords
.AsNoTracking()
.Where(item =>
item.TenantId == tenantId &&
item.StoreId == storeId &&
item.RegisteredAt >= startAt &&
item.RegisteredAt < endAt &&
item.GiftClaimedAt != null)
.CountAsync(cancellationToken);
}
/// <inheritdoc />
public Task<int> CountFirstOrderedCustomersAsync(
long tenantId,
long storeId,
DateTime startAt,
DateTime endAt,
CancellationToken cancellationToken = default)
{
return context.NewCustomerGrowthRecords
.AsNoTracking()
.Where(item =>
item.TenantId == tenantId &&
item.StoreId == storeId &&
item.RegisteredAt >= startAt &&
item.RegisteredAt < endAt &&
item.FirstOrderAt != null)
.CountAsync(cancellationToken);
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,168 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddNewCustomerGiftModule : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "new_customer_coupon_rules",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StoreId = table.Column<long>(type: "bigint", nullable: false, comment: "门店 ID。"),
Scene = table.Column<int>(type: "integer", nullable: false, comment: "券规则场景。"),
CouponType = table.Column<int>(type: "integer", nullable: false, comment: "券类型。"),
Value = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true, comment: "面值或折扣值。"),
MinimumSpend = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true, comment: "使用门槛。"),
ValidDays = table.Column<int>(type: "integer", nullable: false, comment: "有效期天数。"),
SortOrder = table.Column<int>(type: "integer", nullable: false, comment: "排序值(同场景内递增)。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_new_customer_coupon_rules", x => x.Id);
},
comment: "新客有礼券规则。");
migrationBuilder.CreateTable(
name: "new_customer_gift_settings",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StoreId = table.Column<long>(type: "bigint", nullable: false, comment: "门店 ID。"),
GiftEnabled = table.Column<bool>(type: "boolean", nullable: false, comment: "是否开启新客礼包。"),
GiftType = table.Column<int>(type: "integer", nullable: false, comment: "礼包类型。"),
DirectReduceAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true, comment: "首单直减金额。"),
DirectMinimumSpend = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true, comment: "首单直减门槛金额。"),
InviteEnabled = table.Column<bool>(type: "boolean", nullable: false, comment: "是否开启老带新分享。"),
ShareChannelsJson = table.Column<string>(type: "text", nullable: false, comment: "分享渠道JSON。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_new_customer_gift_settings", x => x.Id);
},
comment: "新客有礼门店配置。");
migrationBuilder.CreateTable(
name: "new_customer_growth_records",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StoreId = table.Column<long>(type: "bigint", nullable: false, comment: "门店 ID。"),
CustomerKey = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "顾客业务唯一键。"),
CustomerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true, comment: "顾客展示名。"),
RegisteredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "注册时间。"),
GiftClaimedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "礼包领取时间。"),
FirstOrderAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "首单时间。"),
SourceChannel = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true, comment: "渠道来源。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_new_customer_growth_records", x => x.Id);
},
comment: "新客成长记录。");
migrationBuilder.CreateTable(
name: "new_customer_invite_records",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
StoreId = table.Column<long>(type: "bigint", nullable: false, comment: "门店 ID。"),
InviterName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "邀请人展示名。"),
InviteeName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "被邀请人展示名。"),
InviteTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "邀请时间。"),
OrderStatus = table.Column<int>(type: "integer", nullable: false, comment: "订单状态。"),
RewardStatus = table.Column<int>(type: "integer", nullable: false, comment: "奖励状态。"),
RewardIssuedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "奖励发放时间。"),
SourceChannel = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true, comment: "邀请来源渠道。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_new_customer_invite_records", x => x.Id);
},
comment: "新客邀请记录。");
migrationBuilder.CreateIndex(
name: "IX_new_customer_coupon_rules_TenantId_StoreId_Scene_SortOrder",
table: "new_customer_coupon_rules",
columns: new[] { "TenantId", "StoreId", "Scene", "SortOrder" });
migrationBuilder.CreateIndex(
name: "IX_new_customer_gift_settings_TenantId_StoreId",
table: "new_customer_gift_settings",
columns: new[] { "TenantId", "StoreId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_new_customer_growth_records_TenantId_StoreId_CustomerKey",
table: "new_customer_growth_records",
columns: new[] { "TenantId", "StoreId", "CustomerKey" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_new_customer_growth_records_TenantId_StoreId_RegisteredAt",
table: "new_customer_growth_records",
columns: new[] { "TenantId", "StoreId", "RegisteredAt" });
migrationBuilder.CreateIndex(
name: "IX_new_customer_invite_records_TenantId_StoreId_InviteTime",
table: "new_customer_invite_records",
columns: new[] { "TenantId", "StoreId", "InviteTime" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "new_customer_coupon_rules");
migrationBuilder.DropTable(
name: "new_customer_gift_settings");
migrationBuilder.DropTable(
name: "new_customer_growth_records");
migrationBuilder.DropTable(
name: "new_customer_invite_records");
}
}
}

View File

@@ -604,6 +604,328 @@ namespace TakeoutSaaS.Infrastructure.Migrations
});
});
modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.NewCustomerCouponRule", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<int>("CouponType")
.HasColumnType("integer")
.HasComment("券类型。");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<decimal?>("MinimumSpend")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)")
.HasComment("使用门槛。");
b.Property<int>("Scene")
.HasColumnType("integer")
.HasComment("券规则场景。");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasComment("排序值(同场景内递增)。");
b.Property<long>("StoreId")
.HasColumnType("bigint")
.HasComment("门店 ID。");
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.Property<int>("ValidDays")
.HasColumnType("integer")
.HasComment("有效期天数。");
b.Property<decimal?>("Value")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)")
.HasComment("面值或折扣值。");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId", "Scene", "SortOrder");
b.ToTable("new_customer_coupon_rules", null, t =>
{
t.HasComment("新客有礼券规则。");
});
});
modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.NewCustomerGiftSetting", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<decimal?>("DirectMinimumSpend")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)")
.HasComment("首单直减门槛金额。");
b.Property<decimal?>("DirectReduceAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)")
.HasComment("首单直减金额。");
b.Property<bool>("GiftEnabled")
.HasColumnType("boolean")
.HasComment("是否开启新客礼包。");
b.Property<int>("GiftType")
.HasColumnType("integer")
.HasComment("礼包类型。");
b.Property<bool>("InviteEnabled")
.HasColumnType("boolean")
.HasComment("是否开启老带新分享。");
b.Property<string>("ShareChannelsJson")
.IsRequired()
.HasColumnType("text")
.HasComment("分享渠道JSON。");
b.Property<long>("StoreId")
.HasColumnType("bigint")
.HasComment("门店 ID。");
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId")
.IsUnique();
b.ToTable("new_customer_gift_settings", null, t =>
{
t.HasComment("新客有礼门店配置。");
});
});
modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.NewCustomerGrowthRecord", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<string>("CustomerKey")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasComment("顾客业务唯一键。");
b.Property<string>("CustomerName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasComment("顾客展示名。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<DateTime?>("FirstOrderAt")
.HasColumnType("timestamp with time zone")
.HasComment("首单时间。");
b.Property<DateTime?>("GiftClaimedAt")
.HasColumnType("timestamp with time zone")
.HasComment("礼包领取时间。");
b.Property<DateTime>("RegisteredAt")
.HasColumnType("timestamp with time zone")
.HasComment("注册时间。");
b.Property<string>("SourceChannel")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasComment("渠道来源。");
b.Property<long>("StoreId")
.HasColumnType("bigint")
.HasComment("门店 ID。");
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId", "CustomerKey")
.IsUnique();
b.HasIndex("TenantId", "StoreId", "RegisteredAt");
b.ToTable("new_customer_growth_records", null, t =>
{
t.HasComment("新客成长记录。");
});
});
modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.NewCustomerInviteRecord", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<DateTime>("InviteTime")
.HasColumnType("timestamp with time zone")
.HasComment("邀请时间。");
b.Property<string>("InviteeName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasComment("被邀请人展示名。");
b.Property<string>("InviterName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasComment("邀请人展示名。");
b.Property<int>("OrderStatus")
.HasColumnType("integer")
.HasComment("订单状态。");
b.Property<DateTime?>("RewardIssuedAt")
.HasColumnType("timestamp with time zone")
.HasComment("奖励发放时间。");
b.Property<int>("RewardStatus")
.HasColumnType("integer")
.HasComment("奖励状态。");
b.Property<string>("SourceChannel")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasComment("邀请来源渠道。");
b.Property<long>("StoreId")
.HasColumnType("bigint")
.HasComment("门店 ID。");
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId", "InviteTime");
b.ToTable("new_customer_invite_records", null, t =>
{
t.HasComment("新客邀请记录。");
});
});
modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.PromotionCampaign", b =>
{
b.Property<long>("Id")