3 Commits

Author SHA1 Message Date
kkfluous
5a69ef2993 V2.5.0 车辆追踪:多业务员拆行+车牌合并单元格
- 每个(车牌+业务员)独立一行,各自有独立的月度里程和累计
- 同车多人时车牌/车架号/归属公司等信息列合并单元格
- 奖金池(已发期数/金额/剩余)也合并(整车共享)
- 月度数据拆为应考核/实际/达标三列,不再挤在一个单元格
- 同人多条记录在达标列内换行显示各条

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:13:00 +08:00
kkfluous
04b6035d52 fix: 车辆追踪同人多条记录逐条显示,不合并里程
之前同人多条记录合并为一个总里程/总目标,但达标标记取"任一达标",
导致总里程<总目标却显示✓(如粤AGE4080: 1824/1839 ✓)。
改为每条记录独立显示里程/目标/达标,避免误导。
月度颜色也改为"全部达标才绿,否则红"。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:09:21 +08:00
kkfluous
4c43c00e73 feat: 车辆考核追踪sheet美化
- 达标月份绿底✓,未达标红底✗
- 累计达标/未达标加粗+颜色
- 有发放的行金底高亮
- 奖金池已发期数蓝底加粗,显示为"N/12"格式
- 车辆基本信息列浅灰底色区分
- 冻结首行+首列,支持滚动查看
- 开启自动筛选
- 优化列宽

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:02:50 +08:00

View File

@@ -341,18 +341,28 @@ def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, ve
def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info): def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info):
ws = wb.create_sheet('车辆考核追踪') ws = wb.create_sheet('车辆考核追踪')
wrap_align = Alignment(wrap_text=True, vertical='top') from calc_engine import VEHICLE_TARGET_MAP, DAYS
# 表头:每月用一列"业务员/里程/目标/达标"(多人换行) center_top = Alignment(horizontal='center', vertical='top', wrap_text=True)
headers = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励金额'] left_top = Alignment(vertical='top', wrap_text=True)
green_font = Font(color='006100')
green_fill = PatternFill(start_color='C6EFCE', end_color='C6EFCE', fill_type='solid')
red_font = Font(color='9C0006')
red_fill = PatternFill(start_color='FFC7CE', end_color='FFC7CE', fill_type='solid')
grey_fill = PatternFill(start_color='F2F2F2', end_color='F2F2F2', fill_type='solid')
blue_fill = PatternFill(start_color='DAEEF3', end_color='DAEEF3', fill_type='solid')
gold_fill = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
# 表头:基本信息 + 业务员 + 每月(应考核/实际/达标) + 累计 + 发放 + 奖金池
headers = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励','业务员']
info_cols = 6 # 前6列是车辆信息需要合并
for m in range(1, settle_month+1): for m in range(1, settle_month+1):
headers.append(f'{m}月考核明细') headers += [f'{m}考核', f'{m}月实际', f'{m}月达标']
if settle_month >= 2: if settle_month >= 2:
headers += ['累计里程/目标','累计达标'] headers += ['累计应完成','累计实际','累计达标']
headers += ['本月发放明细','累计已发期数','累计已发金额','剩余可发期数'] headers += ['本月发放金额','发放类型','已发期数','已发金额','剩余期数']
WH(ws, headers) WH(ws, headers)
ws.freeze_panes = 'H2' # 冻结车辆信息+业务员列
from calc_engine import VEHICLE_TARGET_MAP, RULES
rn = 2 rn = 2
for mv in master_vehicles: for mv in master_vehicles:
@@ -360,7 +370,6 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p
info = vehicle_info.get(plate, {}) info = vehicle_info.get(plate, {})
pays = vehicle_payments.get(plate, []) pays = vehicle_payments.get(plate, [])
# 补全缺失的考核目标
target_name = info.get('考核目标','') target_name = info.get('考核目标','')
monthly_bonus = info.get('月度奖励',0) monthly_bonus = info.get('月度奖励',0)
if not target_name: if not target_name:
@@ -370,50 +379,146 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p
if mapped: if mapped:
target_name, _, monthly_bonus = mapped target_name, _, monthly_bonus = mapped
row=[plate,mv.get('车架号',''),mv.get('归属公司',''),mv.get('车型确定',''), # 收集该车所有(业务员) - 跨月去重
target_name or '',monthly_bonus or ''] person_set = {}
for m in range(1, settle_month+1):
for k,g in G.get(m, {}).items():
if k[0] == plate:
person_set[k[1]] = g['部门']
persons = sorted(person_set.keys())
if not persons:
persons = [''] # 无考核记录也占一行
start_rn = rn
for pi, person in enumerate(persons):
sd = person_set.get(person,'').replace('业务','') if person else ''
person_label = f'{sd}-{person}' if person and sd else person
# 车辆信息(只在第一行写,后面留空等合并)
if pi == 0:
row_base = [plate, mv.get('车架号',''), mv.get('归属公司',''),
mv.get('车型确定',''), target_name or '', monthly_bonus or '']
else:
row_base = ['','','','','','']
row = row_base + [person_label]
# 每月数据按该业务员的group
cum_t = 0; cum_a = 0 cum_t = 0; cum_a = 0
for m in range(1, settle_month+1): for m in range(1, settle_month+1):
mgs=[(k,g) for k,g in G.get(m,{}).items() if k[0]==plate] gm = G.get(m, {}).get((plate, person)) if person else None
if mgs: if gm:
# 每个(车牌,销售)组一行,换行显示 # 逐条记录汇总(同人同月可能多条)
lines = [] t_sum = gm['应考核']; a_sum = gm['实际']
for _,g in sorted(mgs, key=lambda x: x[0][1]): cum_t += t_sum; cum_a += a_sum
t=g['应考核']; a=g['实际'] # 每条记录的达标情况
cum_t+=t; cum_a+=a rec_details = []
q='达标' if g['有达标'] else '未达标' for rec in gm['recs']:
sd = g['部门'].replace('业务','') if '业务' in g.get('部门','') else g.get('部门','') rq = rec['是否达标'] == '达标'
lines.append(f"{sd}-{g['销售']}: {R(a,0)}/{R(t,0)} {q}") rec_details.append(f"{R(rec['实际行驶里程(km)'],0)}/{R(rec['应考核里程(km)'],0)}{'' if rq else ''}")
row.append('\n'.join(lines)) all_q = all(rec['是否达标'] == '达标' for rec in gm['recs'])
if len(gm['recs']) == 1:
detail = rec_details[0].split('')[0].split('')[0] # 只取数字
row += [R(t_sum), R(a_sum), '' if all_q else '']
else: else:
row.append('') # 多条:显示汇总,但单元格内注明各条
row += [R(t_sum), R(a_sum), '\n'.join(rec_details)]
else:
row += ['', '', '']
# 累计
if settle_month >= 2: if settle_month >= 2:
cum_q='达标' if (cum_a>=cum_t and cum_t>0) else '未达标' if cum_t > 0:
row+=[f'{R(cum_a,0)}/{R(cum_t,0)}', cum_q] cum_q = cum_a >= cum_t
row += [R(cum_t), R(cum_a), '' if cum_q else '']
# 本月发放明细(多人多类型换行)
tp=[p for p in pays if p['结算月']==settle_month]
if tp:
pay_lines = []
for p in sorted(tp, key=lambda x: x['业务员']):
pd = p.get('部门','').replace('业务','') if '业务' in p.get('部门','') else p.get('部门','')
pay_lines.append(f"{pd}-{p['业务员']}: {R(p['金额'])}({p['类型']})")
row.append('\n'.join(pay_lines))
else: else:
row.append('') row += ['', '', '']
cum_q = False
else:
cum_q = False
# 奖金池截至settle_month # 本月发放(该业务员的
tp = [p for p in pays if p['结算月'] == settle_month and p['业务员'] == person]
if tp:
amt = sum(p['金额'] for p in tp)
types = ', '.join(p['类型'] for p in tp)
row += [R(amt), types]
else:
row += [0, '']
# 奖金池(整车,只在第一行显示)
if pi == 0:
pays_to_date = [p for p in pays if p['结算月'] <= settle_month] pays_to_date = [p for p in pays if p['结算月'] <= settle_month]
tp_count = len(pays_to_date); tp_amt = sum(p['金额'] for p in pays_to_date) tp_count = len(pays_to_date); tp_amt = sum(p['金额'] for p in pays_to_date)
row+=[tp_count,R(tp_amt) if tp_amt>0 else 0,12-tp_count] row += [f'{tp_count}/12', R(tp_amt) if tp_amt > 0 else 0, 12 - tp_count]
else:
row += ['', '', '']
WR(ws, rn, row) WR(ws, rn, row)
# 对含换行的单元格设置自动换行
for ci in range(len(row)): # 美化
cell = ws.cell(row=rn, column=ci+1) # 车辆信息列灰底
if isinstance(cell.value, str) and '\n' in cell.value: for ci in range(1, info_cols + 1):
cell.alignment = wrap_align ws.cell(row=rn, column=ci).fill = grey_fill
# 月度达标列着色
for m in range(1, settle_month + 1):
col_base = info_cols + 1 + (m - 1) * 3 # 业务员列后面
qual_col = col_base + 3 # 达标列
cell = ws.cell(row=rn, column=qual_col)
cell.alignment = center_top
val = cell.value
if val and '' in str(val) and '' not in str(val):
cell.fill = green_fill; cell.font = green_font
elif val and '' in str(val):
cell.fill = red_fill; cell.font = red_font
# 累计达标着色
if settle_month >= 2:
cum_qual_col = info_cols + 1 + settle_month * 3 + 3 # 累计达标列
cell = ws.cell(row=rn, column=cum_qual_col)
if cell.value == '':
cell.fill = green_fill; cell.font = Font(bold=True, color='006100')
elif cell.value == '':
cell.fill = red_fill; cell.font = Font(bold=True, color='9C0006')
# 发放金底
pay_col = info_cols + 1 + settle_month * 3 + (3 if settle_month >= 2 else 0) + 1
if tp:
ws.cell(row=rn, column=pay_col).fill = gold_fill
# 奖金池蓝底
if pi == 0 and tp_count > 0:
pool_col = pay_col + 2
ws.cell(row=rn, column=pool_col).fill = blue_fill
ws.cell(row=rn, column=pool_col).font = Font(bold=True)
rn += 1 rn += 1
AW(ws)
# 合并车辆信息列(如果多人)
if len(persons) > 1:
for ci in range(1, info_cols + 1):
ws.merge_cells(start_row=start_rn, start_column=ci,
end_row=start_rn + len(persons) - 1, end_column=ci)
ws.cell(row=start_rn, column=ci).alignment = Alignment(vertical='center', wrap_text=True)
# 奖金池列也合并
for offset in [2, 3, 4]: # 已发期数/已发金额/剩余期数
pool_col = pay_col + offset
ws.merge_cells(start_row=start_rn, start_column=pool_col,
end_row=start_rn + len(persons) - 1, end_column=pool_col)
# 列宽
col_widths = {'A':12,'B':18,'C':18,'D':10,'E':18,'F':8,'G':14}
for cl, w in col_widths.items():
ws.column_dimensions[cl].width = w
# 月度列
start = ord('H')
for m in range(settle_month):
for i in range(3):
cl = chr(start + m*3 + i)
if cl <= 'Z': ws.column_dimensions[cl].width = 12
# 剩余列
rem = start + settle_month * 3
for i in range(10):
cl = chr(rem + i)
if cl <= 'Z': ws.column_dimensions[cl].width = 14