using Fake.Application;
using Fake.Rbac.Application.Jwt;
using Fake.Rbac.Domain.Managers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.OpenApi;
using Oms.Application.Dtos.Operations;
using Oms.Application.Rbac;
using Oms.Application.Services.Operations;
using Oms.Application.Services.Orders;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Repositories;
namespace Oms.Application.Services.OpenApi;
///
/// 仓配对外开放 API(Swagger 文档分组:warehouseOpenApi)。
/// 鉴权:Authorization: {token}(账号密码换取,有效期 48 小时;无需 Bearer 前缀)。
/// 与客户 HMAC 分组 openApi 相互独立。
///
[ApiExplorerSettings(GroupName = "warehouseOpenApi")]
public class WarehouseOpenApiService : ApplicationService
{
private AccountManager AccountManager =>
LazyServiceProvider.GetRequiredService();
private IJwtService JwtService =>
LazyServiceProvider.GetRequiredService();
private OpenApiTokenIssuer TokenIssuer =>
LazyServiceProvider.GetRequiredService();
private IOrderRepository OrderRepository =>
LazyServiceProvider.GetRequiredService();
private OrderFileManager OrderFileManager =>
LazyServiceProvider.GetRequiredService();
private InboundRecordService InboundRecordService =>
LazyServiceProvider.GetRequiredService();
private OutboundRecordService OutboundRecordService =>
LazyServiceProvider.GetRequiredService();
///
/// 获取 Token(有效期 48 小时)。后续请求 Header:Authorization: {token}(无需 Bearer)。
///
[AllowAnonymous]
[HttpPost("/openapi/warehouse/token")]
[Consumes("application/json")]
[Produces("application/json")]
public async Task GetTokenAsync(
[FromBody] OpenApiGetTokenInput input,
CancellationToken cancellationToken = default)
{
if (input == null
|| string.IsNullOrWhiteSpace(input.Username)
|| string.IsNullOrWhiteSpace(input.Password))
{
throw new BusinessException("用户名或密码不能为空");
}
var user = await AccountManager.ValidateCredentialsAsync(
input.Username.Trim(),
input.Password,
cancellationToken);
var profileRepository = LazyServiceProvider.GetRequiredService();
await profileRepository.EnsureEnabledOrThrowAsync(user.Id, cancellationToken);
var claims = await JwtService.GenerateClaimsByUserIdAsync(user.Id, cancellationToken);
return TokenIssuer.Issue(claims);
}
///
/// 入库上传件重尺。与操作台「入库扫描」页「入库」按钮同源(POST /operation/inbound/create)。
/// 写入收货件重尺、入库记录,并触发入库计费/自动触发。重量默认 kg,长度默认 cm。
/// Header:Authorization: {token}(无需 Bearer)。
///
[Authorize]
[HttpPost("/openapi/warehouse/inbound")]
[Consumes("application/json")]
[Produces("application/json")]
public Task InboundAsync([FromBody] OpenApiUploadWeightInput input) =>
UploadWeightCoreAsync(input, forceMode: 0);
///
/// 上传件重尺。mode:0=入库(同「入库」按钮),1=出库(同「出库」按钮)。
/// Header:Authorization: {token}(无需 Bearer)。
///
[Authorize]
[HttpPost("/openapi/warehouse/weight/upload")]
[Consumes("application/json")]
[Produces("application/json")]
public Task UploadWeightAsync([FromBody] OpenApiUploadWeightInput input) =>
UploadWeightCoreAsync(input, forceMode: null);
private async Task UploadWeightCoreAsync(
OpenApiUploadWeightInput? input,
int? forceMode)
{
try
{
if (input == null || string.IsNullOrWhiteSpace(input.OrderNo))
{
return OpenApiSimpleResult.Fail("订单号不能为空");
}
var mode = forceMode ?? input.Mode;
var weightKg = OpenApiMeasureUnitConverter.ToKilograms(input.Weight, input.WeightUnit);
var lengthCm = OpenApiMeasureUnitConverter.ToCentimeters(input.Length, input.LengthUnit);
var widthCm = OpenApiMeasureUnitConverter.ToCentimeters(input.Width, input.LengthUnit);
var heightCm = OpenApiMeasureUnitConverter.ToCentimeters(input.Height, input.LengthUnit);
var orderNo = input.OrderNo.Trim();
if (weightKg <= 0 || lengthCm <= 0 || widthCm <= 0 || heightCm <= 0)
{
return OpenApiSimpleResult.Fail("实重与长宽高须大于 0");
}
if (mode == 1)
{
await OutboundRecordService.CreateAsync(new CreateOutboundRecordInput
{
WaybillNo = orderNo,
Weight = weightKg,
Length = lengthCm,
Width = widthCm,
Height = heightCm
});
return OpenApiSimpleResult.Ok("出库成功");
}
if (mode == 0)
{
// 与入库扫描页「入库」按钮同一实现:InboundRecordService.CreateAsync
var result = await InboundRecordService.CreateAsync(new CreateInboundRecordInput
{
WaybillNo = orderNo,
Weight = weightKg,
Length = lengthCm,
Width = widthCm,
Height = heightCm
});
var statusName = string.IsNullOrWhiteSpace(result.OrderStatusName)
? "入库成功"
: $"入库成功({result.OrderStatusName})";
return OpenApiSimpleResult.Ok(statusName);
}
return OpenApiSimpleResult.Fail("mode 仅支持 0(入库)或 1(出库)");
}
catch (BusinessException ex)
{
return OpenApiSimpleResult.Fail(ex.Message);
}
catch (Exception ex)
{
return OpenApiSimpleResult.Fail(
string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message);
}
}
///
/// 上传红外图片。Base64 解码后转存 COS,写入订单附件 fileType=infraredImage(Source=upload),
/// 与订单详情「附件」Tab →「红外图片」→「点击上传」(POST /order/files/upload)同源展示。
/// 成功无响应体。Header:Authorization: {token}(无需 Bearer)。
///
[Authorize]
[HttpPost("/openapi/warehouse/image/upload")]
[Consumes("application/json")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task UploadImageAsync(
[FromBody] OpenApiUploadImageInput input,
CancellationToken cancellationToken = default)
{
if (input == null || string.IsNullOrWhiteSpace(input.OrderNo))
{
throw new BusinessException("订单号不能为空");
}
if (string.IsNullOrWhiteSpace(input.ImageBase64))
{
throw new BusinessException("图片内容不能为空");
}
var order = await OrderRepository.GetByWaybillNoAsync(input.OrderNo.Trim(), cancellationToken)
?? throw new BusinessException("未找到对应订单,请检查单号");
var operatorUserId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : 0;
// 固定红外图片类型,路径 uploads/order-files/{orderId}/infraredImage/...
await OrderFileManager.AddFromBase64Async(
order.Id,
OrderFileTypeCodes.InfraredImage,
input.ImageBase64,
operatorUserId,
cancellationToken);
return new NoContentResult();
}
}