"""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) 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(['input[name="btn_submit"]', 'a[href*="btn_submit"]', 'text=登录']) browser.wait_for_text("合同", timeout_seconds=20) def enter_contract_create(browser: BrowserAdapter) -> None: browser.click_first(["text=合同管理", "aria=合同管理"]) browser.click_first(["text=合同起草", "aria=合同起草"]) browser.wait_for_text("最近使用模板", timeout_seconds=20) def select_contract_template(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: template_text = ctx.template_name or ( "非格式--采购协议" if ctx.template_type == "NON_FORMAT" else "购销协议书(2025)" ) browser.click_first([f"text={template_text}", f"text={template_text}...", f"title={template_text}"]) 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( ['label=客户编码', 'input[name="fdCustomerCode"]', 'input[placeholder*="客户编码"]'], ctx.customer_code, ) if customer_name: browser.fill_first( ['label=客户名称', 'input[name="fdCustomerName"]', 'input[placeholder*="客户名称"]'], customer_name, ) browser.screenshot("contract-signatory-info-filled") def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: browser.select_first(['label=合同版本类型', 'select[name="fdTemplateType"]'], contract_version_label(ctx)) if ctx.contract_category: browser.select_first(['label=合同类别', 'select[name="fdContractCategory"]'], ctx.contract_category) browser.select_first(['label=结算类型', 'select[name="fdSettlementType"]'], ctx.settlement_type) if ctx.contract_background: browser.fill_first( ['label=合同背景概述', 'textarea[name="fdContractBackground"]'], ctx.contract_background, ) browser.click_first([f"label={ctx.contract_nature}", f"text={ctx.contract_nature}"]) browser.screenshot("contract-basic-info-filled") def fill_contract_amount_and_dates(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: browser.select_first(['label=合同金额', 'select[name="fdCurrency"]'], ctx.amount_currency) if ctx.amount: browser.fill_first(['label=金额(元)', 'input[name="fdAmount"]'], ctx.amount) browser.fill_first( ['label=合同签订日期', 'input[name="fdSignDate"]', 'input[placeholder*="合同签订"]'], ctx.order_date, ) browser.fill_first( ['label=合同开始日期', 'input[name="fdStartDate"]', 'input[placeholder*="合同开始"]'], ctx.order_date, ) if ctx.contract_end_date: browser.fill_first( ['label=合同结束日期', 'input[name="fdEndDate"]', 'input[placeholder*="合同结束"]'], 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=新增行"]) prefix = f"明细{index}" browser.fill_first([f'label={prefix}操作码', 'input[name="operationCode"]'], line.operation_code) browser.fill_first([f'label={prefix}货品编码', 'input[name="goodsCode"]'], line.goods_code) browser.fill_first([f'label={prefix}名称', 'input[name="goodsName"]'], line.goods_name) browser.fill_first([f'label={prefix}国药准字', 'input[name="approvalNo"]'], line.approval_no) browser.fill_first([f'label={prefix}规格', 'input[name="spec"]'], line.spec) browser.fill_first([f'label={prefix}单位', 'input[name="unit"]'], line.unit) browser.fill_first([f'label={prefix}数量', 'input[name="quantity"]'], str(line.quantity)) browser.screenshot("contract-goods-lines-filled") def fill_contract_payment_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: browser.select_first(['label=是否重大合同', 'select[name="fdIsMajorContract"]'], ctx.is_major_contract) browser.select_first(['label=是否预付款', 'select[name="fdIsPrepayment"]'], ctx.is_prepayment) if ctx.belong_year: browser.fill_first(['label=合同归属年份', 'input[name="fdBelongYear"]'], ctx.belong_year) browser.select_first(['label=付款方式', 'select[name="fdPaymentMethod"]', 'select[name="paymentMethod"]'], ctx.payment_method) browser.fill_first(['label=付款期限', 'input[name="fdPaymentDays"]', 'input[name="paymentDays"]'], ctx.payment_days) browser.select_first(['label=合同状态', 'select[name="fdContractStatus"]'], ctx.contract_status) browser.select_first( ['label=是否包含血液制品', 'select[name="fdContainsBloodProducts"]'], 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 "格式合同"