using System.Linq.Expressions;
using Fake.Application;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Shared;
using Oms.Application.Dtos.Warehouse;
using Oms.Application.Shared;
using Oms.Application.Storage;
using Oms.Domain.WarehouseAggregate.Consts;
using Oms.Domain.WarehouseAggregate.Entities;
namespace Oms.Application.Services.Warehouse;
public partial class WarehouseService
{
///
/// 查询包裹列表。
///
/// 入参:包裹分页查询条件。
/// 包裹分页结果。
[HttpPost("/warehouse/parcel/list")]
public async Task> GetParcelListAsync(WarehouseParcelListInput input)
{
var customerId = await ResolveListCustomerIdAsync(input.CustomerId);
var (hasCustomerFilter, customerIdValue) = CustomerFilter(customerId);
var keyword = input.Keyword?.Trim();
var hasKeyword = !string.IsNullOrWhiteSpace(keyword);
// 前端 Tab「全部」会传 all / 空;需忽略,不能按字面 LifecycleStatus == "all" 过滤
var lifecycleStatus = NormalizeParcelLifecycleStatusFilter(input.LifecycleStatus);
var hasLifecycleStatusFilter = !string.IsNullOrWhiteSpace(lifecycleStatus);
var hasWarehouseFilter = input.WarehouseId.HasValue;
var warehouseIdValue = input.WarehouseId.GetValueOrDefault();
Expression> predicate = hasLifecycleStatusFilter
? x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue)
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
&& x.LifecycleStatus == lifecycleStatus
&& (!hasKeyword || x.TrackingNo.Contains(keyword!) || (x.CarrierName != null && x.CarrierName.Contains(keyword!)))
: x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue)
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
&& (!hasKeyword || x.TrackingNo.Contains(keyword!) || (x.CarrierName != null && x.CarrierName.Contains(keyword!)));
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
// 避免 Fake GetPagedListAsync(sorting) 丢失 Where:total 正确但 items 返回全量
var (entities, total) = await RepositoryPageHelper.GetPageByUpdateTimeDescAsync(
ParcelRepository,
predicate,
pageIndex,
pageSize);
var parcelIds = entities.Select(x => x.Id).ToArray();
var items = parcelIds.Length == 0
? []
: await ParcelItemRepository.GetListAsync(x => parcelIds.Contains(x.ParcelId));
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToArray();
var customers = customerIds.Length == 0
? []
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
var customerMap = customers.ToDictionary(x => x.Id);
return new OmsPageResult
{
Items = entities.Select(x =>
{
var output = MapParcelOutput(x, items.Where(i => i.ParcelId == x.Id));
if (customerMap.TryGetValue(x.CustomerId, out var customer))
{
output.CustomerName = customer.CustomerName;
output.CustomerCode = customer.CustomerCode;
}
return output;
}).ToList(),
Total = total
};
}
///
/// 规范化列表状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。
///
private static string? NormalizeParcelLifecycleStatusFilter(string? status)
{
var value = status?.Trim();
if (string.IsNullOrEmpty(value)
|| value.Equals("all", StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (value.Equals(WarehouseParcelLifecycleCodes.InTransit, StringComparison.OrdinalIgnoreCase))
{
return WarehouseParcelLifecycleCodes.InTransit;
}
if (value.Equals(WarehouseParcelLifecycleCodes.Arrived, StringComparison.OrdinalIgnoreCase))
{
return WarehouseParcelLifecycleCodes.Arrived;
}
if (value.Equals(WarehouseParcelLifecycleCodes.Stored, StringComparison.OrdinalIgnoreCase))
{
return WarehouseParcelLifecycleCodes.Stored;
}
return value;
}
///
/// 保存包裹(新增或编辑)。
///
/// 入参:包裹保存数据。
/// 保存后的包裹。
[HttpPost("/warehouse/parcel/save")]
public async Task SaveParcelAsync(WarehouseParcelSaveInput input)
{
var customerId = await ResolveMutationCustomerIdAsync(input.CustomerId);
if (input.WarehouseId <= 0)
{
throw new BusinessException("仓库ID不能为空");
}
var trackingNo = input.TrackingNo.Trim();
if (string.IsNullOrWhiteSpace(trackingNo))
{
throw new BusinessException("快递单号不能为空");
}
CParcel parcel;
if (input.Id > 0)
{
parcel = await GetParcelOrThrowAsync(customerId, input.Id);
if (parcel.LifecycleStatus == WarehouseParcelLifecycleCodes.Stored)
{
throw new BusinessException("已上架包裹不可编辑");
}
}
else
{
parcel = new CParcel { CustomerId = customerId };
}
var duplicate = await ParcelRepository.FirstOrDefaultAsync(x =>
x.CustomerId == customerId
&& x.WarehouseId == input.WarehouseId
&& x.TrackingNo == trackingNo
&& x.Id != parcel.Id);
if (duplicate != null)
{
throw new BusinessException("快递单号已存在");
}
parcel.WarehouseId = input.WarehouseId;
parcel.TrackingNo = trackingNo;
parcel.CarrierName = string.IsNullOrWhiteSpace(input.CarrierName) ? null : input.CarrierName.Trim();
parcel.LifecycleStatus = string.IsNullOrWhiteSpace(input.LifecycleStatus)
? WarehouseParcelLifecycleCodes.InTransit
: input.LifecycleStatus.Trim();
parcel.Remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
if (input.Id > 0)
{
await ParcelRepository.UpdateAsync(parcel);
await ClearParcelItemsAsync(parcel.Id);
}
else
{
await ParcelRepository.InsertAsync(parcel);
}
foreach (var itemInput in input.Items)
{
if (itemInput.Quantity <= 0)
{
throw new BusinessException("商品数量必须大于 0");
}
var imageUrl = FileStorage.ToStorageKey(itemInput.ImageUrl);
if (imageUrl != null && imageUrl.Length > 1024)
{
imageUrl = imageUrl[..1024];
}
var item = new CParcelItem
{
ParcelId = parcel.Id,
ProductName = string.IsNullOrWhiteSpace(itemInput.ProductName) ? null : itemInput.ProductName.Trim(),
AttributeText = string.IsNullOrWhiteSpace(itemInput.AttributeText)
? null
: itemInput.AttributeText.Trim(),
ImageUrl = imageUrl,
Quantity = itemInput.Quantity,
FrozenQty = 0,
AvailableQty = itemInput.Quantity,
Remark = string.IsNullOrWhiteSpace(itemInput.Remark) ? null : itemInput.Remark.Trim()
};
await ParcelItemRepository.InsertAsync(item);
}
var savedItems = await ParcelItemRepository.GetListAsync(x => x.ParcelId == parcel.Id);
return MapParcelOutput(parcel, savedItems);
}
///
/// 上传包裹商品图片,返回可访问相对路径。
///
[HttpPost("/warehouse/parcel/upload-image")]
[Consumes("multipart/form-data")]
public async Task