借款出借人线上确认借款并以站内通知直达(09-17-company-loan-principal-repayment)
- 新增出借人确认声明:财务发起,出借人本人查看原件后确认或退回;快照与决定不可改删,金额、日期、原件或身份变化后整组失效 - 余额确认接受纸质协议或有效线上声明二选一,代账余额核对材料仍必需;声明与公司代表授权分离 - 普通员工无常驻入口,财务发起后仅通知对应出借人并直达确认页 - 新增迁移 20261010010000_loan_lender_acknowledgements(仅新增三表) - 测试:单测、隔离 HTTP/浏览器、完整借款回归;独立只读复核无阻断;同步发布记录与操作手册 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -10,6 +10,7 @@ module.exports = async function reconciliationBrowser({ browser, origin, output,
|
||||
for (const mobile of [false, true]) {
|
||||
const mode = mobile ? 'mobile' : 'desktop';
|
||||
let view;
|
||||
const uploadedByKind = new Map();
|
||||
async function login(who, batch) {
|
||||
if (view) await view.context.close();
|
||||
const context = await browser.newContext({ viewport: mobile ? { width: 390, height: 844 } : { width: 1440, height: 1000 }, serviceWorkers: 'block' });
|
||||
@@ -28,22 +29,34 @@ module.exports = async function reconciliationBrowser({ browser, origin, output,
|
||||
const dialog = view.page.getByRole('dialog');
|
||||
if (fill) await fill(dialog);
|
||||
if (await dialog.getByLabel('依据与说明').count()) await dialog.getByLabel('依据与说明').fill('合成银行材料唯一定位本批次并独立核对全部指令');
|
||||
const invalid = await dialog.locator('input:invalid, select:invalid, textarea:invalid').evaluateAll(elements =>
|
||||
elements.map(el => el.getAttribute('aria-label') ?? el.labels?.[0]?.textContent?.trim().slice(0, 80) ?? el.tagName));
|
||||
assert.deepEqual(invalid, [], `synthetic UI ${name}: invalid form controls`);
|
||||
await dialog.getByRole('button', { name: '确认', exact: true }).click();
|
||||
try { await expect(dialog).not.toBeVisible(); }
|
||||
catch { throw new Error(`synthetic UI ${name}: ${await dialog.getByRole('alert').allTextContents()}`); }
|
||||
catch {
|
||||
await view.page.screenshot({ path: path.join(output, `${mode}-reconciliation-failure.png`), fullPage: true });
|
||||
throw new Error(`synthetic UI ${name}: ${await dialog.getByRole('alert').allTextContents()}; submit=${await dialog.locator('button[type="submit"]').textContent()}`);
|
||||
}
|
||||
}
|
||||
async function review(name = '独立复核银行事实') {
|
||||
await action(name, d => d.getByLabel('复核结论').selectOption('approve'));
|
||||
}
|
||||
async function upload(kind) {
|
||||
const uploaded = view.page.waitForResponse(response => new URL(response.url()).pathname.endsWith('/loan-terminal-evidences') &&
|
||||
response.request().method() === 'POST');
|
||||
await action('上传银行证据', async d => {
|
||||
await d.getByLabel('证据类别').selectOption(kind);
|
||||
await d.getByLabel('证据文件').setInputFiles({ name: `${kind}.pdf`, mimeType: 'application/pdf', buffer: await fixtures.bankLetterPdf() });
|
||||
});
|
||||
const response = await uploaded; assert.equal(response.status(), 200);
|
||||
uploadedByKind.set(kind, (await response.json()).data.id);
|
||||
}
|
||||
async function bankFields(d, s, amount, reference, incoming = false) {
|
||||
const options = d.getByLabel('对应的成功银行回单').locator('option');
|
||||
await d.getByLabel('对应的成功银行回单').selectOption({ index: (await options.count()) - 1 });
|
||||
const evidenceId = uploadedByKind.get(incoming ? 'incoming_return' : 'late_success');
|
||||
assert.ok(evidenceId, 'use the specific uploaded document, never the placeholder or an older option');
|
||||
await d.getByLabel('对应的成功银行回单').selectOption(evidenceId);
|
||||
await expect(d.getByLabel('对应的成功银行回单')).toHaveValue(evidenceId);
|
||||
await d.getByLabel('回单付款账号').fill(incoming ? s.payee : s.payer);
|
||||
await d.getByLabel('回单收款账号').fill(incoming ? s.payer : s.payee);
|
||||
await d.getByLabel('回单人民币金额').fill(amount);
|
||||
|
||||
在新工单中引用
屏蔽一个用户