12.08.2026 17:30 Логика формирования отчетов работает, все отсутствия учитываются, логика присутствия сотрудников в случае сбоя СКУД на работе настроена.

This commit is contained in:
2026-08-12 17:33:48 +03:00
parent ffb0e18635
commit 8897b14197
13 changed files with 467 additions and 192 deletions
+79 -44
View File
@@ -231,57 +231,73 @@ def get_latest_snapshot_time(date_str=None):
def load_scud_from_db_by_snapshot(date_str, snapshot_param=None):
"""
Загружает логи СКУД по ID снапшота ('20260805-001', 'Y20260805-001') или по штампу времени.
С автоматическим маппингом полей Подразделения и Должности.
Загружает логи СКУД по ID снапшота.
Если snapshot_param не задан (или искался прошлый день), приоритетно выбирает
финальный вечерний снапшот, начинающийся с буквы 'Y' (срез на 22:00) за дату date_str.
"""
with get_connection() as conn:
df = pd.DataFrame()
# 1. Если передан конкретный ID снапшота (например, '20260810-001')
if snapshot_param:
if "-" in str(snapshot_param) or str(snapshot_param).startswith('Y'):
df = pd.read_sql_query(
"SELECT * FROM scud_logs WHERE log_date = ? AND snapshot_id = ?",
conn, params=(date_str, str(snapshot_param))
)
if df.empty:
df = pd.read_sql_query(
"SELECT * FROM scud_logs WHERE snapshot_id = ?",
conn, params=(str(snapshot_param),)
)
df = pd.read_sql_query(
"SELECT * FROM scud_logs WHERE snapshot_id = ?",
conn, params=(str(snapshot_param),)
)
if df.empty:
df = pd.read_sql_query(
"SELECT * FROM scud_logs WHERE log_date = ? AND snapshot_time = ?",
conn, params=(date_str, str(snapshot_param))
)
if df.empty:
latest_time = get_latest_snapshot_time(date_str)
if latest_time:
df = pd.read_sql_query(
"SELECT * FROM scud_logs WHERE log_date = ? AND snapshot_time = ?",
conn, params=(date_str, latest_time)
)
if not df.empty:
df['Сотрудник'] = df['fio']
df['Пришел'] = df['is_present'].astype(int) == 1 if 'is_present' in df.columns else False
df['Начало_дня'] = df['time_in']
df['Первая_активность'] = df['first_activity']
df['Конец_дня'] = df['time_out']
df['Находился_в_здании'] = df['time_in_building']
# 2. Если по snapshot_param ничего не найдено ИЛИ snapshot_param=None (запрос за вчерашний день):
if df.empty and date_str:
cursor = conn.cursor()
# 💡 Маппинг колонок БД в стандартные русские наименования
if 'department' in df.columns and 'Подразделение' not in df.columns:
df['Подразделение'] = df['department']
if 'position' in df.columns and 'Должность' not in df.columns:
df['Должность'] = df['position']
else:
df = pd.DataFrame(columns=[
'Сотрудник', 'fio_clean', 'Подразделение', 'Должность',
'Начало_дня', 'Первая_активность', 'Конец_дня',
'Находился_в_здании', 'Пришел', 'anomaly_flag'
])
# ⭐️ ПРИОРИТЕТ 1: Ищем срез, начальный символ которого 'Y' (вечерний зафиксированный Y-снапшот)
cursor.execute(
"SELECT snapshot_id FROM scud_logs WHERE log_date = ? AND snapshot_id LIKE 'Y%' ORDER BY id DESC LIMIT 1",
(date_str,)
)
row = cursor.fetchone()
# ⭐️ ПРИОРИТЕТ 2: Если Y-снапшот не найден, берем самый свежий обычный
if not row:
cursor.execute(
"SELECT snapshot_id FROM scud_logs WHERE log_date = ? ORDER BY id DESC LIMIT 1",
(date_str,)
)
row = cursor.fetchone()
if row and row[0]:
target_snap_id = row[0]
df = pd.read_sql_query(
"SELECT * FROM scud_logs WHERE snapshot_id = ?",
conn, params=(target_snap_id,)
)
# Приводим названия колонок к стандарту
if not df.empty:
rename_map = {
'department': 'Подразделение',
'position': 'Должность',
'fio': 'Сотрудник',
'time_in': 'Начало_дня',
'first_activity': 'Первая_активность',
'time_out': 'Конец_дня',
'duration': 'Находился_в_здании',
'is_present': 'Пришел'
}
# Переименовываем имеющиеся колонки
df = df.rename(columns={k: v for k, v in rename_map.items() if k in df.columns})
# Заполняем недостающие колонки значениями по умолчанию, если их не было в БД
required_cols = ['Пришел', 'Начало_дня', 'Первая_активность', 'Конец_дня', 'Находился_в_здании', 'anomaly_flag']
for col in required_cols:
if col not in df.columns:
if col == 'Пришел':
df[col] = False
else:
df[col] = '—'
# Приводим тип флага Пришел к bool
if 'Пришел' in df.columns:
df['Пришел'] = df['Пришел'].astype(bool)
return df
@@ -443,3 +459,22 @@ def add_department_synonym_to_db(short_name, full_name):
)
conn.commit()
print(f"[✓] В базу SQLite добавлен новый синоним отдела: '{short_name}' ⟷ '{full_name}'")
def load_staff_from_db(date_str):
"""Загружает Штатное расписание 1С из базы SQLite за указанную дату."""
with get_connection() as conn:
df = pd.read_sql_query(
"SELECT fio as 'ФИО', fio_clean, department as 'Подразделение', position as 'Должность' FROM zup_staff WHERE snapshot_date = ?",
conn, params=(date_str,)
)
return df if not df.empty else None
def load_absences_from_db(date_str):
"""Загружает документальные отсутствия 1С из базы SQLite за указанную дату."""
with get_connection() as conn:
df = pd.read_sql_query(
"SELECT fio as 'ФИО', fio_clean, absence_type as 'Вид_отсутствия' FROM zup_absences WHERE absence_date = ?",
conn, params=(date_str,)
)
return df if not df.empty else None