chore: curate monthly assessment evidence and scripts

This commit is contained in:
kkfluous
2026-08-25 12:26:35 +08:00
parent 2956496fb0
commit 52dde9195a
117 changed files with 10743 additions and 179 deletions
+793
View File
@@ -0,0 +1,793 @@
#!/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}")