diff --git a/README.md b/README.md index 181177a..3af39fc 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,32 @@ [![checks](https://git.akokos.ru/artem.kokos/redmine-reporter/actions/workflows/checks.yaml/badge.svg)](https://git.akokos.ru/artem.kokos/redmine-reporter/actions) -CLI-инструмент для генерации отчётов по задачам Redmine на основе записей о затраченном времени. +- [Возможности](#возможности) +- [Установка](#установка) +- [Быстрый старт](#быстрый-старт) +- [Документация](#документация) +- [Форматы вывода](#форматы-вывода) +- [Разработка](#разработка) +- [Безопасность](#безопасность) +- [Лицензия](#лицензия) -Проект предназначен для внутреннего использования с `https://red.eltex.loc/`. - -Лицензия: MIT. +CLI-инструмент для генерации отчётов по задачам Redmine на основе записей о затраченном времени. Читает time entries текущего или указанного пользователя, группирует задачи по проекту и версии, выводит отчёт в консоль или экспортирует в файл. Предназначен для внутреннего использования с `https://red.eltex.loc/`. ## Возможности -- Получение time entries **текущего** или **указанного** пользователя из Redmine. -- Авторизация через Redmine API token или логин/пароль. -- Группировка задач по проекту и версии. -- Перевод статусов задач на русский язык. -- Разбивка по типам активности (`--by-activity`). -- Вывод в консоль (таблица / компактный вид). -- Экспорт в ODT, CSV, Markdown, HTML, JSON и Excel (.xlsx). -- Excel-отчёт с merge-ячейками по проекту/версии, итогами, автошириной, автофильтром и закреплённой шапкой. -- Сводка по времени (`--summary`). -- YAML-конфиг (`~/.config/redmine-reporter/config.yml`): шаблон имени файла, путь по умолчанию, период, email, настройки содержимого отчёта (`report.no_time`). -- Умное разрешение `--output`: bare-формат (`xlsx`) → путь по шаблону, без расширения → автодописывание. -- `--commit`: автосохранение отчёта в файл + фиксация периода в YAML-конфиге для следующего запуска. -- `--send`: отправка отчёта по email через SMTP сразу после генерации. -- HTML-версия тела письма при `--send`, если включено в YAML-конфиге (`email.html: true`). -- Понятные сообщения об ошибках Redmine API, SMTP и файловой системы. -- Загрузка альтернативного `.env` через `--config`. +- Отчёт по time entries текущего или указанного пользователя (`--user-id`, `--user-login`, `--user-name`). +- Группировка задач по проекту и версии, перевод статусов на русский язык. +- Вывод в консоль (таблица или компактный вид) и экспорт в ODT, CSV, Markdown, HTML, JSON, XLSX. +- Разбивка времени по типам активности (`--by-activity`), сводка (`--summary`), скрытие времени (`--no-time`). +- Гибкий выбор периода: `--date`, переменные окружения, YAML-конфиг, по умолчанию — текущий месяц. +- `--commit`: сохранение отчёта в файл и фиксация периода в конфиге для следующего запуска. +- `--send`: отправка отчёта по email через SMTP (plain-text или HTML-письмо). +- YAML-конфиг с секретами через `${VAR}`; приоритет: CLI-флаги > env > .env > YAML > дефолты (нюанс с `--config` — см. docs/CONFIG.md). ## Установка +Требуется Python >= 3.10. + ```bash git clone https://git.akokos.ru/artem.kokos/redmine-reporter.git cd redmine-reporter @@ -44,263 +43,40 @@ pip install . pip install -e ".[dev]" ``` -## Настройка - -Источники конфигурации (от высшего приоритета к низшему): - -``` -CLI-флаги > переменные окружения > .env > YAML-конфиг > кодовые дефолты -``` - -### YAML-конфиг (основной способ) +## Быстрый старт ```bash -# Сгенерировать YAML из текущего .env +# Сгенерировать конфиг ~/.config/redmine-reporter/config.yml redmine-reporter --init-config -# Редактировать под себя +# Заполнить redmine.url, redmine.api_key (или ${REDMINE_API_KEY}), redmine.author vim ~/.config/redmine-reporter/config.yml -``` -Структура: - -```yaml -redmine: - url: https://red.eltex.loc - api_key: ${REDMINE_API_KEY} - author: "Кокос А.А." - verify_ssl: true - -period: - precision: date # date | datetime - default_from: "2026-06-01" - # default_to можно не указывать — конец периода будет сегодня - default_to: "2026-06-30" - dynamic: false - # last_used заполняется --commit (см. docs/CONFIG.md) - -output: - dir: ~/reports - filename: "{author}_{from}_{to}.{ext}" - default_format: xlsx - -report: - no_time: false - -email: - html: false - smtp: - host: smtp.example.com - port: 587 - user: bot@example.com - password: ${SMTP_PASSWORD} - tls: true - from: bot@example.com - to: - - boss@example.com - cc: [] - bcc: [] - subject: "Отчёт {author} за {period}" - body_text: "Во вложении отчёт." - attach: true -``` - -Шаблон `output.filename` поддерживает `{author}`, `{from}`, `{to}`, `{date}` (DD_MM_YYYY), `{ext}`. - -Шаблоны `email.subject` и `email.body_text` поддерживают `{author}`, `{period}` (строка диапазона, например `2026-06-01--2026-06-30`). - -Подробнее: [docs/CONFIG.md](docs/CONFIG.md). - -### `.env` (legacy) - -```ini -REDMINE_URL=https://red.eltex.loc/ -REDMINE_API_KEY=ваш_api_token -REDMINE_AUTHOR=Иванов Иван Иванович -DEFAULT_FROM_DATE=2026-01-01 -# DEFAULT_TO_DATE можно не задавать — тогда конец периода будет сегодня -DEFAULT_TO_DATE=2026-01-31 -``` - -Переменные окружения: - -| Переменная | Обязательность | Описание | -| --- | --- | --- | -| `REDMINE_URL` | Да | URL Redmine. | -| `REDMINE_API_KEY` | Да, если нет логина и пароля | Redmine API token. | -| `REDMINE_USER` | Да, если нет токена | Логин Redmine. | -| `REDMINE_PASSWORD` | Да, если нет токена | Пароль Redmine. | -| `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. | -| `DEFAULT_FROM_DATE` | Нет | Начальная дата периода по умолчанию (`YYYY-MM-DD`). Если не задана (и нет в YAML) — 1-е число текущего месяца. | -| `DEFAULT_TO_DATE` | Нет | Конечная дата периода по умолчанию (`YYYY-MM-DD`). Если не задана, а `DEFAULT_FROM_DATE` задана — используется сегодняшняя дата. | -| `REDMINE_VERIFY` | Нет | TLS-проверка: `true` (по умолчанию — стандартная проверка средствами requests) / `false` / путь к CA bundle. Семантика совпадает с `redmine.verify_ssl` в YAML. | - -## Использование - -```bash -source .venv/bin/activate -``` - -### Основные сценарии - -Отчёт за период по умолчанию — текущий месяц (с 1-го числа по сегодня), -если период не задан через `--date`, env или YAML: - -```bash -redmine-reporter -``` - -Произвольный период: - -```bash -redmine-reporter --date 2026-02-01--2026-02-28 -``` - -Другой пользователь: - -```bash -redmine-reporter --user-id 42 -redmine-reporter --user-login ivanov -redmine-reporter --user-name "Иванов И.И." -``` - -Переопределить URL / API-ключ: - -```bash -redmine-reporter --url https://red.example.com --api-key ваш_токен -``` - -Альтернативный `.env`: - -```bash -redmine-reporter --config /path/to/.env -``` - -Компактный / отладочный вывод: - -```bash -redmine-reporter --compact -redmine-reporter --debug -``` - -### Экспорт в файл - -Явный путь: - -```bash -redmine-reporter --output report.xlsx -redmine-reporter --output /path/to/report.odt -``` - -Только формат (путь и имя берутся из YAML-шаблона): - -```bash -redmine-reporter --output xlsx # → output.dir/отчёт_01_07_2026.xlsx -redmine-reporter --output odt # → output.dir/отчёт_01_07_2026.odt -``` - -Путь без расширения (дописывается `default_format` из конфига): - -```bash -redmine-reporter --output /tmp/report # → /tmp/report.xlsx (если default_format: xlsx) -``` - -### Отправка по email (`--send`) - -Отправить отчёт на email, указанный в YAML-конфиге (секция `email`): - -```bash -# Сохранить по шаблону и отправить -redmine-reporter --date 2026-06-01--2026-06-30 --send - -# С явным путём -redmine-reporter --date 2026-06-01--2026-06-30 --output ~/report.xlsx --send - -# Вместе с фиксацией периода -redmine-reporter --date 2026-06-01--2026-06-30 --send --commit -``` - -Если в секции `email` установить `html: true`, письмо будет отправлено в двух версиях: plain-text и HTML (таблица отчёта прямо в теле письма). Файл отчёта всё равно прикрепляется, если `attach: true`. - -```yaml -email: - html: true -``` - -Если секция `email` не настроена — ошибка с пояснением. При ошибке SMTP файл отчёта остаётся на диске, данные не теряются. Поддерживаются `to`, `cc`, `bcc`, TLS, отключение вложения (`attach: false`). - -### Фиксация периода (`--commit`) - -```bash -# Сгенерировать, сохранить в файл по шаблону, запомнить период -redmine-reporter --commit - -# С явным путём -redmine-reporter --commit --output report.xlsx - -# Следующий запуск (без --date) возьмёт следующий период автоматически +# Отчёт за текущий месяц в консоль redmine-reporter -# При precision=datetime запоминает момент времени -# (предотвращает дублирование записей внутри дня) +# Сохранить в файл и зафиксировать период для следующего запуска redmine-reporter --commit ``` -### Сводка и опции +## Документация -Без времени / с разбивкой по активностям: - -```bash -redmine-reporter --no-time -redmine-reporter --by-activity -redmine-reporter --by-activity --summary -``` - -`--no-time` можно задать в YAML-конфиге (`report.no_time: true`), чтобы автоматические режимы (`--commit`, `--send`) не включали затраченное время без явного флага. При ручном `--output` YAML-значение не применяется — только CLI-флаг `--no-time`. - -Сводка: - -```bash -redmine-reporter --summary -``` +- [docs/USER_GUIDE.md](docs/USER_GUIDE.md) — руководство пользователя: сценарии использования, справочник всех CLI-флагов, устранение неполадок. +- [docs/CONFIG.md](docs/CONFIG.md) — справочник конфигурации: YAML-структура, переменные окружения, приоритеты, безопасность. ## Форматы вывода | Формат | Особенности | | --- | --- | -| **ODT** | Заголовок с автором и месяцем, группировка по проекту/версии. | -| **CSV** | UTF-8 с BOM, полные значения `project`/`version` в каждой строке. | -| **Markdown** | Компактная таблица, повторяющиеся группы скрыты. | -| **HTML** | Полноценный HTML-документ с `meta charset="utf-8"`. | -| **JSON** | Массив объектов: `project`, `version`, `issue_id`, `subject`, `status`, `time`. | -| **Excel (.xlsx)** | Merge cells, колонки `Hours`/`Spent Time`, итоги, автоширина, автофильтр, freeze panes. | +| Консоль | Таблица или компактный вид (`--compact`). | +| ODT | Требуется `odfpy`; формирование по шаблону. | +| CSV | UTF-8 с BOM; полные значения `project`/`version` в каждой строке. | +| Markdown | Компактная таблица. | +| HTML | Полный HTML-документ; объединение ячеек групп через rowspan. | +| JSON | Объекты `project`, `version`, `issue_id`, `subject`, `status`, `time` + опционально `activities`. | +| XLSX | Объединение ячеек по проекту/версии, итоги, автоширина (максимум 80), автофильтр, freeze panes. | -## Полный список флагов - -``` ---date DATE Диапазон дат: YYYY-MM-DD--YYYY-MM-DD ---compact Компактный текстовый вывод вместо таблицы ---output PATH/FMT Путь к файлу (.odt/.csv/.md/.html/.json/.xlsx) - или bare-формат (xlsx/odt/...) — путь из конфига ---author NAME Переопределить имя автора ---no-time Не включать затраченное время в таблицу ---url URL Переопределить Redmine URL ---api-key KEY Переопределить Redmine API key ---config PATH Путь к альтернативному .env-файлу ---verbose Подробный вывод ---debug Отладочный вывод ---version Показать версию и выйти ---summary Вывести сводку по времени в stderr ---user-id ID Redmine ID пользователя для отчёта ---user-login LOGIN Логин пользователя Redmine ---user-name NAME Полное имя пользователя Redmine ---by-activity Разбить время по типам активности ---init-config Сгенерировать YAML-конфиг и выйти ---force Перезаписать существующий конфиг (с --init-config) ---config-path PATH Путь к YAML-конфигу (по умолчанию ~/.config/redmine-reporter/config.yml) ---commit Сохранить отчёт в файл и зафиксировать период в конфиге ---send Отправить отчёт по email после сохранения -``` +Нюанс `--no-time`: физически удаляет колонку времени только CSV; в XLSX колонки остаются, но пустыми и без итогов; в остальных форматах — пустые значения. ## Разработка @@ -308,17 +84,23 @@ redmine-reporter --summary ```bash pytest +isort --check-only redmine_reporter tests +black --check redmine_reporter tests ruff check redmine_reporter tests ruff format --check redmine_reporter tests mypy redmine_reporter ``` +CI — Gitea Actions (`.gitea/workflows/checks.yaml`): все шесть проверок на матрице Python 3.10–3.13. + ## Безопасность -- Не коммитьте `.env`, API token, пароль или логин. -- YAML-конфиг имеет права `0600`, директория — `0700`. -- Рекомендуется хранить секреты через `${VAR}`, а не plaintext. -- Используйте аккаунт с минимальными правами, достаточными для чтения time entries и задач. -- Инструмент работает только в режиме чтения и не изменяет данные в Redmine. -- `REDMINE_URL` обязан использовать HTTPS: API-ключ передаётся в заголовках запроса, и без TLS он может быть перехвачен. -- `REDMINE_VERIFY=false` (или `verify_ssl: false`) отключает проверку TLS-сертификата — соединение уязвимо для MITM-атак; при старте выводится предупреждение. Используйте только в доверенной сети. +- `REDMINE_URL` обязан использовать HTTPS: валидация отклоняет остальное, API-ключ передаётся в заголовках. +- `verify_ssl` / `REDMINE_VERIFY`: `true` (по умолчанию), `false` (предупреждение о MITM-риске при старте) или путь к CA-bundle. +- Конфиг создаётся с правами `0600`, директория — `0700`; при более широких правах выводится предупреждение. +- Секреты храните через `${VAR}` в YAML или в переменных окружения, не в открытом виде. +- Инструмент только читает данные из Redmine и ничего в нём не изменяет. + +## Лицензия + +MIT, см. [LICENSE](LICENSE). diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 1be67f7..282a383 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1,5 +1,33 @@ # Настройка redmine-reporter +## Оглавление + +- [Источники конфигурации](#источники-конфигурации) +- [YAML-конфиг](#yaml-конфиг) + - [Структура](#структура) + - [`redmine.verify_ssl` — проверка TLS-сертификата](#redmineverify_ssl--проверка-tls-сертификата) + - [`period.precision` — точность периода](#periodprecision--точность-периода) + - [`period.default_to` — необязательное окончание периода](#perioddefault_to--необязательное-окончание-периода) + - [Период по умолчанию — текущий месяц](#период-по-умолчанию--текущий-месяц) + - [`--date` — формат диапазона](#--date--формат-диапазона) + - [`--commit` — автофиксация периода](#--commit--автофиксация-периода) + - [`email` — настройка отправки по почте](#email--настройка-отправки-по-почте) + - [`--send` — отправка отчёта по email](#--send--отправка-отчёта-по-email) + - [`output` — путь и имя файла по умолчанию](#output--путь-и-имя-файла-по-умолчанию) + - [Подстановка переменных окружения](#подстановка-переменных-окружения) + - [`report` — настройки содержимого отчёта](#report--настройки-содержимого-отчёта) + - [`report.status_translation` — перевод статусов](#reportstatus_translation--перевод-статусов) +- [Разрешение выходного пути](#разрешение-выходного-пути) +- [Миграция с `.env` на YAML](#миграция-с-env-на-yaml) + - [Быстрый старт](#быстрый-старт) + - [Что делает `--init-config`](#что-делает---init-config) + - [Флаги миграции](#флаги-миграции) + - [Проверка после миграции](#проверка-после-миграции) + - [Сосуществование `.env` и YAML](#сосуществование-env-и-yaml) + - [Откат](#откат) +- [`.env` (legacy)](#env-legacy) +- [Безопасность](#безопасность) + ## Источники конфигурации Приоритет, от высшего к низшему: @@ -8,6 +36,9 @@ CLI-флаги > переменные окружения > .env > YAML > кодовые дефолты ``` +Нюанс с `override` при автозагрузке `.env` и `--config` — см. +[Сосуществование `.env` и YAML](#сосуществование-env-и-yaml). + Если значение не задано на верхнем уровне, берётся уровень ниже. `.env` и YAML работают одновременно — можно оставить оба, можно удалить `.env` после миграции. @@ -86,7 +117,7 @@ email: Определяет, как вычисляется следующий период после фиксации: - `date` (по умолчанию) — период с точностью до дня. Следующий запуск (после `--commit`) начинается со следующего дня. -- `datetime` — период с точностью до секунды. При повторном запуске time entries с `created_on` и `updated_on` ранее `last_used.to` исключаются (AND-логика: запись исключается только если **оба** поля раньше cutoff). Это предотвращает дублирование при отправке отчёта внутри рабочего дня. +- `datetime` — период с точностью до секунды. При повторном запуске time entries, у которых `created_on` или `updated_on` раньше `last_used.to`, исключаются: запись сохраняется, только если **оба** поля не раньше cutoff (AND-логика). Записи без дат (оба поля отсутствуют) сохраняются; если задано только одно поле, проверяется оно. Это предотвращает дублирование при отправке отчёта внутри рабочего дня. Cutoff применяется всегда, когда вычислен (`precision: datetime` и задан `last_used.to`) — в том числе при явном `--date`. `last_used.from` / `last_used.to` записываются автоматически при `--commit`. Вручную редактировать не требуется. @@ -107,6 +138,10 @@ period: Аналогично работает `DEFAULT_TO_DATE`: если переменная не задана, а `DEFAULT_FROM_DATE` задана, конец периода = сегодня. +Обратное не работает: `period.default_to` без `period.default_from` (и +`DEFAULT_TO_DATE` без `DEFAULT_FROM_DATE`) игнорируется — период определяется +остальными источниками. + ### Период по умолчанию — текущий месяц Если период не задан ни одним из источников (`--date`, `DEFAULT_FROM_DATE`, @@ -114,6 +149,17 @@ period: 1-е число текущего месяца, конец — сегодняшняя дата. Период вычисляется на момент запуска и не хранится в коде или конфиге. +### `--date` — формат диапазона + +Флаг `--date` принимает два формата: + +- Даты: `YYYY-MM-DD--YYYY-MM-DD` (например, `2026-06-01--2026-06-30`). +- Datetime с секундами: `YYYY-MM-DDTHH:MM:SS--YYYY-MM-DDTHH:MM:SS` + (например, `2026-06-30T09:00:00--2026-06-30T12:00:00`). + +Обе границы должны быть в одном формате — смешанная точность отвергается. +Начало позже конца (`start > end`) — ошибка. + ### `--commit` — автофиксация периода Флаг `--commit` сохраняет использованный период в YAML-конфиг, чтобы следующий запуск автоматически начинался с нового периода. @@ -135,6 +181,9 @@ period: - Произвольный диапазон → та же длительность, начиная со дня после `last_used.to`. - `dynamic: false` — `--commit` перезаписывает `default_from`/`default_to` на использованный период. +**Нюанс:** при `--commit` YAML-файл перезаписывается целиком через `yaml.dump` — +пользовательские комментарии и ручное форматирование в файле теряются. + **Примеры:** ```bash @@ -228,7 +277,8 @@ email: 3. Формирует MIME-письмо: - Тема, plain-text тело и вложение (если `attach: true`). - При `email.html: true` — дополнительно HTML-версия тела (`multipart/alternative`), сгенерированная из таблицы отчёта. -4. Отправляет через SMTP с TLS (таймаут 30 секунд). +4. Отправляет через SMTP (таймаут 30 секунд). STARTTLS включается только при + `email.smtp.tls: true`; аутентификация — только если задан `email.smtp.user`. **Файл отчёта сохраняется до попытки отправки** — при ошибке SMTP файл остаётся на диске, данные не теряются. @@ -398,7 +448,8 @@ vim ~/.config/redmine-reporter/config.yml ### Что делает `--init-config` - Читает текущие значения из `.env` и переменных окружения. -- Формирует YAML со всеми секциями (`redmine`, `period`, `output`, `email`). +- Формирует YAML со всеми секциями (`redmine`, `period`, `output`, `report`, `email`). +- В конец файла дописывает закомментированный пример `report.status_translation`. - Секреты (`REDMINE_API_KEY`, `SMTP_PASSWORD`) записывает как `${VAR}`, если переменная существует, иначе — пустая строка. - Создаёт файл с правами `0600`, директорию — с `0700`. @@ -409,7 +460,7 @@ vim ~/.config/redmine-reporter/config.yml |---|---| | `--init-config` | Создать YAML и выйти | | `--init-config --force` | Перезаписать существующий YAML | -| `--config-path PATH` | Сохранить YAML по указанному пути (по умолчанию `~/.config/redmine-reporter/config.yml`) | +| `--config-path PATH` | Путь к YAML-конфигу: загрузка при запуске, запись при `--init-config` и `--commit` (по умолчанию `~/.config/redmine-reporter/config.yml`) | Если `DEFAULT_TO_DATE` не задана, а `DEFAULT_FROM_DATE` задана, сгенерированный YAML будет содержать пустое `default_to`, и при запуске инструмент использует @@ -441,6 +492,10 @@ ls -la ~/.config/redmine-reporter/ YAML работает как базовый слой для всего, что не в .env ``` +Автозагрузка `.env` (без `--config`) не перебивает реальные переменные +окружения (`override=False`). Исключение — `--config FILE`: указанный `.env` +загружается с `override=True` и перебивает переменные окружения (но не CLI-флаги). + Это safe — если с YAML что-то пойдёт не так, просто положи `.env` обратно. ### Откат diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md new file mode 100644 index 0000000..a86665d --- /dev/null +++ b/docs/USER_GUIDE.md @@ -0,0 +1,304 @@ +# Руководство пользователя redmine-reporter + +## Оглавление + +- [Установка](#установка) +- [Первоначальная настройка](#первоначальная-настройка) +- [Выбор периода](#выбор-периода) +- [Отчёт за другого пользователя](#отчёт-за-другого-пользователя) +- [Экспорт в файл](#экспорт-в-файл) +- [Содержимое отчёта](#содержимое-отчёта) +- [Отправка по email](#отправка-по-email) +- [Ежемесячный цикл с --commit](#ежемесячный-цикл-с---commit) +- [Справочник флагов](#справочник-флагов) +- [Устранение неполадок](#устранение-неполадок) +- [См. также](#см-также) + +## Установка + +Требуется Python >= 3.10. + +```bash +git clone https://git.akokos.ru/artem.kokos/redmine-reporter.git +cd redmine-reporter +python3 -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install . +``` + +Проверка: + +```bash +redmine-reporter --version +``` + +Перед каждым запуском активируйте окружение: `source .venv/bin/activate`. + +## Первоначальная настройка + +1. Убедитесь, что в текущей директории есть `.env` с `REDMINE_URL` и `REDMINE_API_KEY` (или задайте эти переменные в окружении). +2. Сгенерируйте YAML-конфиг: + +```bash +redmine-reporter --init-config +``` + +Результат: файл `~/.config/redmine-reporter/config.yml` (права `0600`, директория `0700`) со всеми секциями и закомментированным примером `report.status_translation`. Секреты, заданные в окружении, записываются как `${VAR}`. + +3. Если файл уже существует — ошибка; для перезаписи добавьте `--force`: + +```bash +redmine-reporter --init-config --force +``` + +4. Нестандартное расположение конфига: + +```bash +redmine-reporter --init-config --config-path /etc/redmine-reporter/config.yml +``` + +`--config-path` задаёт путь и для загрузки YAML, и для записи при `--init-config` и `--commit`. + +5. Откройте файл и заполните минимум: `redmine.url`, `redmine.api_key`, `redmine.author`. + +`--init-config` несовместим с флагами отчёта (`--date`, `--output`, `--compact`, `--summary`, `--user-id`/`--user-login`/`--user-name`, `--no-time`, `--by-activity`, `--send`) — указывайте его отдельно. + +Полный референс всех секций и переменных: [CONFIG.md](CONFIG.md). + +## Выбор периода + +Источник периода выбирается по приоритету (от высшего к низшему): + +1. `--date` — явный диапазон. +2. `DEFAULT_FROM_DATE` (env/.env) — конец: `DEFAULT_TO_DATE` или сегодня. `DEFAULT_TO_DATE` без `DEFAULT_FROM_DATE` игнорируется. +3. `period.dynamic: true` + `period.last_used` в YAML — следующий период после `last_used`: + - последний период — полный календарный месяц → следующий полный месяц; + - произвольный диапазон → та же длительность, начиная со дня после `last_used.to`; + - при `precision: datetime` → та же длительность, сдвинутая на 1 секунду после `last_used.to`. +4. `period.default_from` в YAML — конец: `default_to` или сегодня. `default_to` без `default_from` игнорируется. +5. Ничего не задано → текущий месяц: с 1-го числа по сегодня. + +Форматы `--date`: + +```bash +# Даты +redmine-reporter --date 2026-06-01--2026-06-30 + +# Дата-время +redmine-reporter --date 2026-06-01T09:00:00--2026-06-01T18:00:00 +``` + +Смешанная точность (дата + дата-время в одном диапазоне) и диапазон, где начало позже конца, отвергаются с ошибкой. + +## Отчёт за другого пользователя + +Без флагов отчёт строится за текущего пользователя. + +```bash +redmine-reporter --user-id 42 +redmine-reporter --user-login ivanov +redmine-reporter --user-name "Иванов Иван Иванович" +``` + +Правила: + +- Флаги `--user-id`, `--user-login`, `--user-name` взаимоисключающие: укажите только один, иначе ошибка. +- Все три принимают одно значение. Число или строка из цифр → поиск по ID. Иначе — точное регистрозависимое совпадение логина. +- Если по логину совпадений нет — поиск по имени. Одно совпадение → пользователь найден. +- Несколько совпадений по имени → ошибка «Multiple users match...» со списком логинов (при дублях логина — ID). +- Ноль совпадений → ошибка «User '...' not found...». + +## Экспорт в файл + +Флаг `--output` определяется по четырём правилам: + +| Аргумент | Результат | +| --- | --- | +| `--output xlsx` (bare-формат: `xlsx`, `odt`, `csv`, `md`, `html`, `json`, регистронезависимо) | Путь = `output.dir` + шаблон `output.filename` из YAML, расширение = указанный формат | +| `--output /tmp/report` (путь без расширения) | Дописывается `.` + `output.default_format` → `/tmp/report.xlsx` | +| `--output /tmp/report.csv` (путь с любым расширением) | Используется как есть | +| `--output /tmp/report.xyz` (неизвестное расширение) | Ошибка «Неизвестный формат файла: '.xyz'. Поддерживаются: .odt, .csv, .md, .html, .json, .xlsx», exit 1 | + +Формат `.odt` требует установленного пакета `odfpy` (устанавливается по умолчанию) — иначе отдельная ошибка. + +Примеры: + +```bash +redmine-reporter --output xlsx # по шаблону в output.dir +redmine-reporter --output 2026-06-report # → 2026-06-report.xlsx (если default_format: xlsx) +redmine-reporter --output reports/june.csv # как есть +``` + +Плейсхолдеры `output.filename`: + +| Плейсхолдер | Описание | Пример | +| --- | --- | --- | +| `{author}` | Имя автора, пробелы → `_` | `Кокос_А.А.` | +| `{from}` | Начало периода, `YYYY-MM-DD` | `2026-06-01` | +| `{to}` | Конец периода, `YYYY-MM-DD` | `2026-06-30` | +| `{date}` | Конец периода, `DD_MM_YYYY` | `30_06_2026` | +| `{ext}` | Расширение без точки | `xlsx` | + +Неизвестные плейсхолдеры остаются в имени как есть. + +Нюансы с путями: + +- `~` в явном `--output` раскрывает шелл, а не программа: `redmine-reporter --output ~/report.xlsx` работает, а в кавычках (`"~/report.xlsx"`) — нет. +- `expanduser` применяется только к `output.dir` из YAML. + +## Содержимое отчёта + +### `--no-time` + +Убирает затраченное время из отчёта. Физически удаляет колонку времени только CSV. XLSX, HTML, ODT, Markdown, JSON и консольный вывод оставляют пустые колонки/поля; в XLSX дополнительно пропадают строки итогов по версиям/проектам/всего. + +```bash +redmine-reporter --output report.xlsx --no-time +``` + +YAML-настройка `report.no_time` действует только в автоматических режимах (`--commit`, `--send`). При ручном `--output` работает только CLI-флаг. + +### `--by-activity` + +Разбивка затраченного времени по типам активности. В JSON-отчёте добавляется поле `activities`. + +```bash +redmine-reporter --by-activity +``` + +### `--summary` + +Печатает сводку в stderr (после отчёта): + +- `total` — суммарное время; +- `project:*` — время по каждому проекту; +- `activity:*` — время по каждой активности (только при одновременном `--by-activity`). + +Версии в сводку не входят. + +```bash +redmine-reporter --by-activity --summary +``` + +### `--compact` + +Компактный текстовый вывод в консоль вместо таблицы: + +```bash +redmine-reporter --compact +``` + +### Перевод статусов + +Статусы задач переводятся встроенным словарём; секция `report.status_translation` переопределяет или дополняет переводы. Подробно: [CONFIG.md](CONFIG.md). + +## Отправка по email + +Требуется секция `email` в YAML-конфиге (см. [CONFIG.md](CONFIG.md)). + +```bash +redmine-reporter --date 2026-06-01--2026-06-30 --send +redmine-reporter --date 2026-06-01--2026-06-30 --output report.xlsx --send +redmine-reporter --send --commit +``` + +Факты: + +- Файл отчёта сохраняется на диск ДО отправки: при ошибке SMTP файл остаётся на месте. +- Секция `email` отсутствует или `smtp.host` пуст → ошибка «Email не настроен. Добавьте секцию 'email' в конфиг...», exit 1. +- `email.html: true` → письмо `multipart/alternative`: plain-text + HTML-таблица отчёта в теле. +- Файл прикрепляется при `attach: true`; MIME-тип определяется по расширению, неизвестное → `application/octet-stream`. +- Получатели `bcc` указываются только в envelope (не видны в заголовках письма). +- STARTTLS применяется только при `smtp.tls: true`; аутентификация на SMTP-сервере — только если задан `smtp.user`. +- Таймаут соединения с SMTP — 30 секунд. + +Точные тексты ошибок: + +- «Ошибка аутентификации SMTP. Проверьте логин и пароль.» +- «Таймаут соединения с SMTP-сервером.» +- «Ошибка отправки письма: ...» +- «Не удалось подключиться к SMTP-серверу host:port» + +## Ежемесячный цикл с --commit + +`--commit` сохраняет отчёт в файл (по `--output` или по шаблону `output.dir`/`output.filename`) и записывает `period.last_used` в YAML-конфиг. + +Что пишется: + +- `precision: datetime` → `last_used.from` = `last_used.to` = текущий момент UTC. +- `precision: date` → фактические `from`/`to` использованного периода. +- `dynamic: true` → `default_from`/`default_to` не меняются. +- `dynamic: false` → дополнительно перезаписываются `default_from`/`default_to`. + +Типичный цикл: + +```bash +# 1. Отчёт за июнь 2026: сохранить файл, зафиксировать период +redmine-reporter --date 2026-06-01--2026-06-30 --send --commit + +# 2. Следующий запуск без --date возьмёт июль 2026 автоматически +# (при dynamic: true и полном месяце) +redmine-reporter --send --commit +``` + +Защита от дублей при `precision: datetime`: + +- Если в YAML есть `last_used.to` (cutoff), time entry сохраняется в отчёт, только если `created_on` И `updated_on` оба >= cutoff; если хотя бы одно поле раньше cutoff — запись исключается. +- Записи, у которых оба поля (`created_on`, `updated_on`) отсутствуют, сохраняются; если задано только одно поле, решает оно. +- Фильтр применяется всегда при `precision: datetime`, в том числе при явном `--date`. + +Важно: `--commit` перезаписывает YAML через `yaml.dump` — пользовательские комментарии в файле теряются. + +## Справочник флагов + +| Флаг | Описание | +| --- | --- | +| `--date` | Диапазон периода: `YYYY-MM-DD--YYYY-MM-DD` или `YYYY-MM-DDTHH:MM:SS--YYYY-MM-DDTHH:MM:SS`. Смешанная точность и `start > end` отвергаются. | +| `--compact` | Компактный текстовый вывод в консоль вместо таблицы. | +| `--output` | Путь к файлу (`.odt`/`.csv`/`.md`/`.html`/`.json`/`.xlsx`) или bare-формат — тогда путь берётся из YAML-шаблона; без флага — вывод в консоль (stdout). | +| `--author` | Переопределить имя автора отчёта. | +| `--no-time` | Не включать затраченное время в отчёт. | +| `--url` | Переопределить URL Redmine. | +| `--api-key` | Переопределить API-ключ Redmine. | +| `--config` | Путь к альтернативному `.env`-файлу. | +| `--verbose` | Подробный вывод; показывает traceback при ошибках. | +| `--debug` | Отладочный вывод; показывает traceback при ошибках. | +| `--version` | Показать версию и выйти. | +| `--summary` | Сводка по времени в stderr: `total`, `project:*`, `activity:*` (версии не печатаются). | +| `--user-id` | Отчёт за пользователя по ID. | +| `--user-login` | Отчёт за пользователя по логину (точное регистрозависимое совпадение). | +| `--user-name` | Отчёт за пользователя по имени. | +| `--by-activity` | Разбивка времени по типам активности (в JSON — поле `activities`). | +| `--init-config` | Сгенерировать YAML-конфиг из текущего `.env`/окружения и выйти. | +| `--force` | Перезаписать существующий YAML-конфиг (только с `--init-config`). | +| `--config-path` | Путь к YAML-конфигу: загрузка, запись `--init-config` и `--commit`. По умолчанию `~/.config/redmine-reporter/config.yml`. | +| `--commit` | Сохранить отчёт в файл и зафиксировать период (`period.last_used`) в YAML. | +| `--send` | Отправить отчёт по email после сохранения файла. | + +## Устранение неполадок + +| Ошибка | Причина | Решение | +| --- | --- | --- | +| «REDMINE_URL must use HTTPS...» (exit 1) | URL Redmine не по HTTPS | Укажите `https://` в `redmine.url` / `REDMINE_URL` / `--url`. | +| «Authentication failed: invalid API key, login or password. Check REDMINE_API_KEY / REDMINE_USER / REDMINE_PASSWORD.» | Неверный или отсутствующий API-ключ, логин или пароль | Проверьте `redmine.api_key` / `--api-key` (или `REDMINE_USER` / `REDMINE_PASSWORD`). | +| «User '...' not found...» | Пользователь не найден ни по ID, ни по логину, ни по имени | Проверьте написание; логин сравнивается точно с учётом регистра. | +| «Multiple users match...» | По имени найдено несколько пользователей (или несколько дублей логина) | Используйте `--user-login` с логином из списка или уточните числовой ID для `--user-id`. | +| Указано несколько `--user-*` | Флаги взаимоисключающие | Оставьте только один из `--user-id` / `--user-login` / `--user-name`. | +| «Неизвестный формат файла: '.xyz'. Поддерживаются: .odt, .csv, .md, .html, .json, .xlsx» (exit 1) | `--output` с неизвестным расширением | Используйте одно из поддерживаемых расширений. | +| Ошибка про `odfpy` при `.odt` | Пакет `odfpy` не установлен | `pip install odfpy` или выберите другой формат. | +| «Email не настроен. Добавьте секцию 'email' в конфиг...» (exit 1) | `--send` без секции `email` в YAML или с пустым `smtp.host` | Добавьте секцию `email` (см. [CONFIG.md](CONFIG.md)). | +| «Ошибка аутентификации SMTP. Проверьте логин и пароль.» | Неверные `smtp.user`/`smtp.password` | Проверьте логин и пароль в конфиге. | +| «Таймаут соединения с SMTP-сервером.» | Сервер не ответил за 30 секунд | Проверьте сеть, `smtp.host` и `smtp.port`. | +| «Не удалось подключиться к SMTP-серверу host:port» | Нет соединения с SMTP | Проверьте `smtp.host`, `smtp.port` и доступность сервера. | +| «Ошибка отправки письма: ...» | Прочая ошибка SMTP | Смотрите детали после двоеточия; файл отчёта уже сохранён на диске. | +| Предупреждение о правах конфига при старте | Права `config.yml` шире `0600` | `chmod 600 ~/.config/redmine-reporter/config.yml`. | +| Предупреждение о MITM при старте | `verify_ssl: false` / `REDMINE_VERIFY=false` — проверка TLS отключена | Включите проверку или используйте только в доверенной сети. | +| Не виден traceback при ошибке | Traceback показывается только с `--verbose`/`--debug` | Повторите запуск с `--debug`. | + +API-ключ в текстах ошибок маскируется (`***`). + +## См. также + +- [README.md](../README.md) — обзор, быстрый старт, форматы вывода. +- [CONFIG.md](CONFIG.md) — полный референс конфигурации (YAML, env, приоритеты, безопасность). diff --git a/redmine_reporter/client.py b/redmine_reporter/client.py index 14a432f..aba017d 100644 --- a/redmine_reporter/client.py +++ b/redmine_reporter/client.py @@ -275,8 +275,11 @@ def fetch_issues_with_spent_time( along with total spent hours per issue. If user_id is None, uses current user. If by_activity is True, returns per-activity breakdown as third tuple element. - If dedup_before is set, filters out time entries whose created_on AND updated_on - are both before dedup_before (AND logic: both must be < cutoff to exclude). + If dedup_before is set, entries with both fields set are kept only when + created_on AND updated_on are both >= dedup_before; an entry is excluded + if at least one of the fields is before the cutoff (dedup_before). + Entries with both fields missing (None) are kept; + if only one field is set, that field alone decides (>= cutoff keeps the entry). Returns list of (issue, total_hours, activities) tuples. Raises RedmineAPIError on API/auth/network failures. """ @@ -300,8 +303,11 @@ def fetch_issues_with_spent_time( raise RedmineAPIError(_format_redmine_error(exc), original=exc) from exc # Дедупликация: отсекаем записи, которые были учтены в предыдущем отчёте. - # Запись исключается, если BOTH created_on AND updated_on < dedup_before. - # Записи без метаданных (created_on/updated_on == None) не фильтруются. + # Если оба поля заданы, запись сохраняется, только если created_on И updated_on + # оба >= dedup_before; если хотя бы одно из полей < dedup_before, + # запись исключается. + # Если оба поля None — запись сохраняется; если задано только одно поле, + # решает оно (>= dedup_before → запись сохраняется). if dedup_before is not None: # Нормализуем cutoff к aware UTC (#58): naive cutoff трактуем как UTC, # чтобы сравнение с нормализованными created_on/updated_on было корректным.