| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768 |
- # -*- coding: utf-8 -*-
- """生成《响应文件》。
- 做法:直接抽取询比文件「第六章 响应文件格式」的原始 Word 内容(保留其全部原文、
- 字体、字号、缩进、对齐与表格样式),仅在原有空位上填写我方内容;不增删原文条款。
- 仅在原文明确允许「格式自拟」「供应商自行添加」处追加材料。
- 用法:python -X utf8 doc/_gen_bid_response.py
- """
- import copy
- import os
- import shutil
- import sys
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
- from docx import Document
- from docx.enum.text import WD_ALIGN_PARAGRAPH
- from docx.oxml import OxmlElement
- from docx.oxml.ns import qn
- from docx.shared import Cm
- from docx.table import Table
- from docx.text.paragraph import Paragraph
- from _bid_req_items import REQ_ITEMS
- from _gen_bid_annex import SW_COPYRIGHTS, SW_RELEVANCE
- DOC_DIR = os.path.dirname(os.path.abspath(__file__))
- SRC_TENDER = os.path.join(DOC_DIR, "_tmp_xunbi.docx")
- ONEDRIVE_DIR = r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目招投标"
- OUT_NAME = "响应文件-2026年河南产互联制造业数据智能运营平台研发项目-北京智造易科技有限公司.docx"
- OUT_PATH = os.path.join(DOC_DIR, OUT_NAME)
- # ------------------------------------------------------------------ 填写内容
- PROJECT_FULL = "2026年河南产互联制造业数据智能运营平台研发项目"
- PROJECT_NO = "0635-202611FW0036"
- BUYER = "河南产业互联网联合发展有限公司"
- COMPANY = "北京智造易科技有限公司"
- USCC = "91110000MA00AFPA3H"
- LEGAL_REP = "刘涛"
- LEGAL_REP_SEX = "男"
- LEGAL_REP_AGE = "47" # 1978-12-11 生,按投标日 2026-08-05 计
- LEGAL_REP_ID = "340304197812110814"
- LEGAL_REP_TEL = "13955207130"
- AGENT = "郭峰"
- AGENT_ID = "320821198303277151"
- BANK_NAME = "中国建设银行股份有限公司北京天宝北街支行"
- BANK_ACCOUNT = "11050110248800000238"
- COMPANY_TYPE = "有限责任公司(自然人投资或控股)"
- # 注册地址以公共信用报告(2026-07-31 出具)载明的住所为准
- ADDRESS = "北京市北京经济技术开发区文化园西路6号院18号楼6层606"
- OPERATE_UNTIL = "2016年12月14日至2066年12月13日"
- REG_CAPITAL = "1000万元人民币"
- # 经营范围照录营业执照(登记机关:北京经济技术开发区市场监督管理局)
- BUSINESS_SCOPE = (
- "技术推广、技术咨询、技术服务、技术开发、技术转让;软件开发;网页设计;"
- "销售计算机软件及辅助设备、机械设备、电子产品、文化用品、通讯设备、"
- "针纺织品、五金产品(不含电动自行车);组织文化艺术交流活动;承办展览展示;"
- "会议服务;文艺创作;设计、制作、代理、发布广告;电脑图文设计、制作;"
- "摄影摄像服务;企业管理咨询;经济贸易咨询;企业策划;货物进出口;"
- "技术进出口;代理进出口;加工电子产品、计算机软件及辅助设备"
- "(限在外埠从事生产经营活动)。(市场主体依法自主选择经营项目,开展经营活动;"
- "依法须经批准的项目,经相关部门批准后依批准的内容开展经营活动;"
- "不得从事国家和本市产业政策禁止和限制类项目的经营活动。)")
- # 含税报价。另两家为 587,600 / 592,000,基准价=三家均价×97%;
- # 报价 560,000 时基准价 562,470.67,偏低 0.44%,得 39.87 分,领先次高 2.55 分。
- # 分差在临界价(563,651)及以下恒为 2.55,故取整数 56 万,避免报价贴住基准价。
- PRICE_CN = "伍拾陆万元整"
- PRICE_NUM = "560,000.00"
- BID_Y, BID_M, BID_D = "2026", "8", "5"
- PERF_COUNT = 5 # 业绩表份数
- TENDER_START_KEY = ("第六章", "响应文件格式")
- # ------------------------------------------------------------------ 工具函数
- def body_children(doc):
- return list(doc.element.body.iterchildren())
- def para_text(el):
- return "".join(n.text or "" for n in el.iter(qn("w:t")))
- def trim_to_chapter6(doc):
- """删除第六章之外的全部 body 元素,仅保留第六章(不含第七章)。"""
- children = body_children(doc)
- c6 = [i for i, el in enumerate(children)
- if el.tag == qn("w:p")
- and para_text(el).strip().startswith(TENDER_START_KEY[0])
- and TENDER_START_KEY[1] in para_text(el)]
- c7 = [i for i, el in enumerate(children)
- if el.tag == qn("w:p") and para_text(el).strip().startswith("第七章")]
- start = c6[-1]
- end = next(i for i in c7 if i > start)
- body = doc.element.body
- sect = body.find(qn("w:sectPr"))
- for i, el in enumerate(children):
- if el.tag == qn("w:sectPr"):
- continue
- if i < start or i >= end:
- body.remove(el)
- if sect is not None and sect.getparent() is None:
- body.append(sect)
- return start
- def set_run(p, idx, text):
- """替换段落第 idx 个 run 的文本,完整保留该 run 的字体格式。"""
- runs = p.runs
- if idx >= len(runs):
- raise IndexError(f"run {idx} 不存在(共 {len(runs)} 个):{p.text!r}")
- runs[idx].text = text
- def append_run_like(p, src_idx, text):
- """按第 src_idx 个 run 的格式,在段落末尾追加一个新 run。"""
- src = p.runs[src_idx]
- new = copy.deepcopy(src._element)
- p._p.append(new)
- from docx.text.run import Run
- r = Run(new, p)
- r.text = text
- return r
- def cell_fill(cell, text, like_cell=None):
- """填写表格单元格,套用同表已有单元格的字体格式;\n 转为段内换行。
- 会清空该单元格原有全部段落内容(保留第一个段落及其段落格式)。
- """
- tmpl_run = None
- if like_cell is not None:
- for pp in like_cell.paragraphs:
- if pp.runs:
- tmpl_run = pp.runs[0]._element
- break
- paras = cell.paragraphs
- for extra in paras[1:]:
- extra._p.getparent().remove(extra._p)
- p = cell.paragraphs[0]
- for r in list(p.runs):
- r._element.getparent().remove(r._element)
- from docx.text.run import Run
- def new_run():
- if tmpl_run is None:
- return p.add_run()
- el = copy.deepcopy(tmpl_run)
- for child in list(el):
- if child.tag in (qn("w:t"), qn("w:br"), qn("w:cr"), qn("w:tab")):
- el.remove(child)
- p._p.append(el)
- return Run(el, p)
- for i, line in enumerate(str(text).split("\n")):
- if i > 0:
- new_run()._element.append(OxmlElement("w:br"))
- new_run().text = line
- def _page_field_para():
- p = OxmlElement("w:p")
- pPr = OxmlElement("w:pPr")
- st = OxmlElement("w:pStyle")
- st.set(qn("w:val"), "21")
- jc = OxmlElement("w:jc")
- jc.set(qn("w:val"), "center")
- pPr.append(st)
- pPr.append(jc)
- p.append(pPr)
- for kind, payload in (("begin", None), ("instr", " PAGE \\* MERGEFORMAT "),
- ("separate", None), ("text", "1"), ("end", None)):
- r = OxmlElement("w:r")
- if kind == "instr":
- it = OxmlElement("w:instrText")
- it.set(qn("xml:space"), "preserve")
- it.text = payload
- r.append(it)
- elif kind == "text":
- t = OxmlElement("w:t")
- t.text = payload
- r.append(t)
- else:
- fc = OxmlElement("w:fldChar")
- fc.set(qn("w:fldCharType"), kind)
- r.append(fc)
- p.append(r)
- return p
- def fix_footer_page_numbers(doc):
- """模板页脚把页码放在 wrap=none 自动缩放的文本框里,两位数会被压掉。
- 改为每一节都显式挂一个标准的居中 PAGE 域(视觉仍是底部居中页码),
- 既修掉两位数被裁的问题,也避免删除首页后各节页脚失联。
- """
- n = 0
- for sec in doc.sections:
- sec.different_first_page_header_footer = False
- sec.footer.is_linked_to_previous = False
- ftr = sec.footer._element
- for child in list(ftr):
- ftr.remove(child)
- ftr.append(_page_field_para())
- n += 1
- return n
- def relax_row_height(row):
- """模板行高为「固定值」,长原文会被裁切;改为「最小值」让该行自动增高。
- 仅动 hRule,保留原高度作为下限,字体、边框、列宽均不变。
- """
- trPr = row._tr.get_or_add_trPr()
- th = trPr.find(qn("w:trHeight"))
- if th is not None:
- th.set(qn("w:hRule"), "atLeast")
- def clone_para_after(anchor_el, like_el, doc, text=None):
- """在 anchor_el 之后插入一个新段落,段落与首个 run 的格式均取自 like_el。"""
- el = copy.deepcopy(like_el)
- anchor_el.addnext(el)
- p = Paragraph(el, doc)
- for extra in list(p.runs)[1:]:
- extra._element.getparent().remove(extra._element)
- # 模板段落可能带自动编号或分页属性,克隆后需清掉,避免附件标题被编号/乱分页
- pPr = el.find(qn("w:pPr"))
- if pPr is not None:
- numPr = pPr.find(qn("w:numPr"))
- if numPr is not None:
- pPr.remove(numPr)
- p.paragraph_format.page_break_before = False
- if p.runs:
- p.runs[0].text = "" if text is None else text
- else:
- p.add_run(text or "")
- return p
- ATTACH_DIR = os.path.join(DOC_DIR, "_bid_assets", "attach")
- CONTENT_W, CONTENT_H = 14.6, 24.6 # 模板正文区尺寸(cm)
- MERGE_TOKEN = "@@MERGE:%s@@" # 供合并脚本定位并替换为外部文件内容
- def outline(p, lvl):
- """只写 w:outlineLvl,不套 Heading 样式。
- 导航窗格与目录域取的是「大纲级别」而非样式名,因此这样即可得到完整层级
- 与自动目录,而询比文件原有的字体、字号、缩进、对齐全部保持不动。
- """
- pPr = p._p.get_or_add_pPr()
- old = pPr.find(qn("w:outlineLvl"))
- if old is not None:
- pPr.remove(old)
- el = OxmlElement("w:outlineLvl")
- el.set(qn("w:val"), str(lvl - 1))
- pPr.append(el)
- return p
- def fit(path, max_w, max_h):
- """按正文区等比缩放,返回 (宽 cm, 高 cm)。"""
- from PIL import Image as _Image
- w_px, h_px = _Image.open(path).size
- w, h = max_w, max_w * h_px / w_px
- if h > max_h:
- h, w = max_h, max_h * w_px / h_px
- return w, h
- def sub(cursor_el, text, like_el, doc, lvl=None, page_break=False):
- """插入一个从属段落(组织标题、说明或占位提示),返回段落对象。"""
- p = clone_para_after(cursor_el, like_el, doc, text)
- p.alignment = WD_ALIGN_PARAGRAPH.LEFT
- if page_break:
- p.paragraph_format.page_break_before = True
- if lvl:
- outline(p, lvl)
- return p
- def merge_mark(cursor_el, key, like_el, doc):
- """插入合并占位标记;合并脚本会整段删除它并在原位插入对应文件。
- 不加段前分页:分页由合并时插入的分节符或前面的组织标题负责,
- 否则会多出一页空白。
- """
- return clone_para_after(cursor_el, like_el, doc, MERGE_TOKEN % key)._p
- def attachment(cursor_el, title, pages, like_el, doc, max_w=CONTENT_W,
- note=None, lvl=None, page_break=True):
- """插入一个附件块:标题另起页,随后按 pages 逐页排图。
- pages 为「每页一组图」的列表,元素形如 [(文件名, 图注 or None), ...];
- 同一组内的图排在同一页。返回 (标题段, 最后一个元素),便于连续插入。
- """
- cur = clone_para_after(cursor_el, like_el, doc, title)
- cur.paragraph_format.page_break_before = page_break
- cur.alignment = WD_ALIGN_PARAGRAPH.LEFT
- if lvl:
- outline(cur, lvl)
- last = cur._p
- for line in ([note] if isinstance(note, str) else (note or [])):
- last = clone_para_after(last, like_el, doc, line)._p
- for gi, group in enumerate(pages):
- # 每页高度按该页图片数量均分,避免同页多图溢出
- share = (CONTENT_H - 3.0) / max(1, len(group))
- for fn, cap in group:
- path = os.path.join(ATTACH_DIR, fn)
- if not os.path.exists(path):
- raise FileNotFoundError(path)
- w, _ = fit(path, max_w, share)
- ip = clone_para_after(last, like_el, doc)
- ip.alignment = WD_ALIGN_PARAGRAPH.CENTER
- if gi > 0 and group[0][0] == fn:
- ip.paragraph_format.page_break_before = True
- ip.runs[0].add_picture(path, width=Cm(w))
- last = ip._p
- if cap:
- cp = clone_para_after(last, like_el, doc, cap)
- cp.alignment = WD_ALIGN_PARAGRAPH.CENTER
- last = cp._p
- return cur, last
- def add_bookmark(p, name, bid):
- start = OxmlElement("w:bookmarkStart")
- start.set(qn("w:id"), str(bid))
- start.set(qn("w:name"), name)
- end = OxmlElement("w:bookmarkEnd")
- end.set(qn("w:id"), str(bid))
- p._p.insert(0, start)
- p._p.append(end)
- # ------------------------------------------------------------------ 主流程
- shutil.copy2(SRC_TENDER, OUT_PATH + ".tmp")
- doc = Document(OUT_PATH + ".tmp")
- OFF = trim_to_chapter6(doc)
- ch = body_children(doc)
- def P(old_idx):
- return Paragraph(ch[old_idx - OFF], doc)
- def T(old_idx):
- return Table(ch[old_idx - OFF], doc)
- SEALS = [] # (bookmark, 章节, 盖章要求, 签字要求, 依据)
- def seal(p, key, section, stamp, sign, basis):
- add_bookmark(p, key, 1000 + len(SEALS))
- SEALS.append((key, section, stamp, sign, basis))
- # ---------------- 章节标题:补原文漏掉的编号,并设大纲级别
- # 「资格审查资料:」看似缺“四、”,实为该段带自动编号(numId 52)已渲染出序号,
- # 不能再补文字,否则目录会显示成“四、四、资格审查资料”。
- # 「包括但不限于以下资料…」确无编号,补“(三)”后目录编号才连贯。
- p = P(935)
- set_run(p, 0, "(三)包括但不限于以下资料(")
- # 原文把「技术要求的汇总应答」也编成“(一)”,与上一节重号,按序改为“(二)”
- set_run(P(924), 0, "(二)技术要求的汇总应答")
- for _i in (792, 816, 838, 853, 889, 915, 942): # 一~七
- outline(P(_i), 1)
- for _i in (794, 806, 854, 858, 890, 898, 917, 924, 935):
- outline(P(_i), 2)
- # ---------------- 目录:把原文手工目录清单换成自动目录域
- # 原文第 768 段本就有「目 录」标题页,其下 770~777 是七条无页码的手工条目。
- # 换成 TOC 域后可显示到四级并带页码与前导点,标题页与页面格式保持原样。
- def toc_field(p, instr=' TOC \\o "1-4" \\h \\z \\u '):
- for r in list(p.runs):
- r._element.getparent().remove(r._element)
- for kind, payload in (("begin", None), ("instr", instr),
- ("separate", None),
- ("text", "(打开文档后请右键选择“更新域”刷新页码)"),
- ("end", None)):
- r = OxmlElement("w:r")
- if kind == "instr":
- it = OxmlElement("w:instrText")
- it.set(qn("xml:space"), "preserve")
- it.text = payload
- r.append(it)
- elif kind == "text":
- t = OxmlElement("w:t")
- t.text = payload
- r.append(t)
- else:
- fc = OxmlElement("w:fldChar")
- fc.set(qn("w:fldCharType"), kind)
- r.append(fc)
- p._p.append(r)
- toc_field(P(770))
- for _i in range(771, 792): # 余下手工条目与撑页空段一并去掉
- _el = ch[_i - OFF]
- if _el.getparent() is not None:
- _el.getparent().remove(_el)
- P(792).paragraph_format.page_break_before = True # 正文另起页
- # 末尾「七、其他」之后的空段会多撑出一整页,去掉
- _body = doc.element.body
- while True:
- _ps = _body.findall(qn("w:p"))
- if not _ps:
- break
- _txt = "".join(n.text or "" for n in _ps[-1].iter(qn("w:t")))
- if _txt.strip():
- break
- _body.remove(_ps[-1])
- # ---------------- 封面
- p = P(749)
- set_run(p, 0, " 2026年河南产互联制造业数据智能运营平台研发")
- p = P(756)
- set_run(p, 0, f"项目编号:{PROJECT_NO}")
- p = P(764)
- set_run(p, 1, f" {COMPANY} (盖公章)")
- seal(p, "SEAL01", "封面", "供应商(盖公章)",
- "法定代表人或其授权代表(签字或盖章)",
- "第六章「响应文件格式」封面署名栏原文")
- p = P(766)
- set_run(p, 0, f"日 期: {BID_Y} 年 {BID_M} 月 {BID_D} 日")
- # ---------------- 一(一)响应函
- p = P(795)
- set_run(p, 1, BUYER)
- p = P(796)
- set_run(p, 1, f" {PROJECT_NO} ")
- set_run(p, 3, f" {PROJECT_FULL}")
- set_run(p, 6, f" {PRICE_CN} ")
- set_run(p, 8, f" {PRICE_NUM} ")
- p = P(799)
- set_run(p, 1, " 1年 ")
- p = P(803)
- set_run(p, 1, f" {COMPANY} (盖章)")
- seal(p, "SEAL02", "一(一)响应函", "供应商(盖章)",
- "法定代表人或其委托代理人(签字或盖章)",
- "第六章响应函署名栏原文;并为第三章形式评审项「响应函签字盖章」")
- p = P(805)
- set_run(p, 1, f" {BID_Y} ")
- set_run(p, 3, f" {BID_M} ")
- set_run(p, 5, f" {BID_D} ")
- # ---------------- 一(二)响应函附录
- t = T(807)
- lab = t.cell(0, 0)
- cell_fill(t.cell(0, 1), PROJECT_FULL, lab)
- cell_fill(t.cell(1, 1), PROJECT_NO, lab)
- cell_fill(t.cell(2, 1), COMPANY, lab)
- cell_fill(t.cell(3, 1), f"大写:{PRICE_CN};\n小写:¥{PRICE_NUM} 元", lab)
- cell_fill(t.cell(4, 1),
- "2026年河南产互联制造业数据智能运营平台研发,包括故障处理、系统巡检、"
- "版本升级、技术咨询等", lab)
- cell_fill(t.cell(5, 1), "1年", lab)
- cell_fill(t.cell(6, 1), "系统提供不低于1年的免费质保期。", lab)
- cell_fill(t.cell(7, 1), "符合国家、省、市相关规定及行业要求,满足采购人工作规范要求。", lab)
- cell_fill(t.cell(8, 1), "采购人指定地点", lab)
- p = P(811)
- set_run(p, 1, f" {COMPANY} (盖章)")
- seal(p, "SEAL03", "一(二)响应函附录", "供应商(盖章)",
- "法定代表人或其委托代理人(签字或盖章)", "第六章响应函附录署名栏原文")
- p = P(813)
- set_run(p, 1, f" {BID_Y} ")
- set_run(p, 3, f" {BID_M} ")
- set_run(p, 5, f" {BID_D} ")
- # ---------------- 二 法定代表人身份证明书
- set_run(P(818), 2, f" {COMPANY} ")
- set_run(P(819), 1, f" {COMPANY_TYPE} ")
- set_run(P(820), 1, f" {ADDRESS} ")
- p = P(821)
- set_run(p, 1, " 2016 ")
- set_run(p, 3, " 12 ")
- set_run(p, 5, " 14 ")
- set_run(P(822), 1, f" {OPERATE_UNTIL} ")
- p = P(823)
- set_run(p, 1, f" {LEGAL_REP} ")
- set_run(p, 3, f" {LEGAL_REP_SEX} ")
- set_run(p, 5, f" {LEGAL_REP_AGE} ")
- set_run(p, 7, "【待填:职务】")
- set_run(P(824), 1, f" {COMPANY} ")
- p = P(831)
- set_run(p, 1, f" {COMPANY} (盖章)")
- seal(p, "SEAL04", "二、法定代表人身份证明书", "供应商(盖章)",
- "法定代表人(签字或盖章)",
- "第六章署名栏原文写「法定代表人」;另原文明文「附:法定代表人身份证复印件,加盖单位公章」")
- p = P(833)
- set_run(p, 1, f" {BID_Y} ")
- set_run(p, 3, f" {BID_M} ")
- set_run(p, 5, f" {BID_D} ")
- # 法定代表人身份证复印件(原文「附:法定代表人身份证复印件,加盖单位公章」)
- tp, _ = attachment(ch[836 - OFF], "附:法定代表人身份证复印件(正面、反面)",
- [[("id_front.jpg", "(正面)"), ("id_back.jpg", "(反面)")]],
- ch[836 - OFF], doc, max_w=12.4)
- seal(tp, "SEALA1", "二、法定代表人身份证复印件", "加盖单位公章", "—",
- "第六章「附:法定代表人身份证复印件,加盖单位公章。」")
- # ---------------- 三 法定代表人授权委托书
- p = P(840)
- set_run(p, 1, f" {LEGAL_REP} ")
- set_run(p, 3, f" {COMPANY} ")
- set_run(p, 5, f" {AGENT} ")
- set_run(p, 7, f" {COMPANY} ")
- set_run(p, 9, f" {BUYER} ")
- set_run(p, 11, f" {PROJECT_FULL} ")
- p = P(843)
- set_run(p, 1, f" {COMPANY} ")
- seal(p, "SEAL05", "三、法定代表人授权委托书", "供应商(盖单位章)",
- "法定代表人(签字或盖章)",
- "第六章署名栏原文写「法定代表人」;另原文明文「附:委托代理人身份证复印件,加盖单位公章」")
- set_run(P(845), 1, f" {LEGAL_REP_ID} ")
- set_run(P(846), 1, f" {AGENT} ")
- set_run(P(847), 1, f" {AGENT_ID} ")
- p = P(848) # 该段空格 run 为 0/2/4,年/月/日 为 1/3/5
- set_run(p, 0, f" {BID_Y} ")
- set_run(p, 2, f" {BID_M} ")
- set_run(p, 4, f" {BID_D} ")
- # ---------------- 四(一)供应商基本情况表
- t = T(855)
- lab = t.cell(0, 0)
- cell_fill(t.cell(0, 1), COMPANY, lab)
- cell_fill(t.cell(1, 1), ADDRESS, lab)
- cell_fill(t.cell(1, 4), "2016年12月14日", lab)
- cell_fill(t.cell(2, 2), LEGAL_REP, lab)
- cell_fill(t.cell(2, 4), LEGAL_REP_TEL, lab)
- cell_fill(t.cell(3, 2), "【待填:项目负责人姓名】", lab)
- cell_fill(t.cell(3, 4), "【待填:项目负责人电话】", lab)
- cell_fill(t.cell(4, 1), REG_CAPITAL, lab)
- cell_fill(t.cell(5, 1), USCC, lab)
- cell_fill(t.cell(6, 1), BANK_NAME, lab)
- cell_fill(t.cell(7, 1), BANK_ACCOUNT, lab)
- cell_fill(t.cell(8, 1), "【待填:办公场所面积】", lab)
- cell_fill(t.cell(9, 1), BUSINESS_SCOPE, lab)
- relax_row_height(t.rows[9]) # 经营范围原文较长,固定行高会裁字
- cell_fill(t.cell(10, 0), "员工总人数(【待填】人)", lab)
- # ---------------- 四(二)供应商资格声明函
- set_run(P(860), 1, f" {BUYER} ")
- p = P(874)
- set_run(p, 1, f" {COMPANY} (盖章)")
- seal(p, "SEAL06", "四(二)供应商资格声明函", "供应商(盖章)",
- "法定代表人或其委托代理人(签字或盖章)", "第六章供应商资格声明函署名栏原文")
- p = P(876)
- set_run(p, 1, f" {BID_Y} ")
- set_run(p, 3, f" {BID_M} ")
- set_run(p, 5, f" {BID_D} ")
- # ---------------- 四(三)证明资料:逐条对应资格条件 3.1~3.6
- like = ch[884 - OFF] # 借用同节段落与字体格式
- last = sub(ch[887 - OFF], "(三)证明资料", like, doc, lvl=2,
- page_break=True)._p
- # 1、营业执照(资格条件 3.1):紧跟「(三)证明资料」同页,不另起页
- _, last = attachment(last, "1、营业执照", [[("license.jpg", None)]],
- like, doc, lvl=3, page_break=False)
- # 2、增值税专用发票承诺书(资格条件 3.2,明文「提供承诺书」)
- last = sub(last, "2、能够提供增值税专用发票的承诺书", like, doc, lvl=3,
- page_break=True)._p
- last = merge_mark(last, "invoice", like, doc)
- # 3、类似业绩(资格条件 3.3,同时对应「类似业绩」10 分)
- last = sub(last, "3、2023年1月1日以来签订的软件开发项目案例", like, doc,
- lvl=3, page_break=True)._p
- last = sub(last, "注:业绩汇总表见本响应文件「五、商务响应文件」5.1 企业业绩,"
- "本节按每个业绩依次附合同关键页、结算发票及发票查验截图。",
- like, doc)._p
- for tag, name in (("3.1", "合同(首页、服务内容页、金额页、签字盖章页)"),
- ("3.2", "发票(结算发票扫描件)"),
- ("3.3", "发票查询(网上税务系统查验截图)")):
- last = sub(last, f"{tag} {name}", like, doc, lvl=4)._p
- last = sub(last, "【待补:本项扫描件由供应商提供后插入】", like, doc)._p
- # 4、不组成联合体及中选后不转包承诺书(资格条件 3.4)
- last = sub(last, "4、我公司在本项目不是联合体应答、中选后不转包的承诺书",
- like, doc, lvl=3, page_break=True)._p
- last = merge_mark(last, "consortium", like, doc)
- # 5、信用记录(资格条件 3.5,明文「提供相关网页查询截图」)
- last = sub(last, "5、未被列入各个网站违法失信名单的承诺书", like, doc,
- lvl=3, page_break=True)._p
- CREDIT_SITES = [
- ("5.1", "未被“国家企业信用信息公示系统”列入“经营异常名录”"),
- ("5.2", "未被“国家企业信用信息公示系统”列入“严重违法失信名单”"),
- ("5.3", "未被“信用中国”网站列入“失信被执行人”"),
- ("5.4", "未被“信用中国”网站列入“重大税收违法失信主体”"),
- ("5.5", "未被“信用中国”网站列入“政府采购严重违法失信行为记录名单”"),
- ]
- for tag, name in CREDIT_SITES:
- last = sub(last, f"{tag} {name}", like, doc, lvl=4)._p
- last = sub(last, "【待补:网页查询截图由供应商提供后插入】", like, doc)._p
- _, last = attachment(last, "5.6 公共信用信息报告",
- [[(f"credit_{i:02d}.jpg", None)] for i in range(1, 8)],
- like, doc, lvl=4,
- note="说明:本报告由国家公共信用和地理空间信息中心于 2026 年 7 月 31 日出具"
- "(报告编号 202607311929041364K204),载明我方严重失信 0 条、"
- "经营异常 0 条、司法判决 0 条。")
- # 6、不存在下列情形之一的承诺书(资格条件 3.6,明文「提供承诺书」)
- last = sub(last, "6、我公司不存在下列情形之一的承诺书", like, doc, lvl=3,
- page_break=True)._p
- last = merge_mark(last, "credit", like, doc)
- # ---------------- 五 5.1 企业业绩(一个业绩一张表,供应商自行添加)
- perf_tbl = ch[891 - OFF]
- perf_blk = [ch[i - OFF] for i in (892, 893, 894, 895)]
- anchor = perf_blk[-1]
- for n in range(PERF_COUNT, 0, -1):
- if n == 1:
- tbl_el, blk_els = perf_tbl, perf_blk
- else:
- tbl_el = copy.deepcopy(perf_tbl)
- blk_els = [copy.deepcopy(e) for e in perf_blk]
- cur = anchor
- for e in [tbl_el] + blk_els:
- cur.addnext(e)
- cur = e
- tb = Table(tbl_el, doc)
- lb = tb.cell(0, 0)
- cell_fill(tb.cell(0, 1), f"【待填:业绩{n}·项目名称】", lb)
- cell_fill(tb.cell(1, 1), f"【待填:业绩{n}·项目地址】", lb)
- cell_fill(tb.cell(2, 1), f"【待填:业绩{n}·项目金额(不低于20万元)】", lb)
- cell_fill(tb.cell(3, 1), f"【待填:业绩{n}·委托人名称】", lb)
- cell_fill(tb.cell(4, 1), f"【待填:业绩{n}·合同签订日期(2023年1月1日以来)】", lb)
- cell_fill(tb.cell(5, 1), f"【待填:业绩{n}·结算发票号码及金额】", lb)
- pp = Paragraph(blk_els[1], doc)
- set_run(pp, 1, f" {COMPANY} ")
- seal(pp, f"SEALP{n}", f"五、5.1 企业业绩 · 业绩表 {n}", "供应商名称(盖章)",
- "法定代表人或其授权代表(签字或盖章)",
- "第六章「5.1企业业绩(一个业绩一张表,供应商自行添加)」署名栏原文")
- pd = Paragraph(blk_els[3], doc)
- set_run(pd, 1, f" {BID_Y} ")
- set_run(pd, 3, f" {BID_M} ")
- set_run(pd, 5, f" {BID_D} ")
- # ---------------- 五 5.2 采购活动廉洁承诺书
- set_run(P(901), 0, f" {BUYER} ")
- set_run(P(902), 1, f" {PROJECT_FULL} ")
- set_run(P(910), 1, f" {COMPANY} ")
- p = P(912)
- append_run_like(p, 0, f" {COMPANY} ")
- seal(p, "SEAL07", "五、5.2 采购活动廉洁承诺书", "承诺单位(签章)",
- "法定代表人或授权代理人签字或盖章", "第六章采购活动廉洁承诺书署名栏原文")
- set_run(P(913), 0, f"{BID_Y} 年 {BID_M} 月 {BID_D} 日")
- # ---------------- 六 技术响应文件:两处「全部满足」
- set_run(P(919), 3, " 全部满足 ")
- set_run(P(926), 3, " 全部满足 ")
- # 采购需求逐条应答表:在原表内追加行(原表 1 表头 + 2 空行)
- t = T(928)
- hdr = t.rows[0]
- tmpl_row = t.rows[1]._tr
- for r in list(t.rows[1:]):
- r._tr.getparent().remove(r._tr)
- for no, txt in REQ_ITEMS:
- new_tr = copy.deepcopy(tmpl_row)
- t._tbl.append(new_tr)
- from docx.table import _Row
- row = _Row(new_tr, t)
- cell_fill(row.cells[0], no, hdr.cells[0])
- cell_fill(row.cells[1], txt, hdr.cells[0])
- cell_fill(row.cells[2], "完全响应", hdr.cells[0])
- cell_fill(row.cells[3], "", hdr.cells[0])
- p = P(931)
- set_run(p, 2, f" {COMPANY} ")
- seal(p, "SEAL08", "六、技术响应文件(汇总应答之后)", "供应商名称(盖单位章)",
- "法定代表人或其委托代理人(签字)",
- "第六章技术响应文件署名栏原文(原文为「签字」);全章仅此一处署名栏,"
- "同时覆盖合同条款汇总应答与采购需求汇总应答")
- p = P(933)
- set_run(p, 0, f" {BID_Y} ")
- set_run(p, 2, f" {BID_M} ")
- set_run(p, 4, f" {BID_D} ")
- # ---------------- 六(三)格式自拟材料:企业实力、软著、项目团队、技术方案各章
- like = ch[936 - OFF]
- # 1、软件企业证书及软件产品证书(企业实力 6 分)
- _, last = attachment(
- ch[938 - OFF], "1、软件企业证书及软件产品证书",
- [[("sw_enterprise.jpg", "软件企业证书(证书编号 京 RQ-2025-1622)")],
- [("sw_product.jpg", "软件产品证书(证书编号 京 RC-2025-1407)")]],
- like, doc, lvl=3,
- note="说明:我方持有软件企业证书(北京软件和信息服务业协会,证书编号 京RQ-2025-1622,"
- "2025年11月29日发证)与软件产品证书(中国软件行业协会,证书编号 京RC-2025-1407,"
- "评估软件为透明工厂管理平台V1.0),两证均在有效期内,"
- "证书主体均为北京智造易科技有限公司。")
- # 2、软件著作权登记证书(软著 9 分):先一览表,再逐份证书扫描件
- last = sub(last, "2、计算机软件著作权登记证书", like, doc, lvl=3,
- page_break=True)._p
- last = merge_mark(last, "swtable", like, doc)
- _, last = attachment(
- last, "证书扫描件(共 10 份)",
- [[(f"swc_{i:02d}.jpg", f"{SW_COPYRIGHTS[i - 1][0]}({SW_COPYRIGHTS[i - 1][1]})")]
- for i in range(1, 11)],
- like, doc)
- # 3、项目团队(项目团队 2 分)
- last = sub(last, "3、项目团队", like, doc, lvl=3, page_break=True)._p
- last = merge_mark(last, "team", like, doc)
- last = sub(last, "【待补:上表各岗位人员姓名及与我方签署的劳务合同扫描件,"
- "由供应商提供后插入】", like, doc)._p
- # 4~12:技术方案与实施服务方案各章,章标题自带序号与大纲级别
- for _i in range(4, 13):
- last = merge_mark(last, f"ch{_i:02d}", like, doc)
- # 「七、其他」原为空章节:另起页并作出应答,避免标题挤在上一章末尾、末页全白。
- # 该段末尾带模板自留的 <w:br type="page">(原用于收尾第六章),不去掉会把
- # 下面的应答顶到新的一页。
- _other = P(942)
- _other.paragraph_format.page_break_before = True
- for _r in list(_other.runs):
- if _r._element.findall(qn("w:br")) and not _r.text:
- _r._element.getparent().remove(_r._element)
- sub(ch[942 - OFF], "除本响应文件已列明的资格审查资料、商务响应文件与技术响应文件外,"
- "我方无其他需要补充说明的事项。", like, doc)
- print("[结构] 已按截图重建四(三)证明资料 1~6、六(三)1~12 层级")
- # 去掉询比文件的「第六章 响应文件格式」标题页,响应文件从封面开始
- title_el = ch[747 - OFF]
- title_el.getparent().remove(title_el)
- print("[标题页] 已移除询比文件「第六章 响应文件格式」标题页")
- n_ftr = fix_footer_page_numbers(doc)
- print(f"[页脚] 已修正 {n_ftr} 个页脚的页码域(模板文本框会压掉两位数)")
- doc.save(OUT_PATH)
- os.remove(OUT_PATH + ".tmp")
- print(f"[完成] {OUT_PATH} ({os.path.getsize(OUT_PATH):,} bytes)")
- print(f"[签章位] 共 {len(SEALS)} 处(均为询比文件明文要求)")
- import json
- with open(os.path.join(DOC_DIR, "_tmp_seals.json"), "w", encoding="utf-8") as f:
- json.dump(SEALS, f, ensure_ascii=False, indent=2)
- if os.path.isdir(ONEDRIVE_DIR):
- try:
- shutil.copy2(OUT_PATH, os.path.join(ONEDRIVE_DIR, OUT_NAME))
- print(f"[已同步] {os.path.join(ONEDRIVE_DIR, OUT_NAME)}")
- except Exception as e:
- print(f"[同步失败] {type(e).__name__}: {e}")
|