diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs index c5b52d0..79b4753 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; using TakeoutSaaS.Application.App.Merchants.Commands; using TakeoutSaaS.Application.App.Merchants.Dto; using TakeoutSaaS.Application.App.Merchants.Queries; @@ -41,36 +42,32 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController /// /// 查询商户列表。 /// - /// 状态筛选。 - /// 页码。 - /// 每页大小。 - /// 排序字段。 - /// 是否倒序。 + /// 查询参数。 /// 取消标记。 /// 商户分页结果。 [HttpGet] [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List( - [FromQuery] MerchantStatus? status, - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20, - [FromQuery] string? sortBy = null, - [FromQuery] bool sortDesc = true, + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> List( + [FromQuery] GetMerchantListQuery query, CancellationToken cancellationToken = default) { - // 1. 组装查询参数并执行查询 - var result = await mediator.Send(new SearchMerchantsQuery - { - Status = status, - Page = page, - PageSize = pageSize, - SortBy = sortBy, - SortDescending = sortDesc - }, cancellationToken); + var result = await mediator.Send(query, cancellationToken); + return ApiResponse>.Ok(result); + } - // 2. 返回分页结果 - return ApiResponse>.Ok(result); + /// + /// 待审核商户列表。 + /// + [HttpGet("pending-review")] + [PermissionAuthorize("merchant:review")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> PendingReviewList( + [FromQuery] GetPendingReviewListQuery query, + CancellationToken cancellationToken) + { + var result = await mediator.Send(query, cancellationToken); + return ApiResponse>.Ok(result); } /// @@ -82,23 +79,36 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController /// 更新后的商户或未找到。 [HttpPut("{merchantId:long}")] [PermissionAuthorize("merchant:update")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Update(long merchantId, [FromBody] UpdateMerchantCommand command, CancellationToken cancellationToken) + public async Task> Update(long merchantId, [FromBody] UpdateMerchantCommand command, CancellationToken cancellationToken) { - // 1. 绑定商户标识 - if (command.MerchantId == 0) + if (command.MerchantId != 0 && command.MerchantId != merchantId) { - command = command with { MerchantId = merchantId }; + return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 merchantId 与请求体 merchantId 不一致"); } + command = command with { MerchantId = merchantId }; + // 2. 执行更新 var result = await mediator.Send(command, cancellationToken); // 3. 返回更新结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商户不存在") - : ApiResponse.Ok(result); + if (result == null) + { + return ApiResponse.Error(ErrorCodes.NotFound, "商户不存在"); + } + + if (result.RequiresReview) + { + return ApiResponse.Error( + ErrorCodes.ValidationFailed, + "关键信息修改,商户已进入待审核状态,业务已冻结") + with { Data = result }; + } + + return ApiResponse.Ok(result); } /// @@ -130,17 +140,51 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController /// 商户概览或未找到。 [HttpGet("{merchantId:long}")] [PermissionAuthorize("merchant:read")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task> Detail(long merchantId, CancellationToken cancellationToken) + public async Task> Detail(long merchantId, CancellationToken cancellationToken) { // 1. 查询商户概览 - var result = await mediator.Send(new GetMerchantByIdQuery { MerchantId = merchantId }, cancellationToken); + var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken); - // 2. 返回结果或 404 - return result == null - ? ApiResponse.Error(ErrorCodes.NotFound, "商户不存在") - : ApiResponse.Ok(result); + // 2. 返回结果 + return ApiResponse.Ok(result); + } + + /// + /// 获取审核领取信息。 + /// + [HttpGet("{merchantId:long}/review/claim")] + [PermissionAuthorize("merchant:review")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> GetReviewClaim(long merchantId, CancellationToken cancellationToken) + { + var result = await mediator.Send(new GetMerchantReviewClaimQuery(merchantId), cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 领取审核。 + /// + [HttpPost("{merchantId:long}/review/claim")] + [PermissionAuthorize("merchant:review")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> ClaimReview(long merchantId, CancellationToken cancellationToken) + { + var result = await mediator.Send(new ClaimMerchantReviewCommand { MerchantId = merchantId }, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 释放审核领取。 + /// + [HttpDelete("{merchantId:long}/review/claim")] + [PermissionAuthorize("merchant:review")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> ReleaseReviewClaim(long merchantId, CancellationToken cancellationToken) + { + var result = await mediator.Send(new ReleaseClaimCommand { MerchantId = merchantId }, cancellationToken); + return ApiResponse.Ok(result); } /// @@ -290,6 +334,60 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController return ApiResponse.Ok(result); } + /// + /// 撤销审核。 + /// + [HttpPost("{merchantId:long}/review/revoke")] + [PermissionAuthorize("merchant:review:revoke")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> RevokeReview( + long merchantId, + [FromBody] RevokeMerchantReviewCommand body, + CancellationToken cancellationToken) + { + if (body.MerchantId != 0 && body.MerchantId != merchantId) + { + return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 merchantId 与请求体 merchantId 不一致"); + } + + var command = new RevokeMerchantReviewCommand + { + MerchantId = merchantId, + Reason = body.Reason + }; + await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(null); + } + + /// + /// 审核历史。 + /// + [HttpGet("{merchantId:long}/audit-history")] + [PermissionAuthorize("merchant:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> AuditHistory( + long merchantId, + CancellationToken cancellationToken) + { + var result = await mediator.Send(new GetMerchantAuditHistoryQuery(merchantId), cancellationToken); + return ApiResponse>.Ok(result); + } + + /// + /// 变更历史。 + /// + [HttpGet("{merchantId:long}/change-history")] + [PermissionAuthorize("merchant:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> ChangeHistory( + long merchantId, + [FromQuery] string? fieldName, + CancellationToken cancellationToken) + { + var result = await mediator.Send(new GetMerchantChangeHistoryQuery(merchantId, fieldName), cancellationToken); + return ApiResponse>.Ok(result); + } + /// /// 审核日志。 /// @@ -310,6 +408,27 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController return ApiResponse>.Ok(result); } + /// + /// 导出商户 PDF。 + /// + [HttpGet("{merchantId:long}/export-pdf")] + [PermissionAuthorize("merchant:export")] + [Produces("application/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + public async Task ExportPdf(long merchantId, CancellationToken cancellationToken) + { + var bytes = await mediator.Send(new ExportMerchantPdfQuery(merchantId), cancellationToken); + var fileName = $"merchant_{merchantId}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.pdf"; + + Response.Headers[HeaderNames.ContentDisposition] = new ContentDispositionHeaderValue("attachment") + { + FileName = fileName, + FileNameStar = fileName + }.ToString(); + + return File(bytes, "application/pdf"); + } + /// /// 可选商户类目列表。 /// diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/ClaimMerchantReviewCommand.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/ClaimMerchantReviewCommand.cs new file mode 100644 index 0000000..3002b49 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/ClaimMerchantReviewCommand.cs @@ -0,0 +1,15 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; + +namespace TakeoutSaaS.Application.App.Merchants.Commands; + +/// +/// 领取商户审核命令。 +/// +public sealed class ClaimMerchantReviewCommand : IRequest +{ + /// + /// 商户 ID。 + /// + public long MerchantId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/ReleaseClaimCommand.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/ReleaseClaimCommand.cs new file mode 100644 index 0000000..5a06141 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/ReleaseClaimCommand.cs @@ -0,0 +1,15 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; + +namespace TakeoutSaaS.Application.App.Merchants.Commands; + +/// +/// 释放商户审核领取命令。 +/// +public sealed class ReleaseClaimCommand : IRequest +{ + /// + /// 商户 ID。 + /// + public long MerchantId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/RevokeMerchantReviewCommand.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/RevokeMerchantReviewCommand.cs new file mode 100644 index 0000000..30580f4 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/RevokeMerchantReviewCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace TakeoutSaaS.Application.App.Merchants.Commands; + +/// +/// 撤销商户审核命令。 +/// +public sealed class RevokeMerchantReviewCommand : IRequest +{ + /// + /// 商户 ID。 + /// + public long MerchantId { get; init; } + + /// + /// 撤销原因。 + /// + public string Reason { get; init; } = string.Empty; +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs index feb73f9..034a6f8 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs @@ -1,13 +1,12 @@ using MediatR; using TakeoutSaaS.Application.App.Merchants.Dto; -using TakeoutSaaS.Domain.Merchants.Enums; namespace TakeoutSaaS.Application.App.Merchants.Commands; /// /// 更新商户命令。 /// -public sealed record UpdateMerchantCommand : IRequest +public sealed record UpdateMerchantCommand : IRequest { /// /// 商户 ID。 @@ -15,29 +14,29 @@ public sealed record UpdateMerchantCommand : IRequest public long MerchantId { get; init; } /// - /// 品牌名称。 + /// 商户名称。 /// - public string BrandName { get; init; } = string.Empty; + public string? Name { get; init; } /// - /// 品牌简称。 + /// 营业执照号。 /// - public string? BrandAlias { get; init; } + public string? LicenseNumber { get; init; } /// - /// Logo 地址。 + /// 法人或负责人。 /// - public string? LogoUrl { get; init; } + public string? LegalRepresentative { get; init; } /// - /// 品类。 + /// 注册地址。 /// - public string? Category { get; init; } + public string? RegisteredAddress { get; init; } /// /// 联系电话。 /// - public string ContactPhone { get; init; } = string.Empty; + public string? ContactPhone { get; init; } /// /// 联系邮箱。 @@ -45,7 +44,7 @@ public sealed record UpdateMerchantCommand : IRequest public string? ContactEmail { get; init; } /// - /// 入驻状态。 + /// 并发控制版本。 /// - public MerchantStatus Status { get; init; } + public byte[] RowVersion { get; init; } = Array.Empty(); } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/ClaimInfoDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/ClaimInfoDto.cs new file mode 100644 index 0000000..999aead --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/ClaimInfoDto.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Merchants.Dto; + +/// +/// 审核领取信息 DTO。 +/// +public sealed class ClaimInfoDto +{ + /// + /// 商户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long MerchantId { get; init; } + + /// + /// 领取人 ID。 + /// + [JsonConverter(typeof(NullableSnowflakeIdJsonConverter))] + public long? ClaimedBy { get; init; } + + /// + /// 领取人名称。 + /// + public string? ClaimedByName { get; init; } + + /// + /// 领取时间。 + /// + public DateTime? ClaimedAt { get; init; } + + /// + /// 领取过期时间。 + /// + public DateTime? ClaimExpiresAt { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantAuditLogDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantAuditLogDto.cs index 8954353..da98a7e 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantAuditLogDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantAuditLogDto.cs @@ -26,6 +26,12 @@ public sealed class MerchantAuditLogDto /// public MerchantAuditAction Action { get; init; } + /// + /// 操作人 ID。 + /// + [JsonConverter(typeof(NullableSnowflakeIdJsonConverter))] + public long? OperatorId { get; init; } + /// /// 标题。 /// @@ -41,6 +47,11 @@ public sealed class MerchantAuditLogDto /// public string? OperatorName { get; init; } + /// + /// 操作 IP。 + /// + public string? IpAddress { get; init; } + /// /// 创建时间。 /// diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantChangeLogDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantChangeLogDto.cs new file mode 100644 index 0000000..c1f1461 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantChangeLogDto.cs @@ -0,0 +1,52 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Merchants.Dto; + +/// +/// 商户变更日志 DTO。 +/// +public sealed class MerchantChangeLogDto +{ + /// + /// 日志 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 变更字段。 + /// + public string FieldName { get; init; } = string.Empty; + + /// + /// 变更前值。 + /// + public string? OldValue { get; init; } + + /// + /// 变更后值。 + /// + public string? NewValue { get; init; } + + /// + /// 变更人 ID。 + /// + [JsonConverter(typeof(NullableSnowflakeIdJsonConverter))] + public long? ChangedBy { get; init; } + + /// + /// 变更人名称。 + /// + public string? ChangedByName { get; init; } + + /// + /// 变更时间。 + /// + public DateTime ChangedAt { get; init; } + + /// + /// 变更原因。 + /// + public string? ChangeReason { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDetailDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDetailDto.cs index 2fa8b95..35aa22c 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDetailDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDetailDto.cs @@ -1,3 +1,8 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Common.Enums; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Serialization; + namespace TakeoutSaaS.Application.App.Merchants.Dto; /// @@ -6,17 +11,117 @@ namespace TakeoutSaaS.Application.App.Merchants.Dto; public sealed class MerchantDetailDto { /// - /// 基础信息。 + /// 商户 ID。 /// - public MerchantDto Merchant { get; init; } = new(); + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } /// - /// 证照列表。 + /// 租户 ID。 /// - public IReadOnlyList Documents { get; init; } = []; + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } /// - /// 合同列表。 + /// 租户名称。 /// - public IReadOnlyList Contracts { get; init; } = []; + public string? TenantName { get; init; } + + /// + /// 商户名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 经营模式。 + /// + public OperatingMode? OperatingMode { get; init; } + + /// + /// 营业执照号。 + /// + public string? LicenseNumber { get; init; } + + /// + /// 法人或负责人。 + /// + public string? LegalRepresentative { get; init; } + + /// + /// 注册地址。 + /// + public string? RegisteredAddress { get; init; } + + /// + /// 联系电话。 + /// + public string? ContactPhone { get; init; } + + /// + /// 联系邮箱。 + /// + public string? ContactEmail { get; init; } + + /// + /// 审核状态。 + /// + public MerchantStatus Status { get; init; } + + /// + /// 业务冻结标记。 + /// + public bool IsFrozen { get; init; } + + /// + /// 冻结原因。 + /// + public string? FrozenReason { get; init; } + + /// + /// 冻结时间。 + /// + public DateTime? FrozenAt { get; init; } + + /// + /// 审核通过人。 + /// + [JsonConverter(typeof(NullableSnowflakeIdJsonConverter))] + public long? ApprovedBy { get; init; } + + /// + /// 审核通过时间。 + /// + public DateTime? ApprovedAt { get; init; } + + /// + /// 门店列表。 + /// + public IReadOnlyList Stores { get; init; } = []; + + /// + /// 并发控制版本。 + /// + public byte[] RowVersion { get; init; } = Array.Empty(); + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } + + /// + /// 创建人。 + /// + [JsonConverter(typeof(NullableSnowflakeIdJsonConverter))] + public long? CreatedBy { get; init; } + + /// + /// 更新时间。 + /// + public DateTime? UpdatedAt { get; init; } + + /// + /// 更新人。 + /// + [JsonConverter(typeof(NullableSnowflakeIdJsonConverter))] + public long? UpdatedBy { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantListItemDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantListItemDto.cs new file mode 100644 index 0000000..c14cae3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantListItemDto.cs @@ -0,0 +1,69 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Common.Enums; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Merchants.Dto; + +/// +/// 商户列表项 DTO。 +/// +public sealed class MerchantListItemDto +{ + /// + /// 商户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 租户名称。 + /// + public string? TenantName { get; init; } + + /// + /// 商户名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 经营模式。 + /// + public OperatingMode? OperatingMode { get; init; } + + /// + /// 营业执照号。 + /// + public string? LicenseNumber { get; init; } + + /// + /// 审核状态。 + /// + public MerchantStatus Status { get; init; } + + /// + /// 是否冻结业务。 + /// + public bool IsFrozen { get; init; } + + /// + /// 门店数量。 + /// + public int StoreCount { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } + + /// + /// 更新时间。 + /// + public DateTime? UpdatedAt { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantReviewListItemDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantReviewListItemDto.cs new file mode 100644 index 0000000..2775ff8 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantReviewListItemDto.cs @@ -0,0 +1,69 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Common.Enums; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Merchants.Dto; + +/// +/// 待审核商户列表项 DTO。 +/// +public sealed class MerchantReviewListItemDto +{ + /// + /// 商户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 租户名称。 + /// + public string? TenantName { get; init; } + + /// + /// 商户名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 经营模式。 + /// + public OperatingMode? OperatingMode { get; init; } + + /// + /// 营业执照号。 + /// + public string? LicenseNumber { get; init; } + + /// + /// 审核状态。 + /// + public MerchantStatus Status { get; init; } + + /// + /// 领取人名称。 + /// + public string? ClaimedByName { get; init; } + + /// + /// 领取时间。 + /// + public DateTime? ClaimedAt { get; init; } + + /// + /// 领取过期时间。 + /// + public DateTime? ClaimExpiresAt { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/StoreDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/StoreDto.cs new file mode 100644 index 0000000..ebd7f04 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/StoreDto.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Stores.Enums; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Merchants.Dto; + +/// +/// 商户详情门店 DTO。 +/// +public sealed class StoreDto +{ + /// + /// 门店 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 门店名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 营业执照号(主体不一致模式使用)。 + /// + public string? LicenseNumber { get; init; } + + /// + /// 联系电话。 + /// + public string? ContactPhone { get; init; } + + /// + /// 门店地址。 + /// + public string? Address { get; init; } + + /// + /// 门店状态。 + /// + public StoreStatus Status { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/UpdateMerchantResultDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/UpdateMerchantResultDto.cs new file mode 100644 index 0000000..4cb0fcf --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/UpdateMerchantResultDto.cs @@ -0,0 +1,17 @@ +namespace TakeoutSaaS.Application.App.Merchants.Dto; + +/// +/// 商户更新结果 DTO。 +/// +public sealed class UpdateMerchantResultDto +{ + /// + /// 更新后的商户详情。 + /// + public MerchantDetailDto Merchant { get; init; } = new(); + + /// + /// 是否触发重新审核。 + /// + public bool RequiresReview { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ClaimMerchantReviewHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ClaimMerchantReviewHandler.cs new file mode 100644 index 0000000..fdd10a0 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ClaimMerchantReviewHandler.cs @@ -0,0 +1,77 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Commands; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 领取商户审核处理器。 +/// +public sealed class ClaimMerchantReviewHandler( + IMerchantRepository merchantRepository, + ICurrentUserAccessor currentUserAccessor) + : IRequestHandler +{ + /// + public async Task Handle(ClaimMerchantReviewCommand request, CancellationToken cancellationToken) + { + var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + ?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + + if (merchant.Status != MerchantStatus.Pending) + { + throw new BusinessException(ErrorCodes.Conflict, "商户不在待审核状态"); + } + + var now = DateTime.UtcNow; + if (merchant.ClaimedBy.HasValue && merchant.ClaimExpiresAt.HasValue && merchant.ClaimExpiresAt > now) + { + if (merchant.ClaimedBy != currentUserAccessor.UserId) + { + throw new BusinessException(ErrorCodes.Conflict, $"该审核已被 {merchant.ClaimedByName} 领取"); + } + + return ToDto(merchant); + } + + var actorName = currentUserAccessor.IsAuthenticated + ? $"user:{currentUserAccessor.UserId}" + : "system"; + + merchant.ClaimedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId; + merchant.ClaimedByName = actorName; + merchant.ClaimedAt = now; + merchant.ClaimExpiresAt = now.AddMinutes(30); + + await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken); + await merchantRepository.AddAuditLogAsync(new MerchantAuditLog + { + TenantId = merchant.TenantId, + MerchantId = merchant.Id, + Action = MerchantAuditAction.ReviewClaimed, + Title = "领取审核", + Description = $"领取人:{actorName}", + OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + OperatorName = actorName + }, cancellationToken); + await merchantRepository.SaveChangesAsync(cancellationToken); + + return ToDto(merchant); + } + + private static ClaimInfoDto ToDto(Domain.Merchants.Entities.Merchant merchant) + => new() + { + MerchantId = merchant.Id, + ClaimedBy = merchant.ClaimedBy, + ClaimedByName = merchant.ClaimedByName, + ClaimedAt = merchant.ClaimedAt, + ClaimExpiresAt = merchant.ClaimExpiresAt + }; +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ExportMerchantPdfQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ExportMerchantPdfQueryHandler.cs new file mode 100644 index 0000000..4519892 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ExportMerchantPdfQueryHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Application.Identity; +using TakeoutSaaS.Application.Identity.Abstractions; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Domain.Merchants.Services; +using TakeoutSaaS.Domain.Stores.Repositories; +using TakeoutSaaS.Domain.Tenants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 导出商户 PDF 处理器。 +/// +public sealed class ExportMerchantPdfQueryHandler( + IMerchantRepository merchantRepository, + IStoreRepository storeRepository, + ITenantRepository tenantRepository, + IMerchantExportService exportService, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor, + IAdminAuthService adminAuthService) + : IRequestHandler +{ + public async Task Handle(ExportMerchantPdfQuery request, CancellationToken cancellationToken) + { + var currentTenantId = tenantProvider.GetCurrentTenantId(); + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); + + var merchant = isSuperAdmin + ? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + : await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken); + + if (merchant == null) + { + throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + } + + if (!isSuperAdmin && merchant.TenantId != currentTenantId) + { + throw new BusinessException(ErrorCodes.Forbidden, "禁止导出其他租户商户"); + } + + var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken); + var auditLogs = await merchantRepository.GetAuditLogsAsync(merchant.Id, merchant.TenantId, cancellationToken); + var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken); + + return await exportService.ExportToPdfAsync(merchant, tenant?.Name, stores, auditLogs, cancellationToken); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantAuditHistoryQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantAuditHistoryQueryHandler.cs new file mode 100644 index 0000000..905cb54 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantAuditHistoryQueryHandler.cs @@ -0,0 +1,50 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Application.Identity; +using TakeoutSaaS.Application.Identity.Abstractions; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 商户审核历史处理器。 +/// +public sealed class GetMerchantAuditHistoryQueryHandler( + IMerchantRepository merchantRepository, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor, + IAdminAuthService adminAuthService) + : IRequestHandler> +{ + /// + public async Task> Handle( + GetMerchantAuditHistoryQuery request, + CancellationToken cancellationToken) + { + var currentTenantId = tenantProvider.GetCurrentTenantId(); + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); + + var merchant = isSuperAdmin + ? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + : await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken); + + if (merchant == null) + { + throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + } + + if (!isSuperAdmin && merchant.TenantId != currentTenantId) + { + throw new BusinessException(ErrorCodes.Forbidden, "禁止访问其他租户的商户审核历史"); + } + + var logs = await merchantRepository.GetAuditLogsAsync(merchant.Id, merchant.TenantId, cancellationToken); + return logs.Select(MerchantMapping.ToDto).ToList(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantChangeHistoryQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantChangeHistoryQueryHandler.cs new file mode 100644 index 0000000..35d3be5 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantChangeHistoryQueryHandler.cs @@ -0,0 +1,50 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Application.Identity; +using TakeoutSaaS.Application.Identity.Abstractions; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 商户变更历史处理器。 +/// +public sealed class GetMerchantChangeHistoryQueryHandler( + IMerchantRepository merchantRepository, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor, + IAdminAuthService adminAuthService) + : IRequestHandler> +{ + /// + public async Task> Handle( + GetMerchantChangeHistoryQuery request, + CancellationToken cancellationToken) + { + var currentTenantId = tenantProvider.GetCurrentTenantId(); + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); + + var merchant = isSuperAdmin + ? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + : await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken); + + if (merchant == null) + { + throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + } + + if (!isSuperAdmin && merchant.TenantId != currentTenantId) + { + throw new BusinessException(ErrorCodes.Forbidden, "禁止访问其他租户的商户变更历史"); + } + + var logs = await merchantRepository.GetChangeLogsAsync(merchant.Id, merchant.TenantId, request.FieldName, cancellationToken); + return logs.Select(MerchantMapping.ToDto).ToList(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantDetailQueryHandler.cs index 7ddbea3..69a6bcf 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantDetailQueryHandler.cs @@ -1,9 +1,14 @@ using MediatR; using TakeoutSaaS.Application.App.Merchants.Dto; using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Application.Identity; +using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Domain.Stores.Repositories; +using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Merchants.Handlers; @@ -13,7 +18,11 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers; /// public sealed class GetMerchantDetailQueryHandler( IMerchantRepository merchantRepository, - ITenantProvider tenantProvider) + IStoreRepository storeRepository, + ITenantRepository tenantRepository, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor, + IAdminAuthService adminAuthService) : IRequestHandler { /// @@ -24,21 +33,31 @@ public sealed class GetMerchantDetailQueryHandler( /// 商户详情 DTO。 public async Task Handle(GetMerchantDetailQuery request, CancellationToken cancellationToken) { - // 1. 获取租户上下文并查询商户 - var tenantId = tenantProvider.GetCurrentTenantId(); - var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken) - ?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + // 1. 获取权限与商户 + var currentTenantId = tenantProvider.GetCurrentTenantId(); + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. 查询证照与合同 - var documents = await merchantRepository.GetDocumentsAsync(request.MerchantId, tenantId, cancellationToken); - var contracts = await merchantRepository.GetContractsAsync(request.MerchantId, tenantId, cancellationToken); + var merchant = isSuperAdmin + ? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + : await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken); + + if (merchant == null) + { + throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + } + + if (!isSuperAdmin && merchant.TenantId != currentTenantId) + { + throw new BusinessException(ErrorCodes.Forbidden, "禁止访问其他租户的商户"); + } + + // 2. 查询门店与租户信息 + var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken); + var storeDtos = MerchantMapping.ToStoreDtos(stores); + var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken); // 3. 返回明细 DTO - return new MerchantDetailDto - { - Merchant = MerchantMapping.ToDto(merchant), - Documents = MerchantMapping.ToDocumentDtos(documents), - Contracts = MerchantMapping.ToContractDtos(contracts) - }; + return MerchantMapping.ToDetailDto(merchant, tenant?.Name, storeDtos); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantListQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantListQueryHandler.cs new file mode 100644 index 0000000..4d0018e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantListQueryHandler.cs @@ -0,0 +1,106 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Application.Identity; +using TakeoutSaaS.Application.Identity.Abstractions; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Domain.Stores.Repositories; +using TakeoutSaaS.Domain.Tenants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 商户列表查询处理器。 +/// +public sealed class GetMerchantListQueryHandler( + IMerchantRepository merchantRepository, + IStoreRepository storeRepository, + ITenantRepository tenantRepository, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor, + IAdminAuthService adminAuthService) + : IRequestHandler> +{ + /// + public async Task> Handle( + GetMerchantListQuery request, + CancellationToken cancellationToken) + { + // 1. 校验跨租户访问权限 + var currentTenantId = tenantProvider.GetCurrentTenantId(); + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); + + if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) + { + throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户查询商户"); + } + + var effectiveTenantId = isSuperAdmin ? request.TenantId : currentTenantId; + + // 2. 查询商户列表 + var merchants = await merchantRepository.SearchAsync( + effectiveTenantId, + request.Status, + request.OperatingMode, + request.Keyword, + cancellationToken); + + if (merchants.Count == 0) + { + return new PagedResult(Array.Empty(), request.Page, request.PageSize, 0); + } + + // 3. 排序 & 分页 + var sorted = ApplySorting(merchants, request.SortBy, request.SortOrder); + var total = sorted.Count; + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + if (paged.Count == 0) + { + return new PagedResult(Array.Empty(), request.Page, request.PageSize, total); + } + + // 4. 批量查询租户名称 + var tenantIds = paged.Select(x => x.TenantId).Distinct().ToArray(); + var tenants = await tenantRepository.FindByIdsAsync(tenantIds, cancellationToken); + var tenantLookup = tenants.ToDictionary(x => x.Id, x => x.Name); + + // 5. 批量查询门店数量 + var merchantIds = paged.Select(x => x.Id).ToArray(); + var storeCounts = await storeRepository.GetStoreCountsAsync(effectiveTenantId, merchantIds, cancellationToken); + + // 6. 组装 DTO + var items = paged.Select(merchant => + { + var tenantName = tenantLookup.TryGetValue(merchant.TenantId, out var name) ? name : null; + var count = storeCounts.TryGetValue(merchant.Id, out var value) ? value : 0; + return MerchantMapping.ToListItemDto(merchant, tenantName, count); + }).ToList(); + + return new PagedResult(items, request.Page, request.PageSize, total); + } + + private static List ApplySorting( + IReadOnlyList merchants, + string? sortBy, + string? sortOrder) + { + var descending = !string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase); + return (sortBy ?? string.Empty).Trim().ToLowerInvariant() switch + { + "name" => descending ? merchants.OrderByDescending(x => x.BrandName).ToList() : merchants.OrderBy(x => x.BrandName).ToList(), + "status" => descending ? merchants.OrderByDescending(x => x.Status).ToList() : merchants.OrderBy(x => x.Status).ToList(), + "updatedat" => descending ? merchants.OrderByDescending(x => x.UpdatedAt ?? x.CreatedAt).ToList() : merchants.OrderBy(x => x.UpdatedAt ?? x.CreatedAt).ToList(), + _ => descending ? merchants.OrderByDescending(x => x.CreatedAt).ToList() : merchants.OrderBy(x => x.CreatedAt).ToList() + }; + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantReviewClaimQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantReviewClaimQueryHandler.cs new file mode 100644 index 0000000..5578635 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantReviewClaimQueryHandler.cs @@ -0,0 +1,41 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 商户审核领取信息查询处理器。 +/// +public sealed class GetMerchantReviewClaimQueryHandler(IMerchantRepository merchantRepository) + : IRequestHandler +{ + /// + public async Task Handle(GetMerchantReviewClaimQuery request, CancellationToken cancellationToken) + { + var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + ?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + + if (!merchant.ClaimedBy.HasValue) + { + return null; + } + + if (merchant.ClaimExpiresAt.HasValue && merchant.ClaimExpiresAt <= DateTime.UtcNow) + { + return null; + } + + return new ClaimInfoDto + { + MerchantId = merchant.Id, + ClaimedBy = merchant.ClaimedBy, + ClaimedByName = merchant.ClaimedByName, + ClaimedAt = merchant.ClaimedAt, + ClaimExpiresAt = merchant.ClaimExpiresAt + }; + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetPendingReviewListQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetPendingReviewListQueryHandler.cs new file mode 100644 index 0000000..d570d71 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetPendingReviewListQueryHandler.cs @@ -0,0 +1,59 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Application.App.Merchants.Queries; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Domain.Tenants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Results; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 待审核商户列表处理器。 +/// +public sealed class GetPendingReviewListQueryHandler( + IMerchantRepository merchantRepository, + ITenantRepository tenantRepository) + : IRequestHandler> +{ + /// + public async Task> Handle( + GetPendingReviewListQuery request, + CancellationToken cancellationToken) + { + var merchants = await merchantRepository.SearchAsync( + request.TenantId, + MerchantStatus.Pending, + request.OperatingMode, + request.Keyword, + cancellationToken); + + var total = merchants.Count; + var paged = merchants + .OrderByDescending(x => x.CreatedAt) + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + var tenantIds = paged.Select(x => x.TenantId).Distinct().ToArray(); + var tenants = await tenantRepository.FindByIdsAsync(tenantIds, cancellationToken); + var tenantLookup = tenants.ToDictionary(x => x.Id, x => x.Name); + + var items = paged.Select(merchant => new MerchantReviewListItemDto + { + Id = merchant.Id, + TenantId = merchant.TenantId, + TenantName = tenantLookup.TryGetValue(merchant.TenantId, out var name) ? name : null, + Name = merchant.BrandName, + OperatingMode = merchant.OperatingMode, + LicenseNumber = merchant.BusinessLicenseNumber, + Status = merchant.Status, + ClaimedByName = merchant.ClaimedByName, + ClaimedAt = merchant.ClaimedAt, + ClaimExpiresAt = merchant.ClaimExpiresAt, + CreatedAt = merchant.CreatedAt + }).ToList(); + + return new PagedResult(items, request.Page, request.PageSize, total); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReleaseClaimHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReleaseClaimHandler.cs new file mode 100644 index 0000000..a9df182 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReleaseClaimHandler.cs @@ -0,0 +1,64 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Commands; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 释放审核领取处理器。 +/// +public sealed class ReleaseClaimHandler( + IMerchantRepository merchantRepository, + ICurrentUserAccessor currentUserAccessor) + : IRequestHandler +{ + /// + public async Task Handle(ReleaseClaimCommand request, CancellationToken cancellationToken) + { + var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + ?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + + if (!merchant.ClaimedBy.HasValue) + { + return null; + } + + var now = DateTime.UtcNow; + var claimExpired = merchant.ClaimExpiresAt.HasValue && merchant.ClaimExpiresAt <= now; + + if (!claimExpired && merchant.ClaimedBy != currentUserAccessor.UserId) + { + throw new BusinessException(ErrorCodes.Conflict, $"该审核已被 {merchant.ClaimedByName} 领取"); + } + + var actorName = currentUserAccessor.IsAuthenticated + ? $"user:{currentUserAccessor.UserId}" + : "system"; + + merchant.ClaimedBy = null; + merchant.ClaimedByName = null; + merchant.ClaimedAt = null; + merchant.ClaimExpiresAt = null; + + await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken); + await merchantRepository.AddAuditLogAsync(new MerchantAuditLog + { + TenantId = merchant.TenantId, + MerchantId = merchant.Id, + Action = MerchantAuditAction.ReviewReleased, + Title = "释放审核", + Description = $"释放人:{actorName}", + OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + OperatorName = actorName + }, cancellationToken); + await merchantRepository.SaveChangesAsync(cancellationToken); + + return null; + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReviewMerchantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReviewMerchantCommandHandler.cs index 1d67950..20ddefa 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReviewMerchantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/ReviewMerchantCommandHandler.cs @@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Merchants.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Security; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Merchants.Handlers; @@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers; /// public sealed class ReviewMerchantCommandHandler( IMerchantRepository merchantRepository, - ITenantProvider tenantProvider, ICurrentUserAccessor currentUserAccessor) : IRequestHandler { @@ -29,33 +27,62 @@ public sealed class ReviewMerchantCommandHandler( public async Task Handle(ReviewMerchantCommand request, CancellationToken cancellationToken) { // 1. 读取商户 - var tenantId = tenantProvider.GetCurrentTenantId(); - var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken) + var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); - // 2. 已审核通过则直接返回 - if (request.Approve && merchant.Status == MerchantStatus.Approved) + var now = DateTime.UtcNow; + if (!merchant.ClaimedBy.HasValue || !merchant.ClaimExpiresAt.HasValue || merchant.ClaimExpiresAt <= now) { - return MerchantMapping.ToDto(merchant); + throw new BusinessException(ErrorCodes.Conflict, "请先领取审核"); } - // 3. 更新审核状态 - var previousStatus = merchant.Status; + if (merchant.ClaimedBy != currentUserAccessor.UserId) + { + throw new BusinessException(ErrorCodes.Conflict, $"该审核已被 {merchant.ClaimedByName} 领取"); + } + + if (merchant.Status != MerchantStatus.Pending) + { + throw new BusinessException(ErrorCodes.Conflict, "商户不在待审核状态"); + } + + // 2. 更新审核状态 merchant.Status = request.Approve ? MerchantStatus.Approved : MerchantStatus.Rejected; merchant.ReviewRemarks = request.Remarks; - merchant.LastReviewedAt = DateTime.UtcNow; + merchant.LastReviewedAt = now; + merchant.LastReviewedBy = ResolveOperatorId(); if (request.Approve && merchant.JoinedAt == null) { - merchant.JoinedAt = DateTime.UtcNow; + merchant.JoinedAt = now; } - // 4. 持久化与审计 + if (request.Approve) + { + merchant.IsFrozen = false; + merchant.FrozenReason = null; + merchant.FrozenAt = null; + merchant.ApprovedAt = now; + merchant.ApprovedBy = ResolveOperatorId(); + } + else + { + merchant.IsFrozen = false; + merchant.FrozenReason = null; + merchant.FrozenAt = null; + } + + merchant.ClaimedBy = null; + merchant.ClaimedByName = null; + merchant.ClaimedAt = null; + merchant.ClaimExpiresAt = null; + + // 3. 持久化与审计 await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken); await merchantRepository.AddAuditLogAsync(new MerchantAuditLog { - TenantId = tenantId, + TenantId = merchant.TenantId, MerchantId = merchant.Id, - Action = MerchantAuditAction.MerchantReviewed, + Action = request.Approve ? MerchantAuditAction.ReviewApproved : MerchantAuditAction.ReviewRejected, Title = request.Approve ? "商户审核通过" : "商户审核驳回", Description = request.Remarks, OperatorId = ResolveOperatorId(), @@ -63,7 +90,7 @@ public sealed class ReviewMerchantCommandHandler( }, cancellationToken); await merchantRepository.SaveChangesAsync(cancellationToken); - // 5. 返回 DTO + // 4. 返回 DTO return MerchantMapping.ToDto(merchant); } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/RevokeMerchantReviewHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/RevokeMerchantReviewHandler.cs new file mode 100644 index 0000000..deb76cf --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/RevokeMerchantReviewHandler.cs @@ -0,0 +1,61 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Commands; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; + +namespace TakeoutSaaS.Application.App.Merchants.Handlers; + +/// +/// 撤销商户审核处理器。 +/// +public sealed class RevokeMerchantReviewHandler( + IMerchantRepository merchantRepository, + ICurrentUserAccessor currentUserAccessor) + : IRequestHandler +{ + /// + public async Task Handle(RevokeMerchantReviewCommand request, CancellationToken cancellationToken) + { + var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + ?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在"); + + if (merchant.Status != MerchantStatus.Approved) + { + throw new BusinessException(ErrorCodes.Conflict, "商户不在已审核状态"); + } + + var now = DateTime.UtcNow; + var actorName = currentUserAccessor.IsAuthenticated + ? $"user:{currentUserAccessor.UserId}" + : "system"; + + merchant.Status = MerchantStatus.Pending; + merchant.IsFrozen = true; + merchant.FrozenReason = request.Reason; + merchant.FrozenAt = now; + merchant.ReviewRemarks = request.Reason; + merchant.LastReviewedAt = now; + merchant.LastReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId; + merchant.ClaimedBy = null; + merchant.ClaimedByName = null; + merchant.ClaimedAt = null; + merchant.ClaimExpiresAt = null; + + await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken); + await merchantRepository.AddAuditLogAsync(new MerchantAuditLog + { + TenantId = merchant.TenantId, + MerchantId = merchant.Id, + Action = MerchantAuditAction.ReviewRevoked, + Title = "撤销审核", + Description = request.Reason, + OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + OperatorName = actorName + }, cancellationToken); + await merchantRepository.SaveChangesAsync(cancellationToken); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs index ed1a879..4626003 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs @@ -2,7 +2,16 @@ using MediatR; using Microsoft.Extensions.Logging; using TakeoutSaaS.Application.App.Merchants.Commands; using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Application.Identity; +using TakeoutSaaS.Application.Identity.Abstractions; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Merchants.Enums; using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Domain.Stores.Repositories; +using TakeoutSaaS.Domain.Tenants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; +using TakeoutSaaS.Shared.Abstractions.Security; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Merchants.Handlers; @@ -12,51 +21,175 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers; /// public sealed class UpdateMerchantCommandHandler( IMerchantRepository merchantRepository, + IStoreRepository storeRepository, + ITenantRepository tenantRepository, ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor, + IAdminAuthService adminAuthService, ILogger logger) - : IRequestHandler + : IRequestHandler { /// - public async Task Handle(UpdateMerchantCommand request, CancellationToken cancellationToken) + public async Task Handle(UpdateMerchantCommand request, CancellationToken cancellationToken) { - // 1. 读取现有商户 - var tenantId = tenantProvider.GetCurrentTenantId(); - var existing = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken); - if (existing == null) + if (request.RowVersion == null || request.RowVersion.Length == 0) + { + throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空"); + } + + // 1. 获取操作者权限 + var currentTenantId = tenantProvider.GetCurrentTenantId(); + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); + + // 2. 读取商户信息 + var merchant = isSuperAdmin + ? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken) + : await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken); + + if (merchant == null) { return null; } - // 2. 更新字段 - existing.BrandName = request.BrandName.Trim(); - existing.BrandAlias = request.BrandAlias?.Trim(); - existing.LogoUrl = request.LogoUrl?.Trim(); - existing.Category = request.Category?.Trim(); - existing.ContactPhone = request.ContactPhone.Trim(); - existing.ContactEmail = request.ContactEmail?.Trim(); - existing.Status = request.Status; + if (!isSuperAdmin && merchant.TenantId != currentTenantId) + { + return null; + } - // 3. 持久化 - await merchantRepository.UpdateMerchantAsync(existing, cancellationToken); - await merchantRepository.SaveChangesAsync(cancellationToken); - logger.LogInformation("更新商户 {MerchantId} - {BrandName}", existing.Id, existing.BrandName); + // 3. 规范化输入 + var name = NormalizeRequired(request.Name, "商户名称"); + var contactPhone = NormalizeRequired(request.ContactPhone, "联系电话"); + var licenseNumber = NormalizeOptional(request.LicenseNumber); + var legalRepresentative = NormalizeOptional(request.LegalRepresentative); + var registeredAddress = NormalizeOptional(request.RegisteredAddress); + var contactEmail = NormalizeOptional(request.ContactEmail); - // 4. 返回 DTO - return MapToDto(existing); + var now = DateTime.UtcNow; + var actorId = currentUserAccessor.UserId == 0 ? (long?)null : currentUserAccessor.UserId; + var actorName = ResolveActorName(); + var changes = new List(); + var criticalChanged = false; + + TrackChange("name", merchant.BrandName, name, isCritical: true); + TrackChange("licenseNumber", merchant.BusinessLicenseNumber, licenseNumber, isCritical: true); + TrackChange("legalRepresentative", merchant.LegalPerson, legalRepresentative, isCritical: true); + TrackChange("registeredAddress", merchant.Address, registeredAddress, isCritical: true); + TrackChange("contactPhone", merchant.ContactPhone, contactPhone, isCritical: false); + TrackChange("contactEmail", merchant.ContactEmail, contactEmail, isCritical: false); + + // 4. 写入字段 + merchant.BrandName = name; + merchant.BusinessLicenseNumber = licenseNumber; + merchant.LegalPerson = legalRepresentative; + merchant.Address = registeredAddress; + merchant.ContactPhone = contactPhone; + merchant.ContactEmail = contactEmail; + merchant.RowVersion = request.RowVersion; + + var requiresReview = merchant.Status == MerchantStatus.Approved && criticalChanged; + if (requiresReview) + { + merchant.Status = MerchantStatus.Pending; + merchant.IsFrozen = true; + merchant.FrozenReason = "关键信息变更待审核"; + merchant.FrozenAt = now; + } + else if (merchant.Status == MerchantStatus.Rejected) + { + merchant.Status = MerchantStatus.Pending; + merchant.IsFrozen = false; + merchant.FrozenReason = null; + merchant.FrozenAt = null; + } + + // 5. 持久化日志与数据 + await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken); + foreach (var log in changes) + { + await merchantRepository.AddChangeLogAsync(log, cancellationToken); + } + + if (requiresReview) + { + await merchantRepository.AddAuditLogAsync(new MerchantAuditLog + { + TenantId = merchant.TenantId, + MerchantId = merchant.Id, + Action = MerchantAuditAction.ReviewPendingReApproval, + Title = "关键信息变更待审核", + Description = "关键信息修改后已进入待审核状态", + OperatorId = actorId, + OperatorName = actorName + }, cancellationToken); + } + + try + { + await merchantRepository.SaveChangesAsync(cancellationToken); + } + catch (Exception exception) when (IsConcurrencyException(exception)) + { + throw new BusinessException(ErrorCodes.Conflict, "商户信息已被修改,请刷新后重试"); + } + + logger.LogInformation("更新商户 {MerchantId} - {Name}", merchant.Id, merchant.BrandName); + + // 6. 返回更新结果 + var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken); + var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken); + var detail = MerchantMapping.ToDetailDto(merchant, tenant?.Name, MerchantMapping.ToStoreDtos(stores)); + + return new UpdateMerchantResultDto + { + Merchant = detail, + RequiresReview = requiresReview + }; + + void TrackChange(string fieldName, string? oldValue, string? newValue, bool isCritical) + { + var normalizedOld = NormalizeOptional(oldValue); + var normalizedNew = NormalizeOptional(newValue); + if (string.Equals(normalizedOld, normalizedNew, StringComparison.Ordinal)) + { + return; + } + + if (isCritical) + { + criticalChanged = true; + } + + changes.Add(new MerchantChangeLog + { + TenantId = merchant.TenantId, + MerchantId = merchant.Id, + FieldName = fieldName, + OldValue = normalizedOld, + NewValue = normalizedNew, + ChangedBy = actorId, + ChangedByName = actorName, + ChangeType = "Update" + }); + } } - private static MerchantDto MapToDto(Domain.Merchants.Entities.Merchant merchant) => new() + private static string NormalizeRequired(string? value, string fieldName) { - Id = merchant.Id, - TenantId = merchant.TenantId, - BrandName = merchant.BrandName, - BrandAlias = merchant.BrandAlias, - LogoUrl = merchant.LogoUrl, - Category = merchant.Category, - ContactPhone = merchant.ContactPhone, - ContactEmail = merchant.ContactEmail, - Status = merchant.Status, - JoinedAt = merchant.JoinedAt, - CreatedAt = merchant.CreatedAt - }; + if (string.IsNullOrWhiteSpace(value)) + { + throw new BusinessException(ErrorCodes.ValidationFailed, $"{fieldName}不能为空"); + } + + return value.Trim(); + } + + private static string? NormalizeOptional(string? value) + => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + + private string ResolveActorName() + => currentUserAccessor.IsAuthenticated ? $"user:{currentUserAccessor.UserId}" : "system"; + + private static bool IsConcurrencyException(Exception exception) + => string.Equals(exception.GetType().Name, "DbUpdateConcurrencyException", StringComparison.Ordinal); } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/MerchantMapping.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/MerchantMapping.cs index 157dbc1..29e181b 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/MerchantMapping.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/MerchantMapping.cs @@ -1,5 +1,6 @@ using TakeoutSaaS.Application.App.Merchants.Dto; using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Stores.Entities; namespace TakeoutSaaS.Application.App.Merchants; @@ -28,6 +29,53 @@ internal static class MerchantMapping CreatedAt = merchant.CreatedAt }; + /// + /// 将商户实体映射为列表项 DTO。 + /// + public static MerchantListItemDto ToListItemDto(Merchant merchant, string? tenantName, int storeCount) => new() + { + Id = merchant.Id, + TenantId = merchant.TenantId, + TenantName = tenantName, + Name = merchant.BrandName, + OperatingMode = merchant.OperatingMode, + LicenseNumber = merchant.BusinessLicenseNumber, + Status = merchant.Status, + IsFrozen = merchant.IsFrozen, + StoreCount = storeCount, + CreatedAt = merchant.CreatedAt, + UpdatedAt = merchant.UpdatedAt + }; + + /// + /// 将商户实体映射为详情 DTO。 + /// + public static MerchantDetailDto ToDetailDto(Merchant merchant, string? tenantName, IReadOnlyList stores) => new() + { + Id = merchant.Id, + TenantId = merchant.TenantId, + TenantName = tenantName, + Name = merchant.BrandName, + OperatingMode = merchant.OperatingMode, + LicenseNumber = merchant.BusinessLicenseNumber, + LegalRepresentative = merchant.LegalPerson, + RegisteredAddress = merchant.Address, + ContactPhone = merchant.ContactPhone, + ContactEmail = merchant.ContactEmail, + Status = merchant.Status, + IsFrozen = merchant.IsFrozen, + FrozenReason = merchant.FrozenReason, + FrozenAt = merchant.FrozenAt, + ApprovedBy = merchant.ApprovedBy, + ApprovedAt = merchant.ApprovedAt, + Stores = stores, + RowVersion = merchant.RowVersion, + CreatedAt = merchant.CreatedAt, + CreatedBy = merchant.CreatedBy, + UpdatedAt = merchant.UpdatedAt, + UpdatedBy = merchant.UpdatedBy + }; + /// /// 将商户证照实体映射为 DTO。 /// @@ -76,12 +124,42 @@ internal static class MerchantMapping Id = log.Id, MerchantId = log.MerchantId, Action = log.Action, + OperatorId = log.OperatorId, Title = log.Title, Description = log.Description, OperatorName = log.OperatorName, + IpAddress = log.IpAddress, CreatedAt = log.CreatedAt }; + /// + /// 将商户变更日志实体映射为 DTO。 + /// + public static MerchantChangeLogDto ToDto(MerchantChangeLog log) => new() + { + Id = log.Id, + FieldName = log.FieldName, + OldValue = log.OldValue, + NewValue = log.NewValue, + ChangedBy = log.ChangedBy, + ChangedByName = log.ChangedByName, + ChangedAt = log.CreatedAt, + ChangeReason = log.ChangeReason + }; + + /// + /// 将门店实体映射为 DTO。 + /// + public static StoreDto ToStoreDto(Store store) => new() + { + Id = store.Id, + Name = store.Name, + LicenseNumber = store.BusinessLicenseNumber, + ContactPhone = store.Phone, + Address = store.Address, + Status = store.Status + }; + /// /// 将商户分类实体映射为 DTO。 /// @@ -119,4 +197,10 @@ internal static class MerchantMapping /// 分类 DTO 列表。 public static IReadOnlyList ToCategoryDtos(IEnumerable categories) => categories.Select(ToDto).ToList(); + + /// + /// 将门店集合映射为 DTO 集合。 + /// + public static IReadOnlyList ToStoreDtos(IEnumerable stores) + => stores.Select(ToStoreDto).ToList(); } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/ExportMerchantPdfQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/ExportMerchantPdfQuery.cs new file mode 100644 index 0000000..ea009a3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/ExportMerchantPdfQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace TakeoutSaaS.Application.App.Merchants.Queries; + +/// +/// 导出商户 PDF 查询。 +/// +public sealed record ExportMerchantPdfQuery(long MerchantId) : IRequest; diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantAuditHistoryQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantAuditHistoryQuery.cs new file mode 100644 index 0000000..ebbdeda --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantAuditHistoryQuery.cs @@ -0,0 +1,9 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; + +namespace TakeoutSaaS.Application.App.Merchants.Queries; + +/// +/// 商户审核历史查询。 +/// +public sealed record GetMerchantAuditHistoryQuery(long MerchantId) : IRequest>; diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantChangeHistoryQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantChangeHistoryQuery.cs new file mode 100644 index 0000000..8e3e5f3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantChangeHistoryQuery.cs @@ -0,0 +1,10 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; + +namespace TakeoutSaaS.Application.App.Merchants.Queries; + +/// +/// 获取商户变更历史。 +/// +public sealed record GetMerchantChangeHistoryQuery(long MerchantId, string? FieldName = null) + : IRequest>; diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantListQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantListQuery.cs new file mode 100644 index 0000000..6998b15 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantListQuery.cs @@ -0,0 +1,53 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Domain.Common.Enums; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Results; + +namespace TakeoutSaaS.Application.App.Merchants.Queries; + +/// +/// 商户列表查询。 +/// +public sealed class GetMerchantListQuery : IRequest> +{ + /// + /// 关键词(商户名称/营业执照号)。 + /// + public string? Keyword { get; init; } + + /// + /// 状态过滤。 + /// + public MerchantStatus? Status { get; init; } + + /// + /// 经营模式过滤。 + /// + public OperatingMode? OperatingMode { get; init; } + + /// + /// 租户过滤(管理员可用)。 + /// + public long? TenantId { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(createdAt/updatedAt/name/status)。 + /// + public string? SortBy { get; init; } + + /// + /// 排序方向(asc/desc)。 + /// + public string? SortOrder { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantReviewClaimQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantReviewClaimQuery.cs new file mode 100644 index 0000000..e829e3e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantReviewClaimQuery.cs @@ -0,0 +1,9 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; + +namespace TakeoutSaaS.Application.App.Merchants.Queries; + +/// +/// 获取商户审核领取信息查询。 +/// +public sealed record GetMerchantReviewClaimQuery(long MerchantId) : IRequest; diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetPendingReviewListQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetPendingReviewListQuery.cs new file mode 100644 index 0000000..3300be0 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetPendingReviewListQuery.cs @@ -0,0 +1,37 @@ +using MediatR; +using TakeoutSaaS.Application.App.Merchants.Dto; +using TakeoutSaaS.Domain.Common.Enums; +using TakeoutSaaS.Shared.Abstractions.Results; + +namespace TakeoutSaaS.Application.App.Merchants.Queries; + +/// +/// 待审核商户列表查询。 +/// +public sealed class GetPendingReviewListQuery : IRequest> +{ + /// + /// 关键词(商户名称/营业执照号)。 + /// + public string? Keyword { get; init; } + + /// + /// 经营模式筛选。 + /// + public OperatingMode? OperatingMode { get; init; } + + /// + /// 租户筛选。 + /// + public long? TenantId { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/ReviewMerchantValidator.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/ReviewMerchantValidator.cs new file mode 100644 index 0000000..69ab1c7 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/ReviewMerchantValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Merchants.Commands; + +namespace TakeoutSaaS.Application.App.Merchants.Validators; + +/// +/// 商户审核命令验证器。 +/// +public sealed class ReviewMerchantValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public ReviewMerchantValidator() + { + RuleFor(x => x.MerchantId).GreaterThan(0); + RuleFor(x => x.Remarks) + .NotEmpty() + .When(x => !x.Approve); + RuleFor(x => x.Remarks) + .MaximumLength(500) + .When(x => !string.IsNullOrWhiteSpace(x.Remarks)); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs index 6781a0b..248f60c 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs @@ -14,11 +14,13 @@ public sealed class UpdateMerchantCommandValidator : AbstractValidator x.MerchantId).GreaterThan(0); - RuleFor(x => x.BrandName).NotEmpty().MaximumLength(128); - RuleFor(x => x.BrandAlias).MaximumLength(64); - RuleFor(x => x.LogoUrl).MaximumLength(256); - RuleFor(x => x.Category).MaximumLength(64); + RuleFor(x => x.Name).NotEmpty().MaximumLength(128); + RuleFor(x => x.LicenseNumber).MaximumLength(64); + RuleFor(x => x.LegalRepresentative).MaximumLength(64); + RuleFor(x => x.RegisteredAddress).MaximumLength(256); RuleFor(x => x.ContactPhone).NotEmpty().MaximumLength(32); - RuleFor(x => x.ContactEmail).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.ContactEmail)); + RuleFor(x => x.ContactEmail).EmailAddress().MaximumLength(128) + .When(x => !string.IsNullOrWhiteSpace(x.ContactEmail)); + RuleFor(x => x.RowVersion).NotEmpty(); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Commands/ReviewTenantCommand.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Commands/ReviewTenantCommand.cs index 1d87313..7e7a0ae 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Commands/ReviewTenantCommand.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Commands/ReviewTenantCommand.cs @@ -1,6 +1,7 @@ using MediatR; using System.ComponentModel.DataAnnotations; using TakeoutSaaS.Application.App.Tenants.Dto; +using TakeoutSaaS.Domain.Common.Enums; namespace TakeoutSaaS.Application.App.Tenants.Commands; @@ -29,4 +30,9 @@ public sealed record ReviewTenantCommand : IRequest /// 审核通过后续费时长(月)。 /// public int? RenewMonths { get; init; } + + /// + /// 经营模式(审核通过时必填)。 + /// + public OperatingMode? OperatingMode { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Dto/TenantDto.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Dto/TenantDto.cs index 7f490ad..a565e29 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Dto/TenantDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Dto/TenantDto.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Common.Enums; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Shared.Abstractions.Serialization; @@ -55,6 +56,11 @@ public sealed class TenantDto /// public TenantVerificationStatus VerificationStatus { get; init; } + /// + /// 经营模式。 + /// + public OperatingMode? OperatingMode { get; init; } + /// /// 当前套餐 ID。 /// diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs index d45b6e9..8e4e477 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs @@ -1,6 +1,9 @@ using MediatR; using TakeoutSaaS.Application.App.Tenants.Commands; using TakeoutSaaS.Application.App.Tenants.Dto; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Domain.Merchants.Repositories; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; @@ -14,6 +17,7 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// public sealed class ReviewTenantCommandHandler( ITenantRepository tenantRepository, + IMerchantRepository merchantRepository, ICurrentUserAccessor currentUserAccessor) : IRequestHandler { @@ -53,19 +57,25 @@ public sealed class ReviewTenantCommandHandler( // 4. 更新租户与订阅状态 if (request.Approve) { + if (!request.OperatingMode.HasValue) + { + throw new BusinessException(ErrorCodes.ValidationFailed, "审核通过时必须选择经营模式"); + } + var renewMonths = request.RenewMonths ?? 0; if (renewMonths <= 0) { throw new BusinessException(ErrorCodes.ValidationFailed, "续费时长必须为正整数(月)"); } + var now = DateTime.UtcNow; verification.Status = TenantVerificationStatus.Approved; tenant.Status = TenantStatus.Active; + tenant.OperatingMode = request.OperatingMode; if (subscription != null) { subscription.Status = SubscriptionStatus.Active; - var now = DateTime.UtcNow; if (subscription.EffectiveFrom == default || subscription.EffectiveFrom > now) { subscription.EffectiveFrom = now; @@ -92,6 +102,69 @@ public sealed class ReviewTenantCommandHandler( { throw new BusinessException(ErrorCodes.BadRequest, "订阅不存在,无法续费"); } + + var existingMerchant = await merchantRepository.FindByTenantIdAsync(tenant.Id, cancellationToken); + if (existingMerchant == null) + { + var merchant = new Merchant + { + TenantId = tenant.Id, + BrandName = tenant.Name, + BrandAlias = tenant.ShortName, + Category = tenant.Industry, + ContactPhone = tenant.ContactPhone ?? string.Empty, + ContactEmail = tenant.ContactEmail, + BusinessLicenseNumber = verification.BusinessLicenseNumber, + BusinessLicenseImageUrl = verification.BusinessLicenseUrl, + LegalPerson = verification.LegalPersonName, + Province = tenant.Province, + City = tenant.City, + Address = tenant.Address, + Status = MerchantStatus.Approved, + OperatingMode = request.OperatingMode, + ApprovedAt = now, + ApprovedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + JoinedAt = now, + LastReviewedAt = now, + LastReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + IsFrozen = false + }; + + await merchantRepository.AddMerchantAsync(merchant, cancellationToken); + await merchantRepository.AddAuditLogAsync(new MerchantAuditLog + { + TenantId = tenant.Id, + MerchantId = merchant.Id, + Action = MerchantAuditAction.ReviewApproved, + Title = "商户审核通过", + Description = request.Reason, + OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + OperatorName = actorName + }, cancellationToken); + } + else + { + existingMerchant.Status = MerchantStatus.Approved; + existingMerchant.OperatingMode = request.OperatingMode; + existingMerchant.ApprovedAt = now; + existingMerchant.ApprovedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId; + existingMerchant.LastReviewedAt = now; + existingMerchant.LastReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId; + existingMerchant.IsFrozen = false; + existingMerchant.FrozenReason = null; + existingMerchant.FrozenAt = null; + await merchantRepository.UpdateMerchantAsync(existingMerchant, cancellationToken); + await merchantRepository.AddAuditLogAsync(new MerchantAuditLog + { + TenantId = tenant.Id, + MerchantId = existingMerchant.Id, + Action = MerchantAuditAction.ReviewApproved, + Title = "商户审核通过", + Description = request.Reason, + OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, + OperatorName = actorName + }, cancellationToken); + } } else { @@ -141,6 +214,7 @@ public sealed class ReviewTenantCommandHandler( // 8. 保存并返回 DTO await tenantRepository.SaveChangesAsync(cancellationToken); + await merchantRepository.SaveChangesAsync(cancellationToken); return TenantMapping.ToDto(tenant, subscription, verification); } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/TenantMapping.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/TenantMapping.cs index 125f991..0616ea9 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/TenantMapping.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/TenantMapping.cs @@ -28,6 +28,7 @@ internal static class TenantMapping ContactEmail = tenant.ContactEmail, Status = tenant.Status, VerificationStatus = verification?.Status ?? Domain.Tenants.Enums.TenantVerificationStatus.Draft, + OperatingMode = tenant.OperatingMode, CurrentPackageId = subscription?.TenantPackageId, EffectiveFrom = subscription?.EffectiveFrom ?? tenant.EffectiveFrom, EffectiveTo = subscription?.EffectiveTo ?? tenant.EffectiveTo, diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/ReviewTenantValidator.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/ReviewTenantValidator.cs new file mode 100644 index 0000000..84bd0a2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/ReviewTenantValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Tenants.Commands; + +namespace TakeoutSaaS.Application.App.Tenants.Validators; + +/// +/// 租户审核命令验证器。 +/// +public sealed class ReviewTenantValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public ReviewTenantValidator() + { + RuleFor(x => x.TenantId).GreaterThan(0); + RuleFor(x => x.Reason) + .NotEmpty() + .When(x => !x.Approve); + RuleFor(x => x.OperatingMode) + .NotNull() + .When(x => x.Approve); + RuleFor(x => x.RenewMonths) + .NotNull() + .GreaterThan(0) + .When(x => x.Approve); + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs b/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs index 0e0b08d..4b8022b 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs @@ -8,5 +8,5 @@ public static class MenuPolicy /// /// 是否允许维护菜单(创建/更新/删除)。 /// - public const bool CanMaintainMenus = false; + public static bool CanMaintainMenus { get; } = false; } diff --git a/src/Domain/TakeoutSaaS.Domain/Common/Enums/OperatingMode.cs b/src/Domain/TakeoutSaaS.Domain/Common/Enums/OperatingMode.cs new file mode 100644 index 0000000..b8961cb --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Common/Enums/OperatingMode.cs @@ -0,0 +1,17 @@ +namespace TakeoutSaaS.Domain.Common.Enums; + +/// +/// 经营模式。 +/// +public enum OperatingMode +{ + /// + /// 同一主体。 + /// + SameEntity = 1, + + /// + /// 不同主体。 + /// + DifferentEntity = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs index d05ae41..e6b0f73 100644 --- a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs @@ -1,3 +1,4 @@ +using TakeoutSaaS.Domain.Common.Enums; using TakeoutSaaS.Domain.Merchants.Enums; using TakeoutSaaS.Shared.Abstractions.Entities; @@ -103,6 +104,31 @@ public sealed class Merchant : MultiTenantEntityBase /// public MerchantStatus Status { get; set; } = MerchantStatus.Pending; + /// + /// 经营模式(同一主体/不同主体)。 + /// + public OperatingMode? OperatingMode { get; set; } + + /// + /// 是否冻结业务。 + /// + public bool IsFrozen { get; set; } + + /// + /// 冻结原因。 + /// + public string? FrozenReason { get; set; } + + /// + /// 冻结时间。 + /// + public DateTime? FrozenAt { get; set; } + + /// + /// 最近一次审核人。 + /// + public long? LastReviewedBy { get; set; } + /// /// 审核备注或驳回原因。 /// @@ -117,4 +143,39 @@ public sealed class Merchant : MultiTenantEntityBase /// 最近一次审核时间。 /// public DateTime? LastReviewedAt { get; set; } + + /// + /// 审核通过人。 + /// + public long? ApprovedBy { get; set; } + + /// + /// 审核通过时间。 + /// + public DateTime? ApprovedAt { get; set; } + + /// + /// 当前领取人。 + /// + public long? ClaimedBy { get; set; } + + /// + /// 当前领取人姓名。 + /// + public string? ClaimedByName { get; set; } + + /// + /// 领取时间。 + /// + public DateTime? ClaimedAt { get; set; } + + /// + /// 领取过期时间。 + /// + public DateTime? ClaimExpiresAt { get; set; } + + /// + /// 并发控制版本。 + /// + public byte[] RowVersion { get; set; } = Array.Empty(); } diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantAuditLog.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantAuditLog.cs index e41d3e6..2f0a492 100644 --- a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantAuditLog.cs +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantAuditLog.cs @@ -37,4 +37,9 @@ public sealed class MerchantAuditLog : MultiTenantEntityBase /// 操作人名称。 /// public string? OperatorName { get; set; } + + /// + /// 操作 IP。 + /// + public string? IpAddress { get; set; } } diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantChangeLog.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantChangeLog.cs new file mode 100644 index 0000000..a67e98f --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantChangeLog.cs @@ -0,0 +1,49 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Merchants.Entities; + +/// +/// 商户变更日志。 +/// +public sealed class MerchantChangeLog : MultiTenantEntityBase +{ + /// + /// 商户标识。 + /// + public long MerchantId { get; set; } + + /// + /// 变更字段名。 + /// + public string FieldName { get; set; } = string.Empty; + + /// + /// 变更前值。 + /// + public string? OldValue { get; set; } + + /// + /// 变更后值。 + /// + public string? NewValue { get; set; } + + /// + /// 变更类型。 + /// + public string ChangeType { get; set; } = "Update"; + + /// + /// 变更人 ID。 + /// + public long? ChangedBy { get; set; } + + /// + /// 变更人名称。 + /// + public string? ChangedByName { get; set; } + + /// + /// 变更原因。 + /// + public string? ChangeReason { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantAuditAction.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantAuditAction.cs index 330f6c4..865b1ec 100644 --- a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantAuditAction.cs +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantAuditAction.cs @@ -33,5 +33,40 @@ public enum MerchantAuditAction /// /// 商户审核结果。 /// - MerchantReviewed = 5 + MerchantReviewed = 5, + + /// + /// 领取审核。 + /// + ReviewClaimed = 6, + + /// + /// 释放审核。 + /// + ReviewReleased = 7, + + /// + /// 审核通过。 + /// + ReviewApproved = 8, + + /// + /// 审核驳回。 + /// + ReviewRejected = 9, + + /// + /// 撤销审核。 + /// + ReviewRevoked = 10, + + /// + /// 关键信息变更进入待审核。 + /// + ReviewPendingReApproval = 11, + + /// + /// 强制接管审核。 + /// + ReviewForceClaimed = 12 } diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs index 1983bed..1dd5a0f 100644 --- a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs @@ -1,3 +1,4 @@ +using TakeoutSaaS.Domain.Common.Enums; using TakeoutSaaS.Domain.Merchants.Entities; using TakeoutSaaS.Domain.Merchants.Enums; @@ -17,6 +18,22 @@ public interface IMerchantRepository /// 商户实体或 null。 Task FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取商户(忽略租户过滤)。 + /// + /// 商户 ID。 + /// 取消标记。 + /// 商户实体或 null。 + Task FindByIdAsync(long merchantId, CancellationToken cancellationToken = default); + + /// + /// 依据租户标识获取商户(忽略租户过滤)。 + /// + /// 租户 ID。 + /// 取消标记。 + /// 商户实体或 null。 + Task FindByTenantIdAsync(long tenantId, CancellationToken cancellationToken = default); + /// /// 按状态筛选商户列表。 /// @@ -26,6 +43,22 @@ public interface IMerchantRepository /// 商户集合。 Task> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default); + /// + /// 按条件筛选商户列表(支持跨租户)。 + /// + /// 租户 ID,为 null 时查询全部租户。 + /// 状态过滤。 + /// 经营模式过滤。 + /// 关键词过滤。 + /// 取消标记。 + /// 商户集合。 + Task> SearchAsync( + long? tenantId, + MerchantStatus? status, + OperatingMode? operatingMode, + string? keyword, + CancellationToken cancellationToken = default); + /// /// 获取指定商户的员工列表。 /// @@ -168,6 +201,14 @@ public interface IMerchantRepository /// 异步任务。 Task AddAuditLogAsync(MerchantAuditLog log, CancellationToken cancellationToken = default); + /// + /// 记录变更日志。 + /// + /// 变更日志实体。 + /// 取消标记。 + /// 异步任务。 + Task AddChangeLogAsync(MerchantChangeLog log, CancellationToken cancellationToken = default); + /// /// 获取审核日志。 /// @@ -176,4 +217,18 @@ public interface IMerchantRepository /// 取消标记。 /// 审核日志列表。 Task> GetAuditLogsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default); + + /// + /// 获取变更日志。 + /// + /// 商户 ID。 + /// 租户 ID。 + /// 字段过滤。 + /// 取消标记。 + /// 变更日志列表。 + Task> GetChangeLogsAsync( + long merchantId, + long tenantId, + string? fieldName = null, + CancellationToken cancellationToken = default); } diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Services/IMerchantExportService.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Services/IMerchantExportService.cs new file mode 100644 index 0000000..19912e5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Services/IMerchantExportService.cs @@ -0,0 +1,26 @@ +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Stores.Entities; + +namespace TakeoutSaaS.Domain.Merchants.Services; + +/// +/// 商户导出服务接口。 +/// +public interface IMerchantExportService +{ + /// + /// 导出为 PDF。 + /// + /// 商户主体。 + /// 租户名称。 + /// 门店列表。 + /// 审核历史。 + /// 取消标记。 + /// PDF 字节数组。 + Task ExportToPdfAsync( + Merchant merchant, + string? tenantName, + IReadOnlyList stores, + IReadOnlyList auditLogs, + CancellationToken cancellationToken = default); +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs index f3c8e44..b4364e2 100644 --- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs @@ -33,6 +33,26 @@ public sealed class Store : MultiTenantEntityBase /// public string? ManagerName { get; set; } + /// + /// 门店营业执照号(主体不一致模式使用)。 + /// + public string? BusinessLicenseNumber { get; set; } + + /// + /// 门店法人(主体不一致模式使用)。 + /// + public string? LegalRepresentative { get; set; } + + /// + /// 门店注册地址(主体不一致模式使用)。 + /// + public string? RegisteredAddress { get; set; } + + /// + /// 门店营业执照图片地址(主体不一致模式使用)。 + /// + public string? BusinessLicenseImageUrl { get; set; } + /// /// 门店当前运营状态。 /// diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs index fd941bc..9a1b65c 100644 --- a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs @@ -13,11 +13,21 @@ public interface IStoreRepository /// Task FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 获取指定商户的门店列表。 + /// + Task> GetByMerchantIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default); + /// /// 按租户筛选门店列表。 /// Task> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default); + /// + /// 获取指定商户集合的门店数量。 + /// + Task> GetStoreCountsAsync(long? tenantId, IReadOnlyCollection merchantIds, CancellationToken cancellationToken = default); + /// /// 获取门店营业时段。 /// diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs index b2823e3..d689c2b 100644 --- a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs @@ -1,3 +1,4 @@ +using TakeoutSaaS.Domain.Common.Enums; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Shared.Abstractions.Entities; @@ -93,6 +94,11 @@ public sealed class Tenant : AuditableEntityBase /// public TenantStatus Status { get; set; } = TenantStatus.PendingReview; + /// + /// 经营模式(同一主体/不同主体)。 + /// + public OperatingMode? OperatingMode { get; set; } + /// /// 服务生效时间(UTC)。 /// diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs index 4265143..08e10db 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using TakeoutSaaS.Domain.Deliveries.Repositories; using TakeoutSaaS.Domain.Inventory.Repositories; using TakeoutSaaS.Domain.Merchants.Repositories; +using TakeoutSaaS.Domain.Merchants.Services; using TakeoutSaaS.Domain.Orders.Repositories; using TakeoutSaaS.Domain.Payments.Repositories; using TakeoutSaaS.Domain.Products.Repositories; @@ -63,6 +64,7 @@ public static class AppServiceCollectionExtensions // 1. 账单领域/导出服务 services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddOptions() .Bind(configuration.GetSection(AppSeedOptions.SectionName)) diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs index 40a3e0d..173cccb 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs @@ -469,6 +469,7 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.Industry).HasMaxLength(64); builder.Property(x => x.LogoUrl).HasColumnType("text"); builder.Property(x => x.Remarks).HasMaxLength(512); + builder.Property(x => x.OperatingMode).HasConversion(); builder.HasIndex(x => x.Code).IsUnique(); builder.HasIndex(x => x.ContactPhone).IsUnique(); } @@ -533,7 +534,17 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.District).HasMaxLength(64); builder.Property(x => x.Address).HasMaxLength(256); builder.Property(x => x.ReviewRemarks).HasMaxLength(512); + builder.Property(x => x.OperatingMode).HasConversion(); + builder.Property(x => x.IsFrozen).HasDefaultValue(false); + builder.Property(x => x.FrozenReason).HasMaxLength(500); + builder.Property(x => x.ClaimedByName).HasMaxLength(100); + builder.Property(x => x.RowVersion) + .IsRowVersion() + .IsConcurrencyToken() + .HasColumnType("bytea"); builder.HasIndex(x => x.TenantId); + builder.HasIndex(x => new { x.TenantId, x.Status }); + builder.HasIndex(x => x.ClaimedBy); } private static void ConfigureStore(EntityTypeBuilder builder) @@ -544,6 +555,10 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Phone).HasMaxLength(32); builder.Property(x => x.ManagerName).HasMaxLength(64); + builder.Property(x => x.BusinessLicenseNumber).HasMaxLength(50); + builder.Property(x => x.LegalRepresentative).HasMaxLength(100); + builder.Property(x => x.RegisteredAddress).HasMaxLength(500); + builder.Property(x => x.BusinessLicenseImageUrl).HasMaxLength(500); builder.Property(x => x.Province).HasMaxLength(64); builder.Property(x => x.City).HasMaxLength(64); builder.Property(x => x.District).HasMaxLength(64); @@ -553,6 +568,9 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.DeliveryRadiusKm).HasPrecision(6, 2); builder.HasIndex(x => new { x.TenantId, x.MerchantId }); builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); + builder.HasIndex(x => new { x.MerchantId, x.BusinessLicenseNumber }) + .IsUnique() + .HasFilter("\"BusinessLicenseNumber\" IS NOT NULL AND \"Status\" <> 3"); } private static void ConfigureProductCategory(EntityTypeBuilder builder) diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs index db41cdd..3c50cd5 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using TakeoutSaaS.Domain.Common.Enums; using TakeoutSaaS.Domain.Merchants.Entities; using TakeoutSaaS.Domain.Merchants.Enums; using TakeoutSaaS.Domain.Merchants.Repositories; @@ -24,6 +25,26 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLog .FirstOrDefaultAsync(cancellationToken); } + /// + public Task FindByIdAsync(long merchantId, CancellationToken cancellationToken = default) + { + return context.Merchants + .IgnoreQueryFilters() + .AsNoTracking() + .Where(x => x.Id == merchantId) + .FirstOrDefaultAsync(cancellationToken); + } + + /// + public Task FindByTenantIdAsync(long tenantId, CancellationToken cancellationToken = default) + { + return context.Merchants + .IgnoreQueryFilters() + .AsNoTracking() + .Where(x => x.TenantId == tenantId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public async Task> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default) { @@ -208,9 +229,79 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLog public async Task> GetAuditLogsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default) { return await logsContext.MerchantAuditLogs + .IgnoreQueryFilters() .AsNoTracking() .Where(x => x.TenantId == tenantId && x.MerchantId == merchantId) .OrderByDescending(x => x.CreatedAt) .ToListAsync(cancellationToken); } + + /// + public async Task> SearchAsync( + long? tenantId, + MerchantStatus? status, + OperatingMode? operatingMode, + string? keyword, + CancellationToken cancellationToken = default) + { + var query = context.Merchants + .IgnoreQueryFilters() + .AsNoTracking() + .AsQueryable(); + + if (tenantId.HasValue && tenantId.Value > 0) + { + query = query.Where(x => x.TenantId == tenantId.Value); + } + + if (status.HasValue) + { + query = query.Where(x => x.Status == status.Value); + } + + if (operatingMode.HasValue) + { + query = query.Where(x => x.OperatingMode == operatingMode.Value); + } + + if (!string.IsNullOrWhiteSpace(keyword)) + { + var normalized = keyword.Trim(); + query = query.Where(x => + EF.Functions.ILike(x.BrandName, $"%{normalized}%") || + EF.Functions.ILike(x.BusinessLicenseNumber ?? string.Empty, $"%{normalized}%")); + } + + return await query + .OrderByDescending(x => x.CreatedAt) + .ToListAsync(cancellationToken); + } + + /// + public Task AddChangeLogAsync(MerchantChangeLog log, CancellationToken cancellationToken = default) + { + return logsContext.MerchantChangeLogs.AddAsync(log, cancellationToken).AsTask(); + } + + /// + public async Task> GetChangeLogsAsync( + long merchantId, + long tenantId, + string? fieldName = null, + CancellationToken cancellationToken = default) + { + var query = logsContext.MerchantChangeLogs + .IgnoreQueryFilters() + .AsNoTracking() + .Where(x => x.TenantId == tenantId && x.MerchantId == merchantId); + + if (!string.IsNullOrWhiteSpace(fieldName)) + { + query = query.Where(x => x.FieldName == fieldName); + } + + return await query + .OrderByDescending(x => x.CreatedAt) + .ToListAsync(cancellationToken); + } } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs index 23c67ce..665e212 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs @@ -25,6 +25,16 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos .FirstOrDefaultAsync(cancellationToken); } + /// + public async Task> GetByMerchantIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default) + { + return await context.Stores + .AsNoTracking() + .Where(x => x.TenantId == tenantId && x.MerchantId == merchantId) + .OrderBy(x => x.Name) + .ToListAsync(cancellationToken); + } + /// public async Task> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default) { @@ -44,6 +54,31 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos return stores; } + /// + public async Task> GetStoreCountsAsync(long? tenantId, IReadOnlyCollection merchantIds, CancellationToken cancellationToken = default) + { + if (merchantIds.Count == 0) + { + return new Dictionary(); + } + + var query = context.Stores.AsNoTracking(); + if (!tenantId.HasValue || tenantId.Value <= 0) + { + query = query.IgnoreQueryFilters(); + } + else + { + query = query.Where(x => x.TenantId == tenantId.Value); + } + + return await query + .Where(x => merchantIds.Contains(x.MerchantId)) + .GroupBy(x => x.MerchantId) + .Select(group => new { group.Key, Count = group.Count() }) + .ToDictionaryAsync(x => x.Key, x => x.Count, cancellationToken); + } + /// public async Task> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default) { diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/MerchantExportService.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/MerchantExportService.cs new file mode 100644 index 0000000..dd56293 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/MerchantExportService.cs @@ -0,0 +1,152 @@ +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Globalization; +using TakeoutSaaS.Domain.Common.Enums; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Domain.Merchants.Services; +using TakeoutSaaS.Domain.Stores.Entities; +using TakeoutSaaS.Domain.Stores.Enums; + +namespace TakeoutSaaS.Infrastructure.App.Services; + +/// +/// 商户导出服务实现(PDF)。 +/// +public sealed class MerchantExportService : IMerchantExportService +{ + public MerchantExportService() + { + QuestPDF.Settings.License = LicenseType.Community; + } + + /// + public Task ExportToPdfAsync( + Merchant merchant, + string? tenantName, + IReadOnlyList stores, + IReadOnlyList auditLogs, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(merchant); + + var safeStores = stores ?? Array.Empty(); + var safeAuditLogs = auditLogs ?? Array.Empty(); + + var document = Document.Create(container => + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(24); + page.DefaultTextStyle(x => x.FontSize(10)); + + page.Content().Column(column => + { + column.Spacing(10); + column.Item().Text("Merchant Export").FontSize(16).SemiBold(); + + column.Item().Element(section => BuildBasicSection(section, merchant, tenantName)); + column.Item().Element(section => BuildStoresSection(section, safeStores, cancellationToken)); + column.Item().Element(section => BuildAuditSection(section, safeAuditLogs, cancellationToken)); + }); + }); + }); + + return Task.FromResult(document.GeneratePdf()); + } + + private static void BuildBasicSection(IContainer container, Merchant merchant, string? tenantName) + { + container.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(column => + { + column.Spacing(4); + column.Item().Text("Basic Information").SemiBold(); + column.Item().Text($"Merchant: {merchant.BrandName}"); + column.Item().Text($"Tenant: {tenantName ?? "-"} (ID: {merchant.TenantId})"); + column.Item().Text($"Operating Mode: {ResolveOperatingMode(merchant.OperatingMode)}"); + column.Item().Text($"Status: {merchant.Status}"); + column.Item().Text($"Frozen: {(merchant.IsFrozen ? "Yes" : "No")}"); + column.Item().Text($"License Number: {merchant.BusinessLicenseNumber ?? "-"}"); + column.Item().Text($"Legal Representative: {merchant.LegalPerson ?? "-"}"); + column.Item().Text($"Registered Address: {merchant.Address ?? "-"}"); + column.Item().Text($"Contact Phone: {merchant.ContactPhone}"); + column.Item().Text($"Contact Email: {merchant.ContactEmail ?? "-"}"); + column.Item().Text($"Approved At: {FormatDateTime(merchant.ApprovedAt)}"); + column.Item().Text($"Approved By: {merchant.ApprovedBy?.ToString() ?? "-"}"); + }); + } + + private static void BuildStoresSection(IContainer container, IReadOnlyList stores, CancellationToken cancellationToken) + { + container.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(column => + { + column.Spacing(4); + column.Item().Text("Stores").SemiBold(); + + if (stores.Count == 0) + { + column.Item().Text("No stores."); + return; + } + + for (var i = 0; i < stores.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var store = stores[i]; + column.Item().Text($"{i + 1}. {store.Name} | {ResolveStoreStatus(store.Status)} | {store.Address ?? "-"} | {store.Phone ?? "-"}"); + } + }); + } + + private static void BuildAuditSection(IContainer container, IReadOnlyList auditLogs, CancellationToken cancellationToken) + { + container.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(column => + { + column.Spacing(4); + column.Item().Text("Audit History").SemiBold(); + + if (auditLogs.Count == 0) + { + column.Item().Text("No audit records."); + return; + } + + for (var i = 0; i < auditLogs.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var log = auditLogs[i]; + var title = string.IsNullOrWhiteSpace(log.Title) ? log.Action.ToString() : log.Title; + column.Item().Text($"{i + 1}. {title} | {log.OperatorName ?? "-"} | {FormatDateTime(log.CreatedAt)}"); + if (!string.IsNullOrWhiteSpace(log.Description)) + { + column.Item().Text($" {log.Description}"); + } + } + }); + } + + private static string ResolveOperatingMode(OperatingMode? mode) + => mode switch + { + OperatingMode.SameEntity => "SameEntity", + OperatingMode.DifferentEntity => "DifferentEntity", + _ => "-" + }; + + private static string ResolveStoreStatus(StoreStatus status) + => status switch + { + StoreStatus.Closed => "Closed", + StoreStatus.Preparing => "Preparing", + StoreStatus.Operating => "Operating", + StoreStatus.Suspended => "Suspended", + _ => status.ToString() + }; + + private static string FormatDateTime(DateTime? value) + => value.HasValue ? value.Value.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture) : "-"; +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs index 655e566..a34444d 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs @@ -30,6 +30,11 @@ public sealed class TakeoutLogsDbContext( /// public DbSet MerchantAuditLogs => Set(); + /// + /// 商户变更日志集合。 + /// + public DbSet MerchantChangeLogs => Set(); + /// /// 运营操作日志集合。 /// @@ -54,6 +59,7 @@ public sealed class TakeoutLogsDbContext( base.OnModelCreating(modelBuilder); ConfigureTenantAuditLog(modelBuilder.Entity()); ConfigureMerchantAuditLog(modelBuilder.Entity()); + ConfigureMerchantChangeLog(modelBuilder.Entity()); ConfigureOperationLog(modelBuilder.Entity()); ConfigureOperationLogInboxMessage(modelBuilder.Entity()); ConfigureMemberGrowthLog(modelBuilder.Entity()); @@ -75,10 +81,29 @@ public sealed class TakeoutLogsDbContext( builder.ToTable("merchant_audit_logs"); builder.HasKey(x => x.Id); builder.Property(x => x.MerchantId).IsRequired(); - builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); + builder.Property(x => x.Action).HasConversion().IsRequired(); + builder.Property(x => x.Title).HasMaxLength(200).IsRequired(); builder.Property(x => x.Description).HasMaxLength(1024); - builder.Property(x => x.OperatorName).HasMaxLength(64); + builder.Property(x => x.OperatorName).HasMaxLength(100); + builder.Property(x => x.IpAddress).HasMaxLength(50); builder.HasIndex(x => new { x.TenantId, x.MerchantId }); + builder.HasIndex(x => new { x.MerchantId, x.CreatedAt }); + builder.HasIndex(x => new { x.TenantId, x.CreatedAt }); + } + + private static void ConfigureMerchantChangeLog(EntityTypeBuilder builder) + { + builder.ToTable("merchant_change_logs"); + builder.HasKey(x => x.Id); + builder.Property(x => x.MerchantId).IsRequired(); + builder.Property(x => x.FieldName).HasMaxLength(100).IsRequired(); + builder.Property(x => x.OldValue).HasColumnType("text"); + builder.Property(x => x.NewValue).HasColumnType("text"); + builder.Property(x => x.ChangeType).HasMaxLength(20).IsRequired(); + builder.Property(x => x.ChangedByName).HasMaxLength(100); + builder.Property(x => x.ChangeReason).HasMaxLength(512); + builder.HasIndex(x => new { x.MerchantId, x.CreatedAt }); + builder.HasIndex(x => new { x.TenantId, x.CreatedAt }); } private static void ConfigureOperationLog(EntityTypeBuilder builder) diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251229071911_AddMerchantManagement.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251229071911_AddMerchantManagement.Designer.cs new file mode 100644 index 0000000..36c3016 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251229071911_AddMerchantManagement.Designer.cs @@ -0,0 +1,7080 @@ +// +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.Migrations +{ + [DbContext(typeof(TakeoutAppDbContext))] + [Migration("20251229071911_AddMerchantManagement")] + partial class AddMerchantManagement + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricAlertRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConditionJson") + .IsRequired() + .HasColumnType("text") + .HasComment("触发条件 JSON。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Enabled") + .HasColumnType("boolean") + .HasComment("是否启用。"); + + b.Property("MetricDefinitionId") + .HasColumnType("bigint") + .HasComment("关联指标。"); + + b.Property("NotificationChannels") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("通知渠道。"); + + b.Property("Severity") + .HasColumnType("integer") + .HasComment("告警级别。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MetricDefinitionId", "Severity"); + + b.ToTable("metric_alert_rules", null, t => + { + t.HasComment("指标告警规则。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("指标编码。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DefaultAggregation") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("默认聚合方式。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("说明。"); + + b.Property("DimensionsJson") + .HasColumnType("text") + .HasComment("维度描述 JSON。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("指标名称。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.ToTable("metric_definitions", null, t => + { + t.HasComment("指标定义,描述可观测的数据点。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DimensionKey") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("维度键(JSON)。"); + + b.Property("MetricDefinitionId") + .HasColumnType("bigint") + .HasComment("指标定义 ID。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)") + .HasComment("数值。"); + + b.Property("WindowEnd") + .HasColumnType("timestamp with time zone") + .HasComment("统计时间窗口结束。"); + + b.Property("WindowStart") + .HasColumnType("timestamp with time zone") + .HasComment("统计时间窗口开始。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MetricDefinitionId", "DimensionKey", "WindowStart", "WindowEnd") + .IsUnique(); + + b.ToTable("metric_snapshots", null, t => + { + t.HasComment("指标快照,用于大盘展示。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.Coupon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("券码或序列号。"); + + b.Property("CouponTemplateId") + .HasColumnType("bigint") + .HasComment("模板标识。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone") + .HasComment("到期时间。"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone") + .HasComment("发放时间。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("订单 ID(已使用时记录)。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UsedAt") + .HasColumnType("timestamp with time zone") + .HasComment("使用时间。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("归属用户。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.ToTable("coupons", null, t => + { + t.HasComment("用户领取的券。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.CouponTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowStack") + .HasColumnType("boolean") + .HasComment("是否允许叠加其他优惠。"); + + b.Property("ChannelsJson") + .HasColumnType("text") + .HasComment("发放渠道(JSON)。"); + + b.Property("ClaimedQuantity") + .HasColumnType("integer") + .HasComment("已领取数量。"); + + b.Property("CouponType") + .HasColumnType("integer") + .HasComment("券类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注。"); + + b.Property("DiscountCap") + .HasColumnType("numeric") + .HasComment("折扣上限(针对折扣券)。"); + + b.Property("MinimumSpend") + .HasColumnType("numeric") + .HasComment("最低消费门槛。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("模板名称。"); + + b.Property("ProductScopeJson") + .HasColumnType("text") + .HasComment("适用品类或商品范围(JSON)。"); + + b.Property("RelativeValidDays") + .HasColumnType("integer") + .HasComment("有效天数(相对发放时间)。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("StoreScopeJson") + .HasColumnType("text") + .HasComment("适用门店 ID 集合(JSON)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TotalQuantity") + .HasColumnType("integer") + .HasComment("总发放数量上限。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("ValidFrom") + .HasColumnType("timestamp with time zone") + .HasComment("可用开始时间。"); + + b.Property("ValidTo") + .HasColumnType("timestamp with time zone") + .HasComment("可用结束时间。"); + + b.Property("Value") + .HasColumnType("numeric") + .HasComment("面值或折扣额度。"); + + b.HasKey("Id"); + + b.ToTable("coupon_templates", null, t => + { + t.HasComment("优惠券模板。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Coupons.Entities.PromotionCampaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AudienceDescription") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("目标人群描述。"); + + b.Property("BannerUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("营销素材(如 banner)。"); + + b.Property("Budget") + .HasColumnType("numeric") + .HasComment("预算金额。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndAt") + .HasColumnType("timestamp with time zone") + .HasComment("结束时间。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("活动名称。"); + + b.Property("PromotionType") + .HasColumnType("integer") + .HasComment("活动类型。"); + + b.Property("RulesJson") + .IsRequired() + .HasColumnType("text") + .HasComment("活动规则 JSON。"); + + b.Property("StartAt") + .HasColumnType("timestamp with time zone") + .HasComment("开始时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("活动状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.ToTable("promotion_campaigns", null, t => + { + t.HasComment("营销活动配置。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.ChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatSessionId") + .HasColumnType("bigint") + .HasComment("会话标识。"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("消息内容。"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("消息类型(文字/图片/语音等)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasComment("是否已读。"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone") + .HasComment("读取时间。"); + + b.Property("SenderType") + .HasColumnType("integer") + .HasComment("发送方类型。"); + + b.Property("SenderUserId") + .HasColumnType("bigint") + .HasComment("发送方用户 ID。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ChatSessionId", "CreatedAt"); + + b.ToTable("chat_messages", null, t => + { + t.HasComment("会话消息。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.ChatSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentUserId") + .HasColumnType("bigint") + .HasComment("当前客服员工 ID。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CustomerUserId") + .HasColumnType("bigint") + .HasComment("顾客用户 ID。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone") + .HasComment("结束时间。"); + + b.Property("IsBotActive") + .HasColumnType("boolean") + .HasComment("是否机器人接待中。"); + + b.Property("SessionCode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("会话编号。"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasComment("开始时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("会话状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("所属门店(可空为平台)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SessionCode") + .IsUnique(); + + b.ToTable("chat_sessions", null, t => + { + t.HasComment("客服会话。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.SupportTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssignedAgentId") + .HasColumnType("bigint") + .HasComment("指派的客服。"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasComment("关闭时间。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CustomerUserId") + .HasColumnType("bigint") + .HasComment("客户用户 ID。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("工单详情。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("关联订单(如有)。"); + + b.Property("Priority") + .HasColumnType("integer") + .HasComment("优先级。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("工单主题。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TicketNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("工单编号。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "TicketNo") + .IsUnique(); + + b.ToTable("support_tickets", null, t => + { + t.HasComment("客服工单。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.CustomerService.Entities.TicketComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AttachmentsJson") + .HasColumnType("text") + .HasComment("附件 JSON。"); + + b.Property("AuthorUserId") + .HasColumnType("bigint") + .HasComment("评论人 ID。"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("评论内容。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsInternal") + .HasColumnType("boolean") + .HasComment("是否内部备注。"); + + b.Property("SupportTicketId") + .HasColumnType("bigint") + .HasComment("工单标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SupportTicketId"); + + b.ToTable("ticket_comments", null, t => + { + t.HasComment("工单评论/流转记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DeliveryOrderId") + .HasColumnType("bigint") + .HasComment("配送单标识。"); + + b.Property("EventType") + .HasColumnType("integer") + .HasComment("事件类型。"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("事件描述。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("Payload") + .HasColumnType("text") + .HasComment("原始数据 JSON。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "DeliveryOrderId", "EventType"); + + b.ToTable("delivery_events", null, t => + { + t.HasComment("配送状态事件流水。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CourierName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("骑手姓名。"); + + b.Property("CourierPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("骑手电话。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DeliveredAt") + .HasColumnType("timestamp with time zone") + .HasComment("完成时间。"); + + b.Property("DeliveryFee") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("配送费。"); + + b.Property("DispatchedAt") + .HasColumnType("timestamp with time zone") + .HasComment("下发时间。"); + + b.Property("FailureReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("异常原因。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("获取或设置关联订单 ID。"); + + b.Property("PickedUpAt") + .HasColumnType("timestamp with time zone") + .HasComment("取餐时间。"); + + b.Property("Provider") + .HasColumnType("integer") + .HasComment("配送服务商。"); + + b.Property("ProviderOrderId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("第三方配送单号。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId") + .IsUnique(); + + b.ToTable("delivery_orders", null, t => + { + t.HasComment("配送单。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Distribution.Entities.AffiliateOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AffiliatePartnerId") + .HasColumnType("bigint") + .HasComment("推广人标识。"); + + b.Property("BuyerUserId") + .HasColumnType("bigint") + .HasComment("用户 ID。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EstimatedCommission") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("预计佣金。"); + + b.Property("OrderAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("订单金额。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("关联订单。"); + + b.Property("SettledAt") + .HasColumnType("timestamp with time zone") + .HasComment("结算完成时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("当前状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "AffiliatePartnerId", "OrderId") + .IsUnique(); + + b.ToTable("affiliate_orders", null, t => + { + t.HasComment("分销订单记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Distribution.Entities.AffiliatePartner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelType") + .HasColumnType("integer") + .HasComment("渠道类型。"); + + b.Property("CommissionRate") + .HasColumnType("numeric") + .HasComment("分成比例(0-1)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("昵称或渠道名称。"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("联系电话。"); + + b.Property("Remarks") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("审核备注。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("当前状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户 ID(如绑定平台账号)。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "DisplayName"); + + b.ToTable("affiliate_partners", null, t => + { + t.HasComment("分销/推广合作伙伴。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Distribution.Entities.AffiliatePayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AffiliatePartnerId") + .HasColumnType("bigint") + .HasComment("合作伙伴标识。"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("结算金额。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasComment("打款时间。"); + + b.Property("Period") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("结算周期描述。"); + + b.Property("Remarks") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "AffiliatePartnerId", "Period") + .IsUnique(); + + b.ToTable("affiliate_payouts", null, t => + { + t.HasComment("佣金结算记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Engagement.Entities.CheckInCampaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowMakeupCount") + .HasColumnType("integer") + .HasComment("支持补签次数。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("活动描述。"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone") + .HasComment("结束日期。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("活动名称。"); + + b.Property("RewardsJson") + .IsRequired() + .HasColumnType("text") + .HasComment("连签奖励 JSON。"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasComment("开始日期。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name"); + + b.ToTable("checkin_campaigns", null, t => + { + t.HasComment("签到活动配置。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Engagement.Entities.CheckInRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CheckInCampaignId") + .HasColumnType("bigint") + .HasComment("活动标识。"); + + b.Property("CheckInDate") + .HasColumnType("timestamp with time zone") + .HasComment("签到日期(本地)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsMakeup") + .HasColumnType("boolean") + .HasComment("是否补签。"); + + b.Property("RewardJson") + .IsRequired() + .HasColumnType("text") + .HasComment("获得奖励 JSON。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户标识。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "CheckInCampaignId", "UserId", "CheckInDate") + .IsUnique(); + + b.ToTable("checkin_records", null, t => + { + t.HasComment("用户签到记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Engagement.Entities.CommunityComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint") + .HasComment("评论人。"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("评论内容。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasComment("状态。"); + + b.Property("ParentId") + .HasColumnType("bigint") + .HasComment("父级评论 ID。"); + + b.Property("PostId") + .HasColumnType("bigint") + .HasComment("动态标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "PostId", "CreatedAt"); + + b.ToTable("community_comments", null, t => + { + t.HasComment("社区评论。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Engagement.Entities.CommunityPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint") + .HasComment("作者用户 ID。"); + + b.Property("CommentCount") + .HasColumnType("integer") + .HasComment("评论数。"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("内容。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("LikeCount") + .HasColumnType("integer") + .HasComment("点赞数。"); + + b.Property("MediaJson") + .HasColumnType("text") + .HasComment("媒体资源 JSON。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("Title") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("标题。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "AuthorUserId", "CreatedAt"); + + b.ToTable("community_posts", null, t => + { + t.HasComment("社区动态。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Engagement.Entities.CommunityReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("PostId") + .HasColumnType("bigint") + .HasComment("动态 ID。"); + + b.Property("ReactedAt") + .HasColumnType("timestamp with time zone") + .HasComment("时间戳。"); + + b.Property("ReactionType") + .HasColumnType("integer") + .HasComment("反应类型。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户 ID。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "PostId", "UserId") + .IsUnique(); + + b.ToTable("community_reactions", null, t => + { + t.HasComment("社区互动反馈。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.GroupBuying.Entities.GroupOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasComment("取消时间。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentCount") + .HasColumnType("integer") + .HasComment("当前已参与人数。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndAt") + .HasColumnType("timestamp with time zone") + .HasComment("结束时间。"); + + b.Property("GroupOrderNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("拼单编号。"); + + b.Property("GroupPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("拼团价格。"); + + b.Property("LeaderUserId") + .HasColumnType("bigint") + .HasComment("团长用户 ID。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("关联商品或套餐。"); + + b.Property("StartAt") + .HasColumnType("timestamp with time zone") + .HasComment("开始时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("拼团状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("SucceededAt") + .HasColumnType("timestamp with time zone") + .HasComment("成团时间。"); + + b.Property("TargetCount") + .HasColumnType("integer") + .HasComment("成团需要的人数。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "GroupOrderNo") + .IsUnique(); + + b.ToTable("group_orders", null, t => + { + t.HasComment("拼单活动。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.GroupBuying.Entities.GroupParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("GroupOrderId") + .HasColumnType("bigint") + .HasComment("拼单活动标识。"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone") + .HasComment("参与时间。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("对应订单标识。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("参与状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户标识。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "GroupOrderId", "UserId") + .IsUnique(); + + b.ToTable("group_participants", null, t => + { + t.HasComment("拼单参与者。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Inventory.Entities.InventoryAdjustment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdjustmentType") + .HasColumnType("integer") + .HasComment("调整类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("InventoryItemId") + .HasColumnType("bigint") + .HasComment("对应的库存记录标识。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人标识。"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasComment("调整数量,正数增加,负数减少。"); + + b.Property("Reason") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("原因说明。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "InventoryItemId", "OccurredAt"); + + b.ToTable("inventory_adjustments", null, t => + { + t.HasComment("库存调整记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Inventory.Entities.InventoryBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BatchNumber") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("批次编号。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpireDate") + .HasColumnType("timestamp with time zone") + .HasComment("过期日期。"); + + b.Property("ProductSkuId") + .HasColumnType("bigint") + .HasComment("SKU 标识。"); + + b.Property("ProductionDate") + .HasColumnType("timestamp with time zone") + .HasComment("生产日期。"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasComment("入库数量。"); + + b.Property("RemainingQuantity") + .HasColumnType("integer") + .HasComment("剩余数量。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制字段。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "ProductSkuId", "BatchNumber") + .IsUnique(); + + b.ToTable("inventory_batches", null, t => + { + t.HasComment("SKU 批次信息。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Inventory.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BatchConsumeStrategy") + .HasColumnType("integer") + .HasComment("批次扣减策略。"); + + b.Property("BatchNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("批次编号,可为空表示混批。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpireDate") + .HasColumnType("timestamp with time zone") + .HasComment("过期日期。"); + + b.Property("IsPresale") + .HasColumnType("boolean") + .HasComment("是否预售商品。"); + + b.Property("IsSoldOut") + .HasColumnType("boolean") + .HasComment("是否标记售罄。"); + + b.Property("Location") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("储位或仓位信息。"); + + b.Property("MaxQuantityPerOrder") + .HasColumnType("integer") + .HasComment("单品限购(覆盖商品级 MaxQuantityPerOrder)。"); + + b.Property("PresaleCapacity") + .HasColumnType("integer") + .HasComment("预售名额(上限)。"); + + b.Property("PresaleEndTime") + .HasColumnType("timestamp with time zone") + .HasComment("预售结束时间(UTC)。"); + + b.Property("PresaleLocked") + .HasColumnType("integer") + .HasComment("当前预售已锁定数量。"); + + b.Property("PresaleStartTime") + .HasColumnType("timestamp with time zone") + .HasComment("预售开始时间(UTC)。"); + + b.Property("ProductSkuId") + .HasColumnType("bigint") + .HasComment("SKU 标识。"); + + b.Property("QuantityOnHand") + .HasColumnType("integer") + .HasComment("可用库存。"); + + b.Property("QuantityReserved") + .HasColumnType("integer") + .HasComment("已锁定库存(订单占用)。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制字段。"); + + b.Property("SafetyStock") + .HasColumnType("integer") + .HasComment("安全库存阈值。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "ProductSkuId", "BatchNumber"); + + b.ToTable("inventory_items", null, t => + { + t.HasComment("SKU 在门店的库存信息。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Inventory.Entities.InventoryLockRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasComment("过期时间(UTC)。"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("幂等键。"); + + b.Property("IsPresale") + .HasColumnType("boolean") + .HasComment("是否预售锁定。"); + + b.Property("ProductSkuId") + .HasColumnType("bigint") + .HasComment("SKU ID。"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasComment("锁定数量。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制字段。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("锁定状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店 ID。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId", "ProductSkuId", "Status"); + + b.ToTable("inventory_lock_records", null, t => + { + t.HasComment("库存锁定记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberPointLedger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BalanceAfterChange") + .HasColumnType("integer") + .HasComment("变动后余额。"); + + b.Property("ChangeAmount") + .HasColumnType("integer") + .HasComment("变动数量,可为负值。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone") + .HasComment("过期时间(如适用)。"); + + b.Property("MemberId") + .HasColumnType("bigint") + .HasComment("会员标识。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("Reason") + .HasColumnType("integer") + .HasComment("变动原因。"); + + b.Property("SourceId") + .HasColumnType("bigint") + .HasComment("来源 ID(订单、活动等)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MemberId", "OccurredAt"); + + b.ToTable("member_point_ledgers", null, t => + { + t.HasComment("积分变动流水。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("头像。"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone") + .HasComment("生日。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("GrowthValue") + .HasColumnType("integer") + .HasComment("成长值/经验值。"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone") + .HasComment("注册时间。"); + + b.Property("MemberTierId") + .HasColumnType("bigint") + .HasComment("当前会员等级 ID。"); + + b.Property("Mobile") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("手机号。"); + + b.Property("Nickname") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("昵称。"); + + b.Property("PointsBalance") + .HasColumnType("integer") + .HasComment("会员积分余额。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("会员状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户标识。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Mobile") + .IsUnique(); + + b.ToTable("member_profiles", null, t => + { + t.HasComment("会员档案。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberTier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BenefitsJson") + .IsRequired() + .HasColumnType("text") + .HasComment("等级权益(JSON)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("等级名称。"); + + b.Property("RequiredGrowth") + .HasColumnType("integer") + .HasComment("所需成长值。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("member_tiers", null, t => + { + t.HasComment("会员等级定义。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.Merchant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("详细地址。"); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone") + .HasComment("审核通过时间。"); + + b.Property("ApprovedBy") + .HasColumnType("bigint") + .HasComment("审核通过人。"); + + b.Property("BrandAlias") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("品牌简称或别名。"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("品牌名称(对外展示)。"); + + b.Property("BusinessLicenseImageUrl") + .HasColumnType("text") + .HasComment("营业执照扫描件地址。"); + + b.Property("BusinessLicenseNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("营业执照号。"); + + b.Property("Category") + .HasColumnType("text") + .HasComment("品牌所属品类,如火锅、咖啡等。"); + + b.Property("City") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("所在城市。"); + + b.Property("ClaimExpiresAt") + .HasColumnType("timestamp with time zone") + .HasComment("领取过期时间。"); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone") + .HasComment("领取时间。"); + + b.Property("ClaimedBy") + .HasColumnType("bigint") + .HasComment("当前领取人。"); + + b.Property("ClaimedByName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("当前领取人姓名。"); + + b.Property("ContactEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("联系邮箱。"); + + b.Property("ContactPhone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("联系电话。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("District") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("所在区县。"); + + b.Property("FrozenAt") + .HasColumnType("timestamp with time zone") + .HasComment("冻结时间。"); + + b.Property("FrozenReason") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("冻结原因。"); + + b.Property("IsFrozen") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否冻结业务。"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone") + .HasComment("入驻时间。"); + + b.Property("LastReviewedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次审核时间。"); + + b.Property("LastReviewedBy") + .HasColumnType("bigint") + .HasComment("最近一次审核人。"); + + b.Property("Latitude") + .HasColumnType("double precision") + .HasComment("纬度信息。"); + + b.Property("LegalPerson") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("法人或负责人姓名。"); + + b.Property("LogoUrl") + .HasColumnType("text") + .HasComment("品牌 Logo。"); + + b.Property("Longitude") + .HasColumnType("double precision") + .HasComment("经度信息。"); + + b.Property("OperatingMode") + .HasColumnType("integer") + .HasComment("经营模式(同一主体/不同主体)。"); + + b.Property("Province") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("所在省份。"); + + b.Property("ReviewRemarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("审核备注或驳回原因。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制版本。"); + + b.Property("ServicePhone") + .HasColumnType("text") + .HasComment("客服电话。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("入驻状态。"); + + b.Property("SupportEmail") + .HasColumnType("text") + .HasComment("客服邮箱。"); + + b.Property("TaxNumber") + .HasColumnType("text") + .HasComment("税号/统一社会信用代码。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedBy"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "Status"); + + b.ToTable("merchants", null, t => + { + t.HasComment("商户主体信息,承载入驻和资质审核结果。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DisplayOrder") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasComment("显示顺序,越小越靠前。"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否可用。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("类目名称。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name") + .IsUnique(); + + b.ToTable("merchant_categories", null, t => + { + t.HasComment("商户可选类目。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ContractNumber") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("合同编号。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone") + .HasComment("合同结束时间。"); + + b.Property("FileUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("合同文件存储地址。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("所属商户标识。"); + + b.Property("SignedAt") + .HasColumnType("timestamp with time zone") + .HasComment("签署时间。"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasComment("合同开始时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("合同状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TerminatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("终止时间。"); + + b.Property("TerminationReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("终止原因。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MerchantId", "ContractNumber") + .IsUnique(); + + b.ToTable("merchant_contracts", null, t => + { + t.HasComment("商户合同记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DocumentNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("证照编号。"); + + b.Property("DocumentType") + .HasColumnType("integer") + .HasComment("证照类型。"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasComment("到期日期。"); + + b.Property("FileUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("证照文件链接。"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone") + .HasComment("签发日期。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("所属商户标识。"); + + b.Property("Remarks") + .HasColumnType("text") + .HasComment("审核备注或驳回原因。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("审核状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MerchantId", "DocumentType"); + + b.ToTable("merchant_documents", null, t => + { + t.HasComment("商户提交的资质或证照材料。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantStaff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("邮箱地址。"); + + b.Property("IdentityUserId") + .HasColumnType("bigint") + .HasComment("登录账号 ID(指向统一身份体系)。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("所属商户标识。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("员工姓名。"); + + b.Property("PermissionsJson") + .HasColumnType("text") + .HasComment("自定义权限(JSON)。"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("手机号。"); + + b.Property("RoleType") + .HasColumnType("integer") + .HasComment("员工角色类型。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("员工状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("可选的关联门店 ID。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MerchantId", "Phone"); + + b.ToTable("merchant_staff", null, t => + { + t.HasComment("商户员工账号,支持门店维度分配。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Navigation.Entities.MapLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("地址。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Landmark") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("打车/导航落点描述。"); + + b.Property("Latitude") + .HasColumnType("double precision") + .HasComment("纬度。"); + + b.Property("Longitude") + .HasColumnType("double precision") + .HasComment("经度。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("名称。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("关联门店 ID,可空表示独立 POI。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("map_locations", null, t => + { + t.HasComment("地图 POI 信息,用于门店定位和推荐。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Navigation.Entities.NavigationRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Channel") + .HasColumnType("integer") + .HasComment("来源通道(小程序、H5 等)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("RequestedAt") + .HasColumnType("timestamp with time zone") + .HasComment("请求时间。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店 ID。"); + + b.Property("TargetApp") + .HasColumnType("integer") + .HasComment("跳转的地图应用。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户 ID。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "UserId", "StoreId", "RequestedAt"); + + b.ToTable("navigation_requests", null, t => + { + t.HasComment("用户发起的导航请求日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Ordering.Entities.CartItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AttributesJson") + .HasColumnType("text") + .HasComment("扩展 JSON(规格、加料选项等)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("商品或 SKU 标识。"); + + b.Property("ProductName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("商品名称快照。"); + + b.Property("ProductSkuId") + .HasColumnType("bigint") + .HasComment("SKU 标识。"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasComment("数量。"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("自定义备注(口味要求)。"); + + b.Property("ShoppingCartId") + .HasColumnType("bigint") + .HasComment("所属购物车标识。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("单价快照。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ShoppingCartId"); + + b.ToTable("cart_items", null, t => + { + t.HasComment("购物车条目。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Ordering.Entities.CartItemAddon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CartItemId") + .HasColumnType("bigint") + .HasComment("所属购物车条目。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExtraPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("附加价格。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("选项名称。"); + + b.Property("OptionId") + .HasColumnType("bigint") + .HasComment("选项 ID(可对应 ProductAddonOption)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.ToTable("cart_item_addons", null, t => + { + t.HasComment("购物车条目的加料/附加项。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Ordering.Entities.CheckoutSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasComment("过期时间(UTC)。"); + + b.Property("SessionToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("会话 Token。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("会话状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户标识。"); + + b.Property("ValidationResultJson") + .IsRequired() + .HasColumnType("text") + .HasComment("校验结果明细 JSON。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SessionToken") + .IsUnique(); + + b.ToTable("checkout_sessions", null, t => + { + t.HasComment("结账会话,记录校验上下文。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Ordering.Entities.ShoppingCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DeliveryPreference") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("履约方式(堂食/自提/配送)缓存。"); + + b.Property("LastModifiedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次修改时间(UTC)。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("购物车状态,包含正常/锁定。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TableContext") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("桌码或场景标识(扫码点餐)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("用户标识。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "UserId", "StoreId") + .IsUnique(); + + b.ToTable("shopping_carts", null, t => + { + t.HasComment("用户购物车,按租户/门店隔离。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CancelReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("取消原因。"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasComment("取消时间。"); + + b.Property("Channel") + .HasColumnType("integer") + .HasComment("下单渠道。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CustomerName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("顾客姓名。"); + + b.Property("CustomerPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("顾客手机号。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DeliveryType") + .HasColumnType("integer") + .HasComment("履约类型。"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("优惠金额。"); + + b.Property("FinishedAt") + .HasColumnType("timestamp with time zone") + .HasComment("完成时间。"); + + b.Property("ItemsAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("商品总额。"); + + b.Property("OrderNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("订单号。"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("实付金额。"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasComment("支付时间。"); + + b.Property("PayableAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("应付金额。"); + + b.Property("PaymentStatus") + .HasColumnType("integer") + .HasComment("支付状态。"); + + b.Property("QueueNumber") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("排队号(如有)。"); + + b.Property("Remark") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注。"); + + b.Property("ReservationId") + .HasColumnType("bigint") + .HasComment("预约 ID。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("当前状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店。"); + + b.Property("TableNo") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("就餐桌号。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderNo") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId", "Status"); + + b.ToTable("orders", null, t => + { + t.HasComment("交易订单。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AttributesJson") + .HasColumnType("text") + .HasComment("自定义属性 JSON。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("折扣金额。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("订单 ID。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("商品 ID。"); + + b.Property("ProductName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("商品名称。"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasComment("数量。"); + + b.Property("SkuName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("SKU/规格描述。"); + + b.Property("SubTotal") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("小计。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("Unit") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasComment("单位。"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("单价。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("TenantId", "OrderId"); + + b.ToTable("order_items", null, t => + { + t.HasComment("订单明细。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderStatusHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Notes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注信息。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人标识(可为空表示系统)。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("订单标识。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("变更后的状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId", "OccurredAt"); + + b.ToTable("order_status_histories", null, t => + { + t.HasComment("订单状态流转记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.RefundRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("申请金额。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("关联订单标识。"); + + b.Property("ProcessedAt") + .HasColumnType("timestamp with time zone") + .HasComment("审核完成时间。"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("申请原因。"); + + b.Property("RefundNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("退款单号。"); + + b.Property("RequestedAt") + .HasColumnType("timestamp with time zone") + .HasComment("用户提交时间。"); + + b.Property("ReviewNotes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("审核备注。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("退款状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "RefundNo") + .IsUnique(); + + b.ToTable("refund_requests", null, t => + { + t.HasComment("售后/退款申请。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("支付金额。"); + + b.Property("ChannelTransactionId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("第三方渠道单号。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Method") + .HasColumnType("integer") + .HasComment("支付方式。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("关联订单。"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasComment("支付完成时间。"); + + b.Property("Payload") + .HasColumnType("text") + .HasComment("原始回调内容。"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("错误/备注。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("支付状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TradeNo") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("平台交易号。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId"); + + b.ToTable("payment_records", null, t => + { + t.HasComment("支付流水。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRefundRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("退款金额。"); + + b.Property("ChannelRefundId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("渠道退款流水号。"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("完成时间。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("OrderId") + .HasColumnType("bigint") + .HasComment("关联订单标识。"); + + b.Property("Payload") + .HasColumnType("text") + .HasComment("渠道返回的原始数据 JSON。"); + + b.Property("PaymentRecordId") + .HasColumnType("bigint") + .HasComment("原支付记录标识。"); + + b.Property("RequestedAt") + .HasColumnType("timestamp with time zone") + .HasComment("退款请求时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("退款状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "PaymentRecordId"); + + b.ToTable("payment_refund_records", null, t => + { + t.HasComment("支付渠道退款流水。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint") + .HasComment("所属分类。"); + + b.Property("CoverImage") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("主图。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasColumnType("text") + .HasComment("商品描述。"); + + b.Property("EnableDelivery") + .HasColumnType("boolean") + .HasComment("支持配送。"); + + b.Property("EnableDineIn") + .HasColumnType("boolean") + .HasComment("支持堂食。"); + + b.Property("EnablePickup") + .HasColumnType("boolean") + .HasComment("支持自提。"); + + b.Property("GalleryImages") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("Gallery 图片逗号分隔。"); + + b.Property("IsFeatured") + .HasColumnType("boolean") + .HasComment("是否热门推荐。"); + + b.Property("MaxQuantityPerOrder") + .HasColumnType("integer") + .HasComment("最大每单限购。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("商品名称。"); + + b.Property("OriginalPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("原价。"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("现价。"); + + b.Property("SpuCode") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("商品编码。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("商品状态。"); + + b.Property("StockQuantity") + .HasColumnType("integer") + .HasComment("库存数量(可选)。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("所属门店。"); + + b.Property("Subtitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("副标题/卖点。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("Unit") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasComment("售卖单位(份/杯等)。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SpuCode") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("products", null, t => + { + t.HasComment("商品(SPU)信息。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductAddonGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsRequired") + .HasColumnType("boolean") + .HasComment("是否必选。"); + + b.Property("MaxSelect") + .HasColumnType("integer") + .HasComment("最大选择数量。"); + + b.Property("MinSelect") + .HasColumnType("integer") + .HasComment("最小选择数量。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("分组名称。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("所属商品。"); + + b.Property("SelectionType") + .HasColumnType("integer") + .HasComment("选择类型。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ProductId", "Name"); + + b.ToTable("product_addon_groups", null, t => + { + t.HasComment("加料/做法分组。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductAddonOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AddonGroupId") + .HasColumnType("bigint") + .HasComment("所属加料分组。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExtraPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("附加价格。"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasComment("是否默认选项。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("选项名称。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.ToTable("product_addon_options", null, t => + { + t.HasComment("加料选项。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductAttributeGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsRequired") + .HasColumnType("boolean") + .HasComment("是否必选。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("分组名称,例如“辣度”“份量”。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("所属商品标识。"); + + b.Property("SelectionType") + .HasColumnType("integer") + .HasComment("选择类型(单选/多选)。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("显示排序。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("关联门店,可为空表示所有门店共享。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "Name"); + + b.ToTable("product_attribute_groups", null, t => + { + t.HasComment("商品规格/属性分组。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductAttributeOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AttributeGroupId") + .HasColumnType("bigint") + .HasComment("所属规格组。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExtraPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("附加价格。"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasComment("是否默认选中。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("选项名称。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "AttributeGroupId", "Name") + .IsUnique(); + + b.ToTable("product_attribute_options", null, t => + { + t.HasComment("商品规格选项。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("分类描述。"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("分类名称。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("所属门店。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("product_categories", null, t => + { + t.HasComment("商品分类。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductMediaAsset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Caption") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("描述或标题。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("MediaType") + .HasColumnType("integer") + .HasComment("媒体类型。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("商品标识。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("媒资链接。"); + + b.HasKey("Id"); + + b.ToTable("product_media_assets", null, t => + { + t.HasComment("商品媒资素材。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductPricingRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConditionsJson") + .IsRequired() + .HasColumnType("text") + .HasComment("条件描述(JSON),如会员等级、渠道等。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone") + .HasComment("生效结束时间。"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("特殊价格。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("所属商品。"); + + b.Property("RuleType") + .HasColumnType("integer") + .HasComment("策略类型。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone") + .HasComment("生效开始时间。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("WeekdaysJson") + .HasColumnType("text") + .HasComment("生效星期(JSON 数组)。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ProductId", "RuleType"); + + b.ToTable("product_pricing_rules", null, t => + { + t.HasComment("商品价格策略,支持会员价/时段价等。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductSku", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AttributesJson") + .IsRequired() + .HasColumnType("text") + .HasComment("规格属性 JSON(记录选项 ID)。"); + + b.Property("Barcode") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("条形码。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("OriginalPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("原价。"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("售价。"); + + b.Property("ProductId") + .HasColumnType("bigint") + .HasComment("所属商品标识。"); + + b.Property("SkuCode") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("SKU 编码。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("StockQuantity") + .HasColumnType("integer") + .HasComment("可售库存。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("Weight") + .HasPrecision(10, 3) + .HasColumnType("numeric(10,3)") + .HasComment("重量(千克)。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SkuCode") + .IsUnique(); + + b.ToTable("product_skus", null, t => + { + t.HasComment("商品 SKU,记录具体规格组合价格。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Queues.Entities.QueueTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CalledAt") + .HasColumnType("timestamp with time zone") + .HasComment("叫号时间。"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasComment("取消时间。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EstimatedWaitMinutes") + .HasColumnType("integer") + .HasComment("预计等待分钟。"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasComment("过号时间。"); + + b.Property("PartySize") + .HasColumnType("integer") + .HasComment("就餐人数。"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("获取或设置所属门店 ID。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TicketNumber") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("排队编号。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.HasIndex("TenantId", "StoreId", "TicketNumber") + .IsUnique(); + + b.ToTable("queue_tickets", null, t => + { + t.HasComment("排队叫号。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Reservations.Entities.Reservation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasComment("取消时间。"); + + b.Property("CheckInCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("核销码/到店码。"); + + b.Property("CheckedInAt") + .HasColumnType("timestamp with time zone") + .HasComment("实际签到时间。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CustomerName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("客户姓名。"); + + b.Property("CustomerPhone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("联系电话。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("PeopleCount") + .HasColumnType("integer") + .HasComment("用餐人数。"); + + b.Property("Remark") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注。"); + + b.Property("ReservationNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("预约号。"); + + b.Property("ReservationTime") + .HasColumnType("timestamp with time zone") + .HasComment("预约时间(UTC)。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店。"); + + b.Property("TablePreference") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("桌型/标签。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ReservationNo") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("reservations", null, t => + { + t.HasComment("预约/预订记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("详细地址。"); + + b.Property("Announcement") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("门店公告。"); + + b.Property("BusinessHours") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("门店营业时段描述(备用字符串)。"); + + b.Property("BusinessLicenseImageUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("门店营业执照图片地址(主体不一致模式使用)。"); + + b.Property("BusinessLicenseNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("门店营业执照号(主体不一致模式使用)。"); + + b.Property("City") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("所在城市。"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("门店编码,便于扫码及外部对接。"); + + b.Property("Country") + .HasColumnType("text") + .HasComment("所在国家或地区。"); + + b.Property("CoverImageUrl") + .HasColumnType("text") + .HasComment("门店海报。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DeliveryRadiusKm") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)") + .HasComment("默认配送半径(公里)。"); + + b.Property("Description") + .HasColumnType("text") + .HasComment("门店描述或公告。"); + + b.Property("District") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("区县信息。"); + + b.Property("Latitude") + .HasColumnType("double precision") + .HasComment("纬度。"); + + b.Property("LegalRepresentative") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("门店法人(主体不一致模式使用)。"); + + b.Property("Longitude") + .HasColumnType("double precision") + .HasComment("高德/腾讯地图经度。"); + + b.Property("ManagerName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("门店负责人姓名。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("所属商户标识。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("门店名称。"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("联系电话。"); + + b.Property("Province") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("所在省份。"); + + b.Property("RegisteredAddress") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("门店注册地址(主体不一致模式使用)。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("门店当前运营状态。"); + + b.Property("SupportsDelivery") + .HasColumnType("boolean") + .HasComment("是否支持配送。"); + + b.Property("SupportsDineIn") + .HasColumnType("boolean") + .HasComment("是否支持堂食。"); + + b.Property("SupportsPickup") + .HasColumnType("boolean") + .HasComment("是否支持自提。"); + + b.Property("SupportsQueueing") + .HasColumnType("boolean") + .HasComment("支持排队叫号。"); + + b.Property("SupportsReservation") + .HasColumnType("boolean") + .HasComment("支持预约。"); + + b.Property("Tags") + .HasColumnType("text") + .HasComment("门店标签(逗号分隔)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("MerchantId", "BusinessLicenseNumber") + .IsUnique() + .HasFilter("\"BusinessLicenseNumber\" IS NOT NULL AND \"Status\" <> 3"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.HasIndex("TenantId", "MerchantId"); + + b.ToTable("stores", null, t => + { + t.HasComment("门店信息,承载营业配置与能力。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StoreBusinessHour", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CapacityLimit") + .HasColumnType("integer") + .HasComment("最大接待容量或单量限制。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DayOfWeek") + .HasColumnType("integer") + .HasComment("星期几,0 表示周日。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndTime") + .HasColumnType("interval") + .HasComment("结束时间(本地时间)。"); + + b.Property("HourType") + .HasColumnType("integer") + .HasComment("时段类型(正常营业、休息、预约等)。"); + + b.Property("Notes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("StartTime") + .HasColumnType("interval") + .HasComment("开始时间(本地时间)。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "DayOfWeek"); + + b.ToTable("store_business_hours", null, t => + { + t.HasComment("门店营业时段配置。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StoreDeliveryZone", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DeliveryFee") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("配送费。"); + + b.Property("EstimatedMinutes") + .HasColumnType("integer") + .HasComment("预计送达分钟。"); + + b.Property("MinimumOrderAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("起送价。"); + + b.Property("PolygonGeoJson") + .IsRequired() + .HasColumnType("text") + .HasComment("GeoJSON 表示的多边形范围。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("ZoneName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("区域名称。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "ZoneName"); + + b.ToTable("store_delivery_zones", null, t => + { + t.HasComment("门店配送范围配置。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StoreEmployeeShift", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndTime") + .HasColumnType("interval") + .HasComment("结束时间。"); + + b.Property("Notes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("RoleType") + .HasColumnType("integer") + .HasComment("排班角色。"); + + b.Property("ShiftDate") + .HasColumnType("timestamp with time zone") + .HasComment("班次日期。"); + + b.Property("StaffId") + .HasColumnType("bigint") + .HasComment("员工标识。"); + + b.Property("StartTime") + .HasColumnType("interval") + .HasComment("开始时间。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "ShiftDate", "StaffId") + .IsUnique(); + + b.ToTable("store_employee_shifts", null, t => + { + t.HasComment("门店员工排班记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StoreHoliday", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasComment("日期。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("IsClosed") + .HasColumnType("boolean") + .HasComment("是否全天闭店。"); + + b.Property("Reason") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("说明内容。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "Date") + .IsUnique(); + + b.ToTable("store_holidays", null, t => + { + t.HasComment("门店休息日或特殊营业日。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StorePickupSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowDaysAhead") + .HasColumnType("integer") + .HasComment("可预约天数(含当天)。"); + + b.Property("AllowToday") + .HasColumnType("boolean") + .HasComment("是否允许当天自提。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DefaultCutoffMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30) + .HasComment("默认截单分钟(开始前多少分钟截止)。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("MaxQuantityPerOrder") + .HasColumnType("integer") + .HasComment("单笔自提最大份数。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制字段。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId") + .IsUnique(); + + b.ToTable("store_pickup_settings", null, t => + { + t.HasComment("门店自提配置。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StorePickupSlot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Capacity") + .HasColumnType("integer") + .HasComment("容量(份数)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CutoffMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30) + .HasComment("截单分钟(开始前多少分钟截止)。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EndTime") + .HasColumnType("interval") + .HasComment("当天结束时间(UTC)。"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("档期名称。"); + + b.Property("ReservedCount") + .HasColumnType("integer") + .HasComment("已占用数量。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制字段。"); + + b.Property("StartTime") + .HasColumnType("interval") + .HasComment("当天开始时间(UTC)。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("Weekdays") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("适用星期(逗号分隔 1-7)。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "Name"); + + b.ToTable("store_pickup_slots", null, t => + { + t.HasComment("门店自提档期。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StoreTable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AreaId") + .HasColumnType("bigint") + .HasComment("所在区域 ID。"); + + b.Property("Capacity") + .HasColumnType("integer") + .HasComment("可容纳人数。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("QrCodeUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("桌码二维码地址。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("当前桌台状态。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TableCode") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("桌码。"); + + b.Property("Tags") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("桌台标签(堂食、快餐等)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "TableCode") + .IsUnique(); + + b.ToTable("store_tables", null, t => + { + t.HasComment("桌台信息与二维码绑定。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.StoreTableArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("区域描述。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("区域名称。"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasComment("排序值。"); + + b.Property("StoreId") + .HasColumnType("bigint") + .HasComment("门店标识。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId", "Name") + .IsUnique(); + + b.ToTable("store_table_areas", null, t => + { + t.HasComment("门店桌台区域配置。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.QuotaPackage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("描述。"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否上架。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("配额包名称。"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("价格。"); + + b.Property("QuotaType") + .HasColumnType("integer") + .HasComment("配额类型。"); + + b.Property("QuotaValue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("配额数值。"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasComment("排序。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("QuotaType", "IsActive", "SortOrder"); + + b.ToTable("quota_packages", null, t => + { + t.HasComment("配额包定义(平台提供的可购买配额包)。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("text") + .HasComment("详细地址信息。"); + + b.Property("City") + .HasColumnType("text") + .HasComment("所在城市。"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("租户短编码,作为跨系统引用的唯一标识。"); + + b.Property("ContactEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("主联系人邮箱。"); + + b.Property("ContactName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("主联系人姓名。"); + + b.Property("ContactPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("主联系人电话。"); + + b.Property("Country") + .HasColumnType("text") + .HasComment("所在国家/地区。"); + + b.Property("CoverImageUrl") + .HasColumnType("text") + .HasComment("品牌海报或封面图。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone") + .HasComment("服务生效时间(UTC)。"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone") + .HasComment("服务到期时间(UTC)。"); + + b.Property("Industry") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("所属行业,如餐饮、零售等。"); + + b.Property("LegalEntityName") + .HasColumnType("text") + .HasComment("法人或公司主体名称。"); + + b.Property("LogoUrl") + .HasColumnType("text") + .HasComment("LOGO 图片地址。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("租户全称或品牌名称。"); + + b.Property("OperatingMode") + .HasColumnType("integer") + .HasComment("经营模式(同一主体/不同主体)。"); + + b.Property("PrimaryOwnerUserId") + .HasColumnType("bigint") + .HasComment("系统内对应的租户所有者账号 ID。"); + + b.Property("Province") + .HasColumnType("text") + .HasComment("所在省份或州。"); + + b.Property("Remarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注信息,用于运营记录特殊说明。"); + + b.Property("ShortName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("对外展示的简称。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("租户当前状态,涵盖审核、启用、停用等场景。"); + + b.Property("SuspendedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次暂停服务时间。"); + + b.Property("SuspensionReason") + .HasColumnType("text") + .HasComment("暂停或终止的原因说明。"); + + b.Property("Tags") + .HasColumnType("text") + .HasComment("业务标签集合(逗号分隔)。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("Website") + .HasColumnType("text") + .HasComment("官网或主要宣传链接。"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ContactPhone") + .IsUnique(); + + b.ToTable("tenants", null, t => + { + t.HasComment("平台租户信息,描述租户的生命周期与基础资料。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAnnouncement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AnnouncementType") + .HasColumnType("integer") + .HasComment("公告类型。"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("公告正文(可为 Markdown/HTML,前端自行渲染)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone") + .HasComment("生效时间(UTC)。"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone") + .HasComment("失效时间(UTC),为空表示长期有效。"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否启用(已弃用,迁移期保留)。"); + + b.Property("Priority") + .HasColumnType("integer") + .HasComment("展示优先级,数值越大越靠前。"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone") + .HasComment("实际发布时间(UTC)。"); + + b.Property("PublisherScope") + .HasColumnType("integer") + .HasComment("发布者范围。"); + + b.Property("PublisherUserId") + .HasColumnType("bigint") + .HasComment("发布者用户 ID(平台或租户后台账号)。"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone") + .HasComment("撤销时间(UTC)。"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制字段。"); + + b.Property("ScheduledPublishAt") + .HasColumnType("timestamp with time zone") + .HasComment("预定发布时间(UTC)。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("公告状态。"); + + b.Property("TargetParameters") + .HasColumnType("text") + .HasComment("目标受众参数(JSON)。"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("目标受众类型。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("公告标题。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("Status", "EffectiveFrom") + .HasFilter("\"TenantId\" = 0"); + + b.HasIndex("TenantId", "AnnouncementType", "IsActive"); + + b.HasIndex("TenantId", "EffectiveFrom", "EffectiveTo"); + + b.HasIndex("TenantId", "Status", "EffectiveFrom"); + + b.ToTable("tenant_announcements", null, t => + { + t.HasComment("租户公告。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAnnouncementRead", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AnnouncementId") + .HasColumnType("bigint") + .HasComment("公告 ID。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone") + .HasComment("已读时间。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasComment("已读用户 ID(后台账号),为空表示租户级已读。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "AnnouncementId", "UserId") + .IsUnique(); + + b.ToTable("tenant_announcement_reads", null, t => + { + t.HasComment("租户公告已读记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantBillingStatement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AmountDue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("应付金额(原始金额)。"); + + b.Property("AmountPaid") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("实付金额。"); + + b.Property("BillingType") + .HasColumnType("integer") + .HasComment("账单类型(订阅账单/配额包账单/手动账单/续费账单)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasDefaultValue("CNY") + .HasComment("货币类型(默认 CNY)。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("折扣金额。"); + + b.Property("DueDate") + .HasColumnType("timestamp with time zone") + .HasComment("到期日。"); + + b.Property("LineItemsJson") + .HasColumnType("text") + .HasComment("账单明细 JSON,记录各项费用。"); + + b.Property("Notes") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注信息(如:人工备注、取消原因等)。"); + + b.Property("OverdueNotifiedAt") + .HasColumnType("timestamp with time zone") + .HasComment("逾期通知时间。"); + + b.Property("PeriodEnd") + .HasColumnType("timestamp with time zone") + .HasComment("账单周期结束时间。"); + + b.Property("PeriodStart") + .HasColumnType("timestamp with time zone") + .HasComment("账单周期开始时间。"); + + b.Property("ReminderSentAt") + .HasColumnType("timestamp with time zone") + .HasComment("提醒发送时间(续费提醒、逾期提醒等)。"); + + b.Property("StatementNo") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("账单编号,供对账查询。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("当前付款状态。"); + + b.Property("SubscriptionId") + .HasColumnType("bigint") + .HasComment("关联的订阅 ID(仅当 BillingType 为 Subscription 或 Renewal 时有值)。"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("税费金额。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("idx_billing_created_at"); + + b.HasIndex("Status", "DueDate") + .HasDatabaseName("idx_billing_status_duedate") + .HasFilter("\"Status\" IN (0, 2)"); + + b.HasIndex("TenantId", "StatementNo") + .IsUnique(); + + b.HasIndex("TenantId", "Status", "DueDate") + .HasDatabaseName("idx_billing_tenant_status_duedate"); + + b.ToTable("tenant_billing_statements", null, t => + { + t.HasComment("租户账单,用于呈现周期性收费。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Channel") + .HasColumnType("integer") + .HasComment("发布通道(站内、邮件、短信等)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("通知正文。"); + + b.Property("MetadataJson") + .HasColumnType("text") + .HasComment("附加元数据 JSON。"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone") + .HasComment("租户是否已阅读。"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone") + .HasComment("推送时间。"); + + b.Property("Severity") + .HasColumnType("integer") + .HasComment("通知重要级别。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("通知标题。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Channel", "SentAt"); + + b.ToTable("tenant_notifications", null, t => + { + t.HasComment("面向租户的站内通知或消息推送。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantPackage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("套餐描述,包含适用场景、权益等。"); + + b.Property("FeaturePoliciesJson") + .HasColumnType("text") + .HasComment("权益明细 JSON,记录自定义特性开关。"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否仍启用(平台控制)。"); + + b.Property("IsAllowNewTenantPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("是否允许新租户购买/选择(仅影响新购)。"); + + b.Property("IsPublicVisible") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("是否对外可见(展示页/套餐列表可见性)。"); + + b.Property("IsRecommended") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否推荐展示(运营推荐标识)。"); + + b.Property("MaxAccountCount") + .HasColumnType("integer") + .HasComment("允许创建的最大账号数。"); + + b.Property("MaxDeliveryOrders") + .HasColumnType("integer") + .HasComment("每月可调用的配送单数量上限。"); + + b.Property("MaxSmsCredits") + .HasColumnType("integer") + .HasComment("每月短信额度上限。"); + + b.Property("MaxStorageGb") + .HasColumnType("integer") + .HasComment("存储容量上限(GB)。"); + + b.Property("MaxStoreCount") + .HasColumnType("integer") + .HasComment("允许的最大门店数。"); + + b.Property("MonthlyPrice") + .HasColumnType("numeric") + .HasComment("月付价格,单位:人民币元。"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("套餐名称,展示给租户的简称。"); + + b.Property("PackageType") + .HasColumnType("integer") + .HasComment("套餐分类(试用、标准、旗舰等)。"); + + b.Property("PublishStatus") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasComment("发布状态:0=草稿,1=已发布。"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasComment("展示排序,数值越小越靠前。"); + + b.PrimitiveCollection("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasComment("套餐标签(用于展示与对比页)。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("YearlyPrice") + .HasColumnType("numeric") + .HasComment("年付价格,单位:人民币元。"); + + b.HasKey("Id"); + + b.HasIndex("IsActive", "SortOrder"); + + b.HasIndex("PublishStatus", "IsActive", "IsPublicVisible", "IsAllowNewTenantPurchase", "SortOrder"); + + b.ToTable("tenant_packages", null, t => + { + t.HasComment("平台提供的租户套餐定义。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("支付金额。"); + + b.Property("BillingStatementId") + .HasColumnType("bigint") + .HasComment("关联的账单 ID。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Method") + .HasColumnType("integer") + .HasComment("支付方式。"); + + b.Property("Notes") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注信息。"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasComment("支付时间。"); + + b.Property("ProofUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("支付凭证 URL。"); + + b.Property("RefundReason") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("退款原因。"); + + b.Property("RefundedAt") + .HasColumnType("timestamp with time zone") + .HasComment("退款时间。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("支付状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TransactionNo") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("交易号。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("VerifiedAt") + .HasColumnType("timestamp with time zone") + .HasComment("审核时间。"); + + b.Property("VerifiedBy") + .HasColumnType("bigint") + .HasComment("审核人 ID(管理员)。"); + + b.HasKey("Id"); + + b.HasIndex("TransactionNo") + .HasDatabaseName("idx_payment_transaction_no") + .HasFilter("\"TransactionNo\" IS NOT NULL"); + + b.HasIndex("BillingStatementId", "PaidAt") + .HasDatabaseName("idx_payment_billing_paidat"); + + b.HasIndex("TenantId", "BillingStatementId"); + + b.ToTable("tenant_payments", null, t => + { + t.HasComment("租户支付记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantQuotaPackagePurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasComment("过期时间(可选)。"); + + b.Property("Notes") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注。"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("购买价格。"); + + b.Property("PurchasedAt") + .HasColumnType("timestamp with time zone") + .HasComment("购买时间。"); + + b.Property("QuotaPackageId") + .HasColumnType("bigint") + .HasComment("配额包 ID。"); + + b.Property("QuotaValue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasComment("购买时的配额值。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "QuotaPackageId", "PurchasedAt"); + + b.ToTable("tenant_quota_package_purchases", null, t => + { + t.HasComment("租户配额包购买记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantQuotaUsage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("LastResetAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次重置时间。"); + + b.Property("LimitValue") + .HasColumnType("numeric") + .HasComment("当前配额上限。"); + + b.Property("QuotaType") + .HasColumnType("integer") + .HasComment("配额类型,例如门店数、短信条数等。"); + + b.Property("ResetCycle") + .HasColumnType("text") + .HasComment("配额刷新周期描述(如月、年)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UsedValue") + .HasColumnType("numeric") + .HasComment("已消耗的数量。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "QuotaType") + .IsUnique(); + + b.ToTable("tenant_quota_usages", null, t => + { + t.HasComment("租户配额使用情况快照。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantQuotaUsageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeAmount") + .HasColumnType("numeric") + .HasComment("变更量(可选)。"); + + b.Property("ChangeReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("变更原因(可选)。"); + + b.Property("ChangeType") + .HasColumnType("integer") + .HasComment("变更类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("LimitValue") + .HasColumnType("numeric") + .HasComment("限额值(记录时刻的快照)。"); + + b.Property("QuotaType") + .HasColumnType("integer") + .HasComment("配额类型。"); + + b.Property("RecordedAt") + .HasColumnType("timestamp with time zone") + .HasComment("记录时间(UTC)。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.Property("UsedValue") + .HasColumnType("numeric") + .HasComment("已使用值(记录时刻的快照)。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "RecordedAt"); + + b.HasIndex("TenantId", "QuotaType", "RecordedAt"); + + b.ToTable("tenant_quota_usage_histories", null, t => + { + t.HasComment("租户配额使用历史记录(用于追踪配额上下限与使用量的时间序列变化)。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantReviewClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone") + .HasComment("领取时间(UTC)。"); + + b.Property("ClaimedBy") + .HasColumnType("bigint") + .HasComment("领取人用户 ID。"); + + b.Property("ClaimedByName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("领取人名称(展示用快照)。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("ReleasedAt") + .HasColumnType("timestamp with time zone") + .HasComment("释放时间(UTC),未释放时为 null。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("被领取的租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedBy"); + + b.HasIndex("TenantId") + .IsUnique() + .HasFilter("\"ReleasedAt\" IS NULL AND \"DeletedAt\" IS NULL"); + + b.ToTable("tenant_review_claims", null, t => + { + t.HasComment("租户入驻审核领取记录(防止多管理员并发审核)。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoRenew") + .HasColumnType("boolean") + .HasComment("是否开启自动续费。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone") + .HasComment("订阅生效时间(UTC)。"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone") + .HasComment("订阅到期时间(UTC)。"); + + b.Property("NextBillingDate") + .HasColumnType("timestamp with time zone") + .HasComment("下一个计费时间,配合自动续费使用。"); + + b.Property("Notes") + .HasColumnType("text") + .HasComment("运营备注信息。"); + + b.Property("ScheduledPackageId") + .HasColumnType("bigint") + .HasComment("若已排期升降配,对应的新套餐 ID。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("订阅当前状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("TenantPackageId") + .HasColumnType("bigint") + .HasComment("当前订阅关联的套餐标识。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "TenantPackageId"); + + b.ToTable("tenant_subscriptions", null, t => + { + t.HasComment("租户套餐订阅记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantSubscriptionHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("numeric") + .HasComment("相关费用。"); + + b.Property("ChangeType") + .HasColumnType("integer") + .HasComment("变更类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("Currency") + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasComment("币种。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone") + .HasComment("生效时间。"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone") + .HasComment("到期时间。"); + + b.Property("FromPackageId") + .HasColumnType("bigint") + .HasComment("原套餐 ID。"); + + b.Property("Notes") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("备注。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("租户标识。"); + + b.Property("TenantSubscriptionId") + .HasColumnType("bigint") + .HasComment("对应的订阅 ID。"); + + b.Property("ToPackageId") + .HasColumnType("bigint") + .HasComment("新套餐 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "TenantSubscriptionId"); + + b.ToTable("tenant_subscription_histories", null, t => + { + t.HasComment("租户套餐订阅变更记录。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantVerificationProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalDataJson") + .HasColumnType("text") + .HasComment("附加资料(JSON)。"); + + b.Property("BankAccountName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("开户名。"); + + b.Property("BankAccountNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("银行账号。"); + + b.Property("BankName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("银行名称。"); + + b.Property("BusinessLicenseNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("营业执照编号。"); + + b.Property("BusinessLicenseUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("营业执照文件地址。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("LegalPersonIdBackUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("法人身份证反面。"); + + b.Property("LegalPersonIdFrontUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("法人身份证正面。"); + + b.Property("LegalPersonIdNumber") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasComment("法人身份证号。"); + + b.Property("LegalPersonName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("法人姓名。"); + + b.Property("ReviewRemarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("审核备注。"); + + b.Property("ReviewedAt") + .HasColumnType("timestamp with time zone") + .HasComment("审核时间。"); + + b.Property("ReviewedBy") + .HasColumnType("bigint") + .HasComment("审核人 ID。"); + + b.Property("ReviewedByName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("审核人姓名。"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("实名状态。"); + + b.Property("SubmittedAt") + .HasColumnType("timestamp with time zone") + .HasComment("提交时间。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("对应的租户标识。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId") + .IsUnique(); + + b.ToTable("tenant_verification_profiles", null, t => + { + t.HasComment("租户实名认证资料。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b => + { + b.HasOne("TakeoutSaaS.Domain.Orders.Entities.Order", null) + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251229071911_AddMerchantManagement.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251229071911_AddMerchantManagement.cs new file mode 100644 index 0000000..dcf8f5b --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251229071911_AddMerchantManagement.cs @@ -0,0 +1,244 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Migrations +{ + /// + public partial class AddMerchantManagement : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OperatingMode", + table: "tenants", + type: "integer", + nullable: true, + comment: "经营模式(同一主体/不同主体)。"); + + migrationBuilder.AddColumn( + name: "BusinessLicenseImageUrl", + table: "stores", + type: "character varying(500)", + maxLength: 500, + nullable: true, + comment: "门店营业执照图片地址(主体不一致模式使用)。"); + + migrationBuilder.AddColumn( + name: "BusinessLicenseNumber", + table: "stores", + type: "character varying(50)", + maxLength: 50, + nullable: true, + comment: "门店营业执照号(主体不一致模式使用)。"); + + migrationBuilder.AddColumn( + name: "LegalRepresentative", + table: "stores", + type: "character varying(100)", + maxLength: 100, + nullable: true, + comment: "门店法人(主体不一致模式使用)。"); + + migrationBuilder.AddColumn( + name: "RegisteredAddress", + table: "stores", + type: "character varying(500)", + maxLength: 500, + nullable: true, + comment: "门店注册地址(主体不一致模式使用)。"); + + migrationBuilder.AddColumn( + name: "ApprovedAt", + table: "merchants", + type: "timestamp with time zone", + nullable: true, + comment: "审核通过时间。"); + + migrationBuilder.AddColumn( + name: "ApprovedBy", + table: "merchants", + type: "bigint", + nullable: true, + comment: "审核通过人。"); + + migrationBuilder.AddColumn( + name: "ClaimExpiresAt", + table: "merchants", + type: "timestamp with time zone", + nullable: true, + comment: "领取过期时间。"); + + migrationBuilder.AddColumn( + name: "ClaimedAt", + table: "merchants", + type: "timestamp with time zone", + nullable: true, + comment: "领取时间。"); + + migrationBuilder.AddColumn( + name: "ClaimedBy", + table: "merchants", + type: "bigint", + nullable: true, + comment: "当前领取人。"); + + migrationBuilder.AddColumn( + name: "ClaimedByName", + table: "merchants", + type: "character varying(100)", + maxLength: 100, + nullable: true, + comment: "当前领取人姓名。"); + + migrationBuilder.AddColumn( + name: "FrozenAt", + table: "merchants", + type: "timestamp with time zone", + nullable: true, + comment: "冻结时间。"); + + migrationBuilder.AddColumn( + name: "FrozenReason", + table: "merchants", + type: "character varying(500)", + maxLength: 500, + nullable: true, + comment: "冻结原因。"); + + migrationBuilder.AddColumn( + name: "IsFrozen", + table: "merchants", + type: "boolean", + nullable: false, + defaultValue: false, + comment: "是否冻结业务。"); + + migrationBuilder.AddColumn( + name: "LastReviewedBy", + table: "merchants", + type: "bigint", + nullable: true, + comment: "最近一次审核人。"); + + migrationBuilder.AddColumn( + name: "OperatingMode", + table: "merchants", + type: "integer", + nullable: true, + comment: "经营模式(同一主体/不同主体)。"); + + migrationBuilder.AddColumn( + name: "RowVersion", + table: "merchants", + type: "bytea", + rowVersion: true, + nullable: false, + defaultValue: new byte[0], + comment: "并发控制版本。"); + + migrationBuilder.CreateIndex( + name: "IX_stores_MerchantId_BusinessLicenseNumber", + table: "stores", + columns: new[] { "MerchantId", "BusinessLicenseNumber" }, + unique: true, + filter: "\"BusinessLicenseNumber\" IS NOT NULL AND \"Status\" <> 3"); + + migrationBuilder.CreateIndex( + name: "IX_merchants_ClaimedBy", + table: "merchants", + column: "ClaimedBy"); + + migrationBuilder.CreateIndex( + name: "IX_merchants_TenantId_Status", + table: "merchants", + columns: new[] { "TenantId", "Status" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_stores_MerchantId_BusinessLicenseNumber", + table: "stores"); + + migrationBuilder.DropIndex( + name: "IX_merchants_ClaimedBy", + table: "merchants"); + + migrationBuilder.DropIndex( + name: "IX_merchants_TenantId_Status", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "OperatingMode", + table: "tenants"); + + migrationBuilder.DropColumn( + name: "BusinessLicenseImageUrl", + table: "stores"); + + migrationBuilder.DropColumn( + name: "BusinessLicenseNumber", + table: "stores"); + + migrationBuilder.DropColumn( + name: "LegalRepresentative", + table: "stores"); + + migrationBuilder.DropColumn( + name: "RegisteredAddress", + table: "stores"); + + migrationBuilder.DropColumn( + name: "ApprovedAt", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "ApprovedBy", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "ClaimExpiresAt", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "ClaimedAt", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "ClaimedBy", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "ClaimedByName", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "FrozenAt", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "FrozenReason", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "IsFrozen", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "LastReviewedBy", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "OperatingMode", + table: "merchants"); + + migrationBuilder.DropColumn( + name: "RowVersion", + table: "merchants"); + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251229071940_AddMerchantManagementLogs.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251229071940_AddMerchantManagementLogs.Designer.cs new file mode 100644 index 0000000..5a8c373 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251229071940_AddMerchantManagementLogs.Designer.cs @@ -0,0 +1,454 @@ +// +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.Logs.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb +{ + [DbContext(typeof(TakeoutLogsDbContext))] + [Migration("20251229071940_AddMerchantManagementLogs")] + partial class AddMerchantManagementLogs + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberGrowthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeValue") + .HasColumnType("integer") + .HasComment("变动数量。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentValue") + .HasColumnType("integer") + .HasComment("当前成长值。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("MemberId") + .HasColumnType("bigint") + .HasComment("会员标识。"); + + b.Property("Notes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "MemberId", "OccurredAt"); + + b.ToTable("member_growth_logs", null, t => + { + t.HasComment("成长值变动日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasComment("动作类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("详情描述。"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("操作 IP。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("商户标识。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人 ID。"); + + b.Property("OperatorName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("操作人名称。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasComment("标题。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("MerchantId", "CreatedAt"); + + b.HasIndex("TenantId", "CreatedAt"); + + b.HasIndex("TenantId", "MerchantId"); + + b.ToTable("merchant_audit_logs", null, t => + { + t.HasComment("商户入驻审核日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeReason") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("变更原因。"); + + b.Property("ChangeType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("变更类型。"); + + b.Property("ChangedBy") + .HasColumnType("bigint") + .HasComment("变更人 ID。"); + + b.Property("ChangedByName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("变更人名称。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("FieldName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("变更字段名。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("商户标识。"); + + b.Property("NewValue") + .HasColumnType("text") + .HasComment("变更后值。"); + + b.Property("OldValue") + .HasColumnType("text") + .HasComment("变更前值。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("MerchantId", "CreatedAt"); + + b.HasIndex("TenantId", "CreatedAt"); + + b.ToTable("merchant_change_logs", null, t => + { + t.HasComment("商户变更日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.OperationLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("OperationType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作类型:BatchExtend, BatchRemind, StatusChange 等。"); + + b.Property("OperatorId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人ID。"); + + b.Property("OperatorName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("操作人名称。"); + + b.Property("Parameters") + .HasColumnType("text") + .HasComment("操作参数(JSON)。"); + + b.Property("Result") + .HasColumnType("text") + .HasComment("操作结果(JSON)。"); + + b.Property("Success") + .HasColumnType("boolean") + .HasComment("是否成功。"); + + b.Property("TargetIds") + .HasColumnType("text") + .HasComment("目标ID列表(JSON)。"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("目标类型:Subscription, Bill 等。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("OperationType", "CreatedAt"); + + b.ToTable("operation_logs", null, t => + { + t.HasComment("运营操作日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasComment("操作类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentStatus") + .HasColumnType("integer") + .HasComment("新状态。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("详细描述。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人 ID。"); + + b.Property("OperatorName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人名称。"); + + b.Property("PreviousStatus") + .HasColumnType("integer") + .HasComment("原状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("关联的租户标识。"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("日志标题。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("tenant_audit_logs", null, t => + { + t.HasComment("租户运营审核日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Infrastructure.Logs.Persistence.OperationLogInboxMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConsumedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MessageId") + .IsUnique(); + + b.ToTable("operation_log_inbox_messages", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251229071940_AddMerchantManagementLogs.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251229071940_AddMerchantManagementLogs.cs new file mode 100644 index 0000000..3da2be4 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251229071940_AddMerchantManagementLogs.cs @@ -0,0 +1,141 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb +{ + /// + public partial class AddMerchantManagementLogs : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Title", + table: "merchant_audit_logs", + type: "character varying(200)", + maxLength: 200, + nullable: false, + comment: "标题。", + oldClrType: typeof(string), + oldType: "character varying(128)", + oldMaxLength: 128, + oldComment: "标题。"); + + migrationBuilder.AlterColumn( + name: "OperatorName", + table: "merchant_audit_logs", + type: "character varying(100)", + maxLength: 100, + nullable: true, + comment: "操作人名称。", + oldClrType: typeof(string), + oldType: "character varying(64)", + oldMaxLength: 64, + oldNullable: true, + oldComment: "操作人名称。"); + + migrationBuilder.AddColumn( + name: "IpAddress", + table: "merchant_audit_logs", + type: "character varying(50)", + maxLength: 50, + nullable: true, + comment: "操作 IP。"); + + migrationBuilder.CreateTable( + name: "merchant_change_logs", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false, comment: "实体唯一标识。") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + MerchantId = table.Column(type: "bigint", nullable: false, comment: "商户标识。"), + FieldName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false, comment: "变更字段名。"), + OldValue = table.Column(type: "text", nullable: true, comment: "变更前值。"), + NewValue = table.Column(type: "text", nullable: true, comment: "变更后值。"), + ChangeType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, comment: "变更类型。"), + ChangedBy = table.Column(type: "bigint", nullable: true, comment: "变更人 ID。"), + ChangedByName = table.Column(type: "character varying(100)", maxLength: 100, nullable: true, comment: "变更人名称。"), + ChangeReason = table.Column(type: "character varying(512)", maxLength: 512, nullable: true, comment: "变更原因。"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间(UTC)。"), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间(UTC),从未更新时为 null。"), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true, comment: "软删除时间(UTC),未删除时为 null。"), + CreatedBy = table.Column(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"), + UpdatedBy = table.Column(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"), + DeletedBy = table.Column(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"), + TenantId = table.Column(type: "bigint", nullable: false, comment: "所属租户 ID。") + }, + constraints: table => + { + table.PrimaryKey("PK_merchant_change_logs", x => x.Id); + }, + comment: "商户变更日志。"); + + migrationBuilder.CreateIndex( + name: "IX_merchant_audit_logs_MerchantId_CreatedAt", + table: "merchant_audit_logs", + columns: new[] { "MerchantId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_merchant_audit_logs_TenantId_CreatedAt", + table: "merchant_audit_logs", + columns: new[] { "TenantId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_merchant_change_logs_MerchantId_CreatedAt", + table: "merchant_change_logs", + columns: new[] { "MerchantId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_merchant_change_logs_TenantId_CreatedAt", + table: "merchant_change_logs", + columns: new[] { "TenantId", "CreatedAt" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "merchant_change_logs"); + + migrationBuilder.DropIndex( + name: "IX_merchant_audit_logs_MerchantId_CreatedAt", + table: "merchant_audit_logs"); + + migrationBuilder.DropIndex( + name: "IX_merchant_audit_logs_TenantId_CreatedAt", + table: "merchant_audit_logs"); + + migrationBuilder.DropColumn( + name: "IpAddress", + table: "merchant_audit_logs"); + + migrationBuilder.AlterColumn( + name: "Title", + table: "merchant_audit_logs", + type: "character varying(128)", + maxLength: 128, + nullable: false, + comment: "标题。", + oldClrType: typeof(string), + oldType: "character varying(200)", + oldMaxLength: 200, + oldComment: "标题。"); + + migrationBuilder.AlterColumn( + name: "OperatorName", + table: "merchant_audit_logs", + type: "character varying(64)", + maxLength: 64, + nullable: true, + comment: "操作人名称。", + oldClrType: typeof(string), + oldType: "character varying(100)", + oldMaxLength: 100, + oldNullable: true, + oldComment: "操作人名称。"); + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs index 7cfe69a..ad67000 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("ProductVersion", "10.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -124,6 +124,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb .HasColumnType("character varying(1024)") .HasComment("详情描述。"); + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("操作 IP。"); + b.Property("MerchantId") .HasColumnType("bigint") .HasComment("商户标识。"); @@ -133,8 +138,8 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb .HasComment("操作人 ID。"); b.Property("OperatorName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") + .HasMaxLength(100) + .HasColumnType("character varying(100)") .HasComment("操作人名称。"); b.Property("TenantId") @@ -143,8 +148,8 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb b.Property("Title") .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") + .HasMaxLength(200) + .HasColumnType("character varying(200)") .HasComment("标题。"); b.Property("UpdatedAt") @@ -157,6 +162,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb b.HasKey("Id"); + b.HasIndex("MerchantId", "CreatedAt"); + + b.HasIndex("TenantId", "CreatedAt"); + b.HasIndex("TenantId", "MerchantId"); b.ToTable("merchant_audit_logs", null, t => @@ -165,6 +174,93 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb }); }); + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeReason") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasComment("变更原因。"); + + b.Property("ChangeType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("变更类型。"); + + b.Property("ChangedBy") + .HasColumnType("bigint") + .HasComment("变更人 ID。"); + + b.Property("ChangedByName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("变更人名称。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("FieldName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("变更字段名。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("商户标识。"); + + b.Property("NewValue") + .HasColumnType("text") + .HasComment("变更后值。"); + + b.Property("OldValue") + .HasColumnType("text") + .HasComment("变更前值。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("MerchantId", "CreatedAt"); + + b.HasIndex("TenantId", "CreatedAt"); + + b.ToTable("merchant_change_logs", null, t => + { + t.HasComment("商户变更日志。"); + }); + }); + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.OperationLog", b => { b.Property("Id") diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs index 3d77367..26d5026 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace TakeoutSaaS.Infrastructure.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("ProductVersion", "10.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -2373,6 +2373,14 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(256)") .HasComment("详细地址。"); + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone") + .HasComment("审核通过时间。"); + + b.Property("ApprovedBy") + .HasColumnType("bigint") + .HasComment("审核通过人。"); + b.Property("BrandAlias") .HasMaxLength(64) .HasColumnType("character varying(64)") @@ -2402,6 +2410,23 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(64)") .HasComment("所在城市。"); + b.Property("ClaimExpiresAt") + .HasColumnType("timestamp with time zone") + .HasComment("领取过期时间。"); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone") + .HasComment("领取时间。"); + + b.Property("ClaimedBy") + .HasColumnType("bigint") + .HasComment("当前领取人。"); + + b.Property("ClaimedByName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("当前领取人姓名。"); + b.Property("ContactEmail") .HasMaxLength(128) .HasColumnType("character varying(128)") @@ -2434,6 +2459,21 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(64)") .HasComment("所在区县。"); + b.Property("FrozenAt") + .HasColumnType("timestamp with time zone") + .HasComment("冻结时间。"); + + b.Property("FrozenReason") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("冻结原因。"); + + b.Property("IsFrozen") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否冻结业务。"); + b.Property("JoinedAt") .HasColumnType("timestamp with time zone") .HasComment("入驻时间。"); @@ -2442,6 +2482,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("timestamp with time zone") .HasComment("最近一次审核时间。"); + b.Property("LastReviewedBy") + .HasColumnType("bigint") + .HasComment("最近一次审核人。"); + b.Property("Latitude") .HasColumnType("double precision") .HasComment("纬度信息。"); @@ -2459,6 +2503,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("double precision") .HasComment("经度信息。"); + b.Property("OperatingMode") + .HasColumnType("integer") + .HasComment("经营模式(同一主体/不同主体)。"); + b.Property("Province") .HasMaxLength(64) .HasColumnType("character varying(64)") @@ -2469,6 +2517,13 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(512)") .HasComment("审核备注或驳回原因。"); + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasComment("并发控制版本。"); + b.Property("ServicePhone") .HasColumnType("text") .HasComment("客服电话。"); @@ -2499,8 +2554,12 @@ namespace TakeoutSaaS.Infrastructure.Migrations b.HasKey("Id"); + b.HasIndex("ClaimedBy"); + b.HasIndex("TenantId"); + b.HasIndex("TenantId", "Status"); + b.ToTable("merchants", null, t => { t.HasComment("商户主体信息,承载入驻和资质审核结果。"); @@ -4784,6 +4843,16 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(256)") .HasComment("门店营业时段描述(备用字符串)。"); + b.Property("BusinessLicenseImageUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("门店营业执照图片地址(主体不一致模式使用)。"); + + b.Property("BusinessLicenseNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("门店营业执照号(主体不一致模式使用)。"); + b.Property("City") .HasMaxLength(64) .HasColumnType("character varying(64)") @@ -4837,6 +4906,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("double precision") .HasComment("纬度。"); + b.Property("LegalRepresentative") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("门店法人(主体不一致模式使用)。"); + b.Property("Longitude") .HasColumnType("double precision") .HasComment("高德/腾讯地图经度。"); @@ -4866,6 +4940,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(64)") .HasComment("所在省份。"); + b.Property("RegisteredAddress") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("门店注册地址(主体不一致模式使用)。"); + b.Property("Status") .HasColumnType("integer") .HasComment("门店当前运营状态。"); @@ -4908,6 +4987,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations b.HasKey("Id"); + b.HasIndex("MerchantId", "BusinessLicenseNumber") + .IsUnique() + .HasFilter("\"BusinessLicenseNumber\" IS NOT NULL AND \"Status\" <> 3"); + b.HasIndex("TenantId", "Code") .IsUnique(); @@ -5704,6 +5787,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("character varying(128)") .HasComment("租户全称或品牌名称。"); + b.Property("OperatingMode") + .HasColumnType("integer") + .HasComment("经营模式(同一主体/不同主体)。"); + b.Property("PrimaryOwnerUserId") .HasColumnType("bigint") .HasComment("系统内对应的租户所有者账号 ID。"); @@ -5806,6 +5893,18 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("timestamp with time zone") .HasComment("失效时间(UTC),为空表示长期有效。"); + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否启用(已弃用,迁移期保留)。"); + + b.Property("Priority") + .HasColumnType("integer") + .HasComment("展示优先级,数值越大越靠前。"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone") + .HasComment("实际发布时间(UTC)。"); + b.Property("PublisherScope") .HasColumnType("integer") .HasComment("发布者范围。"); @@ -5814,32 +5913,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("bigint") .HasComment("发布者用户 ID(平台或租户后台账号)。"); - b.Property("Status") - .HasColumnType("integer") - .HasComment("公告状态。"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasComment("实际发布时间(UTC)。"); - b.Property("RevokedAt") .HasColumnType("timestamp with time zone") .HasComment("撤销时间(UTC)。"); - b.Property("ScheduledPublishAt") - .HasColumnType("timestamp with time zone") - .HasComment("预定发布时间(UTC)。"); - - b.Property("TargetType") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasComment("目标受众类型。"); - - b.Property("TargetParameters") - .HasColumnType("text") - .HasComment("目标受众参数(JSON)。"); - b.Property("RowVersion") .IsConcurrencyToken() .IsRequired() @@ -5847,13 +5924,23 @@ namespace TakeoutSaaS.Infrastructure.Migrations .HasColumnType("bytea") .HasComment("并发控制字段。"); - b.Property("IsActive") - .HasColumnType("boolean") - .HasComment("是否启用(已弃用,迁移期保留)。"); + b.Property("ScheduledPublishAt") + .HasColumnType("timestamp with time zone") + .HasComment("预定发布时间(UTC)。"); - b.Property("Priority") + b.Property("Status") .HasColumnType("integer") - .HasComment("展示优先级,数值越大越靠前。"); + .HasComment("公告状态。"); + + b.Property("TargetParameters") + .HasColumnType("text") + .HasComment("目标受众参数(JSON)。"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("目标受众类型。"); b.Property("TenantId") .HasColumnType("bigint") @@ -5875,15 +5962,15 @@ namespace TakeoutSaaS.Infrastructure.Migrations b.HasKey("Id"); + b.HasIndex("Status", "EffectiveFrom") + .HasFilter("\"TenantId\" = 0"); + b.HasIndex("TenantId", "AnnouncementType", "IsActive"); b.HasIndex("TenantId", "EffectiveFrom", "EffectiveTo"); b.HasIndex("TenantId", "Status", "EffectiveFrom"); - b.HasIndex("Status", "EffectiveFrom") - .HasFilter("\"TenantId\" = 0"); - b.ToTable("tenant_announcements", null, t => { t.HasComment("租户公告。");