feat: extend receivable-payable APIs with summary, export, and filters
Add summary/export endpoints plus customer code, effective stage, time field, and payment status filtering for finance reconciliation.
This commit is contained in:
parent
2018e45a7d
commit
ce1349572d
@ -32,12 +32,32 @@ public class ReceivablePayablePaginatedInput
|
|||||||
public int? CustomerId { get; set; }
|
public int? CustomerId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订单创建时间起。
|
/// 客户编码关键词(模糊匹配)。
|
||||||
|
/// </summary>
|
||||||
|
public string? CustomerCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅保留各运单费用项的当前生效阶段。
|
||||||
|
/// </summary>
|
||||||
|
public bool EffectiveStageOnly { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订单创建时间起(timeField=createTime 时生效,兼容旧参数)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? StartTime { get; set; }
|
public DateTime? StartTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订单创建时间止。
|
/// 订单创建时间止(timeField=createTime 时生效,兼容旧参数)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? EndTime { get; set; }
|
public DateTime? EndTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时间筛选字段:createTime / receiveTime / shipTime / payTime。
|
||||||
|
/// </summary>
|
||||||
|
public string? TimeField { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付状态:0 未支付,1 已支付。
|
||||||
|
/// </summary>
|
||||||
|
public int? IsPaid { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ namespace Oms.Application.Dtos.Finance;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ReceivablePayablePaginatedOutput
|
public class ReceivablePayablePaginatedOutput
|
||||||
{
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
public string ServiceNo { get; set; } = string.Empty;
|
public string ServiceNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string? ClientNo { get; set; }
|
public string? ClientNo { get; set; }
|
||||||
@ -37,6 +39,8 @@ public class ReceivablePayablePaginatedOutput
|
|||||||
|
|
||||||
public string CostItem { get; set; } = string.Empty;
|
public string CostItem { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? CostItemName { get; set; }
|
||||||
|
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
public string Currency { get; set; } = string.Empty;
|
public string Currency { get; set; } = string.Empty;
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
namespace Oms.Application.Dtos.Finance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应收应付汇总出参。
|
||||||
|
/// </summary>
|
||||||
|
public class ReceivablePayableSummaryOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 匹配筛选条件的费用明细总行数。
|
||||||
|
/// </summary>
|
||||||
|
public int TotalLines { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按费用类型 + 币种汇总。
|
||||||
|
/// </summary>
|
||||||
|
public List<ReceivablePayableSummaryItemOutput> Items { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReceivablePayableSummaryItemOutput
|
||||||
|
{
|
||||||
|
public string FeeType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? FeeTypeName { get; set; }
|
||||||
|
|
||||||
|
public string Currency { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public decimal TotalAmount { get; set; }
|
||||||
|
|
||||||
|
public int LineCount { get; set; }
|
||||||
|
}
|
||||||
@ -1,46 +1,49 @@
|
|||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Users;
|
using Fake.Users;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MiniExcelLibs;
|
||||||
using Oms.Application.DataPerm;
|
using Oms.Application.DataPerm;
|
||||||
using Oms.Application.Dtos.Finance;
|
using Oms.Application.Dtos.Finance;
|
||||||
using Oms.Application.Dtos.Shared;
|
using Oms.Application.Dtos.Shared;
|
||||||
|
using Oms.Application.Services.Orders;
|
||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
using Oms.Domain.CustomersAggregate.Repositories;
|
using Oms.Domain.CustomersAggregate.Repositories;
|
||||||
using Oms.Domain.OrdersAggregate;
|
using Oms.Domain.OrdersAggregate;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Repositories;
|
using Oms.Domain.OrdersAggregate.Repositories;
|
||||||
|
using Oms.Domain.SystemAggregate.Repositories;
|
||||||
|
|
||||||
namespace Oms.Application.Services.Finance;
|
namespace Oms.Application.Services.Finance;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 财务相关 API<EFBFBD>。
|
/// 财务相关 API。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiExplorerSettings(GroupName = "OMS-Finance")]
|
[ApiExplorerSettings(GroupName = "OMS-Finance")]
|
||||||
public class FinanceService : ApplicationService
|
public class FinanceService : ApplicationService
|
||||||
{
|
{
|
||||||
|
private const string FeeItemDictType = "fee_item";
|
||||||
|
private const int ExportMaxRows = 50000;
|
||||||
|
|
||||||
private IOrderRepository OrderRepository =>
|
private IOrderRepository OrderRepository =>
|
||||||
LazyServiceProvider.GetRequiredService<IOrderRepository>();
|
LazyServiceProvider.GetRequiredService<IOrderRepository>();
|
||||||
|
|
||||||
private ICustomerRepository CustomerRepository =>
|
private ICustomerRepository CustomerRepository =>
|
||||||
LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
||||||
|
|
||||||
|
private IDictRepository DictRepository =>
|
||||||
|
LazyServiceProvider.GetRequiredService<IDictRepository>();
|
||||||
|
|
||||||
private EffectiveScopeBuilder DataScopeBuilder =>
|
private EffectiveScopeBuilder DataScopeBuilder =>
|
||||||
LazyServiceProvider.GetRequiredService<EffectiveScopeBuilder>();
|
LazyServiceProvider.GetRequiredService<EffectiveScopeBuilder>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询应收应付(订单费用联查订单)<EFBFBD>。
|
/// 分页查询应收应付(订单费用联查订单)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpGet("/finance/receivable-payable/list")]
|
[HttpGet("/finance/receivable-payable/list")]
|
||||||
public async Task<OmsPageResult<ReceivablePayablePaginatedOutput>> GetReceivablePayableListAsync(
|
public async Task<OmsPageResult<ReceivablePayablePaginatedOutput>> GetReceivablePayableListAsync(
|
||||||
[FromQuery] ReceivablePayablePaginatedInput input)
|
[FromQuery] ReceivablePayablePaginatedInput input)
|
||||||
{
|
{
|
||||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
var filter = await BuildScopedFilterAsync(input);
|
||||||
CustomerRepository,
|
|
||||||
CurrentUser,
|
|
||||||
input.CustomerId);
|
|
||||||
var filter = BuildFilter(input, customerId);
|
|
||||||
await ApplyInternalOrderDataScopeAsync(filter);
|
|
||||||
|
|
||||||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||||
var (rows, total) = await OrderRepository.GetReceivablePayablePagedListAsync(
|
var (rows, total) = await OrderRepository.GetReceivablePayablePagedListAsync(
|
||||||
@ -48,6 +51,102 @@ public class FinanceService : ApplicationService
|
|||||||
pageSize,
|
pageSize,
|
||||||
filter);
|
filter);
|
||||||
|
|
||||||
|
var items = await MapReceivablePayableRowsAsync(rows);
|
||||||
|
return new OmsPageResult<ReceivablePayablePaginatedOutput>
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
Total = total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应收应付汇总(当前筛选条件下全量费用明细)。
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("/finance/receivable-payable/summary")]
|
||||||
|
public async Task<ReceivablePayableSummaryOutput> GetReceivablePayableSummaryAsync(
|
||||||
|
[FromQuery] ReceivablePayablePaginatedInput input)
|
||||||
|
{
|
||||||
|
var filter = await BuildScopedFilterAsync(input);
|
||||||
|
var rows = await OrderRepository.GetReceivablePayableSummaryAsync(filter);
|
||||||
|
var items = rows
|
||||||
|
.Select(row => new ReceivablePayableSummaryItemOutput
|
||||||
|
{
|
||||||
|
FeeType = row.FeeType,
|
||||||
|
FeeTypeName = GetFeeTypeName(row.FeeType),
|
||||||
|
Currency = row.Currency,
|
||||||
|
TotalAmount = row.TotalAmount,
|
||||||
|
LineCount = row.LineCount
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.FeeType)
|
||||||
|
.ThenBy(x => x.Currency)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new ReceivablePayableSummaryOutput
|
||||||
|
{
|
||||||
|
TotalLines = items.Sum(x => x.LineCount),
|
||||||
|
Items = items
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出应收应付 Excel。
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("/finance/receivable-payable/export")]
|
||||||
|
public async Task<FileContentResult> ExportReceivablePayableAsync(
|
||||||
|
[FromQuery] ReceivablePayablePaginatedInput input)
|
||||||
|
{
|
||||||
|
var filter = await BuildScopedFilterAsync(input);
|
||||||
|
var rows = await OrderRepository.GetReceivablePayableExportListAsync(filter, ExportMaxRows);
|
||||||
|
var items = await MapReceivablePayableRowsAsync(rows);
|
||||||
|
var exportRows = items.Select(item => new
|
||||||
|
{
|
||||||
|
运单号 = item.ServiceNo,
|
||||||
|
客户单号 = item.ClientNo,
|
||||||
|
跟踪号 = item.TrackNo,
|
||||||
|
客户编码 = item.CustomerCode,
|
||||||
|
费用类型 = item.FeeTypeName,
|
||||||
|
费用项 = item.CostItemName ?? item.CostItem,
|
||||||
|
金额 = item.Amount,
|
||||||
|
币种 = item.Currency,
|
||||||
|
计费阶段 = item.ChargingStageName,
|
||||||
|
订单状态 = item.OrderStatusName,
|
||||||
|
支付状态 = item.IsPaid == 1 ? "已支付" : "未支付",
|
||||||
|
产品编码 = item.ProductCode,
|
||||||
|
渠道编码 = item.ChannelCode,
|
||||||
|
目的国 = item.ConsigneeCountryName,
|
||||||
|
下游服务商 = item.DownstreamProvider,
|
||||||
|
价格编号 = item.PriceNo,
|
||||||
|
价格生效时间 = item.PriceEffectiveTime,
|
||||||
|
支付时间 = item.PayTime,
|
||||||
|
收货时间 = item.ReceiveTime,
|
||||||
|
发货时间 = item.ShipTime
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
await using var stream = new MemoryStream();
|
||||||
|
await MiniExcel.SaveAsAsync(stream, exportRows);
|
||||||
|
stream.Position = 0;
|
||||||
|
return new FileContentResult(stream.ToArray(),
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
{
|
||||||
|
FileDownloadName = $"receivable-payable-{DateTime.Now:yyyyMMddHHmmss}.xlsx"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OrderReceivablePayableFilter> BuildScopedFilterAsync(
|
||||||
|
ReceivablePayablePaginatedInput input)
|
||||||
|
{
|
||||||
|
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||||
|
CustomerRepository,
|
||||||
|
CurrentUser,
|
||||||
|
input.CustomerId);
|
||||||
|
var filter = BuildFilter(input, customerId);
|
||||||
|
await ApplyInternalOrderDataScopeAsync(filter);
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<ReceivablePayablePaginatedOutput>> MapReceivablePayableRowsAsync(
|
||||||
|
List<OrderReceivablePayableRow> rows)
|
||||||
|
{
|
||||||
var customerIds = rows
|
var customerIds = rows
|
||||||
.Select(x => x.Order.CustomerId)
|
.Select(x => x.Order.CustomerId)
|
||||||
.Where(x => x.HasValue)
|
.Where(x => x.HasValue)
|
||||||
@ -58,8 +157,9 @@ public class FinanceService : ApplicationService
|
|||||||
? []
|
? []
|
||||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||||
var customerMap = customers.ToDictionary(x => x.Id);
|
var customerMap = customers.ToDictionary(x => x.Id);
|
||||||
|
var feeItemNameMap = await BuildFeeItemNameMapAsync();
|
||||||
|
|
||||||
var items = rows.Select(row =>
|
return rows.Select(row =>
|
||||||
{
|
{
|
||||||
string? customerCode = null;
|
string? customerCode = null;
|
||||||
if (row.Order.CustomerId.HasValue
|
if (row.Order.CustomerId.HasValue
|
||||||
@ -68,8 +168,12 @@ public class FinanceService : ApplicationService
|
|||||||
customerCode = customer.CustomerCode;
|
customerCode = customer.CustomerCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var costItem = row.Fee.CostItem;
|
||||||
|
feeItemNameMap.TryGetValue(costItem, out var costItemName);
|
||||||
|
|
||||||
return new ReceivablePayablePaginatedOutput
|
return new ReceivablePayablePaginatedOutput
|
||||||
{
|
{
|
||||||
|
OrderId = row.Order.Id,
|
||||||
ServiceNo = row.Order.ServiceNo,
|
ServiceNo = row.Order.ServiceNo,
|
||||||
ClientNo = row.Order.ClientNo,
|
ClientNo = row.Order.ClientNo,
|
||||||
TrackNo = row.Order.TrackNo,
|
TrackNo = row.Order.TrackNo,
|
||||||
@ -85,7 +189,8 @@ public class FinanceService : ApplicationService
|
|||||||
ConsigneeCountryName = row.Order.ConsigneeCountryName,
|
ConsigneeCountryName = row.Order.ConsigneeCountryName,
|
||||||
FeeType = row.Fee.Type,
|
FeeType = row.Fee.Type,
|
||||||
FeeTypeName = GetFeeTypeName(row.Fee.Type),
|
FeeTypeName = GetFeeTypeName(row.Fee.Type),
|
||||||
CostItem = row.Fee.CostItem,
|
CostItem = costItem,
|
||||||
|
CostItemName = costItemName ?? ParcelBillingCalculator.ResolveFeeDisplayName(costItem, null),
|
||||||
Amount = row.Fee.Amount,
|
Amount = row.Fee.Amount,
|
||||||
Currency = row.Fee.Currency,
|
Currency = row.Fee.Currency,
|
||||||
DownstreamProvider = row.Fee.DownstreamProvider,
|
DownstreamProvider = row.Fee.DownstreamProvider,
|
||||||
@ -95,27 +200,59 @@ public class FinanceService : ApplicationService
|
|||||||
ChargingStageName = GetChargingStageName(row.Fee.ChargingStage)
|
ChargingStageName = GetChargingStageName(row.Fee.ChargingStage)
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
return new OmsPageResult<ReceivablePayablePaginatedOutput>
|
private async Task<Dictionary<string, string>> BuildFeeItemNameMapAsync()
|
||||||
{
|
{
|
||||||
Items = items,
|
var dictRows = await DictRepository.GetListAsync(x => x.DictType == FeeItemDictType);
|
||||||
Total = total
|
return dictRows
|
||||||
};
|
.Where(x => !string.IsNullOrWhiteSpace(x.DictCode))
|
||||||
|
.GroupBy(x => x.DictCode.Trim(), StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => g.First().DictName?.Trim() ?? g.Key,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OrderReceivablePayableFilter BuildFilter(
|
private static OrderReceivablePayableFilter BuildFilter(
|
||||||
ReceivablePayablePaginatedInput input,
|
ReceivablePayablePaginatedInput input,
|
||||||
int? customerId) =>
|
int? customerId)
|
||||||
new()
|
{
|
||||||
|
var timeField = NormalizeTimeField(input.TimeField);
|
||||||
|
var useCreateTime = timeField == "createTime";
|
||||||
|
return new()
|
||||||
{
|
{
|
||||||
Keyword = input.Keyword,
|
Keyword = input.Keyword,
|
||||||
CustomerId = customerId,
|
CustomerId = customerId,
|
||||||
CreateTimeStart = input.StartTime,
|
CustomerCode = input.CustomerCode,
|
||||||
CreateTimeEnd = input.EndTime,
|
CreateTimeStart = useCreateTime ? input.StartTime : null,
|
||||||
|
CreateTimeEnd = useCreateTime ? input.EndTime : null,
|
||||||
|
TimeField = timeField,
|
||||||
|
TimeStart = input.StartTime,
|
||||||
|
TimeEnd = input.EndTime,
|
||||||
FeeType = input.FeeType,
|
FeeType = input.FeeType,
|
||||||
CostItem = input.CostItem,
|
CostItem = input.CostItem,
|
||||||
ChargingStage = input.ChargingStage
|
ChargingStage = input.ChargingStage,
|
||||||
|
EffectiveStageOnly = input.EffectiveStageOnly,
|
||||||
|
IsPaid = NormalizeIsPaid(input.IsPaid)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int? NormalizeIsPaid(int? isPaid)
|
||||||
|
{
|
||||||
|
if (!isPaid.HasValue) return null;
|
||||||
|
return isPaid.Value == 1 ? 1 : isPaid.Value == 0 ? 0 : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeTimeField(string? timeField)
|
||||||
|
{
|
||||||
|
var raw = timeField?.Trim();
|
||||||
|
return raw switch
|
||||||
|
{
|
||||||
|
"receiveTime" or "shipTime" or "payTime" => raw,
|
||||||
|
_ => "createTime"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ApplyInternalOrderDataScopeAsync(OrderReceivablePayableFilter filter)
|
private async Task ApplyInternalOrderDataScopeAsync(OrderReceivablePayableFilter filter)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -19,4 +19,34 @@ public class OrderReceivablePayableFilter : OrderListFilter
|
|||||||
/// 计费阶段:forecast / inbound / outbound。
|
/// 计费阶段:forecast / inbound / outbound。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ChargingStage { get; set; }
|
public string? ChargingStage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户编码关键词(模糊匹配)。
|
||||||
|
/// </summary>
|
||||||
|
public string? CustomerCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅保留各运单费用项的当前生效阶段(出库 > 入库 > 预报)。
|
||||||
|
/// </summary>
|
||||||
|
public bool EffectiveStageOnly { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时间筛选字段:createTime / receiveTime / shipTime / payTime。
|
||||||
|
/// </summary>
|
||||||
|
public string? TimeField { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时间区间起(与 TimeField 对应字段)。
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? TimeStart { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 时间区间止(与 TimeField 对应字段)。
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? TimeEnd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付状态:0 未支付,1 已支付;null 表示全部。
|
||||||
|
/// </summary>
|
||||||
|
public int? IsPaid { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
namespace Oms.Domain.OrdersAggregate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应收应付汇总行(按费用类型 + 币种)。
|
||||||
|
/// </summary>
|
||||||
|
public class OrderReceivablePayableSummaryRow
|
||||||
|
{
|
||||||
|
public string FeeType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Currency { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public decimal TotalAmount { get; set; }
|
||||||
|
|
||||||
|
public int LineCount { get; set; }
|
||||||
|
}
|
||||||
@ -76,6 +76,15 @@ public interface IOrderRepository : IRepository<Order>
|
|||||||
OrderReceivablePayableFilter filter,
|
OrderReceivablePayableFilter filter,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<List<OrderReceivablePayableSummaryRow>> GetReceivablePayableSummaryAsync(
|
||||||
|
OrderReceivablePayableFilter filter,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<List<OrderReceivablePayableRow>> GetReceivablePayableExportListAsync(
|
||||||
|
OrderReceivablePayableFilter filter,
|
||||||
|
int maxRows,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default);
|
Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<List<OrderStatusCountRow>> GetStatusCountsByFilterAsync(
|
Task<List<OrderStatusCountRow>> GetStatusCountsByFilterAsync(
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Fake.DependencyInjection;
|
using Fake.DependencyInjection;
|
||||||
|
using Oms.Domain.CustomersAggregate.Entities;
|
||||||
using Oms.Domain.OrdersAggregate;
|
using Oms.Domain.OrdersAggregate;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
@ -1088,8 +1089,81 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var db = await GetDbContextAsync(cancellationToken);
|
var db = await GetDbContextAsync(cancellationToken);
|
||||||
var orderIdsQuery = ApplyListFilter(db.Queryable<Order>(), filter).Select(x => x.Id);
|
var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken);
|
||||||
|
|
||||||
|
var total = await feeQuery.CountAsync(cancellationToken);
|
||||||
|
var fees = await feeQuery
|
||||||
|
.OrderBy(fee => fee.CreateTime, OrderByType.Desc)
|
||||||
|
.OrderBy(fee => fee.Id, OrderByType.Desc)
|
||||||
|
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
||||||
|
|
||||||
|
return (await MapReceivablePayableRowsAsync(db, fees, cancellationToken), total);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<OrderReceivablePayableSummaryRow>> GetReceivablePayableSummaryAsync(
|
||||||
|
OrderReceivablePayableFilter filter,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var db = await GetDbContextAsync(cancellationToken);
|
||||||
|
var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken);
|
||||||
|
|
||||||
|
return await feeQuery
|
||||||
|
.GroupBy(fee => new { fee.Type, fee.Currency })
|
||||||
|
.Select(g => new OrderReceivablePayableSummaryRow
|
||||||
|
{
|
||||||
|
FeeType = g.Type,
|
||||||
|
Currency = g.Currency,
|
||||||
|
TotalAmount = SqlFunc.AggregateSum(g.Amount),
|
||||||
|
LineCount = SqlFunc.AggregateCount(g.Id)
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<OrderReceivablePayableRow>> GetReceivablePayableExportListAsync(
|
||||||
|
OrderReceivablePayableFilter filter,
|
||||||
|
int maxRows,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (maxRows <= 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var db = await GetDbContextAsync(cancellationToken);
|
||||||
|
var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken);
|
||||||
|
var fees = await feeQuery
|
||||||
|
.OrderBy(fee => fee.CreateTime, OrderByType.Desc)
|
||||||
|
.OrderBy(fee => fee.Id, OrderByType.Desc)
|
||||||
|
.Take(maxRows)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return await MapReceivablePayableRowsAsync(db, fees, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<ISugarQueryable<OrderFee>> BuildReceivablePayableFeeQueryAsync(
|
||||||
|
ISqlSugarClient db,
|
||||||
|
OrderReceivablePayableFilter filter,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var orderQuery = ApplyListFilter(db.Queryable<Order>(), filter);
|
||||||
|
var customerCode = filter.CustomerCode?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(customerCode))
|
||||||
|
{
|
||||||
|
orderQuery = orderQuery.Where(o =>
|
||||||
|
SqlFunc.Subqueryable<Customer>()
|
||||||
|
.Where(c => c.Id == o.CustomerId && c.CustomerCode.Contains(customerCode))
|
||||||
|
.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
orderQuery = ApplyReceivablePayableTimeFilter(orderQuery, filter);
|
||||||
|
|
||||||
|
if (filter.IsPaid is 0 or 1)
|
||||||
|
{
|
||||||
|
var paidFlag = (byte)filter.IsPaid.Value;
|
||||||
|
orderQuery = orderQuery.Where(o => o.IsPaid == paidFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderIdsQuery = orderQuery.Select(x => x.Id);
|
||||||
var feeQuery = db.Queryable<OrderFee>()
|
var feeQuery = db.Queryable<OrderFee>()
|
||||||
.Where(fee => !fee.IsDeleted)
|
.Where(fee => !fee.IsDeleted)
|
||||||
.In(fee => fee.OrderId, orderIdsQuery);
|
.In(fee => fee.OrderId, orderIdsQuery);
|
||||||
@ -1104,7 +1178,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
var costItem = filter.CostItem?.Trim();
|
var costItem = filter.CostItem?.Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(costItem))
|
if (!string.IsNullOrWhiteSpace(costItem))
|
||||||
{
|
{
|
||||||
feeQuery = feeQuery.Where(fee => fee.CostItem.Contains(costItem));
|
feeQuery = feeQuery.Where(fee => fee.CostItem == costItem || fee.CostItem.Contains(costItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
var chargingStage = filter.ChargingStage?.Trim();
|
var chargingStage = filter.ChargingStage?.Trim();
|
||||||
@ -1114,15 +1188,85 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
feeQuery = feeQuery.Where(fee => fee.ChargingStage == chargingStage);
|
feeQuery = feeQuery.Where(fee => fee.ChargingStage == chargingStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
var total = await feeQuery.CountAsync(cancellationToken);
|
if (!filter.EffectiveStageOnly)
|
||||||
var fees = await feeQuery
|
{
|
||||||
.OrderBy(fee => fee.CreateTime, OrderByType.Desc)
|
return feeQuery;
|
||||||
.OrderBy(fee => fee.Id, OrderByType.Desc)
|
}
|
||||||
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
|
||||||
|
|
||||||
|
var candidates = await feeQuery
|
||||||
|
.Select(f => new { f.Id, f.OrderId, f.Type, f.CostItem, f.ChargingStage })
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
return feeQuery.Where(_ => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var winningIds = candidates
|
||||||
|
.GroupBy(f => (f.OrderId, f.Type, f.CostItem))
|
||||||
|
.Select(g => g
|
||||||
|
.OrderByDescending(f => GetChargingStageRank(f.ChargingStage))
|
||||||
|
.ThenByDescending(f => f.Id)
|
||||||
|
.First()
|
||||||
|
.Id)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return feeQuery.Where(f => winningIds.Contains(f.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetChargingStageRank(string? stage) =>
|
||||||
|
stage switch
|
||||||
|
{
|
||||||
|
OrderFeeChargingStageCodes.Outbound => 3,
|
||||||
|
OrderFeeChargingStageCodes.Inbound => 2,
|
||||||
|
OrderFeeChargingStageCodes.Forecast => 1,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
private static ISugarQueryable<Order> ApplyReceivablePayableTimeFilter(
|
||||||
|
ISugarQueryable<Order> query,
|
||||||
|
OrderReceivablePayableFilter filter)
|
||||||
|
{
|
||||||
|
var timeField = filter.TimeField?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(timeField) || string.Equals(timeField, "createTime", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.TimeStart.HasValue)
|
||||||
|
{
|
||||||
|
var start = filter.TimeStart.Value;
|
||||||
|
query = timeField switch
|
||||||
|
{
|
||||||
|
"receiveTime" => query.Where(o => o.ReceiveTime != null && o.ReceiveTime >= start),
|
||||||
|
"shipTime" => query.Where(o => o.ShipTime != null && o.ShipTime >= start),
|
||||||
|
"payTime" => query.Where(o => o.PayTime != null && o.PayTime >= start),
|
||||||
|
_ => query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.TimeEnd.HasValue)
|
||||||
|
{
|
||||||
|
var end = filter.TimeEnd.Value;
|
||||||
|
query = timeField switch
|
||||||
|
{
|
||||||
|
"receiveTime" => query.Where(o => o.ReceiveTime != null && o.ReceiveTime <= end),
|
||||||
|
"shipTime" => query.Where(o => o.ShipTime != null && o.ShipTime <= end),
|
||||||
|
"payTime" => query.Where(o => o.PayTime != null && o.PayTime <= end),
|
||||||
|
_ => query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<OrderReceivablePayableRow>> MapReceivablePayableRowsAsync(
|
||||||
|
ISqlSugarClient db,
|
||||||
|
List<OrderFee> fees,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
if (fees.Count == 0)
|
if (fees.Count == 0)
|
||||||
{
|
{
|
||||||
return ([], total);
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var orderIds = fees.Select(x => x.OrderId).Distinct().ToArray();
|
var orderIds = fees.Select(x => x.OrderId).Distinct().ToArray();
|
||||||
@ -1131,7 +1275,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
var orderMap = orders.ToDictionary(x => x.Id);
|
var orderMap = orders.ToDictionary(x => x.Id);
|
||||||
|
|
||||||
var items = fees
|
return fees
|
||||||
.Where(fee => orderMap.ContainsKey(fee.OrderId))
|
.Where(fee => orderMap.ContainsKey(fee.OrderId))
|
||||||
.Select(fee => new OrderReceivablePayableRow
|
.Select(fee => new OrderReceivablePayableRow
|
||||||
{
|
{
|
||||||
@ -1139,8 +1283,6 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
Order = orderMap[fee.OrderId]
|
Order = orderMap[fee.OrderId]
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return (items, total);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default)
|
public async Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user