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; using TakeoutSaaS.Domain.Merchants.Enums; using TakeoutSaaS.Module.Authorization.Attributes; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Results; using TakeoutSaaS.Shared.Web.Api; namespace TakeoutSaaS.AdminApi.Controllers; /// /// 商户管理。 /// [ApiVersion("1.0")] [Authorize] [Route("api/admin/v{version:apiVersion}/merchants")] public sealed class MerchantsController(IMediator mediator) : BaseApiController { /// /// 创建商户。 /// /// 创建命令。 /// 取消标记。 /// 创建后的商户。 [HttpPost] [PermissionAuthorize("merchant:create")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Create([FromBody] CreateMerchantCommand command, CancellationToken cancellationToken) { // 1. 创建商户 var result = await mediator.Send(command, cancellationToken); // 2. 返回创建结果 return ApiResponse.Ok(result); } /// /// 查询商户列表。 /// /// 查询参数。 /// 取消标记。 /// 商户分页结果。 [HttpGet] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> List( [FromQuery] GetMerchantListQuery query, CancellationToken cancellationToken = default) { var result = await mediator.Send(query, cancellationToken); 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); } /// /// 更新商户。 /// /// 商户 ID。 /// 更新命令。 /// 取消标记。 /// 更新后的商户或未找到。 [HttpPut("{merchantId:long}")] [PermissionAuthorize("merchant:update")] [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) { if (command.MerchantId != 0 && command.MerchantId != merchantId) { return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 merchantId 与请求体 merchantId 不一致"); } command = command with { MerchantId = merchantId }; // 2. 执行更新 var result = await mediator.Send(command, cancellationToken); // 3. 返回更新结果或 404 if (result == null) { return ApiResponse.Error(ErrorCodes.NotFound, "商户不存在"); } if (result.RequiresReview) { return ApiResponse.Error( ErrorCodes.ValidationFailed, "关键信息修改,商户已进入待审核状态,业务已冻结") with { Data = result }; } return ApiResponse.Ok(result); } /// /// 删除商户。 /// /// 商户 ID。 /// 取消标记。 /// 删除结果。 [HttpDelete("{merchantId:long}")] [PermissionAuthorize("merchant:delete")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Delete(long merchantId, CancellationToken cancellationToken) { // 1. 执行删除 var success = await mediator.Send(new DeleteMerchantCommand { MerchantId = merchantId }, cancellationToken); // 2. 返回删除结果或 404 return success ? ApiResponse.Ok(null) : ApiResponse.Error(ErrorCodes.NotFound, "商户不存在"); } /// /// 获取商户概览。 /// /// 商户 ID。 /// 取消标记。 /// 商户概览或未找到。 [HttpGet("{merchantId:long}")] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Detail(long merchantId, CancellationToken cancellationToken) { // 1. 查询商户概览 var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken); // 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); } /// /// 获取商户详细资料(含证照、合同)。 /// /// 创建的证照信息。 [HttpGet("{merchantId:long}/detail")] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> FullDetail(long merchantId, CancellationToken cancellationToken) { // 1. 查询商户详细资料 var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken); // 2. 返回详情 return ApiResponse.Ok(result); } /// /// 上传商户证照信息(先通过文件上传接口获取 COS 地址)。 /// /// 创建的证照信息。 [HttpPost("{merchantId:long}/documents")] [PermissionAuthorize("merchant:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CreateDocument( long merchantId, [FromBody] AddMerchantDocumentCommand body, CancellationToken cancellationToken) { // 1. 绑定商户标识 var command = body with { MerchantId = merchantId }; // 2. 创建证照记录 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } /// /// 商户证照列表。 /// /// 商户证照列表。 [HttpGet("{merchantId:long}/documents")] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Documents(long merchantId, CancellationToken cancellationToken) { // 1. 查询证照列表 var result = await mediator.Send(new GetMerchantDocumentsQuery(merchantId), cancellationToken); // 2. 返回证照集合 return ApiResponse>.Ok(result); } /// /// 审核指定证照。 /// /// 审核后的证照信息。 [HttpPost("{merchantId:long}/documents/{documentId:long}/review")] [PermissionAuthorize("merchant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> ReviewDocument( long merchantId, long documentId, [FromBody] ReviewMerchantDocumentCommand body, CancellationToken cancellationToken) { // 1. 绑定商户与证照标识 var command = body with { MerchantId = merchantId, DocumentId = documentId }; // 2. 执行审核 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } /// /// 新增商户合同。 /// /// 创建的合同信息。 [HttpPost("{merchantId:long}/contracts")] [PermissionAuthorize("merchant:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CreateContract( long merchantId, [FromBody] CreateMerchantContractCommand body, CancellationToken cancellationToken) { // 1. 绑定商户标识 var command = body with { MerchantId = merchantId }; // 2. 创建合同 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } /// /// 合同列表。 /// /// 商户合同列表。 [HttpGet("{merchantId:long}/contracts")] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Contracts(long merchantId, CancellationToken cancellationToken) { // 1. 查询合同列表 var result = await mediator.Send(new GetMerchantContractsQuery(merchantId), cancellationToken); // 2. 返回合同集合 return ApiResponse>.Ok(result); } /// /// 更新合同状态(生效/终止等)。 /// /// 更新后的合同信息。 [HttpPut("{merchantId:long}/contracts/{contractId:long}/status")] [PermissionAuthorize("merchant:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> UpdateContractStatus( long merchantId, long contractId, [FromBody] UpdateMerchantContractStatusCommand body, CancellationToken cancellationToken) { // 1. 绑定商户与合同标识 var command = body with { MerchantId = merchantId, ContractId = contractId }; // 2. 更新合同状态 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } /// /// 审核商户(通过/驳回)。 /// /// 审核后的商户信息。 [HttpPost("{merchantId:long}/review")] [PermissionAuthorize("merchant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Review(long merchantId, [FromBody] ReviewMerchantCommand body, CancellationToken cancellationToken) { // 1. 绑定商户标识 var command = body with { MerchantId = merchantId }; // 2. 执行审核 var result = await mediator.Send(command, cancellationToken); 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); } /// /// 审核日志。 /// /// 商户审核日志分页结果。 [HttpGet("{merchantId:long}/audits")] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> AuditLogs( long merchantId, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default) { // 1. 查询审核日志 var result = await mediator.Send(new GetMerchantAuditLogsQuery(merchantId, page, pageSize), cancellationToken); // 2. 返回日志分页 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"); } /// /// 可选商户类目列表。 /// /// 可选的商户类目列表。 [HttpGet("categories")] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Categories(CancellationToken cancellationToken) { // 1. 查询可选类目 var result = await mediator.Send(new GetMerchantCategoriesQuery(), cancellationToken); // 2. 返回类目列表 return ApiResponse>.Ok(result); } }