feat: add manual recharge and operation logs
This commit is contained in:
@@ -4,6 +4,7 @@ import { TenantId } from '../common/tenant-id.decorator';
|
||||
import {
|
||||
BillingService,
|
||||
BillingActionDto,
|
||||
CreateManualRechargeDto,
|
||||
CreateRechargeOrderDto,
|
||||
CreateAccountTransactionDto,
|
||||
CreateBillingPlanDto,
|
||||
@@ -58,6 +59,16 @@ export class BillingController {
|
||||
return this.billing.createRechargeOrder(body);
|
||||
}
|
||||
|
||||
@Get('manual-recharges')
|
||||
listManualRechargeRecords(@TenantId() tenantId?: string) {
|
||||
return this.billing.listManualRechargeRecords(tenantId);
|
||||
}
|
||||
|
||||
@Post('manual-recharges')
|
||||
createManualRecharge(@Body() body: CreateManualRechargeDto) {
|
||||
return this.billing.createManualRecharge(body);
|
||||
}
|
||||
|
||||
@Post('estimate')
|
||||
estimateSmsCost(@Body() body: EstimateSmsCostDto) {
|
||||
return this.billing.estimateSmsCost(body);
|
||||
|
||||
@@ -104,6 +104,35 @@ describe('BillingService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates manual recharge records for operator top-ups', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new BillingService(prisma as never);
|
||||
|
||||
const order = await service.createManualRecharge({
|
||||
tenantId: 'tenant-1',
|
||||
amountCents: 2000,
|
||||
smsUnits: 0,
|
||||
operatorId: 'admin-1',
|
||||
remark: '线下转账人工充值',
|
||||
});
|
||||
|
||||
expect(order).toEqual(expect.objectContaining({ amountCents: 2000, payMethod: 'manual_topup', status: 'paid' }));
|
||||
expect(prisma.rechargeOrder.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
payMethod: 'manual_topup',
|
||||
operatorId: 'admin-1',
|
||||
remark: '线下转账人工充值',
|
||||
}),
|
||||
});
|
||||
expect(prisma.accountTransaction.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
transactionType: 'recharge',
|
||||
amountCents: 2000,
|
||||
relatedType: 'recharge_order',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('writes freeze, charge, release, refund, and adjustment transactions', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new BillingService(prisma as never);
|
||||
|
||||
@@ -48,6 +48,14 @@ export interface CreateRechargeOrderDto {
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface CreateManualRechargeDto {
|
||||
tenantId: string;
|
||||
amountCents: number;
|
||||
smsUnits?: number;
|
||||
operatorId?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface EstimateSmsCostDto {
|
||||
tenantId: string;
|
||||
applicationId?: string;
|
||||
@@ -147,6 +155,18 @@ export class BillingService {
|
||||
});
|
||||
}
|
||||
|
||||
listManualRechargeRecords(tenantId?: string) {
|
||||
return this.prisma.rechargeOrder.findMany({
|
||||
where: {
|
||||
tenantId,
|
||||
payMethod: 'manual_topup',
|
||||
},
|
||||
include: { plan: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100,
|
||||
});
|
||||
}
|
||||
|
||||
async createRechargeOrder(data: CreateRechargeOrderDto) {
|
||||
const plan = data.planId ? await this.prisma.billingPlan.findUnique({ where: { id: data.planId } }) : null;
|
||||
const amountCents = data.amountCents ?? plan?.priceCents ?? 0;
|
||||
@@ -179,6 +199,17 @@ export class BillingService {
|
||||
return order;
|
||||
}
|
||||
|
||||
createManualRecharge(data: CreateManualRechargeDto) {
|
||||
return this.createRechargeOrder({
|
||||
tenantId: data.tenantId,
|
||||
amountCents: data.amountCents,
|
||||
smsUnits: data.smsUnits ?? 0,
|
||||
payMethod: 'manual_topup',
|
||||
operatorId: data.operatorId,
|
||||
remark: data.remark,
|
||||
});
|
||||
}
|
||||
|
||||
estimateSmsCost(data: EstimateSmsCostDto) {
|
||||
const billingUnits = estimateBillingUnits(data.content);
|
||||
const unitPrice = data.unitPrice ?? 0;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user