feat(finance): implement invoice and business report backend modules

This commit is contained in:
2026-03-04 16:57:06 +08:00
parent fa6e376b86
commit 5dfaac01fd
69 changed files with 17768 additions and 1 deletions

View File

@@ -95,6 +95,26 @@ public sealed class TakeoutAppDbContext(
/// </summary>
public DbSet<TenantVisibilityRoleRule> TenantVisibilityRoleRules => Set<TenantVisibilityRoleRule>();
/// <summary>
/// 租户发票设置。
/// </summary>
public DbSet<TenantInvoiceSetting> TenantInvoiceSettings => Set<TenantInvoiceSetting>();
/// <summary>
/// 租户发票记录。
/// </summary>
public DbSet<TenantInvoiceRecord> TenantInvoiceRecords => Set<TenantInvoiceRecord>();
/// <summary>
/// 经营报表快照。
/// </summary>
public DbSet<FinanceBusinessReportSnapshot> FinanceBusinessReportSnapshots => Set<FinanceBusinessReportSnapshot>();
/// <summary>
/// 成本配置。
/// </summary>
public DbSet<FinanceCostProfile> FinanceCostProfiles => Set<FinanceCostProfile>();
/// <summary>
/// 成本日覆盖。
/// </summary>
public DbSet<FinanceCostDailyOverride> FinanceCostDailyOverrides => Set<FinanceCostDailyOverride>();
/// <summary>
/// 成本录入汇总。
/// </summary>
public DbSet<FinanceCostEntry> FinanceCostEntries => Set<FinanceCostEntry>();
@@ -534,6 +554,11 @@ public sealed class TakeoutAppDbContext(
ConfigureTenantAnnouncementRead(modelBuilder.Entity<TenantAnnouncementRead>());
ConfigureTenantVerificationProfile(modelBuilder.Entity<TenantVerificationProfile>());
ConfigureTenantVisibilityRoleRule(modelBuilder.Entity<TenantVisibilityRoleRule>());
ConfigureTenantInvoiceSetting(modelBuilder.Entity<TenantInvoiceSetting>());
ConfigureTenantInvoiceRecord(modelBuilder.Entity<TenantInvoiceRecord>());
ConfigureFinanceBusinessReportSnapshot(modelBuilder.Entity<FinanceBusinessReportSnapshot>());
ConfigureFinanceCostProfile(modelBuilder.Entity<FinanceCostProfile>());
ConfigureFinanceCostDailyOverride(modelBuilder.Entity<FinanceCostDailyOverride>());
ConfigureFinanceCostEntry(modelBuilder.Entity<FinanceCostEntry>());
ConfigureFinanceCostEntryItem(modelBuilder.Entity<FinanceCostEntryItem>());
ConfigureQuotaPackage(modelBuilder.Entity<QuotaPackage>());
@@ -1053,6 +1078,115 @@ public sealed class TakeoutAppDbContext(
builder.HasIndex(x => x.TenantId).IsUnique();
}
private static void ConfigureTenantInvoiceSetting(EntityTypeBuilder<TenantInvoiceSetting> builder)
{
builder.ToTable("finance_invoice_settings");
builder.HasKey(x => x.Id);
builder.Property(x => x.TenantId).IsRequired();
builder.Property(x => x.CompanyName).HasMaxLength(128).IsRequired();
builder.Property(x => x.TaxpayerNumber).HasMaxLength(64).IsRequired();
builder.Property(x => x.RegisteredAddress).HasMaxLength(256);
builder.Property(x => x.RegisteredPhone).HasMaxLength(32);
builder.Property(x => x.BankName).HasMaxLength(128);
builder.Property(x => x.BankAccount).HasMaxLength(64);
builder.Property(x => x.EnableElectronicNormalInvoice).IsRequired();
builder.Property(x => x.EnableElectronicSpecialInvoice).IsRequired();
builder.Property(x => x.EnableAutoIssue).IsRequired();
builder.Property(x => x.AutoIssueMaxAmount).HasPrecision(18, 2).IsRequired();
builder.HasIndex(x => x.TenantId).IsUnique();
}
private static void ConfigureTenantInvoiceRecord(EntityTypeBuilder<TenantInvoiceRecord> builder)
{
builder.ToTable("finance_invoice_records");
builder.HasKey(x => x.Id);
builder.Property(x => x.TenantId).IsRequired();
builder.Property(x => x.InvoiceNo).HasMaxLength(32).IsRequired();
builder.Property(x => x.ApplicantName).HasMaxLength(64).IsRequired();
builder.Property(x => x.CompanyName).HasMaxLength(128).IsRequired();
builder.Property(x => x.TaxpayerNumber).HasMaxLength(64);
builder.Property(x => x.InvoiceType).HasConversion<int>().IsRequired();
builder.Property(x => x.Amount).HasPrecision(18, 2).IsRequired();
builder.Property(x => x.OrderNo).HasMaxLength(32).IsRequired();
builder.Property(x => x.ContactEmail).HasMaxLength(128);
builder.Property(x => x.ContactPhone).HasMaxLength(32);
builder.Property(x => x.ApplyRemark).HasMaxLength(256);
builder.Property(x => x.Status).HasConversion<int>().IsRequired();
builder.Property(x => x.AppliedAt).IsRequired();
builder.Property(x => x.IssueRemark).HasMaxLength(256);
builder.Property(x => x.VoidReason).HasMaxLength(256);
builder.HasIndex(x => new { x.TenantId, x.InvoiceNo }).IsUnique();
builder.HasIndex(x => new { x.TenantId, x.OrderNo });
builder.HasIndex(x => new { x.TenantId, x.Status, x.AppliedAt });
builder.HasIndex(x => new { x.TenantId, x.Status, x.IssuedAt });
builder.HasIndex(x => new { x.TenantId, x.InvoiceType, x.AppliedAt });
}
private static void ConfigureFinanceBusinessReportSnapshot(EntityTypeBuilder<FinanceBusinessReportSnapshot> builder)
{
builder.ToTable("finance_business_report_snapshots");
builder.HasKey(x => x.Id);
builder.Property(x => x.TenantId).IsRequired();
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.PeriodType).HasConversion<int>().IsRequired();
builder.Property(x => x.PeriodStartAt).IsRequired();
builder.Property(x => x.PeriodEndAt).IsRequired();
builder.Property(x => x.Status).HasConversion<int>().IsRequired();
builder.Property(x => x.RevenueAmount).HasPrecision(18, 2);
builder.Property(x => x.OrderCount).IsRequired();
builder.Property(x => x.AverageOrderValue).HasPrecision(18, 2);
builder.Property(x => x.RefundRate).HasPrecision(9, 4);
builder.Property(x => x.CostTotalAmount).HasPrecision(18, 2);
builder.Property(x => x.NetProfitAmount).HasPrecision(18, 2);
builder.Property(x => x.ProfitRate).HasPrecision(9, 4);
builder.Property(x => x.KpiComparisonJson).HasColumnType("text").IsRequired();
builder.Property(x => x.IncomeBreakdownJson).HasColumnType("text").IsRequired();
builder.Property(x => x.CostBreakdownJson).HasColumnType("text").IsRequired();
builder.Property(x => x.LastError).HasMaxLength(1024);
builder.Property(x => x.HangfireJobId).HasMaxLength(64);
builder.Property(x => x.RetryCount).HasDefaultValue(0);
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PeriodType, x.PeriodStartAt }).IsUnique();
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PeriodType, x.Status, x.PeriodStartAt });
builder.HasIndex(x => new { x.TenantId, x.Status, x.CreatedAt });
}
private static void ConfigureFinanceCostProfile(EntityTypeBuilder<FinanceCostProfile> builder)
{
builder.ToTable("finance_cost_profiles");
builder.HasKey(x => x.Id);
builder.Property(x => x.TenantId).IsRequired();
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.Category).HasConversion<int>().IsRequired();
builder.Property(x => x.CalcMode).HasConversion<int>().IsRequired();
builder.Property(x => x.Ratio).HasPrecision(9, 6).IsRequired();
builder.Property(x => x.FixedDailyAmount).HasPrecision(18, 2).IsRequired();
builder.Property(x => x.EffectiveFrom).IsRequired();
builder.Property(x => x.EffectiveTo);
builder.Property(x => x.IsEnabled).IsRequired();
builder.Property(x => x.SortOrder).HasDefaultValue(100);
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Category, x.EffectiveFrom, x.EffectiveTo });
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.IsEnabled, x.SortOrder });
}
private static void ConfigureFinanceCostDailyOverride(EntityTypeBuilder<FinanceCostDailyOverride> builder)
{
builder.ToTable("finance_cost_daily_overrides");
builder.HasKey(x => x.Id);
builder.Property(x => x.TenantId).IsRequired();
builder.Property(x => x.StoreId).IsRequired();
builder.Property(x => x.BusinessDate).IsRequired();
builder.Property(x => x.Category).HasConversion<int>().IsRequired();
builder.Property(x => x.Amount).HasPrecision(18, 2).IsRequired();
builder.Property(x => x.Remark).HasMaxLength(256);
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.BusinessDate, x.Category }).IsUnique();
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.BusinessDate });
}
private static void ConfigureFinanceCostEntry(EntityTypeBuilder<FinanceCostEntry> builder)
{
builder.ToTable("finance_cost_entries");