V2.0.0 按月拆分Excel + 业务员/车辆维度展示

重构为3文件架构:
- calc_engine.py: 计算引擎(规则/读取/分组/结转/补发/累计)
- excel_writer.py: Excel输出(所有sheet生成函数)
- main.py: 入口(按月循环生成独立文件)

输出3个独立Excel文件(1-3月各一个),每个17个sheet:
- 考核奖励规则 + 里程明细 + 计算过程 + 汇总(原有)
- 业务员_XXX × 12个(新增,按车分组展示每月考核+累计+发放+奖金池)
- 车辆考核追踪(新增,全量492辆,含每月业务员/里程/发放/奖金池状态)

计算逻辑不变,金额与V1.1.0一致:
1月21212.26, 2月21152.14, 3月56607.10, Q1总计98971.50

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-02 14:18:27 +08:00
parent 46fa8aea7a
commit 818aacb875
3 changed files with 598 additions and 0 deletions

184
calc_engine.py Normal file
View File

@@ -0,0 +1,184 @@
"""计算引擎:规则、读取、分组、结转/补发/累计逻辑"""
import openpyxl
from collections import defaultdict
RULES = {
'交投40辆4.5T普货': {'km': 3000, '': 150},
'交投190辆4.5T冷链车': {'km': 3000, '': 150},
'羚牛136辆4.5T冷链车': {'km': 5000, '': 260},
'恒运50辆4.5T普货': {'km': 5000, '': 260},
'恒运50辆4.5T 普货': {'km': 5000, '': 260},
'羚牛100辆18T': {'km': 6000, '': 1000},
}
DAYS = {1: 31, 2: 28, 3: 31}
def read_file(fp, month):
wb = openpyxl.load_workbook(fp, data_only=True)
ws = wb['业务考核视图']
h = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))]
recs = []
for row in ws.iter_rows(min_row=2, values_only=True):
r = dict(zip(h, row))
r['考核天数'] = float(r.get('考核天数') or 0)
r['应考核里程(km)'] = float(r.get('应考核里程(km)') or 0)
r['实际行驶里程(km)'] = float(r.get('实际行驶里程(km)') or 0)
r['月份'] = month
rule = RULES.get(r.get('考核目标', ''), {})
r['月度目标里程'] = rule.get('km', 0)
r['对应月奖励金额'] = rule.get('', 0)
t, a = r['应考核里程(km)'], r['实际行驶里程(km)']
q = a >= t and t > 0
r['是否达标'] = '达标' if q else '未达标'
r['奖金'] = r['对应月奖励金额'] * (r['考核天数'] / DAYS[month]) if q else 0
r['多跑'] = max(0, a - t) if q else 0
r['可结转'] = int(r['多跑'] // r['月度目标里程']) if r['月度目标里程'] > 0 and q else 0
recs.append(r)
wb.close()
return recs
def grp(recs, month):
gs = defaultdict(lambda: {'recs':[], '应考核':0, '实际':0, '奖金':0, '天数':0,
'有达标':False, '目标km':0, '奖励额':0})
for r in recs:
k = (r['车牌号'], r['销售经理'])
g = gs[k]
g['recs'].append(r)
g['应考核'] += r['应考核里程(km)']
g['实际'] += r['实际行驶里程(km)']
g['奖金'] += r['奖金']
g['天数'] += r['考核天数']
if r['是否达标'] == '达标': g['有达标'] = True
g['目标km'] = max(g['目标km'], r['月度目标里程'])
g['奖励额'] = max(g['奖励额'], r['对应月奖励金额'])
g['部门'] = r['部门名称']
g['销售'] = r['销售经理']
g['车牌'] = r['车牌号']
return dict(gs)
def calc_jan_carryover(G1):
for k, g in G1.items():
tc = 0
for r in g['recs']:
if r['是否达标'] == '达标': tc += r['可结转']
g['可结转'] = tc
def calc_feb(G1, G2):
feb_data = {'结转':[], '补发1月':[], '当月':[], '累计补发2月':[]}
for k, g2 in G2.items():
g1 = G1.get(k)
bf = g2['奖励额']
j_t = g1['应考核'] if g1 else 0; j_a = g1['实际'] if g1 else 0
j_q = g1['有达标'] if g1 else False; j_c = g1['可结转'] if g1 else 0
cum_t = j_t + g2['应考核']; cum_a = j_a + g2['实际']
cum_q = cum_a >= cum_t and cum_t > 0
carry = 0
if g1 and j_q and j_c >= 1:
carry = bf
feb_data['结转'].append({'车牌':k[0],'销售':g1['销售'],'部门':g2['部门'],'':carry})
bp1 = 0
if g1 and not j_q and cum_q:
bp1 = g1['奖励额'] * (g1['天数'] / 31)
feb_data['补发1月'].append({'车牌':k[0],'销售':g1['销售'],'部门':g2['部门'],'':bp1})
bonus2 = 0
if g2['有达标'] and carry == 0:
bonus2 = g2['奖金']
feb_data['当月'].append({'车牌':k[0],'销售':g2['销售'],'部门':g2['部门'],'':bonus2})
cbp2 = 0
if not g2['有达标'] and carry == 0 and cum_q:
cbp2 = g2['奖励额'] * (g2['天数'] / 28)
feb_data['累计补发2月'].append({'车牌':k[0],'销售':g2['销售'],'部门':g2['部门'],'':cbp2})
g2['cum_t']=cum_t; g2['cum_a']=cum_a; g2['cum_q']=cum_q
g2['结转']=carry; g2['补发1月']=bp1
g2['补发1月对应']=g1['销售'] if g1 and bp1>0 else ''
g2['当月奖金']=bonus2; g2['累计补发2月']=cbp2; g2['结转占位']=carry>0
fc = sum(r['可结转'] for r in g2['recs'] if r['是否达标']=='达标')
jr = max(0, j_c - (1 if carry>0 else 0))
g2['可结转'] = fc + jr
g2['2月已发'] = carry>0 or bonus2>0 or cbp2>0
g2['1月已补发'] = bp1>0
return feb_data
def calc_mar(G1, G2, G3, feb_data):
mar_data = {'结转':[], '补发1月':[], '补发2月':[], '当月':[], '累计补发3月':[]}
for k, g3 in G3.items():
g1 = G1.get(k); g2 = G2.get(k)
bf = g3['奖励额']
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
f_t=g2['应考核'] if g2 else 0; f_a=g2['实际'] if g2 else 0
f_paid=g2['2月已发'] if g2 else False; f_carry=g2['可结转'] if g2 else 0
if g1 and j_q: j_paid=True
if g2 and g2.get('1月已补发',False): j_paid=True
cum_t=j_t+f_t+g3['应考核']; cum_a=j_a+f_a+g3['实际']
cum_q=cum_a>=cum_t and cum_t>0
carry=0
if g2 and f_carry>=1:
carry=bf; mar_data['结转'].append({'车牌':k[0],'销售':g2['销售'],'部门':g3['部门'],'':carry})
bj=0
if g1 and not j_paid and cum_q:
bj=g1['奖励额']*(g1['天数']/31); mar_data['补发1月'].append({'车牌':k[0],'销售':g1['销售'],'部门':g3['部门'],'':bj})
bf2=0
if g2 and not f_paid and cum_q:
bf2=g2['奖励额']*(g2['天数']/28); mar_data['补发2月'].append({'车牌':k[0],'销售':g2['销售'],'部门':g3['部门'],'':bf2})
bonus3=0
if g3['有达标'] and carry==0:
bonus3=g3['奖金']; mar_data['当月'].append({'车牌':k[0],'销售':g3['销售'],'部门':g3['部门'],'':bonus3})
cbp3=0
if not g3['有达标'] and carry==0 and cum_q:
cbp3=g3['奖励额']*(g3['天数']/31); mar_data['累计补发3月'].append({'车牌':k[0],'销售':g3['销售'],'部门':g3['部门'],'':cbp3})
g3['cum_t']=cum_t; g3['cum_a']=cum_a; g3['cum_q']=cum_q
g3['结转']=carry; g3['补发1月']=bj; g3['补发1月对应']=g1['销售'] if g1 and bj>0 else ''
g3['补发2月']=bf2; g3['补发2月对应']=g2['销售'] if g2 and bf2>0 else ''
g3['当月奖金']=bonus3; g3['累计补发3月']=cbp3; g3['结转占位']=carry>0
return mar_data
def collect_vehicle_payments(G, feb_data, mar_data):
payments = defaultdict(list)
info = {}
for k, g in G[1].items():
plate = k[0]
if not info.get(plate):
first = g['recs'][0]
info[plate] = {'考核目标':first.get('考核目标',''),'月度奖励':g['奖励额'],'目标km':g['目标km']}
if g['奖金'] > 0:
payments[plate].append({'结算月':1,'对应考核月':1,'业务员':g['销售'],'金额':g['奖金'],'类型':'当月达标','部门':g['部门']})
cat_map = {
('结转',2):2, ('补发1月',2):1, ('当月',2):2, ('累计补发2月',2):2,
('结转',3):3, ('补发1月',3):1, ('补发2月',3):2, ('当月',3):3, ('累计补发3月',3):3,
}
for month, mdata in [(2, feb_data), (3, mar_data)]:
for cat, dl in mdata.items():
assess_month = cat_map.get((cat, month), month)
for d in dl:
payments[d['车牌']].append({'结算月':month,'对应考核月':assess_month,
'业务员':d['销售'],'金额':d[''],'类型':cat,'部门':d['部门']})
for m in G:
for k, g in G[m].items():
if not info.get(k[0]):
first = g['recs'][0]
info[k[0]] = {'考核目标':first.get('考核目标',''),'月度奖励':g['奖励额'],'目标km':g['目标km']}
for plate in payments:
records = sorted(payments[plate], key=lambda x: (x['对应考核月'], x['结算月']))
for i, r in enumerate(records): r['期数'] = i + 1
return payments, info
def read_master_vehicles(fp='里程任务考核_Q1汇总.xlsx'):
"""从现有Q1汇总文件读取全量车辆台账"""
wb = openpyxl.load_workbook(fp, data_only=True)
ws = wb['车辆奖金池总览']
h = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))]
vehicles = []
for row in ws.iter_rows(min_row=2, values_only=True):
r = dict(zip(h, row))
if r.get('车牌号'):
vehicles.append({'车牌号': r['车牌号'], '车架号': r.get('车架号',''),
'归属公司': r.get('归属公司',''), '车型确定': r.get('车型','')})
wb.close()
return vehicles