304 lines
13 KiB
Python
304 lines
13 KiB
Python
"""计算引擎:规则、读取、分组、结转/补发/累计逻辑"""
|
||
import openpyxl
|
||
from collections import defaultdict
|
||
|
||
# RULES: 18T用(1000/0/1000/1000), 非18T保持原逻辑(未达标不发, 结转补发=原奖励额)
|
||
RULES = {
|
||
'交投40辆4.5T普货': {'km': 3000, 'good': 150, 'bad': 0, 'carry': 150, 'other': 150},
|
||
'交投190辆4.5T冷链车': {'km': 3000, 'good': 150, 'bad': 0, 'carry': 150, 'other': 150},
|
||
'羚牛136辆4.5T冷链车': {'km': 5000, 'good': 260, 'bad': 0, 'carry': 260, 'other': 260},
|
||
'恒运50辆4.5T普货': {'km': 5000, 'good': 260, 'bad': 0, 'carry': 260, 'other': 260},
|
||
'恒运50辆4.5T 普货': {'km': 5000, 'good': 260, 'bad': 0, 'carry': 260, 'other': 260},
|
||
'羚牛100辆18T': {'km': 6000, 'good': 1000, 'bad': 0, 'carry': 1000, 'other': 1000},
|
||
}
|
||
|
||
# 2026年7月经业务确认的4辆18T执行下半年新政策:
|
||
# 月目标5000公里,达标150元,未达标不发,结转/补发按150元基准。
|
||
# 其余18T继续使用上面的原口径。
|
||
JULY_SCHEME3_PLATES = {'粤AGR0772', '粤AGQ0178', '粤AGP6675', '粤AGP9757'}
|
||
JULY_SCHEME3_RULE = {'km': 5000, 'good': 150, 'bad': 0, 'carry': 150, 'other': 150}
|
||
DAYS = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 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
|
||
if month == 7 and str(r.get('车牌号') or '').strip().upper() in JULY_SCHEME3_PLATES:
|
||
rule = JULY_SCHEME3_RULE
|
||
else:
|
||
rule = RULES.get(r.get('考核目标', ''), {'km': 0, 'good': 0, 'bad': 0, 'carry': 0, 'other': 0})
|
||
r['月度目标里程'] = rule.get('km', 0)
|
||
r['奖励基准_达标'] = rule.get('good', 0)
|
||
r['奖励基准_未达标'] = rule.get('bad', 0)
|
||
r['奖励基准_结转'] = rule.get('carry', 0)
|
||
r['奖励基准_补发'] = rule.get('other', 0)
|
||
t, a = r['应考核里程(km)'], r['实际行驶里程(km)']
|
||
q = a >= t and t > 0
|
||
r['是否达标'] = '达标' if q else '未达标'
|
||
if q:
|
||
r['奖金'] = r['奖励基准_达标'] * (r['考核天数'] / DAYS[month])
|
||
else:
|
||
r['奖金'] = r['奖励基准_未达标'] * (r['考核天数'] / DAYS[month])
|
||
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, '奖励未达标':0,
|
||
'奖励结转':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['奖励未达标'] = max(g['奖励未达标'], r['奖励基准_未达标'])
|
||
g['奖励结转'] = max(g['奖励结转'], 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
|
||
g['累计已发结转'] = 0
|
||
g['1月已发'] = g['有达标'] # 当月有达标 → 已发
|
||
g['cum_t'] = g['应考核']
|
||
g['cum_a'] = g['实际']
|
||
g['cum_q'] = g['实际'] >= g['应考核'] and g['应考核'] > 0
|
||
|
||
def calc_month(month, G_all, all_data, G_prev, G_cur):
|
||
"""通用月计算: 结转/补发/累计补发/当月"""
|
||
prev_m = month - 1
|
||
days_m = DAYS[month]
|
||
data_m = {'结转': [], '当月': []}
|
||
# 向后兼容的key: 当月达标/当月未达标 用于18T区分
|
||
data_18t = {'当月达标': [], '当月未达标': []}
|
||
use_18t = True # 统一使用拆分标签
|
||
|
||
for k, gm in G_cur.items():
|
||
gp = G_prev.get(k)
|
||
|
||
# 累计
|
||
cum_t = (gp['cum_t'] if gp else 0) + gm['应考核']
|
||
cum_a = (gp['cum_a'] if gp else 0) + gm['实际']
|
||
cum_q = cum_a >= cum_t and cum_t > 0
|
||
|
||
# 结转: carry × 1 (整月, 不折算)
|
||
carry = 0
|
||
p_carry = gp['可结转'] if gp else 0
|
||
if gp and p_carry >= 1:
|
||
carry = gm['奖励结转']
|
||
data_m['结转'].append({'车牌': k[0], '销售': gp['销售'], '部门': gm['部门'], '额': carry})
|
||
|
||
# 当月奖金
|
||
is_18t = gm['奖励达标'] == 1000 # 18T特征
|
||
bonus = 0
|
||
if carry == 0 and gm['奖金'] > 0:
|
||
bonus = gm['奖金']
|
||
data_m['当月'].append({'车牌': k[0], '销售': gm['销售'], '部门': gm['部门'], '额': bonus})
|
||
|
||
# 补发历史月: other × 折算
|
||
for back_m in range(1, month):
|
||
label = f'补发{back_m}月'
|
||
if label not in data_m:
|
||
data_m[label] = []
|
||
g_bm = G_all[back_m].get(k)
|
||
if not g_bm:
|
||
continue
|
||
bm_paid = g_bm.get(f'{back_m}月已发', g_bm['有达标'])
|
||
if bm_paid:
|
||
continue
|
||
bm_paid_later = False
|
||
for later_m in range(back_m + 1, month):
|
||
gl = G_all[later_m].get(k)
|
||
if gl and gl.get(f'补发{back_m}月', 0) > 0:
|
||
bm_paid_later = True
|
||
break
|
||
if bm_paid_later:
|
||
continue
|
||
if cum_q:
|
||
bj = g_bm['奖励补发'] * (g_bm['天数'] / DAYS[back_m])
|
||
data_m[label].append({'车牌': k[0], '销售': g_bm['销售'], '部门': gm['部门'], '额': bj})
|
||
gm[f'补发{back_m}月'] = bj
|
||
|
||
# 累计补发当月: other × 折算
|
||
label_cbp = f'累计补发{month}月'
|
||
if label_cbp not in data_m:
|
||
data_m[label_cbp] = []
|
||
cbp = 0
|
||
if not gm['有达标'] and carry == 0 and cum_q:
|
||
cbp = gm['奖励补发'] * (gm['天数'] / days_m)
|
||
data_m[label_cbp].append({'车牌': k[0], '销售': gm['销售'], '部门': gm['部门'], '额': cbp})
|
||
|
||
gm['cum_t'] = cum_t
|
||
gm['cum_a'] = cum_a
|
||
gm['cum_q'] = cum_q
|
||
gm['结转'] = carry
|
||
gm['当月奖金'] = bonus
|
||
gm['累计补发当月'] = cbp
|
||
gm[f'{month}月已发'] = carry > 0 or gm['奖金'] > 0 or cbp > 0
|
||
|
||
# 更新可结转
|
||
cum_over = max(0, cum_a - cum_t) if cum_q else 0
|
||
earned = int(cum_over // gm['目标km']) if gm['目标km'] > 0 else 0
|
||
prev_used = gp['累计已发结转'] if gp else 0
|
||
cur_used = prev_used + (1 if carry > 0 else 0)
|
||
gm['累计已发结转'] = cur_used
|
||
gm['可结转'] = max(0, earned - cur_used)
|
||
|
||
# 补充: 上月有结转但本月无考核记录的车
|
||
for k, gp in G_prev.items():
|
||
if k not in G_cur and gp.get('可结转', 0) >= 1:
|
||
carry = gp['奖励结转']
|
||
data_m['结转'].append({'车牌': k[0], '销售': gp['销售'], '部门': gp['部门'], '额': carry})
|
||
G_cur[k] = {
|
||
'recs': [], '应考核': gp['目标km'], '实际': 0, '奖金': 0, '天数': days_m,
|
||
'有达标': False, '目标km': gp['目标km'],
|
||
'奖励达标': gp['奖励达标'], '奖励未达标': gp['奖励未达标'],
|
||
'奖励结转': gp['奖励结转'], '奖励补发': gp['奖励补发'],
|
||
'部门': gp['部门'], '销售': gp['销售'], '车牌': k[0],
|
||
'cum_t': gp.get('cum_t', 0) + gp['目标km'], 'cum_a': gp.get('cum_a', 0),
|
||
'cum_q': False,
|
||
'结转': carry, '当月奖金': 0,
|
||
f'{month}月已发': True,
|
||
'可结转': max(0, gp['可结转'] - 1),
|
||
'累计已发结转': gp.get('累计已发结转', 0) + 1,
|
||
'虚拟': True,
|
||
}
|
||
|
||
all_data[month] = data_m
|
||
return data_m, G_cur
|
||
|
||
|
||
def collect_vehicle_payments(G, all_data):
|
||
"""收集所有月的付款记录"""
|
||
payments = defaultdict(list)
|
||
info = {}
|
||
|
||
# 1月
|
||
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['部门']})
|
||
|
||
# 2月-7月
|
||
cat_map = {}
|
||
for month in range(2, 8):
|
||
cat_map[('结转', month)] = month
|
||
for bm in range(1, month):
|
||
cat_map[(f'补发{bm}月', month)] = bm
|
||
cat_map[('当月', month)] = month
|
||
cat_map[(f'累计补发{month}月', month)] = month
|
||
|
||
for month in range(2, 8):
|
||
mdata = all_data.get(month, {})
|
||
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] if g['recs'] else None
|
||
if first:
|
||
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
|
||
|
||
|
||
# (归属公司+车型) → 考核目标 映射,用于补全无考核记录的车辆
|
||
VEHICLE_TARGET_MAP = {
|
||
('广州开发区交投氢能运营管理有限公司', '4.5吨冷链车'): ('交投190辆4.5T冷链车', 3000, 150),
|
||
('广州开发区交投氢能运营管理有限公司', '4.5吨货车'): ('交投40辆4.5T普货', 3000, 150),
|
||
('羚牛氢能科技(广东)有限公司', '4.5吨冷链车'): ('羚牛136辆4.5T冷链车', 5000, 260),
|
||
('羚牛氢能科技(广东)有限公司', '18吨双飞翼货车'): ('羚牛100辆18T', 6000, 1000),
|
||
('现代氢能科技(广州)有限公司', '4.5吨货车'): ('恒运50辆4.5T普货', 5000, 260),
|
||
('现代氢能科技(广州)有限公司', '4.5吨货车'): ('恒运50辆4.5T普货', 5000, 260),
|
||
}
|
||
|
||
|
||
def read_loss_data(month):
|
||
"""读取亏损表,返回 {客户名称: '是'/'否'} 字典。无亏损表返回None"""
|
||
import os
|
||
fp = f'{month}月.xlsx'
|
||
if not os.path.exists(fp):
|
||
return None
|
||
|
||
wb = openpyxl.load_workbook(fp, data_only=True)
|
||
ws = wb[wb.sheetnames[0]]
|
||
h = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))]
|
||
|
||
client_col = None; loss_col = None
|
||
for i, col_name in enumerate(h):
|
||
if col_name and ('项目' in str(col_name) or '客户' in str(col_name)):
|
||
client_col = i
|
||
if col_name and '亏损' in str(col_name):
|
||
loss_col = i
|
||
|
||
if client_col is None or loss_col is None:
|
||
wb.close()
|
||
return None
|
||
|
||
result = {}
|
||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||
client = row[client_col]
|
||
loss = row[loss_col]
|
||
if client:
|
||
result[str(client).strip()] = str(loss).strip() if loss else ''
|
||
wb.close()
|
||
return result
|
||
|
||
|
||
def get_vehicle_client_map(D):
|
||
"""从考核源数据构建 {车牌号: 客户名称} 映射(取最新月的客户名)"""
|
||
plate_client = {}
|
||
for m in sorted(D.keys()):
|
||
for r in D[m]:
|
||
plate_client[r['车牌号']] = r.get('客户名称', '')
|
||
return plate_client
|
||
|
||
|
||
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
|