Files
mileage-bonus/excel_writer.py
kkfluous 4c43c00e73 feat: 车辆考核追踪sheet美化
- 达标月份绿底✓,未达标红底✗
- 累计达标/未达标加粗+颜色
- 有发放的行金底高亮
- 奖金池已发期数蓝底加粗,显示为"N/12"格式
- 车辆基本信息列浅灰底色区分
- 冻结首行+首列,支持滚动查看
- 开启自动筛选
- 优化列宽

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:02:50 +08:00

486 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Excel输出工具函数 + 各类sheet生成"""
import openpyxl
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from collections import defaultdict
from calc_engine import RULES, DAYS
hf = Font(bold=True)
hfl = PatternFill(start_color='D9E1F2', end_color='D9E1F2', fill_type='solid')
bd = Border(left=Side(style='thin'),right=Side(style='thin'),top=Side(style='thin'),bottom=Side(style='thin'))
def WH(ws, headers, row=1):
for c, h in enumerate(headers, 1):
cl = ws.cell(row=row, column=c, value=h)
cl.font=hf; cl.fill=hfl; cl.border=bd; cl.alignment=Alignment(horizontal='center', wrap_text=True)
def WR(ws, rn, vals):
for c, v in enumerate(vals, 1):
cl = ws.cell(row=rn, column=c, value=v); cl.border=bd
def AW(ws):
for col in ws.columns:
ml = max((len(str(c.value or '')) for c in col), default=0)
ws.column_dimensions[col[0].column_letter].width = min(ml+3, 22)
def R(v, n=2):
return round(v, n) if isinstance(v, (int,float)) and v else v
def agg(dl):
bp = defaultdict(lambda: {'n':0,'a':0})
for d in dl: bp[d['销售']]['n']+=1; bp[d['销售']]['a']+=d['']
return dict(bp)
def write_sec(ws, rn, title, dl):
ws.cell(row=rn, column=1, value=title).font=Font(bold=True, size=11); rn+=1
WH(ws, ['销售人员','车辆数','金额'], rn); rn+=1
bp=agg(dl); tv=ta=0
for p in sorted(bp.keys()):
WR(ws,rn,[p,bp[p]['n'],R(bp[p]['a'])]); tv+=bp[p]['n']; ta+=bp[p]['a']; rn+=1
WR(ws,rn,['总计',tv,R(ta)]); ws.cell(row=rn,column=1).font=hf; return rn+2
def write_total(ws, rn, month, all_data):
total_bp = defaultdict(lambda: {'部门':'','':0})
for cat,dl in all_data.items():
for d in dl: total_bp[d['销售']]['']+=d['']; total_bp[d['销售']]['部门']=d['部门']
ws.cell(row=rn,column=1,value=f'{month}月合计应发奖励(按销售人员)').font=Font(bold=True,size=11); rn+=1
WH(ws,['销售人员','部门名称','合计应发奖励'],rn); rn+=1
gt=0
for p in sorted(total_bp.keys()):
WR(ws,rn,[p,total_bp[p]['部门'],R(total_bp[p][''])]); gt+=total_bp[p]['']; rn+=1
WR(ws,rn,['合计','',R(gt)]); ws.cell(row=rn,column=1).font=hf; rn+=2
by_dept=defaultdict(float)
for p,d in total_bp.items(): by_dept[d['部门']]+=d['']
ws.cell(row=rn,column=1,value=f'{month}月合计应发奖励(按部门)').font=Font(bold=True,size=11); rn+=1
WH(ws,['部门名称','合计应发奖励'],rn); rn+=1
for dept in sorted(by_dept.keys()): WR(ws,rn,[dept,R(by_dept[dept])]); rn+=1
WR(ws,rn,['合计',R(gt)]); ws.cell(row=rn,column=1).font=hf
# ============================================================
# Sheet生成函数
# ============================================================
def write_rules_sheet(wb):
ws = wb.active; ws.title='考核奖励规则'
WH(ws,['考核目标','月度目标里程(km)','奖励金额(元)'])
for i,(n,km,b) in enumerate([('交投40辆4.5T普货',3000,150),('交投190辆4.5T冷链车',3000,150),
('羚牛136辆4.5T冷链车',5000,260),('恒运50辆4.5T普货',5000,260),('羚牛100辆18T',6000,1000)],2):
WR(ws,i,[n,km,b])
AW(ws)
def write_detail_sheet(wb, records, month):
ws = wb.create_sheet(f'里程明细{month}')
WH(ws,['车牌号','部门名称','销售经理','客户名称','合同编号','考核目标','月度目标里程','对应月奖励金额',
'考核天数',f'{month}月应考核里程',f'{month}月实际行驶里程','完成率',f'{month}月是否达标',
f'{month}月奖金(天数折算)','多跑里程','可结转月数','考核状态'])
rn=2
for r in sorted(records, key=lambda x: (x['销售经理'],x['车牌号'])):
t,a = r['应考核里程(km)'], r['实际行驶里程(km)']
WR(ws,rn,[r['车牌号'],r['部门名称'],r['销售经理'],r.get('客户名称',''),r.get('合同编号',''),
r.get('考核目标',''),r['月度目标里程'],r['对应月奖励金额'],r['考核天数'],
R(t),R(a),R(a/t,4) if t>0 else 0,r['是否达标'],R(r['奖金']),R(r['多跑']),r['可结转'],
r.get('考核状态','')]); rn+=1
AW(ws)
def write_calc_process_jan(wb, G1):
ws = wb.create_sheet('1月计算过程')
WH(ws,['车牌号','销售经理','部门','①记录数','②总考核天数','③总应考核里程','④总实际里程',
'⑤有达标记录','⑥达标记录奖金合计','⑦总多跑里程','⑧月度目标里程','⑨可结转月数=floor(⑦/⑧)',
'→1月发放金额','→发放类型'])
rn=2
for k in sorted(G1.keys(), key=lambda x: (x[1],x[0])):
g=G1[k]
excess=sum(r['多跑'] for r in g['recs'] if r['是否达标']=='达标')
WR(ws,rn,[k[0],k[1],g['部门'],len(g['recs']),g['天数'],R(g['应考核']),R(g['实际']),
'' if g['有达标'] else '',R(g['奖金']),R(excess),g['目标km'],g['可结转'],
R(g['奖金']) if g['有达标'] else 0,'当月达标' if g['有达标'] else '未达标']); rn+=1
AW(ws)
def write_calc_process_feb(wb, G1, G2):
ws = wb.create_sheet('2月计算过程')
WH(ws,['车牌号','销售经理','部门',
'1月总应考核','1月总实际','1月有达标','1月奖金','1月多跑','1月可结转',
'2月记录数','2月总天数','2月总应考核','2月总实际','2月有达标','2月达标奖金',
'累计应完成','累计实际','累计是否达标',
'判断①结转','→结转金额(完整月)','判断②补发1月','→补发1月金额',
'判断③当月','→2月当月金额','判断④累计补发2月','→累计补发2月金额','2月发放合计'])
rn=2
for k in sorted(G2.keys(), key=lambda x: (x[1],x[0])):
g2=G2[k]; g1=G1.get(k)
j_q=g1['有达标'] if g1 else False; j_c=g1['可结转'] if g1 else 0
j_t=g1['应考核'] if g1 else 0; j_a=g1['实际'] if g1 else 0
j_bonus=g1['奖金'] if g1 else 0
je=sum(r['多跑'] for r in g1['recs'] if r['是否达标']=='达标') if g1 else 0
ct=g2.get('cum_t',0); ca=g2.get('cum_a',0); cq=g2.get('cum_q',False)
carry=g2.get('结转',0); bp1=g2.get('补发1月',0); bonus2=g2.get('当月奖金',0); cbp2=g2.get('累计补发2月',0)
c1=f"1月达标={j_q},可结转={j_c}{'' if carry>0 else ''}"
c2=f"1月未达标={not j_q},累计达标={cq}{'' if bp1>0 else ''}" if g1 else "无1月"
c3=f"2月达标={g2['有达标']},无结转={carry==0}{'' if bonus2>0 else ''}"
c4=f"2月未达标={not g2['有达标']},无结转={carry==0},累计达标={cq}{'' if cbp2>0 else ''}"
total=carry+bp1+bonus2+cbp2
WR(ws,rn,[k[0],k[1],g2['部门'],R(j_t),R(j_a),'' if j_q else '',R(j_bonus),R(je),j_c,
len(g2['recs']),g2['天数'],R(g2['应考核']),R(g2['实际']),'' if g2['有达标'] else '',R(g2['奖金']),
R(ct),R(ca),'达标' if cq else '未达标',
c1,R(carry) if carry>0 else 0,c2,R(bp1) if bp1>0 else 0,
c3,R(bonus2) if bonus2>0 else 0,c4,R(cbp2) if cbp2>0 else 0,R(total) if total>0 else 0]); rn+=1
AW(ws)
def write_calc_process_mar(wb, G1, G2, G3, feb_data):
ws = wb.create_sheet('3月计算过程')
WH(ws,['车牌号','销售经理','部门',
'1月应考核','1月实际','1月有达标','1月奖金已发',
'2月应考核','2月实际','2月有达标','2月奖金已发',
'3月记录数','3月总天数','3月总应考核','3月总实际','3月有达标','3月达标奖金',
'累计应完成','累计实际','累计是否达标',
'判断①结转','→结转金额','判断②补发1月','→补发1月金额',
'判断③补发2月','→补发2月金额','判断④当月','→3月当月金额',
'判断⑤累计补发3月','→累计补发3月金额','3月发放合计'])
rn=2
for k in sorted(G3.keys(), key=lambda x: (x[1],x[0])):
g3=G3[k]; g2=G2.get(k); g1=G1.get(k)
j_t=g1['应考核'] if g1 else 0; j_a=g1['实际'] if g1 else 0
j_q=g1['有达标'] if g1 else False; j_paid=(g1['奖金']>0) if g1 else False
if g1 and j_q: j_paid=True
if g2 and g2.get('1月已补发',False): j_paid=True
f_t=g2['应考核'] if g2 else 0; f_a=g2['实际'] if g2 else 0
f_q=g2['有达标'] if g2 else False; f_paid=g2['2月已发'] if g2 else False
f_carry=g2['可结转'] if g2 else 0
ct=g3.get('cum_t',0); ca=g3.get('cum_a',0); cq=g3.get('cum_q',False)
carry=g3.get('结转',0); bj=g3.get('补发1月',0); bf2=g3.get('补发2月',0)
bonus3=g3.get('当月奖金',0); cbp3=g3.get('累计补发3月',0)
c1=f"2月可结转={f_carry}{'' if carry>0 else ''}"
c2=f"1月未发={not j_paid},累计达标={cq}{'' if bj>0 else ''}"
c3=f"2月未发={not f_paid},累计达标={cq}{'' if bf2>0 else ''}"
c4=f"3月达标={g3['有达标']},无结转={carry==0}{'' if bonus3>0 else ''}"
c5=f"3月未达标={not g3['有达标']},无结转={carry==0},累计达标={cq}{'' if cbp3>0 else ''}"
total=carry+bj+bf2+bonus3+cbp3
WR(ws,rn,[k[0],k[1],g3['部门'],R(j_t),R(j_a),'' if j_q else '','' if j_paid else '',
R(f_t),R(f_a),'' if f_q else '','' if f_paid else '',
len(g3['recs']),g3['天数'],R(g3['应考核']),R(g3['实际']),'' if g3['有达标'] else '',R(g3['奖金']),
R(ct),R(ca),'达标' if cq else '未达标',
c1,R(carry) if carry>0 else 0,c2,R(bj) if bj>0 else 0,
c3,R(bf2) if bf2>0 else 0,c4,R(bonus3) if bonus3>0 else 0,
c5,R(cbp3) if cbp3>0 else 0,R(total) if total>0 else 0]); rn+=1
AW(ws)
def write_summary_jan(wb, records):
ws = wb.create_sheet('1月汇总')
jan_dl=[{'车牌':r['车牌号'],'销售':r['销售经理'],'部门':r['部门名称'],'':r['奖金']} for r in records if r['是否达标']=='达标']
rn=write_sec(ws,1,'1月达标奖励',jan_dl)
write_total(ws,rn,1,{'达标':jan_dl})
AW(ws)
def write_summary_month(wb, month, month_data, section_names):
ws = wb.create_sheet(f'{month}月汇总')
rn=1
for i,cat in enumerate(section_names):
label_map = {'结转':f'一、结转:{month-1}月多跑在{month}月发',
'补发1月':'二、补发1月','补发2月':'三、补发2月',
'当月':f'{"" if month==2 else ""}{month}月当月奖励',
f'累计补发{month}':f'{"" if month==2 else ""}、累计达标补发{month}'}
rn=write_sec(ws,rn,label_map.get(cat,cat),month_data.get(cat,[]))
write_total(ws,rn,month,month_data)
AW(ws)
# ============================================================
# 新增业务员sheet
# ============================================================
def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, vehicle_payments):
short_dept = dept.replace('业务','') if '业务' in dept else dept
ws = wb.create_sheet(f'{short_dept}-{person}')
ws.cell(row=1,column=1,value=f'{person} | {dept} | {settle_month}月绩效对账单').font=Font(bold=True,size=14)
plates = set()
for m in range(1, settle_month+1):
for k in G.get(m, {}):
if k[1] == person: plates.add(k[0])
person_total = sum(d[''] for cat,dl in month_data.items() for d in dl if d['销售']==person)
ws.cell(row=2,column=1,value=f'本月考核车辆: {len(plates)}辆 | 本月发放合计: {R(person_total)}').font=Font(bold=True,size=11)
# 表头:车辆信息 + 各月里程/目标 + 累计 + 奖金池
headers = ['车牌号','考核目标','月奖励']
for m in range(1, settle_month+1):
headers.append(f'{m}月里程/目标')
if settle_month >= 2:
headers += ['累计里程/目标']
headers.append('达标')
WH(ws, headers, 4)
rn = 5
for plate in sorted(plates):
g_cur = None
for m in range(settle_month, 0, -1):
g_cur = G.get(m, {}).get((plate, person))
if g_cur: break
if not g_cur: continue
first = g_cur['recs'][0]
mkm = g_cur['目标km']
# 第1行车辆信息 + 各月里程/目标
row = [plate, first.get('考核目标',''), g_cur['奖励额']]
cum_t = 0; cum_a = 0
for m in range(1, settle_month+1):
gm = G.get(m, {}).get((plate, person))
if gm:
t=gm['应考核']; a=gm['实际']; cum_t+=t; cum_a+=a
row.append(f'{R(a,0)}/{R(t,0)}')
else:
row.append('-')
if settle_month >= 2:
row.append(f'{R(cum_a,0)}/{R(cum_t,0)}')
cum_q = cum_a >= cum_t and cum_t > 0
# 达标判断取settle_month的group
g_s = G.get(settle_month, {}).get((plate, person))
if g_s and g_s.get('有达标'):
row.append('达标')
elif cum_q:
row.append('累计达标')
else:
row.append('未达标')
WR(ws, rn, row)
ws.cell(row=rn, column=1).font = Font(bold=True)
rn += 1
# 第2-N行发放明细历史已发 + 本月发放)
WH(ws, ['', '发放项', '金额', '说明', '奖金池'], rn); rn += 1
pays = vehicle_payments.get(plate, [])
pays_to_date = [p for p in pays if p['结算月'] <= settle_month]
total_periods = len(pays_to_date)
# 历史已发m < settle_month
for m in range(1, settle_month):
gm = G.get(m, {}).get((plate, person))
# 查该月是否有发放记录
m_pays = [p for p in pays_to_date if p['结算月'] == m and p['业务员'] == person]
if m_pays:
amt = sum(p['金额'] for p in m_pays)
types = ', '.join(p['类型'] for p in m_pays)
WR(ws, rn, ['', f'{m}月: 已发', R(amt), types, ''])
elif gm:
# 有考核但未发
# 检查是否在后续月被补发过settle_month之前
bp_pays = [p for p in pays_to_date if p['对应考核月'] == m and p['业务员'] == person]
if bp_pays:
amt = sum(p['金额'] for p in bp_pays)
sm = bp_pays[0]['结算月']
WR(ws, rn, ['', f'{m}月: 已发', R(amt), f'{sm}月补发', ''])
else:
WR(ws, rn, ['', f'{m}月: 未发', 0, f'未达标(实际{R(gm["实际"],0)}<目标{R(gm["应考核"],0)})', ''])
else:
WR(ws, rn, ['', f'{m}月: 无记录', '', '', ''])
rn += 1
# 本月发放
g_s = G.get(settle_month, {}).get((plate, person), {})
if not isinstance(g_s, dict): g_s = {}
gm_s = G.get(settle_month, {}).get((plate, person))
plate_this_month = 0
# 结转
carry = g_s.get('结转', 0)
if carry > 0:
prev = settle_month - 1
g_prev = G.get(prev, {}).get((plate, person))
prev_excess = sum(r['多跑'] for r in g_prev['recs'] if r['是否达标'] == '达标') if g_prev else 0
WR(ws, rn, ['', '结转', R(carry), f'{prev}月多跑{R(prev_excess,0)}{mkm},结转(完整月奖金)', ''])
plate_this_month += carry; rn += 1
# 补发过去月份
for prev_m in range(1, settle_month):
bp_key = f'补发{prev_m}'
bp_amt = g_s.get(bp_key, 0)
if bp_amt > 0:
WR(ws, rn, ['', f'补发{prev_m}', R(bp_amt), f'累计{R(cum_a,0)}{R(cum_t,0)}达标,补发{prev_m}', ''])
plate_this_month += bp_amt; rn += 1
# 当月
bonus = g_s.get('当月奖金', 0)
if bonus > 0:
excess = sum(r['多跑'] for r in gm_s['recs'] if r['是否达标'] == '达标') if gm_s else 0
desc = '当月达标'
if excess > 0 and mkm > 0 and int(excess // mkm) >= 1:
desc += f',多跑{R(excess,0)}{mkm}可结转'
WR(ws, rn, ['', f'{settle_month}月当月', R(bonus), desc, ''])
plate_this_month += bonus; rn += 1
# 累计补发当月
cum_bp = g_s.get(f'累计补发{settle_month}', 0)
if cum_bp > 0:
WR(ws, rn, ['', f'累计补发{settle_month}', R(cum_bp), f'累计{R(cum_a,0)}{R(cum_t,0)}达标,补发{settle_month}', ''])
plate_this_month += cum_bp; rn += 1
# 无发放
if plate_this_month == 0 and carry == 0:
if gm_s:
a_s = gm_s['实际']; t_s = gm_s['应考核']
if gm_s.get('结转占位'):
WR(ws, rn, ['', '本月不发', 0, '结转占位,本月不另发', ''])
else:
WR(ws, rn, ['', '本月不发', 0, f'未达标(实际{R(a_s,0)}<目标{R(t_s,0)}),累计也未达标', ''])
else:
WR(ws, rn, ['', '本月不发', 0, '本月无考核记录', ''])
rn += 1
# 本月合计 + 奖金池
pool_str = f'已发{total_periods}期/共12期剩余{12-total_periods}'
WR(ws, rn, ['', '本月合计', R(plate_this_month), '', pool_str])
ws.cell(row=rn, column=2).font = Font(bold=True)
ws.cell(row=rn, column=5).font = Font(italic=True)
rn += 2 # 空行
# 尾部
ws.cell(row=rn,column=1,value=f'{person} {settle_month}月合计: {len(plates)}辆车, 发放 {R(person_total)}').font=Font(bold=True,size=12)
AW(ws)
# ============================================================
# 新增车辆考核追踪sheet
# ============================================================
def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info):
ws = wb.create_sheet('车辆考核追踪')
from calc_engine import VEHICLE_TARGET_MAP
wrap_top = Alignment(wrap_text=True, vertical='top')
center_top = Alignment(horizontal='center', vertical='top')
# 颜色
green_font = Font(color='006100')
green_fill = PatternFill(start_color='C6EFCE', end_color='C6EFCE', fill_type='solid')
red_font = Font(color='9C0006')
red_fill = PatternFill(start_color='FFC7CE', end_color='FFC7CE', fill_type='solid')
grey_fill = PatternFill(start_color='F2F2F2', end_color='F2F2F2', fill_type='solid')
blue_fill = PatternFill(start_color='DAEEF3', end_color='DAEEF3', fill_type='solid')
gold_fill = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
# 表头
headers = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励']
month_start_col = len(headers) # 月度考核开始列(0-indexed)
for m in range(1, settle_month+1):
headers.append(f'{m}月考核明细')
cum_start_col = len(headers)
if settle_month >= 2:
headers += ['累计里程/目标','累计达标']
pay_start_col = len(headers)
headers += ['本月发放明细','已发期数','已发金额','剩余期数']
WH(ws, headers)
# 冻结首行+首列
ws.freeze_panes = 'B2'
rn=2
for mv in master_vehicles:
plate=mv['车牌号']
info=vehicle_info.get(plate, {})
pays=vehicle_payments.get(plate, [])
target_name = info.get('考核目标','')
monthly_bonus = info.get('月度奖励',0)
if not target_name:
company = mv.get('归属公司','')
vtype = mv.get('车型确定','')
mapped = VEHICLE_TARGET_MAP.get((company, vtype))
if mapped:
target_name, _, monthly_bonus = mapped
row=[plate,mv.get('车架号',''),mv.get('归属公司',''),mv.get('车型确定',''),
target_name or '',monthly_bonus or '']
cum_t=0; cum_a=0
month_qualified = {} # {月份: bool}
for m in range(1, settle_month+1):
mgs=[(k,g) for k,g in G.get(m,{}).items() if k[0]==plate]
if mgs:
lines = []
any_q = False
for _,g in sorted(mgs, key=lambda x: x[0][1]):
t=g['应考核']; a=g['实际']
cum_t+=t; cum_a+=a
q = g['有达标']
if q: any_q = True
sd = g['部门'].replace('业务','') if '业务' in g.get('部门','') else g.get('部门','')
lines.append(f"{sd}-{g['销售']}: {R(a,0)}/{R(t,0)} {'' if q else ''}")
row.append('\n'.join(lines))
month_qualified[m] = any_q
else:
row.append('')
month_qualified[m] = None # 无数据
cum_q = False
if settle_month>=2:
cum_q = cum_a>=cum_t and cum_t>0
row+=[f'{R(cum_a,0)}/{R(cum_t,0)}', '✓ 达标' if cum_q else '✗ 未达标']
# 本月发放
tp=[p for p in pays if p['结算月']==settle_month]
if tp:
pay_lines = []
for p in sorted(tp, key=lambda x: x['业务员']):
pd = p.get('部门','').replace('业务','') if '业务' in p.get('部门','') else p.get('部门','')
pay_lines.append(f"{pd}-{p['业务员']}: {R(p['金额'])}元 ({p['类型']})")
row.append('\n'.join(pay_lines))
else:
row.append('')
pays_to_date=[p for p in pays if p['结算月']<=settle_month]
tp_count=len(pays_to_date); tp_amt=sum(p['金额'] for p in pays_to_date)
row+=[f'{tp_count}/12', R(tp_amt) if tp_amt>0 else 0, 12-tp_count]
WR(ws,rn,row)
# --- 美化 ---
# 车辆基本信息列:浅灰底色
for ci in range(1, month_start_col+1):
ws.cell(row=rn, column=ci).fill = grey_fill
# 月度考核列:达标绿底/未达标红底
for mi, m in enumerate(range(1, settle_month+1)):
col = month_start_col + mi + 1
cell = ws.cell(row=rn, column=col)
cell.alignment = wrap_top
if month_qualified.get(m) == True:
cell.fill = green_fill; cell.font = green_font
elif month_qualified.get(m) == False:
cell.fill = red_fill; cell.font = red_font
# 累计列
if settle_month >= 2:
cum_cell = ws.cell(row=rn, column=cum_start_col+2) # 累计达标列
if cum_q:
cum_cell.fill = green_fill; cum_cell.font = Font(bold=True, color='006100')
elif cum_t > 0:
cum_cell.fill = red_fill; cum_cell.font = Font(bold=True, color='9C0006')
# 发放列:有发放金底
pay_cell = ws.cell(row=rn, column=pay_start_col+1)
pay_cell.alignment = wrap_top
if tp:
pay_cell.fill = gold_fill
# 奖金池:已发期数列
period_cell = ws.cell(row=rn, column=pay_start_col+2)
period_cell.alignment = center_top
if tp_count > 0:
period_cell.fill = blue_fill; period_cell.font = Font(bold=True)
rn+=1
# 列宽
col_widths = {'A':12,'B':20,'C':20,'D':12,'E':20,'F':8}
for col_letter, w in col_widths.items():
ws.column_dimensions[col_letter].width = w
# 月度考核列宽
for mi in range(settle_month):
col_letter = chr(ord('G') + mi)
ws.column_dimensions[col_letter].width = 30
# 剩余列自动
remaining_start = ord('G') + settle_month
for i in range(8):
cl = chr(remaining_start + i)
if cl <= 'Z':
ws.column_dimensions[cl].width = 16
# 自动筛选
ws.auto_filter.ref = f"A1:{chr(ord('A')+len(headers)-1)}1"