fix: 部分收付累计释额并扩展结算 E2E 与 CI
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled

账期收付改为按累计已付同步 credit_ledger,修复多笔 partial_paid 与坏账核销重复释额;新增 API E2E(占成、权限、部分收付/坏账)与 GitHub Actions e2e-api job。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 09:48:01 +08:00
parent 6ec9634704
commit 3f29229499
23 changed files with 1251 additions and 111 deletions

View File

@@ -144,6 +144,93 @@ export async function setupCreditPlayer(opts?: { credit_limit?: number; username
return data.data;
}
export async function setupSiteOperator(opts?: {
role_slug?: string;
username?: string;
password?: string;
}): Promise<{
admin_user_id: number;
username: string;
password: string;
role_slug: string;
admin_site_id: number;
site_code: string;
}> {
const data = await e2e<any>('POST', '/site-operator/setup', opts ?? {});
return data.data;
}
export async function adminLoginWithAccount(
account: string,
password: string,
): Promise<ReturnType<typeof adminLogin>> {
const captcha = await fetchAdminCaptcha();
const ctx = await pwRequest.newContext();
const resp = await ctx.post('/api/v1/admin/auth/login', {
data: {
account,
password,
captcha_key: captcha.captcha_key,
captcha_code: 'LOTTERY_E2E_BYPASS',
},
});
expect(resp.ok(), `admin login failed: ${resp.status()}`).toBeTruthy();
const body = (await resp.json()) as { data: any };
await ctx.dispose();
if (!body.data?.token) throw new Error('admin login returned no token: ' + JSON.stringify(body));
return {
accessToken: body.data.token,
tokenType: body.data.token_type ?? 'Bearer',
expiresIn: 0,
admin: body.data.admin,
};
}
/** 以当前时间为中心的 2h 账期窗口;开账前会清掉同站历史账期避免重叠。 */
export function periodWindowIso(): { start: string; end: string } {
const start = new Date(Date.now() - 3600_000).toISOString().replace('T', ' ').slice(0, 19);
const end = new Date(Date.now() + 3600_000).toISOString().replace('T', ' ').slice(0, 19);
return { start, end };
}
export async function resetSiteSettlement(siteCode?: string): Promise<void> {
await e2e('POST', '/settlement/reset', { site_code: siteCode ?? process.env.E2E_PLAYER_SITE_CODE ?? 'demo' });
}
export async function fetchBillCreditLedgerRows(
playerId: number,
billId: number,
reason: 'settlement_confirm' | 'settlement_payout',
): Promise<Array<{ id: number; amount: number; reason: string; ref_type: string; ref_id: number }>> {
const ledger = await e2e<any>('GET', `/inspect/credit-ledger?player_id=${playerId}&limit=50`);
return (ledger.data.rows as any[]).filter(
(row) =>
row.reason === reason &&
row.ref_type === 'settlement_bill' &&
Number(row.ref_id) === billId,
);
}
export async function openSettlementPeriod(
admin: APIRequestContext,
adminSiteId: number,
siteCode?: string,
): Promise<number> {
await resetSiteSettlement(siteCode);
const { start, end } = periodWindowIso();
const open = await admin.post('/api/v1/admin/settlement-periods', {
data: {
admin_site_id: adminSiteId,
period_start: start,
period_end: end,
},
});
expect(open.ok(), `open period status=${open.status()}`).toBeTruthy();
const openBody = (await open.json()) as any;
expect(openBody.code).toBe(0);
return Number(openBody.data.id);
}
export async function mintSsoJwt(sitePlayerId?: string): Promise<{ jwt: string; site_player_id: string }> {
const data = await e2e<any>('POST', '/sso/mint-jwt', {
site_player_id: sitePlayerId ?? `e2e-sso-${Date.now()}`,