перед настройкой выгрузки из СКУД, вместо ps1.
This commit is contained in:
@@ -57,19 +57,18 @@ def load_scud_data():
|
||||
val_status = str(row.get(status_col, '')).strip().lower() if status_col else ''
|
||||
val_time = str(row.get(start_col, '')).strip() if start_col else ''
|
||||
|
||||
# Разделение: Слово "Отсутствовал" в СКУД или пустая метка времени = ФИЗИЧЕСКИ НЕ БЫЛО
|
||||
if 'отсутствовал' in val_status or val_time in ['нет входа', 'none', 'nan', '', '00:00']:
|
||||
return False
|
||||
|
||||
# Во всех остальных случаях (есть время прохода) считаем, что человек ФИЗИЧЕСКИ переступил порог
|
||||
return True
|
||||
|
||||
df_scud['Пришел'] = df_scud.apply(is_person_present, axis=1)
|
||||
|
||||
# Отмечаем флаг исключений, но НЕ удаляем сразу
|
||||
df_scud['is_excluded'] = df_scud.apply(
|
||||
lambda r: is_excluded(r.get('Сотрудник', ''), r.get('Подразделение', ''), r.get('Должность', ''), exceptions),
|
||||
axis=1
|
||||
)
|
||||
return df_scud[~df_scud['is_excluded']].copy()
|
||||
return df_scud
|
||||
|
||||
def load_all_data():
|
||||
df_scud = load_scud_data()
|
||||
|
||||
+69
-19
@@ -33,6 +33,7 @@ FILL_HEADER = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="s
|
||||
FILL_TOTAL_LIST = PatternFill(start_color="F2F2F2", end_color="F2F2F2", fill_type="solid")
|
||||
FILL_UNEXPLAINED = PatternFill(start_color="FCE4D6", end_color="FCE4D6", fill_type="solid")
|
||||
FILL_PRESENT = PatternFill(start_color="E2EFDA", end_color="E2EFDA", fill_type="solid")
|
||||
FILL_ANOMALY = PatternFill(start_color="FCE4D6", end_color="FCE4D6", fill_type="solid")
|
||||
|
||||
CATEGORY_PASTEL_COLORS = ["FFF2CC", "E1D5E7", "E1F5FE", "FFF0F5", "E8F8F5", "FCF3CF"]
|
||||
|
||||
@@ -45,7 +46,7 @@ def apply_borders_to_cell(cell, border=THIN_BORDER):
|
||||
cell.border = border
|
||||
|
||||
|
||||
def format_row_cells(ws, r_num, fill_obj, is_bold=False, align_b="right", bold_font=None):
|
||||
def format_row_cells(ws, r_num, fill_obj, is_bold=False, align_b="right", bold_font=None, wrap_b=False):
|
||||
cell_a = ws.cell(row=r_num, column=1)
|
||||
cell_b = ws.cell(row=r_num, column=2)
|
||||
if fill_obj:
|
||||
@@ -57,7 +58,7 @@ def format_row_cells(ws, r_num, fill_obj, is_bold=False, align_b="right", bold_f
|
||||
cell_a.font = bold_font
|
||||
cell_b.font = bold_font
|
||||
if align_b:
|
||||
cell_b.alignment = Alignment(horizontal=align_b)
|
||||
cell_b.alignment = Alignment(horizontal=align_b, vertical="center", wrap_text=wrap_b)
|
||||
|
||||
|
||||
# --- 1. СВОДКА НА СЕГОДНЯ ---
|
||||
@@ -70,11 +71,10 @@ def generate_summary_excel(merged_df, date_str="27.07.2026", filename=None):
|
||||
ws = wb.active
|
||||
ws.title = "Лист_1"
|
||||
|
||||
# 🎯 ДВОЙНОЕ ВКЛЮЧЕНИЕ СИМВОЛОВ СТРУКТУРЫ В EXCEL (Обязательно для вывода плюсиков)
|
||||
ws.sheet_properties.outlinePr.summaryBelow = False
|
||||
ws.sheet_properties.outlinePr.summaryRight = False
|
||||
ws.sheet_properties.outlinePr.showOutlineSymbols = True # Включает боковую панель
|
||||
ws.sheet_view.showOutlineSymbols = True # Отображает плюсики
|
||||
ws.sheet_properties.outlinePr.showOutlineSymbols = True
|
||||
ws.sheet_view.showOutlineSymbols = True
|
||||
|
||||
bold_font = Font(name="Calibri", size=11, bold=True)
|
||||
|
||||
@@ -93,24 +93,26 @@ def generate_summary_excel(merged_df, date_str="27.07.2026", filename=None):
|
||||
|
||||
current_row = 4
|
||||
|
||||
# 3. НЕИЗВЕСТНО (По умолчанию РАСКРЫТО: hidden=False)
|
||||
# 3. НЕИЗВЕСТНО (По умолчанию РАСКРЫТО, нумерация 1. ФИО)
|
||||
unexplained = merged_df[(merged_df['Пришел'] == False) & (merged_df['Вид_отсутствия'].isna())]
|
||||
ws.cell(row=current_row, column=1, value="неизвестно")
|
||||
ws.cell(row=current_row, column=2, value=len(unexplained))
|
||||
format_row_cells(ws, current_row, FILL_UNEXPLAINED, is_bold=True, bold_font=bold_font)
|
||||
current_row += 1
|
||||
|
||||
for fio in sorted(unexplained['Сотрудник'].dropna().unique()):
|
||||
ws.cell(row=current_row, column=1, value=fio)
|
||||
for idx, fio in enumerate(sorted(unexplained['Сотрудник'].dropna().unique()), 1):
|
||||
ws.cell(row=current_row, column=1, value=f"{idx}. {fio}")
|
||||
format_row_cells(ws, current_row, FILL_UNEXPLAINED, is_bold=False)
|
||||
ws.row_dimensions[current_row].outlineLevel = 1
|
||||
ws.row_dimensions[current_row].hidden = False
|
||||
current_row += 1
|
||||
|
||||
# 4. КАТЕГОРИИ ОТСУТСТВИЙ (Свернуты hidden=True, под плюсики)
|
||||
absent_groups = merged_df[merged_df['Вид_отсутствия'].notna()].groupby('Вид_отсутствия')
|
||||
for idx, (cat_name, group) in enumerate(absent_groups):
|
||||
hex_color = CATEGORY_PASTEL_COLORS[idx % len(CATEGORY_PASTEL_COLORS)]
|
||||
# 4. КАТЕГОРИИ ОТСУТСТВИЙ (Свернуты hidden=True)
|
||||
absent_only = merged_df[(merged_df['Пришел'] == False) & (merged_df['Вид_отсутствия'].notna())]
|
||||
absent_groups = absent_only.groupby('Вид_отсутствия')
|
||||
|
||||
for idx_cat, (cat_name, group) in enumerate(absent_groups):
|
||||
hex_color = CATEGORY_PASTEL_COLORS[idx_cat % len(CATEGORY_PASTEL_COLORS)]
|
||||
cat_fill = PatternFill(start_color=hex_color, end_color=hex_color, fill_type="solid")
|
||||
|
||||
ws.cell(row=current_row, column=1, value=cat_name)
|
||||
@@ -118,21 +120,69 @@ def generate_summary_excel(merged_df, date_str="27.07.2026", filename=None):
|
||||
format_row_cells(ws, current_row, cat_fill, is_bold=True, bold_font=bold_font)
|
||||
current_row += 1
|
||||
|
||||
for fio in sorted(group['Сотрудник'].dropna().unique()):
|
||||
ws.cell(row=current_row, column=1, value=fio)
|
||||
for idx, fio in enumerate(sorted(group['Сотрудник'].dropna().unique()), 1):
|
||||
ws.cell(row=current_row, column=1, value=f"{idx}. {fio}")
|
||||
format_row_cells(ws, current_row, cat_fill, is_bold=False)
|
||||
ws.row_dimensions[current_row].outlineLevel = 1
|
||||
ws.row_dimensions[current_row].hidden = True
|
||||
current_row += 1
|
||||
|
||||
# 5. ИТОГО НА РАБОТЕ
|
||||
present = merged_df[merged_df['Пришел'] == True]
|
||||
# 🎯 5. ИТОГО НА РАБОТЕ (Включает: пришли по СКУД + Командировки + Удаленная работа)
|
||||
is_working_mask = (merged_df['Пришел'] == True) | (
|
||||
merged_df['Вид_отсутствия'].astype(str).str.lower().str.contains('командировк|удален|дистанцион|разъездн', regex=True, na=False)
|
||||
)
|
||||
present = merged_df[is_working_mask]
|
||||
|
||||
ws.cell(row=current_row, column=1, value="Итого на работе")
|
||||
ws.cell(row=current_row, column=2, value=len(present))
|
||||
format_row_cells(ws, current_row, FILL_PRESENT, is_bold=True, bold_font=bold_font)
|
||||
current_row += 1
|
||||
|
||||
ws.column_dimensions['A'].width = 35.0
|
||||
ws.column_dimensions['B'].width = 12.0
|
||||
# 🚨 6. АНОМАЛИИ (В самом низу таблицы, раскрывающийся список)
|
||||
# Столбец A: f"{idx}. {fio} (На работе)"
|
||||
# Столбец B: f"В 1С: {reason}"
|
||||
# Ширина столбца B = 32.0 (ровно 230px). Автоперенос + динамическая высота строк.
|
||||
anomalies = merged_df[(merged_df['Пришел'] == True) & (merged_df['Вид_отсутствия'].notna())]
|
||||
|
||||
ws.cell(row=current_row, column=1, value="Аномалии")
|
||||
ws.cell(row=current_row, column=2, value=len(anomalies))
|
||||
format_row_cells(ws, current_row, FILL_ANOMALY, is_bold=True, bold_font=bold_font)
|
||||
current_row += 1
|
||||
|
||||
chars_per_line_b = 30 # Влезает символов в столбец B шириной 230px (32.0 units)
|
||||
|
||||
if not anomalies.empty:
|
||||
for idx, (_, row) in enumerate(anomalies.iterrows(), 1):
|
||||
fio = row.get('Сотрудник', '')
|
||||
reason = row.get('Вид_отсутствия', 'Неизвестная причина')
|
||||
reason_text = f"В 1С: {reason}"
|
||||
|
||||
cell_a = ws.cell(row=current_row, column=1, value=f"{idx}. {fio} (На работе)")
|
||||
cell_b = ws.cell(row=current_row, column=2, value=reason_text)
|
||||
|
||||
cell_a.fill = FILL_ANOMALY
|
||||
cell_b.fill = FILL_ANOMALY
|
||||
apply_borders_to_cell(cell_a)
|
||||
apply_borders_to_cell(cell_b)
|
||||
|
||||
# Автоперенос текста в столбце B
|
||||
cell_b.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
|
||||
cell_a.alignment = Alignment(horizontal="left", vertical="center")
|
||||
|
||||
# Адаптивный расчет высоты строки
|
||||
if len(reason_text) > chars_per_line_b:
|
||||
lines_count = math.ceil(len(reason_text) / chars_per_line_b)
|
||||
ws.row_dimensions[current_row].height = max(lines_count * 18, 22)
|
||||
else:
|
||||
ws.row_dimensions[current_row].height = 20
|
||||
|
||||
ws.row_dimensions[current_row].outlineLevel = 1
|
||||
ws.row_dimensions[current_row].hidden = True # Свернут по умолчанию
|
||||
current_row += 1
|
||||
|
||||
# 📏 Настройка ширины колонок: B = 32.0 (ровно 230 пикселей)
|
||||
ws.column_dimensions['A'].width = 45.0
|
||||
ws.column_dimensions['B'].width = 32.0 # Ровно 230px в Excel
|
||||
|
||||
try:
|
||||
wb.save(output_path)
|
||||
@@ -225,7 +275,7 @@ def generate_detailed_excel(merged_df, date_str="26.07.2026", filename=None):
|
||||
for col in ws.columns:
|
||||
col_letter = get_column_letter(col[0].column)
|
||||
if col_letter == 'G':
|
||||
ws.column_dimensions['G'].width = 25.0 # Ровно 180 пикселей
|
||||
ws.column_dimensions['G'].width = 25.0
|
||||
else:
|
||||
max_len = 0
|
||||
for cell in col:
|
||||
|
||||
Reference in New Issue
Block a user