AutoRpa/codes/rpa_yisaiqi/contract_flow_steps.py

247 lines
11 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.

"""Flowchart-sized CONTRACT_CREATE browser steps for YiSaiQi.
Each function is intentionally small enough to map to one YiSaiQi flowchart
node or one script block behind a flowchart node.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Protocol
class BrowserAdapter(Protocol):
def open_url(self, url: str) -> None: ...
def fill_first(self, selectors: list[str], value: str) -> None: ...
def click_first(self, selectors: list[str]) -> None: ...
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None: ...
def select_first(self, selectors: list[str], value: str) -> None: ...
def upload_file_first(self, selectors: list[str], file_path: str) -> None: ...
def read_first(self, selectors: list[str], value_name: str) -> str: ...
def screenshot(self, name: str) -> str: ...
@dataclass(frozen=True)
class ContractGoodsLine:
operation_code: str
goods_code: str
goods_name: str
approval_no: str
spec: str
unit: str
quantity: float
@dataclass(frozen=True)
class ContractFlowContext:
contract_url: str
username: str
password: str
supplier_name: str
order_date: str
source_type: str
template_type: str = "FORMAT"
template_name: str = ""
customer_code: str = ""
customer_name: str = ""
contract_category: str = ""
settlement_type: str = "付款"
contract_background: str = ""
contract_nature: str = "主合同"
amount_currency: str = "人民币"
amount: str = ""
contract_end_date: str = ""
is_major_contract: str = "否"
is_prepayment: str = "否"
belong_year: str = ""
payment_method: str = "账期支付"
payment_days: str = "30"
contract_status: str = "正式合同"
contains_blood_products: str = "否"
submit_mode: str = "submit"
goods_lines: list[ContractGoodsLine] = field(default_factory=list)
merged_pdf_path: str | None = None
def run_contract_create_flow(browser: BrowserAdapter, ctx: ContractFlowContext) -> str:
open_contract_system(browser, ctx)
login_contract_system(browser, ctx)
enter_contract_create(browser)
select_contract_template(browser, ctx)
fill_contract_signatory_info(browser, ctx)
fill_contract_basic_info(browser, ctx)
fill_contract_amount_and_dates(browser, ctx)
fill_contract_goods_lines(browser, ctx)
fill_contract_payment_info(browser, ctx)
upload_contract_document_if_needed(browser, ctx)
if not submit_contract_for_approval(browser, ctx):
return ""
return read_contract_number(browser)
# 合同起草页(模板选择)直达 URL,绕过 SPA 菜单/iframe 复杂性
DRAFT_PAGE_URL = (
"http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/"
"draft.jsp?isSimpleCategory=false"
"&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply"
"&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl"
)
# 非格式-采购协议(2025集团版)模板 ID(测试环境固定值)
# 直接跳转表单页,绕过「点模板」这一依赖前端委托事件、Selenium 点击无效的步骤
TEMPLATE_ID = "198d10b04325279b8ffeb8b4c6493b5a"
FORM_PAGE_URL = (
"http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/"
"kmAgreementApply.do?method=add"
f"&fdTemplateId={TEMPLATE_ID}"
f"&.fdTemplate={TEMPLATE_ID}"
f"&i.docTemplate={TEMPLATE_ID}"
"&s_css=default"
)
# Landray/LUI 表单字段的真实 name
F = {
"customer_code": 'input[name="extendDataFormInfo.value(fd_customeropcode)"]',
"customer_name": 'input[name="extendDataFormInfo.value(fd_customername)"]',
"contract_category": 'input[name="extendDataFormInfo.value(fd_contract_cate_type)"]',
"settlement_type": 'select#fdSettype',
"contract_background": 'textarea[name="extendDataFormInfo.value(fd_desc_info)"]',
"nature_main": 'input[name="fdType"][value="00"]',
"amount": 'input[name="fdAccount"]',
"sign_date": 'input[name="fdSigndate"]',
"begin_date": 'input[name="fdEffedate"]',
"end_date": 'input[name="fdEnddate"]',
"is_major_contract": 'input[name="extendDataFormInfo.value(fd_is_major_contract)"]',
"is_prepayment": 'input[name="extendDataFormInfo.value(fd_advance_amout)"]',
"belong_year": 'input[name="extendDataFormInfo.value(fd_contract_year_text)"]',
"payment_method": 'input[name="extendDataFormInfo.value(fd_paytype)"]',
"payment_days": 'input[name="extendDataFormInfo.value(fd_pay_time_limit)"]',
"contract_status": 'input[name="extendDataFormInfo.value(fd_suconstatus)"]',
"contains_blood": 'input[name="extendDataFormInfo.value(fd_3d1ce3953f1162)"]',
"goods_code": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_opcode)"]',
"goods_name": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsname)"]',
"goods_mdm": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_mdmCode)"]',
"goods_approval": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_registdocno)"]',
"goods_spec": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodstype)"]',
"goods_unit": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)"]',
"goods_qty": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)"]',
"goods_price": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_unitprice)"]',
}
def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
browser.open_url(ctx.contract_url)
def login_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
browser.fill_first(['input[name="j_username"]', 'input[placeholder="用户名"]'], ctx.username)
browser.fill_first(['input[name="j_password"]', 'input[placeholder="密码"]'], ctx.password)
browser.click_first(['a[class*="login_button"]', 'a:contains("登录")', 'input[name="btn_submit"]'])
browser.wait_for_text("合同", timeout_seconds=20)
def enter_contract_create(browser: BrowserAdapter) -> None:
# 登录后直接导航到起草表单页,跳过模板选择(前端委托事件 Selenium 点击无效)
browser.open_url(FORM_PAGE_URL)
browser.wait_for_text("客户编码", timeout_seconds=20)
def select_contract_template(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
# 已通过 URL 直接指定模板,本步骤仅兜底:若尚未进入表单则再次导航
if "客户编码" not in browser.read_body_text():
browser.open_url(FORM_PAGE_URL)
browser.wait_for_text("客户编码", timeout_seconds=20)
def fill_contract_signatory_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
customer_name = ctx.customer_name or ctx.supplier_name
if ctx.customer_code:
browser.fill_first([F["customer_code"]], ctx.customer_code)
if customer_name:
browser.fill_first([F["customer_name"]], customer_name)
browser.screenshot("contract-signatory-info-filled")
def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
if ctx.contract_category:
browser.fill_first([F["contract_category"]], ctx.contract_category)
if ctx.settlement_type:
browser.select_first([F["settlement_type"]], ctx.settlement_type)
if ctx.contract_background:
browser.fill_first([F["contract_background"]], ctx.contract_background)
# 合同性质默认主合同(页面已勾选),仅在需要从合同时切换
if ctx.contract_nature and ctx.contract_nature != "主合同":
browser.click_first(['input[name="fdType"][value="01"]'])
browser.screenshot("contract-basic-info-filled")
def fill_contract_amount_and_dates(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
if ctx.amount:
browser.fill_first([F["amount"]], ctx.amount)
browser.fill_first([F["sign_date"]], ctx.order_date)
browser.fill_first([F["begin_date"]], ctx.order_date)
if ctx.contract_end_date:
browser.fill_first([F["end_date"]], ctx.contract_end_date)
browser.screenshot("contract-amount-and-dates-filled")
def fill_contract_goods_lines(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
for index, line in enumerate(ctx.goods_lines, start=1):
if index > 1:
browser.click_first(["text=添加行", "text=新增明细", "text=新增行"])
browser.fill_first([F["goods_code"]], line.goods_code)
browser.fill_first([F["goods_name"]], line.goods_name)
if line.approval_no:
browser.fill_first([F["goods_approval"]], line.approval_no)
if line.spec:
browser.fill_first([F["goods_spec"]], line.spec)
if line.unit:
browser.fill_first([F["goods_unit"]], line.unit)
browser.fill_first([F["goods_qty"]], str(line.quantity))
browser.screenshot("contract-goods-lines-filled")
def fill_contract_payment_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
browser.fill_first([F["is_major_contract"]], ctx.is_major_contract)
browser.fill_first([F["is_prepayment"]], ctx.is_prepayment)
if ctx.belong_year:
browser.fill_first([F["belong_year"]], ctx.belong_year)
if ctx.payment_method:
browser.fill_first([F["payment_method"]], ctx.payment_method)
if ctx.payment_days:
browser.fill_first([F["payment_days"]], ctx.payment_days)
if ctx.contract_status:
browser.fill_first([F["contract_status"]], ctx.contract_status)
if ctx.contains_blood_products:
browser.fill_first([F["contains_blood"]], ctx.contains_blood_products)
browser.screenshot("contract-payment-filled")
def upload_contract_document_if_needed(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
if ctx.source_type != "YYC":
return
if not ctx.merged_pdf_path:
raise ValueError("YYC contract creation requires merged_pdf_path before upload")
browser.upload_file_first(['input[type="file"]', "label=合同证文上传"], ctx.merged_pdf_path)
browser.screenshot("contract-pdf-uploaded")
def submit_contract_for_approval(browser: BrowserAdapter, ctx: ContractFlowContext) -> bool:
if ctx.submit_mode == "dry_run":
browser.screenshot("contract-dry-run-ready")
return False
browser.click_first(["text=提交", "text=提交送审", "text=送审"])
browser.wait_for_text("合同编号", timeout_seconds=30)
browser.screenshot("contract-submitted")
return True
def read_contract_number(browser: BrowserAdapter) -> str:
return browser.read_first(
['input[name="contractNo"]', 'input[name="contractSystemNo"]', "label=合同编号", "text=合同编号"],
"contract_number",
)
def contract_version_label(ctx: ContractFlowContext) -> str:
return "非格式合同" if ctx.template_type == "NON_FORMAT" else "格式合同"