Files

370 lines
14 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.
#!/usr/bin/env python3
"""
6月 18T 绩效考核 — 方案3 (800/300 当月, 800结转, 500补发/累计)
"""
import os, openpyxl, unicodedata
from collections import defaultdict
os.chdir('/Users/kkfluous/Downloads')
DAYS = {1:31, 2:28, 3:31, 4:30, 5:31, 6:30}
TARGET_KM = 6000
BASE_GOOD = 800 # 当月达标
BASE_BAD = 300 # 当月未达标
BASE_CARRY = 800 # 结转
BASE_OTHER = 500 # 补发/累计补发
OUTPUT_DIR = '/Users/kkfluous/Downloads/6月18T测算'
# ========== 数据读取 ==========
def read_18t(filepath, month):
wb = openpyxl.load_workbook(filepath, 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))
if r.get('考核目标') != '羚牛100辆18T':
continue
r['考核天数'] = float(r['考核天数'] or 0)
r['应考核里程(km)'] = float(r['应考核里程(km)'] or 0)
r['实际行驶里程(km)'] = float(r['实际行驶里程(km)'] or 0)
r['月份'] = month
t, a = r['应考核里程(km)'], r['实际行驶里程(km)']
q = a >= t and t > 0
r['是否达标'] = '达标' if q else '未达标'
r['多跑'] = max(0, a - t) if q else 0
r['可结转'] = int(r['多跑'] // TARGET_KM) if q else 0
recs.append(r)
wb.close()
return recs
def grp(recs):
gs = defaultdict(lambda: {
'recs': [], '应考核': 0, '实际': 0, '奖金': 0, '天数': 0,
'有达标': False, '部门': '', '销售': '', '车牌': ''
})
for r in recs:
k = (r['车牌号'], r['销售经理'])
g = gs[k]
g['recs'].append(r)
g['应考核'] += r['应考核里程(km)']
g['实际'] += r['实际行驶里程(km)']
g['奖金'] += r.get('奖金', 0)
g['天数'] += r['考核天数']
if r['是否达标'] == '达标':
g['有达标'] = True
g['部门'] = r['部门名称']
g['销售'] = r['销售经理']
g['车牌'] = r['车牌号']
return dict(gs)
# ========== 计算 ==========
def calc():
D = {}
for m in range(1, 7):
fp = f'租赁任务考核_2026年{m}月.xlsx'
if os.path.exists(fp):
D[m] = read_18t(fp, m)
# 给每条记录算奖金
for m in D:
for r in D[m]:
t, a = r['应考核里程(km)'], r['实际行驶里程(km)']
q = a >= t and t > 0
r['是否达标'] = '达标' if q else '未达标'
if q:
r['奖金'] = BASE_GOOD * (r['考核天数'] / DAYS[m])
else:
r['奖金'] = BASE_BAD * (r['考核天数'] / DAYS[m])
r['多跑'] = max(0, a - t) if q else 0
r['可结转'] = int(r['多跑'] // TARGET_KM) if q else 0
G = {m: grp(D[m]) for m in D}
# 1月: 初始化
for k, g in G[1].items():
cum_q = g['实际'] >= g['应考核'] and g['应考核'] > 0
cum_over = max(0, g['实际'] - g['应考核']) if cum_q else 0
g['可结转'] = int(cum_over // TARGET_KM) if TARGET_KM > 0 else 0
g['累计已发结转'] = 0
g['1月已发'] = g['有达标']
g['cum_t'] = g['应考核']
g['cum_a'] = g['实际']
g['cum_q'] = cum_q
# 逐月计算 2→6
for month in range(2, 7):
if month not in G:
continue
prev_m = month - 1
days_m = DAYS[month]
for k, gm in G[month].items():
gp = G[prev_m].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
# 结转: 800
carry = 0
p_carry = gp['可结转'] if gp else 0
if gp and p_carry >= 1:
carry = BASE_CARRY
# 当月奖金
gm['当月奖金'] = gm['奖金'] if carry == 0 else 0
# 补发历史月: 500
for back_m in range(1, month):
g_bm = G[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[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 = BASE_OTHER * (g_bm['天数'] / DAYS[back_m])
gm[f'补发{back_m}月'] = bj
# 累计补发当月: 500
cbp = 0
if not gm['有达标'] and carry == 0 and cum_q:
cbp = BASE_OTHER * (gm['天数'] / days_m)
gm['累计补发当月'] = cbp
gm['cum_t'] = cum_t
gm['cum_a'] = cum_a
gm['cum_q'] = cum_q
gm['结转'] = carry
gm[f'{month}月已发'] = carry > 0 or gm['有达标'] or cbp > 0
# 更新可结转
cum_over = max(0, cum_a - cum_t) if cum_q else 0
earned = int(cum_over // TARGET_KM) if TARGET_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_m].items():
if k not in G[month] and gp.get('可结转', 0) >= 1:
G[month][k] = {
'recs': [], '应考核': TARGET_KM, '实际': 0, '奖金': 0, '天数': days_m,
'有达标': False, '部门': gp['部门'], '销售': gp['销售'], '车牌': k[0],
'cum_t': gp.get('cum_t', 0) + TARGET_KM, 'cum_a': gp.get('cum_a', 0),
'cum_q': False,
'结转': BASE_CARRY, '当月奖金': 0,
f'{month}月已发': True,
'可结转': max(0, gp['可结转'] - 1),
'累计已发结转': gp.get('累计已发结转', 0) + 1,
'虚拟': True,
}
# 收集6月发放记录
pay_records = collect_payments(G, 6)
return D[6], G[6], pay_records
def collect_payments(G, month):
G_m = G[month]
records = []
for k, g in sorted(G_m.items()):
if g.get('虚拟'):
continue
carry = g.get('结转', 0)
if carry > 0:
records.append({'车牌': k[0], '销售': k[1], '部门': g['部门'],
'金额': carry, '类型': '结转(800)'})
bonus = g.get('当月奖金', 0)
if bonus > 0:
ptype = '当月达标(800)' if g['有达标'] else '当月未达标(300)'
records.append({'车牌': k[0], '销售': k[1], '部门': g['部门'],
'金额': bonus, '类型': ptype})
cbp = g.get('累计补发当月', 0)
if cbp > 0:
records.append({'车牌': k[0], '销售': k[1], '部门': g['部门'],
'金额': cbp, '类型': f'累计补发{month}月(500)'})
for bm in range(1, month):
bv = g.get(f'补发{bm}月', 0)
if bv > 0:
records.append({'车牌': k[0], '销售': k[1], '部门': g['部门'],
'金额': bv, '类型': f'补发{bm}月(500)'})
records.sort(key=lambda x: (x['车牌'], x['类型']))
return records
# ========== Excel输出 ==========
def set_widths(ws, widths):
for i, w in enumerate(widths, 1):
ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = w
def write_xlsx(recs_6, G6, pay_records, filepath):
wb = openpyxl.Workbook()
# Sheet 1: 规则
ws = wb.active
ws.title = '考核规则'
ws.append(['6月 18T 绩效考核 — 方案3'])
ws.append(['考核目标', '羚牛100辆18T'])
ws.append(['月度目标里程', '6000km'])
ws.append(['当月达标', '800元 × (考核天数/30天)'])
ws.append(['当月未达标', '300元 × (考核天数/30天)'])
ws.append(['结转奖励', '800元/次 (消耗1个可结转月)'])
ws.append(['补发历史月', '500元 × (历史月天数/历史总天数)'])
ws.append(['累计补发当月', '500元 × (当月天数/30天)'])
ws.append(['规则', '当月达标800/未达标300 → 结转800 → 补发500 → 累计补发500'])
ws.append(['亏损筛选', '6月无盈亏表,不管亏损全发'])
set_widths(ws, [30, 60])
# Sheet 2: 里程明细
ws2 = wb.create_sheet('6月里程明细')
ws2.append(['车牌号', '销售经理', '部门', '客户', '考核天数', '应考核里程', '实际里程', '是否达标', '月度目标'])
for r in recs_6:
ws2.append([r['车牌号'], r['销售经理'], r.get('部门名称', ''),
r.get('客户名称', ''), r['考核天数'], r['应考核里程(km)'],
r['实际行驶里程(km)'], r['是否达标'], TARGET_KM])
set_widths(ws2, [14, 10, 30, 35, 10, 12, 12, 10, 10])
# Sheet 3: 计算过程
ws3 = wb.create_sheet('6月计算过程')
ws3.append(['车牌号', '销售经理', '部门', '6月应考核', '6月实际', '6月达标',
'累计应考核', '累计实际', '累计达标', '可结转', '结转发放(800)',
'当月奖金', '累计补发6月(500)', '补发1月', '补发2月', '补发3月', '补发4月', '补发5月',
'6月总发放'])
for k, g in sorted(G6.items()):
if g.get('虚拟'):
continue
cum_q_str = '是' if g.get('cum_q') else '否'
dq_str = '是' if g['有达标'] else '否'
total = (g.get('结转', 0) + g.get('当月奖金', 0) + g.get('累计补发当月', 0) +
sum(g.get(f'补发{m}月', 0) for m in range(1, 6)))
ws3.append([k[0], k[1], g['部门'], g['应考核'], g['实际'], dq_str,
g.get('cum_t', ''), g.get('cum_a', ''), cum_q_str,
g.get('可结转', 0), g.get('结转', 0),
g.get('当月奖金', 0), g.get('累计补发当月', 0),
g.get('补发1月', 0), g.get('补发2月', 0), g.get('补发3月', 0),
g.get('补发4月', 0), g.get('补发5月', 0), total])
set_widths(ws3, [14, 10, 30, 12, 12, 10, 12, 12, 10, 10, 14, 12, 16, 10, 10, 10, 10, 10, 14])
# Sheet 4: 发放明细
ws4 = wb.create_sheet('6月发放明细')
ws4.append(['车牌号', '销售经理', '部门', '金额', '发放类型'])
for r in pay_records:
ws4.append([r['车牌'], r['销售'], r['部门'], round(r['金额'], 2), r['类型']])
total = sum(r['金额'] for r in pay_records)
ws4.append([])
ws4.append(['', '', '合计', round(total, 2), ''])
set_widths(ws4, [14, 10, 30, 12, 22])
# Sheet 5: 汇总
ws5 = wb.create_sheet('汇总')
ws5.append(['6月18T 方案3 汇总 (800/300, 结转800, 补发500)'])
ws5.append(['发放类型', '辆数', '金额合计'])
cat_sum, cat_cnt = defaultdict(float), defaultdict(int)
for r in pay_records:
cat_sum[r['类型']] += r['金额']
cat_cnt[r['类型']] += 1
all_cats = ['当月达标(800)', '当月未达标(300)', '结转(800)', '累计补发6月(500)',
'补发1月(500)', '补发2月(500)', '补发3月(500)', '补发4月(500)', '补发5月(500)']
for cat in all_cats:
if cat in cat_sum:
ws5.append([cat, cat_cnt[cat], round(cat_sum[cat], 2)])
ws5.append(['合计', sum(cat_cnt.values()), round(sum(cat_sum.values()), 2)])
set_widths(ws5, [22, 10, 15])
wb.save(filepath)
print(f' ✅ {filepath}')
# ========== 主流程 ==========
def main():
os.makedirs(OUTPUT_DIR, exist_ok=True)
print('计算6月18T方案3...')
recs_6, G6, pay_records = calc()
# 统计
total_groups = sum(1 for g in G6.values() if not g.get('虚拟'))
good_groups = sum(1 for g in G6.values() if g['有达标'] and not g.get('虚拟'))
bad_groups = sum(1 for g in G6.values() if not g['有达标'] and not g.get('虚拟'))
carry_groups = sum(1 for g in G6.values() if g.get('结转', 0) > 0)
print(f' 6月18T组数: {total_groups}, 达标: {good_groups}, 未达标: {bad_groups}, 结转: {carry_groups}')
# 分类汇总
def cats(dl, *keys):
cnt = sum(sum(1 for r in dl if r['类型']==k) for k in keys)
amt = sum(sum(r['金额'] for r in dl if r['类型']==k) for k in keys)
return cnt, amt
def cell(amt, cnt):
if cnt == 0: return '—'
return f'{amt:,.2f} ({cnt}辆)'
rows = [
('当月达标(800)', cats(pay_records, '当月达标(800)')),
('当月未达标(300)', cats(pay_records, '当月未达标(300)')),
('结转(800)', cats(pay_records, '结转(800)')),
('累计补发6月(500)', cats(pay_records, '累计补发6月(500)')),
('补发1月(500)', cats(pay_records, '补发1月(500)')),
('补发2月(500)', cats(pay_records, '补发2月(500)')),
('补发3月(500)', cats(pay_records, '补发3月(500)')),
('补发4月(500)', cats(pay_records, '补发4月(500)')),
('补发5月(500)', cats(pay_records, '补发5月(500)')),
]
grand_total = sum(r['金额'] for r in pay_records)
def vw(s):
return sum(2 if unicodedata.east_asian_width(c) in ('F','W') else 1 for c in str(s))
def padr(s, w):
return ' ' * (w - vw(s)) + str(s)
def padl(s, w):
return str(s) + ' ' * (w - vw(s))
W0, W1 = 17, 26
print()
print(' 6月 18T 绩效考核 — 方案3 (800/300, 结转800, 补发500)')
print(f" ┌{'─'*W0}{'─'*W1}┐")
for label, (cnt, amt) in rows:
if amt > 0 or ('累计补发' in label) or ('补发' in label):
c = cell(amt, cnt)
else:
c = cell(amt, cnt)
if amt > 0 or cnt > 0:
print(f" │{padl(label,W0)}{padr(c,W1)}│")
# separator
print(f" ├{'─'*W0}{'─'*W1}┤")
print(f" │{padl('合计',W0)}{padr(f'{grand_total:,.2f}',W1)}│")
print(f" └{'─'*W0}{'─'*W1}┘")
# 写Excel
write_xlsx(recs_6, G6, pay_records, os.path.join(OUTPUT_DIR, '6月18T_方案3.xlsx'))
print(f"\n文件输出到: {OUTPUT_DIR}/")
if __name__ == '__main__':
main()