#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 2026-11-02 大客户部月会纪要（沿用周会模板版式）。"""
import copy
import os
from openpyxl import load_workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter

BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = os.path.join(BASE, '工具', '大客户部周会纪要_20261026_10月W4.xlsx')
OUTDIR = os.path.join(BASE, '经营分析', '会议纪要')
OUT = os.path.join(OUTDIR, '大客户部月会纪要_20261102_V01.xlsx')
os.makedirs(OUTDIR, exist_ok=True)

TITLE = '大客户部月会纪要（2026-11-02）'
SUMMARY = (
    '1. 呼六生鲜一期测试本周一出客户结果，内部新版本本周出；安图TDH仍在价格拉扯，冻干物料已齐；万福PCT本周出POC。\n'
    '2. 明日达RT预混低浓度客诉初判为内部出库检标准偏低，需建立Ct值35左右低浓度模型并提高质量标准。\n'
    '3. 盛香dNTP污染客诉无法复现，向客户提供100多种菌设计参考清单及16S/18S设计原则，不作绝对质量保证。\n'
    '4. 普泰回款较上半年改善；盛香账期约90天、系统卡顿可能超100天；明日达、安图回款正常，卡点在系统流程。\n'
    '5. 存量商机约546万、占目标约70%；突破型约260万，约一半存在客诉或供货风险，需细化到仪器平台、场景、解决程度和时间。'
)

ROWS = [
    ('SECTION', '一、重点客户项目进展'),
    (1, '呼六生鲜', '测试推进',
     '一期测试试剂已按客户要求发出；本周一客户出测试结果，内部新版本测试结果本周产出。',
     '若竞品测试结果较好，后续测试机会将受限。', '跟进客户与内部结果，评估竞品影响。', '赵云浩', '本周'),
    (2, '安图', 'TDH价格谈判',
     '项目仍在测试环节；客户目标价400元；当前梯度方案：200–1000 ml 580元，200 ml以内600元。',
     '价格未定；预计一个多月后客户按起始量决定采购。', '维持梯度报价，跟进起始量。', '赵云浩', '一个多月'),
    (3, '安图', '恒铭达英瑞达89体系冻干',
     '客户漏寄阳性质粒导致上周进度受阻；本周物料已齐，有机器即可上线测试。',
     '依赖客户设备排期。', '物料到位后立即上线测试。', '赵云浩', '本周'),
    (4, '明日达', 'RT预混客诉',
     '客户反馈低浓度检出灵敏度下降；初判内部出库检质量标准偏低（低浓度只看检出率、不看荧光值）；今晚出复测结果。',
     '复测结果未出；可能影响客户信心。', '今晚与客户沟通复测结果，同步内部质量提升。', '赵云浩', '11/2 晚'),
    (5, '万福', 'PCT项目',
     '物料已寄到，使用方法已对齐；预计本周出POC结果。',
     'POC结果待出。', '跟进POC结果。', '赵云浩', '本周'),
    (6, '万福', 'SBV项目',
     '已发新酶和新体系；客户近期在优化污水项目，短期内暂无测试时间。',
     '测试窗口不确定。', '保持跟进，等待客户排期。', '赵云浩', '待定'),
    (7, '明德', '呼吸道冻干',
     '客户反馈冻干后副孔间荧光值差异大；液体检显示我方酶无差异，初步判断非我方问题。',
     '需新批次结果确认。', '跟进新批次结果。', '赵云浩', '本周'),
    (8, '普泰', 'DNA采购',
     '近两个月DNA采购量持续上涨，上半年采购量已超预期。',
     '年底传染病高发期需求可能波动。', '关注年底采购节奏，提前做备货沟通。', '赵云浩', '持续'),
    (9, '黄山', '项目测试',
     '谭博牵头测试；上周已发两款测试试剂，本周会有初步测试结果反馈。',
     '结果待反馈。', '跟进初步测试结果。', '谭博', '本周'),

    ('SECTION', '二、重点客诉与质量问题'),
    (10, '明日达', 'RT预混低浓度客诉归因',
     '客户反馈低浓度（Ct约35）检出率下降，荧光增量下降约20%，内部模型下部分批次不合格；初判内部出库检标准过低（行业低浓度Ct 33–34，仅看检出率无法拦截偏差）。',
     '内部质量标准与行业水平有差距。', '建立稳定检出Ct值35左右的低浓度模型；推动出厂质量标准提高到行业水平。', '赵云浩', '本周推进'),
    (11, '盛香', 'dNTP污染客诉',
     '4月爆发红绿污染/细菌污染，导致试剂在2–8℃保存条件下稳定性急剧下降；客诉物料配制的试剂目前无法重现污染；客户要求提供出厂检控制100多种菌的清单，难以完全满足。',
     '污染难以复现；客户期望较高。', '向客户提供100多种菌设计参考清单及16S/18S设计原则，明确作为回复参考、不作绝对质量保证。', '赵云浩', '本周'),
    (12, '普泰', 'RT预混替换异常',
     '个别靶标Ct值差异约6，且呈梯度差异；内部分析为客户隐态引物GC含量差异较大、Tm值差异大。',
     '需验证添加剂效果。', '本周向客户发两款添加剂进行测试。', '赵云浩', '本周'),

    ('SECTION', '三、回款与拜访计划'),
    (13, '普泰', '回款情况',
     '回款较上半年大幅好转；存在分批次回款，但整体流程顺畅。',
     '无重大卡点。', '按批次持续跟进。', '赵云浩', '持续'),
    (14, '盛香', '回款/账期',
     '账期较长，内部计算约90天；叠加系统卡顿可能达100多天；客户习惯每月最后一天回款，催促易引起反感。',
     '账期长；催收方式需注意。', '按客户节奏跟进，避免强硬催收。', '赵云浩', '持续'),
    (15, '明日达/安图', '回款情况',
     '回款目前无大问题，主要卡点在系统流程。',
     '系统流程。', '跟进系统流程。', '赵云浩', '持续'),
    (16, '必访客户', '本月拜访计划',
     '本月重点拜访：明日达、安图、盛香、科迪亚；科迪亚已新约到研发与采购负责人。',
     '拜访时间待排。', '安排并确认拜访时间。', '赵云浩', '本月'),
    (17, '万福等', '按需拜访',
     '视具体项目情况决定，例如明德RT预混测试结果出来后再安排。',
     '取决于测试结果。', '结果出来后决定是否拜访。', '赵云浩', '待定'),

    ('SECTION', '四、商机预测与风险评估'),
    (18, '商机预测表', '10月销售额更新',
     '需补充更新10月份商机销售额，并核对ERP中的实际销售数据，找出差值原因。',
     '数据待核对。', '更新商机表并完成ERP差异核对。', '赵云浩', '本周'),
    (19, '商机预测表', '商机分类原则',
     '商机需明确分为存量（稳定采购或优化中）和突破型（新项目或需争取），预测时分开评估。',
     '分类口径需统一。', '按存量/突破型两类更新预测表。', '赵云浩', '本周'),
    (20, '商机预测表', '商机结构',
     '存量商机合计约546万，占整体目标约70%，结构较健康；突破型商机合计约260万，其中约一半存在客诉未解决或供货不稳定，落地风险较大。',
     '突破型商机风险高。', '对突破型商机逐项评估落地概率。', '赵云浩', '本周'),
    (21, '商机预测表', '客诉共性与细化',
     '标红的突破型商机中，客诉原因主要集中在质量标准不够高、污染、实际性能不达标（如NA检出、PCT差异）三类。',
     '需细化到仪器平台、应用场景、解决程度和预计解决时间。', '将商机表中的客诉问题逐项具体化。', '赵云浩', '本周'),

    ('SECTION', '五、待办事项'),
    (1, '10月商机销售额', '数据更新',
     '补充更新10月份商机销售额，并核对ERP中的实际销售数据，找出差值原因。',
     '—', '完成更新与差异说明。', '赵云浩', '本周'),
    (2, '明日达RT预混', '客诉复测沟通',
     '今晚与客户沟通明日达RT预混客诉的复测结果。',
     '—', '完成客户沟通并记录结果。', '赵云浩', '11/2 晚'),
    (3, '明日达RT预混', '质量标准提升',
     '将明日达RT预混客诉的质量标准提升需求推内部解决，建立Ct值35左右的低浓度模型。',
     '—', '形成内部推进计划。', '赵云浩', '本周'),
    (4, '普泰RT预混', '添加剂测试',
     '本周向普泰发两款添加剂，解决RT预混替换中的Ct值差异问题。',
     '—', '完成发货并跟进测试。', '赵云浩', '本周'),
    (5, '盛香质量协议', '协议签署',
     '与赵文对接，完成盛香质量协议的签署。',
     '—', '完成签署。', '赵云浩', '本周'),
    (6, '盛香dNTP', '客诉回复',
     '向客户发送盛香dNTP项目的设计参考菌清单及设计原则，作为客诉回复。',
     '—', '完成发送并留档。', '赵云浩', '本周'),
    (7, '安图', '拜访安排',
     '本周后半周与石总一同前往安图拜访。',
     '—', '确认时间并完成拜访。', '赵云浩、石总', '本周后半周'),
    (8, '安图', '特殊流程签署',
     '催促石总签署特殊流程，以便向客户发货。',
     '—', '完成签署并安排发货。', '赵云浩', '本周'),
    (9, '盛香专题会', '参会确认',
     '找李峰确认盛香专题会是否需要自己参会。',
     '—', '确认参会安排。', '赵云浩', '本周'),
    (10, '商机预测表', '客诉问题细化',
     '将商机预测表中的客诉问题具体化，明确仪器平台、应用场景、解决程度及预计解决时间。',
     '—', '完成逐项细化。', '待定', '本周'),
]

# 载入周会样板，复制 10月W4 后只保留新 sheet
wb = load_workbook(SRC, data_only=False)
ws0 = wb['10月W4']
ws = wb.copy_worksheet(ws0)
ws.title = '11月W1_月会'
for name in list(wb.sheetnames):
    if name != '11月W1_月会':
        del wb[name]

# 清空数据区、解除数据区合并
for m in list(ws.merged_cells.ranges):
    if m.min_row >= 8:
        ws.unmerge_cells(str(m))
for r in range(8, ws.max_row + 1):
    for c in range(1, ws.max_column + 1):
        ws.cell(r, c).value = None

# 标题/表头合并改成 A:H
for r in (1, 2, 3, 4, 5, 6):
    for m in list(ws.merged_cells.ranges):
        if m.min_row == r and m.max_row == r:
            ws.unmerge_cells(str(m))
ws.merge_cells('A1:H1')
ws.merge_cells('C2:E2'); ws.merge_cells('G2:H2')
ws.merge_cells('C3:E3'); ws.merge_cells('G3:H3')
ws.merge_cells('A4:B4'); ws.merge_cells('C4:E4'); ws.merge_cells('G4:H4')
ws.merge_cells('A5:B5'); ws.merge_cells('C5:H5')
ws.merge_cells('A6:H6')

# 页头
ws['A1'] = TITLE
ws['A1'].font = Font(name='微软雅黑', size=16, bold=True, color='FFFFFF')
ws['A1'].fill = PatternFill('solid', fgColor='1F4E78')
ws['A1'].alignment = Alignment(horizontal='center', vertical='center')
ws.row_dimensions[1].height = 32

for cell, value in [('A2', '会议主题'), ('C2', '大客户部月会'), ('F2', '会议时间'), ('G2', '2026-11-02 上午'),
                    ('A3', '会议主持'), ('C3', '刘新元'), ('F3', '纪要纪录'), ('G3', '刘新元'),
                    ('A4', '参会人员'), ('C4', '大客户部（具体名单待补）'), ('F4', '缺席人员'), ('G4', '待补'),
                    ('A5', '重点事项摘要'), ('C5', SUMMARY), ('A6', '会议内容')]:
    ws[cell] = value
for r in (2, 3, 4):
    for c in range(1, 9):
        ws.cell(r, c).font = Font(name='微软雅黑', size=10)
        ws.cell(r, c).alignment = Alignment(vertical='center', wrap_text=True)
for c in (1, 6):
    ws.cell(2 if c == 1 else 2, c).font = Font(name='微软雅黑', size=10, bold=True)
    ws.cell(2 if c == 1 else 2, c).fill = PatternFill('solid', fgColor='D9E2F3')
ws['A2'].font = Font(name='微软雅黑', size=10, bold=True)
ws['A2'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['F2'].font = Font(name='微软雅黑', size=10, bold=True)
ws['F2'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['A3'].font = Font(name='微软雅黑', size=10, bold=True)
ws['A3'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['F3'].font = Font(name='微软雅黑', size=10, bold=True)
ws['F3'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['A4'].font = Font(name='微软雅黑', size=10, bold=True)
ws['A4'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['F4'].font = Font(name='微软雅黑', size=10, bold=True)
ws['F4'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['A5'].font = Font(name='微软雅黑', size=10, bold=True)
ws['A5'].fill = PatternFill('solid', fgColor='D9E2F3')
ws['C5'].font = Font(name='微软雅黑', size=10)
ws['C5'].alignment = Alignment(vertical='top', wrap_text=True)
ws.row_dimensions[5].height = 118
ws['A6'].font = Font(name='微软雅黑', size=12, bold=True, color='FFFFFF')
ws['A6'].fill = PatternFill('solid', fgColor='1F4E78')
ws['A6'].alignment = Alignment(horizontal='center', vertical='center')
ws.row_dimensions[6].height = 24

HEADERS = ['序号', '客户/项目', '议题', '会议同步/结论', '风险/待确认', '下一步', '责任人', '时间']
for c, h in enumerate(HEADERS, 1):
    cell = ws.cell(7, c, h)
    cell.font = Font(name='微软雅黑', size=10, bold=True, color='FFFFFF')
    cell.fill = PatternFill('solid', fgColor='2F5597')
    cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
    cell.border = Border(*([Side(style='thin', color='A6A6A6')] * 4))
ws.row_dimensions[7].height = 28

thin = Side(style='thin', color='A6A6A6')
border = Border(left=thin, right=thin, top=thin, bottom=thin)
r = 8
num = 0
for item in ROWS:
    if item[0] == 'SECTION':
        ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=8)
        cell = ws.cell(r, 1, item[1])
        cell.font = Font(name='微软雅黑', size=11, bold=True, color='1F4E78')
        cell.fill = PatternFill('solid', fgColor='D9E2F3')
        cell.alignment = Alignment(vertical='center')
        for c in range(1, 9):
            ws.cell(r, c).border = border
        ws.row_dimensions[r].height = 24
        r += 1
        continue
    vals = list(item)
    # 五、待办事项 section 的序号重置为 1..10；用 B 列承载事项，C/H 承载类别/时间
    for c, val in enumerate(vals, 1):
        cell = ws.cell(r, c, val)
        cell.font = Font(name='微软雅黑', size=9)
        cell.alignment = Alignment(vertical='top', wrap_text=True)
        cell.border = border
        if c == 1:
            cell.alignment = Alignment(horizontal='center', vertical='top')
    # 行高：按最长文本估算，保证不截断
    longest = max(len(str(x)) for x in vals[2:6])
    ws.row_dimensions[r].height = min(118, max(54, 18 * (longest // 26 + 1)))
    r += 1

# 末行说明
last = r
ws.merge_cells(start_row=last, start_column=1, end_row=last, end_column=8)
note = ('说明：本纪要依据 2026-11-02 上午腾讯会议记录整理；“待补/待定”为会议记录未明确事项；'
        '商机金额为会议口径，需与 ERP 实际销售核对后定稿。')
cell = ws.cell(last, 1, note)
cell.font = Font(name='微软雅黑', size=8, italic=True, color='595959')
cell.alignment = Alignment(vertical='top', wrap_text=True)
cell.fill = PatternFill('solid', fgColor='F2F2F2')
for c in range(1, 9):
    ws.cell(last, c).border = border
ws.row_dimensions[last].height = 32

# 列宽/页面
widths = {'A': 6, 'B': 15, 'C': 20, 'D': 52, 'E': 36, 'F': 34, 'G': 14, 'H': 12}
for col, width in widths.items():
    ws.column_dimensions[col].width = width
# 模板原有 I-K 历史周列，本表不用，隐藏掉，避免预览/打印出现空白旧表头
for col in ('I', 'J', 'K'):
    ws.column_dimensions[col].hidden = True
    ws.column_dimensions[col].width = 3
ws.row_dimensions[5].height = 140
ws.sheet_view.showGridLines = False
ws.freeze_panes = 'A8'
ws.print_area = f'A1:H{last}'
ws.page_setup.orientation = 'landscape'
ws.page_setup.fitToWidth = 1
ws.page_setup.fitToHeight = 0
ws.sheet_properties.pageSetUpPr.fitToPage = True
ws.print_title_rows = '1:7'
ws.page_margins.left = 0.3
ws.page_margins.right = 0.3
ws.page_margins.top = 0.4
ws.page_margins.bottom = 0.4

wb.save(OUT)
print('saved', OUT)
print('sheets', wb.sheetnames, 'rows', ws.max_row, 'cols', ws.max_column)
