From e8140133202daf33051d1681ea1abea33afc0fa4 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Fri, 3 Apr 2026 09:14:37 +0800 Subject: [PATCH] =?UTF-8?q?V3.0.0=20=E5=8F=A0=E5=8A=A0=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E4=BA=8F=E6=8D=9F=E7=AD=9B=E9=80=89=EF=BC=8C=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=9C=80=E7=BB=88=E5=8F=91=E6=94=BE=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - 读取1月/2月亏损表,按客户名称匹配考核数据 - 车辆考核追踪新增列:客户名称、客户是否亏损、考核应发、最终发放、未发放原因 - 月汇总新增亏损筛选section:亏损拦截/未匹配/最终发放/汇总 - 3月无亏损表,全部正常发放 - 亏损拦截不补发 规则:客户亏损→该客户下所有车不发;未匹配→标注待人工确认 Co-Authored-By: Claude Opus 4.6 (1M context) --- calc_engine.py | 41 ++++++++++++++++++ excel_writer.py | 108 +++++++++++++++++++++++++++++++++++++++++++----- main.py | 30 ++++++++++---- 3 files changed, 162 insertions(+), 17 deletions(-) diff --git a/calc_engine.py b/calc_engine.py index 3568593..3ed24fc 100644 --- a/calc_engine.py +++ b/calc_engine.py @@ -179,6 +179,47 @@ VEHICLE_TARGET_MAP = { ('现代氢能科技(广州)有限公司', '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))] + + # 1月: 列名"项目","1月是否亏损" + # 2月: 列名"客户名称","是否亏损" + 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) diff --git a/excel_writer.py b/excel_writer.py index 66ca91e..b510fee 100644 --- a/excel_writer.py +++ b/excel_writer.py @@ -162,14 +162,44 @@ def write_calc_process_mar(wb, G1, G2, G3, feb_data): c5,R(cbp3) if cbp3>0 else 0,R(total) if total>0 else 0]); rn+=1 AW(ws) -def write_summary_jan(wb, records): +def write_summary_jan(wb, records, loss_data=None, plate_client=None): ws = wb.create_sheet('1月汇总') jan_dl=[{'车牌':r['车牌号'],'销售':r['销售经理'],'部门':r['部门名称'],'额':r['奖金']} for r in records if r['是否达标']=='达标'] - rn=write_sec(ws,1,'1月达标奖励',jan_dl) + rn=write_sec(ws,1,'1月达标奖励(考核应发)',jan_dl) + + if loss_data is not None: + # 亏损拦截 + blocked = [] + passed = [] + unmatched = [] + for d in jan_dl: + client = (plate_client or {}).get(d['车牌'], '') + status = loss_data.get(client, '未匹配') if client else '未匹配' + if status == '是': + blocked.append({**d, '客户': client}) + elif status == '未匹配': + unmatched.append({**d, '客户': client}) + else: + passed.append(d) + + rn = write_sec(ws, rn, '亏损拦截(客户亏损不发放)', blocked if blocked else []) + rn = write_sec(ws, rn, '未匹配亏损表(需人工确认)', unmatched if unmatched else []) + rn = write_sec(ws, rn, '最终发放', passed) + + ws.cell(row=rn, column=1, value='汇总').font=Font(bold=True, size=11); rn+=1 + total_考核 = sum(d['额'] for d in jan_dl) + total_拦截 = sum(d['额'] for d in blocked) + total_未匹配 = sum(d['额'] for d in unmatched) + total_最终 = sum(d['额'] for d in passed) + WR(ws, rn, ['考核应发', R(total_考核)]); rn+=1 + WR(ws, rn, ['亏损拦截', R(total_拦截)]); rn+=1 + WR(ws, rn, ['未匹配(待确认)', R(total_未匹配)]); rn+=1 + WR(ws, rn, ['最终发放', R(total_最终)]); ws.cell(row=rn,column=1).font=Font(bold=True); rn+=2 + write_total(ws,rn,1,{'达标':jan_dl}) AW(ws) -def write_summary_month(wb, month, month_data, section_names): +def write_summary_month(wb, month, month_data, section_names, loss_data=None, plate_client=None): ws = wb.create_sheet(f'{month}月汇总') rn=1 for i,cat in enumerate(section_names): @@ -178,7 +208,47 @@ def write_summary_month(wb, month, month_data, section_names): '当月':f'{"三" if month==2 else "四"}、{month}月当月奖励', f'累计补发{month}月':f'{"四" if month==2 else "五"}、累计达标补发{month}月'} rn=write_sec(ws,rn,label_map.get(cat,cat),month_data.get(cat,[])) + + # 考核应发合计 write_total(ws,rn,month,month_data) + # 找最后一行 + rn = ws.max_row + 2 + + if loss_data is not None: + # 合并所有发放记录 + all_dl = [] + for cat, dl in month_data.items(): + all_dl.extend(dl) + + blocked = []; passed = []; unmatched = [] + for d in all_dl: + client = (plate_client or {}).get(d['车牌'], '') + status = loss_data.get(client, '未匹配') if client else '未匹配' + if status == '是': + blocked.append(d) + elif status == '未匹配': + unmatched.append(d) + else: + passed.append(d) + + ws.cell(row=rn, column=1, value='═══ 亏损筛选 ═══').font=Font(bold=True, size=12); rn+=2 + rn = write_sec(ws, rn, '亏损拦截(客户亏损不发放)', blocked) + rn = write_sec(ws, rn, '未匹配亏损表(需人工确认)', unmatched) + rn = write_sec(ws, rn, '最终发放', passed) + + total_考核 = sum(d['额'] for d in all_dl) + total_拦截 = sum(d['额'] for d in blocked) + total_未匹配 = sum(d['额'] for d in unmatched) + total_最终 = sum(d['额'] for d in passed) + + ws.cell(row=rn, column=1, value='亏损筛选汇总').font=Font(bold=True, size=11); rn+=1 + WR(ws, rn, ['考核应发合计', R(total_考核)]); rn+=1 + WR(ws, rn, ['亏损拦截金额', R(total_拦截)]); rn+=1 + WR(ws, rn, ['未匹配金额(待确认)', R(total_未匹配)]); rn+=1 + WR(ws, rn, ['最终应发合计', R(total_最终)]) + ws.cell(row=rn,column=1).font=Font(bold=True, size=12) + ws.cell(row=rn,column=2).font=Font(bold=True, size=12) + AW(ws) # ============================================================ @@ -339,7 +409,7 @@ def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, ve # 新增:车辆考核追踪sheet # ============================================================ -def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info): +def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data=None, plate_client=None): ws = wb.create_sheet('车辆考核追踪') from calc_engine import VEHICLE_TARGET_MAP, DAYS @@ -360,7 +430,11 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p headers += [f'{m}月应考核', f'{m}月实际', f'{m}月达标'] if settle_month >= 2: headers += ['累计应完成','累计实际','累计达标'] - headers += ['本月发放金额','发放类型','已发期数','已发金额','剩余期数'] + if loss_data is not None: + headers += ['客户名称','客户是否亏损','考核应发','最终发放','未发放原因'] + else: + headers += ['本月发放金额','发放类型'] + headers += ['已发期数','已发金额','剩余期数'] WH(ws, headers) ws.freeze_panes = 'H2' # 冻结车辆信息+业务员列 @@ -445,12 +519,26 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p # 本月发放(该业务员的) 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] + 考核应发 = sum(p['金额'] for p in tp) if tp else 0 + pay_types = ', '.join(p['类型'] for p in tp) if tp else '' + + if loss_data is not None: + # 有亏损表:加客户名称、亏损状态、考核应发、最终发放、未发放原因 + client = (plate_client or {}).get(plate, '') + loss_status = loss_data.get(client, '未匹配') if client else '未匹配' + if loss_status == '是': + final_amt = 0 + reason = '客户亏损不发放' if 考核应发 > 0 else '' + elif loss_status == '未匹配': + final_amt = 0 + reason = '未匹配亏损表' if 考核应发 > 0 else '' + else: + final_amt = 考核应发 + reason = '' + row += [client, loss_status, R(考核应发), R(final_amt), reason] else: - row += [0, ''] + # 无亏损表:直接发放 + row += [R(考核应发), pay_types] # 奖金池(整车,只在第一行显示) if pi == 0: diff --git a/main.py b/main.py index cd8a30d..35ad428 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""按月生成独立Excel文件 - 里程考核绩效""" +"""按月生成独立Excel文件 - 里程考核绩效(含亏损筛选)""" import os, openpyxl from calc_engine import * from excel_writer import * @@ -23,6 +23,22 @@ mar_data = calc_mar(G[1], G[2], G[3], feb_data) vehicle_payments, vehicle_info = collect_vehicle_payments(G, feb_data, mar_data) master_vehicles = read_master_vehicles() +# 亏损表 +print("\n读取亏损表...") +loss_data = {} +for m in [1,2,3]: + ld = read_loss_data(m) + if ld: + loss_clients = sum(1 for v in ld.values() if v == '是') + print(f" {m}月: {len(ld)}个客户, 其中亏损{loss_clients}个") + loss_data[m] = ld + else: + print(f" {m}月: 无亏损表") + loss_data[m] = None + +# 车牌→客户名称映射 +plate_client = get_vehicle_client_map(D) + # 所有业务员 all_persons = {} for m in G: @@ -33,8 +49,8 @@ for m in G: jan_total = sum(r['奖金'] for r in D[1] if r['是否达标']=='达标') feb_total = sum(sum(d['额'] for d in v) for v in feb_data.values()) mar_total = sum(sum(d['额'] for d in v) for v in mar_data.values()) -print(f"\n1月: {jan_total:.2f}, 2月: {feb_total:.2f}, 3月: {mar_total:.2f}") -print(f"Q1总计: {jan_total + feb_total + mar_total:.2f}") +print(f"\n考核应发: 1月{jan_total:.2f}, 2月{feb_total:.2f}, 3月{mar_total:.2f}") +print(f"Q1考核应发合计: {jan_total + feb_total + mar_total:.2f}") # ============================================================ # 按月生成 @@ -68,14 +84,14 @@ for settle_month in [1, 2, 3]: # Sheet 4: 汇总 if settle_month == 1: - write_summary_jan(wb, D[1]) + write_summary_jan(wb, D[1], loss_data[1], plate_client) elif settle_month == 2: - write_summary_month(wb, 2, feb_data, ['结转','补发1月','当月','累计补发2月']) + write_summary_month(wb, 2, feb_data, ['结转','补发1月','当月','累计补发2月'], loss_data[2], plate_client) else: - write_summary_month(wb, 3, mar_data, ['结转','补发1月','补发2月','当月','累计补发3月']) + write_summary_month(wb, 3, mar_data, ['结转','补发1月','补发2月','当月','累计补发3月'], loss_data[3], plate_client) # Sheet 5: 车辆考核追踪 - write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info) + write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data[settle_month], plate_client) # Sheet 6-17: 业务员 for person in sorted(all_persons.keys()):