Add --weekly flag to csv command

- Add `write_csv_weekly()` to output.py: writes entries from multiple
  days as a single CSV with one header row, correct date per row
- Add `-w`/`--weekly` flag to csv subparser
- _cmd_csv branches on args.weekly: fetches week sections, formats
  per-day date strings, calls write_csv_weekly; --raw is honoured
- Add TestWriteCsvWeekly with 6 tests
- Update README with weekly csv usage examples
This commit is contained in:
Jef Roosens 2026-05-28 13:14:43 +02:00
parent 985ee28113
commit cd8ca789aa
Signed by: Jef Roosens
GPG key ID: 119385BCAA005C21
4 changed files with 134 additions and 10 deletions

View file

@ -3,7 +3,13 @@ import io
import pytest
from timesheets.output import print_stories, print_summary, to_csv_entries, write_csv
from timesheets.output import (
print_stories,
print_summary,
to_csv_entries,
write_csv,
write_csv_weekly,
)
# ---------------------------------------------------------------------------
# Shared fixtures
@ -129,6 +135,69 @@ class TestWriteCsv:
assert rows[1][3] == "ticket 1"
# ---------------------------------------------------------------------------
# write_csv_weekly
# ---------------------------------------------------------------------------
DAY_SECTIONS = [
(
"22/03/26",
[
{"project": "bugs", "description": "ticket 1", "quantity": 1.0},
{"project": "scrum", "description": "dsu", "quantity": 0.25},
],
),
(
"23/03/26",
[
{"project": "bugs", "description": "ticket 2", "quantity": 0.5},
],
),
]
class TestWriteCsvWeekly:
def _run(self, day_sections=None, project_map=None):
buf = io.StringIO()
write_csv_weekly(
DAY_SECTIONS if day_sections is None else day_sections,
buf,
project_map or {},
)
buf.seek(0)
return list(csv.reader(buf))
def test_header_written_once(self):
rows = self._run()
assert rows[0] == ["Date*", "Project*", "Task", "Description", "Quantity"]
assert sum(1 for r in rows if r[0] == "Date*") == 1
def test_row_count(self):
rows = self._run()
assert len(rows) == 1 + 3 # header + 3 entries across 2 days
def test_correct_date_per_row(self):
rows = self._run()
assert rows[1][0] == "22/03/26"
assert rows[2][0] == "22/03/26"
assert rows[3][0] == "23/03/26"
def test_empty_sections_produce_header_only(self):
rows = self._run(day_sections=[])
assert rows == [["Date*", "Project*", "Task", "Description", "Quantity"]]
def test_project_map_applied(self):
rows = self._run(project_map=PROJECT_MAP)
assert rows[1][1] == "[Factry] Historian"
assert rows[1][2] == "[Historian] Bugs"
def test_quantity_format(self):
rows = self._run()
assert rows[1][4] == "1.00"
assert rows[3][4] == "0.50"
# ---------------------------------------------------------------------------
# print_summary
# ---------------------------------------------------------------------------