完成门店管理后端接口与任务

This commit is contained in:
2026-01-01 07:26:14 +08:00
parent dc9f6136d6
commit fc55003d3d
131 changed files with 15333 additions and 201 deletions

View File

@@ -0,0 +1,222 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Application.App.StoreAudits.Commands;
using TakeoutSaaS.Application.App.StoreAudits.Dto;
using TakeoutSaaS.Application.App.StoreAudits.Queries;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 门店审核与风控管理(平台)。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/platform/store-audits")]
[Route("api/admin/v{version:apiVersion}/platform/store-audits")]
public sealed class StoreAuditsController(IMediator mediator, ITenantContextAccessor tenantContextAccessor) : BaseApiController
{
/// <summary>
/// 查询待审核门店列表。
/// </summary>
/// <returns>待审核门店分页列表。</returns>
[HttpGet("pending")]
[PermissionAuthorize("store-audit:read")]
[ProducesResponseType(typeof(ApiResponse<PagedResult<PendingStoreAuditDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PagedResult<PendingStoreAuditDto>>> ListPending(
[FromQuery] ListPendingStoreAuditsQuery query,
CancellationToken cancellationToken)
{
// 1. 查询待审核门店列表
var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken));
// 2. 返回分页结果
return ApiResponse<PagedResult<PendingStoreAuditDto>>.Ok(result);
}
/// <summary>
/// 获取门店审核详情。
/// </summary>
/// <param name="storeId">门店 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>审核详情。</returns>
[HttpGet("{storeId:long}")]
[PermissionAuthorize("store-audit:read")]
[ProducesResponseType(typeof(ApiResponse<StoreAuditDetailDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<StoreAuditDetailDto>> GetDetail(long storeId, CancellationToken cancellationToken)
{
// 1. 获取审核详情
var result = await ExecuteAsPlatformAsync(() =>
mediator.Send(new GetStoreAuditDetailQuery { StoreId = storeId }, cancellationToken));
// 2. 返回详情或未找到
return result is null
? ApiResponse<StoreAuditDetailDto>.Error(ErrorCodes.NotFound, "门店不存在")
: ApiResponse<StoreAuditDetailDto>.Ok(result);
}
/// <summary>
/// 审核通过。
/// </summary>
/// <param name="storeId">门店 ID。</param>
/// <param name="command">审核命令。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>操作结果。</returns>
[HttpPost("{storeId:long}/approve")]
[PermissionAuthorize("store-audit:approve")]
[ProducesResponseType(typeof(ApiResponse<StoreAuditActionResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreAuditActionResultDto>> Approve(
long storeId,
[FromBody, Required] ApproveStoreCommand command,
CancellationToken cancellationToken)
{
// 1. 执行审核通过
var request = command with { StoreId = storeId };
var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken));
// 2. 返回结果
return ApiResponse<StoreAuditActionResultDto>.Ok(result);
}
/// <summary>
/// 审核驳回。
/// </summary>
/// <param name="storeId">门店 ID。</param>
/// <param name="command">驳回命令。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>操作结果。</returns>
[HttpPost("{storeId:long}/reject")]
[PermissionAuthorize("store-audit:reject")]
[ProducesResponseType(typeof(ApiResponse<StoreAuditActionResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreAuditActionResultDto>> Reject(
long storeId,
[FromBody, Required] RejectStoreCommand command,
CancellationToken cancellationToken)
{
// 1. 执行审核驳回
var request = command with { StoreId = storeId };
var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken));
// 2. 返回结果
return ApiResponse<StoreAuditActionResultDto>.Ok(result);
}
/// <summary>
/// 查询审核记录。
/// </summary>
/// <param name="storeId">门店 ID。</param>
/// <param name="page">页码。</param>
/// <param name="pageSize">每页数量。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>审核记录分页列表。</returns>
[HttpGet("{storeId:long}/records")]
[PermissionAuthorize("store-audit:read")]
[ProducesResponseType(typeof(ApiResponse<PagedResult<StoreAuditRecordDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PagedResult<StoreAuditRecordDto>>> ListRecords(
long storeId,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken cancellationToken = default)
{
// 1. 执行记录查询
var query = new ListStoreAuditRecordsQuery
{
StoreId = storeId,
Page = page,
PageSize = pageSize
};
var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken));
// 2. 返回分页结果
return ApiResponse<PagedResult<StoreAuditRecordDto>>.Ok(result);
}
/// <summary>
/// 获取审核统计数据。
/// </summary>
/// <param name="query">查询参数。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>统计数据。</returns>
[HttpGet("statistics")]
[PermissionAuthorize("store-audit:read")]
[ProducesResponseType(typeof(ApiResponse<StoreAuditStatisticsDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreAuditStatisticsDto>> GetStatistics(
[FromQuery] GetStoreAuditStatisticsQuery query,
CancellationToken cancellationToken)
{
// 1. 执行统计查询
var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken));
// 2. 返回统计结果
return ApiResponse<StoreAuditStatisticsDto>.Ok(result);
}
/// <summary>
/// 强制关闭门店。
/// </summary>
/// <param name="storeId">门店 ID。</param>
/// <param name="command">关闭命令。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>操作结果。</returns>
[HttpPost("{storeId:long}/force-close")]
[PermissionAuthorize("store-audit:force-close")]
[ProducesResponseType(typeof(ApiResponse<StoreAuditActionResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreAuditActionResultDto>> ForceClose(
long storeId,
[FromBody, Required] ForceCloseStoreCommand command,
CancellationToken cancellationToken)
{
// 1. 执行强制关闭
var request = command with { StoreId = storeId };
var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken));
// 2. 返回结果
return ApiResponse<StoreAuditActionResultDto>.Ok(result);
}
/// <summary>
/// 解除强制关闭。
/// </summary>
/// <param name="storeId">门店 ID。</param>
/// <param name="command">解除命令。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>操作结果。</returns>
[HttpPost("{storeId:long}/reopen")]
[PermissionAuthorize("store-audit:force-close")]
[ProducesResponseType(typeof(ApiResponse<StoreAuditActionResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreAuditActionResultDto>> Reopen(
long storeId,
[FromBody, Required] ReopenStoreCommand command,
CancellationToken cancellationToken)
{
// 1. 执行解除强制关闭
var request = command with { StoreId = storeId };
var result = await ExecuteAsPlatformAsync(() => mediator.Send(request, cancellationToken));
// 2. 返回结果
return ApiResponse<StoreAuditActionResultDto>.Ok(result);
}
private async Task<T> ExecuteAsPlatformAsync<T>(Func<Task<T>> action)
{
var original = tenantContextAccessor.Current;
tenantContextAccessor.Current = new TenantContext(0, null, "platform");
// 1. (空行后) 切换到平台上下文执行
try
{
return await action();
}
finally
{
tenantContextAccessor.Current = original;
}
}
}

View File

@@ -0,0 +1,60 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 门店资质预警(平台)。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/platform/store-qualifications")]
[Route("api/admin/v{version:apiVersion}/platform/store-qualifications")]
public sealed class StoreQualificationsController(
IMediator mediator,
ITenantContextAccessor tenantContextAccessor)
: BaseApiController
{
/// <summary>
/// 查询资质即将过期/已过期列表。
/// </summary>
/// <param name="query">查询参数。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>资质预警分页结果。</returns>
[HttpGet("expiring")]
[PermissionAuthorize("store-qualification:read")]
[ProducesResponseType(typeof(ApiResponse<StoreQualificationAlertResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreQualificationAlertResultDto>> ListExpiring(
[FromQuery] ListExpiringStoreQualificationsQuery query,
CancellationToken cancellationToken)
{
// 1. 查询资质预警
var result = await ExecuteAsPlatformAsync(() => mediator.Send(query, cancellationToken));
// 2. (空行后) 返回结果
return ApiResponse<StoreQualificationAlertResultDto>.Ok(result);
}
private async Task<T> ExecuteAsPlatformAsync<T>(Func<Task<T>> action)
{
var original = tenantContextAccessor.Current;
tenantContextAccessor.Current = new TenantContext(0, null, "platform");
// 1. (空行后) 切换到平台上下文执行
try
{
return await action();
}
finally
{
tenantContextAccessor.Current = original;
}
}
}

View File

@@ -47,6 +47,10 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
public async Task<ApiResponse<PagedResult<StoreDto>>> List(
[FromQuery] long? merchantId,
[FromQuery] StoreStatus? status,
[FromQuery] StoreAuditStatus? auditStatus,
[FromQuery] StoreBusinessStatus? businessStatus,
[FromQuery] StoreOwnershipType? ownershipType,
[FromQuery] string? keyword,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? sortBy = null,
@@ -58,6 +62,10 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
{
MerchantId = merchantId,
Status = status,
AuditStatus = auditStatus,
BusinessStatus = businessStatus,
OwnershipType = ownershipType,
Keyword = keyword,
Page = page,
PageSize = pageSize,
SortBy = sortBy,
@@ -131,6 +139,170 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
: ApiResponse<object>.Error(ErrorCodes.NotFound, "门店不存在");
}
/// <summary>
/// 提交门店审核。
/// </summary>
[HttpPost("{storeId:long}/submit")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
public async Task<ApiResponse<bool>> SubmitAudit(long storeId, [FromBody] SubmitStoreAuditCommand command, CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (command.StoreId == 0)
{
command = command with { StoreId = storeId };
}
// 2. (空行后) 执行提交
var result = await mediator.Send(command, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<bool>.Ok(result);
}
/// <summary>
/// 切换门店经营状态。
/// </summary>
[HttpPost("{storeId:long}/business-status")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<StoreDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreDto>> ToggleBusinessStatus(long storeId, [FromBody] ToggleBusinessStatusCommand command, CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (command.StoreId == 0)
{
command = command with { StoreId = storeId };
}
// 2. (空行后) 执行切换
var result = await mediator.Send(command, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<StoreDto>.Ok(result);
}
/// <summary>
/// 查询门店资质列表。
/// </summary>
[HttpGet("{storeId:long}/qualifications")]
[PermissionAuthorize("store:read")]
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<StoreQualificationDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<IReadOnlyList<StoreQualificationDto>>> ListQualifications(long storeId, CancellationToken cancellationToken)
{
// 1. 查询资质列表
var result = await mediator.Send(new ListStoreQualificationsQuery { StoreId = storeId }, cancellationToken);
// 2. 返回结果
return ApiResponse<IReadOnlyList<StoreQualificationDto>>.Ok(result);
}
/// <summary>
/// 检查门店资质完整性。
/// </summary>
[HttpGet("{storeId:long}/qualifications/check")]
[PermissionAuthorize("store:read")]
[ProducesResponseType(typeof(ApiResponse<StoreQualificationCheckResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreQualificationCheckResultDto>> CheckQualifications(long storeId, CancellationToken cancellationToken)
{
// 1. 执行检查
var result = await mediator.Send(new CheckStoreQualificationsQuery { StoreId = storeId }, cancellationToken);
// 2. 返回检查结果
return ApiResponse<StoreQualificationCheckResultDto>.Ok(result);
}
/// <summary>
/// 新增门店资质。
/// </summary>
[HttpPost("{storeId:long}/qualifications")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<StoreQualificationDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreQualificationDto>> CreateQualification(long storeId, [FromBody] CreateStoreQualificationCommand command, CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (command.StoreId == 0)
{
command = command with { StoreId = storeId };
}
// 2. (空行后) 执行创建
var result = await mediator.Send(command, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<StoreQualificationDto>.Ok(result);
}
/// <summary>
/// 更新门店资质。
/// </summary>
[HttpPut("{storeId:long}/qualifications/{qualificationId:long}")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<StoreQualificationDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<StoreQualificationDto>> UpdateQualification(
long storeId,
long qualificationId,
[FromBody] UpdateStoreQualificationCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定资质 ID
if (command.StoreId == 0 || command.QualificationId == 0)
{
command = command with { StoreId = storeId, QualificationId = qualificationId };
}
// 2. (空行后) 执行更新
var result = await mediator.Send(command, cancellationToken);
// 3. (空行后) 返回结果或 404
return result is null
? ApiResponse<StoreQualificationDto>.Error(ErrorCodes.NotFound, "资质不存在")
: ApiResponse<StoreQualificationDto>.Ok(result);
}
/// <summary>
/// 删除门店资质。
/// </summary>
[HttpDelete("{storeId:long}/qualifications/{qualificationId:long}")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
public async Task<ApiResponse<bool>> DeleteQualification(long storeId, long qualificationId, CancellationToken cancellationToken)
{
// 1. 执行删除
var result = await mediator.Send(new DeleteStoreQualificationCommand
{
StoreId = storeId,
QualificationId = qualificationId
}, cancellationToken);
// 2. 返回结果
return ApiResponse<bool>.Ok(result);
}
/// <summary>
/// 批量更新营业时段。
/// </summary>
[HttpPut("{storeId:long}/business-hours/batch")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<StoreBusinessHourDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<IReadOnlyList<StoreBusinessHourDto>>> BatchUpdateBusinessHours(
long storeId,
[FromBody] BatchUpdateBusinessHoursCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (command.StoreId == 0)
{
command = command with { StoreId = storeId };
}
// 2. (空行后) 执行批量更新
var result = await mediator.Send(command, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<IReadOnlyList<StoreBusinessHourDto>>.Ok(result);
}
/// <summary>
/// 查询门店营业时段。
/// </summary>
@@ -259,6 +431,90 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
: ApiResponse<object>.Error(ErrorCodes.NotFound, "配送区域不存在");
}
/// <summary>
/// 配送范围检测。
/// </summary>
[HttpPost("{storeId:long}/delivery-check")]
[PermissionAuthorize("store:read")]
[ProducesResponseType(typeof(ApiResponse<StoreDeliveryCheckResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreDeliveryCheckResultDto>> CheckDeliveryZone(
long storeId,
[FromBody] CheckStoreDeliveryZoneQuery query,
CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (query.StoreId == 0)
{
query = query with { StoreId = storeId };
}
// 2. (空行后) 执行检测
var result = await mediator.Send(query, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<StoreDeliveryCheckResultDto>.Ok(result);
}
/// <summary>
/// 获取门店费用配置。
/// </summary>
[HttpGet("{storeId:long}/fee")]
[PermissionAuthorize("store:read")]
[ProducesResponseType(typeof(ApiResponse<StoreFeeDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreFeeDto>> GetFee(long storeId, CancellationToken cancellationToken)
{
// 1. 查询费用配置
var result = await mediator.Send(new GetStoreFeeQuery { StoreId = storeId }, cancellationToken);
// 2. 返回结果
return ApiResponse<StoreFeeDto>.Ok(result ?? new StoreFeeDto());
}
/// <summary>
/// 更新门店费用配置。
/// </summary>
[HttpPut("{storeId:long}/fee")]
[PermissionAuthorize("store:update")]
[ProducesResponseType(typeof(ApiResponse<StoreFeeDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreFeeDto>> UpdateFee(long storeId, [FromBody] UpdateStoreFeeCommand command, CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (command.StoreId == 0)
{
command = command with { StoreId = storeId };
}
// 2. (空行后) 执行更新
var result = await mediator.Send(command, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<StoreFeeDto>.Ok(result);
}
/// <summary>
/// 门店费用预览。
/// </summary>
[HttpPost("{storeId:long}/fee/calculate")]
[PermissionAuthorize("store:read")]
[ProducesResponseType(typeof(ApiResponse<StoreFeeCalculationResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<StoreFeeCalculationResultDto>> CalculateFee(
long storeId,
[FromBody] CalculateStoreFeeQuery query,
CancellationToken cancellationToken)
{
// 1. 绑定门店 ID
if (query.StoreId == 0)
{
query = query with { StoreId = storeId };
}
// 2. (空行后) 执行计算
var result = await mediator.Send(query, cancellationToken);
// 3. (空行后) 返回结果
return ApiResponse<StoreFeeCalculationResultDto>.Ok(result);
}
/// <summary>
/// 查询门店节假日。
/// </summary>