feat(reports): add simplified report builder, fix nan/inf crash and separate unhired/no-pass staff
This commit is contained in:
@@ -18,12 +18,17 @@ def generate_detailed_excel(merged_df, date_str="20.08.2026", filename=None):
|
||||
if not filename:
|
||||
filename = f"{format_date_ru(date_clean)} отчет.xlsx"
|
||||
|
||||
df_export = merged_df[merged_df.get('is_excluded', False) == False].copy() if merged_df is not None and not merged_df.empty else pd.DataFrame()
|
||||
is_exc = merged_df.get('is_excluded', False) == True
|
||||
is_not_hired = merged_df.get('not_hired_yet', False) == True
|
||||
|
||||
# Исключаем из отчета за вчера сотрудников-исключений И тех, кто еще не принят на работу
|
||||
df_export = merged_df[(~is_exc) & (~is_not_hired)].copy() if merged_df is not None and not merged_df.empty else pd.DataFrame()
|
||||
|
||||
target_dir = get_dated_reports_dir(date_clean)
|
||||
output_path = os.path.join(target_dir, filename)
|
||||
|
||||
wb = xlsxwriter.Workbook(output_path)
|
||||
# Включаем опцию защиты nan_inf_to_errors
|
||||
wb = xlsxwriter.Workbook(output_path, {'nan_inf_to_errors': True})
|
||||
ws = wb.add_worksheet("Детальный_отчет")
|
||||
|
||||
fmt_date_lbl = wb.add_format({'font_name': 'Arial', 'font_size': 10, 'bold': True})
|
||||
@@ -46,18 +51,32 @@ def generate_detailed_excel(merged_df, date_str="20.08.2026", filename=None):
|
||||
|
||||
for idx, row in df_export.reset_index(drop=True).iterrows():
|
||||
row_num = 4 + idx
|
||||
is_present = row.get('Пришел', False)
|
||||
is_present = bool(row.get('Пришел', False))
|
||||
absence_reason = row.get('Вид_отсутствия', '')
|
||||
has_reason = pd.notna(absence_reason) and str(absence_reason).strip() != ''
|
||||
has_reason = pd.notna(absence_reason) and str(absence_reason).strip() not in ['', 'nan', 'None']
|
||||
|
||||
in_val = str(row.get(start_col, 'Нет входа')).strip()
|
||||
if in_val.lower() in ['nan', 'none']: in_val = 'Нет входа'
|
||||
|
||||
out_val = str(row.get(end_col, 'Нет выхода')).strip()
|
||||
in_building_str = str(row.get(hours_col, '00:00'))
|
||||
if out_val.lower() in ['nan', 'none']: out_val = 'Нет выхода'
|
||||
|
||||
in_building_str = str(row.get(hours_col, '00:00')).strip()
|
||||
if in_building_str.lower() in ['nan', 'none']: in_building_str = '00:00'
|
||||
|
||||
first_act_val = str(row.get('Первая_активность', '—')).strip()
|
||||
if first_act_val.lower() in ['nan', 'none']: first_act_val = '—'
|
||||
has_first_act = first_act_val not in ['—', '', 'None', 'nan']
|
||||
|
||||
deviation_val = calculate_deviation(in_building_str, reason=absence_reason if has_reason else "", norm_hours=8, lunch_minutes=30)
|
||||
dept_scud_val = row.get('department_scud', row.get('department', row.get('Подразделение', '')))
|
||||
|
||||
dept_raw = row.get('department_scud', row.get('department', row.get('Подразделение', '')))
|
||||
dept_scud_val = str(dept_raw).strip() if pd.notna(dept_raw) else '—'
|
||||
if dept_scud_val.lower() in ['nan', 'none']: dept_scud_val = '—'
|
||||
|
||||
fio_raw = row.get('Сотрудник', '')
|
||||
fio_val = str(fio_raw).strip() if pd.notna(fio_raw) else ''
|
||||
if fio_val.lower() in ['nan', 'none']: fio_val = ''
|
||||
|
||||
row_color = None
|
||||
if is_present and has_reason:
|
||||
@@ -67,24 +86,29 @@ def generate_detailed_excel(merged_df, date_str="20.08.2026", filename=None):
|
||||
elif not is_present and not has_reason and not has_first_act:
|
||||
row_color = '#FCE4D6'
|
||||
|
||||
val_h_str = str(absence_reason) if has_reason else ""
|
||||
val_h_str = str(absence_reason).strip() if has_reason else ""
|
||||
if val_h_str.lower() in ['nan', 'none']: val_h_str = ""
|
||||
|
||||
lines_count = math.ceil(len(val_h_str) / chars_per_line_h) if len(val_h_str) > chars_per_line_h else 1
|
||||
ws.set_row(row_num, max(lines_count * 18, 20))
|
||||
|
||||
values = [
|
||||
(idx + 1, 'center', False),
|
||||
(row.get('Сотрудник', ''), 'left', False),
|
||||
(fio_val, 'left', False),
|
||||
(dept_scud_val, 'center', False),
|
||||
(in_val, 'center', False),
|
||||
(first_act_val, 'center', False),
|
||||
(out_val, 'center', False),
|
||||
(in_building_str, 'center', False),
|
||||
(absence_reason if has_reason else '', 'left', True),
|
||||
(val_h_str, 'left', True),
|
||||
(8, 'center', False),
|
||||
(deviation_val, 'center', False)
|
||||
]
|
||||
|
||||
for col_idx, (val, align_type, is_wrap) in enumerate(values):
|
||||
# Санитизация для исключения передачи float('nan') в _write_number
|
||||
if pd.isna(val) or val is None or str(val).strip().lower() == 'nan':
|
||||
val = ""
|
||||
fmt = create_xlsx_format(wb, font_name='Arial', font_size=10, bg_color=row_color, align=align_type, wrap=is_wrap)
|
||||
ws.write(row_num, col_idx, val, fmt)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user