Merge branch 'fix/default-to-today'

This commit is contained in:
Кокос Артем Николаевич
2026-07-10 16:52:55 +07:00
6 changed files with 70 additions and 22 deletions

View File

@@ -72,6 +72,7 @@ redmine:
period: period:
precision: date # date | datetime precision: date # date | datetime
default_from: "2026-06-01" default_from: "2026-06-01"
# default_to можно не указывать — конец периода будет сегодня
default_to: "2026-06-30" default_to: "2026-06-30"
dynamic: false dynamic: false
# last_used заполняется --commit (см. docs/CONFIG.md) # last_used заполняется --commit (см. docs/CONFIG.md)
@@ -115,6 +116,7 @@ REDMINE_URL=https://red.eltex.loc/
REDMINE_API_KEY=ваш_api_token REDMINE_API_KEY=ваш_api_token
REDMINE_AUTHOR=Иванов Иван Иванович REDMINE_AUTHOR=Иванов Иван Иванович
DEFAULT_FROM_DATE=2026-01-01 DEFAULT_FROM_DATE=2026-01-01
# DEFAULT_TO_DATE можно не задавать — тогда конец периода будет сегодня
DEFAULT_TO_DATE=2026-01-31 DEFAULT_TO_DATE=2026-01-31
``` ```
@@ -128,7 +130,7 @@ DEFAULT_TO_DATE=2026-01-31
| `REDMINE_PASSWORD` | Да, если нет токена | Пароль Redmine. | | `REDMINE_PASSWORD` | Да, если нет токена | Пароль Redmine. |
| `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. | | `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. |
| `DEFAULT_FROM_DATE` | Нет | Начальная дата периода по умолчанию (`YYYY-MM-DD`). | | `DEFAULT_FROM_DATE` | Нет | Начальная дата периода по умолчанию (`YYYY-MM-DD`). |
| `DEFAULT_TO_DATE` | Нет | Конечная дата периода по умолчанию (`YYYY-MM-DD`). | | `DEFAULT_TO_DATE` | Нет | Конечная дата периода по умолчанию (`YYYY-MM-DD`). Если не задана, а `DEFAULT_FROM_DATE` задана — используется сегодняшняя дата. |
| `REDMINE_VERIFY` | Нет | TLS-проверка: `true` / `false` / путь к CA bundle. | | `REDMINE_VERIFY` | Нет | TLS-проверка: `true` / `false` / путь к CA bundle. |
## Использование ## Использование

View File

@@ -32,6 +32,7 @@ redmine:
period: period:
precision: date precision: date
default_from: "2026-06-01" default_from: "2026-06-01"
# default_to можно не указывать — тогда конец периода будет сегодня
default_to: "2026-06-30" default_to: "2026-06-30"
dynamic: false dynamic: false
last_used: last_used:
@@ -73,6 +74,23 @@ email:
`last_used.from` / `last_used.to` записываются автоматически при `--commit`. Вручную редактировать не требуется. `last_used.from` / `last_used.to` записываются автоматически при `--commit`. Вручную редактировать не требуется.
### `period.default_to` — необязательное окончание периода
Если `period.default_to` не задан, а `period.default_from` задан, инструмент
использует сегодняшнюю дату в качестве конца периода.
```yaml
period:
default_from: "2026-07-01"
# default_to отсутствует → конец периода = сегодня
```
Это предотвращает устаревание периода, когда отчёт генерируется автоматически
(`--send`, `--commit`) без явного `--date`.
Аналогично работает `DEFAULT_TO_DATE`: если переменная не задана, а
`DEFAULT_FROM_DATE` задана, конец периода = сегодня.
### `--commit` — автофиксация периода ### `--commit` — автофиксация периода
Флаг `--commit` сохраняет использованный период в YAML-конфиг, чтобы следующий запуск автоматически начинался с нового периода. Флаг `--commit` сохраняет использованный период в YAML-конфиг, чтобы следующий запуск автоматически начинался с нового периода.
@@ -353,6 +371,10 @@ vim ~/.config/redmine-reporter/config.yml
| `--init-config --force` | Перезаписать существующий YAML | | `--init-config --force` | Перезаписать существующий YAML |
| `--config-path PATH` | Сохранить YAML по указанному пути (по умолчанию `~/.config/redmine-reporter/config.yml`) | | `--config-path PATH` | Сохранить YAML по указанному пути (по умолчанию `~/.config/redmine-reporter/config.yml`) |
Если `DEFAULT_TO_DATE` не задана, а `DEFAULT_FROM_DATE` задана, сгенерированный
YAML будет содержать пустое `default_to`, и при запуске инструмент использует
сегодняшнюю дату.
### Проверка после миграции ### Проверка после миграции
```bash ```bash

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "redmine-reporter" name = "redmine-reporter"
version = "1.10.0" version = "1.10.1"
description = "Redmine time-entry based issue reporter for internal use" description = "Redmine time-entry based issue reporter for internal use"
readme = "README.md" readme = "README.md"
authors = [{ name = "Artem Kokos", email = "artem-kokos@mail.ru" }] authors = [{ name = "Artem Kokos", email = "artem-kokos@mail.ru" }]

View File

@@ -1 +1 @@
__version__ = "1.10.0" __version__ = "1.10.1"

View File

@@ -370,8 +370,11 @@ class Config:
def get_default_date_range(cls) -> str: def get_default_date_range(cls) -> str:
from_env = os.getenv("DEFAULT_FROM_DATE", "").strip() from_env = os.getenv("DEFAULT_FROM_DATE", "").strip()
to_env = os.getenv("DEFAULT_TO_DATE", "").strip() to_env = os.getenv("DEFAULT_TO_DATE", "").strip()
if from_env and to_env: today = date.today()
return f"{from_env}--{to_env}" today_str = today.isoformat()
if from_env:
return f"{from_env}--{to_env or today_str}"
if ( if (
cls._app cls._app
@@ -386,17 +389,12 @@ class Config:
) )
return f"{nf}--{nt}" return f"{nf}--{nt}"
if cls._app and cls._app.period_default_from and cls._app.period_default_to: if cls._app and cls._app.period_default_from:
return f"{cls._app.period_default_from}--{cls._app.period_default_to}" default_to = cls._app.period_default_to or today_str
return f"{cls._app.period_default_from}--{default_to}"
today = date.today()
start = today.replace(day=1) start = today.replace(day=1)
if today.month == 12: return f"{start.isoformat()}--{today_str}"
next_month = today.replace(year=today.year + 1, month=1, day=1)
else:
next_month = today.replace(month=today.month + 1, day=1)
end = next_month - timedelta(days=1)
return f"{start.isoformat()}--{end.isoformat()}"
@classmethod @classmethod
def validate(cls) -> None: def validate(cls) -> None:

View File

@@ -79,21 +79,29 @@ def test_get_default_date_range_from_env():
assert Config.get_default_date_range() == "2026-01-01--2026-01-31" assert Config.get_default_date_range() == "2026-01-01--2026-01-31"
@mock.patch.dict(
os.environ,
{"DEFAULT_FROM_DATE": "2026-01-01"},
clear=True,
)
def test_get_default_date_range_from_env_without_to():
"""Если DEFAULT_TO_DATE не задан, конец периода — сегодня."""
from datetime import date
today = date.today().isoformat()
assert Config.get_default_date_range() == f"2026-01-01--{today}"
@mock.patch.dict(os.environ, {}, clear=True) @mock.patch.dict(os.environ, {}, clear=True)
def test_get_default_date_range_fallback(): def test_get_default_date_range_fallback():
"""Если даты не заданы -- используется текущий месяц.""" """Если даты не заданы -- используется текущий месяц с начала до сегодня."""
from datetime import date, timedelta from datetime import date
today = date.today() today = date.today()
start = today.replace(day=1) start = today.replace(day=1)
if today.month == 12:
next_month = today.replace(year=today.year + 1, month=1, day=1)
else:
next_month = today.replace(month=today.month + 1, day=1)
end = next_month - timedelta(days=1)
result = Config.get_default_date_range() result = Config.get_default_date_range()
assert result == f"{start.isoformat()}--{end.isoformat()}" assert result == f"{start.isoformat()}--{today.isoformat()}"
@mock.patch.dict(os.environ, {}, clear=True) @mock.patch.dict(os.environ, {}, clear=True)
@@ -567,6 +575,24 @@ class TestDefaultDateRangeWithLastUsed:
result = Config.get_default_date_range() result = Config.get_default_date_range()
assert result == "2026-04-01--2026-04-15" assert result == "2026-04-01--2026-04-15"
@mock.patch.dict(os.environ, {}, clear=True)
def test_falls_back_to_today_when_default_to_missing(self):
"""Если default_from задан, а default_to нет — конец периода сегодня."""
from datetime import date
with tempfile.TemporaryDirectory() as tmp:
yaml_path = Path(tmp) / "config.yml"
yaml_path.write_text(
"period:\n"
" precision: date\n"
" dynamic: false\n"
" default_from: '2026-04-01'\n"
)
Config._app = AppConfig.from_yaml(yaml_path)
today = date.today().isoformat()
result = Config.get_default_date_range()
assert result == f"2026-04-01--{today}"
class TestGetEmailConfig: class TestGetEmailConfig:
"""Tests for Config.get_email_config().""" """Tests for Config.get_email_config()."""