607 lines
23 KiB
Python
607 lines
23 KiB
Python
# 合同 S13:按 fdId 打开详情读正式合同号。
|
||
#
|
||
# fdId 优先于标题+客户编码。禁止盲取「我的合同」最新一条。
|
||
|
||
from __future__ import annotations
|
||
|
||
import time
|
||
|
||
from contract_common import (
|
||
BrowserAdapter,
|
||
ContractFlowContext,
|
||
_expected_title,
|
||
_is_logged_in,
|
||
_open_submitted_view,
|
||
_read_document_id,
|
||
_usable_contract_no,
|
||
)
|
||
from contract_login import login_contract_system
|
||
|
||
|
||
# 读正式合同号(S13)。没有 fdId 则返回空字符串。
|
||
def read_contract_number(
|
||
browser: BrowserAdapter, ctx: ContractFlowContext | None = None, *, fd_id: str = ""
|
||
) -> str:
|
||
no, _via = _read_contract_number_routed(browser, ctx, fd_id=fd_id)
|
||
return no
|
||
|
||
|
||
# 按 fdId 打开详情读号。读不到返回空,不回列表盲取最新一条。
|
||
def _read_contract_number_routed(
|
||
browser: BrowserAdapter, ctx: ContractFlowContext | None = None, *, fd_id: str = ""
|
||
) -> tuple[str, str]:
|
||
del ctx
|
||
doc_id = (fd_id or "").strip() or _read_document_id(browser)
|
||
if not doc_id:
|
||
print("S13 没有 fdId,无法打开详情", flush=True)
|
||
return "", "none"
|
||
print(f"S13:开详情读号 fdId={doc_id}", flush=True)
|
||
no = _read_number_by_fd_id(browser, doc_id)
|
||
if no:
|
||
return no, "docNumber"
|
||
print("S13 详情未读到合同编号", flush=True)
|
||
return "", "none"
|
||
|
||
|
||
# 从详情页 #_xform_docNumber 读合同编号。占位符和手机号不算。
|
||
def _read_contract_number_on_page(browser: BrowserAdapter) -> str:
|
||
try:
|
||
browser.switch_to_default_content()
|
||
except Exception:
|
||
pass
|
||
scanned = browser.execute_script(
|
||
"""
|
||
const skip = ['提交时自动生成', '自动生成', '==请选择==', '合同编号', '合同标题'];
|
||
function usable(raw) {
|
||
const v = String(raw || '').replace(/\\s+/g, ' ').trim();
|
||
if (!v || skip.indexOf(v) >= 0) return '';
|
||
return v;
|
||
}
|
||
function fromDoc(doc) {
|
||
const node = doc.querySelector('#_xform_docNumber');
|
||
if (node) {
|
||
const typed = node.querySelector('input, textarea');
|
||
const v = usable(typed ? typed.value : (node.innerText || node.textContent || ''));
|
||
if (v) return v;
|
||
}
|
||
const input = doc.querySelector('input[name="docNumber"]');
|
||
if (input) {
|
||
const v = usable(input.value || '');
|
||
if (v) return v;
|
||
}
|
||
return '';
|
||
}
|
||
function walk(win) {
|
||
try {
|
||
const no = fromDoc(win.document);
|
||
if (no) return no;
|
||
} catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try {
|
||
const no = walk(win.frames[i]);
|
||
if (no) return no;
|
||
} catch (e) {}
|
||
}
|
||
return '';
|
||
}
|
||
return walk(window);
|
||
"""
|
||
)
|
||
if isinstance(scanned, str) and _usable_contract_no(scanned.strip()):
|
||
return scanned.strip()
|
||
if scanned:
|
||
print(f"S13 详情页扫到不可用值={scanned!r}", flush=True)
|
||
return ""
|
||
|
||
|
||
# 把当前页导航回合同系统主界面,等到出现「我的合同」。
|
||
def _reload_contract_home(browser: BrowserAdapter, home: str) -> bool:
|
||
try:
|
||
browser.switch_to_default_content()
|
||
except Exception:
|
||
pass
|
||
print("刷新回合同主界面:", home, flush=True)
|
||
try:
|
||
browser.execute_script(
|
||
"try { window.top.location.href = arguments[0]; } catch (e) { location.href = arguments[0]; } return true;",
|
||
home,
|
||
)
|
||
except Exception:
|
||
pass
|
||
try:
|
||
browser.open_url(home)
|
||
except Exception as exc:
|
||
print(f"open_url 主界面失败:{exc}", flush=True)
|
||
return _wait_text_in_frames(browser, "我的合同", timeout_seconds=12)
|
||
|
||
|
||
# 刷新主界面失败时关页重开;掉登录则再登一次。
|
||
def _reopen_contract_home(browser: BrowserAdapter, ctx: ContractFlowContext, home: str) -> bool:
|
||
print("主界面刷新失败,关闭当前页后重开", flush=True)
|
||
reopen = getattr(browser, "reopen_url", None)
|
||
if callable(reopen):
|
||
reopen(home)
|
||
else:
|
||
browser.open_url(home)
|
||
if _wait_text_in_frames(browser, "我的合同", timeout_seconds=12):
|
||
return True
|
||
if not _is_logged_in(browser):
|
||
login_contract_system(browser, ctx)
|
||
browser.open_url(home)
|
||
return _wait_text_in_frames(browser, "我的合同", timeout_seconds=12)
|
||
|
||
|
||
# 回到主界面并点开「我的合同 / 我起草的」。
|
||
def _open_my_contracts(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||
home = (ctx.contract_url or "").strip() or "http://contract-test.gzmpc.com:8080/"
|
||
if not _reload_contract_home(browser, home):
|
||
_reopen_contract_home(browser, ctx, home)
|
||
if not _click_my_contracts_sidebar(browser):
|
||
print("未点到侧栏「我的合同」,关闭页面后重开", flush=True)
|
||
_reopen_contract_home(browser, ctx, home)
|
||
_click_my_contracts_sidebar(browser)
|
||
_wait_text_in_frames(browser, "我起草的", timeout_seconds=10)
|
||
_click_by_exact_text(browser, "我起草的")
|
||
_wait_text_in_frames(browser, "申请时间", timeout_seconds=10)
|
||
_wait_text_in_frames(browser, "合同编号", timeout_seconds=10)
|
||
|
||
|
||
# 点侧栏「我的合同」。点到后等到「我起草的」。
|
||
def _click_my_contracts_sidebar(browser: BrowserAdapter) -> bool:
|
||
clicked = browser.execute_script(
|
||
"""
|
||
const label = '我的合同';
|
||
function clickIn(win) {
|
||
const nodes = Array.from(win.document.querySelectorAll('a, span, div, li, p, em, td'));
|
||
const hits = nodes.filter((el) => (el.textContent || '').trim() === label);
|
||
hits.sort((a, b) => {
|
||
const ax = a.getBoundingClientRect().left;
|
||
const bx = b.getBoundingClientRect().left;
|
||
if (ax !== bx) return ax - bx;
|
||
return a.children.length - b.children.length;
|
||
});
|
||
if (hits.length) { hits[0].click(); return true; }
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try { if (clickIn(win.frames[i])) return true; } catch (e) {}
|
||
}
|
||
return false;
|
||
}
|
||
return clickIn(window);
|
||
"""
|
||
)
|
||
if clicked is True:
|
||
return _wait_text_in_frames(browser, "我起草的", timeout_seconds=8)
|
||
_click_by_exact_text(browser, "我的合同")
|
||
return _wait_text_in_frames(browser, "我起草的", timeout_seconds=8)
|
||
|
||
|
||
# 在当前页和 iframe 里等到出现指定文本。超时返回 False。
|
||
def _wait_text_in_frames(browser: BrowserAdapter, text: str, timeout_seconds: int = 15) -> bool:
|
||
deadline = time.time() + timeout_seconds
|
||
while time.time() < deadline:
|
||
if _page_has_text(browser, text):
|
||
return True
|
||
time.sleep(0.3)
|
||
return False
|
||
|
||
|
||
# 当前页或任一 iframe 正文是否含有指定文本。
|
||
def _page_has_text(browser: BrowserAdapter, text: str) -> bool:
|
||
found = browser.execute_script(
|
||
"""
|
||
const wanted = String(arguments[0] || '');
|
||
function hasTextInFrames(win) {
|
||
try {
|
||
const body = (win.document && win.document.body && win.document.body.innerText) || '';
|
||
if (body.indexOf(wanted) >= 0) return true;
|
||
} catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try { if (hasTextInFrames(win.frames[i])) return true; } catch (e) {}
|
||
}
|
||
return false;
|
||
}
|
||
return hasTextInFrames(window);
|
||
""",
|
||
text,
|
||
)
|
||
return found is True
|
||
|
||
|
||
# 按可见文本精确点击。叶子节点优先。
|
||
def _click_by_exact_text(browser: BrowserAdapter, label: str) -> None:
|
||
clicked = browser.execute_script(
|
||
"""
|
||
const label = String(arguments[0] || '');
|
||
function clickIn(doc) {
|
||
const nodes = Array.from(doc.querySelectorAll('div, a, span, li, p, em, td, th, button'));
|
||
const leaf = nodes.find((el) => (el.textContent || '').trim() === label && el.children.length === 0);
|
||
if (leaf) { leaf.click(); return true; }
|
||
const parent = nodes.find((el) => (el.textContent || '').trim() === label);
|
||
if (parent) { parent.click(); return true; }
|
||
return false;
|
||
}
|
||
function clickInFrames(win) {
|
||
try { if (clickIn(win.document)) return true; } catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try { if (clickInFrames(win.frames[i])) return true; } catch (e) {}
|
||
}
|
||
return false;
|
||
}
|
||
return clickInFrames(window) ? label : '';
|
||
""",
|
||
label,
|
||
)
|
||
if clicked != label:
|
||
try:
|
||
browser.click_first([f"text={label}"])
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
# 按申请时间排序「我的合同」列表。
|
||
def _sort_my_contracts_by_apply_time(browser: BrowserAdapter) -> None:
|
||
_click_by_exact_text(browser, "申请时间")
|
||
browser.execute_script(
|
||
"""
|
||
const latestByApplyTime = true;
|
||
function textOf(el) { return (el.textContent || '').replace(/\\s+/g, ' ').trim(); }
|
||
function sortIn(doc) {
|
||
const desc = Array.from(doc.querySelectorAll('[title], a, span, i, em')).find((el) => {
|
||
const t = (el.getAttribute('title') || el.textContent || '').trim();
|
||
return t === '降序' || t === '从晚到早' || t === '倒序';
|
||
});
|
||
if (desc) { desc.click(); return 'desc'; }
|
||
const bar = Array.from(doc.querySelectorAll('div, span')).find((el) => {
|
||
const t = textOf(el);
|
||
return t.indexOf('更多排序') >= 0 && t.length < 80;
|
||
});
|
||
if (bar) {
|
||
const downs = Array.from(bar.querySelectorAll('i, em, span, a')).filter((el) => {
|
||
const t = (el.textContent || '').trim();
|
||
const cls = String(el.className || '');
|
||
return t === '∨' || t === 'v' || t === '↓' || t === '▼'
|
||
|| cls.indexOf('down') >= 0 || cls.indexOf('desc') >= 0;
|
||
});
|
||
if (downs.length) { downs[downs.length - 1].click(); return 'arrow'; }
|
||
}
|
||
return '';
|
||
}
|
||
function walk(win) {
|
||
try {
|
||
const hit = sortIn(win.document);
|
||
if (hit) return hit;
|
||
} catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try {
|
||
const hit = walk(win.frames[i]);
|
||
if (hit) return hit;
|
||
} catch (e) {}
|
||
}
|
||
return '';
|
||
}
|
||
return walk(window) || (latestByApplyTime ? 'apply-time' : '');
|
||
"""
|
||
)
|
||
_wait_text_in_frames(browser, "合同编号", timeout_seconds=10)
|
||
|
||
|
||
# 刮「我的合同」第一行可用合同号。给排查用,正式读号不走这里。
|
||
def _scrape_latest_my_contract_number(browser: BrowserAdapter) -> str:
|
||
scanned = browser.execute_script(
|
||
"""
|
||
const latestByApplyTime = true;
|
||
const skip = ['提交时自动生成', '自动生成', '==请选择=='];
|
||
function scrape(doc) {
|
||
const rows = Array.from(doc.querySelectorAll('tr'));
|
||
let headerIdx = null;
|
||
for (const tr of rows) {
|
||
const cells = Array.from(tr.querySelectorAll('th, td')).map((c) =>
|
||
(c.innerText || '').replace(/\\s+/g, ' ').trim()
|
||
);
|
||
if (!headerIdx && cells.indexOf('合同编号') >= 0) {
|
||
headerIdx = {};
|
||
cells.forEach((h, i) => { if (h && headerIdx[h] === undefined) headerIdx[h] = i; });
|
||
continue;
|
||
}
|
||
if (!headerIdx) continue;
|
||
const no = (cells[headerIdx['合同编号']] || '').trim();
|
||
if (!no || skip.indexOf(no) >= 0) continue;
|
||
return latestByApplyTime ? no : no;
|
||
}
|
||
return '';
|
||
}
|
||
function walk(win) {
|
||
try {
|
||
const no = scrape(win.document);
|
||
if (no) return no;
|
||
} catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try {
|
||
const no = walk(win.frames[i]);
|
||
if (no) return no;
|
||
} catch (e) {}
|
||
}
|
||
return '';
|
||
}
|
||
return walk(window);
|
||
"""
|
||
)
|
||
if isinstance(scanned, str) and _usable_contract_no(scanned.strip()):
|
||
return scanned.strip()
|
||
return ""
|
||
|
||
|
||
# 从列表行里按 fdId、标题+客户编码挑合同号。fdId 优先。
|
||
def _pick_contract_row(
|
||
rows: list[dict],
|
||
*,
|
||
fd_id: str = "",
|
||
title: str = "",
|
||
customer_code: str = "",
|
||
) -> str:
|
||
fd_id = (fd_id or "").strip()
|
||
title = (title or "").strip()
|
||
customer_code = (customer_code or "").strip()
|
||
if fd_id:
|
||
for row in rows:
|
||
href = str(row.get("href") or "")
|
||
no = str(row.get("no") or "").strip()
|
||
if fd_id in href and _usable_contract_no(no):
|
||
return no
|
||
if title and customer_code:
|
||
for row in rows:
|
||
row_title = str(row.get("title") or "")
|
||
row_cust = str(row.get("customer") or "")
|
||
no = str(row.get("no") or "").strip()
|
||
if title in row_title and customer_code in row_cust and _usable_contract_no(no):
|
||
return no
|
||
if title:
|
||
for row in rows:
|
||
no = str(row.get("no") or "").strip()
|
||
if title in str(row.get("title") or "") and _usable_contract_no(no):
|
||
return no
|
||
return ""
|
||
|
||
|
||
# 在「我的合同」查询里填标题和客户编码并点搜索。
|
||
def _search_my_contracts(browser: BrowserAdapter, title: str, customer_code: str) -> None:
|
||
title = (title or "").strip()
|
||
customer_code = (customer_code or "").strip()
|
||
if not title and not customer_code:
|
||
return
|
||
browser.execute_script(
|
||
"""
|
||
const title = String(arguments[0] || '');
|
||
const customer = String(arguments[1] || '');
|
||
function setValue(el, value) {
|
||
if (!el) return false;
|
||
el.focus();
|
||
el.value = value;
|
||
el.dispatchEvent(new Event('input', {bubbles: true}));
|
||
el.dispatchEvent(new Event('change', {bubbles: true}));
|
||
return true;
|
||
}
|
||
function fillNearLabel(doc, label, value) {
|
||
if (!value) return false;
|
||
const nodes = Array.from(doc.querySelectorAll('td, th, span, label, div, em'));
|
||
for (const node of nodes) {
|
||
const text = (node.innerText || '').replace(/\\s+/g, '').trim();
|
||
if (text !== label && text !== label + ':' && text !== label + ':') continue;
|
||
const box = node.parentElement || node;
|
||
const input = box.querySelector('input[type="text"], input:not([type]), input[type="search"]');
|
||
if (setValue(input, value)) return true;
|
||
const next = node.nextElementSibling;
|
||
if (next && setValue(next.querySelector('input'), value)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
function fillPlaceholder(doc, hint, value) {
|
||
if (!value) return false;
|
||
const inputs = Array.from(doc.querySelectorAll('input'));
|
||
const el = inputs.find((i) => (i.getAttribute('placeholder') || '').indexOf(hint) >= 0);
|
||
if (!el) return false;
|
||
setValue(el, value);
|
||
const btn = el.parentElement && el.parentElement.querySelector('a, button, span, i, em');
|
||
if (btn) btn.click();
|
||
return true;
|
||
}
|
||
function clickSearch(doc) {
|
||
const labels = ['搜索', '查询'];
|
||
const nodes = Array.from(doc.querySelectorAll('a, div, span, button, input'));
|
||
for (const label of labels) {
|
||
const hit = nodes.find((el) => {
|
||
const text = (el.textContent || el.value || '').trim();
|
||
return text === label && (!el.children || el.children.length === 0);
|
||
});
|
||
if (hit) { hit.click(); return true; }
|
||
}
|
||
return false;
|
||
}
|
||
function walk(win) {
|
||
try {
|
||
fillPlaceholder(win.document, '合同(协议)全称', title)
|
||
|| fillPlaceholder(win.document, '合同标题', title)
|
||
|| fillNearLabel(win.document, '合同标题', title)
|
||
|| fillNearLabel(win.document, '合同(协议)全称', title);
|
||
fillPlaceholder(win.document, '客户编码', customer)
|
||
|| fillNearLabel(win.document, '客户编码', customer);
|
||
clickSearch(win.document);
|
||
} catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try { walk(win.frames[i]); } catch (e) {}
|
||
}
|
||
}
|
||
walk(window);
|
||
return true;
|
||
""",
|
||
title,
|
||
customer_code,
|
||
)
|
||
_wait_text_in_frames(browser, "合同编号", timeout_seconds=10)
|
||
|
||
|
||
# 刮「我的合同」表格:编号、标题、客户、带 fdId 的链接。
|
||
def _scrape_my_contract_rows(browser: BrowserAdapter) -> list[dict]:
|
||
raw = browser.execute_script(
|
||
"""
|
||
function scrapeTable(doc) {
|
||
const rows = Array.from(doc.querySelectorAll('tr'));
|
||
let header = null;
|
||
const found = [];
|
||
for (const tr of rows) {
|
||
const cells = Array.from(tr.querySelectorAll('th, td')).map((c) =>
|
||
(c.innerText || '').replace(/\\s+/g, ' ').trim()
|
||
);
|
||
if (!header && cells.indexOf('合同编号') >= 0) {
|
||
header = {};
|
||
cells.forEach((h, i) => { if (h && header[h] === undefined) header[h] = i; });
|
||
continue;
|
||
}
|
||
if (!header) continue;
|
||
const no = (cells[header['合同编号']] || '').trim();
|
||
const title = (
|
||
cells[header['合同标题']]
|
||
|| cells[header['合同(协议)全称']]
|
||
|| cells[header['合同名称']]
|
||
|| cells[header['主题']]
|
||
|| cells[header['标题']]
|
||
|| ''
|
||
).trim();
|
||
const customer = (
|
||
cells[header['客户编码']] || cells[header['客户名称']] || cells[header['客商编码']] || ''
|
||
).trim();
|
||
const link = tr.querySelector('a[href*="fdId"], a[href*="fdid"]');
|
||
const box = tr.querySelector('input[name="List_Selected"]');
|
||
const href = link
|
||
? String(link.getAttribute('href') || '')
|
||
: (box ? String(box.value || '') : (tr.innerHTML || ''));
|
||
if (!no && !href) continue;
|
||
found.push({no: no, title: title, customer: customer, href: href});
|
||
}
|
||
return found;
|
||
}
|
||
function scrapeLinks(doc) {
|
||
const found = [];
|
||
const links = Array.from(doc.querySelectorAll('a[href*="fdId"], a[href*="fdid"]'));
|
||
for (const a of links) {
|
||
const href = String(a.getAttribute('href') || '');
|
||
const tr = a.closest('tr') || a.parentElement;
|
||
const blob = ((tr && tr.innerText) || a.innerText || '').replace(/\\s+/g, ' ').trim();
|
||
const hit = blob.match(/HT[0-9A-Za-z-]+/) || blob.match(/\\b\\d{10,}\\b/);
|
||
found.push({
|
||
no: hit ? hit[0] : '',
|
||
title: blob,
|
||
customer: blob,
|
||
href: href
|
||
});
|
||
}
|
||
return found;
|
||
}
|
||
function walk(win) {
|
||
let all = [];
|
||
try { all = all.concat(scrapeTable(win.document)); } catch (e) {}
|
||
try { all = all.concat(scrapeLinks(win.document)); } catch (e) {}
|
||
for (let i = 0; i < win.frames.length; i++) {
|
||
try { all = all.concat(walk(win.frames[i])); } catch (e) {}
|
||
}
|
||
return all;
|
||
}
|
||
return walk(window);
|
||
"""
|
||
)
|
||
if not isinstance(raw, list):
|
||
return []
|
||
rows: list[dict] = []
|
||
for item in raw:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
rows.append(
|
||
{
|
||
"no": str(item.get("no") or "").strip(),
|
||
"title": str(item.get("title") or "").strip(),
|
||
"customer": str(item.get("customer") or "").strip(),
|
||
"href": str(item.get("href") or "").strip(),
|
||
}
|
||
)
|
||
return rows
|
||
|
||
|
||
# 列表行的 href 里是否含有该 fdId。
|
||
def _row_has_fd_id(rows: list[dict], fd_id: str) -> bool:
|
||
token = (fd_id or "").strip()
|
||
if not token:
|
||
return False
|
||
return any(token in str(row.get("href") or "") for row in rows)
|
||
|
||
|
||
# 打开该 fdId 的详情页并读合同编号。文档不存在则返回空。
|
||
def _read_number_by_fd_id(browser: BrowserAdapter, fd_id: str) -> str:
|
||
doc_id = (fd_id or "").strip()
|
||
if not doc_id:
|
||
return ""
|
||
_open_submitted_view(browser, doc_id)
|
||
deadline = time.time() + 10
|
||
while time.time() < deadline:
|
||
body = browser.read_body_text() or ""
|
||
if "该文档不存在" in body or "操作失败" in body:
|
||
print(f"S13 详情不存在 fdId={doc_id}", flush=True)
|
||
return ""
|
||
no = _read_contract_number_on_page(browser)
|
||
if no:
|
||
print("从合同详情读到合同编号:", no, flush=True)
|
||
return no
|
||
time.sleep(0.8)
|
||
return ""
|
||
|
||
|
||
# 回「我的合同」按 fdId/标题+客户编码查号。匹配不到不取最新一条。
|
||
def _read_contract_number_from_my_list(
|
||
browser: BrowserAdapter,
|
||
ctx: ContractFlowContext,
|
||
timeout_seconds: int = 25,
|
||
fd_id: str = "",
|
||
) -> str:
|
||
title = _expected_title(ctx)
|
||
customer = (ctx.customer_code or "").strip()
|
||
print(
|
||
f"S13:回我的合同查询 fdId={fd_id or '-'} title={title} customer={customer}",
|
||
flush=True,
|
||
)
|
||
_open_my_contracts(browser, ctx)
|
||
deadline = time.time() + timeout_seconds
|
||
searched = False
|
||
saw_row = False
|
||
while time.time() < deadline:
|
||
rows = _scrape_my_contract_rows(browser)
|
||
print(f"S13 列表行数={len(rows)}", flush=True)
|
||
for row in rows[:6]:
|
||
print(
|
||
"S13 row"
|
||
f" no={row.get('no')!r}"
|
||
f" title={str(row.get('title') or '')[:60]!r}"
|
||
f" customer={str(row.get('customer') or '')[:30]!r}"
|
||
f" href={str(row.get('href') or '')[:90]!r}",
|
||
flush=True,
|
||
)
|
||
no = _pick_contract_row(
|
||
rows,
|
||
fd_id=fd_id,
|
||
title=title,
|
||
customer_code=customer,
|
||
)
|
||
if no:
|
||
print("从我的合同读到合同编号:", no, flush=True)
|
||
return no
|
||
if fd_id and _row_has_fd_id(rows, fd_id):
|
||
saw_row = True
|
||
if not searched:
|
||
_search_my_contracts(browser, title, customer)
|
||
searched = True
|
||
time.sleep(1)
|
||
if saw_row and fd_id:
|
||
no = _read_number_by_fd_id(browser, fd_id)
|
||
if no:
|
||
return no
|
||
print("我的合同列表未匹配到合同编号", flush=True)
|
||
return ""
|