#!/usr/bin/env python3 """ 土地平整工程竣工结算资料(部分)生成 v2.0 从用户提供的工程量结算前后对比表中提取水浇地15~25°区域各田块数据, 生成分部工程工程量汇总表及各田块竣工结算资料套表。 """ import sys import json import os import re try: import openpyxl from openpyxl.styles import ( Font, Border, Side, Alignment, PatternFill ) from openpyxl.utils import get_column_letter except ImportError: print(json.dumps({"error": "openpyxl 未安装"})) sys.exit(1) try: import xlrd HAS_XLRD = True except ImportError: HAS_XLRD = False # ══════════════════════════════════════════════════════════════════════ # Style definitions # ══════════════════════════════════════════════════════════════════════ THIN = Border(left=Side('thin'), right=Side('thin'), top=Side('thin'), bottom=Side('thin')) TITLE_FONT = Font(name='宋体', size=16, bold=True) HEADER_FONT = Font(name='宋体', size=10, bold=True) NORMAL = Font(name='宋体', size=10) BOLD = Font(name='宋体', size=10, bold=True) SMALL = Font(name='宋体', size=9) CA = Alignment(horizontal='center', vertical='center', wrap_text=True) LA = Alignment(horizontal='left', vertical='center', wrap_text=True) HDR_FILL = PatternFill(start_color='D9E2F3', end_color='D9E2F3', fill_type='solid') def ac(ws, r, c, v, font=None, align=None, border=None, fill=None, nf=None): """Set cell value + style.""" cell = ws.cell(row=r, column=c, value=v) if font: cell.font = font if align: cell.alignment = align if border: cell.border = border if fill: cell.fill = fill if nf: cell.number_format = nf return cell def ms(ws, r1, r2, c1, c2, v, font=None, align=None, border=None, fill=None): """Merge + set.""" ws.merge_cells(start_row=r1, start_column=c1, end_row=r2, end_column=c2) return ac(ws, r1, c1, v, font=font, align=align, border=border, fill=fill) def cw(ws, widths): for ci, w in widths.items(): ws.column_dimensions[get_column_letter(ci)].width = w def rh(ws, heights): for ri, h in heights.items(): ws.row_dimensions[ri].height = h # ══════════════════════════════════════════════════════════════════════ # Input parser — 鸡噜工程量结算前后对比表.xlsx # ══════════════════════════════════════════════════════════════════════ def parse_input(input_path): """ Read the 鸡噜工程量结算前后对比表.xlsx (sheet '土地平整工程'). Extract: - project_name - fields: [{'name':'T3', 'area':2.5638, '清理表土':7662.75, '修平田地':4807.15, '田埂修筑':86.66}, ...] Only parses section (一) 水浇地15~25°区域. """ result = {'project_name': '', 'fields': []} ext = os.path.splitext(input_path)[1].lower() if ext == '.xls': if not HAS_XLRD: raise RuntimeError("需安装 xlrd 以读取 .xls 文件") wb = xlrd.open_workbook(input_path) ws = wb.sheet_by_index(0) # assumes first sheet elif ext == '.xlsx': wb = openpyxl.load_workbook(input_path, data_only=True) # Find the right sheet sheet_name = None for sn in ('土地平整工程', wb.sheetnames[0]): if sn in wb.sheetnames: sheet_name = sn break if not sheet_name: sheet_name = wb.sheetnames[0] ws = wb[sheet_name] else: raise ValueError(f"不支持的文件格式: {ext}") # Extract project name from row 2 if ext == '.xls': raw = str(ws.cell(1, 0).value or '') # cell A2: 项目名称:xxx else: raw = str(ws.cell(2, 1).value or '') # cell A2: 项目名称:xxx m = re.search(r'项目名称[::]\s*(.+)', raw) if m: result['project_name'] = m.group(1).strip() else: result['project_name'] = raw # Now parse each field — find section (一) 水浇地15~25°区域 # Then read subsequent rows: field blocks (number + sub-rows for 3 processes) # Data structure in the spreadsheet: # 编号 | 项目名称 | 单位 | 工程量(原) | 单价 | 合价 | ... # (一) | 水浇地15~25°区域 | hm² | 40.2677 | ... # 1 | T3 | hm² | 2.5638 | ... # (1) | 清理表土 | m³ | 7662.75 | ... # (2) | 修平田地 | m³ | 4807.15 | ... # (3) | 田埂修筑 | m³ | 86.66 | ... # 2 | T4 | hm² | ... # ... def cell_val(r, c): if ext == '.xls': v = ws.cell(r, c).value return str(v) if v is not None else '' else: v = ws.cell(r, c).value return str(v) if v is not None else '' def cell_num(r, c, default=0.0): if ext == '.xls': v = ws.cell(r, c).value else: v = ws.cell(r, c).value try: return float(v) except (ValueError, TypeError): return default max_row = ws.nrows if ext == '.xls' else ws.max_row # Find start: (一) row start_row = None for r in range(1, max_row + 1 if ext == '.xls' else max_row + 1): rid = cell_val(r, 0 if ext == '.xls' else 1).strip() name = cell_val(r, 1 if ext == '.xls' else 2).strip() if '水浇地' in name and '15' in name: start_row = r break if start_row is None: raise ValueError("未找到'水浇地15~25°区域'段落") # Parse fields: look for numeric IDs followed by (1)/(2)/(3) process rows current_field = None for r in range(start_row + 1 if ext == '.xls' else start_row + 1, max_row + 1): rid = cell_val(r, 0 if ext == '.xls' else 1).strip() name = cell_val(r, 1 if ext == '.xls' else 2).strip() unit = cell_val(r, 2 if ext == '.xls' else 3).strip() # Stop at next section (二) if '(二)' in rid or ('水田' in name and '6' in name): break # Skip section header row itself if '(一)' in rid: continue # It's a process sub-row if rid in ('(1)', '(2)', '(3)') and current_field: proc_name = name qty = cell_num(r, 3 if ext == '.xls' else 4) if proc_name in ('清理表土', '修平田地', '田埂修筑'): current_field[proc_name] = qty continue # It's a field row (number + field name) try: int(rid) # numeric id = field row except ValueError: # Not a field row, skip continue area = cell_num(r, 3 if ext == '.xls' else 4) field_name_raw = name.strip() # Extract the core field name (e.g., "T3" from "T3" or "T3田块") field_name = field_name_raw # Save current field current_field = { 'name': field_name, 'area': area, 'field_display': f'{field_name}田块', '清理表土': 0, '修平田地': 0, '田埂修筑': 0, } result['fields'].append(current_field) return result # ══════════════════════════════════════════════════════════════════════ # Output: 分部工程工程量汇总表 # ══════════════════════════════════════════════════════════════════════ def generate_subitem_summary(wb, data, params): """ Generate the 分部工程工程量汇总表 (3.3.3-01.01.01.000) filled with all fields' data. """ proj = params.get('project_name') or data.get('project_name', '') contractor = params.get('contractor', '') contract_section = params.get('contract_section', '') fields = data.get('fields', []) ws = wb.active ws.title = '分部工程工程量汇总表' cw(ws, {1: 12, 2: 10, 3: 14, 4: 14, 5: 14, 6: 8, 7: 8, 8: 8, 9: 8, 10: 8}) rh(ws, {1: 36, 2: 24, 3: 24, 4: 30, 5: 24, 6: 22, 7: 22, 8: 22, 9: 22, 10: 22, 11: 22, 12: 22, 13: 22, 14: 22, 15: 22, 16: 22, 17: 22, 18: 22, 19: 22, 20: 22, 21: 22, 22: 22, 23: 22, 24: 24, 25: 24, 26: 24}) # Col A merges for data section (rows 6-21 or so) n_fields = len(fields) data_start = 6 # first data row data_end = data_start + n_fields - 1 # last data row total_row = data_end + 1 # 合计 row sig_start = total_row + 1 # signature rows start # Row 1: Title ms(ws, 1, 1, 1, 10, '水浇地15~25°区域分部工程工程量汇总表', font=TITLE_FONT, align=CA) # Row 2: 项目名称 ms(ws, 2, 2, 1, 1, '项目名称', font=BOLD, align=CA) ms(ws, 2, 2, 2, 10, proj, font=NORMAL, align=LA) # Row 3: 承包单位 ms(ws, 3, 3, 1, 1, '承包单位名称', font=BOLD, align=CA) ms(ws, 3, 3, 2, 8, contractor, font=NORMAL, align=LA) ms(ws, 3, 3, 9, 10, f'合同段 {contract_section}', font=NORMAL, align=LA) # Row 4: Table header line 1 ms(ws, 4, 5, 1, 1, '单元工程', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 5, 2, 2, '工 序 工 程 量', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) # Row 4 sub-headers ms(ws, 4, 4, 3, 3, '工程量', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 4, 4, 4, '清理表土', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 4, 5, 5, '修平田地', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 4, 6, 6, '田埂修筑', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) # Fill remaining header cells for c in range(7, 11): ac(ws, 4, c, '', border=THIN, fill=HDR_FILL) for c in range(3, 11): ac(ws, 5, c, '', border=THIN, fill=HDR_FILL) # Row 5 sub-header units ac(ws, 5, 3, 'hm²', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ac(ws, 5, 4, 'm³', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ac(ws, 5, 5, 'm³', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ac(ws, 5, 6, 'm³', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) # Data rows total_area = 0 total_cl = 0 total_xp = 0 total_tg = 0 for i, f in enumerate(fields): r = data_start + i fd = f.get('field_display', f['name']) ms(ws, r, r, 1, 1, fd, font=NORMAL, align=CA, border=THIN) ms(ws, r, r, 2, 2, '', font=NORMAL, border=THIN) # empty col B area = f.get('area', 0) cl = f.get('清理表土', 0) xp = f.get('修平田地', 0) tg = f.get('田埂修筑', 0) ac(ws, r, 3, area if area else '', font=NORMAL, align=CA, border=THIN, nf='#,##0.0000' if area else None) ac(ws, r, 4, cl if cl else '', font=NORMAL, align=CA, border=THIN, nf='#,##0.00' if cl else None) ac(ws, r, 5, xp if xp else '', font=NORMAL, align=CA, border=THIN, nf='#,##0.00' if xp else None) ac(ws, r, 6, tg if tg else '', font=NORMAL, align=CA, border=THIN, nf='#,##0.00' if tg else None) for c in range(7, 11): ac(ws, r, c, '', font=NORMAL, border=THIN) total_area += area total_cl += cl total_xp += xp total_tg += tg # 合计 row ms(ws, total_row, total_row, 1, 2, '合 计', font=BOLD, align=CA, border=THIN) ac(ws, total_row, 3, total_area if total_area else '', font=BOLD, align=CA, border=THIN, nf='#,##0.0000' if total_area else None) ac(ws, total_row, 4, total_cl if total_cl else '', font=BOLD, align=CA, border=THIN, nf='#,##0.00' if total_cl else None) ac(ws, total_row, 5, total_xp if total_xp else '', font=BOLD, align=CA, border=THIN, nf='#,##0.00' if total_xp else None) ac(ws, total_row, 6, total_tg if total_tg else '', font=BOLD, align=CA, border=THIN, nf='#,##0.00' if total_tg else None) for c in range(7, 11): ac(ws, total_row, c, '', font=BOLD, border=THIN) # Signature rows ms(ws, sig_start, sig_start, 1, 3, '承包单位(盖章)', font=NORMAL, align=LA) ms(ws, sig_start, sig_start, 4, 7, '监理机构(盖章)', font=NORMAL, align=LA) ms(ws, sig_start, sig_start, 8, 10, '项目承担单位(盖章)', font=NORMAL, align=LA) ms(ws, sig_start+1, sig_start+1, 1, 2, '技术负责人:', font=NORMAL, align=LA) ms(ws, sig_start+1, sig_start+1, 4, 5, '监理工程师:', font=NORMAL, align=LA) ms(ws, sig_start+1, sig_start+1, 8, 9, '现场负责人:', font=NORMAL, align=LA) ms(ws, sig_start+2, sig_start+2, 1, 2, '日 期:', font=NORMAL, align=LA) ms(ws, sig_start+2, sig_start+2, 4, 5, '日 期:', font=NORMAL, align=LA) ms(ws, sig_start+2, sig_start+2, 8, 9, '日 期:', font=NORMAL, align=LA) return fields # return for downstream use # ══════════════════════════════════════════════════════════════════════ # Output: 计量报验单 (per field) # ══════════════════════════════════════════════════════════════════════ def generate_report(wb, data, params, field_info): field_display = field_info.get('field_display', 'T3田块') field_name = field_info.get('name', 'T3') proj = params.get('project_name') or data.get('project_name', '') contractor = params.get('contractor', '') cs = params.get('contract_section', '') supervision = params.get('supervision_company', '曲靖南天监理有限公司') ws = wb.active ws.title = '计量报验单' cw(ws, {1: 16, 2: 16, 3: 16, 4: 14, 5: 14, 6: 12, 7: 12, 8: 10, 9: 10}) rh(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}) ms(ws, 1, 1, 1, 9, f'{field_display}单元工程计量报验单', font=TITLE_FONT, align=CA) ms(ws, 2, 2, 1, 2, '项目名称', font=BOLD, align=CA) ms(ws, 2, 2, 3, 7, proj, font=NORMAL, align=LA) ms(ws, 2, 2, 8, 9, f'合同段 {cs}', font=NORMAL, align=LA) ms(ws, 3, 3, 1, 1, '致:', font=BOLD, align=CA) ms(ws, 3, 3, 2, 9, supervision, font=NORMAL, align=LA) ms(ws, 4, 4, 1, 4, '我方按承包合同约定,已完成了', font=NORMAL, align=LA) ms(ws, 4, 4, 5, 7, field_display, font=NORMAL, align=CA) ms(ws, 4, 4, 8, 9, '单元工程(工序)的施工,其', font=NORMAL, align=LA) ms(ws, 5, 5, 1, 9, '工程质量已经检查合格,并对工程量进行了计量测量,现提交计量结果,请予以核准。', font=NORMAL, align=LA) ms(ws, 6, 6, 1, 9, '附件:', font=BOLD, align=LA) attachs = [('1、工序工程计量统计表', '1'), ('2、工程量测量资料', ''), ('3、工序工程计量签证表', '3')] for i, (desc, pages) in enumerate(attachs): r = 7 + i ms(ws, r, r, 1, 1, '', font=NORMAL) ms(ws, r, r, 2, 6, desc, font=NORMAL, align=LA) if pages: ac(ws, r, 8, float(pages), font=NORMAL, align=CA) ac(ws, r, 9, '页', font=NORMAL, align=CA) ms(ws, 10, 10, 1, 3, f'承包单位(盖章): {contractor}', font=NORMAL, align=LA) ms(ws, 10, 10, 4, 9, '', font=NORMAL) ms(ws, 11, 11, 1, 3, '项目经理(签字):', font=NORMAL, align=LA) ms(ws, 11, 11, 4, 9, '', font=NORMAL) ms(ws, 12, 12, 1, 3, '日 期:', font=NORMAL, align=LA) ms(ws, 12, 12, 4, 9, '', font=NORMAL) ms(ws, 13, 13, 1, 5, '监理机构审查意见:同意。', font=NORMAL, align=LA) ms(ws, 13, 13, 6, 9, '项目承担单位审查意见:同意。', font=NORMAL, align=LA) ms(ws, 14, 14, 1, 3, '监理工程师(签字):', font=NORMAL, align=LA) ms(ws, 14, 14, 6, 7, '现场负责人(签字):', font=NORMAL, align=LA) ms(ws, 15, 15, 1, 3, '日 期:', font=NORMAL, align=LA) ms(ws, 15, 15, 6, 7, '日 期:', font=NORMAL, align=LA) # ══════════════════════════════════════════════════════════════════════ # Output: 工序工程量统计表 (per field) # ══════════════════════════════════════════════════════════════════════ def generate_statistics(wb, data, params, field_info): field_display = field_info.get('field_display', 'T3田块') proj = params.get('project_name') or data.get('project_name', '') contractor = params.get('contractor', '') cs = params.get('contract_section', '') qty = field_info ws = wb.active ws.title = '工序工程量统计表' cw(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}) rh(ws, {1: 36, 2: 24, 3: 24, 4: 30, 5: 24, 6: 24, 7: 24, 8: 24, 9: 24, 10: 24, 11: 24}) ms(ws, 1, 1, 1, 15, f'{field_display}单元工程工序工程量统计表', font=TITLE_FONT, align=CA) ms(ws, 2, 2, 1, 2, '项目名称', font=BOLD, align=CA) ms(ws, 2, 2, 3, 12, proj, font=NORMAL, align=LA) ms(ws, 2, 2, 14, 15, f'合同段 {cs}', font=NORMAL, align=LA) ac(ws, 2, 13, '', font=NORMAL) ms(ws, 3, 3, 1, 2, '承包单位', font=BOLD, align=CA) ms(ws, 3, 3, 3, 15, contractor, font=NORMAL, align=LA) # Header rows ms(ws, 4, 5, 1, 1, '序号', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 5, 2, 2, '工程验收段名称及部位', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 4, 3, 3, '工程量', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) ms(ws, 4, 4, 4, 6, '工 序 工 程 量', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) for c in range(7, 16): ac(ws, 4, c, '', border=THIN, fill=HDR_FILL) ac(ws, 5, c, '', border=THIN, fill=HDR_FILL) ac(ws, 5, 3, '', font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) for c, lbl in [(4, '清理表土'), (5, '修平田地'), (6, '田埂修筑')]: ac(ws, 5, c, lbl, font=HEADER_FONT, align=CA, border=THIN, fill=HDR_FILL) # Data row ac(ws, 6, 1, 1, font=NORMAL, align=CA, border=THIN) ac(ws, 6, 2, field_display, font=NORMAL, align=CA, border=THIN) ac(ws, 6, 3, '', font=NORMAL, align=CA, border=THIN) for c, pn in [(4, '清理表土'), (5, '修平田地'), (6, '田埂修筑')]: val = qty.get(pn, 0) ac(ws, 6, c, val if val else '', font=NORMAL, align=CA, border=THIN, nf='#,##0.00' if val else None) for c in range(7, 16): ac(ws, 6, c, '', font=NORMAL, align=CA, border=THIN) # 合计 row ms(ws, 7, 7, 1, 2, '合 计', font=BOLD, align=CA, border=THIN) ac(ws, 7, 3, '', font=BOLD, align=CA, border=THIN) for c, pn in [(4, '清理表土'), (5, '修平田地'), (6, '田埂修筑')]: val = qty.get(pn, 0) ac(ws, 7, c, val if val else '', font=BOLD, align=CA, border=THIN, nf='#,##0.00' if val else None) for c in range(7, 16): ac(ws, 7, c, '', font=BOLD, align=CA, border=THIN) # Signature sigs = [('承包单位(盖章):', '监理机构(盖章):', '项目承担单位(盖章):'), ('技术负责人:', '监理工程师:', '现场负责人:'), ('日 期:', '日 期:', '日 期:')] for ri, (l1, l2, l3) in enumerate(sigs): r = 8 + ri ms(ws, r, r, 1, 5, l1, font=NORMAL, align=LA) ms(ws, r, r, 6, 10, l2, font=NORMAL, align=LA) ms(ws, r, r, 11, 15, l3, font=NORMAL, align=LA) # ══════════════════════════════════════════════════════════════════════ # Output: 工序工程量签证 (per process) # ══════════════════════════════════════════════════════════════════════ def generate_certificate(wb, data, params, field_info, process_name): field_display = field_info.get('field_display', 'T3田块') proj = params.get('project_name') or data.get('project_name', '') contractor = params.get('contractor', '') cs = params.get('contract_section', '') supervision = params.get('supervision_company', '曲靖南天监理有限公司') owner = params.get('project_owner', '巧家县国土资源局') sub_project = params.get('sub_project_name', '水浇地15~25°区域') unit_eng = params.get('unit_engineer_name', '田块修筑') quantity = field_info.get(process_name, 0) ws = wb.active ws.title = '工程量签证' cw(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}) rh(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}) ms(ws, 1, 1, 1, 12, '工序(验收段)工程量签证', font=TITLE_FONT, align=CA) ms(ws, 2, 2, 1, 2, '项目名称', font=BOLD, align=CA) ms(ws, 2, 2, 3, 8, proj, font=NORMAL, align=LA) ms(ws, 2, 2, 10, 12, f'合同段 {cs}', font=NORMAL, align=LA) ac(ws, 2, 9, '', font=NORMAL) ms(ws, 3, 3, 1, 2, '单项工程名称', font=BOLD, align=CA) ms(ws, 3, 3, 3, 5, '土地平整工程', font=NORMAL, align=CA) ms(ws, 3, 3, 6, 8, '分部工程名称', font=BOLD, align=CA) ms(ws, 3, 3, 9, 12, sub_project, font=NORMAL, align=CA) ms(ws, 4, 4, 1, 2, '单位工程名称', font=BOLD, align=CA) ms(ws, 4, 4, 3, 5, unit_eng, font=NORMAL, align=CA) ms(ws, 4, 4, 6, 8, '单元工程名称', font=BOLD, align=CA) ms(ws, 4, 4, 9, 12, field_display, font=NORMAL, align=CA) ms(ws, 5, 5, 1, 1, '致:', font=BOLD, align=LA) ms(ws, 5, 5, 2, 12, owner, font=NORMAL, align=LA) ms(ws, 6, 6, 1, 1, '', font=NORMAL) ms(ws, 6, 6, 2, 12, supervision, font=NORMAL, align=LA) ms(ws, 7, 7, 1, 4, '我方按设计要求完成', font=NORMAL, align=LA) ms(ws, 7, 7, 5, 9, process_name, font=NORMAL, align=CA) ms(ws, 7, 7, 10, 12, '工序验收段施工,', font=NORMAL, align=LA) ms(ws, 8, 8, 1, 12, '经工序质量评定合格,请予以对其工程量进行核定。', font=NORMAL, align=LA) ms(ws, 9, 9, 1, 3, '承包单位(盖章):', font=BOLD, align=LA) ms(ws, 9, 9, 4, 12, contractor, font=NORMAL, align=LA) ms(ws, 10, 10, 1, 3, '项目经理(签字):', font=NORMAL, align=LA) ms(ws, 10, 10, 4, 12, '', font=NORMAL) ms(ws, 11, 11, 1, 3, '日 期:', font=NORMAL, align=LA) ms(ws, 11, 11, 4, 12, '', font=NORMAL) ms(ws, 12, 12, 1, 12, '签证内容及图示:', font=BOLD, align=LA) ms(ws, 13, 13, 1, 4, f'1、{field_display}', font=NORMAL, align=LA) ms(ws, 13, 13, 5, 12, '验收段的工序工程量', font=NORMAL, align=LA) ms(ws, 14, 14, 1, 12, '2、图示详见施工图册', font=NORMAL, align=LA) ms(ws, 15, 15, 1, 12, '', font=NORMAL) ms(ws, 16, 16, 1, 12, '', font=NORMAL) ms(ws, 17, 17, 1, 12, '工程量计算:', font=BOLD, align=LA) qty_str = f'{quantity:.2f} m³' if quantity else '0.00 m³' ms(ws, 18, 18, 1, 4, f'1、{process_name}', font=BOLD, align=LA) ms(ws, 18, 18, 5, 12, qty_str, font=NORMAL, align=LA) ms(ws, 19, 19, 1, 4, '2、工程量计算表达式', font=NORMAL, align=LA) ms(ws, 19, 19, 5, 12, '', font=NORMAL) ms(ws, 20, 20, 1, 4, '承包单位(盖章):', font=NORMAL, align=LA) ms(ws, 20, 20, 5, 8, '监理机构(盖章):', font=NORMAL, align=LA) ms(ws, 20, 20, 9, 12, '项目承担单位(盖章):', font=NORMAL, align=LA) ms(ws, 21, 21, 1, 2, '技术负责人:', font=NORMAL, align=LA) ms(ws, 21, 21, 5, 6, '监理工程师:', font=NORMAL, align=LA) ms(ws, 21, 21, 9, 10, '现场负责人:', font=NORMAL, align=LA) ms(ws, 22, 22, 1, 2, '日 期:', font=NORMAL, align=LA) ms(ws, 22, 22, 5, 6, '日 期:', font=NORMAL, align=LA) ms(ws, 22, 22, 9, 10, '日 期:', font=NORMAL, align=LA) # ══════════════════════════════════════════════════════════════════════ # Main # ══════════════════════════════════════════════════════════════════════ def main(): if len(sys.argv) > 1: p = json.loads(sys.argv[1]) elif 'PARAMS_FILE' in os.environ: with open(os.environ['PARAMS_FILE']) as f: p = json.load(f) else: p = {} for key in ('input_table_path', 'output_dir', 'project_name', 'contractor', 'contract_section', 'supervision_company', 'project_owner', 'sub_project_name', 'unit_engineer_name', 'generate_all_fields'): if key.upper() in os.environ: p[key] = os.environ[key.upper()] required = ['input_table_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)}", })) sys.exit(1) input_path = p['input_table_path'] output_dir = p['output_dir'] os.makedirs(output_dir, exist_ok=True) # ── 1. Parse input ───────────────────────────────────────────── print(json.dumps({"step": "parse_input", "status": "start", "path": input_path})) try: data = parse_input(input_path) except Exception as e: print(json.dumps({"error": f"解析输入表失败: {str(e)}"})) sys.exit(1) fields = data.get('fields', []) print(json.dumps({ "step": "parse_input", "status": "ok", "project": data.get('project_name', ''), "field_count": len(fields), "fields": [f['name'] for f in fields], })) # ── 2. Resolve params ────────────────────────────────────────── params = { 'project_name': p.get('project_name') or data.get('project_name', ''), 'contractor': p.get('contractor', ''), 'contract_section': p.get('contract_section', ''), 'sub_project_name': p.get('sub_project_name', '水浇地15~25°区域'), 'unit_engineer_name': p.get('unit_engineer_name', '田块修筑'), 'supervision_company': p.get('supervision_company', '曲靖南天监理有限公司'), 'project_owner': p.get('project_owner', '巧家县国土资源局'), } generate_all = p.get('generate_all_fields', 'true').lower() in ('true', '1', 'yes') # ── 3. Generate 分部工程工程量汇总表 ───────────────────────────── wb0 = openpyxl.Workbook() generate_subitem_summary(wb0, data, params) f0 = os.path.join(output_dir, '01_水浇地15~25°区域分部工程工程量汇总表.xlsx') wb0.save(f0) print(json.dumps({"step": "generate", "file": f0, "status": "ok"})) generated = [f0] # ── 4. Generate per-field documents ───────────────────────────── target_fields = fields if generate_all else [fields[0]] if fields else [] for field_info in target_fields: fd = field_info.get('field_display', field_info['name']) fn = field_info.get('name', fd) prefix = f'02_{fn}' # 报验单 wb1 = openpyxl.Workbook() generate_report(wb1, data, params, field_info) f1 = os.path.join(output_dir, f'{prefix}_{fd}单元工程计量报验单.xlsx') wb1.save(f1) generated.append(f1) print(json.dumps({"step": "generate", "file": f1, "status": "ok"})) # 统计表 wb2 = openpyxl.Workbook() generate_statistics(wb2, data, params, field_info) f2 = os.path.join(output_dir, f'{prefix}_{fd}单元工程工序工程量统计表.xlsx') wb2.save(f2) generated.append(f2) print(json.dumps({"step": "generate", "file": f2, "status": "ok"})) # 3x签证 for proc in ['清理表土', '修平田地', '田埂修筑']: wb3 = openpyxl.Workbook() generate_certificate(wb3, data, params, field_info, proc) f3 = os.path.join(output_dir, f'{prefix}_{proc}工序工程量签证.xlsx') wb3.save(f3) generated.append(f3) print(json.dumps({"step": "generate", "file": f3, "status": "ok"})) # ── Summary ────────────────────────────────────────────────────── total_summary = { "清理表土": round(sum(f.get('清理表土', 0) for f in fields), 2), "修平田地": round(sum(f.get('修平田地', 0) for f in fields), 2), "田埂修筑": round(sum(f.get('田埂修筑', 0) for f in fields), 2), } result = { "status": "success", "output_dir": output_dir, "files": generated, "summary": { "project": params['project_name'], "contractor": params['contractor'], "total_fields": len(fields), "generated_fields": len(target_fields), "total_quantities": total_summary, } } print(json.dumps(result)) if __name__ == '__main__': main()