feat(finance): 完成发票管理模块后端实现

This commit is contained in:
2026-03-04 16:48:58 +08:00
parent 21a689edec
commit c79e9bd6e8
33 changed files with 2970 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 租户发票记录。
/// </summary>
public sealed class TenantInvoiceRecord : MultiTenantEntityBase
{
/// <summary>
/// 发票号码。
/// </summary>
public string InvoiceNo { get; set; } = string.Empty;
/// <summary>
/// 申请人。
/// </summary>
public string ApplicantName { get; set; } = string.Empty;
/// <summary>
/// 开票抬头(公司名)。
/// </summary>
public string CompanyName { get; set; } = string.Empty;
/// <summary>
/// 纳税人识别号快照。
/// </summary>
public string? TaxpayerNumber { get; set; }
/// <summary>
/// 发票类型。
/// </summary>
public TenantInvoiceType InvoiceType { get; set; } = TenantInvoiceType.Normal;
/// <summary>
/// 开票金额。
/// </summary>
public decimal Amount { get; set; }
/// <summary>
/// 关联订单号。
/// </summary>
public string OrderNo { get; set; } = string.Empty;
/// <summary>
/// 接收邮箱。
/// </summary>
public string? ContactEmail { get; set; }
/// <summary>
/// 联系电话。
/// </summary>
public string? ContactPhone { get; set; }
/// <summary>
/// 申请备注。
/// </summary>
public string? ApplyRemark { get; set; }
/// <summary>
/// 发票状态。
/// </summary>
public TenantInvoiceStatus Status { get; set; } = TenantInvoiceStatus.Pending;
/// <summary>
/// 申请时间UTC
/// </summary>
public DateTime AppliedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// 开票时间UTC
/// </summary>
public DateTime? IssuedAt { get; set; }
/// <summary>
/// 开票人 ID。
/// </summary>
public long? IssuedByUserId { get; set; }
/// <summary>
/// 开票备注。
/// </summary>
public string? IssueRemark { get; set; }
/// <summary>
/// 作废时间UTC
/// </summary>
public DateTime? VoidedAt { get; set; }
/// <summary>
/// 作废人 ID。
/// </summary>
public long? VoidedByUserId { get; set; }
/// <summary>
/// 作废原因。
/// </summary>
public string? VoidReason { get; set; }
}

View File

@@ -0,0 +1,59 @@
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 租户发票开票基础设置。
/// </summary>
public sealed class TenantInvoiceSetting : MultiTenantEntityBase
{
/// <summary>
/// 企业名称。
/// </summary>
public string CompanyName { get; set; } = string.Empty;
/// <summary>
/// 纳税人识别号。
/// </summary>
public string TaxpayerNumber { get; set; } = string.Empty;
/// <summary>
/// 注册地址。
/// </summary>
public string? RegisteredAddress { get; set; }
/// <summary>
/// 注册电话。
/// </summary>
public string? RegisteredPhone { get; set; }
/// <summary>
/// 开户银行。
/// </summary>
public string? BankName { get; set; }
/// <summary>
/// 银行账号。
/// </summary>
public string? BankAccount { get; set; }
/// <summary>
/// 是否启用电子普通发票。
/// </summary>
public bool EnableElectronicNormalInvoice { get; set; } = true;
/// <summary>
/// 是否启用电子专用发票。
/// </summary>
public bool EnableElectronicSpecialInvoice { get; set; }
/// <summary>
/// 是否启用自动开票。
/// </summary>
public bool EnableAutoIssue { get; set; }
/// <summary>
/// 自动开票单张最大金额。
/// </summary>
public decimal AutoIssueMaxAmount { get; set; } = 10_000m;
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Tenants.Enums;
/// <summary>
/// 租户发票状态。
/// </summary>
public enum TenantInvoiceStatus
{
/// <summary>
/// 待开票。
/// </summary>
Pending = 1,
/// <summary>
/// 已开票。
/// </summary>
Issued = 2,
/// <summary>
/// 已作废。
/// </summary>
Voided = 3
}

View File

@@ -0,0 +1,17 @@
namespace TakeoutSaaS.Domain.Tenants.Enums;
/// <summary>
/// 租户发票类型。
/// </summary>
public enum TenantInvoiceType
{
/// <summary>
/// 电子普通发票。
/// </summary>
Normal = 1,
/// <summary>
/// 电子专用发票。
/// </summary>
Special = 2
}

View File

@@ -0,0 +1,104 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 租户发票仓储契约。
/// </summary>
public interface ITenantInvoiceRepository
{
/// <summary>
/// 查询租户发票设置。
/// </summary>
Task<TenantInvoiceSetting?> GetSettingAsync(long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增发票设置。
/// </summary>
Task AddSettingAsync(TenantInvoiceSetting entity, CancellationToken cancellationToken = default);
/// <summary>
/// 更新发票设置。
/// </summary>
Task UpdateSettingAsync(TenantInvoiceSetting entity, CancellationToken cancellationToken = default);
/// <summary>
/// 分页查询发票记录。
/// </summary>
Task<(IReadOnlyList<TenantInvoiceRecord> Items, int TotalCount)> SearchRecordsAsync(
long tenantId,
DateTime? startUtc,
DateTime? endUtc,
TenantInvoiceStatus? status,
TenantInvoiceType? invoiceType,
string? keyword,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取发票页统计。
/// </summary>
Task<TenantInvoiceRecordStatsSnapshot> GetStatsAsync(
long tenantId,
DateTime nowUtc,
CancellationToken cancellationToken = default);
/// <summary>
/// 根据标识查询发票记录。
/// </summary>
Task<TenantInvoiceRecord?> FindRecordByIdAsync(
long tenantId,
long recordId,
CancellationToken cancellationToken = default);
/// <summary>
/// 判断租户下发票号码是否已存在。
/// </summary>
Task<bool> ExistsInvoiceNoAsync(
long tenantId,
string invoiceNo,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增发票记录。
/// </summary>
Task AddRecordAsync(TenantInvoiceRecord entity, CancellationToken cancellationToken = default);
/// <summary>
/// 更新发票记录。
/// </summary>
Task UpdateRecordAsync(TenantInvoiceRecord entity, CancellationToken cancellationToken = default);
/// <summary>
/// 持久化变更。
/// </summary>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
/// <summary>
/// 发票页面统计快照。
/// </summary>
public sealed record TenantInvoiceRecordStatsSnapshot
{
/// <summary>
/// 本月已开票金额。
/// </summary>
public decimal CurrentMonthIssuedAmount { get; init; }
/// <summary>
/// 本月已开票张数。
/// </summary>
public int CurrentMonthIssuedCount { get; init; }
/// <summary>
/// 待开票张数。
/// </summary>
public int PendingCount { get; init; }
/// <summary>
/// 已作废张数。
/// </summary>
public int VoidedCount { get; init; }
}