feat(turnstile): двухконтурный учет СКУД, реестры исключений с автокомплитом 1С и калибровка таймзон

This commit is contained in:
2026-09-10 15:36:32 +03:00
parent 6c9b131cf2
commit 74332aa38a
31 changed files with 4509 additions and 10769 deletions
+16 -21
View File
@@ -71,20 +71,6 @@ def safe_close_workbook(wb, output_path, target_dir, filename):
return output_path
def calculate_autoclose_time(time_in_str: str) -> tuple[str, str, str]:
try:
parts = time_in_str.strip().split(':')
hh = int(parts[0])
mm = int(parts[1]) if len(parts) > 1 else 0
ss = int(parts[2]) if len(parts) > 2 else 0
dt_in = datetime(2000, 1, 1, hh, mm, ss)
dt_out = dt_in + timedelta(hours=8, minutes=30)
return dt_out.strftime("%H:%M:%S"), "08:30", "0:00"
except Exception:
return "17:00:00", "08:30", "0:00"
def calculate_deviation(time_in_building_str, reason="", norm_hours=8, lunch_minutes=30):
"""
Расчет отклонения от нормы.
@@ -228,7 +214,7 @@ def generate_summary_excel(merged_df, date_str="21.08.2026", filename=None):
ws.write(current_row, 1, "", fmt_np_rr)
current_row += 1
# 3. Официальные отсутствия (Сотрудники из исключений при наличии документа 1С попадают сюда)
# 3. Официальные отсутствия
reason_clean = merged_df['Вид_отсутствия'].astype(str).str.lower()
is_remote_reason = reason_clean.str.contains('удален|дистанцион', regex=True, na=False)
@@ -253,10 +239,16 @@ def generate_summary_excel(merged_df, date_str="21.08.2026", filename=None):
ws.write(current_row, 1, len(group), fmt_cat_hr)
current_row += 1
for fio in sorted(group['Сотрудник'].dropna().unique()):
is_other_category = (str(cat_name).strip().lower() == "иное")
for _, row in group.sort_values(by='Сотрудник').iterrows():
fio = row.get('Сотрудник', '')
# Если категория "Иное" — берем детальную причину из manual_absences / detailed_reason
detail_val = row.get('detailed_reason', row.get('comment', '')) if is_other_category else ""
ws.set_row(current_row, 20, None, {'level': 1, 'hidden': True, 'collapsed': True})
ws.write(current_row, 0, fio, fmt_cat_rl)
ws.write(current_row, 1, "", fmt_cat_rr)
ws.write(current_row, 1, detail_val, fmt_cat_rr)
current_row += 1
# 4. Итого на работе (Только общее число, без раскрывающегося списка ФИО. Включает исключения без справок)
@@ -404,10 +396,13 @@ def generate_detailed_excel(merged_df, date_str="20.08.2026", filename=None):
first_act_val = str(row.get('Первая_активность', '—')).strip()
has_first_act = first_act_val not in ['—', '', 'None', 'nan']
if in_val not in ['Нет входа', '—', '', 'nan', 'None'] and out_val in ['Нет выхода', '—', '', 'nan', 'None'] and not has_reason:
out_val, in_building_str, deviation_val = calculate_autoclose_time(in_val)
else:
deviation_val = calculate_deviation(in_building_str, reason=absence_reason if has_reason else "", norm_hours=8, lunch_minutes=30)
# Автозакрытие отключено по согласованию с ОК: сохраняем факт отсутствия выхода
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('Подразделение', '')))