Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a69ef2993 | ||
|
|
04b6035d52 | ||
|
|
4c43c00e73 | ||
|
|
f2de5d5500 | ||
|
|
54ecbc352f | ||
|
|
cdc4cec2ff | ||
|
|
8c2f4e73dd |
@@ -169,6 +169,16 @@ def collect_vehicle_payments(G, feb_data, mar_data):
|
||||
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_master_vehicles(fp='里程任务考核_Q1汇总.xlsx'):
|
||||
"""从现有Q1汇总文件读取全量车辆台账"""
|
||||
wb = openpyxl.load_workbook(fp, data_only=True)
|
||||
|
||||
199
excel_writer.py
199
excel_writer.py
@@ -186,7 +186,8 @@ 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}')
|
||||
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()
|
||||
@@ -340,64 +341,184 @@ 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')
|
||||
from calc_engine import VEHICLE_TARGET_MAP, DAYS
|
||||
|
||||
# 表头:每月用一列"业务员/里程/目标/达标"(多人换行)
|
||||
headers = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励金额']
|
||||
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.append(f'{m}月考核明细')
|
||||
headers += [f'{m}月应考核', f'{m}月实际', f'{m}月达标']
|
||||
if settle_month >= 2:
|
||||
headers += ['累计里程/目标','累计达标']
|
||||
headers += ['本月发放明细','累计已发期数','累计已发金额','剩余可发期数']
|
||||
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, [])
|
||||
row=[plate,mv.get('车架号',''),mv.get('归属公司',''),mv.get('车型确定',''),
|
||||
info.get('考核目标',''),info.get('月度奖励',0) or '']
|
||||
|
||||
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):
|
||||
mgs=[(k,g) for k,g in G.get(m,{}).items() if k[0]==plate]
|
||||
if mgs:
|
||||
# 每个(车牌,销售)组一行,换行显示
|
||||
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))
|
||||
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.append('')
|
||||
# 多条:显示汇总,但单元格内注明各条
|
||||
row += [R(t_sum), R(a_sum), '\n'.join(rec_details)]
|
||||
else:
|
||||
row += ['', '', '']
|
||||
|
||||
# 累计
|
||||
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:
|
||||
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))
|
||||
if cum_t > 0:
|
||||
cum_q = cum_a >= cum_t
|
||||
row += [R(cum_t), R(cum_a), '✓' if cum_q else '✗']
|
||||
else:
|
||||
row.append('')
|
||||
row += ['', '', '']
|
||||
cum_q = False
|
||||
else:
|
||||
cum_q = False
|
||||
|
||||
# 奖金池(截至settle_month)
|
||||
# 本月发放(该业务员的)
|
||||
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+=[tp_count,R(tp_amt) if tp_amt>0 else 0,12-tp_count]
|
||||
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(len(row)):
|
||||
cell = ws.cell(row=rn, column=ci+1)
|
||||
if isinstance(cell.value, str) and '\n' in cell.value:
|
||||
cell.alignment = wrap_align
|
||||
|
||||
# 美化
|
||||
# 车辆信息列灰底
|
||||
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
|
||||
AW(ws)
|
||||
|
||||
# 合并车辆信息列(如果多人)
|
||||
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
|
||||
|
||||
8
main.py
8
main.py
@@ -74,13 +74,13 @@ for settle_month in [1, 2, 3]:
|
||||
else:
|
||||
write_summary_month(wb, 3, mar_data, ['结转','补发1月','补发2月','当月','累计补发3月'])
|
||||
|
||||
# Sheet 5-16: 业务员
|
||||
# Sheet 5: 车辆考核追踪
|
||||
write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info)
|
||||
|
||||
# Sheet 6-17: 业务员
|
||||
for person in sorted(all_persons.keys()):
|
||||
write_salesperson_sheet(wb, person, all_persons[person], settle_month, D, G, month_data, vehicle_payments)
|
||||
|
||||
# Sheet 17: 车辆考核追踪
|
||||
write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info)
|
||||
|
||||
# 删除默认空sheet
|
||||
if 'Sheet' in wb.sheetnames:
|
||||
del wb['Sheet']
|
||||
|
||||
Reference in New Issue
Block a user