// @ts-nocheck
import { addNumbers, roundTo, toDecimal, toFiniteNumberOrZero } from '@/lib/decimal'
import { formatThousands } from '@/lib/numberFormat'
import { DEFAULT_LOCALE, i18n, I18N_LOCALE_KEY } from '@/i18n'
import {
    EXPERT_NAME_EN_BY_CODE,
    MAJOR_NAME_EN_BY_CODE,
    SERVICE_NAME_EN_BY_CODE,
    TASK_NAME_EN_BY_CODE
} from '@/i18n/dictionary-en'
import ExcelJS from "ExcelJS";
// 统一数字千分位格式化，默认保留 2 位小数。
const numberFormatter = (value: unknown, fractionDigits = 2) =>
    formatThousands(value, fractionDigits)

// 将任意输入安全转为有限数字；无效值统一按 0 处理。
const toFiniteNumber = toFiniteNumberOrZero

// 兼容导出 tasks 对象结构：[{ text: [] }, { serviceid, text: [] }]
const normalizeTaskTexts = (tasks: unknown): string[] => {
    if (!Array.isArray(tasks)) return [];
    const result: string[] = [];
    const seen = new Set<string>();
    const pushText = (value: unknown) => {
        const text = String(value ?? '').trim();
        if (!text) return;
        if (seen.has(text)) return;
        seen.add(text);
        result.push(text);
    };
    tasks.forEach(item => {
        if (typeof item === 'string') {
            pushText(item);
            return;
        }
        if (!item || typeof item !== 'object') return;
        const textField = item.text;
        if (Array.isArray(textField)) {
            textField.forEach(pushText);
            return;
        }
        pushText(textField);
    });
    return result;
}

const normalizeServiceTaskGroups = (tasks: unknown): Array<{ serviceid?: number; process?: number; text: string[] }> => {
    if (!Array.isArray(tasks)) return [];
    const groups: Array<{ serviceid?: number; process?: number; text: string[] }> = [];
    tasks.forEach(item => {
        if (!item || typeof item !== 'object') return;
        const taskItem = item as { serviceid?: unknown; process?: unknown; text?: unknown };
        const text = normalizeTaskTexts(Array.isArray(taskItem.text) ? taskItem.text : [taskItem.text]);
        if (!text.length) return;
        const serviceid = Number(taskItem.serviceid);
        const process = Number(taskItem.process);
        groups.push({
            serviceid: Number.isFinite(serviceid) ? serviceid : undefined,
            process: Number.isFinite(process) ? process : undefined,
            text
        });
    });
    return groups;
}
const resolveRuntimeLocale = () => {
    const i18nLocale = String(i18n?.global?.locale?.value || '').trim()
    if (i18nLocale) return i18nLocale
    if (typeof window === 'undefined') return DEFAULT_LOCALE
    const stored = String(localStorage.getItem(I18N_LOCALE_KEY) || '').trim()
    if (stored) return stored
    const language = String(navigator.language || '').toLowerCase()
    return language.startsWith('en') ? 'en-US' : DEFAULT_LOCALE
}
export type WorkType = '基本工作' | '可选工作' | '日常顾问' | '专项顾问' | '附加工作' | '自定义'

export const TYPE_LABEL_MAP: Record<number, WorkType> = {
    0: '基本工作',
    1: '可选工作',
    2: '日常顾问',
    3: '专项顾问',
    4: '附加工作',
    5: '自定义'
}
export const industryTypeList = [
    { id: '0', name: '公路工程', nameEn: 'Highway Engineering', type: 'isRoad' },
    { id: '1', name: '铁路工程', nameEn: 'Railway Engineering', type: 'isRailway' },
    { id: '2', name: '水运工程', nameEn: 'Waterway Engineering', type: 'isWaterway' }
] as const

export const xmProjectConfig = {
    pinnedTotalLabel: 'C0 建设项目总费用',
    feePhaseOptions: ['估算价', '概算价', '预算价', '招标控制价', '投标报价', '合同签订价', '合同结算价', '决算价'],
    feeStageOptions: ['编制', '送审', '审定', '批复', '其他']
}

export const getIndustryDisplayName = (
    industryId: string | number,
    locale: string = 'zh-CN'
) => {
    const key = String(industryId || '').trim()
    const item = industryTypeList.find(entry => String(entry.id) === key)
    if (!item) return ''
    if (String(locale || '').toLowerCase().startsWith('en')) {
        return item.nameEn || item.name
    }
    return item.name
}

const attachNameEnByCode = (source: Record<string, any>, nameMap: Record<string, string>) => {
    Object.values(source).forEach((item) => {
        if (!item || typeof item !== 'object') return
        const code = String((item as Record<string, any>).code || '').trim()
        if (!code) return
        const nameEn = nameMap[code]
        if (nameEn && typeof nameEn === 'string') {
            ;(item as Record<string, any>).nameEn = nameEn
        }
    })
}

const getCurrentLocale = () => {
    return resolveRuntimeLocale()
}

const isEnglishLocale = (locale: string) => String(locale || '').toLowerCase().startsWith('en')

const localizeDictItem = (item: Record<string, any>) => {
    const locale = getCurrentLocale()
    if (!isEnglishLocale(locale)) return item
    return {
        ...item,
        name: item.nameEn || item.name,
        // English locale should prefer English name when quickLabelEn is absent.
        quickLabel: item.quickLabelEn || item.nameEn || item.quickLabel || item.name,
        basicParam: item.basicParamEn || item.basicParam,
        unit: item.unitEn || item.unit,
        desc: item.descEn || item.desc
    }
}

export const majorList = {
    0: { code: 'C0', name: '建设项目总费用', hideInIndustrySelector: false, maxCoe: null, minCoe: null, defCoe: null, desc: '系统自动计算，计算公式C0=C1+...+C5', isRoad: true, isRailway: true, isWaterway: true, order: 1, hasCost: false, hasArea: false },
    1: { code: 'C1', name: '征地拆迁费用', hideInIndustrySelector: false, maxCoe: null, minCoe: null, defCoe: null, desc: '系统自动计算，计算公式C1=(C1-1)+...(C1-3)', isRoad: true, isRailway: true, isWaterway: true, order: 2, hasCost: false, hasArea: false },
    2: { code: 'C1-1', name: '征地(用海)补偿费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 3, hasCost: true, hasArea: false },
    3: { code: 'C1-2', name: '拆迁补偿费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 4, hasCost: true, hasArea: false },
    4: { code: 'C1-3', name: '迁改工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 5, hasCost: true, hasArea: false },
    5: { code: 'C1-4', name: '征地拆迁其他费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 6, hasCost: true, hasArea: false },
    6: { code: 'C2', name: '建安工程费用', hideInIndustrySelector: false, maxCoe: null, minCoe: null, defCoe: null, desc: '系统自动计算，计算公式C2=(C2-1)+...(C2-11)', isRoad: true, isRailway: true, isWaterway: true, order: 7, hasCost: false, hasArea: false },
    7: { code: 'C2-1', name: '临时工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 8, hasCost: true, hasArea: false },
    8: { code: 'C2-2', name: '路基工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 9, hasCost: true, hasArea: false },
    9: { code: 'C2-3', name: '路面工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 10, hasCost: true, hasArea: false },
    10: { code: 'C2-4', name: '桥涵工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 11, hasCost: true, hasArea: false },
    11: { code: 'C2-5', name: '隧道工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 12, hasCost: true, hasArea: false },
    12: { code: 'C2-6', name: '交叉工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 13, hasCost: true, hasArea: false },
    13: { code: 'C2-7', name: '机电工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 14, hasCost: true, hasArea: false },
    14: { code: 'C2-8', name: '交通安全设施工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 15, hasCost: true, hasArea: false },
    15: { code: 'C2-9', name: '绿化及环境保护工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 16, hasCost: true, hasArea: false },
    16: { code: 'C2-10', name: '房建工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 17, hasCost: true, hasArea: false },
    17: { code: 'C2-11', name: '其他工程费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 18, hasCost: true, hasArea: false },
    18: { code: 'C3', name: '工程建设其他费用', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 19, hasCost: true, hasArea: false },
    19: { code: 'C4', name: '预备费', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 20, hasCost: true, hasArea: false },
    20: { code: 'C5', name: '建设期贷款利息', maxCoe: null, minCoe: null, defCoe: 1, desc: '非必填，手动录入，数字，4位', isRoad: true, isRailway: true, isWaterway: true, order: 21, hasCost: true, hasArea: false }
};




export const serviceList = {
    0: { code: 'D1', name: '全过程造价咨询', maxCoe: null, minCoe: null, defCoe: 1, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 1, scale: true, onlyCostScale: true, amount: false, workDay: true },
    1: { code: 'D2-1', name: '前期阶段造价咨询', quickLabel: '项目前期阶段造价咨询', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 2, scale: true, onlyCostScale: true, amount: false, workDay: true },
    2: { code: 'D2-2', name: '实施阶段造价咨询', quickLabel: '项目实施阶段造价咨询', maxCoe: null, minCoe: null, defCoe: 0.55, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 3, scale: true, onlyCostScale: true, amount: false, workDay: true },
    3: { code: 'D3-1', name: '投资估算', maxCoe: null, minCoe: null, defCoe: 0.1, desc: '委托同一咨询人同时负责D3-1和D3-2时，D3-1和D3-2的合计调整系数为0.25。', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 4, scale: true, onlyCostScale: true, amount: false, workDay: true },
    4: { code: 'D3-2', name: '设计概算', maxCoe: null, minCoe: null, defCoe: 0.2, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 5, scale: true, onlyCostScale: true, amount: false, workDay: true },
    5: { code: 'D3-3', name: '工程预算', maxCoe: null, minCoe: null, defCoe: 0.25, desc: '委托同一咨询人同时负责D3-3和D3-4时，D3-3和D3-4的合计调整系数为0.3。', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 6, scale: true, onlyCostScale: false, amount: false, workDay: true },
    6: { code: 'D3-4', name: '招标工程量清单及清单预算', maxCoe: null, minCoe: null, defCoe: 0.15, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 7, scale: true, onlyCostScale: false, amount: false, workDay: true },
    7: { code: 'D3-6-1', name: '工程结算', maxCoe: null, minCoe: null, defCoe: 0.3, desc: '本系数适用于公路和水运工程。', isRoad: true, isRailway: false, isWaterway: true, mutiple: false, order: 8, scale: true, onlyCostScale: false, amount: false, workDay: true },
    8: { code: 'D3-7', name: '竣工决算', maxCoe: null, minCoe: null, defCoe: 0.1, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 9, scale: true, onlyCostScale: true, amount: false, workDay: true },
    9: { code: 'D4-6', name: '工程造价鉴定报告', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '本表系数适用于采用规模计价法基准预算的调整系数。', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 10, scale: true, onlyCostScale: false, amount: true, workDay: true },
    10: { code: 'D5-1', name: '工程经济鉴定评估报告', maxCoe: null, minCoe: null, defCoe: 1, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 11, scale: false, onlyCostScale: null, amount: true, workDay: true }
};


export const taskList = {
    // D4-6 工程造价鉴定报告 (serviceID: 9)
    0: { serviceID: 9, code: 'D4-6', name: '工程造价鉴定报告', basicParam: '鉴定报告份数', required: true, unit: '万元/项', conversion: 10000, maxPrice: null, minPrice: null, defPrice: 1, midPrice: 2, highPrice: 3, desc: '本表系数适用于采用规模计价法基准预算的调整系数' },
};

export const expertList = {
    0: { code: 'C9-1-1', name: '技术员及其他', maxPrice: 800, minPrice: 600, defPrice: 700, manageCoe: 2.3 },
    1: { code: 'C9-1-2', name: '助理工程师', maxPrice: 1000, minPrice: 800, defPrice: 900, manageCoe: 2.3 },
    2: { code: 'C9-1-3', name: '中级工程师或二级造价工程师', maxPrice: 1500, minPrice: 1000, defPrice: 1250, manageCoe: 2.2 },
    3: { code: 'C9-1-4', name: '高级工程师或一级造价工程师', maxPrice: 1800, minPrice: 1500, defPrice: 1650, manageCoe: 2.1 },
    4: { code: 'C9-1-5', name: '正高级工程师或资深专家', maxPrice: 2500, minPrice: 2000, defPrice: 2250, manageCoe: 2 },
    5: { code: 'C9-2-1', name: '二级造价工程师且具备中级工程师资格', maxPrice: 1500, minPrice: 1200, defPrice: 1350, manageCoe: 2.2 },
    6: { code: 'C9-3-1', name: '一级造价工程师且具备中级工程师资格', maxPrice: 1800, minPrice: 1500, defPrice: 1650, manageCoe: 2.1 },
    7: { code: 'C9-3-2', name: '一级造价工程师且具备高级工程师资格', maxPrice: 2000, minPrice: 1800, defPrice: 1900, manageCoe: 2.05 },
};

attachNameEnByCode(majorList as Record<string, any>, MAJOR_NAME_EN_BY_CODE)
attachNameEnByCode(serviceList as Record<string, any>, SERVICE_NAME_EN_BY_CODE)
attachNameEnByCode(taskList as Record<string, any>, TASK_NAME_EN_BY_CODE)
attachNameEnByCode(expertList as Record<string, any>, EXPERT_NAME_EN_BY_CODE)

export const additionalWorkList = [
    {
        id: '1',
        code: {
            richText: [
                { font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'C' },
                { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: 'F' }
            ]
        },
        name: '人员驻场服务及其他附加工作',
        nameEn: 'On-site Personnel Service and Other Additional Work'
    },
    {
        id: '2',
        code: {
            richText: [
                { font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'C' },
                { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: 'X' }
            ]
        },
        name: '咨询服务协调工作',
        nameEn: 'Consulting Service Coordination'
    }
]

export const reserveList = [
    {
        id: 1,
        code: {
            richText: [
                { font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'Y' },
                { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: 'B' }
            ]
        },
        name: '预备费',
        nameEn: 'Reserve Fee'
    }
]

export const getAdditionalWorkListEntries = (locale: string = getCurrentLocale()) => {
    const useEnglish = isEnglishLocale(locale)
    return additionalWorkList.map(item => ({
        ...item,
        name: useEnglish ? String((item as Record<string, any>).nameEn || item.name || '') : String(item.name || '')
    }))
}

export const getReserveListEntries = (locale: string = getCurrentLocale()) => {
    const useEnglish = isEnglishLocale(locale)
    return reserveList.map(item => ({
        ...item,
        name: useEnglish ? String((item as Record<string, any>).nameEn || item.name || '') : String(item.name || '')
    }))
}
//工作内容词典
export const workList = {
    0: { code:'D0', text: '通用工作', textEn: 'General Work', serviceid: 1, order: 1, type: 0, leaf: false },
    1: { code:'D0', text: '收集并整理各阶段及各项造价咨询所需的基础资料，审核基础资料的完整性和合理性', textEn: 'Collect and organize basic data required for various stages and cost consulting items, and review the completeness and rationality of basic data', serviceid: 1, order: 1, type: 0, leaf: true },
    2: { code:'D0', text: '编制造价咨询服务总体及各分项的服务计划、服务大纲及工作方案', textEn: 'Prepare overall and itemized service plans, service outlines, and work programs for cost consulting services', serviceid: 1, order: 2, type: 0, leaf: true },
    3: { code:'D0', text: '了解建设工程项目所在地的建设条件', textEn: 'Understand the construction conditions of the location of the construction project', serviceid: 1, order: 3, type: 0, leaf: true },

    4: { code:'D3-1', text: '投资估算', textEn: 'Investment Estimation', serviceid: 6, order: 2, type: 0, leaf: false },
    5: { code:'D3-1', text: '完成投资估算文件及主要技术经济指标，完成与相关单位核对投资估算', textEn: 'Complete the investment estimation documents and main technical and economic indicators, and complete the investment estimation with the relevant units', serviceid: 6, order: 1, type: 0, leaf: true },
    6: { code:'D3-1', text: '根据项目建议书或可行性研究报告的变化，动态调整投资估算和技术经济指标，并对比各环节投资估算变化，完成项目建议书阶段到可行性研究阶段的量、价对比及原因分析', textEn: 'Dynamically adjust investment estimation and technical economic indicators based on changes in project proposal or feasibility study report, compare investment estimation changes at various stages, and complete quantity and price comparison and cause analysis from project proposal stage to feasibility study stage', serviceid: 6, order: 2, type: 0, leaf: true },
    7: { code:'D3-1', text: '参加项目建议书或可行性研究的技术论证会议、评审会议和与确定投资估算相关的会议', textEn: 'Participate in technical demonstration meetings, review meetings of project proposal or feasibility study, and meetings related to determining investment estimation', serviceid: 6, order: 3, type: 0, leaf: true },
    8: { code:'D3-1', text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 6, order: 4, type: 0, leaf: true },
    9: { code:'D3-1', text: '根据造价差异分析结果，提出调整工程方案的咨询意见供委托人或关联单位决策', textEn: 'Provide consulting opinions on adjusting engineering plans based on cost difference analysis results for decision-making by the client or related units', serviceid: 6, order: 5, type: 0, leaf: true },
    10: {code:'D3-1', text: '依据投资估算和项目建设计划，编制项目资金计划，供委托人决策', textEn: 'Prepare project funding plans based on investment estimation and project construction plans for client decision-making', serviceid: 6, order: 6, type: 0, leaf: true },

    11: { code:'D3-2', text: '设计概算', textEn: 'Design Budget', serviceid: 7, order: 3, type: 0, leaf: false },
    12: { code:'D3-2', text: '完成设计概算文件及主要技术经济指标，完成与相关单位核对设计概算', textEn: 'Complete design budget documents and main technical and economic indicators, and complete design budget verification with relevant units', serviceid: 7, order: 1, type: 0, leaf: true },
    13: { code:'D3-2', text: '依据各版初步设计动态调整设计概算及经济指标，并对比各版设计概算结果，完成可行性研究阶段到初步设计阶段的造价对比，提供造价差异分析报告', textEn: 'Dynamically adjust design budget and economic indicators based on various preliminary design versions, compare design budget results, complete cost comparison from feasibility study stage to preliminary design stage, and provide cost difference analysis report', serviceid: 7, order: 2, type: 0, leaf: true },
    14: { code:'D3-2', text: '参加初步设计的技术论证会议、评审会议和与设计概算相关的会议', textEn: 'Participate in technical demonstration meetings, review meetings of preliminary design, and meetings related to design budget', serviceid: 7, order: 3, type: 0, leaf: true },
    15: { code:'D3-2', text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 7, order: 4, type: 0, leaf: true },
    16: { code:'D3-2', text: '根据造价差异分析结果，提出调整工程设计方案的咨询意见供委托人或关联单位决策', textEn: 'Provide consulting opinions on adjusting engineering design plans based on cost difference analysis results for decision-making by the client or related units', serviceid: 7, order: 5, type: 0, leaf: true },
    17: { code:'D3-2', text: '依据设计概算和项目建设计划，编制或调整项目资金计划，供委托人决策', textEn: 'Prepare or adjust project funding plans based on design budget and project construction plans for client decision-making', serviceid: 7, order: 6, type: 0, leaf: true },

    18: { code:'D3-3', text: '工程预算', textEn: 'Construction Budget', serviceid: 8, order: 4, type: 0, leaf: false },
    19: { code:'D3-3', text: '完成施工图预算文件及主要技术经济指标，完成与相关单位核对施工图预算', textEn: 'Complete construction drawing budget documents and main technical and economic indicators, and complete construction drawing budget verification with relevant units', serviceid: 8, order: 1, type: 0, leaf: true },
    20: { code:'D3-3', text: '依据各版施工图动态调整施工图预算，并对比各版施工图预算结果，完成施工图预算与设计概算的造价对比（如有必要应完成与投资估算的造价对比），提供造价差异分析报告', textEn: 'Dynamically adjust construction drawing budget based on various versions of construction drawings, compare construction drawing budget results, complete cost comparison between construction drawing budget and design budget (and investment estimation if necessary), and provide cost difference analysis report', serviceid: 8, order: 2, type: 0, leaf: true },
    21: { code:'D3-3', text: '参加施工图设计的技术论证会议、评审会议和与施工图预算相关的会议', textEn: 'Participate in technical demonstration meetings, review meetings of construction drawing design, and meetings related to construction drawing budget', serviceid: 8, order: 3, type: 0, leaf: true },
    22: { code:'D3-3', text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 8, order: 4, type: 0, leaf: true },
    23: { code:'D3-3', text: '根据造价差异分析结果，提出调整工程设计方案的咨询意见供委托人或关联单位决策', textEn: 'Provide consulting opinions on adjusting engineering design plans based on cost difference analysis results for decision-making by the client or related units', serviceid: 8, order: 5, type: 0, leaf: true },
    24: { code:'D3-3',text: '依据施工图预算和项目建设计划，编制或调整项目资金计划，供委托人决策', textEn: 'Prepare or adjust project funding plans based on construction drawing budget and project construction plans for client decision-making', serviceid: 8, order: 6, type: 0, leaf: true },
    25: { code:'D3-3',text: '分析专项设计方案的经济合理性，提出咨询意见供委托人决策', textEn: 'Analyze the economic rationality of special design plans and provide consulting opinions for client decision-making', serviceid: 8, order: 7, type: 0, leaf: true },
    26: { code:'D3-3',text: '分析施工组织设计和交通组织方案的经济合理性，提出咨询意见供委托人决策', textEn: 'Analyze the economic rationality of construction organization design and traffic organization plans, and provide consulting opinions for client decision-making', serviceid: 8, order: 8, type: 0, leaf: true },

    27: { code:'D3-4', text: '招标工程量清单及清单预算', textEn: 'Tender Bill of Quantities and Budget', serviceid: 9, order: 5, type: 0, leaf: false },
    28: { code:'D3-4', text: '完成招标工程量清单及工程量清单预算，与相关单位核对工程量清单及工程量清单预算，并完成修改与调整', textEn: 'Complete tender bill of quantities and bill of quantities budget, verify with relevant units, and complete modifications and adjustments', serviceid: 9, order: 1, type: 0, leaf: true },
    29: { code:'D3-4', text: '对比工程量清单预算和施工图预算，提供造价差异分析报告', textEn: 'Compare bill of quantities budget and construction drawing budget, and provide cost difference analysis report', serviceid: 9, order: 2, type: 0, leaf: true },
    30: { code:'D3-4', text: '审核计量与支付条款，对招标文件和合同文件涉及合同价款、工程变更费用咨询、结算费用等与造价相关条款提供咨询意见', textEn: 'Review measurement and payment terms, and provide consulting opinions on cost-related terms in tender documents and contract documents involving contract price, engineering change cost consulting, settlement costs, etc.', serviceid: 9, order: 3, type: 0, leaf: true },
    31: { code:'D3-4', text: '协助招标人完成招标答疑涉及的造价问题的解答与回复', textEn: 'Assist tenderer in answering and responding to cost-related questions in tender clarification', serviceid: 9, order: 4, type: 0, leaf: true },
    32: { code:'D3-4', text: '参加招投标阶段与造价确定相关的会议', textEn: 'Participate in meetings related to cost determination during tendering stage', serviceid: 9, order: 5, type: 0, leaf: true },
    33: { code:'D3-4', text: '对比中标价与工程量清单预算，并分析差异原因，协助招标人完成不平衡报价调整', textEn: 'Compare winning bid price with bill of quantities budget, analyze reasons for differences, and assist tenderer in completing unbalanced bid price adjustment', serviceid: 9, order: 6, type: 0, leaf: true },
    34: { code:'D3-4', text: '参与合同谈判，协助招标人合同谈判过程中相关造价分析', textEn: 'Participate in contract negotiation and assist tenderer in related cost analysis during contract negotiation process', serviceid: 9, order: 7, type: 0, leaf: true },

    35: { code:'D3-6-1', text: '工程结算', textEn: 'Engineering Settlement', serviceid: 10, order: 6, type: 0, leaf: false },
    36: { code:'D3-6-1', text: '完成工程变更费用的测算，或提出性价比更优的替代方案、材料等意见', textEn: 'Complete calculation of engineering change costs, or provide opinions on more cost-effective alternative solutions, materials, etc.', serviceid: 10, order: 1, type: 0, leaf: true },
    37: { code:'D3-6-1', text: '完成新增预算单价或合同单价的计算与核定', textEn: 'Complete calculation and verification of new budget unit prices or contract unit prices', serviceid: 10, order: 2, type: 0, leaf: true },
    38: { code:'D3-6-1', text: '按施工图预算或合同清单方式完成工程变更费用的计算', textEn: 'Complete calculation of engineering change costs according to construction drawing budget or contract list method', serviceid: 10, order: 3, type: 0, leaf: true },
    39: { code:'D3-6-1', text: '完成过程结算，包括建立过程结算多维度清单体系，如：项目清单、工程量清单、分项清单；划分过程结算清单单元；依据经确认的实际完工工程量完成计价计费，完成过程结算文件', textEn: 'Complete interim settlement, including establishing multi-dimensional interim settlement list system such as project list, bill of quantities, itemized list; dividing interim settlement list units; completing pricing and billing based on confirmed actual completed quantities, and completing interim settlement documents', serviceid: 10, order: 4, type: 0, leaf: true },
    40: { code:'D3-6-1', text: '完成价格波动费用的计算或审核', textEn: 'Complete calculation or review of price fluctuation costs', serviceid: 10, order: 5, type: 0, leaf: true },
    41: { code:'D3-6-1', text: '完成索赔与补偿费用的计算或审核', textEn: 'Complete calculation or review of claim and compensation costs', serviceid: 10, order: 6, type: 0, leaf: true },
    42: { code:'D3-6-1', text: '完成结算文件及统计技术经济指标，与相关单位核对合同（工程）结算', textEn: 'Complete settlement documents and statistical technical and economic indicators, and verify contract (engineering) settlement with relevant units', serviceid: 10, order: 7, type: 0, leaf: true },
    43: { code:'D3-6-1', text: '完成合同、变更和结算三环节费用的对比，分析合同费用各环节变化原因，提供对比报表和分析报告', textEn: 'Complete comparison of costs in three stages of contract, change, and settlement, analyze reasons for changes in contract costs at each stage, and provide comparison reports and analysis reports', serviceid: 10, order: 8, type: 0, leaf: true },
    44: { code:'D3-6-1', text: '参加与过程结算、工程变更费用确定和合同（工程）结算相关的会议', textEn: 'Participate in meetings related to interim settlement, engineering change cost determination, and contract (engineering) settlement', serviceid: 10, order: 9, type: 0, leaf: true },
    45: { code:'D3-6-1', text: '现场勘查与测量实际完工工程量', textEn: 'Conduct on-site investigation and measurement of actual completed quantities', serviceid: 10, order: 10, type: 0, leaf: true },
    46: { code:'D3-6-1', text: '复核竣工图纸数量与结算数量的差异，提交数量差异报告', textEn: 'Review differences between completion drawing quantities and settlement quantities, and submit quantity difference report', serviceid: 10, order: 11, type: 0, leaf: true },
    47: { code:'D3-6-1', text: '协助委托人对项目工程变更费用审批情况进行清理，编制交工验收造价文件', textEn: 'Assist client in clearing approval status of project engineering change costs, and prepare cost documents for handover acceptance', serviceid: 10, order: 12, type: 0, leaf: true },
    48: { code:'D3-6-1', text: '协助委托人处理工期索赔、造价费用等费用的争议与纠纷、仲裁与诉讼', textEn: 'Assist client in handling disputes and conflicts, arbitration and litigation related to time extension claims, cost expenses, etc.', serviceid: 10, order: 13, type: 0, leaf: true },

    49: { code:'D3-7', text: '竣工决算', textEn: 'Final Account', serviceid: 11, order: 7, type: 0, leaf: false },
    50: { code:'D3-7', text: '编制项目竣工决算文件', textEn: 'Prepare project final account documents', serviceid: 11, order: 1, type: 0, leaf: true },
    51: { code:'D3-7', text: '编制竣工决算备案送审文件', textEn: 'Prepare final account filing and review documents', serviceid: 11, order: 2, type: 0, leaf: true },
    52: { code:'D3-7', text: '参与竣工决算相关的会议', textEn: 'Participate in meetings related to final account', serviceid: 11, order: 3, type: 0, leaf: true },
    53: { code:'D3-7', text: '根据审计意见和决算备案意见调整竣工决算文件', textEn: 'Adjust final account documents based on audit opinions and final account filing opinions', serviceid: 11, order: 4, type: 0, leaf: true },
    54: { code:'D3-7', text: '协助委托人编制建设项目造价执行情况报告', textEn: 'Assist client in preparing construction project cost implementation report', serviceid: 11, order: 5, type: 0, leaf: true },
    55: { code:'D3-7', text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 11, order: 6, type: 0, leaf: true },
    56: { code:'D3-7', text: '根据造价差异分析结果，提出调整工程方案的咨询意见供委托人或关联单位决策', textEn: 'Provide consulting opinions on adjusting engineering plans based on cost difference analysis results for decision-making by the client or related units', serviceid: 11, order: 7, type: 0, leaf: true },
    57: { code:'D3-7', text: '协助委托人指导参建单位完成与决算相关辅助文件的编制', textEn: 'Assist client in guiding participating units to complete preparation of auxiliary documents related to final account', serviceid: 11, order: 8, type: 0, leaf: true },
    58: { code:'D3-7', text: '协助委托人开展项目造价管理及投资效益后评估', textEn: 'Assist client in conducting project cost management and post-investment benefit evaluation', serviceid: 11, order: 9, type: 0, leaf: true },

    59: { code:'D4-6', text: '工程造价鉴定报告', textEn: 'Engineering Cost Appraisal Report', serviceid: 12, order: 8, type: 0, leaf: false },
    60: { code:'D4-6', text: '根据委托人的目标，确定鉴定工作范围', textEn: 'Determine appraisal work scope based on client objectives', serviceid: 12, order: 1, type: 0, leaf: true },
    61: { code:'D4-6', text: '收集与复核鉴定资料', textEn: 'Collect and review appraisal materials', serviceid: 12, order: 2, type: 0, leaf: true },
    62: { code:'D4-6', text: '组织或参与必要的现场勘查工作', textEn: 'Organize or participate in necessary on-site investigation work', serviceid: 12, order: 3, type: 0, leaf: true },
    63: { code:'D4-6', text: '工程量的计算与工程费用的计价或估价', textEn: 'Calculate quantities and price or estimate engineering costs', serviceid: 12, order: 4, type: 0, leaf: true },
    64: { code:'D4-6', text: '对争议焦点进行鉴定，提出解决争议的意见', textEn: 'Appraise dispute focus points and provide opinions on resolving disputes', serviceid: 12, order: 5, type: 0, leaf: true },
    65: { code:'D4-6', text: '回复各方当事人的质证意见', textEn: 'Respond to cross-examination opinions from all parties', serviceid: 12, order: 6, type: 0, leaf: true },
    66: { code:'D4-6', text: '编制并出具造价鉴定报告', textEn: 'Prepare and issue cost appraisal report', serviceid: 12, order: 7, type: 0, leaf: true },

    67: { code:'D5-1', text: '工程经济鉴定（评估）报告', textEn: 'Engineering Economic Appraisal (Evaluation) Report', serviceid: 13, order: 9, type: 0, leaf: false },
    68: { code:'D5-1', text: '接收委托方提交的经济分析或造价结果文件', textEn: 'Receive economic analysis or cost result documents submitted by the client', serviceid: 13, order: 1, type: 0, leaf: true },
    69: { code:'D5-1', text: '检查提交资料的完整性、合规性', textEn: 'Check the completeness and compliance of submitted materials', serviceid: 13, order: 2, type: 0, leaf: true },
    70: { code:'D5-1', text: '数据复核、计算与验证', textEn: 'Data review, calculation and verification', serviceid: 13, order: 3, type: 0, leaf: true },
    71: { code:'D5-1', text: '合理性对比分析，判断其合理性，别是否存在明显高估、低估、漏项、重复计算等问题', textEn: 'Reasonableness comparison analysis, judge its reasonableness, and identify whether there are obvious overestimation, underestimation, omissions, duplicate calculations, etc.', serviceid: 13, order: 4, type: 0, leaf: true },
    72: { code:'D5-1', text: '依据与规范符合性检查', textEn: 'Check compliance with standards and specifications', serviceid: 13, order: 5, type: 0, leaf: true },
    73: { code:'D5-1', text: '撰写报告，阐述过程、依据、方法及结论', textEn: 'Write report, elaborating process, basis, methods and conclusions', serviceid: 13, order: 6, type: 0, leaf: true },

    74: { code:'D0-1', text: '通用工作2', textEn: 'General Work', serviceid: 1, order: 10, type: 0, leaf: false },
    75: { code:'D0-1', text: '协助委托人完成咨询服务相应造价文件的报审、报备、报批、检查与审计涉及的解释与回复、修改与调整等工作', textEn: 'Assist client in completing review, filing, approval, inspection and audit related explanations, responses, modifications and adjustments of corresponding cost documents for consulting services', serviceid: 1, order: 1, type: 0, leaf: true },
    76: { code:'D0-1', text: '完成本合同造价档案的收集、整理和归档', textEn: 'Complete collection, organization and archiving of cost archives under this contract', serviceid: 1, order: 2, type: 0, leaf: true }
}

export const workList2 = {
    0: { text: '完成投资估算文件及主要技术经济指标，完成与相关单位核对投资估算', textEn: 'Complete the investment estimation documents and main technical and economic indicators, and complete the investment estimation with the relevant units.', serviceid: 6, order: 1, type: 0 },
    1: { text: '根据项目建议书或可行性研究报告的变化，动态调整投资估算和技术经济指标，并对比各环节投资估算变化，完成项目建议书阶段到可行性研究阶段的量、价对比及原因分析', textEn: 'According to the changes in the project proposal or feasibility study report, dynamically adjust the investment estimate and technical and economic indicators, compare the changes in the investment estimate of each link, and complete the quantitative, price comparison and reason analysis from the project proposal stage to the feasibility study stage.', serviceid: 6, order: 2, type: 0 },
    2: { text: '参加项目建议书或可行性研究的技术论证会议、评审会议和与确定投资估算相关的会议', textEn: 'Participate in technical demonstration meetings, review meetings, and meetings related to determining investment estimates for a project proposal or feasibility study', serviceid: 6, order: 3, type: 0 },
    3: { text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 6, order: 4, type: 1 },
    4: { text: '根据造价差异分析结果，提出调整工程方案的咨询意见供委托人或关联单位决策', textEn: 'Based on the results of the cost variance analysis, advise on adjusting the project plan for the decision of the client or affiliated unit', serviceid: 6, order: 5, type: 1 },
    5: { text: '依据投资估算和项目建设计划，编制项目资金计划，供委托人决策', textEn: 'Prepare the project funding plan based on the investment estimate and the project construction plan for the client to decide', serviceid: 6, order: 6, type: 1 },
    6: { text: '完成设计概算文件及主要技术经济指标，完成与相关单位核对设计概算', textEn: 'Complete the design budget proposal document and the main technical and economic indicators, and complete the verification of the design budget estimate with the relevant units.', serviceid: 7, order: 7, type: 0 },
    7: { text: '依据各版初步设计动态调整设计概算及经济指标，并对比各版设计概算结果，完成可行性研究阶段到初步设计阶段的造价对比，提供造价差异分析报告', textEn: 'Dynamically adjust the design estimate and economic indicators according to each version of the preliminary design, compare the results of each version of the design estimate, complete the cost comparison from the feasibility study stage to the preliminary design stage, and provide a cost difference analysis report.', serviceid: 7, order: 8, type: 0 },
    8: { text: '参加初步设计的技术论证会议、评审会议和与设计概算相关的会议', textEn: 'Attend preliminary design technical demonstration meetings, review meetings, and meetings related to design budget proposals', serviceid: 7, order: 9, type: 0 },
    9: { text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 7, order: 10, type: 1 },
    10: { text: '根据造价差异分析结果，提出调整工程设计方案的咨询意见供委托人或关联单位决策', textEn: 'According to the results of the cost difference analysis, advise on adjusting the engineering design plan for the decision of the client or affiliated unit', serviceid: 7, order: 11, type: 1 },
    11: { text: '依据设计概算和项目建设计划，编制或调整项目资金计划，供委托人决策', textEn: 'Prepare or adjust the project funding plan according to the design estimate and the project construction plan for the client to decide', serviceid: 7, order: 12, type: 1 },
    12: { text: '完成施工图预算文件及主要技术经济指标，完成与相关单位核对施工图预算', textEn: 'Complete the construction drawing budget document and main technical and economic indicators, and complete checking the construction drawing budget with the relevant units', serviceid: 8, order: 13, type: 0 },
    13: { text: '依据各版施工图动态调整施工图预算，并对比各版施工图预算结果，完成施工图预算与设计概算的造价对比（如有必要应完成与投资估算的造价对比），提供造价差异分析报告', textEn: 'Dynamically adjust the construction drawing budget according to each version of the construction drawing, compare the budget results of each version of the construction drawing, complete the cost comparison between the construction drawing budget and the design estimate (if necessary, complete the cost comparison with the investment estimate), and provide a cost difference analysis report', serviceid: 8, order: 14, type: 0 },
    14: { text: '参加施工图设计的技术论证会议、评审会议和与施工图预算相关的会议', textEn: 'Participate in technical demonstration meetings, review meetings, and meetings related to construction drawing budgets for construction drawing designs', serviceid: 8, order: 15, type: 0 },
    15: { text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 8, order: 16, type: 1 },
    16: { text: '根据造价差异分析结果，提出调整工程设计方案的咨询意见供委托人或关联单位决策', textEn: 'According to the results of the cost difference analysis, advise on adjusting the engineering design plan for the decision of the client or affiliated unit', serviceid: 8, order: 17, type: 1 },
    17: { text: '依据施工图预算和项目建设计划，编制或调整项目资金计划，供委托人决策', textEn: 'Prepare or adjust the project funding plan according to the construction drawing budget and the project construction plan for the client to decide', serviceid: 8, order: 18, type: 1 },
    18: { text: '分析专项设计方案的经济合理性，提出咨询意见供委托人决策', textEn: 'Analyze the economic rationality of the special design plan, and provide advice for the client to decide', serviceid: 8, order: 19, type: 1 },
    19: { text: '分析施工组织设计和交通组织方案的经济合理性，提出咨询意见供委托人决策', textEn: 'Analyze the economic rationality of the construction organization design and transportation organization plan, and provide advice for the client to make decisions', serviceid: 8, order: 20, type: 1 },
    20: { text: '完成招标工程量清单及工程量清单预算，与相关单位核对工程量清单及工程量清单预算，并完成修改与调整', textEn: 'Complete the bidding bill of quantities and the bill of quantities budget, check the bill of quantities and the bill of quantities budget with the relevant units, and complete the revision and adjustment', serviceid: 9, order: 21, type: 0 },
    21: { text: '对比工程量清单预算和施工图预算，提供造价差异分析报告', textEn: 'Compare the bill of quantities budget and construction drawing budget, and provide a cost variance analysis report', serviceid: 9, order: 22, type: 0 },
    22: { text: '审核计量与支付条款，对招标文件和合同文件涉及合同价款、工程变更费用咨询、结算费用等与造价相关条款提供咨询意见', textEn: 'Review the measurement and payment clauses, and provide advice on the bidding documents and contract documents related to the contract price, engineering change cost consultation, settlement costs, and other cost-related clauses', serviceid: 9, order: 23, type: 0 },
    23: { text: '协助招标人完成招标答疑涉及的造价问题的解答与回复', textEn: 'Assist the tenderer to complete the answer and reply to the cost issues involved in the bidding question', serviceid: 9, order: 24, type: 0 },
    24: { text: '参加招投标阶段与造价确定相关的会议', textEn: 'Participate in meetings related to cost determination during the bidding phase', serviceid: 9, order: 25, type: 0 },
    25: { text: '对比中标价与工程量清单预算，并分析差异原因，协助招标人完成不平衡报价调整', textEn: 'Compare the winning bid price with the BOQ budget and analyze the reason for the difference to assist the tenderer in completing the unbalanced bid adjustment', serviceid: 9, order: 26, type: 1 },
    26: { text: '参与合同谈判，协助招标人合同谈判过程中相关造价分析', textEn: 'Participate in contract negotiation to assist the tenderer in the process of contract negotiation related cost analysis', serviceid: 9, order: 27, type: 1 },
    27: { text: '清理施工承包合同费用、征地拆迁费用、各项价差、其他类费用及费用依据、计算资料等', textEn: 'Clear construction contract costs, land acquisition and demolition costs, various price differences, other types of costs and cost bases, calculation data, etc.', serviceid: 10, order: 28, type: 0 },
    28: { text: '清理征地拆迁费用，包括清理建设项目的用地、青苗补偿、房屋拆迁等费用，清理管线迁改、改路、改河、改沟以及构筑物迁改等费用，土地复垦费用及征拆各项规费的清理等', textEn: 'Clearance of land requisition and demolition costs, including clearance of land for construction projects, compensation for seedlings, house demolition, etc., clearance of pipeline relocation, alteration of roads, alteration of rivers, alteration of ditches, and relocation of structures, land reclamation costs and clearance of various fees for demolition, etc.', serviceid: 10, order: 29, type: 0 },
    29: { text: '清理材料价差，包括调整甲供材料设备价差、政策性文件规定的其他材料价差等', textEn: 'Clean up the price difference of materials, including adjusting the price difference of materials and equipment supplied by Party A, and other material price differences stipulated in policy documents, etc.', serviceid: 10, order: 30, type: 0 },
    30: { text: '清理政策性调整费用', textEn: 'Cleanup Policy Adjustment Fee', serviceid: 10, order: 31, type: 0 },
    31: { text: '清理基本预备费和招标降造费，包括分析降造费的使用情况', textEn: 'Clean up the basic reserve fee and the bidding reduction fee, including analyzing the use of the reduction fee', serviceid: 10, order: 32, type: 0 },
    32: { text: '对未进行初步验收的剩余工程及投资情况进行清理', textEn: 'Clean up remaining projects and investments that have not undergone preliminary acceptance', serviceid: 10, order: 33, type: 0 },
    33: { text: '梳理和清理批复的概算费用，分析费用变化的原因', textEn: 'Sort and clean up the estimated cost of the approval and analyze the reasons for the change in cost', serviceid: 10, order: 34, type: 0 },
    34: { text: '根据收集的资料和费用分析结果，编制清理概算文件，包括清理概算申请报告、相关批复文件、设计图纸、施工组织设计、合同、验工计价、设计变更和新增工程等支撑性资料、审计报告（如有）、第三方审价报告及其他相关依据', textEn: 'Based on the collected information and the results of the cost analysis, prepare a clearance budget proposal document, including clearance budget proposal application report, relevant approval documents, design drawings, construction organization design, contract, work inspection pricing, design changes and new projects and other supporting information, audit reports (if any), third-party audit reports and other relevant bases.', serviceid: 10, order: 35, type: 0 },
    35: { text: '招标图纸与实际施工图纸进行验核，核实工程数量，针对分析概算费用变化的原因', textEn: 'Verify the bidding drawings with the actual construction drawings, verify the number of projects, and analyze the reasons for the change in the estimated budget cost', serviceid: 10, order: 36, type: 1 },
    36: { text: '参加与清理概算相关的会议，协助委托人完成清理概算文件预评审工作', textEn: 'Participate in meetings related to clearing the budget proposal and assist the client in completing the pre-screening of the budget proposal document', serviceid: 10, order: 37, type: 0 },
    37: { text: '完成工程变更费用的测算，或提出性价比更优的替代方案、材料等意见', textEn: 'Complete the estimation of the cost of engineering changes, or suggest cost-effective alternatives, materials, etc.', serviceid: 11, order: 38, type: 0 },
    38: { text: '完成新增预算单价或合同单价的计算与核定', textEn: 'Complete the calculation and approval of the new budget unit price or contract unit price', serviceid: 11, order: 39, type: 0 },
    39: { text: '按施工图预算或合同清单方式完成工程变更费用的计算', textEn: 'Calculate the cost of engineering changes according to the construction drawing budget or contract list', serviceid: 11, order: 40, type: 0 },
    40: { text: '完成过程结算，包括建立过程结算多维度清单体系，如：项目清单、工程量清单、分项清单；划分过程结算清单单元；依据经确认的实际完工工程量完成计价计费，完成过程结算文件', textEn: 'Complete the process settlement, including establishing a process settlement multi-dimensional list system, such as: project list, project quantity list, sub-item list; dividing the process settlement list unit; completing the pricing and billing based on the confirmed actual completed project quantity, and completing the process settlement documents', serviceid: 11, order: 41, type: 0 },
    41: { text: '完成价格波动费用的计算或审核', textEn: 'Complete calculation or review of price fluctuation charges', serviceid: 11, order: 42, type: 0 },
    42: { text: '完成索赔与补偿费用的计算或审核', textEn: 'Completing claims and reimbursement calculations or reviews', serviceid: 11, order: 43, type: 0 },
    43: { text: '完成结算文件及统计技术经济指标，与相关单位核对合同（工程）结算', textEn: 'Complete settlement documents and statistical technical and economic indicators, and check the contract (project) settlement with the relevant units', serviceid: 11, order: 44, type: 0 },
    44: { text: '完成合同、变更和结算三环节费用的对比，分析合同费用各环节变化原因，提供对比报表和分析报告', textEn: 'Complete the comparison of the costs of the contract, change and settlement, analyze the reasons for the changes in the contract costs, and provide comparative statements and analysis reports', serviceid: 11, order: 45, type: 0 },
    45: { text: '参加与过程结算、工程变更费用确定和合同（工程）结算相关的会议', textEn: 'Participate in meetings related to process settlement, engineering change cost determination, and contract (engineering) settlement', serviceid: 11, order: 46, type: 0 },
    46: { text: '现场勘查与测量实际完工工程量', textEn: 'On-site survey and measurement of actual completed works', serviceid: 11, order: 47, type: 1 },
    47: { text: '复核竣工图纸数量与结算数量的差异，提交数量差异报告', textEn: 'Review the discrepancy between the quantity of as-built drawings and the settlement quantity, and submit a report on the discrepancy in the quantity', serviceid: 11, order: 48, type: 1 },
    48: { text: '协助委托人对项目工程变更费用审批情况进行清理，编制交工验收造价文件', textEn: 'Assist the client to clean up the project engineering change cost approval, and prepare the delivery acceptance cost document', serviceid: 11, order: 49, type: 1 },
    49: { text: '协助委托人处理工期索赔、造价费用等费用的争议与纠纷、仲裁与诉讼', textEn: 'Assist the client in handling disputes and disputes, arbitration and litigation over construction period claims, construction costs and other expenses', serviceid: 11, order: 50, type: 1 },
    50: { text: '完成工程变更费用的测算，或提出性价比更优的替代方案、材料等意见', textEn: 'Complete the estimation of the cost of engineering changes, or suggest cost-effective alternatives, materials, etc.', serviceid: 12, order: 51, type: 0 },
    51: { text: '完成新增预算单价或合同单价的计算与核定', textEn: 'Complete the calculation and approval of the new budget unit price or contract unit price', serviceid: 12, order: 52, type: 0 },
    52: { text: '按施工图预算或合同清单方式完成工程变更费用的计算', textEn: 'Calculate the cost of engineering changes according to the construction drawing budget or contract list', serviceid: 12, order: 53, type: 0 },
    53: { text: '完成过程结算，包括建立过程结算多维度清单体系，如：项目清单、工程量清单、分项清单；划分过程结算清单单元；依据经确认的实际完工工程量完成计价计费，完成过程结算文件', textEn: 'Complete the process settlement, including establishing a process settlement multi-dimensional list system, such as: project list, project quantity list, sub-item list; dividing the process settlement list unit; completing the pricing and billing based on the confirmed actual completed project quantity, and completing the process settlement documents', serviceid: 12, order: 54, type: 0 },
    54: { text: '完成价格波动费用的计算或审核', textEn: 'Complete calculation or review of price fluctuation charges', serviceid: 12, order: 55, type: 0 },
    55: { text: '完成索赔与补偿费用的计算或审核', textEn: 'Completing claims and reimbursement calculations or reviews', serviceid: 12, order: 56, type: 0 },
    56: { text: '完成结算文件及统计技术经济指标，与相关单位核对合同（工程）结算', textEn: 'Complete settlement documents and statistical technical and economic indicators, and check the contract (project) settlement with the relevant units', serviceid: 12, order: 57, type: 0 },
    57: { text: '完成合同、变更和结算三环节费用的对比，分析合同费用各环节变化原因，提供对比报表和分析报告', textEn: 'Complete the comparison of the costs of the contract, change and settlement, analyze the reasons for the changes in the contract costs, and provide comparative statements and analysis reports', serviceid: 12, order: 58, type: 0 },
    58: { text: '参加与过程结算、工程变更费用确定和合同（工程）结算相关的会议', textEn: 'Participate in meetings related to process settlement, engineering change cost determination, and contract (engineering) settlement', serviceid: 12, order: 59, type: 0 },
    59: { text: '现场勘查与测量实际完工工程量', textEn: 'On-site survey and measurement of actual completed works', serviceid: 12, order: 60, type: 1 },
    60: { text: '复核竣工图纸数量与结算数量的差异，提交数量差异报告', textEn: 'Review the discrepancy between the quantity of as-built drawings and the settlement quantity, and submit a report on the discrepancy in the quantity', serviceid: 12, order: 61, type: 1 },
    61: { text: '协助委托人对项目工程变更费用审批情况进行清理，编制交工验收造价文件', textEn: 'Assist the client to clean up the project engineering change cost approval, and prepare the delivery acceptance cost document', serviceid: 12, order: 62, type: 1 },
    62: { text: '协助委托人处理工期索赔、造价费用等费用的争议与纠纷、仲裁与诉讼', textEn: 'Assist the client in handling disputes and disputes, arbitration and litigation over construction period claims, construction costs and other expenses', serviceid: 12, order: 63, type: 1 },
    63: { text: '编制项目竣工决算文件', textEn: 'Preparation of project completion settlement documents', serviceid: 13, order: 64, type: 0 },
    64: { text: '编制竣工决算备案送审文件', textEn: 'Prepare final account filing and submission documents for review', serviceid: 13, order: 65, type: 0 },
    65: { text: '参与竣工决算相关的会议', textEn: 'Participate in meetings related to the final settlement of construction projects', serviceid: 13, order: 66, type: 0 },
    66: { text: '根据审计意见和决算备案意见调整竣工决算文件', textEn: 'Adjust the Completion Final Accounts Document according to the Audit Opinion and the Final Accounts Filing Opinion', serviceid: 13, order: 67, type: 0 },
    67: { text: '协助委托人编制建设项目造价执行情况报告', textEn: 'Assist the client in preparing the construction project cost implementation report', serviceid: 13, order: 68, type: 1 },
    68: { text: '对比类似项目的技术经济指标，分析技术经济指标差异原因，提供造价差异分析报告', textEn: 'Compare technical and economic indicators of similar projects, analyze the reasons for differences in technical and economic indicators, and provide cost difference analysis reports', serviceid: 13, order: 69, type: 1 },
    69: { text: '根据造价差异分析结果，提出调整工程方案的咨询意见供委托人或关联单位决策', textEn: 'Based on the results of the cost variance analysis, advise on adjusting the project plan for the decision of the client or affiliated unit', serviceid: 13, order: 70, type: 1 },
    70: { text: '协助委托人指导参建单位完成与决算相关辅助文件的编制', textEn: 'Assist the client to guide the participating units to complete the preparation of auxiliary documents related to the settlement of accounts', serviceid: 13, order: 71, type: 1 },
    71: { text: '协助委托人开展项目造价管理及投资效益后评估', textEn: 'Assist the client in project cost management and post-assessment of investment benefits', serviceid: 13, order: 72, type: 1 },
    72: { text: '定期向委托人介绍并解读国家及地方最新发布的关于造价管理有关的法律法规、政策文件和技术标准等信息，针对风险控制方面，提供专业的造价管理建议及实施对策', textEn: 'Regularly introduce and interpret the latest national and local laws and regulations, policy documents and technical standards on cost management to the client, provide professional cost management advice and implement countermeasures for risk control', serviceid: 15, order: 73, type: 2 },
    73: { text: '为委托人在日常建设、运营及经营管理中遇到的造价编制与审核问题，提供专业咨询建议', textEn: 'Provide professional advice on cost preparation and review issues encountered by the client in daily construction, operation and management', serviceid: 15, order: 74, type: 2 },
    74: { text: '对委托人起草的工程勘察设计、工程监理、施工、物资采购和技术服务等方面的合同与协议文本，从造价控制与投资风险角度提供审阅与修改建议', textEn: 'Provide review and revision suggestions from the perspective of cost control and investment risk for the contract and agreement text drafted by the client in engineering survey and design, engineering supervision, construction, material procurement and technical services, etc.', serviceid: 15, order: 75, type: 2 },
    75: { text: '审查各类合同履行情况时，对识别出的潜在漏洞或风险，提出相应的修改建议或补救措施', textEn: 'When reviewing the performance of various types of contracts, suggest modifications or remedies for potential vulnerabilities or risks identified', serviceid: 15, order: 76, type: 2 },
    76: { text: '对委托人提供的造价管理相关的制度，进行审阅与修改，或提供专业咨询建议', textEn: 'Review and modify the system related to cost management provided by the client, or provide professional advice', serviceid: 15, order: 77, type: 2 },
    77: { text: '针对具体建设工程项目的重大投资决策，出具独立的造价专业意见；或根据委托人的要求， 对造价争议事项提供专业依据并出具顾问报告', textEn: 'Provide independent cost professional opinions for major investment decisions of specific construction projects; or provide professional basis for cost disputes and issue consultant reports according to the client\'s requirements', serviceid: 15, order: 78, type: 3 },
    78: { text: '为委托人起草与工程建设相关的合同、协议等文书，包括且不限于工程勘察设计、监理、施 工、采购及技术服务等领域', textEn: 'Draft contracts, agreements and other documents related to engineering construction for the client, including but not limited to engineering survey and design, supervision, construction, procurement and technical services', serviceid: 15, order: 79, type: 3 },
    79: { text: '独立对各类合同的履行情况进行审查，识别潜在漏洞或风险，并提出系统性的修改建议或补 救方案', textEn: 'Independently review the performance of various types of contracts, identify potential vulnerabilities or risks, and propose systematic amendments or remedies', serviceid: 15, order: 80, type: 3 },
    80: { text: '为委托人制订或修订与造价管理相关的制度', textEn: 'Develop or revise systems related to cost management for the Principal', serviceid: 15, order: 81, type: 3 },
    81: { text: '受托处理涉及委托人的造价纠纷、仲裁及诉讼案件，提供专业意见与支持，并出具咨询顾问 报告', textEn: 'Entrusted to handle cost disputes, arbitration and litigation cases involving the client, provide professional advice and support, and issue consultant reports', serviceid: 15, order: 82, type: 3 },
    82: { text: '组织并完成调研工作，包括制定调研计划、实施调研和撰写调研报告', textEn: 'Organize and complete research work, including developing research plans, conducting research, and writing research reports', serviceid: 16, order: 83, type: 0 },
    83: { text: '完成各阶段文件的起草、修订、调整与校对工作；整理并分析各阶段的 反馈意见、完成反馈意见的采纳情况，编制必要的采纳情况报告或说明；完成工作报告', textEn: 'Complete the drafting, revision, adjustment and proofreading of documents at each stage; organize and analyze the feedback at each stage, complete the adoption of feedback, and prepare the necessary adoption reports or explanations; complete the work report', serviceid: 16, order: 84, type: 0 },
    84: { text: '完成评审汇报材料，编写报告；整理并汇总评审意见、复核评审意见，完成评审 意见的采纳及反馈工作', textEn: 'Complete the review report materials and prepare the report; collate and summarize the review comments, review the review comments, and complete the adoption and feedback of the review comments.', serviceid: 16, order: 85, type: 0 },
    85: { text: '负责评审会议的全过程组织工作， 包括准备会议材料、发出通知、组织召开并主持评审会。', textEn: 'Responsible for organizing the entire process of the review meeting, including preparing meeting materials, issuing notices, organizing the convening and chairing the review meeting.', serviceid: 16, order: 86, type: 1 },
    86: { text: '组织并完成调研工作，包括制定调研计划、实施调研和撰写调研报告', textEn: 'Organize and complete research work, including developing research plans, conducting research, and writing research reports', serviceid: 17, order: 87, type: 0 },
    87: { text: '完成工作大纲和编写大纲；开展专项研究工作，完成各阶段研究报告的起 草、修订、调整与校对工作；分研究阶段进行技术报告的编写、修改、调整以及相应的报审 报批工作，包括大纲与成果的评审及验收等；整理并分析各阶段的反馈意见、完成反馈意见 的采纳情况，编制必要的采纳情况报告或说明；编制工作报告', textEn: 'Complete the work outline and prepare the outline; carry out special research work, complete the drafting, revision, adjustment and proofreading of the research report at each stage; prepare, modify, adjust the technical report and the corresponding report and approval work at each stage of the research, including the review and acceptance of the outline and results; collate and analyze the feedback at each stage, complete the adoption of feedback, prepare the necessary adoption report or explanation; prepare the work report', serviceid: 17, order: 88, type: 0 },
    88: { text: '确定测试与验证项目，完成测试与验证工作，完成测试与验证报告', textEn: 'Determine the test and verification items, complete the test and verification work, and complete the test and verification report', serviceid: 17, order: 89, type: 0 },
    89: { text: '完成评审与验收所需汇报材料，编写报告；整理并汇总评审意见、复核评审意见，完成评审意见的采纳及反馈工作', textEn: 'Complete the reporting materials required for review and acceptance, prepare the report; collate and summarize the review comments, review the review comments, and complete the adoption and feedback of the review comments.', serviceid: 17, order: 90, type: 0 },
    90: { text: '编写培训与宣贯相关的材料，组织研究成果的宣贯与推广工作', textEn: 'Prepare materials related to training and dissemination, and organize the dissemination and promotion of research results', serviceid: 17, order: 91, type: 1 },
    91: { text: '负责评审与验收会议的全过程组织工作，包括准备会议材料、发出通知、 组织召开并主持评审与验收会议', textEn: 'Responsible for organizing the entire process of review and acceptance meetings, including preparing meeting materials, issuing notices, organizing and chairing review and acceptance meetings', serviceid: 17, order: 92, type: 1 },
    92: { text: '组织并完成调研工作，包括制定调研计划、实施调研和撰写调研报告', textEn: 'Organize and complete research work, including developing research plans, conducting research, and writing research reports', serviceid: 18, order: 93, type: 0 },
    93: { text: '制定工作计划并完成编制大纲，编制大纲包括工作大纲和编写大纲', textEn: 'Develop the work plan and complete the outline, including the work outline and the outline', serviceid: 18, order: 94, type: 0 },
    94: { text: '完成数据采集与测定工作，编制相应的数据采集与测定报告', textEn: 'Complete the data collection and measurement work, and prepare the corresponding data collection and measurement report.', serviceid: 18, order: 95, type: 0 },
    95: { text: '完成数据整理与分析工作，编制相应的数据分析报告，整理支撑性资料', textEn: 'Complete data collation and analysis work, prepare corresponding data analysis reports, and organize supporting materials', serviceid: 18, order: 96, type: 0 },
    96: { text: '完成定额文本的起草、修改及调整工作；完成各阶段研究报告的起草、修订、 调整与校对工作，编制必要的采纳情况报告或说明；编制工作报告或报批报告', textEn: 'Complete the drafting, revision and adjustment of the quota text; complete the drafting, revision, adjustment and proofreading of the research report at each stage, and prepare the necessary adoption report or explanation; prepare the work report or approval report', serviceid: 18, order: 97, type: 0 },
    97: { text: '确定测试与验证项目，完成测试与验证工作，完成测试与验证报告', textEn: 'Determine the test and verification items, complete the test and verification work, and complete the test and verification report', serviceid: 18, order: 98, type: 0 },
    98: { text: '完成评审与验收所需汇报材料，编写报告；整理并汇总评审意见、复核评 审意见，完成评审意见的采纳及反馈工作', textEn: 'Complete the reporting materials required for review and acceptance, prepare the report; collate and summarize the review comments, review the review comments, and complete the adoption and feedback of the review comments.', serviceid: 18, order: 99, type: 0 },
    99: { text: '编写培训与宣贯相关的材料，组织定额成果的培训与宣贯工作', textEn: 'Prepare materials related to training and dissemination, and organize training and dissemination of quota results', serviceid: 18, order: 100, type: 1 },
    100: { text: '负责评审与验收会议的全过程组织工作，包括准备会议材料、发出通知、 组织召开并主持评审与验收会议', textEn: 'Responsible for organizing the entire process of review and acceptance meetings, including preparing meeting materials, issuing notices, organizing and chairing review and acceptance meetings', serviceid: 18, order: 101, type: 1 },
    101: { text: '开展各类造价信息的搜集、筛选与整理工作', textEn: 'Carry out the collection, screening and collation of various types of cost information', serviceid: 19, order: 102, type: 0 },
    102: { text: '对整理后的信息与数据进行对比与分析', textEn: 'Compare and analyze the collated information and data', serviceid: 19, order: 103, type: 0 },
    103: { text: '依据分析结果，对特定价格或费用进行评估与论证，编制咨询报告', textEn: 'Based on the results of the analysis, evaluate and demonstrate the specific price or cost, and prepare a consulting report', serviceid: 19, order: 104, type: 0 },
    104: { text: '预测未来特定时期的价格或费用，编制预测报告', textEn: 'Forecast prices or expenses for a specific period in the future and prepare forecast reports', serviceid: 19, order: 105, type: 1 },
    105: { text: '研究价格或费用的长期变动规律，编制趋势分析报告', textEn: 'Study the long-term dynamics of prices or fees and prepare trend analysis reports', serviceid: 19, order: 106, type: 1 },
    106: { text: '根据委托人的目标，确定鉴定工作范围', textEn: 'Determine the scope of work of the appraisal according to the objectives of the client', serviceid: 20, order: 107, type: 0 },
    107: { text: '收集与复核鉴定资料', textEn: 'Collect and review appraisal data', serviceid: 20, order: 108, type: 0 },
    108: { text: '组织或参与必要的现场勘查工作', textEn: 'Organize or participate in necessary site surveys', serviceid: 20, order: 109, type: 0 },
    109: { text: '工程量的计算与工程费用的计价或估价', textEn: 'Calculation of project quantities and valuation or valuation of project costs', serviceid: 20, order: 110, type: 0 },
    110: { text: '对争议焦点进行鉴定，提出解决争议的意见', textEn: 'Identify the focus of the dispute and put forward suggestions for resolving the dispute', serviceid: 20, order: 111, type: 0 },
    111: { text: '回复各方当事人的质证意见', textEn: 'Respond to the parties\' cross-examination comments', serviceid: 20, order: 112, type: 0 },
    112: { text: '编制并出具造价鉴定报告', textEn: 'Prepare and issue cost appraisal report', serviceid: 20, order: 113, type: 0 },
    113: { text: '组织或参与现场数据复测或特殊鉴定', textEn: 'Organize or participate in on-site data retesting or special identification', serviceid: 20, order: 114, type: 1 },
    114: { text: '与当事人相关方进行必要的造价数据核对', textEn: 'Conduct necessary cost data checks with the parties concerned', serviceid: 20, order: 115, type: 1 },
    115: { text: '出庭就鉴定报告及相关专业问题接受质证', textEn: 'Appear in court to be cross-examined on the appraisal report and related professional issues', serviceid: 20, order: 116, type: 1 },
    116: { text: '编制影响工程成本的资源要素清单', textEn: 'Compile a list of resource elements that affect the cost of the project', serviceid: 21, order: 117, type: 0 },
    117: { text: '调查人工、材料、设备和施工机械的市场供应情况，主要是价格情况', textEn: 'Investigate the market availability of labor, materials, equipment and construction machinery, mainly in terms of price', serviceid: 21, order: 118, type: 0 },
    118: { text: '测算、分析、计算工程成本费用，结合其他相关因素，编制并出具工程成本测算报告', textEn: 'Calculate, analyze, and calculate the cost of the project, and prepare and issue a project cost estimation report combined with other relevant factors', serviceid: 21, order: 119, type: 0 },
    119: { text: '参加与工程成本测算相关的会议', textEn: 'Participate in meetings related to engineering cost estimation', serviceid: 21, order: 120, type: 0 },
    120: { text: '审核及分析资源配置、施工措施、施工管理方案的经济合理性', textEn: 'Review and analyze the economic rationality of resource allocation, construction measures, and construction management schemes', serviceid: 21, order: 121, type: 1 },
    121: { text: '对比工程成本与投资估算、设计概算、施工图预算、工程量清单预算、合同价，并分析费用差异，提交分析报告', textEn: 'Compare project cost and investment estimates, design estimates, construction drawing budgets, bill of quantities budgets, and contract prices, analyze cost differences, and submit analysis reports', serviceid: 21, order: 122, type: 1 },
    122: { text: '编制影响工程成本的资源要素清单或成本组成科目', textEn: 'Prepare a list of resource elements or cost components that affect the cost of the project', serviceid: 22, order: 123, type: 0 },
    123: { text: '收集人工、材料、设备、施工机械、措施、税费等实际发生的费用', textEn: 'Actual expenses incurred for collecting labor, materials, equipment, construction machinery, measures, taxes, etc.', serviceid: 22, order: 124, type: 0 },
    124: { text: '统计与汇总、拆解与合并、对比与分析工程成本各项费用，编制并出具工程成本核算报告', textEn: 'Statistics and summarization, dismantling and consolidation, comparison and analysis of project costs, preparation and issuance of project cost accounting reports', serviceid: 22, order: 125, type: 0 },
    125: { text: '参加与工程成本核算相关的会议', textEn: 'Participate in meetings related to engineering costing', serviceid: 22, order: 126, type: 0 },
    126: { text: '分析实际工程成本与测算工程成本差异的合理性，提交分析报告', textEn: 'Analyze the rationality of the difference between the actual project cost and the calculated project cost, and submit an analysis report', serviceid: 22, order: 127, type: 1 },
    127: { text: '归纳与总结实际工程成本与测算工程成本的差异原因，提出改进建议', textEn: 'Summarize and summarize the reasons for the difference between the actual project cost and the calculated project cost, and propose improvement suggestions', serviceid: 22, order: 128, type: 1 },
    128: { text: '依据本项目在招标阶段确认的设计图纸工程量，结合工程量清单计量支付规则，对设计工程量进行复核，包括构件工程量、明细表工程量和汇总表工程量，同时与相关方核对工程量', textEn: 'Review the design quantity of the project according to the design drawings confirmed in the bidding stage of the project, combined with the bill of quantities measurement and payment rules, including the component quantity of the project, the schedule quantity of the project and the summary table quantity of the project, and check the quantity of the project with the relevant parties', serviceid: 23, order: 129, type: 0 },
    129: { text: '依据核对后确认的设计图纸数量，细化与合并招标工程量清单或合同工程量清单，建立各维度清单间的数据链接，与相关单位完成核对与确认', textEn: 'Refine and consolidate the bill of quantities or contract bill of quantities according to the number of design drawings confirmed after checking, establish data links between each dimension list, and complete check and confirmation with relevant units', serviceid: 23, order: 130, type: 0 },
    130: { text: '依据确定的招标工程量清单或合同工程量清单，拆解与合并相应的清单费用，与相关单位完成核对与确认', textEn: 'Dismantle and consolidate the corresponding bill of quantities according to the determined bidding bill of quantities or contract bill of quantities, and complete the check and confirmation with the relevant units', serviceid: 23, order: 131, type: 1 },
    131: { text: '现场勘查与测量现场实施工程量', textEn: 'On-site Survey and Measurement On-site Implementation Quantity', serviceid: 23, order: 132, type: 1 },
    132: { text: '完成设计图纸所载数量与复核后的数量进行对比与分析，提供对比分析报告', textEn: 'Complete the comparison and analysis of the quantity contained in the design drawings and the reviewed quantity, and provide a comparative analysis report', serviceid: 23, order: 133, type: 1 },
    133: { text: '参加计算工程量相关的会议', textEn: 'Attend meetings related to the calculation of workloads', serviceid: 23, order: 134, type: 0 },
    134: { text: '协助委托人处理涉及工程数量的争议、纠纷、仲裁或诉讼事务', textEn: 'Assisting the Client in disputes, disputes, arbitrations or litigation involving the number of works', serviceid: 23, order: 135, type: 1 },
    135: { text: '完成工程变更费用的测算', textEn: 'Calculation of engineering change costs completed', serviceid: 24, order: 136, type: 0 },
    136: { text: '完成新增预算单价或合同单价的计算与核定', textEn: 'Complete the calculation and approval of the new budget unit price or contract unit price', serviceid: 24, order: 137, type: 0 },
    137: { text: '按施工图预算或合同清单方式完成工程变更费用的计算', textEn: 'Calculate the cost of engineering changes according to the construction drawing budget or contract list', serviceid: 24, order: 138, type: 0 },
    138: { text: '完成价格波动引起的价差费用的计算', textEn: 'Complete the calculation of spread costs due to price fluctuations', serviceid: 24, order: 139, type: 1 },
    139: { text: '完成索赔与补偿费用的计算，如加速施工费用、暂停施工补偿等', textEn: 'Complete the calculation of claims and reimbursement costs, such as accelerated construction costs, suspended construction reimbursement, etc.', serviceid: 24, order: 140, type: 1 },
    140: { text: '协助委托人处理涉及工程变更费用的争议与纠纷、仲裁与诉讼', textEn: 'Assisting the Client in disputes and disputes, arbitration and litigation involving the cost of engineering changes', serviceid: 24, order: 141, type: 1 },
    141: { text: '分析、论证及评估影响投资估算的主要因素', textEn: 'Analyze, justify and evaluate the main factors affecting investment estimates', serviceid: 25, order: 142, type: 0 },
    142: { text: '根据分析论证结果，完成调整后投资估算文件及主要技术经济指标，与相关单位核对数据', textEn: 'According to the analysis and demonstration results, complete the adjusted investment estimation documents and main technical and economic indicators, and check the data with the relevant units.', serviceid: 25, order: 143, type: 0 },
    143: { text: '完成调整后投资估算与原批复估算的对比及原因分析', textEn: 'Completion of Comparison and Reason Analysis of Adjusted Investment Estimates and Original Approved Estimates', serviceid: 25, order: 144, type: 0 },
    144: { text: '出席与投资估算调整工作相关的会议', textEn: 'Attend meetings related to investment estimation adjustment work', serviceid: 25, order: 145, type: 0 },
    145: { text: '协助调规报告编制单位完成报告的造价专业部分的内容', textEn: 'Assist the preparation unit of the regulation report to complete the contents of the cost professional section of the report', serviceid: 25, order: 146, type: 1 },
    146: { text: '分析、论证及评估影响设计概算的主要因素', textEn: 'Analyze, justify and evaluate the main factors influencing the design estimate', serviceid: 26, order: 147, type: 0 },
    147: { text: '根据分析论证结果，编制调整后设计概算文件及主要技术经济指标，与相关单位核对数据', textEn: 'Based on the analysis and demonstration results, prepare the adjusted design budget proposal document and the main technical and economic indicators, and check the data with the relevant units', serviceid: 26, order: 148, type: 0 },
    148: { text: '完成调整后概算与原批复概算的对比及原因分析，完成调整概算报告', textEn: 'Completion of the comparison and reason analysis between the adjusted budget estimate and the original approved budget estimate, and completion of the adjustment budget estimate report', serviceid: 26, order: 149, type: 0 },
    149: { text: '出席与概算调整工作相关的会议', textEn: 'Attend meetings related to budget adjustment work', serviceid: 26, order: 150, type: 0 },
    150: { text: '协助委托人开展项目预估决算费用的测算、估算工作', textEn: 'Assist the client in estimating and estimating the final cost of the project', serviceid: 26, order: 151, type: 1 },
    151: { text: '检查相关单位对工程造价管理法律法规、规章制度以及工程造价依据的执行情况', textEn: 'Check the implementation of engineering cost management laws and regulations, rules and regulations, and engineering cost basis by relevant units', serviceid: 27, order: 152, type: 0 },
    152: { text: '检查各阶段造价文件编制、审查（批）或备案以及对批复意见的落实情况', textEn: 'Check the preparation, review (batch) or filing of the cost documents at each stage and the implementation of the approval comments', serviceid: 27, order: 153, type: 0 },
    153: { text: '检查造价管理台账和计量支付制度的建立与执行、造价全过程管理与控制情况', textEn: 'Check the establishment and implementation of the cost management ledger and the measurement and payment system, and the management and control of the entire cost process', serviceid: 27, order: 154, type: 0 },
    154: { text: '检查工程变更管理情况', textEn: 'Check engineering change management', serviceid: 27, order: 155, type: 0 },
    155: { text: '检查项目造价信息的收集、分析及报送情况', textEn: 'Check the collection, analysis and submission of project cost information', serviceid: 27, order: 156, type: 0 },
    156: { text: '检查项目从业单位的造价人员执业情况', textEn: 'Inspection of the practice of the cost personnel of the project employer', serviceid: 27, order: 157, type: 0 },
    157: { text: '协助委托人进行现场核查、资料抽检和台账复核工作', textEn: 'Assist the client in on-site verification, data sampling and ledger review', serviceid: 27, order: 158, type: 0 },
    158: { text: '协助委托人整理检查结果和起草检查报告等工作', textEn: 'Assist the client in collating inspection results and drafting inspection reports, etc.', serviceid: 27, order: 159, type: 0 },
    159: { text: '作为造价咨询服务总体协调单位，依据造价技术标准的具体条款或委托方的个性化需求，进一步细化各项工作的具体要求，检查其他服务单位的造价文件的组成完整性、电子文件格式是否符合要求、电子版与纸质版是否对应、造价文件报表的规范性', textEn: 'As the overall coordination unit of cost consulting services, according to the specific terms of the cost technical standards or the personalized needs of the commissioning party, further refine the specific requirements of each work, check the composition integrity of the cost documents of other service units, whether the electronic file format meets the requirements, whether the electronic version corresponds to the paper version, and the standardization of the cost document report.', serviceid: -1, order: 160, type: 4 },
    160: { text: '作为造价咨询服务总体协调单位，负责总体协调其他咨询人或专家团队的工作，确保各方在项目服务中的沟通顺畅，监控造价咨询服务的进展情况，确保各咨询人按时完成工作', textEn: 'As the overall coordination unit of the cost consulting services, it is responsible for the overall coordination of the work of other consultants or expert teams to ensure smooth communication between all parties in the project services, monitor the progress of the cost consulting services, and ensure that each consultant completes the work on time.', serviceid: -1, order: 161, type: 4 },
}

export type WorkListItem = {
    text: string
    textEn?: string
    serviceid: number
    order: number
    type: number
    code: string
    leaf: boolean
}

export const getWorkListEntries = (locale: string = getCurrentLocale()): WorkListItem[] => {
    const useEnglish = isEnglishLocale(locale)
    return Object.values(workList).map(item => {
        const row = item as WorkListItem
        return {
            ...row,
            text: useEnglish ? String(row.textEn || row.text || '') : String(row.text || ''),
            remark: '',
            checked: true,
            custom: false
        }
    })
}

//工作内容树形关系表
export const wholeProcessTasks = [
    {
        fid: 0, //seviceid
        industry: 0,//行业
        sid: [6, 7, 8, 9, 11, 13], //seviceidid
    },
    {
        fid: 0,
        industry: 1,
        sid: [6, 7, 8, 9, 10, 12, 13],
    },
    {
        fid: 0,
        industry: 2,
        sid: [6, 7, 8, 9, 11, 13],
    },
    {
        fid: 2,
        industry: 0,
        sid: [6, 7, 8],
    },
    {
        fid: 2,
        industry: 1,
        sid: [6, 7, 8],
    },
    {
        fid: 2,
        industry: 2,
        sid: [6, 7, 8],
    },
    {
        fid: 3,
        industry: 0,
        sid: [9, 11, 13],
    },
    {
        fid: 4,
        industry: 1,
        sid: [9, 10, 12, 13],
    },
    {
        fid: 3,
        industry: 2,
        sid: [9, 11, 13],
    },
];



let costScaleCal = [
    { code: 'C1-1', staLine: 0, endLine: 100, basic: { staPrice: 0, rate: 0.01 }, optional: { staPrice: 0, rate: 0.002 } },
    { code: 'C1-2', staLine: 100, endLine: 300, basic: { staPrice: 10000, rate: 0.008 }, optional: { staPrice: 2000, rate: 0.0016 } },
    { code: 'C1-3', staLine: 300, endLine: 500, basic: { staPrice: 26000, rate: 0.005 }, optional: { staPrice: 5200, rate: 0.001 } },
    { code: 'C1-4', staLine: 500, endLine: 1000, basic: { staPrice: 36000, rate: 0.004 }, optional: { staPrice: 7200, rate: 0.0008 } },
    { code: 'C1-5', staLine: 1000, endLine: 5000, basic: { staPrice: 56000, rate: 0.003 }, optional: { staPrice: 11200, rate: 0.0006 } },
    { code: 'C1-6', staLine: 5000, endLine: 10000, basic: { staPrice: 176000, rate: 0.002 }, optional: { staPrice: 35200, rate: 0.0004 } },
    { code: 'C1-7', staLine: 10000, endLine: 30000, basic: { staPrice: 276000, rate: 0.0016 }, optional: { staPrice: 55200, rate: 0.00032 } },
    { code: 'C1-8', staLine: 30000, endLine: 50000, basic: { staPrice: 596000, rate: 0.0013 }, optional: { staPrice: 119200, rate: 0.00026 } },
    { code: 'C1-9', staLine: 50000, endLine: 100000, basic: { staPrice: 856000, rate: 0.001 }, optional: { staPrice: 171200, rate: 0.0002 } },
    { code: 'C1-10', staLine: 100000, endLine: 150000, basic: { staPrice: 1356000, rate: 0.0009 }, optional: { staPrice: 271200, rate: 0.00018 } },
    { code: 'C1-11', staLine: 150000, endLine: 200000, basic: { staPrice: 1806000, rate: 0.0008 }, optional: { staPrice: 361200, rate: 0.00016 } },
    { code: 'C1-12', staLine: 200000, endLine: 300000, basic: { staPrice: 2206000, rate: 0.0007 }, optional: { staPrice: 441200, rate: 0.00014 } },
    { code: 'C1-13', staLine: 300000, endLine: 400000, basic: { staPrice: 2906000, rate: 0.0006 }, optional: { staPrice: 581200, rate: 0.00012 } },
    { code: 'C1-14', staLine: 400000, endLine: 600000, basic: { staPrice: 3506000, rate: 0.0005 }, optional: { staPrice: 701200, rate: 0.0001 } },
    { code: 'C1-15', staLine: 600000, endLine: 800000, basic: { staPrice: 4506000, rate: 0.0004 }, optional: { staPrice: 901200, rate: 0.00008 } },
    { code: 'C1-16', staLine: 800000, endLine: 1000000, basic: { staPrice: 5306000, rate: 0.0003 }, optional: { staPrice: 1061200, rate: 0.00006 } },
    { code: 'C1-17', staLine: 1000000, endLine: null, basic: { staPrice: 5906000, rate: 0.00025 }, optional: { staPrice: 1181200, rate: 0.00005 } },
];

let areaScaleCal = [
    { code: 'C2-1', staLine: 0, endLine: 50, basic: { staPrice: 0, rate: 200 }, optional: { staPrice: 0, rate: 40 } },
    { code: 'C2-2', staLine: 50, endLine: 100, basic: { staPrice: 10000, rate: 160 }, optional: { staPrice: 2000, rate: 32 } },
    { code: 'C2-3', staLine: 100, endLine: 500, basic: { staPrice: 18000, rate: 120 }, optional: { staPrice: 3600, rate: 24 } },
    { code: 'C2-4', staLine: 500, endLine: 1000, basic: { staPrice: 66000, rate: 80 }, optional: { staPrice: 13200, rate: 16 } },
    { code: 'C2-5', staLine: 1000, endLine: 5000, basic: { staPrice: 106000, rate: 60 }, optional: { staPrice: 21200, rate: 12 } },
    { code: 'C2-6', staLine: 5000, endLine: null, basic: { staPrice: 346000, rate: 20 }, optional: { staPrice: 69200, rate: 4 } },
];







export type IndustryType = (typeof industryTypeList)[number]['type']
type DictItem = Record<string, any>
type DictEntry = { id: string; rawId: string; item: DictItem }
type DictByIdMap = Record<string, DictItem>
type BasicFeeFromScaleResult = {
    basic: number
    optional: number
    basicFormula: string
    optionalFormula: string
}

const industryTypeById = new Map(
    industryTypeList.flatMap(item => {
        return [[String(item.id).trim(), item.type]]

    })
)
/**
 * 根据行业的id获取对应专业字段（isRoad/isRailway/isWaterway）。
 * @returns 对应行业字段；未命中时返回 null
 */
export const getIndustryTypeValue = (industryId: unknown): IndustryType | null =>
    industryTypeById.get(String(industryId || '').trim()) || null

/**
 * 判断字典项是否在指定行业下可用。
 * @returns 是否可用
 */
export const isIndustryEnabledByType = (
    item: DictItem | undefined,
    typeValue: IndustryType | null
): boolean => Boolean(typeValue && item?.[typeValue] === true)

// 判断是否为“通用项”（三种行业都适用）。
const isGenericIndustryItem = (item: Record<string, any> | undefined) =>
    Boolean(item?.isRoad && item?.isRailway && item?.isWaterway)

/**
 * 判断专业ID（majorList 的 key）是否属于指定行业范围（可选是否包含通用项）。
 * @returns 是否属于行业范围
 */
export const isMajorIdInIndustryScope = (
    majorId: unknown,
    industryCode: unknown,
    includeGeneric = true
): boolean => {
    const industryType = getIndustryTypeValue(industryCode)
    if (!majorId || !industryType) return false
    const id = String(majorId).trim()
    const majorItem = getMajorDictEntries().find(entry => String(entry.id).trim() === id)?.item
    if (isIndustryEnabledByType(majorItem, industryType)) return true

    if (!includeGeneric) return false
    return isGenericIndustryItem(majorItem)
}

const isPlainObject = (value: unknown): value is Record<string, any> =>
    Boolean(value) && typeof value === 'object' && !Array.isArray(value)

const hasCodeLike = (value: Record<string, any>) =>
    typeof value.code === 'string'

const isDictLeafNode = (value: unknown): value is Record<string, any> =>
    isPlainObject(value) && hasCodeLike(value) && typeof value.name === 'string'

// 递归提取字典树中的叶子节点（具有 code + name 的业务项）。
const collectDictLeafEntries = (
    source: Record<string, any>,
    prefix = ''
): Array<{ rawId: string; item: Record<string, any> }> => {
    const result: Array<{ rawId: string; item: Record<string, any> }> = []
    if (!isPlainObject(source)) return result

    for (const [key, value] of Object.entries(source)) {
        const rawId = prefix ? `${prefix}.${key}` : String(key)
        if (isDictLeafNode(value)) {
            result.push({ rawId, item: value })
            continue
        }
        if (isPlainObject(value)) {
            result.push(...collectDictLeafEntries(value, rawId))
        }
    }
    return result
}

// 计算排序权重：优先用 order，再退化为 rawId 的数值。
const getItemSortValue = (item: Record<string, any>, rawId: string) => {
    const order = Number(item?.order)
    if (Number.isFinite(order)) return order
    const rawIdNum = Number(rawId)
    if (Number.isFinite(rawIdNum)) return rawIdNum
    return Number.POSITIVE_INFINITY
}

// 按权重与编码排序，保证下拉展示顺序稳定。
const sortDictEntries = (
    entries: Array<{ id: string; rawId: string; item: Record<string, any> }>
) =>
    entries.sort((a, b) => {
        const orderDiff =
            getItemSortValue(a.item, a.rawId) - getItemSortValue(b.item, b.rawId)
        if (orderDiff !== 0) return orderDiff
        const codeA = String(a.item?.code || a.id)
        const codeB = String(b.item?.code || b.id)
        return codeA.localeCompare(codeB)
    })

// 将原始字典对象转换为扁平 entries 列表。
const buildDictEntries = (source: Record<string, any>) =>
    sortDictEntries(
        collectDictLeafEntries(source).map(({ rawId, item }) => {
            return {
                id: rawId,
                rawId,
                item: localizeDictItem(item)
            }
        })
    )

/**
 * 获取专业字典的扁平化列表。
 * @returns 专业字典条目列表
 */
export const getMajorDictEntries = (): DictEntry[] => buildDictEntries(majorList as Record<string, any>)

/**
 * 获取服务字典的扁平化列表。
 * @returns 服务字典条目列表
 */
export const getServiceDictEntries = (): DictEntry[] => buildDictEntries(serviceList as Record<string, any>)


/**
 * 构建“专业ID -> 专业项”映射。
 * @returns 专业项映射表
 */
export const getMajorDictById = (): DictByIdMap =>
    Object.fromEntries(getMajorDictEntries().map(entry => [entry.id, entry.item]))

/**
 * 构建“服务ID -> 服务项”映射。
 * @returns 服务项映射表
 */
export const getServiceDictById = (): DictByIdMap =>
    Object.fromEntries(getServiceDictEntries().map(entry => [entry.id, entry.item]))

/**
 * 构建“专业编码(code) -> 专业ID”别名映射。
 * @returns 编码到专业ID的别名映射
 */
export const getMajorIdAliasMap = (): Map<string, string> =>
    new Map(
        getMajorDictEntries().flatMap(entry => {
            const code = String(entry.item?.code || '')
            return code ? [[code, entry.id]] : []
        })
    )

/**
 * 按 ID 或 code 获取专业项（优先 ID，找不到时按 code 别名回查）。
 * @returns 匹配的专业项；未命中时返回 undefined
 */
export const getMajorDictItemById = (id: string | number): DictItem | undefined => {
    const key = String(id)
    const dict = getMajorDictById() as DictByIdMap
    if (dict[key]) return dict[key]
    const alias = getMajorIdAliasMap().get(key)
    return alias ? dict[alias] : undefined
}

/**
 * 按 ID 获取服务项。
 * @returns 匹配的服务项；未命中时返回 undefined
 */
export const getServiceDictItemById = (id: string | number): DictItem | undefined => {
    const key = String(id)
    const dict = getServiceDictById() as DictByIdMap
    return dict[key]
}

export type QuickCalcOption = {
    key: string
    label: string
    id: string | Record<string, string>
}

export type QuickCalcGroup = {
    key: string
    label: string
    hint: string
    items: QuickCalcOption[]
    rows: string[][]
    industryId?: string
}

const getQuickCalcI18nText = (path: string, fallback: string) => {
    try {
        const text = i18n?.global?.t?.(path as any)
        if (typeof text === 'string' && text !== path) return text
    } catch {
        // ignore i18n runtime errors and fallback to default text
    }
    return fallback
}

const getQuickDictLabel = (item: DictItem | undefined, fallback = '') =>
    String(item?.quickLabel || item?.name || fallback)

const createQuickOptionByServiceKey = (
    key: string,
    override?: Partial<QuickCalcOption> & { label?: string }
): QuickCalcOption => {
    const entry = getServiceDictEntries().find(item => String(item.id || '') === key)
    return {
        key: override?.key || key,
        label: override?.label || getQuickDictLabel(entry?.item, key),
        id: override?.id || key
    }
}

const createQuickOptionByMajorKey = (
    key: string,
    override?: Partial<QuickCalcOption> & { label?: string }
): QuickCalcOption => {
    const entry = getMajorDictEntries().find(item => String(item.id || '') === key)
    return {
        key: override?.key || key,
        label: override?.label || getQuickDictLabel(entry?.item, key),
        id: override?.id || key
    }
}

export const getQuickCalcGroups = (): QuickCalcGroup[] => [
    {
        key: 'consult',
        label: getQuickCalcI18nText('quickCalc.types.consult.label', '咨询类别（常用）'),
        hint: getQuickCalcI18nText('quickCalc.types.consult.hint', '先选择咨询类别，再补规模和预算参数。'),
        items: [
            createQuickOptionByServiceKey('0'),
            createQuickOptionByServiceKey('2'),
            createQuickOptionByServiceKey('3', {
                key: 'D2-2',
                id: {//行业不同显示的不同
                    '0': '3',
                    '1': '4',
                    '2': '3'
                }
            }),
            createQuickOptionByServiceKey('6'),
            createQuickOptionByServiceKey('7'),
            createQuickOptionByServiceKey('8'),
            createQuickOptionByServiceKey('9'),
            createQuickOptionByServiceKey('10'),
            createQuickOptionByServiceKey('11', {
                key: 'D3-6',
                id: {
                    '0': '11',
                    '1': '12',
                    '2': '11'
                }
            }),
            createQuickOptionByServiceKey('13'),
            createQuickOptionByServiceKey('20'),
            createQuickOptionByServiceKey('21'),
            createQuickOptionByServiceKey('22'),
            createQuickOptionByServiceKey('23'),
            createQuickOptionByServiceKey('24'),
            createQuickOptionByServiceKey('25'),
            createQuickOptionByServiceKey('26')
        ],
        rows: [
            ['0', '2', 'D2-2'],
            ['6', '7', '8', '9', '10', 'D3-6', '13'],
            ['20', '21', '22', '23', '24', '25', '26']
        ]
    },
    {
        key: 'general',
        label: getQuickCalcI18nText('quickCalc.types.general.label', '通用专业'),
        hint: getQuickCalcI18nText('quickCalc.types.general.hint', '跨行业共用的补偿与其他费用专业。'),
        items: [
            createQuickOptionByMajorKey('1'),
            createQuickOptionByMajorKey('2'),
            createQuickOptionByMajorKey('3'),
            createQuickOptionByMajorKey('4')
        ],
        rows: [['1', '2', '3', '4']]
    },
    {
        key: 'road',
        label: getQuickCalcI18nText('quickCalc.types.road.label', '公路工程专业'),
        hint: getQuickCalcI18nText('quickCalc.types.road.hint', '首页行业为公路工程时默认展示。'),
        industryId: '0',
        items: [
            createQuickOptionByMajorKey('7'),
            createQuickOptionByMajorKey('8'),
            createQuickOptionByMajorKey('9'),
            createQuickOptionByMajorKey('10'),
            createQuickOptionByMajorKey('11'),
            createQuickOptionByMajorKey('12'),
            createQuickOptionByMajorKey('13'),
            createQuickOptionByMajorKey('14'),
            createQuickOptionByMajorKey('15'),
            createQuickOptionByMajorKey('16'),
            createQuickOptionByMajorKey('17')
        ],
        rows: [
            ['7'],
            ['8', '9', '10', '11', '12', '13'],
            ['14', '15', '16', '17']
        ]
    },
    {
        key: 'railway',
        label: getQuickCalcI18nText('quickCalc.types.railway.label', '铁路工程专业'),
        hint: getQuickCalcI18nText('quickCalc.types.railway.hint', '首页行业为铁路工程时默认展示。'),
        industryId: '1',
        items: [
            createQuickOptionByMajorKey('18'),
            createQuickOptionByMajorKey('19'),
            createQuickOptionByMajorKey('20'),
            createQuickOptionByMajorKey('21'),
            createQuickOptionByMajorKey('22'),
            createQuickOptionByMajorKey('23'),
            createQuickOptionByMajorKey('24'),
            createQuickOptionByMajorKey('25'),
            createQuickOptionByMajorKey('26'),
            createQuickOptionByMajorKey('27')
        ],
        rows: [
            ['18'],
            ['19', '20', '21', '22', '23'],
            ['24', '25', '26', '27']
        ]
    },
    {
        key: 'waterway',
        label: getQuickCalcI18nText('quickCalc.types.waterway.label', '水运工程专业'),
        hint: getQuickCalcI18nText('quickCalc.types.waterway.hint', '首页行业为水运工程时默认展示。'),
        industryId: '2',
        items: [
            createQuickOptionByMajorKey('28'),
            createQuickOptionByMajorKey('29'),
            createQuickOptionByMajorKey('30'),
            createQuickOptionByMajorKey('31'),
            createQuickOptionByMajorKey('32'),
            createQuickOptionByMajorKey('33')
        ],
        rows: [
            ['28'],
            ['29', '30'],
            ['31', '32', '33']
        ]
    }
]


// 判断数值是否命中分段区间：默认 (staLine, endLine]，但首段 0 需要包含 0。
const inRange = (sv: number, staLine: number, endLine: number | null) =>
    (staLine === 0 ? staLine <= sv : staLine < sv) && (endLine == null || sv <= endLine)

// 按分段参数计算基础费用。
const calcScaleFee = (params: {
    staPrice: number
    sv: number
    staLine: number
    rate: number
    multiplier?: number
}) => {
    const multiplier = params.multiplier ?? 1
    return roundTo(
        toDecimal(params.staPrice).plus(
            toDecimal(params.sv)
                .minus(params.staLine)
                .mul(multiplier)
                .mul(params.rate)
        )
    )
}

// 将费率转成千分数文本（例如 0.012 -> 12‰）。
const scaleRatePermillage = (rate: number) => roundTo(toDecimal(rate).mul(1000), 2)

// 生成“基础费用”计算公式字符串，供前端展示。
const buildScaleFormula = (params: {
    staPrice: number
    sv: number
    staLine: number
    rate: number
    multiplier?: number
    showPermillage?: boolean
}) => {
    const multiplier = params.multiplier ?? 1
    const currentValue = toDecimal(params.sv).mul(multiplier).toNumber()
    const staLineValue = toDecimal(params.staLine).mul(multiplier).toNumber()
    const rateText = params.showPermillage ? `${scaleRatePermillage(params.rate)}‰` : params.rate
    if (params.staPrice) {
        return `${numberFormatter(params.staPrice, 0)}+(${numberFormatter(currentValue, 0)}-${numberFormatter(staLineValue, 0)})×${rateText}`
    }
    return `${numberFormatter(currentValue, 0)}×${rateText}`
}



/**
 * 根据规模值与规模类型，计算基础费用及对应公式（返回基础/附加两部分）。
 * @returns 基础费用计算结果；输入非法或未命中区间时返回 null
 */
export function getBasicFeeFromScale(
    scaleValue: unknown,
    scaleType: 'cost' | 'area'
): BasicFeeFromScaleResult | null {
    if (scaleValue == null || scaleValue === '') return null
    const sv = Number(scaleValue)
    if (!Number.isFinite(sv) || sv < 0) return null

    if (scaleType === 'cost') {
        const targetRange = costScaleCal.find(f => inRange(sv, f.staLine, f.endLine))
        if (!targetRange) return null
        return {
            basic: calcScaleFee({
                staPrice: targetRange.basic.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.basic.rate,
                multiplier: 10000
            }),
            optional: calcScaleFee({
                staPrice: targetRange.optional.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.optional.rate,
                multiplier: 10000
            }),
            basicFormula: buildScaleFormula({
                staPrice: targetRange.basic.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.basic.rate,
                multiplier: 10000,
                showPermillage: true
            }),
            optionalFormula: buildScaleFormula({
                staPrice: targetRange.optional.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.optional.rate,
                multiplier: 10000,
                showPermillage: true
            })
        }
    }

    if (scaleType === 'area') {
        const targetRange = areaScaleCal.find(f => inRange(sv, f.staLine, f.endLine))
        if (!targetRange) return null
        return {
            basic: calcScaleFee({
                staPrice: targetRange.basic.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.basic.rate
            }),
            optional: calcScaleFee({
                staPrice: targetRange.optional.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.optional.rate
            }),
            basicFormula: buildScaleFormula({
                staPrice: targetRange.basic.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.basic.rate
            }),
            optionalFormula: buildScaleFormula({
                staPrice: targetRange.optional.staPrice,
                sv,
                staLine: targetRange.staLine,
                rate: targetRange.optional.rate
            })
        }
    }

    return null
}


/**
 * 导出入口：生成 Excel 并触发保存（优先使用 File System Access API）。
 * @returns 导出流程完成后的 Promise
 */
export async function exportFile(fileName: string, data: any | (() => Promise<any>), onSaveConfirmed?: () => void): Promise<string | null> {
    if (window.showSaveFilePicker) {
        const handle = await window.showSaveFilePicker({
            suggestedName: fileName,
            types: [{
                description: "Excel 文件",
                accept: { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"] }
            }]
        });
        try {
            onSaveConfirmed?.()
            const resolvedData = typeof data === 'function' ? await data() : data
            const workbook = await generateTemplate(resolvedData);
            const buffer = await workbook.xlsx.writeBuffer();
            const writable = await handle.createWritable();
            await writable.write(buffer);
            await writable.close();
            // 返回 blob URL 供打开文件使用
            const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
            return URL.createObjectURL(blob);
            // ecCom.WeaLoadingGlobal.destroy();
            // antd.notification['success']({
            //     message: '下载成功！',
            // });
        } catch (err) {
            console.log('err:' + err);
            return null;
        }
    } else {
        const resolvedData = typeof data === 'function' ? await data() : data
        const workbook = await generateTemplate(resolvedData);
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
        const url1 = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url1;
        a.download = `${fileName}.xlsx`;
        a.click();
        URL.revokeObjectURL(url1);
        return null;
    }
}




// 按模板生成最终工作簿：填充封面、目录、各分表及汇总数据。
async function generateTemplate(data) {
    console.log(data)
    data = cloneCellValue(data);
    const resolveScaleMajorId = (scaleRow) => {
        const majorid = Number(scaleRow?.majorid);
        if (Number.isFinite(majorid)) return majorid;
        const major = Number(scaleRow?.major);
        return Number.isFinite(major) ? major : null;
    };
    // 编制说明 → 工作内容的前后默认项
    let prefixIDs = [0,2,3,4,6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,29,30,31,32,33,34,35,36];
    let suffixIDs = [0,2,3,4,6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,29,30,31,32,33,34,35,36];
    const prefixTexts = ['收集并整理各阶段及各项造价咨询所需的基础资料，审核基础资料的完整性和合理性', '编制造价咨询服务总体及各分项的服务计划、服务大纲及工作方案', '了解建设工程项目所在地的建设条件'];
    const suffixTexts = ['协助委托人完成咨询服务相应造价文件的报审、报备、报批、检查与审计涉及的解释与回复、修改与调整等工作', '完成本合同造价档案的收集、整理和归档'];
    try {
        // 获取模板
        let templateExcel = 'template-excel';
        let templateUrl = `../public/${templateExcel}.xlsx`;
        let buf = await (await fetch(templateUrl)).arrayBuffer();
        let workbook = new ExcelJS.Workbook();
        await workbook.xlsx.load(buf);

        // 生成表格
        const mustGetWorksheet = (name: string) => {
            const sheet = workbook.getWorksheet(name);
            if (!sheet) throw new Error(`模板缺少工作表: ${name}`);
            return sheet;
        };
        let fm_sheet = mustGetWorksheet('封面');
        let ml_sheet = mustGetWorksheet('目录');
        let yz01_sheet = mustGetWorksheet('预总01表');
        yz01_sheet.headerFooter.oddHeader = yz01_sheet.headerFooter.oddHeader.replace(/×××/g, data.name);

        // 更新封面
        fm_sheet.getRow(2).getCell(1).value = data.name;
        fm_sheet.getRow(8).getCell(4).value = data.writer;
        fm_sheet.getRow(9).getCell(4).value = data.reviewer;
        fm_sheet.getRow(10).getCell(4).value = data.company;
        if (data.date) {
            const dateArr = data.date.split('-');
            let year = dateArr[0];
            let month = Number(dateArr[1]);
            let day = Number(dateArr[2]);
            fm_sheet.getRow(11).getCell(4).value = `${year} 年 ${month} 月 ${day} 日`;
        }

        // 更新预总01表的列数
        let yz01Mod = (data.contracts.length + 1) % 4;
        let yz01Num = (data.contracts.length + 1 - yz01Mod) / 4;
        switch (yz01Mod) {
            case 0:
                yz01_sheet.spliceColumns(8, 15);
                break;
            case 1:
                yz01_sheet.spliceColumns(8, 11);
                break;
            case 2:
                yz01_sheet.spliceColumns(19, 4);
                yz01_sheet.spliceColumns(8, 6);
                break;
            case 3:
                yz01_sheet.spliceColumns(14, 9);
                break;
        }
        if (yz01Num == 0) {
            yz01_sheet.spliceColumns(1, 7);
        } else {
            if (yz01Num > 1) {
                for (let i = 0; i < yz01Num - 1; i++) {
                    insertAndCopyColumn(7 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7], yz01_sheet);
                }
            }
        }

        // 按合同段更新目录及相关表
        let ml_slotRow = 14;
        let ml_number = 1;
        let allServices = [];
        let allAddtional = {};
        let allReserve = {};
        let allMajors = {};
        let allServiceMajors = new Set();
        let allMethods = new Set();
        let allReserveMethods = { 0: { coe: [], cid: [], coeSet: new Set() }, 4: [], 5: [] };
        let contractFeeSummary = [];
        const applyScaleToMajorMap = (scaleRows, slotIndex) => {
            if (!Array.isArray(scaleRows)) return;
            scaleRows.forEach(sci => {
                const scaleMajorId = resolveScaleMajorId(sci);
                if (scaleMajorId == null) return;
                if (allMajors[scaleMajorId]) {
                    allMajors[scaleMajorId][slotIndex] = sci;
                } else {
                    allMajors[scaleMajorId] = { [slotIndex]: sci };
                }
            });
        };
        const costRowExists = (scaleRows) => Array.isArray(scaleRows) && scaleRows.some(sci => toFiniteNumber(sci?.cost) != null);
        const setTotalCostRow = (slotIndex, cost) => {
            if (toFiniteNumber(cost) == null) return;
            const totalScaleRow = { majorid: -1, major: -1, cost, area: null };
            if (allMajors[-1]) {
                allMajors[-1][slotIndex] = totalScaleRow;
            } else {
                allMajors[-1] = { [slotIndex]: totalScaleRow };
            }
        };
        applyScaleToMajorMap(data.scale, data.contracts.length);
        const projectScaleCost = toFiniteNumber(data.scaleCost);
        if (projectScaleCost != null && (projectScaleCost !== 0 || costRowExists(data.scale))) {
            // 模板内部仍需要“总投资”行，但不把它写回导出 payload。
            setTotalCostRow(data.contracts.length, projectScaleCost);
        }
        data.contracts.forEach((ci, index) => {
            contractFeeSummary.push(`${ci.name} ${Number(ci.fee).toLocaleString()}元`);
            ci.allServiceMajors = new Set();
            // 记录allMajors
            applyScaleToMajorMap(ci.scale, index);
            if (costRowExists(ci.scale)) {
                setTotalCostRow(index, addNumbers(...ci.scale.map(sci => toFiniteNumber(sci?.cost))));
            }
            ci.method1 = [];
            ci.method2 = [];
            ci.method3 = [];
            ci.method4 = [];
            // 按计价方式汇总服务数据对象
            ci.services.forEach(si => {
                if (prefixIDs.includes(si.id)) ci.hasPreTexts = true;
                if (suffixIDs.includes(si.id)) ci.hasSufTexts = true;
                if (si.method1) ci.method1.push(si.id);
                if (si.method2) ci.method2.push(si.id);
                if (si.method3) ci.method3.push(si.id);
                if (si.method4) ci.method4.push(si.id);
            });
            const addtionalM4 = [];
            const addtionalM5 = [];
            if (ci.addtional) {
                allAddtional[index] = ci.addtional;
                ci.addtional.det.forEach(f => {
                    if (f.m4) addtionalM4.push(f);
                    if (f.m5) addtionalM5.push(f);
                });
            }
            if (addtionalM5.length) ci.method5 = { addtional: addtionalM5 };
            if (ci.reserve) allReserve[index] = ci.reserve;
            const reserveM4 = ci.reserve?.m4;
            const reserveM5 = ci.reserve?.m5;
            if (reserveM5) ci.method5 = ci.method5 ? Object.assign({ reserve: reserveM5 }, ci.method5) : { reserve: reserveM5 };

            // 创建sheet
            let ml_sourceRows = [ml_sheet.getRow(6)];
            let sheet_1 = copyWorksheet(workbook, '预i-1表', `预${index + 1}-1表`);
            sheet_1.headerFooter.oddHeader = sheet_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-1 表/g, `预 ${index + 1}-1 表`).replace(/第i合同/g, ci.name);
            sheet_1.getRow(1).getCell(4).value = sheet_1.getRow(1).getCell(4).value.replace(/第i合同/g, ci.name);
            let sheet_2;
            let sheet_2_1;
            let sheet_2_2;
            let sheet_3;
            let sheet_4;
            let sheet_4_1;
            let sheet_5;
            if (ci.method1.length || ci.method2.length) {
                ml_sourceRows.push(ml_sheet.getRow(7));
                sheet_2 = copyWorksheet(workbook, '预i-2表', `预${index + 1}-2表`);
                sheet_2.headerFooter.oddHeader = sheet_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2 表/g, `预 ${index + 1}-2 表`).replace(/第i合同/g, ci.name);
                if (ci.method1.length) {
                    allMethods.add(1);
                    ml_sourceRows.push(ml_sheet.getRow(8));
                    sheet_2_1 = copyWorksheet(workbook, '预i-2-1表', `预${index + 1}-2-1表`);
                    sheet_2_1.headerFooter.oddHeader = sheet_2_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-1 表/g, `预 ${index + 1}-2-1 表`).replace(/第i合同/g, ci.name);
                }
                if (ci.method2.length) {
                    allMethods.add(2);
                    ml_sourceRows.push(ml_sheet.getRow(9));
                    sheet_2_2 = copyWorksheet(workbook, '预i-2-2表', `预${index + 1}-2-2表`);
                    sheet_2_2.headerFooter.oddHeader = sheet_2_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-2 表/g, `预 ${index + 1}-2-2 表`).replace(/第i合同/g, ci.name);
                }
            }
            if (ci.method3.length) {
                allMethods.add(3);
                ml_sourceRows.push(ml_sheet.getRow(10));
                sheet_3 = copyWorksheet(workbook, '预i-3表', `预${index + 1}-3表`);
                sheet_3.headerFooter.oddHeader = sheet_3.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-3 表/g, `预 ${index + 1}-3 表`).replace(/第i合同/g, ci.name);
            }
            if (ci.method4.length || addtionalM4.length || reserveM4) {
                allMethods.add(4);
                ml_sourceRows.push(ml_sheet.getRow(11));
                ml_sourceRows.push(ml_sheet.getRow(12));
                sheet_4 = copyWorksheet(workbook, '预i-4表', `预${index + 1}-4表`);
                sheet_4.headerFooter.oddHeader = sheet_4.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4 表/g, `预 ${index + 1}-4 表`).replace(/第i合同/g, ci.name);
                sheet_4_1 = copyWorksheet(workbook, '预i-4-1表', `预${index + 1}-4-1表`);
                sheet_4_1.headerFooter.oddHeader = sheet_4_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4-1 表/g, `预 ${index + 1}-4-1 表`).replace(/第i合同/g, ci.name);
                // 更新i-4和i-4-1表的小计行
                let sumArr = ci.method4.map(m => ci.services.find(f => f.id == m)?.method4).concat(addtionalM4.map(m => m.m4));
                if (reserveM4) sumArr.push(reserveM4);
                let sumObj = sumArr.reduce((a, b) => {
                    return {
                        person_num: addNumbers(a.person_num, toFiniteNumber(b?.person_num)),
                        work_day: addNumbers(a.work_day, toFiniteNumber(b?.work_day)),
                        fee: addNumbers(a.fee, toFiniteNumber(b?.fee))
                    };
                }, { person_num: 0, work_day: 0, fee: 0 });
                sheet_4.getRow(3).getCell(4).value = numberFormatter(sumObj.person_num, 0);
                sheet_4.getRow(3).getCell(5).value = numberFormatter(sumObj.work_day, 2);
                sheet_4.getRow(3).getCell(6).value = numberFormatter(sumObj.fee, 2);
                sheet_4_1.getRow(3).getCell(4).value = '/';
                sheet_4_1.getRow(3).getCell(5).value = '/';
                sheet_4_1.getRow(3).getCell(6).value = '/';
                sheet_4_1.getRow(3).getCell(7).value = numberFormatter(sumObj.person_num, 0);
                sheet_4_1.getRow(3).getCell(8).value = numberFormatter(sumObj.work_day, 2);
                sheet_4_1.getRow(3).getCell(9).value = numberFormatter(sumObj.fee, 2);
            }
            if (ci.method5) {
                allMethods.add(5);
                ml_sourceRows.push(ml_sheet.getRow(13));
                sheet_5 = copyWorksheet(workbook, '预i-5表', `预${index + 1}-5表`);
                sheet_5.headerFooter.oddHeader = sheet_5.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-5 表/g, `预 ${index + 1}-5 表`).replace(/第i合同/g, ci.name);
                sheet_5.getRow(3).getCell(3).value = '/';
                sheet_5.getRow(3).getCell(4).value = '/';
                sheet_5.getRow(3).getCell(5).value = '/';
                const method5AdditionalFee = ci.method5.addtional?.reduce((a, b) =>
                    addNumbers(a, toFiniteNumber(b?.m5?.fee)), 0) || 0;
                sheet_5.getRow(3).getCell(6).value = numberFormatter(
                    addNumbers(method5AdditionalFee, toFiniteNumber(ci.method5.reserve?.fee)),
                    2
                );
            }

            // 更新目录的第三部分
            cusInsertRowFunc(ml_slotRow, ml_sourceRows, ml_sheet, () => ml_slotRow++, (targetCell, sourceCell, colNumber) => {
                if (colNumber == 1) {
                    targetCell.value = ml_number++;
                } else if (colNumber == 2) {
                    targetCell.value = sourceCell.value.replaceAll('第i合同', ci.name);
                } else if (colNumber == 3) {
                    targetCell.value = sourceCell.value.replaceAll('i', index + 1);
                } else {
                    targetCell.value = sourceCell.value;
                }
            });

            // 根据服务更新相关表
            let num_2 = 1;
            let num_2_1 = 1;
            let num_2_2 = 1;
            let num_3 = 1;
            let num_4 = 1;
            let num_4_1 = 1;
            let num_5 = 1;
            let m1Sum = 0;
            let m2Sum = 0;
            let m3Sum = 0;
            let m4Sum = 0;
            let serviceSum1 = 0;
            let serviceSum2 = 0;
            ci.services.forEach((sobj, sindex) => {
                let allServicesX = allServices.find(s => s.id == sobj.id);
                if (allServicesX) {
                    allServicesX.contracts[index] = sobj.fee;
                } else {
                    allServices.push({
                        id: sobj.id,
                        contracts: {
                            [index]: sobj.fee,
                        },
                    });
                }
                let serviceX = serviceList[sobj.id];
                cusInsertRowFunc(4 + sindex, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
                    targetRow.getCell(1).value = sindex + 1;
                    targetRow.getCell(2).value = serviceX.code;
                    targetRow.getCell(3).value = serviceX.name;
                    if (sobj.method1) {
                        targetRow.getCell(4).value = numberFormatter(sobj.method1.fee, 2);
                        m1Sum = addNumbers(m1Sum, toFiniteNumber(sobj.method1.fee));
                    }
                    if (sobj.method2) {
                        targetRow.getCell(5).value = numberFormatter(sobj.method2.fee, 2);
                        m2Sum = addNumbers(m2Sum, toFiniteNumber(sobj.method2.fee));
                    }
                    if (sobj.method3) {
                        targetRow.getCell(6).value = numberFormatter(sobj.method3.fee, 2);
                        m3Sum = addNumbers(m3Sum, toFiniteNumber(sobj.method3.fee));
                    }
                    if (sobj.method4) {
                        targetRow.getCell(7).value = numberFormatter(sobj.method4.fee, 2);
                        m4Sum = addNumbers(m4Sum, toFiniteNumber(sobj.method4.fee));
                    }
                    targetRow.getCell(8).value = numberFormatter(sobj.fee, 2);
                    targetRow.getCell(9).value = numberFormatter(sobj.finalFee, 2);
                    serviceSum1 = addNumbers(serviceSum1, toFiniteNumber(sobj.fee));
                    serviceSum2 = addNumbers(serviceSum2, toFiniteNumber(sobj.finalFee));
                });
                if (sobj.method1 || sobj.method2) {
                    cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
                        targetRow.getCell(1).value = num_2++;
                        targetRow.getCell(2).value = serviceX.code;
                        targetRow.getCell(3).value = serviceX.name;
                        targetRow.getCell(4).value = '/';
                        targetRow.getCell(5).value = '/';
                        targetRow.getCell(6).value = '/';
                        targetRow.getCell(7).value = '/';
                        if (sobj.method1) {
                            targetRow.getCell(8).value = numberFormatter(sobj.method1.basicFee, 2);
                            targetRow.getCell(9).value = numberFormatter(sobj.method1.fee, 2);
                            cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
                                targetRow.getCell(1).value = num_2_1++;
                                targetRow.getCell(2).value = serviceX.code;
                                targetRow.getCell(3).value = serviceX.name;
                                targetRow.getCell(4).value = numberFormatter(sobj.method1.cost, 2);
                                targetRow.getCell(5).value = '/';
                                targetRow.getCell(6).value = numberFormatter(sobj.method1.basicFee_basic, 2);
                                targetRow.getCell(7).value = '/';
                                targetRow.getCell(8).value = numberFormatter(sobj.method1.basicFee_optional, 2);
                                targetRow.getCell(9).value = numberFormatter(sobj.method1.basicFee, 2);
                            });
                        }
                        if (sobj.method2) {
                            targetRow.getCell(10).value = numberFormatter(sobj.method2.basicFee, 2);
                            targetRow.getCell(11).value = numberFormatter(sobj.method2.fee, 2);
                            cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
                                targetRow.getCell(1).value = num_2_2++;
                                targetRow.getCell(2).value = serviceX.code;
                                targetRow.getCell(3).value = serviceX.name;
                                targetRow.getCell(4).value = numberFormatter(sobj.method2.area, 3);
                                targetRow.getCell(5).value = '/';
                                targetRow.getCell(6).value = numberFormatter(sobj.method2.basicFee_basic, 2);
                                targetRow.getCell(7).value = '/';
                                targetRow.getCell(8).value = numberFormatter(sobj.method2.basicFee_optional, 2);
                                targetRow.getCell(9).value = numberFormatter(sobj.method2.basicFee, 2);
                            });
                        }
                    });
                    if (serviceX.mutiple) {
                        const maxProNum = Math.max(sobj.method1 ? sobj.method1.proAmount : 0, sobj.method2 ? sobj.method2.proAmount : 0);
                        for (let pi = 1; pi <= maxProNum; pi++) {
                            const m1PI = sobj.method1 ? sobj.method1.det.filter(m => m.proNum == pi) : [];
                            const m2PI = sobj.method2 ? sobj.method2.det.filter(m => m.proNum == pi) : [];
                            let m1PISum = m1PI.reduce((a, b) => {
                                return {
                                    cost: addNumbers(a.cost, toFiniteNumber(b.cost)),
                                    basicFee_basic: addNumbers(a.basicFee_basic, toFiniteNumber(b.basicFee_basic)),
                                    basicFee_optional: addNumbers(a.basicFee_optional, toFiniteNumber(b.basicFee_optional)),
                                    basicFee: addNumbers(a.basicFee, toFiniteNumber(b.basicFee)),
                                    fee: addNumbers(a.fee, toFiniteNumber(b.fee)),
                                }
                            }, { cost: 0, basicFee_basic: 0, basicFee_optional: 0, basicFee: 0, fee: 0 });
                            let m2PISum = m2PI.reduce((a, b) => {
                                return {
                                    area: addNumbers(a.area, toFiniteNumber(b.area)),
                                    basicFee_basic: addNumbers(a.basicFee_basic, toFiniteNumber(b.basicFee_basic)),
                                    basicFee_optional: addNumbers(a.basicFee_optional, toFiniteNumber(b.basicFee_optional)),
                                    basicFee: addNumbers(a.basicFee, toFiniteNumber(b.basicFee)),
                                    fee: addNumbers(a.fee, toFiniteNumber(b.fee)),
                                }
                            }, { area: 0, basicFee_basic: 0, basicFee_optional: 0, basicFee: 0, fee: 0 });
                            cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
                                targetRow.getCell(1).value = num_2++;
                                targetRow.getCell(2).value = serviceX.code + '-' + pi;
                                targetRow.getCell(3).value = '项目' + pi;
                                targetRow.getCell(4).value = '/';
                                targetRow.getCell(5).value = '/';
                                targetRow.getCell(6).value = '/';
                                targetRow.getCell(7).value = '/';
                                if (m1PI.length) {
                                    targetRow.getCell(8).value = numberFormatter(m1PISum.basicFee, 2);
                                    targetRow.getCell(9).value = numberFormatter(m1PISum.fee, 2);
                                    cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
                                        targetRow.getCell(1).value = num_2_1++;
                                        targetRow.getCell(2).value = serviceX.code + '-' + pi;
                                        targetRow.getCell(3).value = '项目' + pi;
                                        targetRow.getCell(4).value = numberFormatter(m1PISum.cost, 2);
                                        targetRow.getCell(5).value = '/';
                                        targetRow.getCell(6).value = numberFormatter(m1PISum.basicFee_basic, 2);
                                        targetRow.getCell(7).value = '/';
                                        targetRow.getCell(8).value = numberFormatter(m1PISum.basicFee_optional, 2);
                                        targetRow.getCell(9).value = numberFormatter(m1PISum.basicFee, 2);
                                    });
                                }
                                if (m2PI.length) {
                                    targetRow.getCell(10).value = numberFormatter(m2PISum.basicFee, 2);
                                    targetRow.getCell(11).value = numberFormatter(m2PISum.fee, 2);
                                    cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
                                        targetRow.getCell(1).value = num_2_2++;
                                        targetRow.getCell(2).value = serviceX.code + '-' + pi;
                                        targetRow.getCell(3).value = '项目' + pi;
                                        targetRow.getCell(4).value = numberFormatter(m2PISum.area, 3);
                                        targetRow.getCell(5).value = '/';
                                        targetRow.getCell(6).value = numberFormatter(m2PISum.basicFee_basic, 2);
                                        targetRow.getCell(7).value = '/';
                                        targetRow.getCell(8).value = numberFormatter(m2PISum.basicFee_optional, 2);
                                        targetRow.getCell(9).value = numberFormatter(m2PISum.basicFee, 2);
                                    });
                                }
                            });
                            let det1 = m1PI.map(m => m.major);
                            let det2 = m2PI.map(m => m.major);
                            let allDet = [...(new Set([...det1, ...det2]))].sort((a, b) => majorList[a].order - majorList[b].order).map(m => {
                                let d1index = det1.indexOf(m);
                                let d2index = det2.indexOf(m);
                                return {
                                    major: m,
                                    mth1: d1index < 0 ? null : m1PI[d1index],
                                    mth2: d2index < 0 ? null : m2PI[d2index],
                                };
                            });
                            allDet.forEach((m, mindex) => {
                                let majorX = majorList[m.major];
                                cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
                                    targetRow.getCell(1).value = num_2++;
                                    targetRow.getCell(2).value = serviceX.code + '-' + pi + '-' + (mindex + 1);
                                    targetRow.getCell(3).value = majorX.name;
                                    if (m.mth1) {
                                        targetRow.getCell(4).value = numberFormatter(m.mth1.serviceCoe, 3);
                                        targetRow.getCell(5).value = numberFormatter(m.mth1.majorCoe, 3);
                                        targetRow.getCell(6).value = numberFormatter(m.mth1.processCoe, 3);
                                        targetRow.getCell(7).value = numberFormatter(m.mth1.proportion, 2);
                                        targetRow.getCell(8).value = numberFormatter(m.mth1.basicFee, 2);
                                        targetRow.getCell(9).value = numberFormatter(m.mth1.fee, 2);
                                        targetRow.getCell(10).value = '/';
                                        targetRow.getCell(11).value = '/';
                                        cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
                                            targetRow.getCell(1).value = num_2_1++;
                                            targetRow.getCell(2).value = serviceX.code + '-' + pi + '-' + (mindex + 1);
                                            targetRow.getCell(3).value = majorX.name;
                                            targetRow.getCell(4).value = numberFormatter(m.mth1.cost, 2);
                                            targetRow.getCell(5).value = m.mth1.basicFormula;
                                            targetRow.getCell(6).value = numberFormatter(m.mth1.basicFee_basic, 2);
                                            targetRow.getCell(7).value = m.mth1.optionalFormula;
                                            targetRow.getCell(8).value = numberFormatter(m.mth1.basicFee_optional, 2);
                                            targetRow.getCell(9).value = numberFormatter(m.mth1.basicFee, 2);
                                        });
                                    } else {
                                        targetRow.getCell(4).value = numberFormatter(m.mth2.serviceCoe, 3);
                                        targetRow.getCell(5).value = numberFormatter(m.mth2.majorCoe, 3);
                                        targetRow.getCell(6).value = numberFormatter(m.mth2.processCoe, 3);
                                        targetRow.getCell(7).value = numberFormatter(m.mth2.proportion, 2);
                                        targetRow.getCell(8).value = '/';
                                        targetRow.getCell(9).value = '/';
                                        targetRow.getCell(10).value = numberFormatter(m.mth2.basicFee, 2);
                                        targetRow.getCell(11).value = numberFormatter(m.mth2.fee, 2);
                                        cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
                                            targetRow.getCell(1).value = num_2_2++;
                                            targetRow.getCell(2).value = serviceX.code + '-' + pi + '-' + (mindex + 1);
                                            targetRow.getCell(3).value = majorX.name;
                                            targetRow.getCell(4).value = numberFormatter(m.mth2.area, 3);
                                            targetRow.getCell(5).value = m.mth2.basicFormula;
                                            targetRow.getCell(6).value = numberFormatter(m.mth2.basicFee_basic, 2);
                                            targetRow.getCell(7).value = m.mth2.optionalFormula;
                                            targetRow.getCell(8).value = numberFormatter(m.mth2.basicFee_optional, 2);
                                            targetRow.getCell(9).value = numberFormatter(m.mth2.basicFee, 2);
                                        });
                                    }
                                });
                            });
                        }
                    } else {
                        let det1 = sobj.method1 ? sobj.method1.det.map(m => m.major) : [];
                        let det2 = sobj.method2 ? sobj.method2.det.map(m => m.major) : [];
                        let allDet = [...(new Set([...det1, ...det2]))].sort((a, b) => majorList[a].order - majorList[b].order).map(m => {
                            let d1index = det1.indexOf(m);
                            let d2index = det2.indexOf(m);
                            return {
                                major: m,
                                mth1: d1index < 0 ? null : sobj.method1.det[d1index],
                                mth2: d2index < 0 ? null : sobj.method2.det[d2index],
                            };
                        });
                        allDet.forEach((m, mindex) => {
                            allServiceMajors.add(m.major);
                            ci.allServiceMajors.add(m.major);
                            let majorX = majorList[m.major];
                            cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
                                targetRow.getCell(1).value = num_2++;
                                targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
                                targetRow.getCell(3).value = majorX.name;
                                if (m.mth1) {
                                    targetRow.getCell(4).value = numberFormatter(m.mth1.serviceCoe, 3);
                                    targetRow.getCell(5).value = numberFormatter(m.mth1.majorCoe, 3);
                                    targetRow.getCell(6).value = numberFormatter(m.mth1.processCoe, 3);
                                    targetRow.getCell(7).value = numberFormatter(m.mth1.proportion, 2);
                                    targetRow.getCell(8).value = numberFormatter(m.mth1.basicFee, 2);
                                    targetRow.getCell(9).value = numberFormatter(m.mth1.fee, 2);
                                    targetRow.getCell(10).value = '/';
                                    targetRow.getCell(11).value = '/';
                                    cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
                                        targetRow.getCell(1).value = num_2_1++;
                                        targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
                                        targetRow.getCell(3).value = majorX.name;
                                        targetRow.getCell(4).value = numberFormatter(m.mth1.cost, 2);
                                        targetRow.getCell(5).value = m.mth1.basicFormula;
                                        targetRow.getCell(6).value = numberFormatter(m.mth1.basicFee_basic, 2);
                                        targetRow.getCell(7).value = m.mth1.optionalFormula;
                                        targetRow.getCell(8).value = numberFormatter(m.mth1.basicFee_optional, 2);
                                        targetRow.getCell(9).value = numberFormatter(m.mth1.basicFee, 2);
                                    });
                                } else {
                                    targetRow.getCell(4).value = numberFormatter(m.mth2.serviceCoe, 3);
                                    targetRow.getCell(5).value = numberFormatter(m.mth2.majorCoe, 3);
                                    targetRow.getCell(6).value = numberFormatter(m.mth2.processCoe, 3);
                                    targetRow.getCell(7).value = numberFormatter(m.mth2.proportion, 2);
                                    targetRow.getCell(8).value = '/';
                                    targetRow.getCell(9).value = '/';
                                    targetRow.getCell(10).value = numberFormatter(m.mth2.basicFee, 2);
                                    targetRow.getCell(11).value = numberFormatter(m.mth2.fee, 2);
                                    cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
                                        targetRow.getCell(1).value = num_2_2++;
                                        targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
                                        targetRow.getCell(3).value = majorX.name;
                                        targetRow.getCell(4).value = numberFormatter(m.mth2.area, 3);
                                        targetRow.getCell(5).value = m.mth2.basicFormula;
                                        targetRow.getCell(6).value = numberFormatter(m.mth2.basicFee_basic, 2);
                                        targetRow.getCell(7).value = m.mth2.optionalFormula;
                                        targetRow.getCell(8).value = numberFormatter(m.mth2.basicFee_optional, 2);
                                        targetRow.getCell(9).value = numberFormatter(m.mth2.basicFee, 2);
                                    });
                                }
                            });
                        });
                    }
                }
                if (sobj.method3) {
                    cusInsertRowFunc(3 + num_3, [sheet_3.getRow(3)], sheet_3, (targetRow) => {
                        targetRow.getCell(1).value = num_3++;
                        targetRow.getCell(2).value = serviceX.code;
                        targetRow.getCell(3).value = serviceX.name;
                        targetRow.getCell(4).value = '/';
                        targetRow.getCell(5).value = '/';
                        targetRow.getCell(6).value = '/';
                        targetRow.getCell(7).value = numberFormatter(sobj.method3.basicFee, 2);
                        targetRow.getCell(8).value = '/';
                        targetRow.getCell(9).value = numberFormatter(sobj.method3.fee, 2);
                    });
                    sobj.method3.det.forEach((tobj, tindex) => {
                        const taskX = taskList[tobj.task];
                        cusInsertRowFunc(3 + num_3, [sheet_3.getRow(3)], sheet_3, (targetRow) => {
                            targetRow.getCell(1).value = num_3++;
                            targetRow.getCell(2).value = taskX.code;
                            targetRow.getCell(3).value = taskX.name + (taskX.desc ? `(${taskX.desc})` : '');
                            targetRow.getCell(4).value = taskX.basicParam;
                            targetRow.getCell(5).value = numberFormatter(tobj.price * taskX.conversion, 2);
                            targetRow.getCell(6).value = numberFormatter(tobj.amount, 2);
                            targetRow.getCell(7).value = numberFormatter(tobj.basicFee, 2);
                            targetRow.getCell(8).value = numberFormatter(tobj.serviceCoe, 3);
                            targetRow.getCell(9).value = numberFormatter(tobj.fee, 2);
                        });
                    });
                }
                if (sobj.method4) {
                    cusInsertRowFunc(4 + num_4, [sheet_4.getRow(4)], sheet_4, (targetRow) => {
                        targetRow.getCell(1).value = num_4++;
                        targetRow.getCell(2).value = serviceX.code;
                        targetRow.getCell(3).value = serviceX.name;
                        targetRow.getCell(4).value = numberFormatter(sobj.method4.person_num, 0);
                        targetRow.getCell(5).value = numberFormatter(sobj.method4.work_day, 2);
                        targetRow.getCell(6).value = numberFormatter(sobj.method4.fee, 2);
                    });
                    cusInsertRowFunc(4 + num_4_1, [sheet_4_1.getRow(4)], sheet_4_1, (targetRow) => {
                        targetRow.getCell(1).value = num_4_1++;
                        targetRow.getCell(2).value = serviceX.code;
                        targetRow.getCell(3).value = serviceX.name;
                        targetRow.getCell(4).value = '/';
                        targetRow.getCell(5).value = '/';
                        targetRow.getCell(6).value = '/';
                        targetRow.getCell(7).value = numberFormatter(sobj.method4.person_num, 0);
                        targetRow.getCell(8).value = numberFormatter(sobj.method4.work_day, 2);
                        targetRow.getCell(9).value = numberFormatter(sobj.method4.fee, 2);
                    });
                    sobj.method4.det.forEach((eobj, eindex) => {
                        const expertX = expertList[eobj.expert];
                        cusInsertRowFunc(4 + num_4_1, [sheet_4_1.getRow(4)], sheet_4_1, (targetRow) => {
                            targetRow.getCell(1).value = num_4_1++;
                            targetRow.getCell(2).value = expertX.code;
                            targetRow.getCell(3).value = expertX.name;
                            targetRow.getCell(4).value = `${expertX.minPrice}～${expertX.maxPrice}`;
                            targetRow.getCell(5).value = `${roundTo(toDecimal(toFiniteNumber(expertX.minPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}～${roundTo(toDecimal(toFiniteNumber(expertX.maxPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}`;
                            targetRow.getCell(6).value = numberFormatter(eobj.price, 2);
                            targetRow.getCell(7).value = numberFormatter(eobj.person_num, 0);
                            targetRow.getCell(8).value = numberFormatter(eobj.work_day, 2);
                            targetRow.getCell(9).value = numberFormatter(eobj.fee, 2);
                            targetRow.getCell(10).value = eobj.remark;
                        });
                    });
                }
            });
            let endRows = 1;
            if (ci.services.length) {
                cusInsertRowFunc(ci.services.length + 3 + endRows, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
                    targetRow.getCell(1).value = ci.services.length + endRows;
                    targetRow.getCell(2).value = '';
                    targetRow.getCell(3).value = '基本、可选工作小计';
                    targetRow.getCell(4).value = numberFormatter(m1Sum, 2);
                    targetRow.getCell(5).value = numberFormatter(m2Sum, 2);
                    targetRow.getCell(6).value = numberFormatter(m3Sum, 2);
                    targetRow.getCell(7).value = numberFormatter(m4Sum, 2);
                    targetRow.getCell(8).value = numberFormatter(serviceSum1, 2);
                    targetRow.getCell(9).value = numberFormatter(serviceSum2, 2);
                });
            }
            if (ci.addtional) {
                endRows++;
                cusInsertRowFunc(ci.services.length + 3 + endRows, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
                    targetRow.getCell(1).value = ci.services.length + endRows;
                    targetRow.getCell(2).value = ci.addtional.code;
                    targetRow.getCell(3).value = ci.addtional.name;
                    targetRow.getCell(4).value = '';
                    targetRow.getCell(5).value = '';
                    targetRow.getCell(6).value = '';
                    targetRow.getCell(7).value = '';
                    targetRow.getCell(8).value = '';
                    targetRow.getCell(9).value = '';
                });
                ci.addtional.det.forEach((addobj, addindex) => {
                    endRows++;
                    cusInsertRowFunc(ci.services.length + 3 + endRows, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
                        targetRow.getCell(1).value = ci.services.length + endRows;
                        targetRow.getCell(2).value = addobj.code;
                        targetRow.getCell(3).value = addobj.name;
                        let tmpArr = [];
                        if (addobj.m0) {
                            tmpArr.push(`按基本工作和可选工作确认预算之和的${addobj.m0.coe}%计得${addobj.m0.fee}元`);
                            allMethods.add(0);
                        }
                        if (addobj.m4) tmpArr.push(`按工时法计得${addobj.m4.fee}元`);
                        if (addobj.m5) tmpArr.push(`按数量单价计得${addobj.m5.fee}元`);
                        targetRow.getCell(4).value = tmpArr.join(';');
                        targetRow.getCell(9).value = numberFormatter(addobj.fee, 2);
                        if (addobj.m4) {
                            cusInsertRowFunc(4 + num_4, [sheet_4.getRow(4)], sheet_4, (targetRow) => {
                                targetRow.getCell(1).value = num_4++;
                                targetRow.getCell(2).value = addobj.code;
                                targetRow.getCell(3).value = addobj.name;
                                targetRow.getCell(4).value = numberFormatter(addobj.m4.person_num, 0);
                                targetRow.getCell(5).value = numberFormatter(addobj.m4.work_day, 2);
                                targetRow.getCell(6).value = numberFormatter(addobj.m4.fee, 2);
                            });
                            cusInsertRowFunc(4 + num_4_1, [sheet_4_1.getRow(4)], sheet_4_1, (targetRow) => {
                                targetRow.getCell(1).value = num_4_1++;
                                targetRow.getCell(2).value = addobj.code;
                                targetRow.getCell(3).value = addobj.name;
                                targetRow.getCell(4).value = '/';
                                targetRow.getCell(5).value = '/';
                                targetRow.getCell(6).value = '/';
                                targetRow.getCell(7).value = numberFormatter(addobj.m4.person_num, 0);
                                targetRow.getCell(8).value = numberFormatter(addobj.m4.work_day, 2);
                                targetRow.getCell(9).value = numberFormatter(addobj.m4.fee, 2);
                            });
                            addobj.m4.det.forEach((eobj, eindex) => {
                                const expertX = expertList[eobj.expert];
                                cusInsertRowFunc(4 + num_4_1, [sheet_4_1.getRow(4)], sheet_4_1, (targetRow) => {
                                    targetRow.getCell(1).value = num_4_1++;
                                    targetRow.getCell(2).value = expertX.code;
                                    targetRow.getCell(3).value = expertX.name;
                                    targetRow.getCell(4).value = `${expertX.minPrice}～${expertX.maxPrice}`;
                                    targetRow.getCell(5).value = `${roundTo(toDecimal(toFiniteNumber(expertX.minPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}～${roundTo(toDecimal(toFiniteNumber(expertX.maxPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}`;
                                    targetRow.getCell(6).value = numberFormatter(eobj.price, 2);
                                    targetRow.getCell(7).value = numberFormatter(eobj.person_num, 0);
                                    targetRow.getCell(8).value = numberFormatter(eobj.work_day, 2);
                                    targetRow.getCell(9).value = numberFormatter(eobj.fee, 2);
                                    targetRow.getCell(10).value = eobj.remark;
                                });
                            });
                        }
                        if (addobj.m5) {
                            cusInsertRowFunc(4 + num_5, [sheet_5.getRow(4)], sheet_5, (targetRow) => {
                                num_5++;
                                targetRow.getCell(1).value = addobj.code;
                                targetRow.getCell(2).value = addobj.name;
                                targetRow.getCell(3).value = '/';
                                targetRow.getCell(4).value = '/';
                                targetRow.getCell(5).value = '/';
                                targetRow.getCell(6).value = numberFormatter(addobj.m5.fee, 2);
                            });
                            const tmpJSS = JSON.stringify(addobj.code);
                            addobj.m5.det.forEach((eobj, eindex) => {
                                let code = JSON.parse(tmpJSS);
                                code.richText.push({ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: '-' + (eindex + 1) });
                                cusInsertRowFunc(4 + num_5, [sheet_5.getRow(4)], sheet_5, (targetRow) => {
                                    num_5++;
                                    targetRow.getCell(1).value = code;
                                    targetRow.getCell(2).value = eobj.name;
                                    targetRow.getCell(3).value = eobj.unit;
                                    targetRow.getCell(4).value = numberFormatter(eobj.amount, 3);
                                    targetRow.getCell(5).value = numberFormatter(eobj.price, 2);
                                    targetRow.getCell(6).value = numberFormatter(eobj.fee, 2);
                                    targetRow.getCell(7).value = eobj.remark;
                                });
                            });
                        }
                    });
                });
                endRows++;
                cusInsertRowFunc(ci.services.length + 3 + endRows, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
                    targetRow.getCell(1).value = ci.services.length + endRows;
                    targetRow.getCell(2).value = '';
                    targetRow.getCell(3).value = '附加工作小计';
                    targetRow.getCell(4).value = '';
                    targetRow.getCell(5).value = '';
                    targetRow.getCell(6).value = '';
                    targetRow.getCell(7).value = '';
                    targetRow.getCell(8).value = '';
                    targetRow.getCell(9).value = numberFormatter(ci.addtional.fee, 2);
                });
            }
            if (ci.reserve) {
                endRows++;
                cusInsertRowFunc(ci.services.length + 3 + endRows, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
                    targetRow.getCell(1).value = ci.services.length + endRows;
                    targetRow.getCell(2).value = ci.reserve.code;
                    targetRow.getCell(3).value = ci.reserve.name;
                    let tmpArr = [];
                    if (ci.reserve.m0) {
                        tmpArr.push(`按基本工作、可选工作与附加工作确认预算之和的${ci.reserve.m0.coe}%计得${ci.reserve.m0.fee}元`);
                        allMethods.add(0);
                        allReserveMethods[0].cid.push(index);
                        allReserveMethods[0].coe.push(ci.reserve.m0.coe);
                        allReserveMethods[0].coeSet.add(ci.reserve.m0.coe);
                    }
                    if (ci.reserve.m4) {
                        tmpArr.push(`按工时法计得${ci.reserve.m4.fee}元`);
                        allReserveMethods[4].push(index);
                    }
                    if (ci.reserve.m5) {
                        tmpArr.push(`按数量单价计得${ci.reserve.m5.fee}元`);
                        allReserveMethods[5].push(index);
                    }
                    targetRow.getCell(4).value = tmpArr.join(';');
                    targetRow.getCell(9).value = numberFormatter(ci.reserve.fee, 2);
                });
                if (ci.reserve.m4) {
                    cusInsertRowFunc(4 + num_4, [sheet_4.getRow(4)], sheet_4, (targetRow) => {
                        targetRow.getCell(1).value = num_4++;
                        targetRow.getCell(2).value = ci.reserve.code;
                        targetRow.getCell(3).value = ci.reserve.name;
                        targetRow.getCell(4).value = numberFormatter(ci.reserve.m4.person_num, 0);
                        targetRow.getCell(5).value = numberFormatter(ci.reserve.m4.work_day, 2);
                        targetRow.getCell(6).value = numberFormatter(ci.reserve.m4.fee, 2);
                    });
                    cusInsertRowFunc(4 + num_4_1, [sheet_4_1.getRow(4)], sheet_4_1, (targetRow) => {
                        targetRow.getCell(1).value = num_4_1++;
                        targetRow.getCell(2).value = ci.reserve.code;
                        targetRow.getCell(3).value = ci.reserve.name;
                        targetRow.getCell(4).value = '/';
                        targetRow.getCell(5).value = '/';
                        targetRow.getCell(6).value = '/';
                        targetRow.getCell(7).value = numberFormatter(ci.reserve.m4.person_num, 0);
                        targetRow.getCell(8).value = numberFormatter(ci.reserve.m4.work_day, 2);
                        targetRow.getCell(9).value = numberFormatter(ci.reserve.m4.fee, 2);
                    });
                    ci.reserve.m4.det.forEach((eobj, eindex) => {
                        const expertX = expertList[eobj.expert];
                        cusInsertRowFunc(4 + num_4_1, [sheet_4_1.getRow(4)], sheet_4_1, (targetRow) => {
                            targetRow.getCell(1).value = num_4_1++;
                            targetRow.getCell(2).value = expertX.code;
                            targetRow.getCell(3).value = expertX.name;
                            targetRow.getCell(4).value = `${expertX.minPrice}～${expertX.maxPrice}`;
                            targetRow.getCell(5).value = `${roundTo(toDecimal(toFiniteNumber(expertX.minPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}～${roundTo(toDecimal(toFiniteNumber(expertX.maxPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}`;
                            targetRow.getCell(6).value = numberFormatter(eobj.price, 2);
                            targetRow.getCell(7).value = numberFormatter(eobj.person_num, 0);
                            targetRow.getCell(8).value = numberFormatter(eobj.work_day, 2);
                            targetRow.getCell(9).value = numberFormatter(eobj.fee, 2);
                            targetRow.getCell(10).value = eobj.remark;
                        });
                    });
                }
                if (ci.reserve.m5) {
                    cusInsertRowFunc(4 + num_5, [sheet_5.getRow(4)], sheet_5, (targetRow) => {
                        num_5++;
                        targetRow.getCell(1).value = ci.reserve.code;
                        targetRow.getCell(2).value = ci.reserve.name;
                        targetRow.getCell(3).value = '/';
                        targetRow.getCell(4).value = '/';
                        targetRow.getCell(5).value = '/';
                        targetRow.getCell(6).value = numberFormatter(ci.reserve.m5.fee, 2);
                    });
                    const tmpJSS = JSON.stringify(ci.reserve.code);
                    ci.reserve.m5.det.forEach((eobj, eindex) => {
                        let code = JSON.parse(tmpJSS);
                        code.richText.push({ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: '-' + (eindex + 1) });
                        cusInsertRowFunc(4 + num_5, [sheet_5.getRow(4)], sheet_5, (targetRow) => {
                            num_5++;
                            targetRow.getCell(1).value = code;
                            targetRow.getCell(2).value = eobj.name;
                            targetRow.getCell(3).value = eobj.unit;
                            targetRow.getCell(4).value = numberFormatter(eobj.amount, 3);
                            targetRow.getCell(5).value = numberFormatter(eobj.price, 2);
                            targetRow.getCell(6).value = numberFormatter(eobj.fee, 2);
                            targetRow.getCell(7).value = eobj.remark;
                        });
                    });
                }
            }

            sheet_1.spliceRows(3, 1);
            sheet_1.getRow(ci.services.length + endRows + 3).getCell(1).value = ci.services.length + endRows + 1;
            sheet_1.getRow(ci.services.length + endRows + 3).getCell(9).value = numberFormatter(ci.fee, 2);
            sheet_1.mergeCells(2 + ci.services.length + endRows + 2, 2, 2 + ci.services.length + endRows + 2, 9);
            sheet_1.getRow(2 + ci.services.length + endRows + 2).height = 100;
            sheet_1.getRow(2 + ci.services.length + endRows + 2).getCell(2).border.right = { style: 'thin' };

            if (sheet_2) {
                sheet_2.spliceRows(4, 1);
                sheet_2.mergeCells(3 + num_2, 2, 3 + num_2, 11);
                sheet_2.getRow(3 + num_2).height = 100;
                sheet_2.getRow(3 + num_2).getCell(2).border.right = { style: 'thin' };
                if (sheet_2_1) {
                    sheet_2_1.spliceRows(4, 1);
                    sheet_2_1.mergeCells(3 + num_2_1, 2, 3 + num_2_1, 9);
                    sheet_2_1.getRow(3 + num_2_1).height = 100;
                    sheet_2_1.getRow(3 + num_2_1).getCell(2).border.right = { style: 'thin' };
                }
                if (sheet_2_2) {
                    sheet_2_2.spliceRows(4, 1);
                    sheet_2_2.mergeCells(3 + num_2_2, 2, 3 + num_2_2, 9);
                    sheet_2_2.getRow(3 + num_2_2).height = 100;
                    sheet_2_2.getRow(3 + num_2_2).getCell(2).border.right = { style: 'thin' };
                }
            }

            if (sheet_3) {
                sheet_3.spliceRows(3, 1);
                sheet_3.mergeCells(2 + num_3, 2, 2 + num_3, 9);
                sheet_3.getRow(2 + num_3).height = 100;
                sheet_3.getRow(2 + num_3).getCell(2).border.right = { style: 'thin' };
            }

            if (sheet_4) {
                sheet_4.spliceRows(4, 1);
                sheet_4.getRow(2).height = 20.25;
                sheet_4_1.spliceRows(4, 1);
                sheet_4_1.mergeCells(3 + num_4_1, 2, 3 + num_4_1, 10);
                sheet_4_1.getRow(3 + num_4_1).height = 100;
                sheet_4_1.getRow(3 + num_4_1).getCell(2).border.right = { style: 'thin' };
            }

            if (sheet_5) {
                sheet_5.spliceRows(4, 1);
                sheet_5.getRow(2).height = 20.25;
            }

            for (let i = 1; i <= endRows; i++) {
                sheet_1.mergeCells(ci.services.length + 3 + i, 4, ci.services.length + 3 + i, 8);
                if (sheet_1.getRow(ci.services.length + 3 + i).getCell(4).style.alignment) {
                    sheet_1.getRow(ci.services.length + 3 + i).getCell(4).style.alignment.horizontal = 'center';
                    sheet_1.getRow(ci.services.length + 3 + i).getCell(4).style.alignment.wrapText = true;
                    if (i != 1 && i != endRows && sheet_1.getRow(ci.services.length + 3 + i).getCell(2).value) sheet_1.getRow(ci.services.length + 3 + i).getCell(10).style.font.size = 22;
                }
            }
        });

        allServices.sort((a, b) => serviceList[a.id].order - serviceList[b.id].order);
        allServices.forEach((s, sindex) => {
            const serviceX = serviceList[s.id];
            cusInsertRowFunc(3 + sindex, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
                let siSum = 0;
                for (let i = 0; i < yz01Num; i++) {
                    targetRow.getCell(i * 7 + 1).value = sindex + 1;
                    targetRow.getCell(i * 7 + 2).value = serviceX.code;
                    targetRow.getCell(i * 7 + 3).value = serviceX.name;
                    targetRow.getCell(i * 7 + 4).value = numberFormatter(s.contracts[i * 4], 2);
                    targetRow.getCell(i * 7 + 5).value = numberFormatter(s.contracts[i * 4 + 1], 2);
                    targetRow.getCell(i * 7 + 6).value = numberFormatter(s.contracts[i * 4 + 2], 2);
                    siSum = addNumbers(
                        siSum,
                        toFiniteNumber(s.contracts[i * 4]),
                        toFiniteNumber(s.contracts[i * 4 + 1]),
                        toFiniteNumber(s.contracts[i * 4 + 2])
                    );
                    if (i == yz01Num - 1 && yz01Mod == 0) {
                        targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
                    } else {
                        targetRow.getCell(i * 7 + 7).value = numberFormatter(s.contracts[i * 4 + 3], 2);
                        siSum = addNumbers(siSum, toFiniteNumber(s.contracts[i * 4 + 3]));
                    }
                }
                if (yz01Mod) {
                    targetRow.getCell(yz01Num * 7 + 1).value = sindex + 1;
                    targetRow.getCell(yz01Num * 7 + 2).value = serviceX.code;
                    targetRow.getCell(yz01Num * 7 + 3).value = serviceX.name;
                    if (yz01Mod == 1) {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
                    } else if (yz01Mod == 2) {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(s.contracts[yz01Num * 4], 2);
                        siSum = addNumbers(siSum, toFiniteNumber(s.contracts[yz01Num * 4]));
                        targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
                    } else {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(s.contracts[yz01Num * 4], 2);
                        targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(s.contracts[yz01Num * 4 + 1], 2);
                        siSum = addNumbers(
                            siSum,
                            toFiniteNumber(s.contracts[yz01Num * 4]),
                            toFiniteNumber(s.contracts[yz01Num * 4 + 1])
                        );
                        targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
                    }
                }
            });
        });
        let endRows = 0;
        if (Object.keys(allAddtional).length) {
            endRows++;
            let firstNum = Object.keys(allAddtional)[0];
            cusInsertRowFunc(3 + allServices.length, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
                let siSum = 0;
                for (let i = 0; i < yz01Num; i++) {
                    targetRow.getCell(i * 7 + 1).value = allServices.length + 1;
                    targetRow.getCell(i * 7 + 2).value = allAddtional[firstNum].code;
                    targetRow.getCell(i * 7 + 3).value = allAddtional[firstNum].name;
                    targetRow.getCell(i * 7 + 4).value = numberFormatter(allAddtional[i * 4]?.fee, 2);
                    targetRow.getCell(i * 7 + 5).value = numberFormatter(allAddtional[i * 4 + 1]?.fee, 2);
                    targetRow.getCell(i * 7 + 6).value = numberFormatter(allAddtional[i * 4 + 2]?.fee, 2);
                    siSum = addNumbers(
                        siSum,
                        toFiniteNumber(allAddtional[i * 4]?.fee),
                        toFiniteNumber(allAddtional[i * 4 + 1]?.fee),
                        toFiniteNumber(allAddtional[i * 4 + 2]?.fee)
                    );
                    if (i == yz01Num - 1 && yz01Mod == 0) {
                        targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
                    } else {
                        targetRow.getCell(i * 7 + 7).value = numberFormatter(allAddtional[i * 4 + 3]?.fee, 2);
                        siSum = addNumbers(siSum, toFiniteNumber(allAddtional[i * 4 + 3]?.fee));
                    }
                }
                if (yz01Mod) {
                    targetRow.getCell(yz01Num * 7 + 1).value = allServices.length + 1;
                    targetRow.getCell(yz01Num * 7 + 2).value = allAddtional[firstNum].code;
                    targetRow.getCell(yz01Num * 7 + 3).value = allAddtional[firstNum].name;
                    if (yz01Mod == 1) {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
                    } else if (yz01Mod == 2) {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(allAddtional[yz01Num * 4]?.fee, 2);
                        siSum = addNumbers(siSum, toFiniteNumber(allAddtional[yz01Num * 4]?.fee));
                        targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
                    } else {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(allAddtional[yz01Num * 4]?.fee, 2);
                        targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(allAddtional[yz01Num * 4 + 1]?.fee, 2);
                        siSum = addNumbers(
                            siSum,
                            toFiniteNumber(allAddtional[yz01Num * 4]?.fee),
                            toFiniteNumber(allAddtional[yz01Num * 4 + 1]?.fee)
                        );
                        targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
                    }
                }
            });
        }
        if (Object.keys(allReserve).length) {
            endRows++;
            let firstNum = Object.keys(allReserve)[0];
            cusInsertRowFunc(3 + allServices.length + endRows - 1, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
                let siSum = 0;
                for (let i = 0; i < yz01Num; i++) {
                    targetRow.getCell(i * 7 + 1).value = allServices.length + endRows;
                    targetRow.getCell(i * 7 + 2).value = allReserve[firstNum].code;
                    targetRow.getCell(i * 7 + 3).value = allReserve[firstNum].name;
                    targetRow.getCell(i * 7 + 4).value = numberFormatter(allReserve[i * 4]?.fee, 2);
                    targetRow.getCell(i * 7 + 5).value = numberFormatter(allReserve[i * 4 + 1]?.fee, 2);
                    targetRow.getCell(i * 7 + 6).value = numberFormatter(allReserve[i * 4 + 2]?.fee, 2);
                    siSum = addNumbers(
                        siSum,
                        toFiniteNumber(allReserve[i * 4]?.fee),
                        toFiniteNumber(allReserve[i * 4 + 1]?.fee),
                        toFiniteNumber(allReserve[i * 4 + 2]?.fee)
                    );
                    if (i == yz01Num - 1 && yz01Mod == 0) {
                        targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
                    } else {
                        targetRow.getCell(i * 7 + 7).value = numberFormatter(allReserve[i * 4 + 3]?.fee, 2);
                        siSum = addNumbers(siSum, toFiniteNumber(allReserve[i * 4 + 3]?.fee));
                    }
                }
                if (yz01Mod) {
                    targetRow.getCell(yz01Num * 7 + 1).value = allServices.length + endRows;
                    targetRow.getCell(yz01Num * 7 + 2).value = allReserve[firstNum].code;
                    targetRow.getCell(yz01Num * 7 + 3).value = allReserve[firstNum].name;
                    if (yz01Mod == 1) {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
                    } else if (yz01Mod == 2) {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(allReserve[yz01Num * 4]?.fee, 2);
                        siSum = addNumbers(siSum, toFiniteNumber(allReserve[yz01Num * 4]?.fee));
                        targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
                    } else {
                        targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(allReserve[yz01Num * 4]?.fee, 2);
                        targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(allReserve[yz01Num * 4 + 1]?.fee, 2);
                        siSum = addNumbers(
                            siSum,
                            toFiniteNumber(allReserve[yz01Num * 4]?.fee),
                            toFiniteNumber(allReserve[yz01Num * 4 + 1]?.fee)
                        );
                        targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
                    }
                }
            });
        }
        const yz01SumRow = yz01_sheet.getRow(allServices.length + endRows + 3);
        for (let i = 0; i < yz01Num; i++) {
            yz01SumRow.getCell(i * 7 + 1).value = allServices.length + endRows + 1;
            yz01SumRow.getCell(i * 7 + 4).value = numberFormatter(data.contracts[i * 4]?.fee, 2);
            yz01SumRow.getCell(i * 7 + 5).value = numberFormatter(data.contracts[i * 4 + 1]?.fee, 2);
            yz01SumRow.getCell(i * 7 + 6).value = numberFormatter(data.contracts[i * 4 + 2]?.fee, 2);
            if (i == yz01Num - 1 && yz01Mod == 0) {
                yz01SumRow.getCell(i * 7 + 7).value = numberFormatter(data.fee, 2);
            } else {
                yz01SumRow.getCell(i * 7 + 7).value = numberFormatter(data.contracts[i * 4 + 3]?.fee, 2);
            }
        }
        if (yz01Mod) {
            yz01SumRow.getCell(yz01Num * 7 + 1).value = allServices.length + endRows + 1;
            if (yz01Mod == 1) {
                yz01SumRow.getCell(yz01Num * 7 + 4).value = numberFormatter(data.fee, 2);
            } else if (yz01Mod == 2) {
                yz01SumRow.getCell(yz01Num * 7 + 4).value = numberFormatter(data.contracts[yz01Num * 4]?.fee, 2);
                yz01SumRow.getCell(yz01Num * 7 + 5).value = numberFormatter(data.fee, 2);
            } else {
                yz01SumRow.getCell(yz01Num * 7 + 4).value = numberFormatter(data.contracts[yz01Num * 4]?.fee, 2);
                yz01SumRow.getCell(yz01Num * 7 + 5).value = numberFormatter(data.contracts[yz01Num * 4 + 1]?.fee, 2);
                yz01SumRow.getCell(yz01Num * 7 + 6).value = numberFormatter(data.fee, 2);
            }
        }

        ml_sheet.spliceRows(6, 7);
        ml_sheet.spliceRows(6, 1);
        yz01_sheet.spliceRows(2, 1);

        // 合并说明
        let yz01_lastCol;
        if (yz01Num) {
            for (let i = 0; i < yz01Num; i++) {
                yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4].name}预算(元)`;
                yz01_sheet.getRow(1).getCell(i * 7 + 4 + 1).value = `${data.contracts[i * 4 + 1].name}预算(元)`;
                yz01_sheet.getRow(1).getCell(i * 7 + 4 + 2).value = `${data.contracts[i * 4 + 2].name}预算(元)`;
                if (i == yz01Num - 1 && yz01Mod == 0) {
                    yz01_sheet.getRow(1).getCell(i * 7 + 4 + 3).value = `预算小计(元)`;
                    yz01_lastCol = i * 7 + 4;
                } else {
                    yz01_sheet.getRow(1).getCell(i * 7 + 4 + 3).value = `${data.contracts[i * 4 + 3].name}预算(元)`;
                }
                yz01_sheet.mergeCells(allServices.length + endRows + 3, i * 7 + 2, allServices.length + endRows + 3, i * 7 + 7);
                yz01_sheet.getRow(allServices.length + endRows + 3).getCell(i * 7 + 2).border.right = { style: 'thin' };
            }
        }
        if (yz01Mod) {
            for (let i = 0; i < yz01Mod; i++) {
                if (i == yz01Mod - 1) {
                    yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `预算小计(元)`;
                    yz01_lastCol = yz01Num * 7 + 4 + i;
                } else {
                    yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `${data.contracts[yz01Num * 4 + i].name}预算(元)`;
                }
            }
            yz01_sheet.mergeCells(allServices.length + endRows + 3, yz01Num * 7 + 2, allServices.length + endRows + 3, yz01Num * 7 + 3 + yz01Mod);
            yz01_sheet.getRow(allServices.length + endRows + 3).getCell(yz01Num * 7 + 2).border.right = { style: 'thin' };
        }
        yz01_sheet.getRow(allServices.length + endRows + 3).height = 100;
        // for (let i = 1; i <= allServices.length + endRows + 3; i++) {
        //     yz01_sheet.getRow(i).getCell(yz01_lastCol + 1).value = ' ';
        //     if (yz01_sheet.getRow(i).getCell(yz01_lastCol + 1).style.font) {
        //         yz01_sheet.getRow(i).getCell(yz01_lastCol + 1).style.font.size = i == 1 ? 22 : 20;
        //     } else {
        //         yz01_sheet.getRow(i).getCell(yz01_lastCol + 1).style = { font: { size: i == 1 ? 22 : 20 } };
        // //     }
        // // }
        // yz01_sheet.pageSetup.printArea = `A1:${yz01_sheet.getRow(allServices.length + endRows + 3).getCell(yz01_lastCol)._address}`;

        ml_sheet.mergeCells(ml_slotRow - 8, 1, ml_slotRow - 8, 4);

        workbook.removeWorksheet('预i-1表');
        workbook.removeWorksheet('预i-2表');
        workbook.removeWorksheet('预i-2-1表');
        workbook.removeWorksheet('预i-2-2表');
        workbook.removeWorksheet('预i-3表');
        workbook.removeWorksheet('预i-4表');
        workbook.removeWorksheet('预i-4-1表');
        workbook.removeWorksheet('预i-5表');

        // 编辑辅01/02/03表内容
        // 辅01表
        if (Object.keys(allMajors).length) {
        let f01_sheet = mustGetWorksheet('辅01表');
            let f01Mod = (data.contracts.length + 1) % 3;
            let f01Num = (data.contracts.length + 1 - f01Mod) / 3;
            switch (f01Mod) {
                case 0:
                    f01_sheet.spliceColumns(11, 14);
                    break;
                case 1:
                    f01_sheet.spliceColumns(11, 8);
                    break;
                case 2:
                    f01_sheet.spliceColumns(19, 6);
                    break;
            }
            if (f01Num == 0) {
                f01_sheet.spliceColumns(1, 10);
            } else {
                if (f01Num > 1) {
                    for (let i = 0; i < f01Num - 1; i++) {
                        insertAndCopyColumn(10 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], f01_sheet);
                    }
                }
                for (let i = 0; i < f01Num; i++) {
                    f01_sheet.mergeCells(1, i * 10 + 1, 2, i * 10 + 1);
                    f01_sheet.mergeCells(1, i * 10 + 2, 2, i * 10 + 2);
                    f01_sheet.mergeCells(1, i * 10 + 3, 2, i * 10 + 3);
                    f01_sheet.mergeCells(1, i * 10 + 10, 2, i * 10 + 10);
                    f01_sheet.mergeCells(1, i * 10 + 4, 1, i * 10 + 5);
                    f01_sheet.getRow(1).getCell(i * 10 + 4).value = data.contracts[i * 3].name;
                    f01_sheet.mergeCells(1, i * 10 + 6, 1, i * 10 + 7);
                    f01_sheet.getRow(1).getCell(i * 10 + 6).value = data.contracts[i * 3 + 1].name;
                    f01_sheet.mergeCells(1, i * 10 + 8, 1, i * 10 + 9);
                    if (f01Mod == 0 && i == f01Num - 1) {
                        f01_sheet.getRow(1).getCell(i * 10 + 8).value = '项目小计';
                    } else {
                        f01_sheet.getRow(1).getCell(i * 10 + 8).value = data.contracts[i * 3 + 2].name;
                    }
                }
            }
            if (f01Mod > 0) {
                f01_sheet.mergeCells(1, f01Num * 10 + 1, 2, f01Num * 10 + 1);
                f01_sheet.mergeCells(1, f01Num * 10 + 2, 2, f01Num * 10 + 2);
                f01_sheet.mergeCells(1, f01Num * 10 + 3, 2, f01Num * 10 + 3);
                f01_sheet.mergeCells(1, f01Num * 10 + 2 * f01Mod + 4, 2, f01Num * 10 + 2 * f01Mod + 4);
                f01_sheet.mergeCells(1, f01Num * 10 + 4, 1, f01Num * 10 + 5);
                f01_sheet.getRow(1).getCell(f01Num * 10 + 4).value = f01Mod == 1 ? '项目小计' : data.contracts[f01Num * 3].name;
                if (f01Mod == 2) {
                    f01_sheet.mergeCells(1, f01Num * 10 + 6, 1, f01Num * 10 + 7);
                    f01_sheet.getRow(1).getCell(f01Num * 10 + 6).value = '项目小计';
                }
            }
            let num_f01 = 1;
            Object.keys(allMajors).sort((a, b) => (a < 0 ? a : majorList[a].order) - (b < 0 ? b : majorList[b].order)).forEach(majorid => {
                let scaleX = allMajors[majorid];
                let code;
                let name;
                let hasCost;
                let hasArea;
                if (majorid == -1) {
                    code = '';
                    name = '总投资';
                    hasCost = true;
                    hasArea = false;
                } else {
                    code = majorList[majorid].code;
                    name = majorList[majorid].name;
                    hasCost = majorList[majorid].hasCost;
                    hasArea = majorList[majorid].hasArea;
                }
                cusInsertRowFunc(3 + num_f01, [f01_sheet.getRow(3)], f01_sheet, (targetRow) => {
                    let rowNum = num_f01++;
                    for (let i = 0; i < f01Num; i++) {
                        targetRow.getCell(i * 10 + 1).value = rowNum;
                        targetRow.getCell(i * 10 + 2).value = code;
                        targetRow.getCell(i * 10 + 3).value = name;
                        if (hasCost) {
                            targetRow.getCell(i * 10 + 4).value = numberFormatter(scaleX[i * 3]?.cost, 2);
                            targetRow.getCell(i * 10 + 6).value = numberFormatter(scaleX[i * 3 + 1]?.cost, 2);
                            targetRow.getCell(i * 10 + 8).value = numberFormatter(scaleX[i * 3 + 2]?.cost, 2);
                        } else {
                            targetRow.getCell(i * 10 + 4).value = '/';
                            targetRow.getCell(i * 10 + 6).value = '/';
                            targetRow.getCell(i * 10 + 8).value = '/';
                        }
                        if (hasArea) {
                            targetRow.getCell(i * 10 + 5).value = numberFormatter(scaleX[i * 3]?.area, 3);
                            targetRow.getCell(i * 10 + 7).value = numberFormatter(scaleX[i * 3 + 1]?.area, 3);
                            targetRow.getCell(i * 10 + 9).value = numberFormatter(scaleX[i * 3 + 2]?.area, 3);
                        } else {
                            targetRow.getCell(i * 10 + 5).value = '/';
                            targetRow.getCell(i * 10 + 7).value = '/';
                            targetRow.getCell(i * 10 + 9).value = '/';
                        }
                    }
                    if (f01Mod > 0) {
                        targetRow.getCell(f01Num * 10 + 1).value = rowNum;
                        targetRow.getCell(f01Num * 10 + 2).value = code;
                        targetRow.getCell(f01Num * 10 + 3).value = name;
                        for (let i = 0; i < f01Mod; i++) {
                            if (hasCost) {
                                targetRow.getCell(f01Num * 10 + 4 + i * 2).value = numberFormatter(scaleX[f01Num * 3 + i]?.cost, 2);
                            } else {
                                targetRow.getCell(f01Num * 10 + 4 + i * 2).value = '/';
                            }
                            if (hasArea) {
                                targetRow.getCell(f01Num * 10 + 5 + i * 2).value = numberFormatter(scaleX[f01Num * 3 + i]?.area, 3);
                            } else {
                                targetRow.getCell(f01Num * 10 + 5 + i * 2).value = '/';
                            }
                        }
                    }
                });
            });
            f01_sheet.spliceRows(3, 1);
            // f01_sheet.getRow(2).height = 27;
            f01_sheet.orderNo = ml_number + 2 + 12;
        } else {
            workbook.removeWorksheet('辅01表');
        }

        // 辅02/03表
        if (data.serviceCoes?.length) {
        let f02_sheet = mustGetWorksheet('辅02表');
            let num_f02 = 1;
            data.serviceCoes.sort((a, b) => serviceList[a.serviceid].order - serviceList[b.serviceid].order).forEach(scoei => {
                let serviceCoeX = serviceList[scoei.serviceid];
                cusInsertRowFunc(2 + num_f02, [f02_sheet.getRow(2)], f02_sheet, (targetRow) => {
                    targetRow.getCell(1).value = num_f02++;
                    targetRow.getCell(2).value = serviceCoeX.code;
                    targetRow.getCell(3).value = serviceCoeX.name;
                    targetRow.getCell(4).value = numberFormatter(serviceCoeX.defCoe, 3);
                    targetRow.getCell(5).value = numberFormatter(scoei.coe, 3);
                    targetRow.getCell(6).value = scoei.remark;
                });
            });
            f02_sheet.spliceRows(2, 1);
            f02_sheet.orderNo = ml_number + 3 + 12;
        } else {
            workbook.removeWorksheet('辅02表');
        }
        if (data.majorCoes?.length) {
        let f03_sheet = mustGetWorksheet('辅03表');
            let num_f03 = 1;
            data.majorCoes.sort((a, b) => (a < 0 ? a : majorList[a.majorid].order) - (b < 0 ? b : majorList[b.majorid].order)).forEach(mcoei => {
                let majorCoeX = majorList[mcoei.majorid];
                cusInsertRowFunc(2 + num_f03, [f03_sheet.getRow(2)], f03_sheet, (targetRow) => {
                    targetRow.getCell(1).value = num_f03++;
                    targetRow.getCell(2).value = majorCoeX.code;
                    targetRow.getCell(3).value = majorCoeX.name;
                    targetRow.getCell(4).value = numberFormatter(majorCoeX.defCoe, 3);
                    targetRow.getCell(5).value = numberFormatter(mcoei.coe, 3);
                    targetRow.getCell(6).value = mcoei.remark;
                });
            });
            f03_sheet.spliceRows(2, 1);
            f03_sheet.orderNo = ml_number + 4 + 12;
        } else {
            workbook.removeWorksheet('辅03表');
        }

        // 统一设置页眉页脚字体
        workbook._worksheets.forEach(sheet => {
            if (sheet) {
                if (sheet.headerFooter.oddHeader) sheet.headerFooter.oddHeader = sheet.headerFooter.oddHeader.replace(/&([CLR])&/g, '&$1&"宋体"&');
                if (sheet.headerFooter.oddFooter) sheet.headerFooter.oddFooter = sheet.headerFooter.oddFooter.replace(/&([CLR])&/g, '&$1&"宋体"&');
            }
        });

        // 更新编制说明
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        // const pageMaxHei = 733;
        // 20.25 25.5 33.75
        const titleHeiDet = { fir: 11.25, els: 22.5 };
        const textHeiDet = { fir: 6.75, els: 13.5 };

        const descSheet = mustGetWorksheet('编制说明');
        let descRowNum = 1;
        let titleArr = paragraphLineBreakFor1112(data.name, ctx, 0);
        descSheet.getRow(descRowNum).getCell(1).value = titleArr[0];
        descRowNum++;
        if (titleArr.length > 1) {
            for (let i = 1; i < titleArr.length; i++) {
                cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                    descRowNum++;
                    targetRow.getCell(1).value = titleArr[i];
                });
            }
        }
        descRowNum += 2;
        if (data.overview?.length) {
            let overviewArr = paragraphLineBreakFor1112('    （一）' + data.overview + (/。$/.test(data.overview) ? '' : '。'), ctx);
            descSheet.getRow(descRowNum).getCell(1).value = overviewArr[0];
            descRowNum++;
            if (overviewArr.length > 1) {
                for (let i = 1; i < overviewArr.length; i++) {
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                        descRowNum++;
                        targetRow.getCell(1).value = overviewArr[i];
                    });
                }
            }
        } else {
            descRowNum++;
        }
        if (data.scale?.length) {
            let engCosts = data.scale.filter(f => f.major > 6 && majorList[f.major].hasCost);
            if (engCosts.length) {
                descRowNum++;
                let copyRowNum = descRowNum;
                engCosts.forEach((sci, scindex) => {
                    descRowNum++;
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(copyRowNum)], descSheet, (targetRow) => {
                        targetRow.getCell(1).value = `        ${scindex + 1}.${majorList[sci.major].name}${Number(sci.cost).toLocaleString()}万元${scindex == engCosts.length - 1 ? '。' : '；'}`;
                    });
                });
                descSheet.spliceRows(copyRowNum, 1);
            } else {
                descSheet.spliceRows(descRowNum, 2);
                descSheet.getRow(descRowNum).getCell(1).value = '    （二）本项目征地拆迁费包括：';
                descSheet.getRow(descRowNum + 2).getCell(1).value = '    （三）其他费用包括：';
            }
            let zcScales = data.scale.filter(f => [1, 2, 3].includes(f.major));
            if (zcScales.length) {
                descRowNum++;
                let copyRowNum = descRowNum;
                zcScales.forEach((sci, scindex) => {
                    descRowNum++;
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(copyRowNum)], descSheet, (targetRow) => {
                        switch (sci.major) {
                            case 1:
                                let desc1 = [];
                                if (sci.area) desc1.push(`征地总面积约${Number(sci.area).toLocaleString()}亩`);
                                if (sci.cost) desc1.push(`征地补偿费用${Number(sci.cost).toLocaleString()}万元`);
                                targetRow.getCell(1).value = `        ${scindex + 1}.征地情况：${desc1.join('，')}。`;
                                break;
                            case 2:
                                let desc2 = [];
                                if (sci.area) desc2.push(`拆迁总面积约${Number(sci.area).toLocaleString()}亩`);
                                if (sci.cost) desc2.push(`拆迁补偿费用${Number(sci.cost).toLocaleString()}万元`);
                                targetRow.getCell(1).value = `        ${scindex + 1}.拆迁补偿情况：${desc2.join('，')}。`;
                                break;
                            case 3:
                                targetRow.getCell(1).value = `        ${scindex + 1}.迁改工程情况：费用${Number(sci.cost).toLocaleString()}万元。`;
                                break;
                        }
                    });
                });
                descSheet.spliceRows(copyRowNum, 1);
            } else {
                descSheet.spliceRows(descRowNum, 2);
                descSheet.getRow(descRowNum).getCell(1).value = engCosts.length ? '    （三）其他费用包括：' : '    （二）其他费用包括：';
            }
            let otherCosts = data.scale.filter(f => [4, 5, 6].includes(f.major));
            if (otherCosts.length) {
                let desc = [];
                otherCosts.forEach(sci => {
                    desc.push(`${majorList[sci.major].name}${Number(sci.cost).toLocaleString()}万元`);
                });
                let text = descSheet.getRow(descRowNum).getCell(1).value + `${desc.join('，')}。`;
                let textArr = paragraphLineBreakFor1112(text, ctx);
                descSheet.getRow(descRowNum).getCell(1).value = textArr[0];
                descRowNum++;
                if (textArr.length > 1) {
                    for (let i = 1; i < textArr.length; i++) {
                        cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                            descRowNum++;
                            targetRow.getCell(1).value = textArr[i];
                        });
                    }
                }
            } else {
                descSheet.spliceRows(descRowNum, 1);
            }
        } else {
            descSheet.spliceRows(descRowNum, 5);
            descRowNum++;
        }
        descRowNum++;
        if (allServiceMajors.size > 0) {
            if (allServiceMajors.has(7) || allServiceMajors.has(18) || allServiceMajors.has(28)) {
                descSheet.getRow(descRowNum).getCell(1).value = descSheet.getRow(descRowNum).getCell(1).value + '本次委托包括全部工程及费用。';
                descRowNum++;
            } else {
                let majorTexts = [];
                let hasOther = false;
                [...allServiceMajors].sort((a, b) => majorList[a].order - majorList[b].order).forEach(mid => {
                    if (mid > 6) {
                        majorTexts.push(majorList[mid].name);
                    } else {
                        hasOther = true;
                    }
                });
                let text = descSheet.getRow(descRowNum).getCell(1).value + '本项目的' + majorTexts.join('、') + (hasOther ? '及其他专项工程。' : '。');
                let textArr = paragraphLineBreakFor1112(text, ctx);
                descSheet.getRow(descRowNum).getCell(1).value = textArr[0];
                descRowNum++;
                if (textArr.length > 1) {
                    for (let i = 1; i < textArr.length; i++) {
                        cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                            descRowNum++;
                            targetRow.getCell(1).value = textArr[i];
                        });
                    }
                }
            }
        } else {
            descSheet.getRow(descRowNum).getCell(1).value = descSheet.getRow(descRowNum).getCell(1).value + '×××××。';
            descRowNum++;
        }
        if (data.contracts.length == 1) {
            // 合同段划分
            descSheet.spliceRows(descRowNum, 1);
            // 咨询服务类型
            if (data.contracts[0].services.length) {
                let serviceText = '    （二）咨询服务类型：' + data.contracts[0].services.slice(0, -1).map(si => serviceList[si.id].name).join('、') + (data.contracts[0].services.length > 1 ? '和' : '') + serviceList[data.contracts[0].services.slice(-1)[0].id].name + '。';
                let serviceTextArr = paragraphLineBreakFor1112(serviceText, ctx);
                serviceTextArr.forEach(ti => {
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                        descRowNum++;
                        targetRow.getCell(1).value = ti;
                    });
                });
                descSheet.spliceRows(descRowNum, 1);
            } else {
                descSheet.getRow(descRowNum).getCell(1).value = '    （二）咨询服务类型：×××××。';
                descRowNum++;
            }
            // 工作内容及要求
            let ci = data.contracts[0];
            let ciTastNum = 1;
            let ciTaskTitle = `    （三）${ci.name}工作内容及要求`;
            let ciTaskTitleArr = paragraphLineBreakFor1112(ciTaskTitle, ctx);
            ciTaskTitleArr.forEach(ti => {
                cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                    descRowNum++;
                    targetRow.getCell(1).value = ti;
                });
            });
            cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                descRowNum++;
                targetRow.getCell(1).value = '    1.工作内容包括：';
            });
            if (ci.hasPreTexts) {
                prefixTexts.forEach(ti => {
                    let texts = paragraphLineBreakFor1112(`    （${ciTastNum}）${ti}。`, ctx);
                    texts.forEach(ti => {
                        cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                            descRowNum++;
                            targetRow.getCell(1).value = ti;                            
                        });
                    });
                    ciTastNum++;
                });
            }
            ci.services.forEach((si, sindex) => {
                let flag = false;
                if (si.tasks?.length) {
                    si.tasks.forEach(tsi => {
                        if (tsi.text?.length) {
                            flag = true;
                            let sx = tsi.serviceid == null ? { id: si.id, process: si.process } : { id: tsi.serviceid, process: tsi.process };
                            let tsiTextArr = paragraphLineBreakFor1112(`    （${ciTastNum}）${sx.process == null ? '完成' : (sx.process == 1 ? '审核' : '编制')}${serviceList[sx.id].name}，具体工作内容包括：`, ctx);
                            tsiTextArr.forEach(ti => {
                                cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                                    descRowNum++;
                                    targetRow.getCell(1).value = ti;                                    
                                });                                
                            });
                            ciTastNum++;
                            tsi.text.forEach((sti, stindex) => {
                                let stiTextArr = paragraphLineBreakFor1112(`    ${stindex + 1}）${sti}。`, ctx);
                                stiTextArr.forEach(ti => {
                                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                                        descRowNum++;
                                        targetRow.getCell(1).value = ti;
                                    });
                                });
                            });
                        }
                    });
                }
                if (!flag) {
                    let siTextArr = paragraphLineBreakFor1112(`    （${ciTastNum}）${si.process == null ? '完成' : (si.process == 1 ? '审核' : '编制')}${serviceList[si.id].name}，具体工作内容包括：`, ctx);
                    siTextArr.forEach(ti => {
                        cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                            descRowNum++;
                            targetRow.getCell(1).value = ti;                            
                        });
                    });
                    ciTastNum++;
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                        descRowNum++;
                        targetRow.getCell(1).value = '    1）×××××。';
                    });
                }
            });
            if (ci.addtional && ci.addtional.det.length) {
                ci.addtional.det.forEach(ai => {
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                        descRowNum++;
                        targetRow.getCell(1).value = `    （${ciTastNum}）${ai.id == 1 ? '负责协调工作，具体工作内容包括：' : '其他附加工作，具体工作内容包括：'}`;
                        ciTastNum++;
                    });
                    const additionalTaskTexts = normalizeTaskTexts(ai.tasks);
                    if (additionalTaskTexts.length) {
                        additionalTaskTexts.forEach((ati, atindex) => {
                            let atiTextArr = paragraphLineBreakFor1112(`    ${atindex + 1}）${ati}。`, ctx);
                            atiTextArr.forEach(ti => {
                                cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                                    descRowNum++;
                                    targetRow.getCell(1).value = ti;
                                });
                            });
                        });
                    } else {
                        cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                            descRowNum++;
                            targetRow.getCell(1).value = '    1）×××××。';
                        });
                    }
                });
            }
            if (ci.hasSufTexts) {
                suffixTexts.forEach(ti => {
                    let texts = paragraphLineBreakFor1112(`    （${ciTastNum}）${ti}。`, ctx);
                    texts.forEach(ti => {
                        cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                            descRowNum++;
                            targetRow.getCell(1).value = ti;                            
                        });
                    });
                    ciTastNum++;
                });
            }
            cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                descRowNum++;
                targetRow.getCell(1).value = `    2.质量标准：${ci.quality || '×××××'}${/。$/.test(ci.quality) ? '' : '。'}`;
            });
            cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                descRowNum++;
                targetRow.getCell(1).value = `    3.服务工期：${ci.duration || '×××××'}${/。$/.test(ci.duration) ? '' : '。'}`;
            });
        } else {
            descSheet.getRow(descRowNum).getCell(1).value = descSheet.getRow(descRowNum).getCell(1).value.replace(/×××/g, data.contracts.length);
            let descRowNum1 = descRowNum + 1;
            let descRowNum2 = descRowNum + 2;
            let descRowNum3 = descRowNum + 2;
            data.contracts.forEach((ci, cindex) => {
                descRowNum3 = descRowNum3 - descRowNum2;
                descRowNum2 = descRowNum2 - descRowNum1 - 1;
                // 合同段划分
                let ciMajorText = `    ${cindex + 1}.${ci.name}：`;
                if (ci.allServiceMajors.size > 0) {
                    if (ci.allServiceMajors.has(7) || ci.allServiceMajors.has(18) || ci.allServiceMajors.has(28)) {
                        ciMajorText = ciMajorText + '全部工程及费用。';
                    } else {
                        let ciMajorTexts = [];
                        let hasOther = false;
                        [...ci.allServiceMajors].sort((a, b) => majorList[a].order - majorList[b].order).forEach(mid => {
                            if (mid > 6) {
                                ciMajorTexts.push(majorList[mid].name);
                            } else {
                                hasOther = true;
                            }
                        });
                        ciMajorText = ciMajorText + ciMajorTexts.join('、') + (hasOther ? '及其他专项工程。' : '。');
                    }
                } else {
                    ciMajorText = ciMajorText + '×××××。';
                }
                let majorTextArr = paragraphLineBreakFor1112(ciMajorText, ctx);
                majorTextArr.forEach(ti => {
                    cusInsertRowFunc(descRowNum1, [descSheet.getRow(descRowNum1 - 1)], descSheet, (targetRow) => {
                        descRowNum1++;
                        targetRow.getCell(1).value = ti;
                    });
                });
                // 咨询服务类型
                descRowNum2 = descRowNum1 + descRowNum2 + 1;

                if (ci.services.length) {
                    var ciServiceText = `    ${cindex + 1}.${ci.name}：` + ci.services.slice(0, -1).map(si => serviceList[si.id].name).join('、') + (ci.services.length > 1 ? '和' : '') + serviceList[ci.services.slice(-1)[0].id].name + '。';
                } else {
                    var ciServiceText = `    ${cindex + 1}.${ci.name}：×××××。`;
                }
                let ciServiceTextArr = paragraphLineBreakFor1112(ciServiceText, ctx);
                ciServiceTextArr.forEach(ti => {
                    cusInsertRowFunc(descRowNum2, [descSheet.getRow(descRowNum2 - 1)], descSheet, (targetRow) => {
                        descRowNum2++;
                        targetRow.getCell(1).value = ti;
                    });
                });
                // 工作内容及要求
                descRowNum3 = descRowNum2 + descRowNum3;
                let ciTastNum = 1;
                let ciTaskTitle = `    （${ArabicToChinese(cindex + 4)}）${ci.name}工作内容及要求`;
                let ciTaskTitleArr = paragraphLineBreakFor1112(ciTaskTitle, ctx);
                ciTaskTitleArr.forEach(ti => {
                    cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                        descRowNum3++;
                        targetRow.getCell(1).value = ti;
                    });
                });
                cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                    descRowNum3++;
                    targetRow.getCell(1).value = '    1.工作内容包括：';
                });
                if (ci.hasPreTexts) {
                    prefixTexts.forEach(ti => {
                        let texts = paragraphLineBreakFor1112(`    （${ciTastNum}）${ti}。`, ctx);
                        texts.forEach(ti => {
                            cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                descRowNum3++;
                                targetRow.getCell(1).value = ti;                                
                            });
                        });
                        ciTastNum++;
                    });
                }
                ci.services.forEach((si, sindex) => {
                    let flag = false;
                    const taskGroups = normalizeServiceTaskGroups(si.tasks);
                    if (taskGroups.length) {
                        taskGroups.forEach(tsi => {
                            flag = true;
                            let sx = tsi.serviceid == null
                                ? { id: si.id, process: si.process }
                                : { id: tsi.serviceid, process: tsi.process };
                            let tsiTextArr = paragraphLineBreakFor1112(`    （${ciTastNum}）${sx.process == null ? '完成' : (sx.process == 1 ? '审核' : '编制')}${serviceList[sx.id].name}，具体工作内容包括：`, ctx);
                            tsiTextArr.forEach(ti => {
                                cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                    descRowNum3++;
                                    targetRow.getCell(1).value = ti;                                    
                                });
                            });
                            ciTastNum++;
                            tsi.text.forEach((sti, stindex) => {
                                let stiTextArr = paragraphLineBreakFor1112(`    ${stindex + 1}）${sti}。`, ctx);
                                stiTextArr.forEach(ti => {
                                    cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                        descRowNum3++;
                                        targetRow.getCell(1).value = ti;
                                    });
                                });
                            });
                        });
                    }
                    if (!flag) {
                        let siTextArr = paragraphLineBreakFor1112(`    （${ciTastNum}）${si.process == null ? '完成' : (si.process == 1 ? '审核' : '编制')}${serviceList[si.id].name}，具体工作内容包括：`, ctx);
                        siTextArr.forEach(ti => {
                            cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                descRowNum3++;
                                targetRow.getCell(1).value = ti;
                            });
                        });
                        ciTastNum++;
                        cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                            descRowNum3++;
                            targetRow.getCell(1).value = '    1）×××××。';
                        });
                    }
                });
                if (ci.addtional && ci.addtional.det.length) {
                    ci.addtional.det.forEach(ai => {
                        cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                            descRowNum3++;
                            targetRow.getCell(1).value = `    （${ciTastNum}）${ai.id == 1 ? '负责协调工作，具体工作内容包括：' : '其他附加工作，具体工作内容包括：'}`;
                            ciTastNum++;
                        });
                        const additionalTaskTexts = normalizeTaskTexts(ai.tasks);
                        if (additionalTaskTexts.length) {
                            additionalTaskTexts.forEach((ati, atindex) => {
                                let atiTextArr = paragraphLineBreakFor1112(`    ${atindex + 1}）${ati}。`, ctx);
                                atiTextArr.forEach(ti => {
                                    cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                        descRowNum3++;
                                        targetRow.getCell(1).value = ti;
                                    });
                                });
                            });
                        } else {
                            cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                descRowNum3++;
                                targetRow.getCell(1).value = '    1）×××××。';
                            });
                        }
                    });
                }
                if (ci.hasSufTexts) {
                    suffixTexts.forEach(ti => {
                        let texts = paragraphLineBreakFor1112(`    （${ciTastNum}）${ti}。`, ctx);
                        texts.forEach(ti => {
                            cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                                descRowNum3++;
                                targetRow.getCell(1).value = ti;                                
                            });
                        });
                        ciTastNum++;
                    });
                }
                cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                    descRowNum3++;
                    targetRow.getCell(1).value = `    2.质量标准：${ci.quality || '×××××'}${/。$/.test(ci.quality) ? '' : '。'}`;
                });
                cusInsertRowFunc(descRowNum3, [descSheet.getRow(descRowNum3 - 1)], descSheet, (targetRow) => {
                    descRowNum3++;
                    targetRow.getCell(1).value = `    3.服务工期：${ci.duration || '×××××'}${/。$/.test(ci.duration) ? '' : '。'}`;
                });
            });
            descRowNum = descRowNum3;
        }
        descSheet.spliceRows(descRowNum, 1);
        descRowNum += 3;
        const methodNames = ['费率计取', '投资规模法', '用地规模法', '工作量法', '工时法', '数量单价'];
        let allMethodsArr = [...allMethods].sort((a, b) => a - b).map(mid => methodNames[mid]);
        let allMethodsTextArr = paragraphLineBreakFor1112('    3.预算编制采用方法：' + allMethodsArr.slice(0, -1).join('、') + (allMethodsArr.length > 1 ? '和' : '') + allMethodsArr.slice(-1) + '。', ctx);
        allMethodsTextArr.forEach(ti => {
            cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                descRowNum++;
                targetRow.getCell(1).value = ti;
            });
        });
        if (allReserveMethods[0].cid.length || allReserveMethods[4].length || allReserveMethods[5].length) {
            if (allReserveMethods[0].coeSet.size == 1 && allReserveMethods[4].length == 0 && allReserveMethods[5].length == 0) {
                cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                    descRowNum++;
                    targetRow.getCell(1).value = `    4.预备费：本预算按${allReserveMethods[0].coe[0]}%费率计列预备费。`;
                });
            } else {
                let rmta = [];
                data.contracts.forEach((ci, cindex) => {
                    let ciTextArr = [];
                    if (allReserveMethods[0].cid.includes(cindex)) ciTextArr.push(`${ci.reserve.m0.coe}%费率`);
                    if (allReserveMethods[4].includes(cindex)) ciTextArr.push('工时法');
                    if (allReserveMethods[5].includes(cindex)) ciTextArr.push('数量单价');
                    if (ciTextArr.length) {
                        rmta.push(`${ci.name}按${ciTextArr.join('、')}计列`)
                    } else {
                        rmta.push(`${ci.name}不计列`);
                    }
                });
                let rmtaa = paragraphLineBreakFor1112(`    4.预备费：${rmta.join('；')}。`, ctx);
                rmtaa.forEach(ti => {
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                        descRowNum++;
                        targetRow.getCell(1).value = ti;
                    });
                });
            }
        } else {
            cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                descRowNum++;
                targetRow.getCell(1).value = '    4.预备费：本预算不计列预备费。';
            });
        }
        descRowNum++;
        let contractFeeSummaryArr = paragraphLineBreakFor1112(`    本项目造价咨询服务总预算为${Number(data.fee).toLocaleString()}元。其中，${contractFeeSummary.join('，')}。计算过程详见相关计算表格。`, ctx);
        descSheet.getRow(descRowNum).getCell(1).value = contractFeeSummaryArr[0];
        descRowNum++;
        if (contractFeeSummaryArr.length > 1) {
            for (let i = 1; i < contractFeeSummaryArr.length; i++) {
                cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                    descRowNum++;
                    targetRow.getCell(1).value = contractFeeSummaryArr[i];
                });
            }
        }
        if (String(data.desc || '').trim()) {
            descRowNum++;
            const otherDescText = String(data.desc || '').trim();
            let otherDesc = paragraphLineBreakFor1112(`    ${otherDescText}${/。$/.test(otherDescText) ? '' : '。'}`, ctx);
            descSheet.getRow(descRowNum).getCell(1).value = otherDesc[0];
            descRowNum++;
            if (otherDesc.length > 1) {
                for (let i = 1; i < otherDesc.length; i++) {
                    cusInsertRowFunc(descRowNum, [descSheet.getRow(descRowNum - 1)], descSheet, (targetRow) => {
                        descRowNum++;
                        targetRow.getCell(1).value = otherDesc[i];
                    });
                }
            }
        } else {
            descSheet.spliceRows(descRowNum, 2);
        }
        window.workbook = workbook;
        return workbook;
    } catch (error) {
        console.log(error)
        throw error
    }
}

function paragraphLineBreakFor1112(paragraph, ctx, type = 1) {// 仅适用于内容为11或12号字体的段落
    // // number,最后判断是否数字
    // const numberNoBreak1 = /\d[\d,]*(?:\.\d+)?$/;
    // const numberNoBreak2 = /^[\d,]*\.*\d+/;
    // // number&unit
    // const numberUnitNoBreak1 = /\d[\d,]*(?:\.\d+)?\s*(?:[a-zA-Z](?:[a-zA-Z\d·/]*[a-zA-Z\d])?)?$/;
    // const numberUnitNoBreak2 = /^[\d,]*(?:\.\d+)?\s*[a-zA-Z](?:[a-zA-Z\d·/]*[a-zA-Z\d])?/;
    // // 英文单词
    // const ewordNoBreak1 = /[a-zA-Z]+$/;
    // const ewordNoBreak2 = /^[a-zA-Z]+/;
    /*
       西文字符串
     */
    const matchASCIIChar1 = /[\x21-\x7E]+$/;
    const matchASCIIChar2 = /^[\x21-\x7E]+/;
    /*
       行首禁则
       1.不能为段落第一个字符
       2.将上一行最后1～2个字符转至下一行
       3.上一行最后1～2个字符如符合数字、英文单词、公式等规则，应整个下移至本行
     */
    const forbidLineSta1 = /^[,，.。、:：;；!！?？)\]\}）〕］｝〉》」』】〗〙〛”’」』﹂﹄％‰℃°—–～·]/;
    const forbidLineSta2 = /[,，.。、:：;；!！?？)\]\}）〕］｝〉》」』】〗〙〛”’」』﹂﹄％‰℃°—–～·]$/;
    /*
       行尾禁则
       1.不能为段落最后一个字符(即后续内容不能为空)
       2.将本行最后1～2个字符转至下一行
     */
    const forbidLineEnd = /[￥$€£@([{（〔［｛〈《「『【〖〘〚“‘「『﹁﹃—–～·]+$/;

    ctx.font = "12pt 宋体";
    const maxWidth = type == 1 ? 720 : 336;
    const minChar = Math.ceil(maxWidth / 8);
    let text = paragraph;
    let res = [];
    // let enterRows = new Set();
    while (ctx.measureText(text).width > maxWidth) {
        let t1 = text.slice(0, minChar);
        let t1PX = ctx.measureText(t1).width;
        while (t1PX > maxWidth) {
            let t1MinChar = Math.floor((t1PX - maxWidth) / 16) || 1;
            t1 = t1.slice(0, -1 * t1MinChar);
            t1PX = ctx.measureText(t1).width;
        }
        let t2 = text.slice(t1.length);
        if (matchASCIIChar1.test(t1) && matchASCIIChar2.test(t2)) {
            let strASCII1 = t1.match(matchASCIIChar1)[0];
            if (strASCII1.length < t1.length) {
                t1 = t1.slice(0, -1 * strASCII1.length);
                t2 = text.slice(t1.length);
                // enterRows.add(res.length);
            }
        }
        if (forbidLineSta1.test(t2)) {
            if (forbidLineSta2.test(t1)) {
                t1 = t1.slice(0, -2);
            } else {
                t1 = t1.slice(0, -1);
            }
            t2 = text.slice(t1.length);
            // enterRows.add(res.length);
        }
        if (forbidLineEnd.test(t1)) {
            let endTrans = t1.match(forbidLineEnd)[0];
            t1 = t1.slice(0, -1 * (Math.min(endTrans.length, 2)));
            t2 = text.slice(t1.length);
            // enterRows.add(res.length);
        }
        res.push(t1);
        text = text.slice(t1.length);
    }
    res.push(text);
    // [...enterRows].forEach(ri => {
    //     res[ri] = res[ri] + '\n';
    // });
    return res;
}

// 在指定位置插入行，并按模板行复制样式，可选回调填充值。
function cusInsertRowFunc(insertRowNum, sourceRows, worksheet, RowFun, cellFun) {
    // 插入行
    let newRows = [];
    for (let i = 0; i < sourceRows.length; i++) {
        newRows.push([]);
    }
    worksheet.insertRows(insertRowNum, newRows);

    for (let i = 0; i < sourceRows.length; i++) {
        const sourceRow = sourceRows[i];
        const targetRowNum = insertRowNum + i;
        const targetRow = worksheet.getRow(targetRowNum);
        targetRow.height = undefined;
        sourceRow.eachCell({ includeEmpty: true }, (cell, colNumber) => {
            const targetCell = targetRow.getCell(colNumber);
            // targetCell.value = cell.value; // 复制内容
            // 复制样式
            if (cell.style) {
                targetCell.style = JSON.parse(JSON.stringify(cell.style));
            }

            if (cellFun) {
                cellFun(targetCell, cell, colNumber, targetRow, sourceRow);
            }
        });
        if (RowFun) {
            RowFun(targetRow, sourceRow, i);
        }
    }
}

// 复制整张工作表（含页眉页脚、合并单元格、列宽样式与单元格值）。
function copyWorksheet(workbook, sourceName, targetName) {
    const source = workbook.getWorksheet(sourceName);
    if (!source) throw new Error("Source sheet not found");

    const target = workbook.addWorksheet(targetName, {
        properties: JSON.parse(JSON.stringify(source.properties || {})),
        pageSetup: JSON.parse(JSON.stringify(source.pageSetup || {})),
        views: source.views ? JSON.parse(JSON.stringify(source.views || {})) : [],
    });

    /* 复制页眉页脚（关键补充） */
    if (source.headerFooter) {
        target.headerFooter = JSON.parse(JSON.stringify(source.headerFooter));
    }

    /* 复制合并单元格 */
    Object.keys(source._merges).forEach(range => {
        let rangeModel = source._merges[range].model;
        target.mergeCells(rangeModel.top, rangeModel.left, rangeModel.bottom, rangeModel.right);
    });

    /* 复制列（宽度、样式） */
    source.columns.forEach((col, i) => {
        const targetCol = target.getColumn(i + 1);
        targetCol.width = col.width;
        targetCol.style = JSON.parse(JSON.stringify(col.style || {}));
    });

    /* 复制行 & 单元格 */
    source.eachRow({ includeEmpty: true }, (row, rowNumber) => {
        const targetRow = target.getRow(rowNumber);
        targetRow.height = undefined;

        row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
            const targetCell = targetRow.getCell(colNumber);

            // value（富文本/公式安全）
            targetCell.value = cloneCellValue(cell.value);

            // style（必须深拷贝）
            targetCell.style = JSON.parse(JSON.stringify(cell.style || {}));
        });
    });

    return target;
}

// 在指定列位置批量插入新列，并从给定来源列复制样式与内容。
function insertAndCopyColumn(insertAt, cols, ws) {
    let insertAti = insertAt;
    cols.forEach((col, index) => {
        // 在 insertAti 位置插入空列
        ws.spliceColumns(insertAti, 0, []);

        // 插入后，col 可能需要偏移
        const srcColIndex = col >= insertAti ? col + 1 + index : col;

        // 复制列
        copyColumn(insertAti, srcColIndex, ws);

        insertAti++;
    });
}

// 复制单列：列级属性 + 该列所有单元格值和样式。
function copyColumn(toCol, fromCol, ws) {
    const srcCol = ws.getColumn(fromCol);
    const dstCol = ws.getColumn(toCol);

    // 列级属性
    dstCol.width = srcCol.width;
    dstCol.hidden = srcCol.hidden;
    dstCol.style = JSON.parse(JSON.stringify(srcCol.style || {}));

    // 单元格
    srcCol.eachCell({ includeEmpty: true }, (cell, rowNumber) => {
        const dstCell = ws.getRow(rowNumber).getCell(toCol);
        dstCell.value = cloneCellValue(cell.value);
        dstCell.style = JSON.parse(JSON.stringify(cell.style || {}));
    });
}

// 深拷贝单元格值，避免对象类型（公式/富文本）引用同一实例。
function cloneCellValue(value) {
    if (value == null) return value;

    if (typeof value === "object") {
        return JSON.parse(JSON.stringify(value));
    }
    return value;
}

function ArabicToChinese(Arabic_numerals) {
    let strI = String(Arabic_numerals);
    let mixNumerals = "";
    let positionArr = ["", "十", "百", "千", "万", "十", "百", "千", "亿", "十", "百", "千", "万", "十", "百", "千", "亿"];
    for (let j = 1; j <= strI.length; j++) {
        let poistion = (strI.slice(-j)[0] == 0 && positionArr[j - 1] !== "万" && positionArr[j - 1] !== "亿") ? "" : positionArr[j - 1];
        let strIJ = (strI.slice(-j)[0] == 0 && (positionArr[j - 1] == "万" || positionArr[j - 1] == "亿")) ? "" : strI.slice(-j)[0];
        mixNumerals = strIJ + poistion + mixNumerals;
    }
    for (let j = Math.floor((strI.length - 1) / 4); j >= 1; j--) {
        if (j % 2 !== 0) {
            let regExp1 = new RegExp(`000+${positionArr[j * 4]}`, "g");
            mixNumerals = mixNumerals.replaceAll(regExp1, "");
        }
        let regExp2 = new RegExp(`0+${positionArr[j * 4]}`, "g");
        mixNumerals = mixNumerals.replaceAll(regExp2, `${positionArr[j * 4]}0`);
    }
    mixNumerals = mixNumerals.replaceAll(/00+/g, "0");
    mixNumerals = mixNumerals.replace(/^1十/, "十");
    mixNumerals = mixNumerals.replace(/0+$/, "");
    mixNumerals = mixNumerals.replace(/[0-9]/g, match => {
        const chineseNumerals = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
        return chineseNumerals[parseInt(match)];
    });
    return mixNumerals;
}
