4 Commits

Author SHA1 Message Date
kkfluous
e47cc00b3d V2.3.0 车辆考核追踪:多人多条用单元格内换行显示
同车多个销售经理在同一单元格内换行展示:
- X月考核明细: "刘念念: 4834/2903 达标\n董剑煜: 1294/2710 未达标"
- 本月发放明细: "赵连飞: 150(结转)\n董剑煜: 260(当月达标)"
设置wrap_text自动换行,一车一行不拆分。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:51:16 +08:00
kkfluous
4273592160 V2.2.0 业务员sheet重写为对账单风格
每辆车展示:
- 第1行:车辆信息 + 各月里程/目标 + 累计 + 达标状态
- 下方:历史已发(哪月发的、金额、来源)+ 本月发放明细 + 奖金池
- "历史已发"列清楚展示防重复证据
- 发放说明包含关键数字(累计里程≥目标等)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:33:04 +08:00
kkfluous
1b7d25c821 fix: 业务员sheet奖金池期数按当前核算月过滤
之前vehicle_payments是Q1全量,导致1月文件显示了3个月的累计期数。
改为只统计截至settle_month的发放记录。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:41:34 +08:00
kkfluous
95f1685612 V2.1.0 业务员sheet增加"发放说明"列
将技术化的"发放类型"改为动态生成的"发放说明",用一句话解释为什么发/不发:
- 当月达标 → "当月达标"
- 结转 → "1月多跑9578≥3000,结转(完整月奖金)"
- 累计补发 → "1-2月累计10620≥10000,累计达标补发"
- 未达标 → "未达标(实际1486<目标5000)"
- 补发过去月 → "1-3月累计14427≥10161,补发1月"

包含关键数字,非技术人员一眼看懂计算依据。

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

View File

@@ -187,87 +187,148 @@ def write_summary_month(wb, month, month_data, section_names):
def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, vehicle_payments):
ws = wb.create_sheet(f'业务员_{person}')
ws.cell(row=1,column=1,value=f'{person} | {dept} | {settle_month}考核').font=Font(bold=True,size=14)
ws.cell(row=1,column=1,value=f'{person} | {dept} | {settle_month}绩效对账单').font=Font(bold=True,size=14)
# 收集该人在1-settle_month中有记录的所有车牌
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)
rn = 4
# 表头:车辆信息 + 各月里程/目标 + 累计 + 奖金池
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):
# 车辆信息取最近月的group
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']
ws.cell(row=rn,column=1,value=plate).font=Font(bold=True,size=11)
ws.cell(row=rn,column=2,value=first.get('合同编号',''))
ws.cell(row=rn,column=3,value=first.get('客户名称',''))
ws.cell(row=rn,column=4,value=first.get('考核目标',''))
ws.cell(row=rn,column=5,value=f'月度目标{g_cur["目标km"]}km | 月奖励{g_cur["奖励额"]}')
rn += 1
# 表头
headers = ['月份','考核天数','应考核里程','实际里程','完成率','达标','本月奖金','发放类型']
if settle_month >= 2:
headers += ['累计应完成','累计实际','累计达标']
WH(ws, headers, rn); rn += 1
# 每月一行
# 第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 not gm:
WR(ws, rn, [f'{m}','','','','','无记录']); rn+=1; continue
if gm:
t=gm['应考核']; a=gm['实际']; cum_t+=t; cum_a+=a
qual='达标' if gm['有达标'] else '未达标'
rate=R(a/t*100,0) if t>0 else 0
# 发放类型和金额
pay_type=''; pay_amt=0
if m == settle_month:
carry=gm.get('结转',0); bonus=gm.get('当月奖金',0)
cum_bp=gm.get(f'累计补发{m}',0)
if carry>0: pay_type='结转'; pay_amt=carry
elif bonus>0: pay_type='当月达标'; pay_amt=bonus
elif cum_bp>0: pay_type=f'累计补发'; pay_amt=cum_bp
row.append(f'{R(a,0)}/{R(t,0)}')
else:
if gm['有达标']: pay_type='(已发)'; pay_amt=gm.get('奖金',0)
else: pay_type='(未达标)'
row_data=[f'{m}',gm['天数'],R(t),R(a),f'{rate}%',qual,R(pay_amt) if pay_amt>0 else 0,pay_type]
row.append('-')
if settle_month >= 2:
cum_q='达标' if (cum_a>=cum_t and cum_t>0) else '未达标'
row_data+=[R(cum_t),R(cum_a),cum_q]
WR(ws,rn,row_data); rn+=1
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
# 补发行(当前月补发之前月份
if settle_month >= 2:
# 第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 isinstance(g_s, dict) else 0
bp_amt = g_s.get(bp_key, 0)
if bp_amt > 0:
row_data = [f'补发{prev_m}','','','','','',R(bp_amt),f'补发{prev_m}']
if settle_month>=2: row_data += ['','','']
WR(ws,rn,row_data); rn+=1
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
# 奖金池
pays = vehicle_payments.get(plate, [])
total_periods = len(pays)
plate_this = sum(p['金额'] for p in pays if p['结算月']==settle_month and p['业务员']==person)
ws.cell(row=rn,column=1,value=f'小计: {R(plate_this)}元 | 奖金池: 已发{total_periods}期/共12期, 剩余{12-total_periods}').font=Font(italic=True)
rn += 2
# 当月
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)
@@ -279,12 +340,15 @@ def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, ve
def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info):
ws = wb.create_sheet('车辆考核追踪')
wrap_align = Alignment(wrap_text=True, vertical='top')
# 表头:每月用一列"业务员/里程/目标/达标"(多人换行)
headers = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励金额']
for m in range(1, settle_month+1):
headers += [f'{m}月业务员',f'{m}考核',f'{m}月实际',f'{m}月达标']
headers.append(f'{m}月考核明细')
if settle_month >= 2:
headers += ['累计应完成','累计实际','累计达标']
headers += ['本月发放金额','发放给谁','发放类型','累计已发期数','累计已发金额','剩余可发期数']
headers += ['累计里程/目标','累计达标']
headers += ['本月发放明细','累计已发期数','累计已发金额','剩余可发期数']
WH(ws, headers)
rn=2
@@ -294,31 +358,46 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p
pays=vehicle_payments.get(plate, [])
row=[plate,mv.get('车架号',''),mv.get('归属公司',''),mv.get('车型确定',''),
info.get('考核目标',''),info.get('月度奖励',0) or '']
cum_t=0; cum_a=0
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:
persons=', '.join(sorted(set(g['销售'] for _,g in mgs)))
ts=sum(g['应考核'] for _,g in mgs); As=sum(g['实际'] for _,g in mgs)
q='达标' if any(g['有达标'] for _,g in mgs) else '未达标'
cum_t+=ts; cum_a+=As
row+=[persons,R(ts),R(As),q]
# 每个(车牌,销售)组一行,换行显示
lines = []
for _,g in sorted(mgs, key=lambda x: x[0][1]):
t=g['应考核']; a=g['实际']
cum_t+=t; cum_a+=a
q='达标' if g['有达标'] else '未达标'
lines.append(f"{g['销售']}: {R(a,0)}/{R(t,0)} {q}")
row.append('\n'.join(lines))
else:
row+=['','','','']
if settle_month>=2:
row+=[R(cum_t),R(cum_a),'达标' if (cum_a>=cum_t and cum_t>0) else '未达标']
row.append('')
# 本月发放(截至settle_month
if settle_month>=2:
cum_q='达标' if (cum_a>=cum_t and cum_t>0) else '未达标'
row+=[f'{R(cum_a,0)}/{R(cum_t,0)}', cum_q]
# 本月发放明细(多人多类型换行)
tp=[p for p in pays if p['结算月']==settle_month]
if tp:
row+=[R(sum(p['金额'] for p in tp)),', '.join(sorted(set(p['业务员'] for p in tp))),
', '.join(sorted(set(p['类型'] for p in tp)))]
pay_lines = []
for p in sorted(tp, key=lambda x: x['业务员']):
pay_lines.append(f"{p['业务员']}: {R(p['金额'])}({p['类型']})")
row.append('\n'.join(pay_lines))
else:
row+=[0,'','']
row.append('')
# 奖金池截至settle_month
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+=[tp_count,R(tp_amt) if tp_amt>0 else 0,12-tp_count]
WR(ws,rn,row); rn+=1
WR(ws,rn,row)
# 对含换行的单元格设置自动换行
for ci in range(len(row)):
cell = ws.cell(row=rn, column=ci+1)
if isinstance(cell.value, str) and '\n' in cell.value:
cell.alignment = wrap_align
rn+=1
AW(ws)