实现 B-2b 统一报销工作台迁移(reimbursement-form-shared-component-migration)
这个提交包含在:
@@ -564,6 +564,75 @@ function scanNavigationTitleCoverage(file, source) {
|
||||
}
|
||||
}
|
||||
|
||||
const reimbursementWorkspaceShellFiles = new Set([
|
||||
"client/src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx",
|
||||
"client/src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx",
|
||||
]);
|
||||
|
||||
function scanReimbursementWorkspaceShellBoundary(file, source) {
|
||||
const normalized = normalizeFindingFile(file);
|
||||
if (!reimbursementWorkspaceShellFiles.has(normalized)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const patterns = [
|
||||
{ regex: /\buseQuery\b/g, evidence: "useQuery" },
|
||||
{ regex: /\buseMutation\b/g, evidence: "useMutation" },
|
||||
{ regex: /(?:from\s+["'][^"']*\/api\/|\bapiClient\b|\bapi\.(?:get|post|put|patch|delete)\b)/g, evidence: "API client" },
|
||||
{ regex: /\bfetch\s*\(/g, evidence: "fetch" },
|
||||
{ regex: /\b(?:hasPermission|roleCodes?|permissionCodes?)\b/g, evidence: "permission/role" },
|
||||
{ regex: /\b(?:workflowStatus|reimbursementStatus)\b/g, evidence: "business status" },
|
||||
{ regex: /\bmode\s*(?:={2,3}|!==?)\s*["']/g, evidence: "mode comparison" },
|
||||
{ regex: /\bisMobile\b/g, evidence: "isMobile branch" },
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
let match = pattern.regex.exec(source);
|
||||
while (match) {
|
||||
addFinding({
|
||||
rule: "architecture.reimbursement_workspace_shell_boundary",
|
||||
severity: "error",
|
||||
file,
|
||||
line: lineNumber(source, match.index),
|
||||
message: "报销工作台布局壳只能组合已计算模型和能力,不得查询 API、判断角色/业务模式或用移动端分支推导行为。",
|
||||
evidence: pattern.evidence,
|
||||
});
|
||||
match = pattern.regex.exec(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanLegacyWorkflowReimbursementBranch(file, source) {
|
||||
const normalized = normalizeFindingFile(file);
|
||||
if (!["client/src/pages/WorkflowTaskDetailPage.tsx", "client/src/pages/MobileApprovalsPage.tsx"].includes(normalized)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const patterns = [
|
||||
/\bReimbursementItemEvidenceList\b/g,
|
||||
/\b(?:isReimbursementTask|isSelectedReimbursement|reimbursementDecisionReady|reimbursementEvidence|reimbursementDetailQuery)\b/g,
|
||||
/\bbuildReimbursementReturnReference\b/g,
|
||||
/\bselectedTask\?*\.reimbursement\b/g,
|
||||
/data-testid=["']reimbursement-approval-(?:evidence|checks)["']/g,
|
||||
/(?:提交时检查|当前票据检查|打开完整原始表单)/g,
|
||||
];
|
||||
|
||||
for (const regex of patterns) {
|
||||
let match = regex.exec(source);
|
||||
while (match) {
|
||||
addFinding({
|
||||
rule: "architecture.no_legacy_workflow_reimbursement_branch",
|
||||
severity: "error",
|
||||
file,
|
||||
line: lineNumber(source, match.index),
|
||||
message: "通用审批详情不得保留报销证据、检查或决策就绪分支;报销只能在授权分派层进入工作台适配器。",
|
||||
evidence: match[0],
|
||||
});
|
||||
match = regex.exec(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanFileSource(file, source) {
|
||||
scanForbiddenCopy(file, source);
|
||||
scanCompetitorReferenceCopy(file, source);
|
||||
@@ -580,6 +649,8 @@ function scanFileSource(file, source) {
|
||||
scanReimbursementRawCodeDisplay(file, source);
|
||||
scanDuplicateBusinessIdentifierDisplay(file, source);
|
||||
scanNavigationTitleCoverage(file, source);
|
||||
scanReimbursementWorkspaceShellBoundary(file, source);
|
||||
scanLegacyWorkflowReimbursementBranch(file, source);
|
||||
}
|
||||
|
||||
function scanFile(file) {
|
||||
@@ -628,6 +699,30 @@ function runSelfTest() {
|
||||
expectedRules: [],
|
||||
expectedSuppressedRules: ["data.no_uuid_primary_display"],
|
||||
},
|
||||
{
|
||||
file: "client/src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx",
|
||||
source: `
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
export function ReimbursementDesktopWorkspaceShell({ mode, roleCodes }) {
|
||||
const query = useQuery({ queryKey: ["bad"] });
|
||||
void fetch("/api/reimbursements/bad");
|
||||
return mode === "approval" && roleCodes ? <div>{query.data}</div> : null;
|
||||
}
|
||||
`,
|
||||
expectedRules: ["architecture.reimbursement_workspace_shell_boundary"],
|
||||
expectedSuppressedRules: [],
|
||||
},
|
||||
{
|
||||
file: "client/src/pages/WorkflowTaskDetailPage.tsx",
|
||||
source: `
|
||||
import { ReimbursementItemEvidenceList } from "../components/reimbursements/ReimbursementItemEvidenceList";
|
||||
export function Fixture({ selectedTask }) {
|
||||
return selectedTask?.reimbursement ? <><ReimbursementItemEvidenceList evidence={reimbursementEvidence} />提交时检查</> : null;
|
||||
}
|
||||
`,
|
||||
expectedRules: ["architecture.no_legacy_workflow_reimbursement_branch"],
|
||||
expectedSuppressedRules: [],
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户