5 Commits

Author SHA1 Message Date
Кокос Артем Николаевич
5f51d40fef Merge branch 'fix/default-to-today' 2026-07-10 16:52:55 +07:00
Кокос Артем Николаевич
22f1733a5d refactor: use single date.today() call in get_default_date_range
Avoids potential midnight inconsistency between start and end of the
fallback period. Related to #50.
2026-07-10 16:50:44 +07:00
Кокос Артем Николаевич
17b0e99aa3 chore: bump version to 1.10.1 and update README
- Update README to document optional DEFAULT_TO_DATE / default_to
- Default end date falls back to today when not explicitly set

Closes #50
2026-07-10 16:46:30 +07:00
Кокос Артем Николаевич
8992bb922e docs: explain optional period.default_to and today fallback 2026-07-10 16:40:22 +07:00
Кокос Артем Николаевич
608afe08e3 fix: default missing period.default_to to today 2026-07-10 16:33:28 +07:00
6 changed files with 70 additions and 22 deletions

View File

@@ -72,6 +72,7 @@ redmine:
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)
@@ -115,6 +116,7 @@ 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
```
@@ -128,7 +130,7 @@ DEFAULT_TO_DATE=2026-01-31
| `REDMINE_PASSWORD` | Да, если нет токена | Пароль Redmine. |
| `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. |
| `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. |
## Использование

View File

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

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "redmine-reporter"
version = "1.10.0"
version = "1.10.1"
description = "Redmine time-entry based issue reporter for internal use"
readme = "README.md"
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:
from_env = os.getenv("DEFAULT_FROM_DATE", "").strip()
to_env = os.getenv("DEFAULT_TO_DATE", "").strip()
if from_env and to_env:
return f"{from_env}--{to_env}"
today = date.today()
today_str = today.isoformat()
if from_env:
return f"{from_env}--{to_env or today_str}"
if (
cls._app
@@ -386,17 +389,12 @@ class Config:
)
return f"{nf}--{nt}"
if cls._app and cls._app.period_default_from and cls._app.period_default_to:
return f"{cls._app.period_default_from}--{cls._app.period_default_to}"
if cls._app and cls._app.period_default_from:
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)
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)
return f"{start.isoformat()}--{end.isoformat()}"
return f"{start.isoformat()}--{today_str}"
@classmethod
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"
@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)
def test_get_default_date_range_fallback():
"""Если даты не заданы -- используется текущий месяц."""
from datetime import date, timedelta
"""Если даты не заданы -- используется текущий месяц с начала до сегодня."""
from datetime import date
today = date.today()
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()
assert result == f"{start.isoformat()}--{end.isoformat()}"
assert result == f"{start.isoformat()}--{today.isoformat()}"
@mock.patch.dict(os.environ, {}, clear=True)
@@ -567,6 +575,24 @@ class TestDefaultDateRangeWithLastUsed:
result = Config.get_default_date_range()
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:
"""Tests for Config.get_email_config()."""