feat: refine operations UI and transport limits
This commit is contained in:
@@ -7,6 +7,9 @@ function createPrismaMock() {
|
||||
tenant: {
|
||||
findFirst: jest.fn().mockResolvedValue({ id: 'tenant-1', name: '示例企业', code: 'TENANT-1' }),
|
||||
},
|
||||
user: {
|
||||
findMany: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
tenantAccount: {
|
||||
findMany: jest.fn(),
|
||||
findUnique: jest.fn().mockImplementation(() => Promise.resolve({ ...accountState })),
|
||||
@@ -181,9 +184,10 @@ describe('BillingService', () => {
|
||||
it('returns the historical balance after each manual recharge', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.rechargeOrder.findMany.mockResolvedValue([
|
||||
{ id: 'order-1', tenantId: 'tenant-1', payMethod: 'manual_topup', amountCents: 2000 },
|
||||
{ id: 'order-1', tenantId: 'tenant-1', payMethod: 'manual_topup', amountCents: 2000, operatorId: 'admin-1' },
|
||||
{ id: 'order-2', tenantId: 'tenant-1', payMethod: 'manual_topup', amountCents: -300 },
|
||||
]);
|
||||
prisma.user.findMany.mockResolvedValue([{ id: 'admin-1', displayName: '运营人员张三', username: 'admin' }]);
|
||||
prisma.accountTransaction.findMany.mockResolvedValue([
|
||||
{ relatedId: 'order-1', balanceAfter: 3000 },
|
||||
{ relatedId: 'order-2', balanceAfter: 2700 },
|
||||
@@ -191,8 +195,8 @@ describe('BillingService', () => {
|
||||
const service = new BillingService(prisma as never);
|
||||
|
||||
await expect(service.listManualRechargeRecords()).resolves.toEqual([
|
||||
expect.objectContaining({ id: 'order-1', balanceAfterCents: 3000 }),
|
||||
expect.objectContaining({ id: 'order-2', balanceAfterCents: 2700 }),
|
||||
expect.objectContaining({ id: 'order-1', balanceAfterCents: 3000, operatorName: '运营人员张三' }),
|
||||
expect.objectContaining({ id: 'order-2', balanceAfterCents: 2700, operatorName: null }),
|
||||
]);
|
||||
expect(prisma.accountTransaction.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
@@ -201,6 +205,10 @@ describe('BillingService', () => {
|
||||
},
|
||||
select: { relatedId: true, balanceAfter: true },
|
||||
});
|
||||
expect(prisma.user.findMany).toHaveBeenCalledWith({
|
||||
where: { id: { in: ['admin-1'] } },
|
||||
select: { id: true, displayName: true, username: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('allows negative manual recharge amounts for balance correction', async () => {
|
||||
|
||||
@@ -163,18 +163,27 @@ export class BillingService {
|
||||
return orders;
|
||||
}
|
||||
|
||||
const transactions = await this.prisma.accountTransaction.findMany({
|
||||
where: {
|
||||
relatedType: 'recharge_order',
|
||||
relatedId: { in: orderIds },
|
||||
},
|
||||
select: { relatedId: true, balanceAfter: true },
|
||||
});
|
||||
const operatorIds = [...new Set(orders.map((order) => order.operatorId).filter((id): id is string => Boolean(id)))];
|
||||
const [transactions, operators] = await Promise.all([
|
||||
this.prisma.accountTransaction.findMany({
|
||||
where: {
|
||||
relatedType: 'recharge_order',
|
||||
relatedId: { in: orderIds },
|
||||
},
|
||||
select: { relatedId: true, balanceAfter: true },
|
||||
}),
|
||||
operatorIds.length ? this.prisma.user.findMany({
|
||||
where: { id: { in: operatorIds } },
|
||||
select: { id: true, displayName: true, username: true },
|
||||
}) : [],
|
||||
]);
|
||||
const balanceAfterByOrderId = new Map(transactions.map((transaction) => [transaction.relatedId, moneyToNumber(transaction.balanceAfter)]));
|
||||
const operatorNameById = new Map(operators.map((operator) => [operator.id, operator.displayName || operator.username]));
|
||||
|
||||
return orders.map((order) => ({
|
||||
...order,
|
||||
balanceAfterCents: balanceAfterByOrderId.get(order.id) ?? null,
|
||||
operatorName: order.operatorId ? operatorNameById.get(order.operatorId) ?? null : null,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -195,13 +204,25 @@ export class BillingService {
|
||||
this.prisma.rechargeOrder.count({ where }),
|
||||
]);
|
||||
const orderIds = orders.map((order) => order.id);
|
||||
const transactions = orderIds.length ? await this.prisma.accountTransaction.findMany({
|
||||
where: { relatedType: 'recharge_order', relatedId: { in: orderIds } },
|
||||
select: { relatedId: true, balanceAfter: true },
|
||||
}) : [];
|
||||
const operatorIds = [...new Set(orders.map((order) => order.operatorId).filter((id): id is string => Boolean(id)))];
|
||||
const [transactions, operators] = await Promise.all([
|
||||
orderIds.length ? this.prisma.accountTransaction.findMany({
|
||||
where: { relatedType: 'recharge_order', relatedId: { in: orderIds } },
|
||||
select: { relatedId: true, balanceAfter: true },
|
||||
}) : [],
|
||||
operatorIds.length ? this.prisma.user.findMany({
|
||||
where: { id: { in: operatorIds } },
|
||||
select: { id: true, displayName: true, username: true },
|
||||
}) : [],
|
||||
]);
|
||||
const balances = new Map(transactions.map((item) => [item.relatedId, moneyToNumber(item.balanceAfter)]));
|
||||
const operatorNames = new Map(operators.map((operator) => [operator.id, operator.displayName || operator.username]));
|
||||
return {
|
||||
items: orders.map((order) => ({ ...order, balanceAfterCents: balances.get(order.id) ?? null })),
|
||||
items: orders.map((order) => ({
|
||||
...order,
|
||||
balanceAfterCents: balances.get(order.id) ?? null,
|
||||
operatorName: order.operatorId ? operatorNames.get(order.operatorId) ?? null : null,
|
||||
})),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
|
||||
Reference in New Issue
Block a user