chore: curate monthly assessment evidence and scripts
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user