using System.Linq.Expressions; using Fake.Application; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.Shared; using Oms.Application.Dtos.Warehouse; using Oms.Application.Shared; using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.OrdersAggregate.Repositories; using Oms.Domain.WarehouseAggregate.Consts; using Oms.Domain.WarehouseAggregate.Entities; using Oms.Domain.WarehouseAggregate.Repositories; namespace Oms.Application.Services.Warehouse; public partial class WarehouseService { private ICParcelArrivalRepository ParcelArrivalRepository => LazyServiceProvider.GetRequiredService(); private OmsRbacUserDisplayNameResolver UserDisplayNameResolver => LazyServiceProvider.GetRequiredService(); private IOrderStockBindRepository ParcelArrivalStockBindRepository => LazyServiceProvider.GetRequiredService(); private IOrderRepository ParcelArrivalOrderRepository => LazyServiceProvider.GetRequiredService(); /// /// 快递到仓记录列表。 /// [HttpPost("/warehouse/parcel-arrival/list")] public async Task> GetParcelArrivalListAsync( WarehouseParcelArrivalListInput input) { var customerId = await ResolveListCustomerIdAsync(input.CustomerId); var (hasCustomerFilter, customerIdValue) = CustomerFilter(customerId); var keyword = input.Keyword?.Trim(); var hasKeyword = !string.IsNullOrWhiteSpace(keyword); var hasWarehouseFilter = input.WarehouseId is > 0; var warehouseIdValue = input.WarehouseId.GetValueOrDefault(); Expression> predicate = x => (!hasCustomerFilter || x.CustomerId == customerIdValue) && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) && (!hasKeyword || x.TrackingNo.Contains(keyword!)); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var entities = await ParcelArrivalRepository.GetPagedListAsync( predicate, pageIndex, pageSize, new Dictionary { [nameof(CParcelArrival.CreateTime)] = false, [nameof(CParcelArrival.Id)] = false }); var total = await ParcelArrivalRepository.CountAsync(predicate); var warehouseIds = entities.Select(x => x.WarehouseId).Distinct().ToArray(); var warehouses = warehouseIds.Length == 0 ? [] : await WarehouseAddressRepository.GetListAsync(x => warehouseIds.Contains(x.Id)); var warehouseNameMap = warehouses.ToDictionary(x => x.Id, x => x.WarehouseName); var userIds = entities.Select(x => x.CreateUserId).Distinct().ToArray(); var userNameMap = await UserDisplayNameResolver.ResolveManyAsync(userIds); return new OmsPageResult { Items = entities.Select(x => new WarehouseParcelArrivalOutput { Id = x.Id, CustomerId = x.CustomerId, WarehouseId = x.WarehouseId, WarehouseName = warehouseNameMap.GetValueOrDefault(x.WarehouseId), ParcelId = x.ParcelId, TrackingNo = x.TrackingNo, CreateTime = x.CreateTime, CreateUserId = x.CreateUserId, CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(x.CreateUserId, userNameMap) }).ToList(), Total = total }; } /// /// 快递到仓:写入到仓记录,并将对应「我的包裹」状态改为 arrived。 /// [HttpPost("/warehouse/parcel-arrival/create")] public async Task CreateParcelArrivalAsync(WarehouseParcelArrivalCreateInput input) { // 到仓按仓库 + 快递单号定位包裹;客户归属取自包裹本身,管理端无需先选客户。 // 客户门户会话仍会限制只能到自己的包裹。 var sessionCustomerId = await ResolveListCustomerIdAsync(null); var (hasCustomerFilter, customerIdValue) = CustomerFilter(sessionCustomerId); if (input.WarehouseId <= 0) { throw new BusinessException("仓库ID不能为空"); } var trackingNo = input.TrackingNo.Trim(); if (string.IsNullOrWhiteSpace(trackingNo)) { throw new BusinessException("国内快递单号不能为空"); } var warehouse = await WarehouseAddressRepository.FirstOrDefaultAsync(x => x.Id == input.WarehouseId) ?? throw new BusinessException("仓库不存在"); var parcels = await ParcelRepository.GetListAsync(x => x.WarehouseId == input.WarehouseId && x.TrackingNo == trackingNo && (!hasCustomerFilter || x.CustomerId == customerIdValue)); if (parcels.Count == 0) { throw new BusinessException("未找到对应包裹,请确认快递单号与仓库"); } if (parcels.Count > 1) { throw new BusinessException("同一仓库下存在多条相同快递单号的包裹,请先在「我的包裹」核对后再到仓"); } var parcel = parcels[0]; if (parcel.LifecycleStatus == WarehouseParcelLifecycleCodes.Stored) { throw new BusinessException("包裹已上架,无法再到仓"); } parcel.LifecycleStatus = WarehouseParcelLifecycleCodes.Arrived; parcel.ArrivalTime ??= DateTime.Now; await ParcelRepository.UpdateAsync(parcel); var entity = new CParcelArrival { CustomerId = parcel.CustomerId, WarehouseId = input.WarehouseId, ParcelId = parcel.Id, TrackingNo = trackingNo }; await ParcelArrivalRepository.InsertAsync(entity); var linkedOrders = await LoadParcelArrivalLinkedOrdersAsync(parcel.Id); var userNameMap = await UserDisplayNameResolver.ResolveManyAsync([entity.CreateUserId]); return new WarehouseParcelArrivalOutput { Id = entity.Id, CustomerId = entity.CustomerId, WarehouseId = entity.WarehouseId, WarehouseName = warehouse.WarehouseName, ParcelId = entity.ParcelId, TrackingNo = entity.TrackingNo, CreateTime = entity.CreateTime, CreateUserId = entity.CreateUserId, CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, userNameMap), LinkedOrders = linkedOrders }; } private async Task> LoadParcelArrivalLinkedOrdersAsync(int parcelId) { var binds = await ParcelArrivalStockBindRepository.GetListAsync(x => x.ParcelId == parcelId && !x.IsDeleted && x.Status == OrderStockBindStatusCodes.Bound); if (binds.Count == 0) { return []; } var orderIds = binds.Select(x => x.OrderId).Distinct().ToArray(); var orders = await ParcelArrivalOrderRepository.GetListAsync(x => orderIds.Contains(x.Id)); var orderMap = orders.ToDictionary(x => x.Id); return orderIds .Where(orderMap.ContainsKey) .Select(orderId => { var order = orderMap[orderId]; var isWarehouse = FulfillmentTypeCodes.IsWarehouse(order.FulfillmentType); return new WarehouseParcelArrivalLinkedOrderOutput { OrderId = orderId, ServiceNo = order.ServiceNo, ClientNo = order.ClientNo, FulfillProgress = isWarehouse ? FulfillmentProgressCodes.ArrivedPendingPick : null }; }) .OrderBy(x => x.ServiceNo, StringComparer.OrdinalIgnoreCase) .ToList(); } /// /// 删除快递到仓记录(不回退包裹状态)。 /// [HttpPost("/warehouse/parcel-arrival/delete")] public async Task DeleteParcelArrivalAsync(IdInput input) { var customerId = await ResolveListCustomerIdAsync(null); var entity = customerId.HasValue ? await ParcelArrivalRepository.FirstOrDefaultAsync(x => x.Id == input.Id && x.CustomerId == customerId.Value) : await ParcelArrivalRepository.FirstOrDefaultAsync(x => x.Id == input.Id); if (entity == null) { throw new BusinessException("到仓记录不存在"); } await ParcelArrivalRepository.DeleteAsync(entity); } }