All checks were successful
Build and Deploy / deploy (push) Successful in 1m43s
上传接口接受 query customerId;ConcurrencyStamp 默认改为空串并用主键确定性回填,避免 Galera UUID unsafe。 Co-authored-by: Cursor <cursoragent@cursor.com>
264 lines
10 KiB
C#
264 lines
10 KiB
C#
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;
|
||
|
||
namespace Oms.Application.Services.Warehouse;
|
||
|
||
public partial class WarehouseService
|
||
{
|
||
private IFileStorage FileStorage =>
|
||
LazyServiceProvider.GetRequiredService<IFileStorage>();
|
||
|
||
/// <summary>
|
||
/// 查询 SKU 列表。
|
||
/// </summary>
|
||
/// <param name="input">入参:SKU 分页查询条件。</param>
|
||
/// <returns>SKU 分页结果。</returns>
|
||
[HttpPost("/warehouse/sku/list")]
|
||
public async Task<OmsPageResult<WarehouseSkuOutput>> GetSkuListAsync(WarehouseSkuListInput input)
|
||
{
|
||
var customerId = await ResolveListCustomerIdAsync(input.CustomerId);
|
||
var (hasCustomerFilter, customerIdValue) = CustomerFilter(customerId);
|
||
var keyword = input.Keyword?.Trim();
|
||
var hasKeyword = !string.IsNullOrWhiteSpace(keyword);
|
||
|
||
Expression<Func<Domain.WarehouseAggregate.Entities.CSku, bool>> predicate = x =>
|
||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||
&& (!hasKeyword || x.SkuCode.Contains(keyword!) || x.SkuName.Contains(keyword!));
|
||
|
||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||
var entities = await SkuRepository.GetPagedListAsync(
|
||
predicate,
|
||
pageIndex,
|
||
pageSize,
|
||
new Dictionary<string, bool> { [nameof(Domain.WarehouseAggregate.Entities.CSku.UpdateTime)] = false, [nameof(Domain.WarehouseAggregate.Entities.CSku.Id)] = false });
|
||
var total = await SkuRepository.CountAsync(predicate);
|
||
|
||
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<WarehouseSkuOutput>
|
||
{
|
||
Items = entities.Select(x =>
|
||
{
|
||
var output = MapSkuListOutput(x);
|
||
if (customerMap.TryGetValue(x.CustomerId, out var customer))
|
||
{
|
||
output.CustomerName = customer.CustomerName;
|
||
output.CustomerCode = customer.CustomerCode;
|
||
}
|
||
|
||
return output;
|
||
}).ToList(),
|
||
Total = total
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询 SKU 详情。
|
||
/// </summary>
|
||
/// <param name="id">SKU ID。</param>
|
||
/// <param name="customerId">客户 ID(可选)。</param>
|
||
/// <returns>SKU 详情。</returns>
|
||
[HttpGet("/warehouse/sku/detail")]
|
||
public async Task<WarehouseSkuOutput> GetSkuDetailAsync([FromQuery] int id, [FromQuery] int? customerId = null)
|
||
{
|
||
var resolvedCustomerId = await ResolveListCustomerIdAsync(customerId);
|
||
var (hasCustomerFilter, customerIdValue) = CustomerFilter(resolvedCustomerId);
|
||
var entity = await GetSkuOrThrowAsync(resolvedCustomerId, id);
|
||
var inventories = await InventoryRepository.GetListAsync(x =>
|
||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||
&& x.SkuId == id);
|
||
return MapSkuOutput(entity, inventories.Sum(x => x.Qty));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存 SKU(新增或编辑)。
|
||
/// </summary>
|
||
/// <param name="input">入参:SKU 保存数据。</param>
|
||
/// <returns>保存后的 SKU。</returns>
|
||
[HttpPost("/warehouse/sku/save")]
|
||
public async Task<WarehouseSkuOutput> SaveSkuAsync(WarehouseSkuSaveInput input)
|
||
{
|
||
var customerId = await ResolveMutationCustomerIdAsync(input.CustomerId);
|
||
var skuCode = input.SkuCode.Trim();
|
||
var skuName = input.SkuName.Trim();
|
||
if (string.IsNullOrWhiteSpace(skuCode) || string.IsNullOrWhiteSpace(skuName))
|
||
{
|
||
throw new BusinessException("SKU 编码和名称不能为空");
|
||
}
|
||
|
||
Domain.WarehouseAggregate.Entities.CSku entity;
|
||
if (input.Id > 0)
|
||
{
|
||
entity = await GetSkuOrThrowAsync(customerId, input.Id);
|
||
}
|
||
else
|
||
{
|
||
entity = new Domain.WarehouseAggregate.Entities.CSku { CustomerId = customerId };
|
||
}
|
||
|
||
var duplicate = await SkuRepository.FirstOrDefaultAsync(x =>
|
||
x.CustomerId == customerId && x.SkuCode == skuCode && x.Id != entity.Id);
|
||
if (duplicate != null)
|
||
{
|
||
throw new BusinessException("SKU 编码已存在");
|
||
}
|
||
|
||
entity.SkuCode = skuCode;
|
||
entity.SkuName = skuName;
|
||
entity.SkuNameEn = string.IsNullOrWhiteSpace(input.SkuNameEn) ? null : input.SkuNameEn.Trim();
|
||
entity.SkuImageUrl = FileStorage.ToStorageKey(input.SkuImageUrl);
|
||
entity.UnitPrice = input.UnitPrice;
|
||
entity.ExHsCode = string.IsNullOrWhiteSpace(input.ExHsCode) ? null : input.ExHsCode.Trim();
|
||
entity.ImHsCode = string.IsNullOrWhiteSpace(input.ImHsCode) ? null : input.ImHsCode.Trim();
|
||
entity.WeightG = input.WeightG;
|
||
entity.LengthCm = input.LengthCm;
|
||
entity.WidthCm = input.WidthCm;
|
||
entity.HeightCm = input.HeightCm;
|
||
entity.Remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
|
||
|
||
if (input.Id > 0)
|
||
{
|
||
await SkuRepository.UpdateAsync(entity);
|
||
}
|
||
else
|
||
{
|
||
await SkuRepository.InsertAsync(entity);
|
||
}
|
||
|
||
return MapSkuOutput(entity, null);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除 SKU。
|
||
/// </summary>
|
||
/// <param name="input">入参:SKU ID。</param>
|
||
[HttpPost("/warehouse/sku/delete")]
|
||
public async Task DeleteSkuAsync(IdInput input)
|
||
{
|
||
var customerId = await ResolveListCustomerIdAsync(null);
|
||
var entity = await GetSkuOrThrowAsync(customerId, input.Id);
|
||
var hasInventory = await InventoryRepository.CountAsync(x =>
|
||
x.SkuId == entity.Id && x.Qty > 0);
|
||
if (hasInventory > 0)
|
||
{
|
||
throw new BusinessException("SKU 仍有库存,不能删除");
|
||
}
|
||
|
||
await SkuRepository.DeleteAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传 SKU 图片,返回可访问相对路径。
|
||
/// </summary>
|
||
[HttpPost("/warehouse/sku/upload-image")]
|
||
[Consumes("multipart/form-data")]
|
||
public async Task<object> UploadSkuImageAsync(IFormFile file, [FromQuery] int? customerId = null)
|
||
{
|
||
await ResolveMutationCustomerIdAsync(customerId);
|
||
var key = await OmsFileUploadHelper.SaveWarehouseSkuImageAsync(file, FileStorage);
|
||
var url = FileStorage.GetPublicUrl(key);
|
||
return new { key, url, fileUrl = key };
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绑定平台 SKU 与库内 SKU 映射。
|
||
/// </summary>
|
||
/// <param name="input">入参:映射关系。</param>
|
||
/// <returns>映射结果。</returns>
|
||
[HttpPost("/warehouse/sku/bind-mapping")]
|
||
public async Task<WarehouseSkuMappingOutput> BindSkuMappingAsync(WarehouseSkuBindMappingInput input)
|
||
{
|
||
var customerId = await ResolveMutationCustomerIdAsync(input.CustomerId);
|
||
var platformSku = input.PlatformSku.Trim();
|
||
if (string.IsNullOrWhiteSpace(platformSku))
|
||
{
|
||
throw new BusinessException("平台 SKU 不能为空");
|
||
}
|
||
|
||
var sku = await GetSkuOrThrowAsync(customerId, input.SkuId);
|
||
var existing = await SkuPlatformMapRepository.FirstOrDefaultAsync(x =>
|
||
x.CustomerId == customerId && x.StoreId == input.StoreId && x.PlatformSku == platformSku);
|
||
if (existing == null)
|
||
{
|
||
existing = new Domain.WarehouseAggregate.Entities.CSkuPlatformMap
|
||
{
|
||
CustomerId = customerId,
|
||
StoreId = input.StoreId,
|
||
PlatformSku = platformSku,
|
||
SkuId = sku.Id,
|
||
SkuCode = sku.SkuCode
|
||
};
|
||
await SkuPlatformMapRepository.InsertAsync(existing);
|
||
}
|
||
else
|
||
{
|
||
existing.SkuId = sku.Id;
|
||
existing.SkuCode = sku.SkuCode;
|
||
await SkuPlatformMapRepository.UpdateAsync(existing);
|
||
}
|
||
|
||
return new WarehouseSkuMappingOutput
|
||
{
|
||
Id = existing.Id,
|
||
StoreId = existing.StoreId,
|
||
PlatformSku = existing.PlatformSku,
|
||
SkuId = existing.SkuId,
|
||
SkuCode = existing.SkuCode
|
||
};
|
||
}
|
||
|
||
private WarehouseSkuOutput MapSkuListOutput(Domain.WarehouseAggregate.Entities.CSku entity) =>
|
||
new()
|
||
{
|
||
Id = entity.Id,
|
||
CustomerId = entity.CustomerId,
|
||
SkuCode = entity.SkuCode,
|
||
SkuName = entity.SkuName,
|
||
SkuNameEn = entity.SkuNameEn,
|
||
SkuImageUrl = FileStorage.GetPublicUrl(entity.SkuImageUrl),
|
||
UnitPrice = entity.UnitPrice,
|
||
ExHsCode = entity.ExHsCode,
|
||
ImHsCode = entity.ImHsCode,
|
||
WeightG = entity.WeightG,
|
||
LengthCm = entity.LengthCm,
|
||
WidthCm = entity.WidthCm,
|
||
HeightCm = entity.HeightCm,
|
||
Remark = entity.Remark,
|
||
CreateTime = entity.CreateTime,
|
||
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
|
||
};
|
||
|
||
private WarehouseSkuOutput MapSkuOutput(Domain.WarehouseAggregate.Entities.CSku entity, int? totalQty) =>
|
||
new()
|
||
{
|
||
Id = entity.Id,
|
||
CustomerId = entity.CustomerId,
|
||
SkuCode = entity.SkuCode,
|
||
SkuName = entity.SkuName,
|
||
SkuNameEn = entity.SkuNameEn,
|
||
SkuImageUrl = FileStorage.GetPublicUrl(entity.SkuImageUrl),
|
||
UnitPrice = entity.UnitPrice,
|
||
ExHsCode = entity.ExHsCode,
|
||
ImHsCode = entity.ImHsCode,
|
||
WeightG = entity.WeightG,
|
||
LengthCm = entity.LengthCm,
|
||
WidthCm = entity.WidthCm,
|
||
HeightCm = entity.HeightCm,
|
||
Remark = entity.Remark,
|
||
TotalQty = totalQty,
|
||
CreateTime = entity.CreateTime,
|
||
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
|
||
};
|
||
}
|