Commit Graph

73 Commits

Author SHA1 Message Date
Кокос Артем Николаевич
598f2d35a1 fix: resolve --user-login by exact match
Redmine user.filter(login=...) performs an inexact substring search, but
_resolve_user_id took users[0].id unconditionally, so a report could
silently be built for the wrong user (e.g. 'ivanov' matching 'ivanov2').

Now only exact (case-sensitive) login matches are considered: exactly one
match resolves to its id, multiple matches raise an ambiguity error,
no match falls through to the name lookup and then to a 'not found'
error — symmetric with the --user-name resolution.

Closes #60
2026-07-17 12:21:47 +07:00
Кокос Артем Николаевич
46674ba926 fix: normalize datetimes to aware UTC in dedup
Dedup with precision=datetime crashed with TypeError: redminelib returns
naive created_on/updated_on while the cutoff from _compute_dedup_cutoff
is aware UTC. Normalize at a single point: _parse_datetime now always
returns an aware datetime (naive treated as UTC), matching its docstring.
The dedup cutoff is normalized the same way, and any residual comparison
TypeError is wrapped in RedmineAPIError instead of leaking raw.

Also fix --commit saving last_used.to as naive local time; it now stores
aware UTC (datetime.now(timezone.utc)) so the next run computes a correct
aware cutoff.

Closes #58
2026-07-17 12:11:35 +07:00
Кокос Артем Николаевич
c4ec23048a fix: accept datetime ranges in parse_date_range
With period.dynamic: true and period.precision: datetime, running
without --date crashed: compute_next_period() returns a datetime range
(YYYY-MM-DDTHH:MM:SS), which parse_date_range() rejected with a
fullmatch against YYYY-MM-DD only.

parse_date_range now accepts YYYY-MM-DDTHH:MM:SS--YYYY-MM-DDTHH:MM:SS
in addition to plain date ranges, returning datetime strings unchanged
(pass-through, matching compute_next_period output). Mixed-precision
ranges, invalid times and reversed ranges are still rejected with the
existing error messages; date-range behavior is unchanged.

Closes #59
2026-07-17 11:44:13 +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
Кокос Артем Николаевич
608afe08e3 fix: default missing period.default_to to today 2026-07-10 16:33:28 +07:00
Кокос Артем Николаевич
e1862462af feat: add HTML email body for --send (#48)
Add email.html config flag (default false). When enabled, --send
includes an HTML version of the report body generated via HTMLFormatter
alongside the plain-text part in a multipart/alternative message.

- EmailConfig gains html: bool field
- mailer.build_message/send_report accept rows for HTML generation
- CLI passes rows to send_report
- --init-config generates email.html: false
- README.md and docs/CONFIG.md updated

Bump version to 1.10.0.

Closes #48
2026-07-10 15:24:27 +07:00
Кокос Артем Николаевич
863ad50cc3 feat: add report.no_time config option for automatic modes
Add YAML section `report.no_time` that controls `--no-time` behavior
in automatic modes (`--commit`, `--send`). CLI flag `--no-time`
always wins. Manual `--output` ignores the YAML setting.

- Config.get_report_no_time() reads the YAML value (defaults to false)
- cli._resolve_no_time() encapsulates CLI > YAML priority
- --init-config now generates the report section
- docs updated in README.md and docs/CONFIG.md

Closes #49
2026-07-10 14:38:07 +07:00
Кокос Артем Николаевич
0968560090 style: apply ruff format to all source files 2026-07-10 12:39:24 +07:00
Кокос Артем Николаевич
25425901b1 fix: handle save_period_to_config errors, remove duplicate _MockIssue
- cli.py: wrap save_period_to_config in try/except to avoid
  unhandled traceback on disk full / permission denial (I2)
- test_cli.py: remove duplicate _MockIssue class definition (I3)
2026-07-10 12:37:46 +07:00
Кокос Артем Николаевич
b0e353c565 feat: auto-email sending via SMTP (--send flag)
Closes #45

- New redmine_reporter/mailer.py: SMTP email sending with
  {author}/{period} template substitution, MIME attachment
  with correct content-type per file extension
- Config.get_email_config(): returns EmailConfig from YAML
  or None when not configured
- CLI --send flag: sends report after generation, works with
  --output, --commit, or standalone (saves to template path)
- 31 new tests (22 mailer + 6 CLI + 3 config)
- 249/249 tests passing, ruff clean, mypy clean
2026-07-10 12:37:02 +07:00
Кокос Артем Николаевич
b926dd0d49 chore: bump version to 1.9.0 2026-07-07 11:11:58 +07:00
Кокос Артем Николаевич
9b78d66769 feat: --commit flag (#44)
- --commit: после генерации отчёта сохраняет период в
  period.last_used.from/to YAML-конфига
- При period.dynamic=true следующий запуск вычисляет период от last_used:
  полный месяц → следующий месяц, произвольный диапазон → та же длина
- При period.dynamic=false перезаписывает default_from/default_to
- При period.precision=datetime сохраняет timestamps с точностью до секунд
- --commit без --output сохраняет отчёт по шаблону из конфига
- compute_next_period() в config.py
- save_period_to_config() в yaml_config.py
- 23 новых теста (7 CLI, 6 config, 6 yaml, 4 date_range)

Closes #44
2026-07-07 10:47:08 +07:00
Кокос Артем Николаевич
47152f8f04 feat: output path defaults (#43) + datetime precision & dedup (#47)
#43 — Имя файла и пути по умолчанию:
- resolve_output_path() в yaml_config.py: резолвит --output с учётом
  output.dir, filename_template, default_format из YAML-конфига
- Bare format (xlsx/odt/...) → путь по шаблону
- Без расширения → автодописывается default_format (.xlsx)
- Config.get_output_dir/filename/default_format()

#47 — datetime precision и дедупликация:
- period.last_used.from/to в YAML-конфиге и AppConfig
- period.precision: date|datetime
- _compute_dedup_cutoff() в cli.py: при precision=datetime вычисляет
  cutoff из period.last_used.to
- _parse_datetime() + AND-логика дедупликации в client.py:
  запись исключается если created_on И updated_on < cutoff
- Пропуск issues без часов после дедупликации

Closes #43
Closes #47
2026-07-07 10:16:46 +07:00
Кокос Артем Николаевич
67350bfcd6 fix: verify_ssl: true in YAML now uses DEFAULT_REDMINE_VERIFY path
Previously boolean True was passed directly to python-redmine
which used system CA bundle instead of the project's default CA file.
Now 'true' in YAML maps to DEFAULT_REDMINE_VERIFY path.
2026-07-03 18:24:11 +07:00
Кокос Артем Николаевич
0fa4e271a7 fix: auto-load YAML config on CLI startup
CLI now reads ~/.config/redmine-reporter/config.yml automatically.
Previously Config.load_yaml() existed but was never called.
2026-07-03 18:19:30 +07:00
Кокос Артем Николаевич
b1a565bc9e feat: filename template expansion with {date} placeholder
- Add expand_filename_template() to yaml_config.py
- Supports {author}, {from}, {to}, {date}, {ext} placeholders
- {date} formats as DD_MM_YYYY (e.g. 31_03_2026)
- Spaces in author replaced with underscores for safe filenames
- Unknown placeholders left as-is
- Add comprehensive CONFIG.md documentation covering YAML setup, migration, and template syntax
2026-07-03 18:13:55 +07:00
Кокос Артем Николаевич
c962a93f30 feat: YAML config support (~/.config/redmine-reporter/config.yml)
- Add AppConfig/SmtpConfig/EmailConfig dataclasses with from_yaml()/from_env()
- Add yaml_config.py: ${VAR} resolver, 0700/0600 permission helpers
- Config.get_*() methods gain YAML fallback in priority chain
- Priority: CLI > env > .env > YAML > code defaults
- CLI --init-config generates YAML from current environment
- --force flag allows overwriting existing config
- Secrets default to ${VAR} references, plaintext allowed
- Full backward compatibility with existing .env setups

Closes #46
2026-07-03 18:00:28 +07:00
Кокос Артем Николаевич
676f7ede30 fix: bump __version__ to 1.8.1 2026-06-30 09:48:37 +07:00
Кокос Артем Николаевич
5dd234e7b3 fix(odt): remove stray blank paragraph from template (#42)
The template contained an empty <text:p/> that was rendered as
a blank line before the report header.  After loading the template,
strip all text:p children so that ODT output no longer depends on
template editing artifacts.

Closes #42.
2026-06-30 09:43:08 +07:00
Кокос Артем Николаевич
59af7ce464 feat(activity): add --by-activity flag to break down spent time by activity type
- Load time entry activity names from Redmine enumeration.
- Aggregate hours per issue and per activity in fetch_issues_with_spent_time.
- Add --by-activity CLI flag and propagate it through report builder,
  summary, and all formatters (console, CSV, HTML, JSON, ODT).
- Keep backward-compatible 2-tuple input in build_grouped_report.
- Bump version to 1.8.0.

Closes #41
2026-06-29 16:44:52 +07:00
Кокос Артем Николаевич
f6861382e6 feat(user): report on another user's time entries
- Add user_id parameter to fetch_issues_with_spent_time().
- Support numeric ID, login, or full name resolution.
- Reject ambiguous names and unknown users with clear messages.
- Add CLI flags: --user-id, --user-login, --user-name.
- Only allow one user flag at a time.
- Add ResourceNotFoundError handling.
- Update README with usage examples.
- Add tests for user resolution and CLI flags.

Closes #40
2026-06-29 15:15:17 +07:00
Кокос Артем Николаевич
f80f3a8b52 feat(errors): provide readable Redmine API error messages
- Introduce RedmineAPIError with human-friendly messages.
- Distinguish AuthError, ForbiddenError, HTTP status codes, timeouts
  and connection errors in client.py.
- Update CLI to print the readable message instead of generic
  "Redmine API error: ...".
- Log original exception with traceback when --debug is enabled.
- Add tests for all error paths and CLI output.

Closes #39
2026-06-29 14:58:42 +07:00
Кокос Артем Николаевич
222d31730e feat(xlsx): make Excel export production-grade
- Add merge cells for project/version groups.
- Add numeric Hours column and human-readable Spent Time.
- Add version/project/grand totals.
- Apply auto-width, freeze panes, auto-filter and styling.
- Respect --no-time: keep columns empty, skip totals.
- Pass no_time flag through formatter factory to all file formatters.
- Add tests for XLSX features and --no-time behavior.

Closes #38
2026-06-29 14:41:37 +07:00
Кокос Артем Николаевич
86f083aa79 chore(release): bump version to 1.6.0
Bump version from 1.5.0 to 1.6.0 in __init__.py and pyproject.toml.
Update test_cli_version_flag to assert against the current package
version instead of a hardcoded string.
2026-06-29 12:38:54 +07:00
Кокос Артем Николаевич
a82be05b83 fix: handle empty time entries and add missing mypy override for openpyxl
- Treat empty list from fetch_issues_with_spent_time as 'no entries' and exit 0
  instead of crashing later in the pipeline.
- Update CLI output-extension tests to use non-empty mock data so they actually
  reach the extension validation code path.
- Add openpyxl to mypy ignore_missing_imports overrides.

Closes #36
2026-06-29 12:21:44 +07:00
Кокос Артем Николаевич
ca89832d74 feat: JSON, Excel export and time summary
- #23: add JSONFormatter and XLSXFormatter
- add openpyxl dependency for .xlsx export
- #22: add --summary flag and calculate_summary() in report_builder
- ReportRow now carries raw hours for summary calculations
- update CLI help and README with .json/.xlsx formats and --summary
- add tests for new formatters and summary computation

Closes #22, closes #23
2026-06-29 12:09:58 +07:00
Кокос Артем Николаевич
f6afc4096d feat(cli): dynamic default range, --version, --verbose, --debug, --url, --api-key, --config
- #17: default date range falls back to current month when .env dates are missing
- #18: add --version, --verbose, --debug flags
- #20: add --url, --api-key, --config CLI overrides
- Config supports CLI overrides for URL/API key and explicit config file loading
- Update README with new CLI options
- Add tests for new flags and config overrides

Closes #17, closes #18, closes #20
2026-06-29 12:02:51 +07:00
Кокос Артем Николаевич
738d9d543e fix: mount retry adapter on Redmine engine session
python-redmine exposes  as a context manager, while the
actual requests.Session lives in . Mounting the
retry adapter on the wrong object caused:

    'function' object has no attribute 'mount'

on startup. Update the related test to mock the real session.
2026-06-29 11:53:09 +07:00
Артём Кокос
58fa5a7ab4 Add retry/backoff, timeout, and issue_id chunking to Redmine client
- #24: Mount HTTPAdapter with Retry(total=3, backoff_factor=0.5) for
  HTTP 429/500/502/503/504 on the Redmine session, set 30s timeout
- #21: Split large issue_id lists into chunks of 100 to avoid exceeding
  URL length limits on reverse proxies

Closes #24, #21
2026-06-27 13:06:41 +07:00
Артём Кокос
14219564dd Fix 9 bugs: ODT covered-cells, CSV BOM, HTML charset, stderr, dead code
- #13 (critical): Add CoveredTableCell elements to ODT for valid row spans (ODF 1.2)
- #28: Move "Total issues" info message from stdout to stderr (clean pipe output)
- #27: Wrap HTML export in full document with DOCTYPE and meta charset utf-8
- #26: Save CSV with utf-8-sig encoding (UTF-8 BOM for Excel compatibility)
- #31: Document CSV uses full project/version values (not display_* like console/MD)
- #30: Fix ODT header formatting when author is empty (no leading dot/space)
- #36: Remove test_cli_smoke_empty testing unreachable code path (return [])
- #37: Remove unused mock_path variable in ODT test fixture
- #34: Remove unreachable len(parts) != 2 check in parse_date_range

Closes #13, #28, #27, #26, #31, #30, #36, #37, #34
2026-06-27 13:01:32 +07:00
Артём Кокос
da069993b9 Extract grouping logic into shared function (#19)
Add group_rows_by_project_and_version() to report_builder.py and replace
the duplicated inline grouping in both html.py and odt.py with a single
call to the shared function. This ensures consistent grouping behavior
across formatters and removes a maintenance burden — any change to
grouping logic now happens in one place.

Closes #19
2026-06-26 00:21:58 +07:00
Артём Кокос
dbc4cf960a Fix version display, deterministic sort, remove duplicate sort
get_version() now returns version.name instead of str(version), which
returned the numeric ID from redminelib.resources.Version. Falls back
to str() when .name is absent (#14).

Add issue.id as tertiary sort key in build_grouped_report so tasks
within the same project+version group always appear in the same order
regardless of API response ordering (#33).

Remove redundant sort from client.fetch_issues_with_spent_time — the
sort already runs in report_builder.build_grouped_report, so doing it
twice wastes CPU. Also remove the now-unused get_version import (#32).

Closes #14, closes #32, closes #33
2026-06-25 23:32:14 +07:00
Артём Кокос
3a6d1b7ba7 Fix .env override priority and password whitespace stripping
Change load_dotenv() to load_dotenv(override=False) so real environment
variables always take priority over stale .env files. This prevents a
hardcoded or outdated REDMINE_API_KEY in .env from silently overriding
a fresh token exported in the shell (#15).

Add .strip() to get_redmine_password() for consistency with all other
config getters — trailing whitespace in .env no longer causes silent
auth failures (#35).

Closes #15, closes #35
2026-06-25 21:06:19 +07:00
Артём Кокос
3956decd4e Fix package structure and lazy-import ODT formatter
Add missing __init__.py to formatters/ so setuptools.find_packages
includes the subpackage in wheel/sdist builds (#16).

Move ODTFormatter import from top-level to lazy import inside
get_formatter_by_extension() so missing odfpy no longer crashes module
load before main() runs. Remove dead except ImportError handler in
cli.py save() block; surface a clear 'odfpy is not installed' message
when the formatter factory returns None for .odt (#25).

Closes #16, closes #25
2026-06-25 20:42:13 +07:00
Кокос Артем Николаевич
0e4e0f3ee2 Bump version: v1.5.0 2026-05-22 17:49:18 +07:00
Кокос Артем Николаевич
2db0ab1f0b Tighten configuration and export handling 2026-05-22 17:41:56 +07:00
Кокос Артем Николаевич
8bc8181ce3 Add Redmine API token authentication 2026-05-22 17:19:00 +07:00
Artem Kokos
7bc6e024c0 Quick fixes & tests 2026-03-28 23:55:46 +07:00
Кокос Артем Николаевич
06cd57e2c4 Blacked 2026-02-05 15:31:31 +07:00
Кокос Артем Николаевич
d7e927e6eb Bump version: v1.4.1 2026-02-05 15:18:40 +07:00
Кокос Артем Николаевич
000bf37503 Update status map 2026-02-05 15:15:44 +07:00
Кокос Артем Николаевич
b7f03666dc Bump version: v1.4.0 2026-01-26 13:43:54 +07:00
Кокос Артем Николаевич
30310d614d Add HTML formatter
Closes #7
2026-01-26 12:57:33 +07:00
Кокос Артем Николаевич
ad62ef4f6c Fix CSV & MD 2026-01-26 12:56:57 +07:00
Кокос Артем Николаевич
355849e004 Fix issue mock for tests 2026-01-26 12:37:57 +07:00
Артём Кокос
ea90fe79d0 ODT template into resources 2026-01-25 12:24:32 +07:00
Артём Кокос
8278864b01 Formatters factory 2026-01-25 11:01:34 +07:00
Кокос Артем Николаевич
f858618a13 Bump version: 1.3.0 2026-01-24 16:10:03 +07:00
Кокос Артем Николаевич
e344715f61 feat(formatter): unify data pipeline with ReportRow and report_builder 2026-01-24 16:09:25 +07:00