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(); /// /// 查询 SKU 列表。 /// /// 入参:SKU 分页查询条件。 /// SKU 分页结果。 [HttpPost("/warehouse/sku/list")] public async Task> 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> 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 { [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 { 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 }; } /// /// 查询 SKU 详情。 /// /// SKU ID。 /// 客户 ID(可选)。 /// SKU 详情。 [HttpGet("/warehouse/sku/detail")] public async Task 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)); } /// /// 保存 SKU(新增或编辑)。 /// /// 入参:SKU 保存数据。 /// 保存后的 SKU。 [HttpPost("/warehouse/sku/save")] public async Task 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); } /// /// 删除 SKU。 /// /// 入参:SKU ID。 [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); } /// /// 上传 SKU 图片,返回可访问相对路径。 /// [HttpPost("/warehouse/sku/upload-image")] [Consumes("multipart/form-data")] public async Task 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 }; } /// /// 绑定平台 SKU 与库内 SKU 映射。 /// /// 入参:映射关系。 /// 映射结果。 [HttpPost("/warehouse/sku/bind-mapping")] public async Task 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) }; }