316 lines
11 KiB
Python
316 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""按参考格式重建 6月汇总 sheet:剔除离职,转嫁用接收人格式"""
|
|
import openpyxl
|
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
|
from openpyxl.utils import get_column_letter
|
|
from collections import defaultdict, OrderedDict
|
|
import os, copy
|
|
|
|
os.chdir('/Users/kkfluous/Downloads')
|
|
fp = '里程任务考核_6月核算.xlsx'
|
|
|
|
# ── Config ──
|
|
RESIGNED = {'赵连飞', '伍仲文', '岑彦'}
|
|
TRANSFERS = [
|
|
('刘念念', '赵连飞', 2260.00),
|
|
('钟祥', '伍仲文、岑彦', 180.00),
|
|
]
|
|
TRANSFER_TOTAL = sum(t[2] for t in TRANSFERS)
|
|
# Map resigned -> receiver for aggregation
|
|
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'
|
|
|
|
# ── Step 1: Parse existing data ──
|
|
wb = openpyxl.load_workbook(fp)
|
|
ws_old = wb['6月汇总']
|
|
|
|
# Parse all考核应发 subsections
|
|
subsections = [] # [(title, [(name, vehicles, amount), ...], total_row)]
|
|
current_title = None
|
|
current_rows = []
|
|
in_detail = False
|
|
|
|
# Map person->dept from按销售人员 section
|
|
person_dept = {}
|
|
in_sales = False
|
|
for r in range(1, ws_old.max_row + 1):
|
|
v1 = str(ws_old.cell(row=r, column=1).value or '')
|
|
v2 = str(ws_old.cell(row=r, column=2).value or '')
|
|
if '6月最终发放(按销售人员)' in v1:
|
|
in_sales = True
|
|
continue
|
|
if in_sales:
|
|
name = str(ws_old.cell(row=r, column=1).value or '')
|
|
dept = str(ws_old.cell(row=r, column=2).value or '')
|
|
if name == '合计':
|
|
break
|
|
if name and name != '销售人员' and dept:
|
|
person_dept[name] = dept
|
|
|
|
print(f"人员部门映射: {person_dept}")
|
|
|
|
# Parse考核应发 subsections
|
|
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 = []
|
|
in_detail = True
|
|
continue
|
|
|
|
if current_title and in_detail:
|
|
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 = []
|
|
in_detail = False
|
|
elif name and name != '销售人员':
|
|
current_rows.append((name, vehicles, amount))
|
|
|
|
print(f"\n解析到 {len(subsections)} 个考核应发 subsection:")
|
|
|
|
# ── Step 2: Filter out resigned, recalculate ──
|
|
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]
|
|
# Recalculate vehicles and amounts
|
|
new_vehicles = sum(int(v or 0) for _, v, _ in filtered)
|
|
new_amount = sum(float(a or 0) for _, _, a in filtered)
|
|
grand_total += new_amount
|
|
filtered_subsections.append((title, filtered, new_vehicles, new_amount))
|
|
removed = [(n, a) for (n, _, a) in rows if n in RESIGNED]
|
|
if removed:
|
|
print(f" {title}: 剔除 {removed}, 新合计={new_amount:.2f}")
|
|
|
|
print(f"\n考核应发合计: {grand_total:.2f}")
|
|
|
|
# ── Step 3: Build最终发放 (aggregate by person + transfers) ──
|
|
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)
|
|
|
|
# Add transfers to receivers
|
|
for resigned, (receiver, amt) in TRANSFER_MAP.items():
|
|
person_data[receiver]['amount'] += amt
|
|
# Count vehicles? The reference doesn't seem to add vehicle count for transfers
|
|
|
|
# Remove resigned (should already be filtered, but just in case)
|
|
for name in list(person_data.keys()):
|
|
if name in RESIGNED:
|
|
del person_data[name]
|
|
|
|
final_total = sum(d['amount'] for d in person_data.values())
|
|
print(f"最终发放合计: {final_total:.2f}")
|
|
|
|
# Sort by department then name (matching reference order)
|
|
dept_order = ['业务二部', '业务三部', '业务五部', '业务六部']
|
|
def sort_key(item):
|
|
name = item[0]
|
|
dept = person_dept.get(name, '')
|
|
try:
|
|
di = dept_order.index(dept)
|
|
except ValueError:
|
|
di = 99
|
|
return (di, name)
|
|
|
|
sorted_persons = sorted(person_data.items(), key=sort_key)
|
|
|
|
# ── Step 4: Department totals ──
|
|
dept_totals = defaultdict(float)
|
|
for name, data in sorted_persons:
|
|
dept = person_dept.get(name, '')
|
|
dept_totals[dept] += data['amount']
|
|
|
|
# ── Step 5: Rebuild the sheet ──
|
|
# Delete old sheet and create new
|
|
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
|
|
|
|
# Sort rows by dept then name
|
|
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
|
|
|
|
# 6月最终发放(按销售人员)
|
|
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
|
|
|
|
# 6月最终发放(按部门)
|
|
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
|
|
hdr_row(ws, rn, ['', '', ''])
|
|
# Override header style for总览
|
|
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_text = '备注:赵连飞(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_text).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_name, new_name in [('二部-赵连飞','【已离职】二部-赵连飞'),('六部-伍仲文','【已离职】六部-伍仲文'),('六部-岑彦','【已离职】六部-岑彦')]:
|
|
if old_name in wb.sheetnames:
|
|
wb[old_name].title = new_name
|
|
wb.move_sheet(new_name, offset=len(wb.sheetnames)-1-wb.sheetnames.index(new_name))
|
|
print(f" Sheet: {old_name} → {new_name}")
|
|
|
|
# ── Copy to output ──
|
|
import shutil
|
|
out = '/Users/kkfluous/Downloads/20260713/里程任务考核_6月核算.xlsx'
|
|
wb.save(out)
|
|
print(f"\n✅ {out} 已重建")
|
|
|
|
# ── Verify ──
|
|
wb2 = openpyxl.load_workbook(out, data_only=True)
|
|
ws2 = wb2['6月汇总']
|
|
print(f"\n验证 ({ws2.max_row} rows):")
|
|
for row in ws2.iter_rows(min_row=1, max_row=ws2.max_row):
|
|
vals = [str(c.value)[:50] if c.value is not None else '' for c in row]
|
|
line = ' | '.join(vals)
|
|
if line.replace(' | ', '').strip():
|
|
print(f" {line}")
|
|
wb2.close()
|