feat(turnstile): двухконтурный учет СКУД, реестры исключений с автокомплитом 1С и калибровка таймзон
This commit is contained in:
+16
-21
@@ -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('Подразделение', '')))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user