from pathlib import Path from zipfile import ZipFile,ZIP_DEFLATED from lxml import etree as E import hashlib,json from copy import deepcopy root=Path('scripts/quotation-contract-20261015') source=root/'reference.docx' target=Path('yudao-module-crm/src/main/resources/templates/contract/sale-contract.docx');target.parent.mkdir(parents=True,exist_ok=True) ns={'w':'http://schemas.openxmlformats.org/wordprocessingml/2006/main'};W='{'+ns['w']+'}' with ZipFile(source) as src: xml=E.fromstring(src.read('word/document.xml'));body=xml.find('w:body',ns);ps=body.findall('w:p',ns) for t in ps[1].findall('.//w:t',ns): if '中国科学院西北高原生物研究所' in (t.text or ''):t.text=t.text.replace('中国科学院西北高原生物研究所','${customerName}') metadata=ps[4] for child in list(metadata):metadata.remove(child) ppr=ps[1].find('w:pPr',ns) if ppr is not None:metadata.append(deepcopy(ppr)) run=E.SubElement(metadata,W+'r');rpr=ps[1].find('w:r/w:rPr',ns) if rpr is not None:run.append(deepcopy(rpr)) E.SubElement(run,W+'t').text='合同编号:${contractNo} 合同日期:${contractDate}' body.remove(metadata);body.insert(list(body).index(ps[0])+1,metadata) for paragraph in ps: text=''.join(paragraph.itertext()) if any(text.startswith(prefix) for prefix in ['第二条','第三条','第四条','第五条','第六条','第七条']): prop=paragraph.find('w:pPr',ns) if prop is None:prop=E.SubElement(paragraph,W+'pPr') keep=prop.find('w:keepNext',ns) if keep is None:keep=E.SubElement(prop,W+'keepNext') keep.set(W+'val','1') table=body.find('w:tbl',ns);rows=table.findall('w:tr',ns) for cell,key in zip(rows[1].findall('w:tc',ns),['index','itemName','unitName','count','price','amount']): texts=cell.findall('.//w:t',ns);texts[0].text='${'+key+'}' for t in texts[1:]:t.text='' rowprop=rows[1].find('w:trPr',ns) if rowprop is None:rowprop=E.SubElement(rows[1],W+'trPr') E.SubElement(rowprop,W+'cantSplit') for row in rows[2:4]:table.remove(row) for row,text in [(rows[4],'合计金额:${totalPrice}${discountNote}'),(rows[5],'大写金额:${totalUpper}')]: texts=row.findall('.//w:t',ns);texts[0].text=text for t in texts[1:]:t.text='' prop=rows[0].find('w:trPr',ns) if prop is None:prop=E.SubElement(rows[0],W+'trPr') E.SubElement(prop,W+'tblHeader') with ZipFile(target,'w',ZIP_DEFLATED) as dst: for info in src.infolist():dst.writestr(info,E.tostring(xml,xml_declaration=True,encoding='UTF-8',standalone=True) if info.filename=='word/document.xml' else src.read(info.filename)) original=Path('D:/WechatRecord/xwechat_files/wxid_vmeut7wztkbp22_1546/temp/RWTemp/2026-10/c40afdcfa98e2b4561823e6babbefa33/虫草购销合同-西高所.doc') (root/'artifact.md').write_text('''# 合同模板执行说明 原始 DOC 保持不变,使用 Word 转为 reference.docx。版式以该副本为准:保留页面尺寸、字体、段落、原合同全部条款和甲方收款信息。 仅编辑 word/document.xml:乙方名称、标题下的合同编号与日期、产品明细行、合计和大写金额;产品行可按数量克隆,原两条空行移除,表头跨页重复,产品行禁止跨页拆分,条款标题与后文同页。 金额来自合同实际数据,含整单优惠时明确注明优惠金额和实付合计。签章区和签字时间保留为空,不自动代签。 原模板开户行写为公司名称,按原样保留,正式签署前应核对。 '''+f'原始文件 SHA256:{hashlib.sha256(original.read_bytes()).hexdigest()}\nDOCX SHA256:{hashlib.sha256(source.read_bytes()).hexdigest()}\n',encoding='utf-8') print(target)