#!/usr/bin/env python3 """ 全流程:用GPS汇总文件替换测试车辆里程 → 重新核算6月 → 离职转嫁 → 输出 输出目录: 20260713_1500/ """ import openpyxl, os, shutil, xlrd from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter from collections import defaultdict from datetime import datetime, timedelta from calc_engine import * import excel_writer as ew os.chdir('/Users/kkfluous/Downloads') GPS_BASE = 'gps里程汇总2026年1-6月 3' OUT_DIR = '20260713_1500' os.makedirs(OUT_DIR, exist_ok=True) # ============================================================ # Step 1: Build GPS daily mileage lookup from all 4 providers # ============================================================ print("=" * 60) print("Step 1: 构建GPS日里程映射") print("=" * 60) # {plate: {day_number (1-30): mileage_km}} gps_daily = defaultdict(lambda: defaultdict(float)) # --- G7 --- print("\n读取 G7...") fp_g7 = os.path.join(GPS_BASE, 'G7/202606_G7.xlsx') wb = openpyxl.load_workbook(fp_g7, data_only=True) ws = wb.active h_g7 = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))] # Find daily columns: 06月01日 ... 06月30日 g7_day_cols = {} for i, h in enumerate(h_g7): if h and '月' in str(h) and '日' in str(h): # Parse "06月15日" -> day=15 import re m = re.search(r'(\d+)月(\d+)日', str(h)) if m: day = int(m.group(2)) g7_day_cols[day] = i print(f" G7日期列: {len(g7_day_cols)} 天, 共 {ws.max_row-1} 辆车") g7_count = 0 for row in ws.iter_rows(min_row=2, values_only=True): plate = str(row[0]).strip() if row[0] else '' if not plate: continue for day, col in g7_day_cols.items(): val = float(row[col] or 0) if val > 0: gps_daily[plate][day] += val g7_count += 1 wb.close() print(f" G7: {g7_count} 辆车") # --- 东方北斗 --- print("\n读取 东方北斗...") fp_df = os.path.join(GPS_BASE, '东方北斗/202606_东方北斗.xlsx') wb = openpyxl.load_workbook(fp_df, data_only=True) ws = wb.active h_df = [c.value for c in next(ws.iter_rows(min_row=3, max_row=3))] # Daily columns: 1号(Col 11) ... 30号(Col 40) df_day_cols = {} for i, h in enumerate(h_df): if h and '号' in str(h) and str(h)[0].isdigit(): try: day = int(str(h).replace('号', '')) df_day_cols[day] = i except: pass print(f" 东方北斗日期列: {len(df_day_cols)} 天") df_count = 0 for row in ws.iter_rows(min_row=4, values_only=True): plate = str(row[5]).strip() if row[5] else '' if not plate: continue for day, col in df_day_cols.items(): val = float(row[col] or 0) if val > 0: gps_daily[plate][day] += val df_count += 1 wb.close() print(f" 东方北斗: {df_count} 辆车") # --- 信达GPS (daily detail format) --- print("\n读取 信达GPS...") fp_xd = os.path.join(GPS_BASE, '信达GPS/202606_信达.xlsx') wb = openpyxl.load_workbook(fp_xd, data_only=True) ws = wb.active xd_count = 0 for row in ws.iter_rows(min_row=4, values_only=True): plate = str(row[7]).strip() if row[7] else '' date_str = str(row[14]).strip() if row[14] else '' mileage = float(row[15] or 0) if not plate or not date_str or mileage <= 0: continue try: # Parse date: "2026-06-01" dt = datetime.strptime(date_str[:10], '%Y-%m-%d') day = dt.day gps_daily[plate][day] += mileage xd_count += 1 except: pass wb.close() print(f" 信达GPS: {xd_count} 条日记录") # --- 广安北斗 (monthly only - xls) --- print("\n读取 广安北斗...") fp_ga = os.path.join(GPS_BASE, '广安北斗/202606_广安北斗.xls') gdwb = xlrd.open_workbook(fp_ga) gdws = gdwb.sheet_by_index(0) ga_count = 0 for r in range(7, gdws.nrows): plate = str(gdws.cell_value(r, 3)).strip() mileage = float(gdws.cell_value(r, 11) or 0) if not plate or mileage <= 0: continue # Spread across all 30 days proportionally? No - store as a special total # Better: store at day 0 as "full month total" if 0 not in gps_daily[plate] or gps_daily[plate][0] < mileage: gps_daily[plate][0] = mileage # day 0 = full month total marker ga_count += 1 print(f" 广安北斗: {ga_count} 辆车 (月度汇总)") total_plates = len(gps_daily) print(f"\nGPS映射总计: {total_plates} 辆车") # ============================================================ # Step 2: Read test vehicle plates # ============================================================ print("\n" + "=" * 60) print("Step 2: 提取测试车辆清单") print("=" * 60) # Extract from干预情况说明 fp_interv = '租赁任务考核_2026年6月_干预情况说明.xlsx' if os.path.exists(fp_interv): wb = openpyxl.load_workbook(fp_interv, data_only=True) ws = wb['涉及测试的业务考核车辆'] h = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))] plate_col = h.index('车牌号') test_plates = set() for row in ws.iter_rows(min_row=2, values_only=True): p = row[plate_col] if p: test_plates.add(str(p).strip()) wb.close() else: # Fallback: vehicles with test mileage > 0 in current source wb = openpyxl.load_workbook('租赁任务考核_2026年6月.xlsx', data_only=True) ws = wb['业务考核视图'] h = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))] pcol = h.index('车牌号') tcol = h.index('测试里程(km)') test_plates = set() for row in ws.iter_rows(min_row=2, values_only=True): if float(row[tcol] or 0) > 0: test_plates.add(str(row[pcol]).strip()) wb.close() print(f"测试车辆: {len(test_plates)} 辆") # Check GPS coverage gps_hit = sum(1 for p in test_plates if p in gps_daily) gps_miss = test_plates - set(gps_daily.keys()) print(f"GPS命中: {gps_hit}, GPS缺失: {len(gps_miss)}") if gps_miss: print(f"缺失车辆(前10): {sorted(gps_miss)[:10]}") # ============================================================ # Step 3: Replace GPS mileage for test vehicles # ============================================================ print("\n" + "=" * 60) print("Step 3: 替换测试车辆实际行驶里程") print("=" * 60) # Start from current source file (has correct structure with TBOX/GPS/测试 columns) src_fp = '租赁任务考核_2026年6月.xlsx' wb_src = openpyxl.load_workbook(src_fp) ws_src = wb_src['业务考核视图'] h_src = [c.value for c in next(ws_src.iter_rows(min_row=1, max_row=1))] # Column indices col_plate = h_src.index('车牌号') col_tbox = h_src.index('原TBOX里程(km)') col_actual = h_src.index('实际行驶里程(km)') col_test = h_src.index('测试里程(km)') col_rate = h_src.index('完成率(%)') col_dabiao = h_src.index('是否达标') col_start = h_src.index('考核开始日期') col_end = h_src.index('考核结束日期') col_target_km = h_src.index('应考核里程(km)') YELLOW_FILL = PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid') updated_count = 0 no_gps_count = 0 records = [] for row_idx, row in enumerate(ws_src.iter_rows(min_row=2), start=2): plate = str(row[col_plate].value or '').strip() tbox_val = float(row[col_tbox].value or 0) start_date = row[col_start].value end_date = row[col_end].value target_km = float(row[col_target_km].value or 0) if plate not in test_plates: records.append((row_idx, plate, False, None, None)) continue # Calculate GPS mileage within考核日期区间 gps_sum = 0.0 gps_data = gps_daily.get(plate, {}) if 0 in gps_data and len(gps_data) == 1: # 广安北斗: only has full month total gps_sum = gps_data[0] elif start_date and end_date: # Parse dates and sum daily GPS if isinstance(start_date, datetime) and isinstance(end_date, datetime): start_day = start_date.day end_day = end_date.day for day in range(start_day, end_day + 1): gps_sum += gps_data.get(day, 0) elif isinstance(start_date, str): try: sd = datetime.strptime(str(start_date)[:10], '%Y-%m-%d') ed = datetime.strptime(str(end_date)[:10], '%Y-%m-%d') for day in range(sd.day, ed.day + 1): gps_sum += gps_data.get(day, 0) except: pass else: # No dates, use full month gps_sum = sum(gps_data.values()) else: gps_sum = sum(gps_data.values()) if gps_sum <= 0 and tbox_val <= 0: no_gps_count += 1 records.append((row_idx, plate, True, tbox_val, tbox_val)) continue # Test mileage = max(0, TBOX - GPS) test_mileage = max(0, tbox_val - gps_sum) # Completion rate rate = (gps_sum / target_km * 100) if target_km > 0 else 0 # 达标判断 dabiao = '达标' if gps_sum >= target_km and target_km > 0 else '未达标' # Update cells row[col_actual].value = round(gps_sum, 2) row[col_test].value = round(test_mileage, 2) row[col_rate].value = round(rate, 2) row[col_dabiao].value = dabiao # Yellow highlight for cell in row: cell.fill = YELLOW_FILL updated_count += 1 records.append((row_idx, plate, True, gps_sum, test_mileage)) print(f"测试车辆记录: {updated_count} 条已更新, {no_gps_count} 条无GPS数据") # Save updated source file new_src = os.path.join(OUT_DIR, '租赁任务考核_2026年6月.xlsx') wb_src.save(new_src) print(f"✅ 新数据源: {new_src}") # ============================================================ # Step 4: Run 核算 # ============================================================ print("\n" + "=" * 60) print("Step 4: 运行核算") print("=" * 60) # Temporarily copy source to working location shutil.copy(new_src, '租赁任务考核_2026年6月.xlsx') # Re-run main.py logic print("读取源数据...") D = {} for m in range(1, 7): fp = f'租赁任务考核_2026年{m}月.xlsx' if os.path.exists(fp): D[m] = read_file(fp, m) print(f" {m}月: {len(D[m])}条") G = {m: grp(D[m], m) for m in D} calc_jan_carryover(G[1]) all_data = {} all_data[1] = {'当月': [{'车牌':k[0],'销售':g['销售'],'部门':g['部门'],'额':g['奖金']} for k,g in G[1].items() if g['奖金'] > 0]} for m in range(2, 7): if m not in G: continue data_m, G[m] = calc_month(m, G, all_data, G[m-1], G[m]) vehicle_payments, vehicle_info = collect_vehicle_payments(G, all_data) master_vehicles = read_master_vehicles() # Loss data loss_data = {} for m in D: ld = read_loss_data(m) if ld: loss_data[m] = ld else: from collections import defaultdict as dd ld_tmp = dd(lambda: '否') for r in D[m]: c = r.get('客户名称') if c: ld_tmp[c] = '否' loss_data[m] = ld_tmp plate_client = get_vehicle_client_map(D) all_persons = {} for m in G: for k, g in G[m].items(): all_persons[g['销售']] = g['部门'] # Print summary settle_month = 6 month_data = all_data[settle_month] total_6 = sum(sum(d['额'] for d in v) for v in month_data.values()) print(f"\n6月考核应发: {total_6:.2f}") # Generate核算 file print(f"\n生成 {settle_month}月核算文件...") wb = openpyxl.Workbook() ew.write_rules_sheet(wb) ew.write_detail_sheet(wb, D[settle_month], settle_month) ew.write_calc_process_generic(wb, settle_month, G, all_data) ew.write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data[settle_month], plate_client) class _AllPass: def get(self, _k, _d=None): return '否' def __bool__(self): return True payment_records = ew.build_payment_records(settle_month, month_data, {m: _AllPass() for m in D}, plate_client) ew.write_payment_record_sheet(wb, settle_month, payment_records) ew.write_summary_from_records(wb, settle_month, payment_records) for person in sorted(all_persons.keys()): ew.write_salesperson_sheet(wb, person, all_persons[person], settle_month, D, G, month_data, vehicle_payments) if 'Sheet' in wb.sheetnames: del wb['Sheet'] calc_fp = os.path.join(OUT_DIR, '里程任务考核_6月核算.xlsx') wb.save(calc_fp) print(f"✅ 核算文件: {calc_fp}") # ============================================================ # Step 5: 离职转嫁 + 格式重建 # ============================================================ print("\n" + "=" * 60) print("Step 5: 离职转嫁 + 汇总格式重建") print("=" * 60) RESIGNED = {'赵连飞', '伍仲文', '岑彦'} TRANSFERS = [ ('刘念念', '赵连飞', 2260.00), ('钟祥', '伍仲文、岑彦', 180.00), ] TRANSFER_TOTAL = sum(t[2] for t in TRANSFERS) TRANSFER_MAP = {'赵连飞': ('刘念念', 2260.00), '伍仲文': ('钟祥', 150.00), '岑彦': ('钟祥', 30.00)} # Styles HEAD_FILL = PatternFill('solid', fgColor='4472C4') HEAD_FONT = Font(bold=True, color='FFFFFF', size=10, name='宋体') SUB_FILL = PatternFill('solid', fgColor='D9E1F2') TITLE_FNT = Font(bold=True, size=12, name='宋体') SECTION_FNT = Font(bold=True, size=11, name='宋体') NOTE_FNT = Font(size=10, color='C00000', name='宋体') NORM_FNT = Font(size=10, name='宋体') BOLD_FNT = Font(bold=True, size=10, name='宋体') THIN = Side('thin', color='B4B4B4') BORD = Border(THIN, THIN, THIN, THIN) CC = Alignment(horizontal='center', vertical='center', wrap_text=True) LL = Alignment(horizontal='left', vertical='center', wrap_text=True) def hdr_row(ws, r, headers): for c, h in enumerate(headers, 1): cell = ws.cell(row=r, column=c, value=h) cell.font = HEAD_FONT; cell.fill = HEAD_FILL; cell.alignment = CC; cell.border = BORD def data_row(ws, r, values, bold=False): for c, v in enumerate(values, 1): cell = ws.cell(row=r, column=c, value=v) cell.font = BOLD_FNT if bold else NORM_FNT; cell.alignment = CC; cell.border = BORD if isinstance(v, float): cell.number_format = '#,##0.00' def sub_total(ws, r, values): for c, v in enumerate(values, 1): cell = ws.cell(row=r, column=c, value=v) cell.font = BOLD_FNT; cell.fill = SUB_FILL; cell.alignment = CC; cell.border = BORD if isinstance(v, float): cell.number_format = '#,##0.00' # Parse existing summary ws_old = wb['6月汇总'] person_dept = {} for r in range(1, ws_old.max_row + 1): v1 = str(ws_old.cell(row=r, column=1).value or '') if '6月最终发放(按销售人员)' in v1: for r2 in range(r+1, ws_old.max_row + 1): name = str(ws_old.cell(row=r2, column=1).value or '') dept = str(ws_old.cell(row=r2, column=2).value or '') if name == '合计': break if name and name != '销售人员' and dept: person_dept[name] = dept break # Parse考核应发 subsections subsections = [] current_title = None; current_rows = [] for r in range(1, ws_old.max_row + 1): v1 = str(ws_old.cell(row=r, column=1).value or '') if v1.startswith('考核应发-'): if current_title and current_rows: subsections.append((current_title, current_rows)) current_title = v1; current_rows = [] continue if current_title: name = str(ws_old.cell(row=r, column=1).value or '') vehicles = ws_old.cell(row=r, column=2).value amount = ws_old.cell(row=r, column=3).value if name == '总计': subsections.append((current_title, current_rows)) current_title = None; current_rows = [] elif name and name != '销售人员': current_rows.append((name, vehicles, amount)) # Filter out resigned filtered_subsections = [] grand_total = 0.0 for title, rows in subsections: filtered = [(n, v, a) for (n, v, a) in rows if n not in RESIGNED] new_veh = sum(int(v or 0) for _, v, _ in filtered) new_amt = sum(float(a or 0) for _, _, a in filtered) grand_total += new_amt filtered_subsections.append((title, filtered, new_veh, new_amt)) # Aggregate最终发放 person_data = defaultdict(lambda: {'vehicles': 0, 'amount': 0.0}) for title, rows, _, _ in filtered_subsections: for name, vehicles, amount in rows: person_data[name]['vehicles'] += int(vehicles or 0) person_data[name]['amount'] += float(amount or 0) for resigned, (receiver, amt) in TRANSFER_MAP.items(): person_data[receiver]['amount'] += amt for name in RESIGNED: person_data.pop(name, None) final_total = sum(d['amount'] for d in person_data.values()) dept_order = ['业务二部', '业务三部', '业务五部', '业务六部'] def sort_key(item): name = item[0] dept = person_dept.get(name, '') try: di = dept_order.index(dept) except: di = 99 return (di, name) sorted_persons = sorted(person_data.items(), key=sort_key) dept_totals = defaultdict(float) for name, data in sorted_persons: dept = person_dept.get(name, '') dept_totals[dept] += data['amount'] # Rebuild 6月汇总 sheet ws = wb['6月汇总'] ws.delete_rows(1, ws.max_row) rn = 1 # 一、考核应发明细 ws.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=3) ws.cell(row=rn, column=1, value='一、考核应发明细').font = TITLE_FNT rn += 2 for title, rows, total_veh, total_amt in filtered_subsections: ws.cell(row=rn, column=1, value=title).font = SECTION_FNT rn += 1 hdr_row(ws, rn, ['销售人员', '车辆数', '金额']) rn += 1 sorted_rows = sorted(rows, key=lambda x: sort_key((x[0],)) if x[0] in person_dept else (99, x[0])) for name, vehicles, amount in sorted_rows: data_row(ws, rn, [name, vehicles, round(float(amount or 0), 2)]) rn += 1 sub_total(ws, rn, ['总计', total_veh, round(total_amt, 2)]) rn += 2 ws.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=2) ws.cell(row=rn, column=1, value='考核应发合计').font = BOLD_FNT ws.cell(row=rn, column=1).alignment = CC ws.cell(row=rn, column=3, value=round(grand_total, 2)).font = BOLD_FNT ws.cell(row=rn, column=3).alignment = CC; ws.cell(row=rn, column=3).number_format = '#,##0.00' for c in range(1, 4): ws.cell(row=rn, column=c).border = BORD; ws.cell(row=rn, column=c).fill = SUB_FILL rn += 2 # 二、离职人员转嫁收入 ws.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=3) ws.cell(row=rn, column=1, value='二、离职人员转嫁收入').font = TITLE_FNT rn += 1 hdr_row(ws, rn, ['接收人', '来源', '转嫁金额']) rn += 1 for receiver, source, amt in TRANSFERS: data_row(ws, rn, [receiver, source, amt]); rn += 1 sub_total(ws, rn, ['合计', '', TRANSFER_TOTAL]); rn += 2 # 三、最终发放 ws.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=3) ws.cell(row=rn, column=1, value='三、最终发放').font = TITLE_FNT rn += 2 ws.cell(row=rn, column=1, value='最终发放明细').font = SECTION_FNT; rn += 1 hdr_row(ws, rn, ['销售人员', '车辆数', '金额']); rn += 1 for name, data in sorted_persons: data_row(ws, rn, [name, data['vehicles'], round(data['amount'], 2)]); rn += 1 sub_total(ws, rn, ['总计', sum(d['vehicles'] for _, d in sorted_persons), round(final_total, 2)]); rn += 2 ws.cell(row=rn, column=1, value='6月最终发放(按销售人员)').font = SECTION_FNT; rn += 1 hdr_row(ws, rn, ['销售人员', '部门名称', '最终发放']); rn += 1 for name, data in sorted_persons: dept = person_dept.get(name, '') data_row(ws, rn, [name, dept, round(data['amount'], 2)]); rn += 1 sub_total(ws, rn, ['合计', '', round(final_total, 2)]); rn += 2 ws.cell(row=rn, column=1, value='6月最终发放(按部门)').font = SECTION_FNT; rn += 1 hdr_row(ws, rn, ['部门名称', '最终发放', '']); rn += 1 for dept in dept_order: if dept in dept_totals: data_row(ws, rn, [dept, round(dept_totals[dept], 2), '']); rn += 1 sub_total(ws, rn, ['合计', round(sum(dept_totals.values()), 2), '']); rn += 2 # 总览 ws.cell(row=rn, column=1, value='总览').font = SECTION_FNT; rn += 1 data_row(ws, rn, ['考核应发', round(final_total, 2), '']); rn += 1 data_row(ws, rn, ['亏损拦截', 0, '']); rn += 1 data_row(ws, rn, ['最终发放', round(final_total, 2), '']); rn += 2 # 备注 note = '备注:赵连飞(2026-04-30离职)、伍仲文(2026-05-09离职)、岑彦(2026-05-09离职)\n三人已从上述汇总中剔除,其6月当月达标奖金已转嫁至新销售;其余结转、补发、累计补发等一律不发了。' ws.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=3) ws.cell(row=rn, column=1, value=note).font = NOTE_FNT ws.cell(row=rn, column=1).alignment = Alignment(horizontal='left', vertical='center', wrap_text=True) ws.row_dimensions[rn].height = 36 # Column widths ws.column_dimensions['A'].width = 16 ws.column_dimensions['B'].width = 18 ws.column_dimensions['C'].width = 16 # Rename resigned sheets for old, new in [('二部-赵连飞','【已离职】二部-赵连飞'),('六部-伍仲文','【已离职】六部-伍仲文'),('六部-岑彦','【已离职】六部-岑彦')]: if old in wb.sheetnames: wb[old].title = new wb.move_sheet(new, offset=len(wb.sheetnames)-1-wb.sheetnames.index(new)) wb.save(calc_fp) print(f"✅ 核算文件(含转嫁): {calc_fp}") # ============================================================ # Step 6: 干预情况说明 (copy from existing) # ============================================================ print("\n" + "=" * 60) print("Step 6: 干预情况说明") print("=" * 60) old_interv = '租赁任务考核_2026年6月_干预情况说明.xlsx' if os.path.exists(old_interv): shutil.copy(old_interv, os.path.join(OUT_DIR, '租赁任务考核_2026年6月_干预情况说明.xlsx')) print(f"✅ 干预说明已复制") # ============================================================ # Step 7: 离职明细 (regenerate with new amounts) # ============================================================ print("\n" + "=" * 60) print("Step 7: 离职人员处理明细") print("=" * 60) # Collect resigned data from all_data[6] detail_records = [] for ptype, items in month_data.items(): for it in items: if it['销售'] not in RESIGNED: continue name = it['销售'] amount = it['额'] is_dangyue = (ptype == '当月') transfer_to = TRANSFER_MAP[name][0] handling = f'转{transfer_to}(需确认)' if is_dangyue else '不发了' transfer_disp = transfer_to if is_dangyue else '—' note_txt = '6月当月达标,转新销售' if is_dangyue else f'{ptype},不发了' client = ''; target_name = '' for r in D.get(6, []): if r['车牌号'] == it['车牌'] and r['销售经理'] == name: client = r.get('客户名称', ''); target_name = r.get('考核目标', '') break g6 = G[6].get((it['车牌'], name)) monthly_bonus = g6['奖励达标'] if g6 else 0 days = g6['天数'] if g6 else '' if ptype in ('当月', f'累计补发{settle_month}月', '结转'): loss_month = settle_month elif ptype.startswith('补发'): try: loss_month = int(ptype.replace('补发','').replace('月','')) except: loss_month = settle_month else: loss_month = settle_month detail_records.append({ 'name': name, 'plate': it['车牌'], 'pay_type': ptype, 'amount': amount, 'handling': handling, 'transfer': transfer_disp, 'note': note_txt, 'client': client, 'loss_month': loss_month, 'loss_status': '否', 'monthly_bonus': monthly_bonus, 'days': days, 'target': target_name, }) print(f"离职人员发放记录: {len(detail_records)} 条") # Generate resignation workbook (simplified) wb_res = openpyxl.Workbook() # Sheet 1: 总览 ws1 = wb_res.active; ws1.title = '离职人员发放处理总览' ws1.merge_cells('A1:G1') ws1.cell(row=1, column=1, value='2026年6月核算 — 离职人员奖金处理方案').font = Font(bold=True, size=14, name='宋体') ws1.cell(row=1, column=1).alignment = CC ws1.row_dimensions[1].height = 30 ws1.merge_cells('A2:G2') ws1.cell(row=2, column=1, value='处理规则:仅6月当月达标奖金转给新接手的销售(需与其他部门确认是否发放);其余结转、补发、累计补发等一律不发了。').font = NOTE_FNT ws1.cell(row=2, column=1).alignment = LL r = 4 for c, h in enumerate(['离职人员', '部门', '业务转嫁至', '不发了', '转新销售(需确认)', '原考核应发合计'], 1): ws1.cell(row=r, column=c, value=h) ws1.cell(row=r, column=c).font = HEAD_FONT; ws1.cell(row=r, column=c).fill = HEAD_FILL ws1.cell(row=r, column=c).alignment = CC; ws1.cell(row=r, column=c).border = BORD r += 1 dept_map = {'赵连飞': '业务二部', '伍仲文': '业务六部', '岑彦': '业务六部'} total_not = 0; total_trf = 0 for name in ['赵连飞', '伍仲文', '岑彦']: recs = [p for p in detail_records if p['name'] == name] not_pay = sum(p['amount'] for p in recs if p['pay_type'] != '当月') transfer = sum(p['amount'] for p in recs if p['pay_type'] == '当月') total_not += not_pay; total_trf += transfer data_row(ws1, r, [name, dept_map[name], TRANSFER_MAP[name][0], round(not_pay,2), round(transfer,2), round(not_pay+transfer,2)]) r += 1 sub_total(ws1, r, ['合计', '', '', round(total_not,2), round(total_trf,2), round(total_not+total_trf,2)]) r += 2 ws1.merge_cells(start_row=r, start_column=1, end_row=r, end_column=6) ws1.cell(row=r, column=1, value='转嫁接收方汇总(需与其他部门确认是否发放)').font = Font(bold=True, size=12, color='1F4E78') ws1.cell(row=r, column=1).alignment = CC; r += 1 for c, h in enumerate(['接收人', '来源离职人员', '转嫁金额', '备注'], 1): ws1.cell(row=r, column=c, value=h) ws1.cell(row=r, column=c).font = HEAD_FONT; ws1.cell(row=r, column=c).fill = HEAD_FILL ws1.cell(row=r, column=c).alignment = CC; ws1.cell(row=r, column=c).border = BORD r += 1 by_recv = defaultdict(lambda: {'sources':[], 'amt':0}) for rec in detail_records: if rec['pay_type'] == '当月': t = rec['transfer'] by_recv[t]['sources'].append(rec['name']) by_recv[t]['amt'] += rec['amount'] for receiver in ['刘念念', '钟祥']: d = by_recv[receiver] data_row(ws1, r, [receiver, '、'.join(sorted(set(d['sources']))), round(d['amt'],2), '需与相关部门确认是否发放给新销售']) r += 1 for i, w in enumerate([14, 22, 18, 18, 18, 18], 1): ws1.column_dimensions[get_column_letter(i)].width = w # Sheet 2: 逐条明细 ws2 = wb_res.create_sheet('发放明细(逐条)') ws2.merge_cells('A1:M1') ws2.cell(row=1, column=1, value='离职人员 — 6月考核应发逐条明细').font = Font(bold=True, size=14, name='宋体') ws2.cell(row=1, column=1).alignment = CC dh = ['离职人员', '车牌号', '发放类型', '考核应发', '盈亏查询月', '客户名称', '客户盈亏', '处理方式', '转嫁接收人', '6月考核天数', '月度奖励', '考核目标', '备注'] r2 = 3 hdr_row(ws2, r2, dh); r2 += 1 name_order = ['赵连飞', '伍仲文', '岑彦'] detail_records.sort(key=lambda x: (name_order.index(x['name']), 0 if x['pay_type']=='当月' else 1, x['plate'])) for i, rec in enumerate(detail_records): vals = [rec['name'], rec['plate'], rec['pay_type'], round(rec['amount'],2), f"{rec['loss_month']}月", rec['client'], rec['loss_status'], rec['handling'], rec['transfer'], rec['days'] if rec['days'] else '', rec['monthly_bonus'] if rec['monthly_bonus'] else '', rec['target'], rec['note']] data_row(ws2, r2, vals); ws2.row_dimensions[r2].height = 22; r2 += 1 sub_total(ws2, r2, ['', '', '合计', round(sum(r['amount'] for r in detail_records),2), '', '', '', '', '', '', '', '', '']) for i, w in enumerate([10, 12, 14, 14, 10, 24, 8, 18, 14, 12, 10, 22, 28], 1): ws2.column_dimensions[get_column_letter(i)].width = w # Personal sheets (3-5) + Transfer sheets (6-7) - simplified for name, title_txt, transfer_to in [ ('赵连飞', '赵连飞(2026-04-30离职,业务转→刘念念)', '刘念念'), ('伍仲文', '伍仲文(2026-05-09离职,业务转→钟祥)', '钟祥'), ('岑彦', '岑彦(2026-05-09离职,业务转→钟祥)', '钟祥'), ]: ws = wb_res.create_sheet(name) ws.merge_cells('A1:K1') ws.cell(row=1, column=1, value=title_txt).font = Font(bold=True, size=14, name='宋体') ws.cell(row=1, column=1).alignment = CC ph = ['车牌号', '发放类型', '考核应发', '盈亏查询月', '客户名称', '客户盈亏', '处理方式', '6月考核天数', '月度奖励', '考核目标', '备注'] r3 = 3 hdr_row(ws, r3, ph); r3 += 1 person_recs = [p for p in detail_records if p['name'] == name] for rec in person_recs: vals = [rec['plate'], rec['pay_type'], round(rec['amount'],2), f"{rec['loss_month']}月", rec['client'], rec['loss_status'], rec['handling'], rec['days'] if rec['days'] else '', rec['monthly_bonus'] if rec['monthly_bonus'] else '', rec['target'], rec['note']] data_row(ws, r3, vals); ws.row_dimensions[r3].height = 22; r3 += 1 dn = sum(p['amount'] for p in person_recs if p['pay_type']=='当月') ot = sum(p['amount'] for p in person_recs if p['pay_type']!='当月') sub_total(ws, r3, ['', '不发了小计', round(ot,2), '', '', '', '', '', '', '', '']); r3 += 1 sub_total(ws, r3, ['', f'转{transfer_to}小计', round(dn,2), '', '', '', '', '', '', '', '需与其他部门确认']); r3 += 1 sub_total(ws, r3, ['', '合计', round(dn+ot,2), '', '', '', '', '', '', '', '']) for i, w in enumerate([12, 14, 14, 10, 26, 8, 18, 12, 10, 22, 30], 1): ws.column_dimensions[get_column_letter(i)].width = w # Transfer detail sheets for title_txt, sources, receiver in [ ('转嫁至刘念念的奖金 — 来源:赵连飞(2026-04-30离职)', ['赵连飞'], '刘念念'), ('转嫁至钟祥的奖金 — 来源:伍仲文(2026-05-09离职)、岑彦(2026-05-09离职)', ['伍仲文', '岑彦'], '钟祥'), ]: ws = wb_res.create_sheet(f'转{receiver}明细') ws.merge_cells('A1:K1') ws.cell(row=1, column=1, value=title_txt).font = Font(bold=True, size=14, name='宋体') ws.cell(row=1, column=1).alignment = CC ws.merge_cells('A2:K2') ws.cell(row=2, column=1, value='⚠ 以下金额需与其他部门确认是否可发放给新销售').font = NOTE_FNT ws.cell(row=2, column=1).alignment = LL th = ['来源离职人员', '车牌号', '发放类型', '考核应发', '盈亏查询月', '客户名称', '客户盈亏', '6月考核天数', '月度奖励', '考核目标', '备注'] r4 = 4 hdr_row(ws, r4, th); r4 += 1 trf_recs = [p for p in detail_records if p['name'] in sources and p['pay_type']=='当月'] for rec in trf_recs: vals = [rec['name'], rec['plate'], '当月', round(rec['amount'],2), f"{rec['loss_month']}月", rec['client'], rec['loss_status'], rec['days'] if rec['days'] else '', rec['monthly_bonus'] if rec['monthly_bonus'] else '', rec['target'], '需确认是否发给新销售'] data_row(ws, r4, vals); r4 += 1 sub_total(ws, r4, ['', '', '合计', round(sum(r['amount'] for r in trf_recs),2), '', '', '', '', '', '', '']) for i, w in enumerate([14, 12, 14, 14, 10, 26, 8, 12, 10, 22, 22], 1): ws.column_dimensions[get_column_letter(i)].width = w resign_fp = os.path.join(OUT_DIR, '离职人员6月奖金处理明细.xlsx') wb_res.save(resign_fp) print(f"✅ 离职明细: {resign_fp}") # ============================================================ # Step 8: Summary # ============================================================ print("\n" + "=" * 60) print("Step 8: 输出汇总") print("=" * 60) files = os.listdir(OUT_DIR) for f in sorted(files): fp = os.path.join(OUT_DIR, f) size_kb = os.path.getsize(fp) / 1024 print(f" {f} ({size_kb:.0f} KB)") print(f"\n{'=' * 60}") print(f"6月考核应发: {total_6:.2f}") print(f"最终发放: {final_total:.2f}") print(f"全部文件已输出到 {OUT_DIR}/") print(f"{'=' * 60}")