chore: curate monthly assessment evidence and scripts
This commit is contained in:
@@ -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}")
|
||||
Reference in New Issue
Block a user