AutoRpa/rpa_contract/contract_upload.py

198 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 合同 S11:挂合同正文。仅 YYC 传合并 PDF;提交前无文件则用占位 PDF。
#
# dry_run 且没有文件时跳过。
from __future__ import annotations
import time
from pathlib import Path
from accept import evidence_shot
from errors import NodeError
from contract_common import (
BrowserAdapter,
ContractFlowContext,
_ACCEPT,
_close_blocking_dialogs,
)
# 决定要挂的合同正文路径。提交且无文件时用占位 PDF。
def _resolve_contract_body_path(ctx: ContractFlowContext) -> str:
path = (ctx.contract_document_path or "").strip()
if not path and ctx.source_type == "YYC":
path = (ctx.merged_pdf_path or "").strip()
if path:
return path
if ctx.submit_mode == "dry_run":
return ""
from pdf_utils import ensure_placeholder_contract_pdf
return ensure_placeholder_contract_pdf()
# 挂合同正文(S11)。dry_run 且没有文件则跳过。
def upload_contract_document_if_needed(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
path = _resolve_contract_body_path(ctx)
if not path:
evidence_shot(browser, "contract-pdf-upload-skipped")
_ACCEPT.note("S11", "上传正文", {"skipped": "dry_run_no_file"}, ok=True)
return
try:
_upload_contract_body(browser, path)
except Exception as exc:
_ACCEPT.note("S11", "上传正文", {"attached": "no", "error": exc}, ok=False, raise_error=False)
raise
evidence_shot(browser, "contract-body-uploaded")
attached = _contract_body_attached(browser, path)
_ACCEPT.check(
"S11",
"上传正文",
[("attached", "yes" if attached else "no", "yes"), ("file", Path(path).name, Path(path).name)],
browser=browser,
screenshot_on_fail="contract-body-uploaded",
)
# 打开合同正文区,点添加合同,把本地文件塞进 file input。
def _upload_contract_body(browser: BrowserAdapter, file_path: str) -> None:
from pathlib import Path
if not Path(file_path).is_file():
raise NodeError(f"合同正文文件不存在:{file_path}")
_close_blocking_dialogs(browser)
_reveal_contract_body_section(browser)
evidence_shot(browser, "contract-body-tab")
if _contract_body_attached(browser, file_path):
evidence_shot(browser, "contract-body-uploaded")
return
_click_add_contract_body(browser)
time.sleep(0.8)
try:
browser.click_native(["text=打开本地初稿", "text=本地初稿", "text=上传本地文件"])
time.sleep(0.8)
except Exception:
pass
browser.upload_file_first(
[
'input[name="_apply_filemainOnline"]',
'input[name="__landray_filemainOnline"]',
'input[name*="filemainOnline"]',
'input[name*="fdMainOnline"]',
'.lui_dialog input[type="file"]',
'input[type="file"]',
],
file_path,
)
deadline = time.time() + 12
while time.time() < deadline:
if _contract_body_attached(browser, file_path) or _on_body_editor_tab(browser):
evidence_shot(browser, "contract-body-uploaded")
return
time.sleep(0.6)
evidence_shot(browser, "contract-body-uploaded", failed=True)
raise NodeError("合同正文未挂上:页面仍是「+添加合同」,提交会被系统拒绝")
# 滚到或点开「合同正文」区域,露出「+添加合同」。
def _reveal_contract_body_section(browser: BrowserAdapter) -> None:
browser.execute_script(
"""
const labelOf = (el) => (el.textContent || '').replace(/\\s+/g, '').trim();
const addBtn = Array.from(document.querySelectorAll('a, span, div, td')).find((el) => {
const text = labelOf(el);
return text === '+添加合同' || text === '添加合同';
});
if (addBtn) {
addBtn.scrollIntoView({block: 'center'});
return {ok: true, via: 'add'};
}
const hits = Array.from(document.querySelectorAll('div, li, span, a')).filter((el) => labelOf(el) === '合同正文');
for (const el of hits) {
const group = (el.parentElement && el.parentElement.parentElement) || el;
if (labelOf(group).indexOf('签约方信息') >= 0) {
(el.closest('li') || el).click();
return {ok: true, via: 'tabbar'};
}
}
return {ok: false};
"""
)
time.sleep(0.6)
# 点「+添加合同」打开本地初稿入口。
def _click_add_contract_body(browser: BrowserAdapter) -> None:
clicked = browser.execute_script(
"""
const labelOf = (el) => (el.textContent || '').replace(/\\s+/g, '').trim();
const hit = Array.from(document.querySelectorAll('a, span, div, td, button')).find((el) => {
const text = labelOf(el);
return (text === '+添加合同' || text === '添加合同') && el.children.length <= 1;
});
if (!hit) return {ok: false};
hit.scrollIntoView({block: 'center'});
hit.click();
return {ok: true};
"""
)
if isinstance(clicked, dict) and clicked.get("ok"):
return
browser.click_native(["text=+添加合同", "text=添加合同"])
# 看合同正文是否已经挂上:文件名、office iframe 或 file input 有文件。
def _contract_body_attached(browser: BrowserAdapter, file_path: str) -> bool:
from pathlib import Path
stem = Path(file_path).name
result = browser.execute_script(
"""
const wanted = String(arguments[0] || '');
const wantedStem = wanted.replace(/\\.[^.]+$/, '');
const fileInputs = Array.from(document.querySelectorAll('input[type="file"]'));
if (fileInputs.some((el) => el.files && el.files.length > 0)) {
return {ok: true, via: 'input'};
}
const frames = Array.from(document.querySelectorAll('iframe, object, embed'));
const office = frames.find((el) => {
const hint = ((el.src || '') + ' ' + (el.id || '') + ' ' + (el.className || '')).toLowerCase();
const box = el.getBoundingClientRect();
return box.width > 80 && box.height > 80 && /office|ntko|wps|kingsoft|weboffice|filemain|online|pdf/.test(hint);
});
if (office) return {ok: true, via: 'iframe'};
const rows = Array.from(document.querySelectorAll('tr'));
for (const row of rows) {
const cells = Array.from(row.querySelectorAll('td, th'));
if (!cells.length) continue;
const head = (cells[0].innerText || '').replace(/\\s+/g, '').trim();
if (head !== '合同正文') continue;
const body = (row.innerText || '').replace(/\\s+/g, ' ').trim();
if (wanted && body.indexOf(wanted) >= 0) return {ok: true, via: 'name'};
if (wantedStem && body.indexOf(wantedStem) >= 0) return {ok: true, via: 'stem'};
if (/\\.(pdf|doc|docx|wps)/i.test(body)) return {ok: true, via: 'ext'};
if (body.indexOf('添加合同') >= 0 && body.indexOf('打开本地初稿') < 0) return {ok: false, via: 'empty'};
}
const page = (document.body.innerText || '');
if (wanted && page.indexOf(wanted) >= 0) return {ok: true, via: 'page'};
return {ok: false, via: 'missing'};
""",
stem,
)
return isinstance(result, dict) and bool(result.get("ok"))
# 底部「合同正文」在线编辑页:文件进编辑器后不一定回显文件名。
def _on_body_editor_tab(browser: BrowserAdapter) -> bool:
result = browser.execute_script(
"""
const page = (document.body.innerText || '').replace(/\\s+/g, '');
return page.indexOf('打开本地初稿') >= 0 && page.indexOf('添加合同') < 0;
"""
)
return result is True