账期收付改为按累计已付同步 credit_ledger,修复多笔 partial_paid 与坏账核销重复释额;新增 API E2E(占成、权限、部分收付/坏账)与 GitHub Actions e2e-api job。 Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
3.9 KiB
TypeScript
106 lines
3.9 KiB
TypeScript
/**
|
||
* E2E:部分收付后坏账核销 → settlement_confirm 累计释额至账单全额。
|
||
*/
|
||
|
||
import { test, expect } from '@playwright/test';
|
||
import {
|
||
adminLoginViaBypass,
|
||
adminCtxOf,
|
||
adminLoginWithAccount,
|
||
fetchBillCreditLedgerRows,
|
||
fetchOpenDrawNo,
|
||
openSettlementPeriod,
|
||
playerLoginViaBypass,
|
||
setupCreditPlayer,
|
||
setupSiteOperator,
|
||
} from './_helper';
|
||
import { runWalletDrawSettlement } from './helpers/draw-settlement';
|
||
|
||
const DEFAULT_PASS = process.env.E2E_PLAYER_PASSWORD ?? '12345678';
|
||
|
||
test('部分收付后坏账核销 → credit_ledger 累计至全额', async () => {
|
||
test.setTimeout(360_000);
|
||
|
||
const credit = await setupCreditPlayer({
|
||
credit_limit: 100_000,
|
||
username: `e2e_bd_partial_${Date.now()}`,
|
||
});
|
||
const finance = await setupSiteOperator({
|
||
username: `e2e_fin_bd_${Date.now()}`,
|
||
password: DEFAULT_PASS,
|
||
role_slug: 'site_finance',
|
||
});
|
||
const drawNo = await fetchOpenDrawNo();
|
||
|
||
const superSession = await adminLoginViaBypass();
|
||
const superAdmin = await adminCtxOf(superSession.accessToken);
|
||
const periodId = await openSettlementPeriod(superAdmin, credit.admin_site_id);
|
||
|
||
const playerSession = await playerLoginViaBypass({
|
||
site_code: credit.site_code,
|
||
username: credit.username,
|
||
password: credit.password,
|
||
});
|
||
|
||
await runWalletDrawSettlement({
|
||
adminToken: superSession.accessToken,
|
||
playerToken: playerSession.access_token,
|
||
drawNo,
|
||
betNumber: '4321',
|
||
publishWinNumber: '9999',
|
||
betAmount: 10_000,
|
||
expectWalletIncrease: false,
|
||
});
|
||
|
||
const close = await superAdmin.post(`/api/v1/admin/settlement-periods/${periodId}/close`);
|
||
expect(close.ok()).toBeTruthy();
|
||
|
||
const bills = await superAdmin.get(
|
||
`/api/v1/admin/settlement-bills?settlement_period_id=${periodId}&size=20`,
|
||
);
|
||
const billsBody = (await bills.json()) as any;
|
||
const playerBill = billsBody.data.items.find(
|
||
(b: any) => b.bill_type === 'player' && Number(b.owner_id) === credit.player_id,
|
||
);
|
||
expect(playerBill).toBeTruthy();
|
||
expect(Number(playerBill.net_amount)).toBeGreaterThan(0);
|
||
|
||
const billId = Number(playerBill.id);
|
||
await superAdmin.post(`/api/v1/admin/settlement-bills/${billId}/confirm`);
|
||
|
||
const billDetail = (await (await superAdmin.get(`/api/v1/admin/settlement-bills/${billId}`)).json()) as any;
|
||
const unpaid = Math.abs(Number(billDetail.data.bill.unpaid_amount));
|
||
const firstPay = Math.floor(unpaid / 2);
|
||
expect(firstPay).toBeGreaterThan(0);
|
||
|
||
const financeSession = await adminLoginWithAccount(finance.username, finance.password);
|
||
const financeAdmin = await adminCtxOf(financeSession.accessToken);
|
||
|
||
const pay1 = await financeAdmin.post(`/api/v1/admin/settlement-bills/${billId}/payments`, {
|
||
data: { amount: firstPay, method: 'e2e_bd_partial', remark: 'partial before write-off' },
|
||
});
|
||
expect(pay1.ok()).toBeTruthy();
|
||
expect((await pay1.json()).data.bill.status).toBe('partial_paid');
|
||
|
||
const ledgerAfterPay1 = await fetchBillCreditLedgerRows(credit.player_id, billId, 'settlement_confirm');
|
||
expect(ledgerAfterPay1).toHaveLength(1);
|
||
expect(Number(ledgerAfterPay1[0].amount)).toBe(firstPay);
|
||
|
||
const writeOff = await financeAdmin.post(`/api/v1/admin/settlement-bills/${billId}/bad-debt-write-off`, {
|
||
data: { reason: 'e2e partial then bad debt' },
|
||
});
|
||
expect(writeOff.ok(), `bad debt status=${writeOff.status()}`).toBeTruthy();
|
||
expect((await writeOff.json()).code).toBe(0);
|
||
|
||
const settledBill = (await (await superAdmin.get(`/api/v1/admin/settlement-bills/${billId}`)).json()) as any;
|
||
expect(settledBill.data.bill.status).toBe('settled');
|
||
expect(Number(settledBill.data.bill.unpaid_amount)).toBe(0);
|
||
|
||
const ledgerAfterWriteOff = await fetchBillCreditLedgerRows(credit.player_id, billId, 'settlement_confirm');
|
||
expect(ledgerAfterWriteOff).toHaveLength(1);
|
||
expect(Number(ledgerAfterWriteOff[0].amount)).toBe(unpaid);
|
||
|
||
await financeAdmin.dispose();
|
||
await superAdmin.dispose();
|
||
});
|