commit 3cf5ba655c818bdf4eef68e1c9a148db4de62d4a Author: XuHengJie Date: Tue Jul 21 14:45:08 2026 +0800 init: 土地平整工程竣工结算资料(部分)生成套件 v1.0.0 从土地平整工程单项工程结算表中提取清理表土、修平田地、 田埂修筑三项工序的工程量,自动生成 T3 田块单元工程 竣工结算资料套表(计量报验单、工序工程量统计表、3份工序工程量签证)。 - 支持 .xls / .xlsx 格式输入 - 输出 5 份 .xlsx 文档,含合并单元格、边框、表头蓝色底色 - 参数可覆盖项目名称/承包单位/合同段/监理/业主等元信息 diff --git a/scripts/generate.py b/scripts/generate.py new file mode 100644 index 0000000..059eb1c --- /dev/null +++ b/scripts/generate.py @@ -0,0 +1,682 @@ +#!/usr/bin/env python3 +""" +土地平整工程竣工结算资料(部分)生成 +从单项工程结算表中提取数据,生成T3田块竣工结算资料套表。 +""" + +import sys +import json +import os +import re +from copy import copy + +# ── helpers for openpyxl ────────────────────────────────────────────── +try: + import openpyxl + from openpyxl.styles import ( + Font, Border, Side, Alignment, PatternFill, NamedStyle + ) + from openpyxl.utils import get_column_letter +except ImportError: + print(json.dumps({"error": "openpyxl 未安装,请在环境安装: pip install openpyxl"})) + sys.exit(1) + +# ── also try xlrd for .xls input ────────────────────────────────────── +try: + import xlrd + HAS_XLRD = True +except ImportError: + HAS_XLRD = False + + +# ══════════════════════════════════════════════════════════════════════ +# Style definitions +# ══════════════════════════════════════════════════════════════════════ + +THIN_BORDER = Border( + left=Side(style='thin'), + right=Side(style='thin'), + top=Side(style='thin'), + bottom=Side(style='thin'), +) + +NO_BORDER = Border() + +TITLE_FONT = Font(name='宋体', size=16, bold=True) +HEADER_FONT = Font(name='宋体', size=10, bold=True) +NORMAL_FONT = Font(name='宋体', size=10) +SIGN_FONT = Font(name='宋体', size=10, bold=True) +SMALL_FONT = Font(name='宋体', size=9) + +CENTER_ALIGN = Alignment(horizontal='center', vertical='center', wrap_text=True) +LEFT_ALIGN = Alignment(horizontal='left', vertical='center', wrap_text=True) +LEFT_TOP = Alignment(horizontal='left', vertical='top', wrap_text=True) + +HEADER_FILL = PatternFill(start_color='D9E2F3', end_color='D9E2F3', fill_type='solid') +LIGHT_YELLOW = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid') + + +def apply_cell(ws, row, col, value, font=None, alignment=None, border=None, fill=None, number_format=None): + """Helper to set a cell value + style.""" + cell = ws.cell(row=row, column=col, value=value) + if font: + cell.font = font + if alignment: + cell.alignment = alignment + if border: + cell.border = border + if fill: + cell.fill = fill + if number_format: + cell.number_format = number_format + return cell + + +def merge_and_set(ws, r1, r2, c1, c2, value, font=None, alignment=None, border=None, fill=None): + """Merge cells and set value + style in the top-left cell.""" + ws.merge_cells(start_row=r1, start_column=c1, end_row=r2, end_column=c2) + return apply_cell(ws, r1, c1, value, font=font, alignment=alignment, border=border, fill=fill) + + +def thin_border_range(ws, r1, r2, c1, c2): + """Apply thin border to all cells in range.""" + for r in range(r1, r2 + 1): + for c in range(c1, c2 + 1): + ws.cell(row=r, column=c).border = THIN_BORDER + + +def set_col_widths(ws, widths): + """Set column widths: widths is a dict {col_index: width_in_chars} (1-based)""" + for col_idx, w in widths.items(): + ws.column_dimensions[get_column_letter(col_idx)].width = w + + +def set_row_heights(ws, heights): + """Set row heights: heights is a dict {row_index: height_in_points} (1-based)""" + for row_idx, h in heights.items(): + ws.row_dimensions[row_idx].height = h + + +# ══════════════════════════════════════════════════════════════════════ +# Input reader +# ══════════════════════════════════════════════════════════════════════ + +def read_input(input_path): + """ + Read the 单项工程结算表 and return a dict with: + - project_name + - contractor + - quantities: { '清理表土': float, '修平田地': float, '田埂修筑': float } + - date_value (if available) + """ + ext = os.path.splitext(input_path)[1].lower() + result = { + 'project_name': '', + 'contractor': '', + 'contract_section': '', + 'quantities': {}, + 'date_value': None, + } + + if ext in ('.xls',): + if not HAS_XLRD: + raise RuntimeError( + "输入文件为 .xls 格式,但环境未安装 xlrd 库。" + "请将文件另存为 .xlsx 格式后重试。" + ) + wb = xlrd.open_workbook(input_path) + ws = wb.sheet_by_index(0) + # Row 0: title + # Row 1: project name (col 1) + # Row 2: contractor (col 1) + # Row 3: header row + # Row 4-8: process rows + result['project_name'] = str(ws.cell(1, 1).value or '') + result['contractor'] = str(ws.cell(2, 1).value or '') + # 合同段 at (1, 8) + result['contract_section'] = str(ws.cell(1, 9).value or '') + # Quantity cell: col 8 (0-indexed) in rows 4,5,6 + process_map = { + '清理表土': (4, 9), + '修平田地': (5, 9), + '田埂修筑': (6, 9), + } + for pname, (r, c) in process_map.items(): + cv = ws.cell(r, c).value + try: + result['quantities'][pname] = float(cv) if cv else 0.0 + except (ValueError, TypeError): + result['quantities'][pname] = 0.0 + # Date + for r in range(ws.nrows): + for c in range(ws.ncols): + if ws.cell(r, c).ctype == 3: + dt = xlrd.xldate_as_tuple(ws.cell(r, c).value, wb.datemode) + result['date_value'] = dt + break + if result['date_value']: + break + + elif ext in ('.xlsx',): + wb = openpyxl.load_workbook(input_path, data_only=True) + ws = wb.active + result['project_name'] = str(ws.cell(2, 2).value or '') + result['contractor'] = str(ws.cell(3, 2).value or '') + result['contract_section'] = str(ws.cell(2, 10).value or '') + process_map = { + '清理表土': (5, 10), + '修平田地': (6, 10), + '田埂修筑': (7, 10), + } + for pname, (r, c) in process_map.items(): + cv = ws.cell(r, c).value + try: + result['quantities'][pname] = float(cv) if cv else 0.0 + except (ValueError, TypeError): + result['quantities'][pname] = 0.0 + else: + raise ValueError(f"不支持的文件格式: {ext},请提供 .xls 或 .xlsx 文件") + + return result + + +# ══════════════════════════════════════════════════════════════════════ +# Output document generators +# ══════════════════════════════════════════════════════════════════════ + +def generate_report_form(wb, data, params): + """ + 输出文件1:计量报验单 + 文件名: 01_{field_name}单元工程计量报验单.xlsx + Template: 3.3.3-01.01.01.001.1 + """ + field = params['field_name'] + proj = params.get('project_name') or data.get('project_name', '') + contractor = params.get('contractor') or data.get('contractor', '') + contract_section = params.get('contract_section') or data.get('contract_section', '') + supervision = params.get('supervision_company', '') + + ws = wb.active + ws.title = '计量报验单' + + # Column widths (1-indexed) + set_col_widths(ws, {1: 16, 2: 16, 3: 16, 4: 14, 5: 14, 6: 12, 7: 12, 8: 10, 9: 10}) + set_row_heights(ws, {1: 36, 2: 24, 3: 24, 4: 30, 5: 24, 6: 24, 7: 24, 8: 24, + 9: 24, 10: 24, 11: 24, 12: 30, 13: 24, 14: 24, 15: 24}) + + # Row 1: Title + merge_and_set(ws, 1, 1, 1, 9, + f'{field}单元工程计量报验单', + font=Font(name='宋体', size=16, bold=True), + alignment=CENTER_ALIGN) + + # Row 2: 项目名称 & 合同段 + merge_and_set(ws, 2, 2, 1, 2, '项目名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 2, 2, 3, 7, proj, font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 2, 2, 8, 9, f'合同段 {contract_section}', font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 3: 致 监理单位 + merge_and_set(ws, 3, 3, 1, 1, '致:', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 3, 3, 2, 9, supervision, font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 4: 已完成...单元工程 + merge_and_set(ws, 4, 4, 1, 4, + '我方按承包合同约定,已完成了', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 4, 4, 5, 7, field, font=NORMAL_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 4, 4, 8, 9, + '单元工程(工序)的施工,其', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 5: 工程质量... + merge_and_set(ws, 5, 5, 1, 9, + '工程质量已经检查合格,并对工程量进行了计量测量,现提交计量结果,请予以核准。', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 6: 附件: + merge_and_set(ws, 6, 6, 1, 9, '附件:', font=SIGN_FONT, alignment=LEFT_ALIGN) + + # Row 7-9: Attachment items + attachments = [ + ('1、工序工程计量统计表', '1'), + ('2、工程量测量资料', ''), + ('3、工序工程计量签证表', '3'), + ] + for i, (desc, pages) in enumerate(attachments): + r = 7 + i + merge_and_set(ws, r, r, 1, 1, '', font=NORMAL_FONT) # spacing + merge_and_set(ws, r, r, 2, 6, desc, font=NORMAL_FONT, alignment=LEFT_ALIGN) + if pages: + apply_cell(ws, r, 8, float(pages), font=NORMAL_FONT, alignment=CENTER_ALIGN) + apply_cell(ws, r, 9, '页', font=NORMAL_FONT, alignment=CENTER_ALIGN) + + # Row 10: 承包单位 + merge_and_set(ws, 10, 10, 1, 3, + f'承包单位(盖章): {contractor}', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 10, 10, 4, 9, '', font=NORMAL_FONT) + + # Row 11: 项目经理 + merge_and_set(ws, 11, 11, 1, 3, + '项目经理(签字):', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 11, 11, 4, 9, '', font=NORMAL_FONT) + + # Row 12: 日期 + merge_and_set(ws, 12, 12, 1, 3, + '日 期:', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 12, 12, 4, 9, '', font=NORMAL_FONT) + + # Row 13: 审查意见 + merge_and_set(ws, 13, 13, 1, 5, + '监理机构审查意见:同意。', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 13, 13, 6, 9, + '项目承担单位审查意见:同意。', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 14: 签字 + merge_and_set(ws, 14, 14, 1, 3, + '监理工程师(签字):', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 14, 14, 6, 7, + '现场负责人(签字):', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 15: 日期 + merge_and_set(ws, 15, 15, 1, 3, + '日 期:', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 15, 15, 4, 5, '', font=NORMAL_FONT) + merge_and_set(ws, 15, 15, 6, 7, + '日 期:', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + apply_cell(ws, 14, 8, '', font=NORMAL_FONT) + apply_cell(ws, 14, 9, '', font=NORMAL_FONT) + apply_cell(ws, 15, 8, '', font=NORMAL_FONT) + apply_cell(ws, 15, 9, '', font=NORMAL_FONT) + + +def generate_statistics_table(wb, data, params): + """ + 输出文件2:工序工程量统计表 + 文件名: 02_{field_name}单元工程工序工程量统计表.xlsx + Template: 3.3.3-01.01.01.001.2 + """ + field = params['field_name'] + proj = params.get('project_name') or data.get('project_name', '') + contractor = params.get('contractor') or data.get('contractor', '') + contract_section = params.get('contract_section') or data.get('contract_section', '') + qty = data['quantities'] + + ws = wb.active + ws.title = '工序工程量统计表' + + set_col_widths(ws, {1: 8, 2: 16, 3: 10, 4: 14, 5: 14, 6: 14, + 7: 8, 8: 8, 9: 8, 10: 8, 11: 8, 12: 8, 13: 8, 14: 8, 15: 8}) + set_row_heights(ws, {1: 36, 2: 24, 3: 24, 4: 30, 5: 24, 6: 24, 7: 24, + 8: 24, 9: 24, 10: 24, 11: 24}) + + # Row 1: Title + merge_and_set(ws, 1, 1, 1, 15, + f'{field}单元工程工序工程量统计表', + font=Font(name='宋体', size=16, bold=True), + alignment=CENTER_ALIGN) + + # Row 2: 项目名称 + merge_and_set(ws, 2, 2, 1, 2, '项目名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 2, 2, 3, 12, proj, font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 2, 2, 14, 15, f'合同段 {contract_section}', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + apply_cell(ws, 2, 13, '', font=NORMAL_FONT) + + # Row 3: 承包单位 + merge_and_set(ws, 3, 3, 1, 2, '承包单位', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 3, 3, 3, 15, contractor, font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 4: Table header row 1 + merge_and_set(ws, 4, 5, 1, 1, '序号', font=HEADER_FONT, alignment=CENTER_ALIGN, + border=THIN_BORDER, fill=HEADER_FILL) + merge_and_set(ws, 4, 5, 2, 2, '工程验收段名称及部位', font=HEADER_FONT, + alignment=CENTER_ALIGN, border=THIN_BORDER, fill=HEADER_FILL) + merge_and_set(ws, 4, 4, 3, 3, '工程量', font=HEADER_FONT, + alignment=CENTER_ALIGN, border=THIN_BORDER, fill=HEADER_FILL) + merge_and_set(ws, 4, 4, 4, 6, '工 序 工 程 量', font=HEADER_FONT, + alignment=CENTER_ALIGN, border=THIN_BORDER, fill=HEADER_FILL) + # fill remaining header cells + for c in range(7, 16): + ws.cell(row=4, column=c).border = THIN_BORDER + ws.cell(row=4, column=c).fill = HEADER_FILL + for c in range(7, 16): + ws.cell(row=5, column=c).border = THIN_BORDER + ws.cell(row=5, column=c).fill = HEADER_FILL + + # Row 5: Sub-header for 清理表土, 修平田地, 田埂修筑 + apply_cell(ws, 5, 3, '', font=HEADER_FONT, alignment=CENTER_ALIGN, + border=THIN_BORDER, fill=HEADER_FILL) + sub_headers = {4: '清理表土', 5: '修平田地', 6: '田埂修筑'} + for c, text in sub_headers.items(): + apply_cell(ws, 5, c, text, font=HEADER_FONT, alignment=CENTER_ALIGN, + border=THIN_BORDER, fill=HEADER_FILL) + + # Row 6: Data + hm2 = '' + apply_cell(ws, 6, 1, 1, font=NORMAL_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER) + apply_cell(ws, 6, 2, field, font=NORMAL_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER) + apply_cell(ws, 6, 3, hm2, font=NORMAL_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER) + for c_idx, pname in [(4, '清理表土'), (5, '修平田地'), (6, '田埂修筑')]: + val = qty.get(pname, 0) + apply_cell(ws, 6, c_idx, val if val else '', + font=NORMAL_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER, + number_format='#,##0.00' if val else None) + for c in range(7, 16): + apply_cell(ws, 6, c, '', font=NORMAL_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER) + + # Row 7: 合计 + merge_and_set(ws, 7, 7, 1, 2, '合 计', font=SIGN_FONT, alignment=CENTER_ALIGN, + border=THIN_BORDER) + apply_cell(ws, 7, 3, '', font=SIGN_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER) + for c_idx, pname in [(4, '清理表土'), (5, '修平田地'), (6, '田埂修筑')]: + val = qty.get(pname, 0) + apply_cell(ws, 7, c_idx, val if val else '', + font=SIGN_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER, + number_format='#,##0.00' if val else None) + for c in range(7, 16): + apply_cell(ws, 7, c, '', font=SIGN_FONT, alignment=CENTER_ALIGN, border=THIN_BORDER) + + # Row 8-11: Signature area + sig_labels = [ + ('承包单位(盖章):', '监理机构(盖章):', '项目承担单位(盖章):'), + ('技术负责人:', '监理工程师:', '现场负责人:'), + ('日 期:', '日 期:', '日 期:'), + ] + block_cols = [(1, 5), (6, 10), (11, 15)] + for ri, (l1, l2, l3) in enumerate(sig_labels): + r = 8 + ri + merge_and_set(ws, r, r, 1, 5, l1, font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, r, r, 6, 10, l2, font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, r, r, 11, 15, l3, font=NORMAL_FONT, alignment=LEFT_ALIGN) + + +def generate_certificate(wb, data, params, process_name, quantity, date_val=None): + """ + 输出文件3.x:工序工程量签证 + Template: 3.3.3-01.01.01.001.3.01.xx + """ + field = params['field_name'] + proj = params.get('project_name') or data.get('project_name', '') + contractor = params.get('contractor') or data.get('contractor', '') + contract_section = params.get('contract_section') or data.get('contract_section', '') + supervision = params.get('supervision_company', '') + project_owner = params.get('project_owner', '') + sub_project = params.get('sub_project_name', '水浇地15~25°区域') + unit_eng = params.get('unit_engineer_name', '田块修筑') + + ws = wb.active + ws.title = '工程量签证' + + set_col_widths(ws, {1: 16, 2: 12, 3: 14, 4: 14, 5: 14, + 6: 12, 7: 12, 8: 12, 9: 12, 10: 10, 11: 10, 12: 10}) + set_row_heights(ws, {1: 36, 2: 24, 3: 24, 4: 24, 5: 24, 6: 24, + 7: 30, 8: 30, 9: 24, 10: 24, 11: 24, 12: 24, + 13: 24, 14: 24, 15: 36, 16: 36, 17: 30, 18: 24, + 19: 24, 20: 24, 21: 24, 22: 24}) + + # Row 1: Title + merge_and_set(ws, 1, 1, 1, 12, + '工序(验收段)工程量签证', + font=Font(name='宋体', size=16, bold=True), + alignment=CENTER_ALIGN) + + # Row 2: 项目名称 / 合同段 + merge_and_set(ws, 2, 2, 1, 2, '项目名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 2, 2, 3, 8, proj, font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 2, 2, 10, 12, f'合同段 {contract_section}', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + apply_cell(ws, 2, 9, '', font=NORMAL_FONT) + + # Row 3: 单项/分部/单元/工程名称 + merge_and_set(ws, 3, 3, 1, 2, '单项工程名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 3, 3, 3, 5, '土地平整工程', font=NORMAL_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 3, 3, 6, 8, '分部工程名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 3, 3, 9, 12, sub_project, font=NORMAL_FONT, alignment=CENTER_ALIGN) + + # Row 4: 单位/单元工程 + merge_and_set(ws, 4, 4, 1, 2, '单位工程名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 4, 4, 3, 5, unit_eng, font=NORMAL_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 4, 4, 6, 8, '单元工程名称', font=SIGN_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 4, 4, 9, 12, field, font=NORMAL_FONT, alignment=CENTER_ALIGN) + + # Row 5-6: 致 业主/监理 + merge_and_set(ws, 5, 5, 1, 1, '致:', font=SIGN_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 5, 5, 2, 12, project_owner, font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 6, 6, 1, 1, '', font=NORMAL_FONT) + merge_and_set(ws, 6, 6, 2, 12, supervision, font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 7: 我方按设计要求完成... + merge_and_set(ws, 7, 7, 1, 4, + '我方按设计要求完成', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 7, 7, 5, 9, process_name, font=NORMAL_FONT, alignment=CENTER_ALIGN) + merge_and_set(ws, 7, 7, 10, 12, + '工序验收段施工,', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 8: 经工序... + merge_and_set(ws, 8, 8, 1, 12, + '经工序质量评定合格,请予以对其工程量进行核定。', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 9: 承包单位 + merge_and_set(ws, 9, 9, 1, 3, '承包单位(盖章):', font=SIGN_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 9, 9, 4, 12, contractor, font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 10: 项目经理 + merge_and_set(ws, 10, 10, 1, 3, '项目经理(签字):', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 10, 10, 4, 6, '', font=NORMAL_FONT) + merge_and_set(ws, 10, 10, 7, 12, '', font=NORMAL_FONT) + + # Row 11: 日期 + merge_and_set(ws, 11, 11, 1, 3, '日 期:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 11, 11, 4, 12, '', font=NORMAL_FONT) + + # Row 12: 签证内容及图示 + merge_and_set(ws, 12, 12, 1, 12, + '签证内容及图示:', + font=SIGN_FONT, alignment=LEFT_ALIGN) + + # Row 13: 1、田块 + merge_and_set(ws, 13, 13, 1, 4, f'1、{field}', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 13, 13, 5, 12, + '验收段的工序工程量', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 14: 2、图示 + merge_and_set(ws, 14, 14, 1, 12, + '2、图示详见施工图册', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 15-16: blank spacing for diagram + merge_and_set(ws, 15, 15, 1, 12, '', font=NORMAL_FONT) + merge_and_set(ws, 16, 16, 1, 12, '', font=NORMAL_FONT) + + # Row 17: 工程量计算 + merge_and_set(ws, 17, 17, 1, 12, + '工程量计算:', + font=SIGN_FONT, alignment=LEFT_ALIGN) + + # Row 18: 1、工序名称 + 数量 + quantity_str = f'{quantity:.2f} m³' if quantity else '0.00 m³' + merge_and_set(ws, 18, 18, 1, 4, f'1、{process_name}', font=SIGN_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 18, 18, 5, 12, quantity_str, font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 19: 2、表达式 + merge_and_set(ws, 19, 19, 1, 4, '2、工程量计算表达式', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 19, 19, 5, 12, '', font=NORMAL_FONT) + + # Row 20: Signatures row 1 + merge_and_set(ws, 20, 20, 1, 4, '承包单位(盖章):', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 20, 20, 5, 8, '监理机构(盖章):', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 20, 20, 9, 12, '项目承担单位(盖章):', + font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 21: Signatures row 2 + merge_and_set(ws, 21, 21, 1, 2, '技术负责人:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 21, 21, 5, 6, '监理工程师:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 21, 21, 9, 10, '现场负责人:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + + # Row 22: Signatures row 3 (dates) + merge_and_set(ws, 22, 22, 1, 2, '日 期:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 22, 22, 3, 4, '', font=NORMAL_FONT) + merge_and_set(ws, 22, 22, 5, 6, '日 期:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 22, 22, 7, 8, '', font=NORMAL_FONT) + merge_and_set(ws, 22, 22, 9, 10, '日 期:', font=NORMAL_FONT, alignment=LEFT_ALIGN) + merge_and_set(ws, 22, 22, 11, 12, '', font=NORMAL_FONT) + + # Apply full borders to all rows (for visual consistency) + for r in range(1, 23): + for c in range(1, 13): + cell = ws.cell(row=r, column=c) + if cell.border == Border(): + cell.border = THIN_BORDER + + +# ══════════════════════════════════════════════════════════════════════ +# Main +# ══════════════════════════════════════════════════════════════════════ + +def main(): + if len(sys.argv) > 1: + params_raw = sys.argv[1] + try: + p = json.loads(params_raw) + except json.JSONDecodeError: + print(json.dumps({"error": f"无法解析参数 JSON: {params_raw}"})) + sys.exit(1) + elif 'PARAMS_FILE' in os.environ: + with open(os.environ['PARAMS_FILE'], 'r') as f: + p = json.load(f) + else: + p = {} + # For development: use env vars as fallback + for key in ('input_path', 'output_dir', 'field_name', 'sub_project_name', + 'unit_engineer_name', 'project_name', 'contractor', + 'contract_section', 'supervision_company', 'project_owner'): + if key.upper() in os.environ: + p[key] = os.environ[key.upper()] + + required = ['input_path', 'output_dir'] + missing = [k for k in required if k not in p or not p[k]] + if missing: + print(json.dumps({ + "error": f"缺少必要参数: {', '.join(missing)}", + "usage": "python generate.py '{\"input_path\": \"...\", \"output_dir\": \"...\"}'" + })) + sys.exit(1) + + input_path = p['input_path'] + output_dir = p['output_dir'] + os.makedirs(output_dir, exist_ok=True) + + # ── 1. Read input ────────────────────────────────────────────── + print(json.dumps({"step": "read_input", "status": "start", "path": input_path})) + try: + data = read_input(input_path) + except Exception as e: + print(json.dumps({"error": f"读取结算表失败: {str(e)}"})) + sys.exit(1) + + print(json.dumps({ + "step": "read_input", + "status": "ok", + "project_name": data['project_name'], + "contractor": data['contractor'], + "quantities": data['quantities'], + })) + + qty = data['quantities'] + + # ── 2. Collect params with fallback ───────────────────────────── + params = { + 'field_name': p.get('field_name', 'T3田块'), + 'sub_project_name': p.get('sub_project_name', '水浇地15~25°区域'), + 'unit_engineer_name': p.get('unit_engineer_name', '田块修筑'), + 'project_name': p.get('project_name') or data.get('project_name', ''), + 'contractor': p.get('contractor') or data.get('contractor', ''), + 'contract_section': p.get('contract_section') or data.get('contract_section', ''), + 'supervision_company': p.get('supervision_company', '曲靖南天监理有限公司'), + 'project_owner': p.get('project_owner', '巧家县国土资源局'), + } + + # ── 3. Generate output documents ──────────────────────────────── + generated = [] + + # 3.1 计量报验单 + wb1 = openpyxl.Workbook() + generate_report_form(wb1, data, params) + f1 = os.path.join(output_dir, f'01_{params["field_name"]}单元工程计量报验单.xlsx') + wb1.save(f1) + generated.append(f1) + print(json.dumps({"step": "generate", "file": f1, "status": "ok"})) + + # 3.2 工序工程量统计表 + wb2 = openpyxl.Workbook() + generate_statistics_table(wb2, data, params) + f2 = os.path.join(output_dir, f'02_{params["field_name"]}单元工程工序工程量统计表.xlsx') + wb2.save(f2) + generated.append(f2) + print(json.dumps({"step": "generate", "file": f2, "status": "ok"})) + + # 3.3 清理表土签证 + proc_name = '清理表土' + q_val = qty.get(proc_name, 0) + wb31 = openpyxl.Workbook() + generate_certificate(wb31, data, params, proc_name, q_val) + f31 = os.path.join(output_dir, f'03.01_{proc_name}工序工程量签证.xlsx') + wb31.save(f31) + generated.append(f31) + print(json.dumps({"step": "generate", "file": f31, "status": "ok"})) + + # 3.4 修平田地签证 + proc_name = '修平田地' + q_val = qty.get(proc_name, 0) + wb32 = openpyxl.Workbook() + generate_certificate(wb32, data, params, proc_name, q_val) + f32 = os.path.join(output_dir, f'03.02_{proc_name}工序工程量签证.xlsx') + wb32.save(f32) + generated.append(f32) + print(json.dumps({"step": "generate", "file": f32, "status": "ok"})) + + # 3.5 田埂修筑签证 + proc_name = '田埂修筑' + q_val = qty.get(proc_name, 0) + wb33 = openpyxl.Workbook() + generate_certificate(wb33, data, params, proc_name, q_val) + f33 = os.path.join(output_dir, f'03.03_{proc_name}工序工程量签证.xlsx') + wb33.save(f33) + generated.append(f33) + print(json.dumps({"step": "generate", "file": f33, "status": "ok"})) + + # ── Done ──────────────────────────────────────────────────────── + result = { + "status": "success", + "output_dir": output_dir, + "files": generated, + "summary": { + "field": params['field_name'], + "project": params['project_name'], + "contractor": params['contractor'], + "quantities": { + "清理表土": round(qty.get('清理表土', 0), 2), + "修平田地": round(qty.get('修平田地', 0), 2), + "田埂修筑": round(qty.get('田埂修筑', 0), 2), + } + } + } + print(json.dumps(result)) + + +if __name__ == '__main__': + main() diff --git a/workflow.yaml b/workflow.yaml new file mode 100644 index 0000000..f63e5c2 --- /dev/null +++ b/workflow.yaml @@ -0,0 +1,94 @@ +name: 土地平整工程竣工结算资料(部分)生成 +description: > + 从土地平整工程单项工程结算表中提取清理表土、修平田地、田埂修筑三项工序的工程量, + 自动生成 T3 田块单元工程竣工结算资料套表,包含: + 计量报验单、工序工程量统计表、三份工序工程量签证。 +version: 1.0.0 +author: SuiteForge +tags: [竣工结算, 土地平整, 工程量, 结算资料] +category: 竣工结算 + +params: + input_path: + type: string + required: true + desc: > + 用户提供的土地平整工程单项工程工作量结算表路径,支持 .xls / .xlsx 格式。 + 该表格应包含"清理表土""修平田地""田埂修筑"三项工序的工程量。 + 示例:/home/user/data/单项工程结算表.xls + output_dir: + type: string + required: true + desc: > + 输出目录路径,生成的竣工结算资料文件存放于此。 + 示例:/home/user/output/ + field_name: + type: string + required: false + default: T3田块 + desc: > + 单元工程名称,如 T3田块 / T4田块 / T5田块 等。 + 此名称会出现在所有输出文档的标题及相关位置。 + 默认值:T3田块 + sub_project_name: + type: string + required: false + default: 水浇地15~25°区域 + desc: > + 分部工程名称,出现在工程量签证文档中。 + 默认值:水浇地15~25°区域 + unit_engineer_name: + type: string + required: false + default: 田块修筑 + desc: > + 单位工程名称,出现在工程量签证文档中。 + 默认值:田块修筑 + project_name: + type: string + required: false + desc: > + 项目名称,不填则自动从结算表中提取。 + contractor: + type: string + required: false + desc: > + 承包单位名称,不填则自动从结算表中提取。 + contract_section: + type: string + required: false + desc: > + 合同段,不填则自动从结算表中提取或留空。 + supervision_company: + type: string + required: false + default: 曲靖南天监理有限公司 + desc: > + 监理单位名称。 + 默认值:曲靖南天监理有限公司 + project_owner: + type: string + required: false + default: 巧家县国土资源局 + desc: > + 建设单位/业主单位名称。 + 默认值:巧家县国土资源局 + +base_image: gis-base:latest + +steps: + - id: generate_docs + name: 生成竣工结算资料 + type: python + script_id: generate + params: + input_path: $params.input_path + output_dir: $params.output_dir + field_name: $params.field_name + sub_project_name: $params.sub_project_name + unit_engineer_name: $params.unit_engineer_name + project_name: $params.project_name + contractor: $params.contractor + contract_section: $params.contract_section + supervision_company: $params.supervision_company + project_owner: $params.project_owner