fix: 部分收付累计释额并扩展结算 E2E 与 CI
账期收付改为按累计已付同步 credit_ledger,修复多笔 partial_paid 与坏账核销重复释额;新增 API E2E(占成、权限、部分收付/坏账)与 GitHub Actions e2e-api job。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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()}`,
|
||||
|
||||
Reference in New Issue
Block a user