feat(customer): account flow ledger, pay deduct balance, status counts
Add t_customer_account_flow with list API, deduct balance on order pay, credit ledger on recharge approve, and GET /order/status-counts for tab badges. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
82df5d1e79
commit
d766343ffe
23
sql/migrate_t_customer_account_flow.sql
Normal file
23
sql/migrate_t_customer_account_flow.sql
Normal file
@ -0,0 +1,23 @@
|
||||
-- 客户账户资金流水
|
||||
CREATE TABLE IF NOT EXISTS `t_customer_account_flow` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`CustomerId` int NOT NULL COMMENT '客户 ID',
|
||||
`SerialNumber` varchar(64) NOT NULL COMMENT '流水号',
|
||||
`BizType` varchar(32) NOT NULL COMMENT '业务类型 recharge/orderPay',
|
||||
`BizId` int DEFAULT NULL COMMENT '关联业务主键',
|
||||
`BizNo` varchar(64) DEFAULT NULL COMMENT '关联业务单号',
|
||||
`Amount` decimal(18,2) NOT NULL COMMENT '变动金额,正入账负出账',
|
||||
`BalanceBefore` decimal(18,2) NOT NULL COMMENT '变动前余额',
|
||||
`BalanceAfter` decimal(18,2) NOT NULL COMMENT '变动后余额',
|
||||
`Currency` varchar(8) NOT NULL DEFAULT 'CNY' COMMENT '币种',
|
||||
`Remark` varchar(512) DEFAULT NULL COMMENT '备注',
|
||||
`IsDeleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`CreateUserId` int NOT NULL DEFAULT '0',
|
||||
`CreateTime` datetime NOT NULL,
|
||||
`UpdateUserId` int DEFAULT NULL,
|
||||
`UpdateTime` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `uk_customer_account_flow_serial` (`SerialNumber`),
|
||||
KEY `idx_customer_account_flow_customer_time` (`CustomerId`, `CreateTime`),
|
||||
KEY `idx_customer_account_flow_biz` (`BizType`, `BizId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户账户资金流水';
|
||||
@ -0,0 +1,35 @@
|
||||
namespace Oms.Application.Dtos.Customers;
|
||||
|
||||
/// <summary>
|
||||
/// 客户账户流水列表项。
|
||||
/// </summary>
|
||||
public class CustomerAccountFlowOutput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
public string SerialNumber { get; set; } = string.Empty;
|
||||
|
||||
public string BizType { get; set; } = string.Empty;
|
||||
|
||||
public string? BizTypeName { get; set; }
|
||||
|
||||
public int? BizId { get; set; }
|
||||
|
||||
public string? BizNo { get; set; }
|
||||
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public decimal BalanceBefore { get; set; }
|
||||
|
||||
public decimal BalanceAfter { get; set; }
|
||||
|
||||
public string Currency { get; set; } = "CNY";
|
||||
|
||||
public string? Remark { get; set; }
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace Oms.Application.Dtos.Customers;
|
||||
|
||||
/// <summary>
|
||||
/// 客户账户流水分页查询入参。
|
||||
/// </summary>
|
||||
public class CustomerAccountFlowPaginatedInput
|
||||
{
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
public int PageSize { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 流水号或关联单号关键词。
|
||||
/// </summary>
|
||||
public string? Keyword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务类型:recharge / orderPay,空表示全部。
|
||||
/// </summary>
|
||||
public string? BizType { get; set; }
|
||||
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
public DateTime? EndTime { get; set; }
|
||||
}
|
||||
17
src/Oms.Application/Dtos/Orders/OrderStatusCountsOutput.cs
Normal file
17
src/Oms.Application/Dtos/Orders/OrderStatusCountsOutput.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 各状态订单数量统计(单次聚合查询,非分页列表)。
|
||||
/// </summary>
|
||||
public class OrderStatusCountsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 全部订单数量。
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按 OrderStatus 分组的数量,key 为状态码。
|
||||
/// </summary>
|
||||
public Dictionary<string, int> Counts { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
using System.Linq.Expressions;
|
||||
using Fake.Application;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Customers;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.CustomersAggregate.Consts;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Customers;
|
||||
|
||||
/// <summary>
|
||||
/// 客户账户流水 API。
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "OMS-Customer")]
|
||||
public class CustomerAccountFlowService : ApplicationService
|
||||
{
|
||||
private ICustomerAccountFlowRepository FlowRepository =>
|
||||
LazyServiceProvider.GetRequiredService<ICustomerAccountFlowRepository>();
|
||||
|
||||
private ICustomerRepository CustomerRepository =>
|
||||
LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询客户账户流水。
|
||||
/// </summary>
|
||||
[HttpGet("/customer/account-flow/list")]
|
||||
public async Task<OmsPageResult<CustomerAccountFlowOutput>> GetPaginatedListAsync(
|
||||
[FromQuery] CustomerAccountFlowPaginatedInput input)
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
input.CustomerId);
|
||||
var predicate = BuildPaginatedPredicate(input, customerId);
|
||||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||
|
||||
var entities = await FlowRepository.GetPagedListAsync(
|
||||
predicate,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
new Dictionary<string, bool>
|
||||
{
|
||||
[nameof(CustomerAccountFlow.CreateTime)] = false,
|
||||
[nameof(CustomerAccountFlow.Id)] = false
|
||||
});
|
||||
|
||||
var total = await FlowRepository.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);
|
||||
|
||||
var items = entities.Select(entity =>
|
||||
{
|
||||
customerMap.TryGetValue(entity.CustomerId, out var customer);
|
||||
return new CustomerAccountFlowOutput
|
||||
{
|
||||
Id = entity.Id,
|
||||
CustomerId = entity.CustomerId,
|
||||
CustomerCode = customer?.CustomerCode,
|
||||
SerialNumber = entity.SerialNumber,
|
||||
BizType = entity.BizType,
|
||||
BizTypeName = GetBizTypeName(entity.BizType),
|
||||
BizId = entity.BizId,
|
||||
BizNo = entity.BizNo,
|
||||
Amount = entity.Amount,
|
||||
BalanceBefore = entity.BalanceBefore,
|
||||
BalanceAfter = entity.BalanceAfter,
|
||||
Currency = entity.Currency,
|
||||
Remark = entity.Remark,
|
||||
CreateTime = entity.CreateTime
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return new OmsPageResult<CustomerAccountFlowOutput>
|
||||
{
|
||||
Items = items,
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
|
||||
private static Expression<Func<CustomerAccountFlow, bool>> BuildPaginatedPredicate(
|
||||
CustomerAccountFlowPaginatedInput input,
|
||||
int? customerId)
|
||||
{
|
||||
var keyword = input.Keyword?.Trim();
|
||||
var bizType = input.BizType?.Trim();
|
||||
var hasKeyword = !string.IsNullOrWhiteSpace(keyword);
|
||||
var hasBizType = !string.IsNullOrWhiteSpace(bizType)
|
||||
&& !string.Equals(bizType, "all", StringComparison.OrdinalIgnoreCase);
|
||||
var hasCustomerFilter = customerId.HasValue;
|
||||
var customerIdValue = customerId.GetValueOrDefault();
|
||||
|
||||
if (input.StartTime.HasValue && input.EndTime.HasValue)
|
||||
{
|
||||
var start = input.StartTime.Value;
|
||||
var end = input.EndTime.Value;
|
||||
return flow =>
|
||||
(!hasCustomerFilter || flow.CustomerId == customerIdValue) &&
|
||||
(!hasBizType || flow.BizType == bizType) &&
|
||||
(!hasKeyword ||
|
||||
flow.SerialNumber.Contains(keyword!) ||
|
||||
(flow.BizNo ?? string.Empty).Contains(keyword!)) &&
|
||||
flow.CreateTime >= start &&
|
||||
flow.CreateTime <= end;
|
||||
}
|
||||
|
||||
return flow =>
|
||||
(!hasCustomerFilter || flow.CustomerId == customerIdValue) &&
|
||||
(!hasBizType || flow.BizType == bizType) &&
|
||||
(!hasKeyword ||
|
||||
flow.SerialNumber.Contains(keyword!) ||
|
||||
(flow.BizNo ?? string.Empty).Contains(keyword!));
|
||||
}
|
||||
|
||||
private static string GetBizTypeName(string bizType)
|
||||
{
|
||||
return bizType switch
|
||||
{
|
||||
CustomerAccountFlowBizTypes.Recharge => "充值入账",
|
||||
CustomerAccountFlowBizTypes.OrderPay => "订单支付",
|
||||
_ => bizType
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
@ -58,6 +59,28 @@ public partial class OrderService : ApplicationService
|
||||
return new OmsPageResult<OrderOutput> { Items = await MapOrderOutputsAsync(orders), Total = total };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按状态聚合统计订单数量(单次查询,供 Tab 角标展示)。
|
||||
/// </summary>
|
||||
[HttpGet("/order/status-counts")]
|
||||
public async Task<OrderStatusCountsOutput> GetStatusCountsAsync()
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
null);
|
||||
var rows = await OrderRepository.GetStatusCountsAsync(customerId);
|
||||
var counts = rows.ToDictionary(
|
||||
x => x.Status,
|
||||
x => x.Count,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
return new OrderStatusCountsOutput
|
||||
{
|
||||
Total = rows.Sum(x => x.Count),
|
||||
Counts = counts
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询订单详情。
|
||||
/// </summary>
|
||||
@ -91,6 +114,44 @@ public partial class OrderService : ApplicationService
|
||||
public async Task PayAsync(OrderActionInput input)
|
||||
{
|
||||
var order = await GetOrderOrThrowAsync(input.Id);
|
||||
if (order.OrderStatus == OrderStatusCodes.PendingShip && order.PayTime.HasValue)
|
||||
{
|
||||
throw new BusinessException("订单已支付");
|
||||
}
|
||||
|
||||
if (order.OrderStatus != OrderStatusCodes.PendingPayment)
|
||||
{
|
||||
throw new BusinessException("当前订单状态不可支付");
|
||||
}
|
||||
|
||||
if (!order.CustomerId.HasValue)
|
||||
{
|
||||
throw new BusinessException("订单未关联客户,无法支付");
|
||||
}
|
||||
|
||||
var fees = await OrderRepository.GetFeesByOrderIdsAsync([order.Id]);
|
||||
var payAmount = fees
|
||||
.Where(x => x.Type == OrderFeeTypeCodes.Receivable)
|
||||
.Sum(x => x.Amount);
|
||||
if (payAmount <= 0)
|
||||
{
|
||||
throw new BusinessException("订单应收金额无效,无法支付");
|
||||
}
|
||||
|
||||
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == order.CustomerId.Value);
|
||||
if (customer == null)
|
||||
{
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
|
||||
var balanceMutator = LazyServiceProvider.GetRequiredService<CustomerBalanceMutator>();
|
||||
await balanceMutator.DebitOrderPayAsync(
|
||||
customer,
|
||||
payAmount,
|
||||
order.Id,
|
||||
order.ServiceNo,
|
||||
$"订单支付 {order.ServiceNo}");
|
||||
|
||||
order.PayTime = DateTime.Now;
|
||||
order.OrderStatus = OrderStatusCodes.PendingShip;
|
||||
await UpdateOrderAsync(order);
|
||||
|
||||
@ -96,10 +96,15 @@ public class RechargeService : ApplicationService
|
||||
record.AuditTime = now;
|
||||
record.AuditBy = CurrentUser.UserName;
|
||||
|
||||
customer.Balance += record.RechargeAmount;
|
||||
var balanceMutator = LazyServiceProvider.GetRequiredService<CustomerBalanceMutator>();
|
||||
await balanceMutator.CreditRechargeAsync(
|
||||
customer,
|
||||
record.RechargeAmount,
|
||||
record.Id,
|
||||
record.SerialNumber,
|
||||
input.Remark ?? "充值审核通过");
|
||||
|
||||
await RechargeRecordRepository.UpdateAsync(record);
|
||||
await CustomerRepository.UpdateAsync(customer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
119
src/Oms.Application/Shared/CustomerBalanceMutator.cs
Normal file
119
src/Oms.Application/Shared/CustomerBalanceMutator.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Fake.Application;
|
||||
using Oms.Domain.CustomersAggregate.Consts;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 客户余额变动与流水写入。
|
||||
/// </summary>
|
||||
public class CustomerBalanceMutator : IScopedDependency
|
||||
{
|
||||
private readonly ICustomerRepository _customerRepository;
|
||||
private readonly ICustomerAccountFlowRepository _flowRepository;
|
||||
|
||||
public CustomerBalanceMutator(
|
||||
ICustomerRepository customerRepository,
|
||||
ICustomerAccountFlowRepository flowRepository)
|
||||
{
|
||||
_customerRepository = customerRepository;
|
||||
_flowRepository = flowRepository;
|
||||
}
|
||||
|
||||
public Task CreditRechargeAsync(
|
||||
Customer customer,
|
||||
decimal amount,
|
||||
int rechargeRecordId,
|
||||
string rechargeSerialNumber,
|
||||
string? remark,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ApplyAsync(
|
||||
customer,
|
||||
amount,
|
||||
CustomerAccountFlowBizTypes.Recharge,
|
||||
rechargeRecordId,
|
||||
rechargeSerialNumber,
|
||||
remark,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task DebitOrderPayAsync(
|
||||
Customer customer,
|
||||
decimal amount,
|
||||
int orderId,
|
||||
string serviceNo,
|
||||
string? remark,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
throw new BusinessException("支付金额必须大于 0");
|
||||
}
|
||||
|
||||
return ApplyAsync(
|
||||
customer,
|
||||
-amount,
|
||||
CustomerAccountFlowBizTypes.OrderPay,
|
||||
orderId,
|
||||
serviceNo,
|
||||
remark,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ApplyAsync(
|
||||
Customer customer,
|
||||
decimal signedAmount,
|
||||
string bizType,
|
||||
int? bizId,
|
||||
string? bizNo,
|
||||
string? remark,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (signedAmount == 0)
|
||||
{
|
||||
throw new BusinessException("流水金额不能为 0");
|
||||
}
|
||||
|
||||
var balanceBefore = customer.Balance;
|
||||
var balanceAfter = balanceBefore + signedAmount;
|
||||
if (balanceAfter < 0)
|
||||
{
|
||||
throw new BusinessException("账户余额不足");
|
||||
}
|
||||
|
||||
customer.Balance = balanceAfter;
|
||||
await _customerRepository.UpdateAsync(customer, cancellationToken);
|
||||
|
||||
var flow = new CustomerAccountFlow
|
||||
{
|
||||
CustomerId = customer.Id,
|
||||
SerialNumber = await GenerateSerialNumberAsync(cancellationToken),
|
||||
BizType = bizType,
|
||||
BizId = bizId,
|
||||
BizNo = bizNo,
|
||||
Amount = signedAmount,
|
||||
BalanceBefore = balanceBefore,
|
||||
BalanceAfter = balanceAfter,
|
||||
Currency = "CNY",
|
||||
Remark = remark
|
||||
};
|
||||
await _flowRepository.InsertAsync(flow, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<string> GenerateSerialNumberAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var serialNumber = $"FLOW{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(100000, 999999)}";
|
||||
if (!await _flowRepository.ExistsSerialNumberAsync(serialNumber, cancellationToken))
|
||||
{
|
||||
return serialNumber;
|
||||
}
|
||||
}
|
||||
|
||||
throw new BusinessException("账户流水号生成失败");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Oms.Domain.CustomersAggregate.Consts;
|
||||
|
||||
/// <summary>
|
||||
/// 客户账户流水业务类型。
|
||||
/// </summary>
|
||||
public static class CustomerAccountFlowBizTypes
|
||||
{
|
||||
public const string Recharge = "recharge";
|
||||
|
||||
public const string OrderPay = "orderPay";
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
using Oms.Domain.Shared;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Oms.Domain.CustomersAggregate.Entities;
|
||||
|
||||
[SugarTable("t_customer_account_flow")]
|
||||
public class CustomerAccountFlow : OmsAuditedAggregateRoot
|
||||
{
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string SerialNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 业务类型:recharge / orderPay 等。
|
||||
/// </summary>
|
||||
public string BizType { get; set; } = string.Empty;
|
||||
|
||||
public int? BizId { get; set; }
|
||||
|
||||
public string? BizNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变动金额:正数为入账,负数为出账。
|
||||
/// </summary>
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public decimal BalanceBefore { get; set; }
|
||||
|
||||
public decimal BalanceAfter { get; set; }
|
||||
|
||||
public string Currency { get; set; } = "CNY";
|
||||
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using Fake.Domain.Repositories;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
|
||||
namespace Oms.Domain.CustomersAggregate.Repositories;
|
||||
|
||||
public interface ICustomerAccountFlowRepository : IRepository<CustomerAccountFlow>
|
||||
{
|
||||
Task<bool> ExistsSerialNumberAsync(string serialNumber, CancellationToken cancellationToken = default);
|
||||
}
|
||||
11
src/Oms.Domain/OrdersAggregate/OrderStatusCountRow.cs
Normal file
11
src/Oms.Domain/OrdersAggregate/OrderStatusCountRow.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Oms.Domain.OrdersAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// 订单按状态分组统计行。
|
||||
/// </summary>
|
||||
public class OrderStatusCountRow
|
||||
{
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
public int Count { get; set; }
|
||||
}
|
||||
@ -48,4 +48,6 @@ public interface IOrderRepository : IRepository<Order>
|
||||
Task UpdateRecipientAsync(OrderRecipient recipient, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<OrderFee>> GetFeesByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.SqlSugarCore.Repositories;
|
||||
|
||||
namespace Oms.SqlSugarCore.Customers;
|
||||
|
||||
public class CustomerAccountFlowRepository
|
||||
: OmsSqlSugarRepository<CustomerAccountFlow>, ICustomerAccountFlowRepository, IScopedDependency
|
||||
{
|
||||
public Task<bool> ExistsSerialNumberAsync(string serialNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return AnyAsync(x => x.SerialNumber == serialNumber, cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.OrdersAggregate;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
using Oms.SqlSugarCore.Repositories;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Oms.SqlSugarCore.Orders;
|
||||
|
||||
@ -249,4 +251,24 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
.Where(x => ids.Contains(x.OrderId))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var query = db.Queryable<Order>();
|
||||
if (customerId.HasValue)
|
||||
{
|
||||
var cid = customerId.Value;
|
||||
query = query.Where(x => x.CustomerId == cid);
|
||||
}
|
||||
|
||||
return await query
|
||||
.GroupBy(x => x.OrderStatus)
|
||||
.Select(x => new OrderStatusCountRow
|
||||
{
|
||||
Status = x.OrderStatus,
|
||||
Count = SqlFunc.AggregateCount(x.Id)
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user