фикс перед применением прямого запроса к базе, минуя файлы Excel.
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
[2026-07-28 14:45:26] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 14:45:26] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 14:45:26] [INFO] Файл Сотрудники_27.07.2026.xlsx уже существует. Удаляем для перезаписи...
|
||||||
|
[2026-07-28 14:45:26] [ERROR] ОШИБКА при обработке даты 27.07.2026: ('IM002', '[IM002] [Microsoft][Диспетчер драйверов ODBC] Источник данных не найден и не указан драйвер, используемый по умолчанию (0) (SQLDriverConnect)')
|
||||||
|
[2026-07-28 14:45:26] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 14:45:26] [INFO] Файл Сотрудники_28.07.2026.xlsx уже существует. Удаляем для перезаписи...
|
||||||
|
[2026-07-28 14:45:26] [ERROR] ОШИБКА при обработке даты 28.07.2026: ('IM002', '[IM002] [Microsoft][Диспетчер драйверов ODBC] Источник данных не найден и не указан драйвер, используемый по умолчанию (0) (SQLDriverConnect)')
|
||||||
|
[2026-07-28 14:45:26] [INFO] === Завершение работы скрипта ===
|
||||||
|
[2026-07-28 14:47:46] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 14:47:46] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 14:48:17] [INFO] База вернула 272 строк за 27.07.2026
|
||||||
|
[2026-07-28 14:48:17] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_27.07.2026.xlsx
|
||||||
|
[2026-07-28 14:48:17] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 14:48:44] [INFO] База вернула 272 строк за 28.07.2026
|
||||||
|
[2026-07-28 14:48:44] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_28.07.2026.xlsx
|
||||||
|
[2026-07-28 14:48:44] [INFO] === Завершение работы скрипта ===
|
||||||
|
[2026-07-28 14:49:55] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 14:49:55] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 14:49:55] [INFO] Файл Сотрудники_27.07.2026.xlsx уже существует. Удаляем для перезаписи...
|
||||||
|
[2026-07-28 14:50:17] [INFO] База вернула 272 строк за 27.07.2026
|
||||||
|
[2026-07-28 14:50:18] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_27.07.2026.xlsx
|
||||||
|
[2026-07-28 14:50:18] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 14:50:18] [INFO] Файл Сотрудники_28.07.2026.xlsx уже существует. Удаляем для перезаписи...
|
||||||
|
[2026-07-28 14:50:50] [INFO] База вернула 272 строк за 28.07.2026
|
||||||
|
[2026-07-28 14:50:50] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_28.07.2026.xlsx
|
||||||
|
[2026-07-28 14:50:50] [INFO] === Завершение работы скрипта ===
|
||||||
|
[2026-07-28 14:53:08] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 14:53:08] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 14:53:08] [INFO] Файл Сотрудники_27.07.2026.xlsx уже существует. Удаляем для перезаписи...
|
||||||
|
[2026-07-28 14:53:30] [INFO] База вернула 272 строк за 27.07.2026
|
||||||
|
[2026-07-28 14:53:30] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_27.07.2026.xlsx
|
||||||
|
[2026-07-28 14:53:30] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 14:53:30] [INFO] Файл Сотрудники_28.07.2026.xlsx уже существует. Удаляем для перезаписи...
|
||||||
|
[2026-07-28 14:53:52] [INFO] База вернула 272 строк за 28.07.2026
|
||||||
|
[2026-07-28 14:53:52] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_28.07.2026.xlsx
|
||||||
|
[2026-07-28 14:53:52] [INFO] === Завершение работы скрипта ===
|
||||||
|
[2026-07-28 15:12:05] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 15:12:05] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 15:12:05] [INFO] Файл Сотрудники_27.07.2026.xlsx существует. Перезаписываем...
|
||||||
|
[2026-07-28 15:12:05] [ERROR] ОШИБКА при обработке даты 27.07.2026: ('42000', '[42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]Неправильный синтаксис около конструкции "me". (102) (SQLExecDirectW)')
|
||||||
|
[2026-07-28 15:12:05] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 15:12:05] [INFO] Файл Сотрудники_28.07.2026.xlsx существует. Перезаписываем...
|
||||||
|
[2026-07-28 15:12:05] [ERROR] ОШИБКА при обработке даты 28.07.2026: ('42000', '[42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]Неправильный синтаксис около конструкции "me". (102) (SQLExecDirectW)')
|
||||||
|
[2026-07-28 15:12:05] [INFO] === Завершение работы скрипта ===
|
||||||
|
[2026-07-28 15:13:31] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 15:13:31] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 15:15:02] [INFO] База вернула 272 строк за 27.07.2026
|
||||||
|
[2026-07-28 15:15:03] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_27.07.2026.xlsx
|
||||||
|
[2026-07-28 15:15:03] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 15:16:20] [INFO] База вернула 272 строк за 28.07.2026
|
||||||
|
[2026-07-28 15:16:21] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_28.07.2026.xlsx
|
||||||
|
[2026-07-28 15:16:21] [INFO] === Завершение работы скрипта ===
|
||||||
|
[2026-07-28 15:17:22] [INFO] === Запуск скрипта экспорта СКУД ===
|
||||||
|
[2026-07-28 15:17:22] [INFO] --- Обработка периода: Вчера (27.07.2026) ---
|
||||||
|
[2026-07-28 15:17:22] [INFO] Файл Сотрудники_27.07.2026.xlsx существует. Перезаписываем...
|
||||||
|
[2026-07-28 15:17:46] [INFO] База вернула 272 строк за 27.07.2026
|
||||||
|
[2026-07-28 15:17:47] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_27.07.2026.xlsx
|
||||||
|
[2026-07-28 15:17:47] [INFO] --- Обработка периода: Сегодня (28.07.2026) ---
|
||||||
|
[2026-07-28 15:17:47] [INFO] Файл Сотрудники_28.07.2026.xlsx существует. Перезаписываем...
|
||||||
|
[2026-07-28 15:18:11] [INFO] База вернула 272 строк за 28.07.2026
|
||||||
|
[2026-07-28 15:18:12] [INFO] [SUCCESS] Успешно создан файл C:\scud_exchsnge\Сотрудники_28.07.2026.xlsx
|
||||||
|
[2026-07-28 15:18:12] [INFO] === Завершение работы скрипта ===
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
"""
|
||||||
|
Скрипт экспорта данных СКУД (Orion) в Excel.
|
||||||
|
Оптимизированная супер-быстрая версия (использует ROW_NUMBER() вместо подзапросов).
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
python scud_export.py
|
||||||
|
python scud_export.py -d
|
||||||
|
python scud_export.py --date 28.07.2026 -d
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import pyodbc
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
|
warnings.filterwarnings("ignore", message="pandas only supports SQLAlchemy connectable")
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 1. Настройки путей и логов
|
||||||
|
# ==============================================================================
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
LOG_DIR = os.path.join(SCRIPT_DIR, "logs")
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
TODAY_DATE_STR = datetime.now().strftime("%d.%m.%Y")
|
||||||
|
LOG_FILE = os.path.join(LOG_DIR, f"export_{TODAY_DATE_STR}.log")
|
||||||
|
|
||||||
|
logger = logging.getLogger("scud_export")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
_file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8")
|
||||||
|
_console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
_formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||||
|
_file_handler.setFormatter(_formatter)
|
||||||
|
_console_handler.setFormatter(_formatter)
|
||||||
|
logger.addHandler(_file_handler)
|
||||||
|
logger.addHandler(_console_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def log(message: str, level: str = "INFO"):
|
||||||
|
level_map = {
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"SUCCESS": logging.INFO,
|
||||||
|
}
|
||||||
|
if level == "SUCCESS":
|
||||||
|
logger.info(f"[SUCCESS] {message}")
|
||||||
|
else:
|
||||||
|
logger.log(level_map.get(level, logging.INFO), message)
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 2. Настройки базы данных и папки
|
||||||
|
# ==============================================================================
|
||||||
|
SERVER_NAME = r"192.168.1.253\SQL"
|
||||||
|
DATABASE_NAME = "Orion-14.01.21-1"
|
||||||
|
SQL_USER = "sa"
|
||||||
|
SQL_PASSWORD = "123456"
|
||||||
|
|
||||||
|
FOLDER_PATH = r"C:\scud_exchsnge"
|
||||||
|
ODBC_DRIVER = "SQL Server Native Client 10.0"
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 3. СУПЕР-ОПТИМИЗИРОВАННЫЙ SQL-ЗАПРОС (Мгновенное выполнение)
|
||||||
|
# ==============================================================================
|
||||||
|
SQL_QUERY_TEMPLATE = r"""
|
||||||
|
DECLARE @InputDate DATE = '{target_date}';
|
||||||
|
DECLARE @TargetDate DATE = @InputDate;
|
||||||
|
|
||||||
|
DECLARE @StartDate DATETIME = CAST(@TargetDate AS DATETIME);
|
||||||
|
DECLARE @EndDate DATETIME = DATEADD(SECOND, -1, DATEADD(DAY, 1, @StartDate));
|
||||||
|
|
||||||
|
-- 1. Фильтруем логи только за нужные 24 часа за один проход
|
||||||
|
WITH DailyLogs AS (
|
||||||
|
SELECT
|
||||||
|
log.HozOrgan AS EmployeeID,
|
||||||
|
log.TimeVal,
|
||||||
|
log.Event,
|
||||||
|
log.Mode,
|
||||||
|
CASE WHEN log.Event IN (28, 26, 32) OR log.Mode = 1 THEN 'IN'
|
||||||
|
WHEN log.Event IN (29, 27, 33) OR log.Mode = 2 THEN 'OUT'
|
||||||
|
ELSE 'OTHER' END AS Direction,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY log.HozOrgan ORDER BY log.TimeVal DESC) AS RowNumDesc
|
||||||
|
FROM pLogData log WITH (NOLOCK)
|
||||||
|
WHERE log.TimeVal BETWEEN @StartDate AND @EndDate
|
||||||
|
AND log.HozOrgan IS NOT NULL
|
||||||
|
AND log.HozOrgan > 0
|
||||||
|
AND (log.Event IN (28, 29, 26, 27, 32, 33) OR log.Mode IN (1, 2))
|
||||||
|
),
|
||||||
|
-- 2. Агрегируем времена входа/выхода и определяем последнее событие
|
||||||
|
Passages AS (
|
||||||
|
SELECT
|
||||||
|
EmployeeID,
|
||||||
|
MIN(CASE WHEN Direction = 'IN' THEN TimeVal END) AS FirstIn,
|
||||||
|
MAX(CASE WHEN Direction = 'OUT' THEN TimeVal END) AS LastOut,
|
||||||
|
MAX(CASE WHEN RowNumDesc = 1 THEN Direction END) AS LastEventType
|
||||||
|
FROM DailyLogs
|
||||||
|
GROUP BY EmployeeID
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
N'ЛЕНМОРНИИПРОЕКТ' AS [Фирма],
|
||||||
|
ISNULL(CAST(div.Name AS NVARCHAR(255)), N'Без подразделения') AS [Подразделение],
|
||||||
|
LTRIM(RTRIM(
|
||||||
|
ISNULL(CAST(p.Name AS NVARCHAR(255)), N'') +
|
||||||
|
CASE WHEN p.FirstName IS NOT NULL AND CAST(p.FirstName AS NVARCHAR(255)) <> ''
|
||||||
|
THEN N' ' + CAST(p.FirstName AS NVARCHAR(255)) ELSE N'' END +
|
||||||
|
CASE WHEN p.MidName IS NOT NULL AND CAST(p.MidName AS NVARCHAR(255)) <> ''
|
||||||
|
THEN N' ' + CAST(p.MidName AS NVARCHAR(255)) ELSE N'' END
|
||||||
|
)) AS [Сотрудник],
|
||||||
|
ISNULL(CAST(post.Name AS NVARCHAR(255)), N'—') AS [Должность],
|
||||||
|
ISNULL(CAST(p.TabNumber AS NVARCHAR(50)), N'—') AS [Таб_№],
|
||||||
|
CONVERT(VARCHAR(10), @TargetDate, 104) AS [Дата],
|
||||||
|
ISNULL(CAST(CONVERT(VARCHAR(8), pass.FirstIn, 108) AS NVARCHAR(20)), N'Нет входа') AS [Начало_дня],
|
||||||
|
CASE
|
||||||
|
WHEN @TargetDate = CAST(GETDATE() AS DATE) AND (pass.LastEventType = 'IN' OR pass.LastOut IS NULL OR pass.LastOut <= pass.FirstIn)
|
||||||
|
THEN N'Нет выхода'
|
||||||
|
WHEN pass.LastOut IS NOT NULL AND pass.LastOut > pass.FirstIn
|
||||||
|
THEN CAST(CONVERT(VARCHAR(8), pass.LastOut, 108) AS NVARCHAR(20))
|
||||||
|
ELSE N'Нет выхода'
|
||||||
|
END AS [Конец_дня],
|
||||||
|
CASE
|
||||||
|
WHEN pass.EmployeeID IS NOT NULL AND pass.FirstIn IS NOT NULL THEN
|
||||||
|
RIGHT('0' + CAST(DATEDIFF(MINUTE,
|
||||||
|
pass.FirstIn,
|
||||||
|
CASE
|
||||||
|
WHEN @TargetDate = CAST(GETDATE() AS DATE) AND (pass.LastEventType = 'IN' OR pass.LastOut IS NULL OR pass.LastOut <= pass.FirstIn) THEN GETDATE()
|
||||||
|
ELSE ISNULL(pass.LastOut, pass.FirstIn)
|
||||||
|
END) / 60 AS VARCHAR), 2) + ':' +
|
||||||
|
RIGHT('0' + CAST(DATEDIFF(MINUTE,
|
||||||
|
pass.FirstIn,
|
||||||
|
CASE
|
||||||
|
WHEN @TargetDate = CAST(GETDATE() AS DATE) AND (pass.LastEventType = 'IN' OR pass.LastOut IS NULL OR pass.LastOut <= pass.FirstIn) THEN GETDATE()
|
||||||
|
ELSE ISNULL(pass.LastOut, pass.FirstIn)
|
||||||
|
END) % 60 AS VARCHAR), 2)
|
||||||
|
ELSE N'00:00'
|
||||||
|
END AS [Находился_в_здании],
|
||||||
|
CASE
|
||||||
|
WHEN pass.EmployeeID IS NOT NULL THEN N'Присутствовал'
|
||||||
|
ELSE N'Отсутствовал (Нет событий)'
|
||||||
|
END AS [Статус]
|
||||||
|
FROM pList p WITH (NOLOCK)
|
||||||
|
LEFT JOIN PDivision div WITH (NOLOCK) ON p.Section = div.ID
|
||||||
|
LEFT JOIN PPost post WITH (NOLOCK) ON p.Post = post.ID
|
||||||
|
LEFT JOIN Passages pass ON p.ID = pass.EmployeeID
|
||||||
|
WHERE
|
||||||
|
ISNULL(p.StatusRecord, 0) = 0
|
||||||
|
AND p.DateTimeInArchive IS NULL
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT LIKE N'Аренд%'
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT IN (N'Без подразделения', N'')
|
||||||
|
AND p.Name NOT LIKE N'бр.%'
|
||||||
|
AND p.Name NOT LIKE N'Гость%'
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT IN (N'БГИ', N'КНР')
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT LIKE N'Рабоч%'
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT LIKE N'Врем%'
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT LIKE N'Практика%'
|
||||||
|
AND ISNULL(CAST(div.Name AS NVARCHAR(255)), N'') NOT LIKE N'тест%'
|
||||||
|
AND ISNULL(CAST(post.Name AS NVARCHAR(255)), N'') NOT LIKE N'Практикант%'
|
||||||
|
ORDER BY p.Name ASC;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 4. Вспомогательные функции
|
||||||
|
# ==============================================================================
|
||||||
|
def auto_fit_columns(file_path: str, sheet_name: str, padding: float = 2.0, min_width: float = 8.0, max_width: float = 60.0):
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
|
wb = load_workbook(file_path)
|
||||||
|
ws = wb[sheet_name]
|
||||||
|
|
||||||
|
for col_cells in ws.columns:
|
||||||
|
max_len = 0
|
||||||
|
col_letter = get_column_letter(col_cells[0].column)
|
||||||
|
for cell in col_cells:
|
||||||
|
if cell.value is not None:
|
||||||
|
cell_len = len(str(cell.value))
|
||||||
|
if cell_len > max_len:
|
||||||
|
max_len = cell_len
|
||||||
|
width = max(min_width, min(max_len + padding, max_width))
|
||||||
|
ws.column_dimensions[col_letter].width = width
|
||||||
|
|
||||||
|
wb.save(file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_targets(input_date: str | None):
|
||||||
|
targets = []
|
||||||
|
if input_date:
|
||||||
|
try:
|
||||||
|
parsed = datetime.strptime(input_date, "%d.%m.%Y").date()
|
||||||
|
targets.append({"name": "Указанная дата", "date": parsed})
|
||||||
|
log(f"Запущен ручной режим для даты: {input_date}")
|
||||||
|
except ValueError:
|
||||||
|
log(f"ОШИБКА: Неверный формат даты '{input_date}'. Используйте ДД.ММ.ГГГГ", "ERROR")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
now = datetime.now()
|
||||||
|
yesterday = (now - timedelta(days=3 if now.weekday() == 0 else 1)).date()
|
||||||
|
today = now.date()
|
||||||
|
|
||||||
|
targets.append({"name": "Вчера", "date": yesterday})
|
||||||
|
targets.append({"name": "Сегодня", "date": today})
|
||||||
|
|
||||||
|
return targets
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 5. Основная функция выгрузки
|
||||||
|
# ==============================================================================
|
||||||
|
def run_export(input_date: str | None, debug: bool = False):
|
||||||
|
if debug:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
log("=== ВКЛЮЧЕН РЕЖИМ ОТЛАДКИ (DEBUG MODE) ===", "WARNING")
|
||||||
|
|
||||||
|
log("=== Запуск скрипта экспорта СКУД ===")
|
||||||
|
os.makedirs(FOLDER_PATH, exist_ok=True)
|
||||||
|
|
||||||
|
targets = get_targets(input_date)
|
||||||
|
|
||||||
|
conn_str = (
|
||||||
|
f"DRIVER={{{ODBC_DRIVER}}};"
|
||||||
|
f"SERVER={SERVER_NAME};"
|
||||||
|
f"DATABASE={DATABASE_NAME};"
|
||||||
|
f"UID={SQL_USER};"
|
||||||
|
f"PWD={SQL_PASSWORD};"
|
||||||
|
f"TrustServerCertificate=yes;"
|
||||||
|
f"Encrypt=no;"
|
||||||
|
)
|
||||||
|
|
||||||
|
for target in targets:
|
||||||
|
processing_date = target["date"]
|
||||||
|
processing_date_str = processing_date.strftime("%d.%m.%Y")
|
||||||
|
period_label = target["name"]
|
||||||
|
|
||||||
|
log(f"--- Обработка периода: {period_label} ({processing_date_str}) ---")
|
||||||
|
|
||||||
|
file_name = f"Сотрудники_{processing_date_str}.xlsx"
|
||||||
|
file_path = os.path.join(FOLDER_PATH, file_name)
|
||||||
|
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
log(f"Файл {file_name} существует. Перезаписываем...")
|
||||||
|
try:
|
||||||
|
os.remove(file_path)
|
||||||
|
except OSError as e:
|
||||||
|
log(f"ОШИБКА при удалении файла {file_name}: {e}", "ERROR")
|
||||||
|
continue
|
||||||
|
|
||||||
|
sql_query = SQL_QUERY_TEMPLATE.format(target_date=processing_date.strftime("%Y-%m-%d"))
|
||||||
|
|
||||||
|
connection = None
|
||||||
|
try:
|
||||||
|
connection = pyodbc.connect(conn_str, timeout=120)
|
||||||
|
df = pd.read_sql(sql_query, connection)
|
||||||
|
|
||||||
|
log(f"База вернула {len(df)} строк за {processing_date_str}")
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
present_df = df[df["Начало_дня"] != "Нет входа"]
|
||||||
|
still_inside_df = df[(df["Начало_дня"] != "Нет входа") & (df["Конец_дня"] == "Нет выхода")]
|
||||||
|
left_df = df[(df["Начало_дня"] != "Нет входа") & (df["Конец_дня"] != "Нет выхода")]
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print(f"[DEBUG STATS] Дата: {processing_date_str}")
|
||||||
|
print(f" Всего активных сотрудников в БД: {len(df)}")
|
||||||
|
print(f" Присутствуют / были сегодня: {len(present_df)}")
|
||||||
|
print(f" Физически находятся в здании: {len(still_inside_df)} (Конец_дня = 'Нет выхода')")
|
||||||
|
print(f" Завершили рабочий день (Ушли): {len(left_df)}")
|
||||||
|
|
||||||
|
if not still_inside_df.empty:
|
||||||
|
print("\n Первые 5 сотрудников, находящихся сейчас в здании:")
|
||||||
|
for _, r in still_inside_df.head(5).iterrows():
|
||||||
|
print(f" • {r['Сотрудник']} | Вход: {r['Начало_дня']} | В здании: {r['Находился_в_здании']}")
|
||||||
|
print("=" * 50 + "\n")
|
||||||
|
|
||||||
|
if len(df) > 0:
|
||||||
|
df.to_excel(file_path, sheet_name="Отчет", index=False, engine="openpyxl")
|
||||||
|
auto_fit_columns(file_path, sheet_name="Отчет")
|
||||||
|
log(f"Успешно создан файл {file_path}", "SUCCESS")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log(f"ОШИБКА при обработке даты {processing_date_str}: {e}", "ERROR")
|
||||||
|
finally:
|
||||||
|
if connection is not None:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
log("=== Завершение работы скрипта ===")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Экспорт данных СКУД в Excel")
|
||||||
|
parser.add_argument(
|
||||||
|
"--date",
|
||||||
|
dest="input_date",
|
||||||
|
default=None,
|
||||||
|
help='Дата в формате ДД.ММ.ГГГГ (например, "28.07.2026")',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d", "--debug",
|
||||||
|
action="store_true",
|
||||||
|
help="Запуск в режиме отладки (вывод детальной статистики)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
run_export(args.input_date, debug=args.debug)
|
||||||
Reference in New Issue
Block a user