Files
mileage-bonus/excel_writer.py
kkfluous 5a69ef2993 V2.5.0 车辆追踪:多业务员拆行+车牌合并单元格
- 每个(车牌+业务员)独立一行,各自有独立的月度里程和累计
- 同车多人时车牌/车架号/归属公司等信息列合并单元格
- 奖金池(已发期数/金额/剩余)也合并(整车共享)
- 月度数据拆为应考核/实际/达标三列,不再挤在一个单元格
- 同人多条记录在达标列内换行显示各条

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

525 lines
26 KiB
Python
Raw Permalink 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, DAYS
center_top = Alignment(horizontal='center', vertical='top', wrap_text=True)
left_top = Alignment(vertical='top', wrap_text=True)
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 = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励','业务员']
info_cols = 6 # 前6列是车辆信息需要合并
for m in range(1, settle_month+1):
headers += [f'{m}月应考核', f'{m}月实际', f'{m}月达标']
if settle_month >= 2:
headers += ['累计应完成','累计实际','累计达标']
headers += ['本月发放金额','发放类型','已发期数','已发金额','剩余期数']
WH(ws, headers)
ws.freeze_panes = 'H2' # 冻结车辆信息+业务员列
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
# 收集该车所有(业务员) - 跨月去重
person_set = {}
for m in range(1, settle_month+1):
for k,g in G.get(m, {}).items():
if k[0] == plate:
person_set[k[1]] = g['部门']
persons = sorted(person_set.keys())
if not persons:
persons = [''] # 无考核记录也占一行
start_rn = rn
for pi, person in enumerate(persons):
sd = person_set.get(person,'').replace('业务','') if person else ''
person_label = f'{sd}-{person}' if person and sd else person
# 车辆信息(只在第一行写,后面留空等合并)
if pi == 0:
row_base = [plate, mv.get('车架号',''), mv.get('归属公司',''),
mv.get('车型确定',''), target_name or '', monthly_bonus or '']
else:
row_base = ['','','','','','']
row = row_base + [person_label]
# 每月数据按该业务员的group
cum_t = 0; cum_a = 0
for m in range(1, settle_month+1):
gm = G.get(m, {}).get((plate, person)) if person else None
if gm:
# 逐条记录汇总(同人同月可能多条)
t_sum = gm['应考核']; a_sum = gm['实际']
cum_t += t_sum; cum_a += a_sum
# 每条记录的达标情况
rec_details = []
for rec in gm['recs']:
rq = rec['是否达标'] == '达标'
rec_details.append(f"{R(rec['实际行驶里程(km)'],0)}/{R(rec['应考核里程(km)'],0)}{'' if rq else ''}")
all_q = all(rec['是否达标'] == '达标' for rec in gm['recs'])
if len(gm['recs']) == 1:
detail = rec_details[0].split('')[0].split('')[0] # 只取数字
row += [R(t_sum), R(a_sum), '' if all_q else '']
else:
# 多条:显示汇总,但单元格内注明各条
row += [R(t_sum), R(a_sum), '\n'.join(rec_details)]
else:
row += ['', '', '']
# 累计
if settle_month >= 2:
if cum_t > 0:
cum_q = cum_a >= cum_t
row += [R(cum_t), R(cum_a), '' if cum_q else '']
else:
row += ['', '', '']
cum_q = False
else:
cum_q = False
# 本月发放(该业务员的)
tp = [p for p in pays if p['结算月'] == settle_month and p['业务员'] == person]
if tp:
amt = sum(p['金额'] for p in tp)
types = ', '.join(p['类型'] for p in tp)
row += [R(amt), types]
else:
row += [0, '']
# 奖金池(整车,只在第一行显示)
if pi == 0:
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]
else:
row += ['', '', '']
WR(ws, rn, row)
# 美化
# 车辆信息列灰底
for ci in range(1, info_cols + 1):
ws.cell(row=rn, column=ci).fill = grey_fill
# 月度达标列着色
for m in range(1, settle_month + 1):
col_base = info_cols + 1 + (m - 1) * 3 # 业务员列后面
qual_col = col_base + 3 # 达标列
cell = ws.cell(row=rn, column=qual_col)
cell.alignment = center_top
val = cell.value
if val and '' in str(val) and '' not in str(val):
cell.fill = green_fill; cell.font = green_font
elif val and '' in str(val):
cell.fill = red_fill; cell.font = red_font
# 累计达标着色
if settle_month >= 2:
cum_qual_col = info_cols + 1 + settle_month * 3 + 3 # 累计达标列
cell = ws.cell(row=rn, column=cum_qual_col)
if cell.value == '':
cell.fill = green_fill; cell.font = Font(bold=True, color='006100')
elif cell.value == '':
cell.fill = red_fill; cell.font = Font(bold=True, color='9C0006')
# 发放金底
pay_col = info_cols + 1 + settle_month * 3 + (3 if settle_month >= 2 else 0) + 1
if tp:
ws.cell(row=rn, column=pay_col).fill = gold_fill
# 奖金池蓝底
if pi == 0 and tp_count > 0:
pool_col = pay_col + 2
ws.cell(row=rn, column=pool_col).fill = blue_fill
ws.cell(row=rn, column=pool_col).font = Font(bold=True)
rn += 1
# 合并车辆信息列(如果多人)
if len(persons) > 1:
for ci in range(1, info_cols + 1):
ws.merge_cells(start_row=start_rn, start_column=ci,
end_row=start_rn + len(persons) - 1, end_column=ci)
ws.cell(row=start_rn, column=ci).alignment = Alignment(vertical='center', wrap_text=True)
# 奖金池列也合并
for offset in [2, 3, 4]: # 已发期数/已发金额/剩余期数
pool_col = pay_col + offset
ws.merge_cells(start_row=start_rn, start_column=pool_col,
end_row=start_rn + len(persons) - 1, end_column=pool_col)
# 列宽
col_widths = {'A':12,'B':18,'C':18,'D':10,'E':18,'F':8,'G':14}
for cl, w in col_widths.items():
ws.column_dimensions[cl].width = w
# 月度列
start = ord('H')
for m in range(settle_month):
for i in range(3):
cl = chr(start + m*3 + i)
if cl <= 'Z': ws.column_dimensions[cl].width = 12
# 剩余列
rem = start + settle_month * 3
for i in range(10):
cl = chr(rem + i)
if cl <= 'Z': ws.column_dimensions[cl].width = 14