借款出借人线上确认借款并以站内通知直达(09-17-company-loan-principal-repayment)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

- 新增出借人确认声明:财务发起,出借人本人查看原件后确认或退回;快照与决定不可改删,金额、日期、原件或身份变化后整组失效
- 余额确认接受纸质协议或有效线上声明二选一,代账余额核对材料仍必需;声明与公司代表授权分离
- 普通员工无常驻入口,财务发起后仅通知对应出借人并直达确认页
- 新增迁移 20261010010000_loan_lender_acknowledgements(仅新增三表)
- 测试:单测、隔离 HTTP/浏览器、完整借款回归;独立只读复核无阻断;同步发布记录与操作手册

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
2026-10-10 15:22:12 +08:00
共同撰写人 Claude Opus 5.5
父节点 2063ff1e93
当前提交 4a5cd8f2b3
共修改 34 个文件,包含 1610 行新增和 41 行删除
+11 -1
查看文件
@@ -18,11 +18,16 @@ module.exports = async function relatedBrowser(h) {
try {
for (const s of relatedParty.ui) {
const mobile = s.mode === 'mobile'; const prefix = mobile ? '/mobile' : ''; const contexts = [];
const technicalFinanceRequests = [];
console.log(JSON.stringify({ stage: 'loan_related_browser_start', mode: s.mode }));
async function login(who, route = `${prefix}/company-loans`) {
const context = await browser.newContext({ viewport: mobile ? { width: 390, height: 844 } : { width: 1440, height: 1000 }, serviceWorkers: 'block' });
contexts.push(context); await context.route('**/*', r => r.request().url().startsWith(`${origin}/`) ? r.continue() : r.abort());
const p = await context.newPage();
if (who.id === ordinary.id) p.on('request', request => {
const pathname = new URL(request.url()).pathname;
if (/^\/api\/(company-loans|loan-lenders|loan-repayments|loan-related-authorizations)(\/|$)/.test(pathname)) technicalFinanceRequests.push(pathname);
});
await p.goto(`${origin}/login?redirect=${encodeURIComponent(route)}`);
await p.getByTestId('login-identifier').fill(who.email); await p.getByTestId('login-password').fill(password); await p.getByTestId('login-submit').click();
await p.waitForURL(url => !url.pathname.includes('login'));
@@ -237,7 +242,12 @@ module.exports = async function relatedBrowser(h) {
assert.equal(await mp.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false);
await mp.screenshot({ path: path.join(output, `related-${s.mode}-closed.png`), fullPage: true });
checks.push(`${s.mode}_related_evidenced_duplicate_dismissal_and_no_overflow`);
const tech = await login(ordinary); await tech.waitForURL(/\/403$/); await expect(tech.getByRole('heading', { name: '公司借款', exact: true })).toHaveCount(0);
const tech = await login(ordinary);
await expect(tech.getByRole('heading', { name: '出借人确认借款', exact: true })).toBeVisible();
await expect(tech.getByText('暂无可查看的出借人确认。', { exact: true })).toBeVisible();
await expect(tech.getByRole('tablist')).toHaveCount(0);
await expect(tech.getByRole('button', { name: /登记借入|登记出借人|申请还本|发起出借人确认|登记公司代表授权范围|代表公司确认授权范围|停止新付款授权/ })).toHaveCount(0);
assert.deepEqual(technicalFinanceRequests, [], 'technical employee own declarations must not load finance or company-authority data');
assert.equal(await prisma.notification.count({ where: { recipient_employee_id: ordinary.id, target_id: { in: [b.id, failed.id, old.id] } } }), 0);
// Stopping from the actual scope card uses the current grant only.
await mp.goto(`${origin}${prefix}/company-loans`);