feat: migrate snowflake ids and refresh migrations

This commit is contained in:
2025-12-02 09:04:37 +08:00
parent 462e15abbb
commit 148475fa43
174 changed files with 8020 additions and 34278 deletions

View File

@@ -0,0 +1,48 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TakeoutSaaS.Domain.Deliveries.Repositories;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Orders.Repositories;
using TakeoutSaaS.Domain.Payments.Repositories;
using TakeoutSaaS.Domain.Products.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Infrastructure.App.Options;
using TakeoutSaaS.Infrastructure.App.Persistence;
using TakeoutSaaS.Infrastructure.App.Repositories;
using TakeoutSaaS.Infrastructure.Common.Extensions;
using TakeoutSaaS.Shared.Abstractions.Constants;
namespace TakeoutSaaS.Infrastructure.App.Extensions;
/// <summary>
/// 业务主库基础设施注册扩展。
/// </summary>
public static class AppServiceCollectionExtensions
{
/// <summary>
/// 注册业务主库 DbContext 与仓储。
/// </summary>
/// <param name="services">服务集合。</param>
/// <param name="configuration">配置源。</param>
/// <returns>服务集合。</returns>
public static IServiceCollection AddAppInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDatabaseInfrastructure(configuration);
services.AddPostgresDbContext<TakeoutAppDbContext>(DatabaseConstants.AppDataSource);
services.AddScoped<IMerchantRepository, EfMerchantRepository>();
services.AddScoped<IStoreRepository, EfStoreRepository>();
services.AddScoped<IProductRepository, EfProductRepository>();
services.AddScoped<IOrderRepository, EfOrderRepository>();
services.AddScoped<IPaymentRepository, EfPaymentRepository>();
services.AddScoped<IDeliveryRepository, EfDeliveryRepository>();
services.AddOptions<AppSeedOptions>()
.Bind(configuration.GetSection(AppSeedOptions.SectionName))
.ValidateDataAnnotations();
services.AddHostedService<AppDataSeeder>();
return services;
}
}

View File

@@ -1,949 +0,0 @@
// <auto-generated />
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.App.Migrations
{
[DbContext(typeof(TakeoutAppDbContext))]
[Migration("20251201044927_InitialApp")]
partial class InitialApp
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CourierName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CourierPhone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeliveredAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("DeliveryFee")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("DispatchedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FailureReason")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime?>("PickedUpAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<string>("ProviderOrderId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "OrderId")
.IsUnique();
b.ToTable("delivery_orders", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.Merchant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Address")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("BrandAlias")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("BrandName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("BusinessLicenseNumber")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("City")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ContactEmail")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ContactPhone")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("District")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("LegalPerson")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime?>("OnboardedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Province")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ReviewRemarks")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId");
b.ToTable("merchants", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.Order", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CancelReason")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Channel")
.HasColumnType("integer");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("CustomerName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CustomerPhone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int>("DeliveryType")
.HasColumnType("integer");
b.Property<decimal>("DiscountAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("FinishedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("ItemsAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<string>("OrderNo")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<decimal>("PaidAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("PaidAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("PayableAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<int>("PaymentStatus")
.HasColumnType("integer");
b.Property<string>("QueueNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("Remark")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<Guid?>("ReservationId")
.HasColumnType("uuid");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<string>("TableNo")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "OrderNo")
.IsUnique();
b.HasIndex("TenantId", "StoreId", "Status");
b.ToTable("orders", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AttributesJson")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<decimal>("DiscountAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<Guid>("ProductId")
.HasColumnType("uuid");
b.Property<string>("ProductName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<int>("Quantity")
.HasColumnType("integer");
b.Property<string>("SkuName")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<decimal>("SubTotal")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("Unit")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<decimal>("UnitPrice")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("OrderId");
b.HasIndex("TenantId", "OrderId");
b.ToTable("order_items", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRecord", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<string>("ChannelTransactionId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int>("Method")
.HasColumnType("integer");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime?>("PaidAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Payload")
.HasColumnType("text");
b.Property<string>("Remark")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("TradeNo")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "OrderId");
b.ToTable("payment_records", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CoverImage")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<bool>("EnableDelivery")
.HasColumnType("boolean");
b.Property<bool>("EnableDineIn")
.HasColumnType("boolean");
b.Property<bool>("EnablePickup")
.HasColumnType("boolean");
b.Property<string>("GalleryImages")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.Property<bool>("IsFeatured")
.HasColumnType("boolean");
b.Property<int?>("MaxQuantityPerOrder")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<decimal?>("OriginalPrice")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<decimal>("Price")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<string>("SpuCode")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int?>("StockQuantity")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<string>("Subtitle")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("Unit")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "SpuCode")
.IsUnique();
b.HasIndex("TenantId", "StoreId");
b.ToTable("products", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductCategory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("SortOrder")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId");
b.ToTable("product_categories", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Queues.Entities.QueueTicket", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime?>("CalledAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int?>("EstimatedWaitMinutes")
.HasColumnType("integer");
b.Property<DateTime?>("ExpiredAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("PartySize")
.HasColumnType("integer");
b.Property<string>("Remark")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("TicketNumber")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId");
b.HasIndex("TenantId", "StoreId", "TicketNumber")
.IsUnique();
b.ToTable("queue_tickets", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Reservations.Entities.Reservation", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CheckInCode")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("CheckedInAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("CustomerName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CustomerPhone")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int>("PeopleCount")
.HasColumnType("integer");
b.Property<string>("Remark")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("ReservationNo")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("ReservationTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<string>("TablePreference")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "ReservationNo")
.IsUnique();
b.HasIndex("TenantId", "StoreId");
b.ToTable("reservations", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Address")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Announcement")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("BusinessHours")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("City")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<decimal>("DeliveryRadiusKm")
.HasPrecision(6, 2)
.HasColumnType("numeric(6,2)");
b.Property<string>("District")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<double?>("Latitude")
.HasColumnType("double precision");
b.Property<double?>("Longitude")
.HasColumnType("double precision");
b.Property<string>("ManagerName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<Guid>("MerchantId")
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Phone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("Province")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<bool>("QueueEnabled")
.HasColumnType("boolean");
b.Property<bool>("ReservationEnabled")
.HasColumnType("boolean");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<bool>("SupportsDelivery")
.HasColumnType("boolean");
b.Property<bool>("SupportsDineIn")
.HasColumnType("boolean");
b.Property<bool>("SupportsPickup")
.HasColumnType("boolean");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("MerchantId");
b.HasIndex("TenantId", "Code")
.IsUnique();
b.HasIndex("TenantId", "MerchantId");
b.ToTable("stores", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.Tenant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ContactEmail")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ContactName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ContactPhone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("EffectiveFrom")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("EffectiveTo")
.HasColumnType("timestamp with time zone");
b.Property<string>("Industry")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("LogoUrl")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Remarks")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("ShortName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.ToTable("tenants", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b =>
{
b.HasOne("TakeoutSaaS.Domain.Orders.Entities.Order", null)
.WithMany()
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b =>
{
b.HasOne("TakeoutSaaS.Domain.Merchants.Entities.Merchant", "Merchant")
.WithMany()
.HasForeignKey("MerchantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Merchant");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,497 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.App.Migrations
{
/// <inheritdoc />
public partial class InitialApp : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "delivery_orders",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
Provider = table.Column<int>(type: "integer", nullable: false),
ProviderOrderId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
DeliveryFee = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
CourierName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
CourierPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
DispatchedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
PickedUpAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeliveredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
FailureReason = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_delivery_orders", x => x.Id);
});
migrationBuilder.CreateTable(
name: "merchants",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
BrandName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
BrandAlias = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
LegalPerson = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
BusinessLicenseNumber = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ContactPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
ContactEmail = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Province = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
City = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
District = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Address = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
ReviewRemarks = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
OnboardedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_merchants", x => x.Id);
});
migrationBuilder.CreateTable(
name: "orders",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderNo = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
Channel = table.Column<int>(type: "integer", nullable: false),
DeliveryType = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
PaymentStatus = table.Column<int>(type: "integer", nullable: false),
CustomerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
CustomerPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
TableNo = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
QueueNumber = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ReservationId = table.Column<Guid>(type: "uuid", nullable: true),
ItemsAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
DiscountAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
PayableAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
PaidAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
PaidAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
FinishedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelReason = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Remark = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_orders", x => x.Id);
});
migrationBuilder.CreateTable(
name: "payment_records",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
Method = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
TradeNo = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ChannelTransactionId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
PaidAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Remark = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Payload = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_payment_records", x => x.Id);
});
migrationBuilder.CreateTable(
name: "product_categories",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Description = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
SortOrder = table.Column<int>(type: "integer", nullable: false),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_product_categories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "products",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
CategoryId = table.Column<Guid>(type: "uuid", nullable: false),
SpuCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Subtitle = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Unit = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true),
Price = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
OriginalPrice = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
StockQuantity = table.Column<int>(type: "integer", nullable: true),
MaxQuantityPerOrder = table.Column<int>(type: "integer", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
CoverImage = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
GalleryImages = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
Description = table.Column<string>(type: "text", nullable: true),
EnableDineIn = table.Column<bool>(type: "boolean", nullable: false),
EnablePickup = table.Column<bool>(type: "boolean", nullable: false),
EnableDelivery = table.Column<bool>(type: "boolean", nullable: false),
IsFeatured = table.Column<bool>(type: "boolean", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_products", x => x.Id);
});
migrationBuilder.CreateTable(
name: "queue_tickets",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
TicketNumber = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
PartySize = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
EstimatedWaitMinutes = table.Column<int>(type: "integer", nullable: true),
CalledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
ExpiredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Remark = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_queue_tickets", x => x.Id);
});
migrationBuilder.CreateTable(
name: "reservations",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
ReservationNo = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
CustomerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
CustomerPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
PeopleCount = table.Column<int>(type: "integer", nullable: false),
ReservationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
TablePreference = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Remark = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CheckInCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
CheckedInAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_reservations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "tenants",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
ShortName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ContactName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ContactPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ContactEmail = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Industry = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
LogoUrl = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
EffectiveFrom = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
EffectiveTo = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
Remarks = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_tenants", x => x.Id);
});
migrationBuilder.CreateTable(
name: "stores",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
MerchantId = table.Column<Guid>(type: "uuid", nullable: false),
Code = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Phone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ManagerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
Province = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
City = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
District = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Address = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Longitude = table.Column<double>(type: "double precision", nullable: true),
Latitude = table.Column<double>(type: "double precision", nullable: true),
BusinessHours = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
SupportsDineIn = table.Column<bool>(type: "boolean", nullable: false),
SupportsPickup = table.Column<bool>(type: "boolean", nullable: false),
SupportsDelivery = table.Column<bool>(type: "boolean", nullable: false),
DeliveryRadiusKm = table.Column<decimal>(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false),
QueueEnabled = table.Column<bool>(type: "boolean", nullable: false),
ReservationEnabled = table.Column<bool>(type: "boolean", nullable: false),
Announcement = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_stores", x => x.Id);
table.ForeignKey(
name: "FK_stores_merchants_MerchantId",
column: x => x.MerchantId,
principalTable: "merchants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "order_items",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
ProductId = table.Column<Guid>(type: "uuid", nullable: false),
ProductName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
SkuName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Unit = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true),
Quantity = table.Column<int>(type: "integer", nullable: false),
UnitPrice = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
DiscountAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
SubTotal = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
AttributesJson = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_order_items", x => x.Id);
table.ForeignKey(
name: "FK_order_items_orders_OrderId",
column: x => x.OrderId,
principalTable: "orders",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_delivery_orders_TenantId_OrderId",
table: "delivery_orders",
columns: new[] { "TenantId", "OrderId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_merchants_TenantId",
table: "merchants",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_order_items_OrderId",
table: "order_items",
column: "OrderId");
migrationBuilder.CreateIndex(
name: "IX_order_items_TenantId_OrderId",
table: "order_items",
columns: new[] { "TenantId", "OrderId" });
migrationBuilder.CreateIndex(
name: "IX_orders_TenantId_OrderNo",
table: "orders",
columns: new[] { "TenantId", "OrderNo" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_orders_TenantId_StoreId_Status",
table: "orders",
columns: new[] { "TenantId", "StoreId", "Status" });
migrationBuilder.CreateIndex(
name: "IX_payment_records_TenantId_OrderId",
table: "payment_records",
columns: new[] { "TenantId", "OrderId" });
migrationBuilder.CreateIndex(
name: "IX_product_categories_TenantId_StoreId",
table: "product_categories",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_products_TenantId_SpuCode",
table: "products",
columns: new[] { "TenantId", "SpuCode" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_products_TenantId_StoreId",
table: "products",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_queue_tickets_TenantId_StoreId",
table: "queue_tickets",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_queue_tickets_TenantId_StoreId_TicketNumber",
table: "queue_tickets",
columns: new[] { "TenantId", "StoreId", "TicketNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_reservations_TenantId_ReservationNo",
table: "reservations",
columns: new[] { "TenantId", "ReservationNo" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_reservations_TenantId_StoreId",
table: "reservations",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_stores_MerchantId",
table: "stores",
column: "MerchantId");
migrationBuilder.CreateIndex(
name: "IX_stores_TenantId_Code",
table: "stores",
columns: new[] { "TenantId", "Code" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_stores_TenantId_MerchantId",
table: "stores",
columns: new[] { "TenantId", "MerchantId" });
migrationBuilder.CreateIndex(
name: "IX_tenants_Code",
table: "tenants",
column: "Code",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "delivery_orders");
migrationBuilder.DropTable(
name: "order_items");
migrationBuilder.DropTable(
name: "payment_records");
migrationBuilder.DropTable(
name: "product_categories");
migrationBuilder.DropTable(
name: "products");
migrationBuilder.DropTable(
name: "queue_tickets");
migrationBuilder.DropTable(
name: "reservations");
migrationBuilder.DropTable(
name: "stores");
migrationBuilder.DropTable(
name: "tenants");
migrationBuilder.DropTable(
name: "orders");
migrationBuilder.DropTable(
name: "merchants");
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 业务数据种子配置。
/// </summary>
public sealed class AppSeedOptions
{
/// <summary>
/// 配置节名称。
/// </summary>
public const string SectionName = "App:Seed";
/// <summary>
/// 是否启用业务数据种子。
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// 默认租户配置。
/// </summary>
public TenantSeedOptions? DefaultTenant { get; set; }
/// <summary>
/// 基础字典分组。
/// </summary>
public List<DictionarySeedGroupOptions> DictionaryGroups { get; set; } = new();
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Domain.Dictionary.Enums;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 字典分组种子配置。
/// </summary>
public sealed class DictionarySeedGroupOptions
{
/// <summary>
/// 所属租户,不填则使用默认租户或系统租户。
/// </summary>
public long? TenantId { get; set; }
/// <summary>
/// 分组编码。
/// </summary>
[Required]
[MaxLength(64)]
public string Code { get; set; } = string.Empty;
/// <summary>
/// 分组名称。
/// </summary>
[Required]
[MaxLength(128)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 分组作用域。
/// </summary>
public DictionaryScope Scope { get; set; } = DictionaryScope.Business;
/// <summary>
/// 描述信息。
/// </summary>
[MaxLength(512)]
public string? Description { get; set; }
/// <summary>
/// 是否启用。
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 字典项集合。
/// </summary>
public List<DictionarySeedItemOptions> Items { get; set; } = new();
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 字典项种子配置。
/// </summary>
public sealed class DictionarySeedItemOptions
{
/// <summary>
/// 字典项键。
/// </summary>
[Required]
[MaxLength(64)]
public string Key { get; set; } = string.Empty;
/// <summary>
/// 字典项值。
/// </summary>
[Required]
[MaxLength(256)]
public string Value { get; set; } = string.Empty;
/// <summary>
/// 描述。
/// </summary>
[MaxLength(512)]
public string? Description { get; set; }
/// <summary>
/// 排序。
/// </summary>
public int SortOrder { get; set; } = 100;
/// <summary>
/// 是否启用。
/// </summary>
public bool IsEnabled { get; set; } = true;
}

View File

@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 默认租户种子配置。
/// </summary>
public sealed class TenantSeedOptions
{
/// <summary>
/// 自定义租户标识,不填则自动生成。
/// </summary>
public long TenantId { get; set; }
/// <summary>
/// 租户编码。
/// </summary>
[Required]
[MaxLength(64)]
public string Code { get; set; } = string.Empty;
/// <summary>
/// 租户名称。
/// </summary>
[Required]
[MaxLength(128)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 租户简称。
/// </summary>
[MaxLength(128)]
public string? ShortName { get; set; }
/// <summary>
/// 联系人姓名。
/// </summary>
[MaxLength(64)]
public string? ContactName { get; set; }
/// <summary>
/// 联系电话。
/// </summary>
[MaxLength(32)]
public string? ContactPhone { get; set; }
}

View File

@@ -0,0 +1,301 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TakeoutSaaS.Domain.Dictionary.Entities;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Infrastructure.App.Options;
using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Persistence;
/// <summary>
/// 业务数据种子,确保默认租户与基础字典可重复执行。
/// </summary>
public sealed class AppDataSeeder : IHostedService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<AppDataSeeder> _logger;
private readonly AppSeedOptions _options;
/// <summary>
/// 初始化种子服务。
/// </summary>
public AppDataSeeder(
IServiceProvider serviceProvider,
ILogger<AppDataSeeder> logger,
IOptions<AppSeedOptions> options)
{
_serviceProvider = serviceProvider;
_logger = logger;
_options = options.Value;
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_options.Enabled)
{
_logger.LogInformation("AppSeed 未启用,跳过业务数据初始化");
return;
}
using var scope = _serviceProvider.CreateScope();
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
_logger.LogInformation("AppSeed 完成业务数据初始化");
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>
/// 确保默认租户存在。
/// </summary>
private async Task<long?> EnsureDefaultTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
{
var tenantOptions = _options.DefaultTenant;
if (tenantOptions == null || string.IsNullOrWhiteSpace(tenantOptions.Code) || string.IsNullOrWhiteSpace(tenantOptions.Name))
{
_logger.LogInformation("AppSeed 未配置默认租户,跳过租户种子");
return null;
}
var code = tenantOptions.Code.Trim();
var existingTenant = await dbContext.Tenants
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.Code == code, cancellationToken);
if (existingTenant == null)
{
var tenant = new Tenant
{
Id = tenantOptions.TenantId,
Code = code,
Name = tenantOptions.Name.Trim(),
ShortName = tenantOptions.ShortName?.Trim(),
ContactName = tenantOptions.ContactName?.Trim(),
ContactPhone = tenantOptions.ContactPhone?.Trim(),
Status = TenantStatus.Active
};
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("AppSeed 已创建默认租户 {TenantCode}", code);
return tenant.Id;
}
var updated = false;
if (!string.Equals(existingTenant.Name, tenantOptions.Name, StringComparison.Ordinal))
{
existingTenant.Name = tenantOptions.Name.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ShortName, tenantOptions.ShortName, StringComparison.Ordinal))
{
existingTenant.ShortName = tenantOptions.ShortName?.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ContactName, tenantOptions.ContactName, StringComparison.Ordinal))
{
existingTenant.ContactName = tenantOptions.ContactName?.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ContactPhone, tenantOptions.ContactPhone, StringComparison.Ordinal))
{
existingTenant.ContactPhone = tenantOptions.ContactPhone?.Trim();
updated = true;
}
if (existingTenant.Status != TenantStatus.Active)
{
existingTenant.Status = TenantStatus.Active;
updated = true;
}
if (updated)
{
dbContext.Tenants.Update(existingTenant);
await dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("AppSeed 已更新默认租户 {TenantCode}", code);
}
else
{
_logger.LogInformation("AppSeed 默认租户 {TenantCode} 已存在且无需更新", code);
}
return existingTenant.Id;
}
/// <summary>
/// 确保基础字典存在。
/// </summary>
private async Task EnsureDictionarySeedsAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken)
{
if (_options.DictionaryGroups == null || _options.DictionaryGroups.Count == 0)
{
_logger.LogInformation("AppSeed 未配置基础字典,跳过字典种子");
return;
}
foreach (var groupOptions in _options.DictionaryGroups)
{
if (string.IsNullOrWhiteSpace(groupOptions.Code) || string.IsNullOrWhiteSpace(groupOptions.Name))
{
_logger.LogWarning("AppSeed 跳过字典分组Code 或 Name 为空");
continue;
}
var tenantId = groupOptions.TenantId ?? defaultTenantId ?? 0;
var code = groupOptions.Code.Trim();
var group = await dbContext.DictionaryGroups
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Code == code, cancellationToken);
if (group == null)
{
group = new DictionaryGroup
{
Id = 0,
TenantId = tenantId,
Code = code,
Name = groupOptions.Name.Trim(),
Scope = groupOptions.Scope,
Description = groupOptions.Description?.Trim(),
IsEnabled = groupOptions.IsEnabled
};
await dbContext.DictionaryGroups.AddAsync(group, cancellationToken);
_logger.LogInformation("AppSeed 创建字典分组 {GroupCode} (Tenant: {TenantId})", code, tenantId);
}
else
{
var groupUpdated = false;
if (!string.Equals(group.Name, groupOptions.Name, StringComparison.Ordinal))
{
group.Name = groupOptions.Name.Trim();
groupUpdated = true;
}
if (!string.Equals(group.Description, groupOptions.Description, StringComparison.Ordinal))
{
group.Description = groupOptions.Description?.Trim();
groupUpdated = true;
}
if (group.Scope != groupOptions.Scope)
{
group.Scope = groupOptions.Scope;
groupUpdated = true;
}
if (group.IsEnabled != groupOptions.IsEnabled)
{
group.IsEnabled = groupOptions.IsEnabled;
groupUpdated = true;
}
if (groupUpdated)
{
dbContext.DictionaryGroups.Update(group);
}
}
await UpsertDictionaryItemsAsync(dbContext, group, groupOptions.Items, tenantId, cancellationToken);
}
await dbContext.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// 合并字典项。
/// </summary>
private static async Task UpsertDictionaryItemsAsync(
DictionaryDbContext dbContext,
DictionaryGroup group,
IEnumerable<DictionarySeedItemOptions> seedItems,
long tenantId,
CancellationToken cancellationToken)
{
var materializedItems = seedItems
.Where(item => !string.IsNullOrWhiteSpace(item.Key) && !string.IsNullOrWhiteSpace(item.Value))
.ToList();
if (materializedItems.Count == 0)
{
return;
}
var existingItems = await dbContext.DictionaryItems
.IgnoreQueryFilters()
.Where(x => x.GroupId == group.Id)
.ToListAsync(cancellationToken);
foreach (var seed in materializedItems)
{
var key = seed.Key.Trim();
var existing = existingItems.FirstOrDefault(x => x.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
var newItem = new DictionaryItem
{
Id = 0,
TenantId = tenantId,
GroupId = group.Id,
Key = key,
Value = seed.Value.Trim(),
Description = seed.Description?.Trim(),
SortOrder = seed.SortOrder,
IsEnabled = seed.IsEnabled
};
await dbContext.DictionaryItems.AddAsync(newItem, cancellationToken);
continue;
}
var updated = false;
if (!string.Equals(existing.Value, seed.Value, StringComparison.Ordinal))
{
existing.Value = seed.Value.Trim();
updated = true;
}
if (!string.Equals(existing.Description, seed.Description, StringComparison.Ordinal))
{
existing.Description = seed.Description?.Trim();
updated = true;
}
if (existing.SortOrder != seed.SortOrder)
{
existing.SortOrder = seed.SortOrder;
updated = true;
}
if (existing.IsEnabled != seed.IsEnabled)
{
existing.IsEnabled = seed.IsEnabled;
updated = true;
}
if (updated)
{
dbContext.DictionaryItems.Update(existing);
}
}
}
}

View File

@@ -20,6 +20,7 @@ using TakeoutSaaS.Domain.Reservations.Entities;
using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Infrastructure.Common.Persistence;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
@@ -31,8 +32,9 @@ namespace TakeoutSaaS.Infrastructure.App.Persistence;
public sealed class TakeoutAppDbContext(
DbContextOptions<TakeoutAppDbContext> options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor)
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator)
{
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<TenantPackage> TenantPackages => Set<TenantPackage>();

View File

@@ -0,0 +1,71 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Deliveries.Entities;
using TakeoutSaaS.Domain.Deliveries.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 配送聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfDeliveryRepository : IDeliveryRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfDeliveryRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<DeliveryOrder?> FindByIdAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.DeliveryOrders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == deliveryOrderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<DeliveryOrder?> FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.DeliveryOrders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<DeliveryEvent>> GetEventsAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default)
{
var events = await _context.DeliveryEvents
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.DeliveryOrderId == deliveryOrderId)
.OrderBy(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return events;
}
/// <inheritdoc />
public Task AddDeliveryOrderAsync(DeliveryOrder deliveryOrder, CancellationToken cancellationToken = default)
{
return _context.DeliveryOrders.AddAsync(deliveryOrder, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddEventAsync(DeliveryEvent deliveryEvent, CancellationToken cancellationToken = default)
{
return _context.DeliveryEvents.AddAsync(deliveryEvent, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,116 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Merchants.Enums;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 商户聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfMerchantRepository : IMerchantRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfMerchantRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Merchant?> FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Merchants
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == merchantId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Merchant>> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default)
{
var query = _context.Merchants
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
return await query
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantStaff>> GetStaffAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
var staffs = await _context.MerchantStaff
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.MerchantId == merchantId)
.OrderBy(x => x.Name)
.ToListAsync(cancellationToken);
return staffs;
}
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantContract>> GetContractsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
var contracts = await _context.MerchantContracts
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.MerchantId == merchantId)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return contracts;
}
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantDocument>> GetDocumentsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
var documents = await _context.MerchantDocuments
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.MerchantId == merchantId)
.OrderBy(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return documents;
}
/// <inheritdoc />
public Task AddMerchantAsync(Merchant merchant, CancellationToken cancellationToken = default)
{
return _context.Merchants.AddAsync(merchant, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddStaffAsync(MerchantStaff staff, CancellationToken cancellationToken = default)
{
return _context.MerchantStaff.AddAsync(staff, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddContractAsync(MerchantContract contract, CancellationToken cancellationToken = default)
{
return _context.MerchantContracts.AddAsync(contract, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddDocumentAsync(MerchantDocument document, CancellationToken cancellationToken = default)
{
return _context.MerchantDocuments.AddAsync(document, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,133 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Orders.Entities;
using TakeoutSaaS.Domain.Orders.Enums;
using TakeoutSaaS.Domain.Orders.Repositories;
using TakeoutSaaS.Domain.Payments.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 订单聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfOrderRepository : IOrderRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfOrderRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Order?> FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Orders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == orderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<Order?> FindByOrderNoAsync(string orderNo, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Orders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderNo == orderNo)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Order>> SearchAsync(long tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default)
{
var query = _context.Orders
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
if (paymentStatus.HasValue)
{
query = query.Where(x => x.PaymentStatus == paymentStatus.Value);
}
var orders = await query
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return orders;
}
/// <inheritdoc />
public async Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
var items = await _context.OrderItems
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderBy(x => x.Id)
.ToListAsync(cancellationToken);
return items;
}
/// <inheritdoc />
public async Task<IReadOnlyList<OrderStatusHistory>> GetStatusHistoryAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
var histories = await _context.OrderStatusHistories
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderBy(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return histories;
}
/// <inheritdoc />
public async Task<IReadOnlyList<RefundRequest>> GetRefundsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
var refunds = await _context.RefundRequests
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return refunds;
}
/// <inheritdoc />
public Task AddOrderAsync(Order order, CancellationToken cancellationToken = default)
{
return _context.Orders.AddAsync(order, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddItemsAsync(IEnumerable<OrderItem> items, CancellationToken cancellationToken = default)
{
return _context.OrderItems.AddRangeAsync(items, cancellationToken);
}
/// <inheritdoc />
public Task AddStatusHistoryAsync(OrderStatusHistory history, CancellationToken cancellationToken = default)
{
return _context.OrderStatusHistories.AddAsync(history, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddRefundAsync(RefundRequest refund, CancellationToken cancellationToken = default)
{
return _context.RefundRequests.AddAsync(refund, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,71 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Payments.Entities;
using TakeoutSaaS.Domain.Payments.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 支付记录的 EF Core 仓储实现。
/// </summary>
public sealed class EfPaymentRepository : IPaymentRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfPaymentRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<PaymentRecord?> FindByIdAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.PaymentRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == paymentId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<PaymentRecord?> FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.PaymentRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<PaymentRefundRecord>> GetRefundsAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default)
{
var refunds = await _context.PaymentRefundRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.PaymentRecordId == paymentId)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return refunds;
}
/// <inheritdoc />
public Task AddPaymentAsync(PaymentRecord payment, CancellationToken cancellationToken = default)
{
return _context.PaymentRecords.AddAsync(payment, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddRefundAsync(PaymentRefundRecord refund, CancellationToken cancellationToken = default)
{
return _context.PaymentRefundRecords.AddAsync(refund, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,227 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Products.Entities;
using TakeoutSaaS.Domain.Products.Enums;
using TakeoutSaaS.Domain.Products.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 商品聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfProductRepository : IProductRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfProductRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Product?> FindByIdAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Products
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == productId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Product>> SearchAsync(long tenantId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default)
{
var query = _context.Products
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (categoryId.HasValue)
{
query = query.Where(x => x.CategoryId == categoryId.Value);
}
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
var products = await query
.OrderBy(x => x.Name)
.ToListAsync(cancellationToken);
return products;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductCategory>> GetCategoriesAsync(long tenantId, CancellationToken cancellationToken = default)
{
var categories = await _context.ProductCategories
.AsNoTracking()
.Where(x => x.TenantId == tenantId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return categories;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductSku>> GetSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var skus = await _context.ProductSkus
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return skus;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAddonGroup>> GetAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groups = await _context.ProductAddonGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return groups;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAddonOption>> GetAddonOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groupIds = await _context.ProductAddonGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.Select(x => x.Id)
.ToListAsync(cancellationToken);
if (groupIds.Count == 0)
{
return Array.Empty<ProductAddonOption>();
}
var options = await _context.ProductAddonOptions
.AsNoTracking()
.Where(x => x.TenantId == tenantId && groupIds.Contains(x.AddonGroupId))
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return options;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAttributeGroup>> GetAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groups = await _context.ProductAttributeGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return groups;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAttributeOption>> GetAttributeOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groupIds = await _context.ProductAttributeGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.Select(x => x.Id)
.ToListAsync(cancellationToken);
if (groupIds.Count == 0)
{
return Array.Empty<ProductAttributeOption>();
}
var options = await _context.ProductAttributeOptions
.AsNoTracking()
.Where(x => x.TenantId == tenantId && groupIds.Contains(x.AttributeGroupId))
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return options;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductMediaAsset>> GetMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var assets = await _context.ProductMediaAssets
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return assets;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductPricingRule>> GetPricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var rules = await _context.ProductPricingRules
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return rules;
}
/// <inheritdoc />
public Task AddCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default)
{
return _context.ProductCategories.AddAsync(category, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddProductAsync(Product product, CancellationToken cancellationToken = default)
{
return _context.Products.AddAsync(product, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddSkusAsync(IEnumerable<ProductSku> skus, CancellationToken cancellationToken = default)
{
return _context.ProductSkus.AddRangeAsync(skus, cancellationToken);
}
/// <inheritdoc />
public Task AddAddonGroupsAsync(IEnumerable<ProductAddonGroup> groups, IEnumerable<ProductAddonOption> options, CancellationToken cancellationToken = default)
{
var addGroupsTask = _context.ProductAddonGroups.AddRangeAsync(groups, cancellationToken);
var addOptionsTask = _context.ProductAddonOptions.AddRangeAsync(options, cancellationToken);
return Task.WhenAll(addGroupsTask, addOptionsTask);
}
/// <inheritdoc />
public Task AddAttributeGroupsAsync(IEnumerable<ProductAttributeGroup> groups, IEnumerable<ProductAttributeOption> options, CancellationToken cancellationToken = default)
{
var addGroupsTask = _context.ProductAttributeGroups.AddRangeAsync(groups, cancellationToken);
var addOptionsTask = _context.ProductAttributeOptions.AddRangeAsync(options, cancellationToken);
return Task.WhenAll(addGroupsTask, addOptionsTask);
}
/// <inheritdoc />
public Task AddMediaAssetsAsync(IEnumerable<ProductMediaAsset> assets, CancellationToken cancellationToken = default)
{
return _context.ProductMediaAssets.AddRangeAsync(assets, cancellationToken);
}
/// <inheritdoc />
public Task AddPricingRulesAsync(IEnumerable<ProductPricingRule> rules, CancellationToken cancellationToken = default)
{
return _context.ProductPricingRules.AddRangeAsync(rules, cancellationToken);
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,174 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 门店聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfStoreRepository : IStoreRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfStoreRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Store?> FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Stores
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == storeId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Store>> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default)
{
var query = _context.Stores
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
var stores = await query
.OrderBy(x => x.Name)
.ToListAsync(cancellationToken);
return stores;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreBusinessHour>> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var hours = await _context.StoreBusinessHours
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.DayOfWeek)
.ThenBy(x => x.StartTime)
.ToListAsync(cancellationToken);
return hours;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreDeliveryZone>> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var zones = await _context.StoreDeliveryZones
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return zones;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreHoliday>> GetHolidaysAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var holidays = await _context.StoreHolidays
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.Date)
.ToListAsync(cancellationToken);
return holidays;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreTableArea>> GetTableAreasAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var areas = await _context.StoreTableAreas
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return areas;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreTable>> GetTablesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var tables = await _context.StoreTables
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.TableCode)
.ToListAsync(cancellationToken);
return tables;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreEmployeeShift>> GetShiftsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var shifts = await _context.StoreEmployeeShifts
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.ShiftDate)
.ThenBy(x => x.StartTime)
.ToListAsync(cancellationToken);
return shifts;
}
/// <inheritdoc />
public Task AddStoreAsync(Store store, CancellationToken cancellationToken = default)
{
return _context.Stores.AddAsync(store, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddBusinessHoursAsync(IEnumerable<StoreBusinessHour> hours, CancellationToken cancellationToken = default)
{
return _context.StoreBusinessHours.AddRangeAsync(hours, cancellationToken);
}
/// <inheritdoc />
public Task AddDeliveryZonesAsync(IEnumerable<StoreDeliveryZone> zones, CancellationToken cancellationToken = default)
{
return _context.StoreDeliveryZones.AddRangeAsync(zones, cancellationToken);
}
/// <inheritdoc />
public Task AddHolidaysAsync(IEnumerable<StoreHoliday> holidays, CancellationToken cancellationToken = default)
{
return _context.StoreHolidays.AddRangeAsync(holidays, cancellationToken);
}
/// <inheritdoc />
public Task AddTableAreasAsync(IEnumerable<StoreTableArea> areas, CancellationToken cancellationToken = default)
{
return _context.StoreTableAreas.AddRangeAsync(areas, cancellationToken);
}
/// <inheritdoc />
public Task AddTablesAsync(IEnumerable<StoreTable> tables, CancellationToken cancellationToken = default)
{
return _context.StoreTables.AddRangeAsync(tables, cancellationToken);
}
/// <inheritdoc />
public Task AddShiftsAsync(IEnumerable<StoreEmployeeShift> shifts, CancellationToken cancellationToken = default)
{
return _context.StoreEmployeeShifts.AddRangeAsync(shifts, cancellationToken);
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}