Files
lotteryLaravel/e2e/tests/api/17.settlement-partial-payment.spec.ts
kang 3f29229499
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled
fix: 部分收付累计释额并扩展结算 E2E 与 CI
账期收付改为按累计已付同步 credit_ledger,修复多笔 partial_paid 与坏账核销重复释额;新增 API E2E(占成、权限、部分收付/坏账)与 GitHub Actions e2e-api job。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 09:48:01 +08:00

203 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* E2E账期账单部分收付 → partial_paid → 尾款结清 settled。
*/
import { test, expect } from '@playwright/test';
import {
adminLoginViaBypass,
adminCtxOf,
fetchBillCreditLedgerRows,
fetchOpenDrawNo,
playerLoginViaBypass,
openSettlementPeriod,
setupCreditPlayer,
} from './_helper';
import { runWalletDrawSettlement } from './helpers/draw-settlement';
test('confirm 后分两笔收付 → partial_paid → settled', async () => {
test.setTimeout(300_000);
const credit = await setupCreditPlayer({
credit_limit: 100_000,
username: `e2e_partial_${Date.now()}`,
});
const drawNo = await fetchOpenDrawNo();
const adminSession = await adminLoginViaBypass();
const admin = await adminCtxOf(adminSession.accessToken);
const periodId = await openSettlementPeriod(admin, credit.admin_site_id);
const playerSession = await playerLoginViaBypass({
site_code: credit.site_code,
username: credit.username,
password: credit.password,
});
await runWalletDrawSettlement({
adminToken: adminSession.accessToken,
playerToken: playerSession.access_token,
drawNo,
betNumber: '1234',
publishWinNumber: '9999',
betAmount: 10_000,
expectWalletIncrease: false,
});
const close = await admin.post(`/api/v1/admin/settlement-periods/${periodId}/close`);
expect(close.ok()).toBeTruthy();
expect((await close.json()).code).toBe(0);
const bills = await admin.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 confirm = await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/confirm`);
expect(confirm.ok()).toBeTruthy();
const confirmBody = (await confirm.json()) as any;
expect(confirmBody.code).toBe(0);
expect(confirmBody.data.status).toBe('confirmed');
const billShow = await admin.get(`/api/v1/admin/settlement-bills/${playerBill.id}`);
const billDetail = (await billShow.json()) as any;
const unpaid = Math.abs(Number(billDetail.data.bill.unpaid_amount));
expect(unpaid).toBeGreaterThan(1);
const firstPay = Math.floor(unpaid / 2);
expect(firstPay).toBeGreaterThan(0);
const pay1 = await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/payments`, {
data: { amount: firstPay, method: 'e2e_partial_1', remark: 'e2e first half' },
});
expect(pay1.ok()).toBeTruthy();
const pay1Body = (await pay1.json()) as any;
expect(pay1Body.code).toBe(0);
expect(pay1Body.data.bill.status).toBe('partial_paid');
const ledgerAfterPay1 = await fetchBillCreditLedgerRows(
credit.player_id,
Number(playerBill.id),
'settlement_confirm',
);
expect(ledgerAfterPay1).toHaveLength(1);
expect(Number(ledgerAfterPay1[0].amount)).toBe(firstPay);
const midShow = await admin.get(`/api/v1/admin/settlement-bills/${playerBill.id}`);
const midDetail = (await midShow.json()) as any;
const secondPay = Math.abs(Number(midDetail.data.bill.unpaid_amount));
expect(secondPay).toBeGreaterThan(0);
const pay2 = await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/payments`, {
data: { amount: secondPay, method: 'e2e_partial_2', remark: 'e2e remainder' },
});
const pay2Text = await pay2.text();
expect(pay2.ok(), `pay2 status=${pay2.status()} body=${pay2Text.slice(0, 400)}`).toBeTruthy();
const pay2Body = JSON.parse(pay2Text) as any;
expect(pay2Body.data.bill.status).toBe('settled');
expect(Number(pay2Body.data.bill.unpaid_amount)).toBe(0);
const ledgerAfterPay2 = await fetchBillCreditLedgerRows(
credit.player_id,
Number(playerBill.id),
'settlement_confirm',
);
expect(ledgerAfterPay2).toHaveLength(1);
expect(Number(ledgerAfterPay2[0].amount)).toBe(unpaid);
const exceed = await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/payments`, {
data: { amount: 1, method: 'e2e_over', remark: 'should fail' },
});
const exceedBody = (await exceed.json()) as any;
expect(exceedBody.code).not.toBe(0);
await admin.dispose();
});
test('玩家赢单 confirm 后分两笔收付 → settlement_payout 累计', async () => {
test.setTimeout(300_000);
const winNumber = `6${String(Date.now()).slice(-3)}`;
const credit = await setupCreditPlayer({
credit_limit: 100_000,
username: `e2e_partial_win_${Date.now()}`,
});
const drawNo = await fetchOpenDrawNo();
const adminSession = await adminLoginViaBypass();
const admin = await adminCtxOf(adminSession.accessToken);
const periodId = await openSettlementPeriod(admin, credit.admin_site_id);
const playerSession = await playerLoginViaBypass({
site_code: credit.site_code,
username: credit.username,
password: credit.password,
});
await runWalletDrawSettlement({
adminToken: adminSession.accessToken,
playerToken: playerSession.access_token,
drawNo,
betNumber: winNumber,
betAmount: 10_000,
expectWalletIncrease: false,
});
const close = await admin.post(`/api/v1/admin/settlement-periods/${periodId}/close`);
expect(close.ok()).toBeTruthy();
const bills = await admin.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)).toBeLessThan(0);
await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/confirm`);
const billDetail = (await (await admin.get(`/api/v1/admin/settlement-bills/${playerBill.id}`)).json()) as any;
const unpaid = Math.abs(Number(billDetail.data.bill.unpaid_amount));
expect(unpaid).toBeGreaterThan(1);
const firstPay = Math.floor(unpaid / 2);
expect(firstPay).toBeGreaterThan(0);
const pay1 = await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/payments`, {
data: { amount: firstPay, method: 'e2e_win_partial_1', remark: 'win first half' },
});
expect(pay1.ok()).toBeTruthy();
expect((await pay1.json()).data.bill.status).toBe('partial_paid');
const ledgerAfterPay1 = await fetchBillCreditLedgerRows(
credit.player_id,
Number(playerBill.id),
'settlement_payout',
);
expect(ledgerAfterPay1).toHaveLength(1);
expect(Number(ledgerAfterPay1[0].amount)).toBe(firstPay);
const midDetail = (await (await admin.get(`/api/v1/admin/settlement-bills/${playerBill.id}`)).json()) as any;
const secondPay = Math.abs(Number(midDetail.data.bill.unpaid_amount));
const pay2 = await admin.post(`/api/v1/admin/settlement-bills/${playerBill.id}/payments`, {
data: { amount: secondPay, method: 'e2e_win_partial_2', remark: 'win remainder' },
});
expect(pay2.ok()).toBeTruthy();
expect((await pay2.json()).data.bill.status).toBe('settled');
const ledgerAfterPay2 = await fetchBillCreditLedgerRows(
credit.player_id,
Number(playerBill.id),
'settlement_payout',
);
expect(ledgerAfterPay2).toHaveLength(1);
expect(Number(ledgerAfterPay2[0].amount)).toBe(unpaid);
await admin.dispose();
});