diff --git a/excel_writer.py b/excel_writer.py index 28b88bb..19908d8 100644 --- a/excel_writer.py +++ b/excel_writer.py @@ -341,11 +341,10 @@ 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): ws = wb.create_sheet('车辆考核追踪') - from calc_engine import VEHICLE_TARGET_MAP + from calc_engine import VEHICLE_TARGET_MAP, DAYS - wrap_top = Alignment(wrap_text=True, vertical='top') - center_top = Alignment(horizontal='center', vertical='top') - # 颜色 + center_top = Alignment(horizontal='center', vertical='top', wrap_text=True) + 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') @@ -354,26 +353,22 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p 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 = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励'] - month_start_col = len(headers) # 月度考核开始列(0-indexed) + # 表头:基本信息 + 业务员 + 每月(应考核/实际/达标) + 累计 + 发放 + 奖金池 + headers = ['车牌号','车架号','归属公司','车型','考核目标','月度奖励','业务员'] + info_cols = 6 # 前6列是车辆信息(需要合并) for m in range(1, settle_month+1): - headers.append(f'{m}月考核明细') - cum_start_col = len(headers) + headers += [f'{m}月应考核', f'{m}月实际', f'{m}月达标'] if settle_month >= 2: - headers += ['累计里程/目标','累计达标'] - pay_start_col = len(headers) - headers += ['本月发放明细','已发期数','已发金额','剩余期数'] + headers += ['累计应完成','累计实际','累计达标'] + headers += ['本月发放金额','发放类型','已发期数','已发金额','剩余期数'] WH(ws, headers) + ws.freeze_panes = 'H2' # 冻结车辆信息+业务员列 - # 冻结首行+首列 - ws.freeze_panes = 'B2' - - rn=2 + rn = 2 for mv in master_vehicles: - plate=mv['车牌号'] - info=vehicle_info.get(plate, {}) - pays=vehicle_payments.get(plate, []) + plate = mv['车牌号'] + info = vehicle_info.get(plate, {}) + pays = vehicle_payments.get(plate, []) target_name = info.get('考核目标','') monthly_bonus = info.get('月度奖励',0) @@ -384,104 +379,146 @@ def write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_p if mapped: target_name, _, monthly_bonus = mapped - row=[plate,mv.get('车架号',''),mv.get('归属公司',''),mv.get('车型确定',''), - target_name or '',monthly_bonus or ''] - - cum_t=0; cum_a=0 - month_qualified = {} # {月份: bool} - 所有记录都达标才算True + # 收集该车所有(业务员) - 跨月去重 + person_set = {} for m in range(1, settle_month+1): - mgs=[(k,g) for k,g in G.get(m,{}).items() if k[0]==plate] - if mgs: - lines = [] - all_q = True # 是否全部达标 - for _,g in sorted(mgs, key=lambda x: x[0][1]): - cum_t+=g['应考核']; cum_a+=g['实际'] - sd = g['部门'].replace('业务','') if '业务' in g.get('部门','') else g.get('部门','') - # 逐条记录显示,不合并 - for rec in g['recs']: - rt=rec['应考核里程(km)']; ra=rec['实际行驶里程(km)'] - rq = rec['是否达标'] == '达标' - if not rq: all_q = False - lines.append(f"{sd}-{g['销售']}: {R(ra,0)}/{R(rt,0)} {'✓' if rq else '✗'}") - row.append('\n'.join(lines)) - month_qualified[m] = all_q + 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.append('') - month_qualified[m] = None + row_base = ['','','','','',''] - cum_q = False - if settle_month>=2: - cum_q = cum_a>=cum_t and cum_t>0 - row+=[f'{R(cum_a,0)}/{R(cum_t,0)}', '✓ 达标' if cum_q else '✗ 未达标'] + row = row_base + [person_label] - # 本月发放 - 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: - row.append('') + # 每月数据(按该业务员的group) + cum_t = 0; cum_a = 0 + for m in range(1, settle_month+1): + gm = G.get(m, {}).get((plate, person)) if person else None + if gm: + # 逐条记录汇总(同人同月可能多条) + t_sum = gm['应考核']; a_sum = gm['实际'] + cum_t += t_sum; cum_a += a_sum + # 每条记录的达标情况 + rec_details = [] + for rec in gm['recs']: + rq = rec['是否达标'] == '达标' + rec_details.append(f"{R(rec['实际行驶里程(km)'],0)}/{R(rec['应考核里程(km)'],0)}{'✓' if rq else '✗'}") + 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: + # 多条:显示汇总,但单元格内注明各条 + row += [R(t_sum), R(a_sum), '\n'.join(rec_details)] + else: + row += ['', '', ''] - 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) - row+=[f'{tp_count}/12', R(tp_amt) if tp_amt>0 else 0, 12-tp_count] + # 累计 + if settle_month >= 2: + if cum_t > 0: + cum_q = cum_a >= cum_t + row += [R(cum_t), R(cum_a), '✓' if cum_q else '✗'] + else: + row += ['', '', ''] + cum_q = False + else: + cum_q = False - WR(ws,rn,row) + # 本月发放(该业务员的) + 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, ''] - # --- 美化 --- - # 车辆基本信息列:浅灰底色 - for ci in range(1, month_start_col+1): - ws.cell(row=rn, column=ci).fill = grey_fill + # 奖金池(整车,只在第一行显示) + if pi == 0: + 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) + row += [f'{tp_count}/12', R(tp_amt) if tp_amt > 0 else 0, 12 - tp_count] + else: + row += ['', '', ''] - # 月度考核列:达标绿底/未达标红底 - for mi, m in enumerate(range(1, settle_month+1)): - col = month_start_col + mi + 1 - cell = ws.cell(row=rn, column=col) - cell.alignment = wrap_top - if month_qualified.get(m) == True: - cell.fill = green_fill; cell.font = green_font - elif month_qualified.get(m) == False: - cell.fill = red_fill; cell.font = red_font + WR(ws, rn, row) - # 累计列 - if settle_month >= 2: - cum_cell = ws.cell(row=rn, column=cum_start_col+2) # 累计达标列 - if cum_q: - cum_cell.fill = green_fill; cum_cell.font = Font(bold=True, color='006100') - elif cum_t > 0: - cum_cell.fill = red_fill; cum_cell.font = Font(bold=True, color='9C0006') + # 美化 + # 车辆信息列灰底 + for ci in range(1, info_cols + 1): + ws.cell(row=rn, column=ci).fill = grey_fill - # 发放列:有发放金底 - pay_cell = ws.cell(row=rn, column=pay_start_col+1) - pay_cell.alignment = wrap_top - if tp: - pay_cell.fill = gold_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 - # 奖金池:已发期数列 - period_cell = ws.cell(row=rn, column=pay_start_col+2) - period_cell.alignment = center_top - if tp_count > 0: - period_cell.fill = blue_fill; period_cell.font = Font(bold=True) + # 累计达标着色 + 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') - rn+=1 + # 发放金底 + 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 + + # 合并车辆信息列(如果多人) + 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':20,'C':20,'D':12,'E':20,'F':8} - for col_letter, w in col_widths.items(): - ws.column_dimensions[col_letter].width = w - # 月度考核列宽 - for mi in range(settle_month): - col_letter = chr(ord('G') + mi) - ws.column_dimensions[col_letter].width = 30 - # 剩余列自动 - remaining_start = ord('G') + settle_month - for i in range(8): - cl = chr(remaining_start + i) - if cl <= 'Z': - ws.column_dimensions[cl].width = 16 - - # 自动筛选 - ws.auto_filter.ref = f"A1:{chr(ord('A')+len(headers)-1)}1" + 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