feat(marketing): add new customer gift backend module
This commit is contained in:
@@ -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>();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
9190
src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20260302065925_AddNewCustomerGiftModule.Designer.cs
generated
Normal file
9190
src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20260302065925_AddNewCustomerGiftModule.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user