3 Commits

Author SHA1 Message Date
kkfluous
da487c41d4 V3.2.0 结转不依赖考核记录 + 补发查对应月盈亏
1. 结转不依赖考核记录:
   - 1月有结转但2月/3月无考核记录 → 创建虚拟group(目标=满月,实际=0)
   - 虚拟记录正常发放结转奖金
   - 无客户关联 → 不查盈亏,正常发放

2. 补发查对应月盈亏:
   - 补发1月 → 查1月亏损表
   - 补发2月 → 查2月亏损表
   - 当月/结转/累计补发 → 查当月亏损表
   - 奖金发放记录新增"盈亏查询月"列

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:30:42 +08:00
kkfluous
ee962c97ae fix: 无亏损表的月份全部不发放
3月无亏损表→所有车辆标注"未匹配"→拦截全部考核应发→实发0。
移除了"未匹配且有亏损表"的多余条件判断。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:56:03 +08:00
kkfluous
4f48d1986f V3.1.0 新增奖金发放记录sheet,汇总改为从发放记录生成
数据链路:车辆考核追踪 → 奖金发放记录 → 月汇总
- 奖金发放记录:逐条明细,含车牌/业务员/部门/客户/发放类型/考核应发/客户盈亏/亏损拦截/实发
- 亏损→红底,未匹配→黄底,正常发放→绿底
- 月汇总改为从发放记录SUM生成:考核应发→亏损筛选→最终发放
- 业务员列不带部门前缀(部门独立列)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:47:06 +08:00
3 changed files with 210 additions and 9 deletions

View File

@@ -98,6 +98,26 @@ def calc_feb(G1, G2):
g2['可结转'] = fc + jr g2['可结转'] = fc + jr
g2['2月已发'] = carry>0 or bonus2>0 or cbp2>0 g2['2月已发'] = carry>0 or bonus2>0 or cbp2>0
g2['1月已补发'] = bp1>0 g2['1月已补发'] = bp1>0
# 补充1月有结转但2月无考核记录的车 → 创建虚拟2月group并发放结转
for k, g1 in G1.items():
if k not in G2 and g1['可结转'] >= 1:
bf = g1['奖励额']
carry = bf
feb_data['结转'].append({'车牌':k[0],'销售':g1['销售'],'部门':g1['部门'],'':carry})
# 创建虚拟2月group考核里程=满月目标,实际=0
G2[k] = {
'recs':[], '应考核':g1['目标km'], '实际':0, '奖金':0, '天数':DAYS[2],
'有达标':False, '目标km':g1['目标km'], '奖励额':g1['奖励额'],
'部门':g1['部门'], '销售':g1['销售'], '车牌':k[0],
'cum_t':g1['应考核']+g1['目标km'], 'cum_a':g1['实际'],
'cum_q': g1['实际'] >= g1['应考核']+g1['目标km'],
'结转':carry, '补发1月':0, '补发1月对应':'',
'当月奖金':0, '累计补发2月':0, '结转占位':True,
'可结转': max(0, g1['可结转'] - 1),
'2月已发':True, '1月已补发':False,
'虚拟':True, # 标记为无考核记录
}
return feb_data return feb_data
def calc_mar(G1, G2, G3, feb_data): def calc_mar(G1, G2, G3, feb_data):
@@ -134,6 +154,25 @@ def calc_mar(G1, G2, G3, feb_data):
g3['结转']=carry; g3['补发1月']=bj; g3['补发1月对应']=g1['销售'] if g1 and bj>0 else '' g3['结转']=carry; g3['补发1月']=bj; g3['补发1月对应']=g1['销售'] if g1 and bj>0 else ''
g3['补发2月']=bf2; g3['补发2月对应']=g2['销售'] if g2 and bf2>0 else '' g3['补发2月']=bf2; g3['补发2月对应']=g2['销售'] if g2 and bf2>0 else ''
g3['当月奖金']=bonus3; g3['累计补发3月']=cbp3; g3['结转占位']=carry>0 g3['当月奖金']=bonus3; g3['累计补发3月']=cbp3; g3['结转占位']=carry>0
# 补充2月有结转但3月无考核记录的车
for k, g2 in G2.items():
if k not in G3 and g2.get('可结转', 0) >= 1:
bf = g2['奖励额']
carry = bf
mar_data['结转'].append({'车牌':k[0],'销售':g2['销售'],'部门':g2['部门'],'':carry})
G3[k] = {
'recs':[], '应考核':g2['目标km'], '实际':0, '奖金':0, '天数':DAYS[3],
'有达标':False, '目标km':g2['目标km'], '奖励额':g2['奖励额'],
'部门':g2['部门'], '销售':g2['销售'], '车牌':k[0],
'cum_t':(G1.get(k,{}).get('应考核',0))+g2['应考核']+g2['目标km'],
'cum_a':(G1.get(k,{}).get('实际',0))+g2['实际'],
'cum_q':False,
'结转':carry, '补发1月':0, '补发1月对应':'',
'补发2月':0, '补发2月对应':'',
'当月奖金':0, '累计补发3月':0, '结转占位':True,
'虚拟':True,
}
return mar_data return mar_data
def collect_vehicle_payments(G, feb_data, mar_data): def collect_vehicle_payments(G, feb_data, mar_data):

View File

@@ -199,6 +199,166 @@ def write_summary_jan(wb, records, loss_data=None, plate_client=None):
write_total(ws,rn,1,{'达标':jan_dl}) write_total(ws,rn,1,{'达标':jan_dl})
AW(ws) AW(ws)
def build_payment_records(month, month_data, all_loss_data, plate_client):
"""构建奖金发放记录列表,每条考核应发一行,叠加亏损筛选。
all_loss_data: {1: loss_dict, 2: loss_dict, 3: loss_dict_or_None}
补发X月 → 查X月盈亏其他 → 查当月盈亏;无客户 → 正常发放
"""
# 发放类型→查哪个月盈亏
def get_loss_month(cat, settle_month):
if '补发1月' in cat: return 1
if '补发2月' in cat: return 2
if '补发3月' in cat: return 3
return settle_month
records = []
for cat, dl in month_data.items():
loss_month = get_loss_month(cat, month)
loss_data = all_loss_data.get(loss_month)
for d in dl:
client = (plate_client or {}).get(d['车牌'], '')
# 无客户关联(如虚拟结转记录)→ 正常发放
if not client:
loss_status = ''
elif loss_data:
loss_status = loss_data.get(client, '未匹配')
else:
loss_status = '未匹配' # 无亏损表视为未匹配,不发放
考核应发 = d['']
if loss_status == '':
拦截 = 考核应发; 实发 = 0
elif loss_status == '未匹配':
拦截 = 考核应发; 实发 = 0
else:
拦截 = 0; 实发 = 考核应发
records.append({
'车牌号': d['车牌'],
'业务员': d['销售'],
'部门': d['部门'],
'客户名称': client or '(无客户关联)',
'发放类型': cat,
'盈亏查询月': f'{loss_month}',
'考核应发': 考核应发,
'客户盈亏': loss_status,
'亏损拦截': 拦截,
'实发金额': 实发,
})
return records
def write_payment_record_sheet(wb, month, payment_records):
"""写入奖金发放记录sheet"""
ws = wb.create_sheet(f'{month}月奖金发放记录')
headers = ['车牌号','业务员','部门','客户名称','发放类型','盈亏查询月',
'考核应发','客户盈亏','亏损拦截','实发金额']
WH(ws, headers)
green_fill = PatternFill(start_color='C6EFCE', end_color='C6EFCE', fill_type='solid')
red_fill = PatternFill(start_color='FFC7CE', end_color='FFC7CE', fill_type='solid')
yellow_fill = PatternFill(start_color='FFFFCC', end_color='FFFFCC', fill_type='solid')
rn = 2
for r in sorted(payment_records, key=lambda x: (x['业务员'], x['车牌号'])):
WR(ws, rn, [r['车牌号'], r['业务员'], r['部门'], r['客户名称'], r['发放类型'],
r.get('盈亏查询月',''),
R(r['考核应发']), r['客户盈亏'], R(r['亏损拦截']), R(r['实发金额'])])
if r['客户盈亏'] == '':
for ci in [8, 9, 10]: ws.cell(row=rn, column=ci).fill = red_fill
elif r['客户盈亏'] == '未匹配':
for ci in [8, 9, 10]: ws.cell(row=rn, column=ci).fill = yellow_fill
elif r['实发金额'] > 0:
ws.cell(row=rn, column=10).fill = green_fill
rn += 1
total_应发 = sum(r['考核应发'] for r in payment_records)
total_拦截 = sum(r['亏损拦截'] for r in payment_records)
total_实发 = sum(r['实发金额'] for r in payment_records)
rn += 1
WR(ws, rn, ['', '', '', '', '合计', '', R(total_应发), '', R(total_拦截), R(total_实发)])
for ci in range(5, 11): ws.cell(row=rn, column=ci).font = Font(bold=True)
ws.auto_filter.ref = f"A1:J1"
AW(ws)
return payment_records
def write_summary_from_records(wb, month, payment_records):
"""从奖金发放记录生成月汇总"""
ws = wb.create_sheet(f'{month}月汇总')
rn = 1
# 按发放类型分section
by_type = defaultdict(list)
for r in payment_records:
by_type[r['发放类型']].append({'车牌':r['车牌号'],'销售':r['业务员'],'部门':r['部门'],'':r['实发金额']})
# 考核应发(按类型)
ws.cell(row=rn, column=1, value='一、考核应发明细').font=Font(bold=True, size=12); rn+=2
by_type_应发 = defaultdict(list)
for r in payment_records:
by_type_应发[r['发放类型']].append({'车牌':r['车牌号'],'销售':r['业务员'],'部门':r['部门'],'':r['考核应发']})
for cat in sorted(by_type_应发.keys()):
rn = write_sec(ws, rn, f'考核应发-{cat}', by_type_应发[cat])
total_应发 = sum(r['考核应发'] for r in payment_records)
WR(ws, rn, ['考核应发合计', '', R(total_应发)]); ws.cell(row=rn,column=1).font=Font(bold=True,size=11); rn+=2
# 亏损拦截
blocked = [r for r in payment_records if r['客户盈亏'] == '' and r['考核应发'] > 0]
unmatched = [r for r in payment_records if r['客户盈亏'] == '未匹配' and r['考核应发'] > 0]
if blocked or unmatched:
ws.cell(row=rn, column=1, value='二、亏损筛选').font=Font(bold=True, size=12); rn+=2
if blocked:
bl_dl = [{'车牌':r['车牌号'],'销售':r['业务员'],'部门':r['部门'],'':r['亏损拦截']} for r in blocked]
rn = write_sec(ws, rn, '亏损拦截(客户亏损不发放)', bl_dl)
if unmatched:
um_dl = [{'车牌':r['车牌号'],'销售':r['业务员'],'部门':r['部门'],'':r['亏损拦截']} for r in unmatched]
rn = write_sec(ws, rn, '未匹配亏损表(需人工确认)', um_dl)
total_拦截 = sum(r['亏损拦截'] for r in payment_records)
WR(ws, rn, ['拦截合计', '', R(total_拦截)]); ws.cell(row=rn,column=1).font=Font(bold=True); rn+=2
# 最终发放(按销售人员)
ws.cell(row=rn, column=1, value='三、最终发放').font=Font(bold=True, size=12); rn+=2
passed = [r for r in payment_records if r['实发金额'] > 0]
passed_dl = [{'车牌':r['车牌号'],'销售':r['业务员'],'部门':r['部门'],'':r['实发金额']} for r in passed]
rn = write_sec(ws, rn, '最终发放明细', passed_dl)
# 最终发放合计(按销售/按部门)
total_by_person = defaultdict(lambda: {'部门':'','':0})
for r in passed:
total_by_person[r['业务员']][''] += r['实发金额']
total_by_person[r['业务员']]['部门'] = r['部门']
ws.cell(row=rn, column=1, value=f'{month}月最终发放(按销售人员)').font=Font(bold=True, size=11); rn+=1
WH(ws, ['销售人员','部门名称','最终发放'], rn); rn+=1
gt = 0
for p in sorted(total_by_person.keys()):
d = total_by_person[p]
WR(ws, rn, [p, d['部门'], R(d[''])]); gt += d['']; rn+=1
WR(ws, rn, ['合计','', R(gt)]); ws.cell(row=rn,column=1).font=hf; rn+=2
by_dept = defaultdict(float)
for p, d in total_by_person.items(): by_dept[d['部门']] += d['']
ws.cell(row=rn, column=1, value=f'{month}月最终发放(按部门)').font=Font(bold=True, size=11); rn+=1
WH(ws, ['部门名称','最终发放'], rn); rn+=1
for dept in sorted(by_dept.keys()):
WR(ws, rn, [dept, R(by_dept[dept])]); rn+=1
WR(ws, rn, ['合计', R(gt)]); ws.cell(row=rn,column=1).font=hf; rn+=2
# 汇总数字
total_实发 = sum(r['实发金额'] for r in payment_records)
total_拦截 = sum(r['亏损拦截'] for r in payment_records)
ws.cell(row=rn, column=1, value='总览').font=Font(bold=True, size=12); rn+=1
WR(ws, rn, ['考核应发', R(total_应发)]); rn+=1
WR(ws, rn, ['亏损拦截', R(total_拦截)]); rn+=1
WR(ws, rn, ['最终发放', R(total_实发)]); ws.cell(row=rn,column=1).font=Font(bold=True,size=12)
ws.cell(row=rn,column=2).font=Font(bold=True,size=12)
AW(ws)
# 保留旧函数兼容3月无亏损表时使用
def write_summary_month(wb, month, month_data, section_names, loss_data=None, plate_client=None): def write_summary_month(wb, month, month_data, section_names, loss_data=None, plate_client=None):
ws = wb.create_sheet(f'{month}月汇总') ws = wb.create_sheet(f'{month}月汇总')
rn=1 rn=1
@@ -285,7 +445,10 @@ def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, ve
if g_cur: break if g_cur: break
if not g_cur: continue if not g_cur: continue
first = g_cur['recs'][0] if g_cur['recs']:
first = g_cur['recs'][0]
else:
first = {'合同编号':'','客户名称':'(无考核记录)','考核目标':g_cur.get('考核目标','')}
mkm = g_cur['目标km'] mkm = g_cur['目标km']
# 第1行车辆信息 + 各月里程/目标 # 第1行车辆信息 + 各月里程/目标

15
main.py
View File

@@ -82,17 +82,16 @@ for settle_month in [1, 2, 3]:
else: else:
write_calc_process_mar(wb, G[1], G[2], G[3], feb_data) write_calc_process_mar(wb, G[1], G[2], G[3], feb_data)
# Sheet 4: 汇总
if settle_month == 1:
write_summary_jan(wb, D[1], loss_data[1], plate_client)
elif settle_month == 2:
write_summary_month(wb, 2, feb_data, ['结转','补发1月','当月','累计补发2月'], loss_data[2], plate_client)
else:
write_summary_month(wb, 3, mar_data, ['结转','补发1月','补发2月','当月','累计补发3月'], loss_data[3], plate_client)
# Sheet 5: 车辆考核追踪 # Sheet 5: 车辆考核追踪
write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data[settle_month], plate_client) write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data[settle_month], plate_client)
# Sheet 6: 奖金发放记录(叠加亏损筛选,补发查对应月盈亏)
payment_records = build_payment_records(settle_month, month_data, loss_data, plate_client)
write_payment_record_sheet(wb, settle_month, payment_records)
# Sheet 7: 月汇总(从发放记录生成)
write_summary_from_records(wb, settle_month, payment_records)
# Sheet 6-17: 业务员 # Sheet 6-17: 业务员
for person in sorted(all_persons.keys()): for person in sorted(all_persons.keys()):
write_salesperson_sheet(wb, person, all_persons[person], settle_month, D, G, month_data, vehicle_payments) write_salesperson_sheet(wb, person, all_persons[person], settle_month, D, G, month_data, vehicle_payments)