From 608afe08e3dfc86f44aad6e95820fd1441c9a8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Fri, 10 Jul 2026 16:33:28 +0700 Subject: [PATCH 1/4] fix: default missing period.default_to to today --- redmine_reporter/config.py | 21 ++++++++----------- tests/test_config.py | 42 ++++++++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/redmine_reporter/config.py b/redmine_reporter/config.py index 11a4e62..7a5bd6f 100644 --- a/redmine_reporter/config.py +++ b/redmine_reporter/config.py @@ -370,8 +370,10 @@ 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_str = date.today().isoformat() + + if from_env: + return f"{from_env}--{to_env or today_str}" if ( cls._app @@ -386,17 +388,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()}" + start = date.today().replace(day=1) + return f"{start.isoformat()}--{today_str}" @classmethod def validate(cls) -> None: diff --git a/tests/test_config.py b/tests/test_config.py index 21348d5..c1feb7b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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().""" From 8992bb922ee74920be00d1da1a7a9010a5784573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Fri, 10 Jul 2026 16:40:22 +0700 Subject: [PATCH 2/4] docs: explain optional period.default_to and today fallback --- docs/CONFIG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 660e66f..d181886 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -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 From 17b0e99aa3fdb8d8c760d3d0ce7ee1deca5727dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Fri, 10 Jul 2026 16:46:30 +0700 Subject: [PATCH 3/4] 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 --- README.md | 4 +++- pyproject.toml | 2 +- redmine_reporter/__init__.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f4cfc0..1cf982e 100644 --- a/README.md +++ b/README.md @@ -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. | ## Использование diff --git a/pyproject.toml b/pyproject.toml index 2d977b7..063a232 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" }] diff --git a/redmine_reporter/__init__.py b/redmine_reporter/__init__.py index fcfdf38..a0865bb 100644 --- a/redmine_reporter/__init__.py +++ b/redmine_reporter/__init__.py @@ -1 +1 @@ -__version__ = "1.10.0" +__version__ = "1.10.1" From 22f1733a5d4269687279327fba660a9bea7599f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Fri, 10 Jul 2026 16:50:44 +0700 Subject: [PATCH 4/4] 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. --- redmine_reporter/config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/redmine_reporter/config.py b/redmine_reporter/config.py index 7a5bd6f..9d3fc29 100644 --- a/redmine_reporter/config.py +++ b/redmine_reporter/config.py @@ -370,7 +370,8 @@ 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() - today_str = date.today().isoformat() + today = date.today() + today_str = today.isoformat() if from_env: return f"{from_env}--{to_env or today_str}" @@ -392,7 +393,7 @@ class Config: default_to = cls._app.period_default_to or today_str return f"{cls._app.period_default_from}--{default_to}" - start = date.today().replace(day=1) + start = today.replace(day=1) return f"{start.isoformat()}--{today_str}" @classmethod