Skip to content

Commit 074050d

Browse files
committed
feat(cli): add detailed calendar reporting
add a new calendar detailed command with day-by-day events, day-off\nreasons, and celebrations\n\nadd a global --duration-format flag for minute-based read output\n(minutes, hours, days, hhmm) across worktimes, absence, saldo,\nholidays, and calendar commands\n\nupdate help text, docs, and skill guidance to state that worktimes\ncommands are worktime-only and to prefer calendar detailed/overview\nfor full schedule interpretation
1 parent 55706e0 commit 074050d

21 files changed

Lines changed: 1264 additions & 61 deletions

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ It is intended for Finnish time tracking use cases.
77

88
- auth command (`otta auth login`)
99
- status check command (`otta status`)
10+
- cumulative saldo command (`otta saldo`)
1011
- worktime commands (`list/browse/report/options/add/update/delete`)
11-
- calendar overview command (`otta calendar overview`)
12+
- calendar commands (`overview/detailed`)
1213
- holidays retrieval command
1314
- absence commands (`options/browse/comment`)
1415
- configurable local config path
@@ -67,13 +68,35 @@ otta worktimes list --date 2026-02-20
6768
otta worktimes browse --from 2026-02-20 --to 2026-02-26 --format json
6869
otta worktimes report --from 2026-02-01 --to 2026-02-28 --format csv
6970
otta calendar overview --from 2026-02-01 --to 2026-02-28 --format json
71+
otta calendar detailed --from 2026-02-01 --to 2026-02-28 --format json
7072
otta worktimes options --date 2026-02-20 --format json
73+
otta saldo --format json
7174
otta holidays --from 2026-02-20 --to 2026-02-20 --worktimegroup <id> --format json
7275
otta absence browse --from 2026-02-01 --to 2026-02-28 --format json
7376
otta absence options --format json
7477
otta absence comment --type sick --from 2026-02-20 --to 2026-02-20
7578
```
7679

80+
Important: `worktimes list/browse/report` return only worktime rows and do not include absences.
81+
For full day-by-day schedule checks (worktimes + absences + holidays/day-off signals), prefer:
82+
83+
```bash
84+
otta calendar detailed --from 2026-02-01 --to 2026-02-28 --format json
85+
```
86+
87+
Duration conversion on read commands:
88+
89+
- Global flag: `--duration-format` with values `minutes` (default), `hours`, `days`, `hhmm`
90+
- Works across read commands that expose minute totals (`worktimes`, `absence browse`, `saldo`, `holidays`, `calendar overview`, `calendar detailed`)
91+
- Day conversion uses a fixed basis: `1 day = 24 hours = 1440 minutes`
92+
- Example:
93+
94+
```bash
95+
otta calendar detailed --from 2026-02-01 --to 2026-02-28 --duration-format hours
96+
otta worktimes browse --from 2026-02-01 --to 2026-02-28 --format json --duration-format days
97+
otta absence browse --from 2026-02-01 --to 2026-02-28 --format json --duration-format hhmm
98+
```
99+
77100
For non-interactive scripts, prefer stdin or env secrets to reduce shell history exposure:
78101

79102
```bash
@@ -110,8 +133,8 @@ Credential env vars:
110133
- `OTTA_CLI_TOKEN_TYPE`
111134
- `OTTA_CLI_REFRESH_TOKEN`
112135
- `OTTA_CLI_TOKEN_SCOPE`
113-
- `OTTA_CLI_USER_ID` (optional convenience for `worktimes add`)
114-
- `OTTA_CLI_WORKTIMEGROUP_ID` (optional convenience for `holidays` and `calendar overview`)
136+
- `OTTA_CLI_USER_ID` (optional convenience for `worktimes add` and `saldo`)
137+
- `OTTA_CLI_WORKTIMEGROUP_ID` (optional convenience for `holidays`, `calendar overview`, and `calendar detailed`)
115138

116139
## Test and Lint
117140

docs/cli-auth.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ otta auth login --username <username> --password "$OTTA_CLI_PASSWORD"
5151
`otta auth login` stores credentials/token in local config.
5252
API-derived profile data is stored in a separate cache file.
5353
`otta status` refreshes cached user metadata (`user.id`, `worktimegroup_id`) used by
54-
`worktimes add`, `holidays`, and `calendar overview` fallback resolution.
54+
`worktimes add`, `holidays`, `calendar overview`, and `calendar detailed` fallback resolution.
5555

5656
Any authenticated command uses silent token renewal when possible:
5757

docs/cli-installation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ $OTTA_BIN config path
5858
$OTTA_BIN config cache-path
5959
$OTTA_BIN absence browse --from 2026-02-01 --to 2026-02-28 --format json
6060
$OTTA_BIN calendar overview --from 2026-02-01 --to 2026-02-28 --format json
61+
$OTTA_BIN calendar detailed --from 2026-02-01 --to 2026-02-28 --format json --duration-format hours
6162
```
6263

64+
Note: `worktimes` commands are worktime-only and do not return absences.
65+
Minute-based read commands support `--duration-format minutes|hours|days|hhmm`.
66+
6367
## Next Steps
6468

6569
1. [Authentication](./cli-auth.md)

docs/cli-output-contract.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ For scriptability, command output is deterministic.
1212
- JSON mode with `--format json` is available on:
1313
- `auth login`
1414
- `status`
15+
- `saldo`
1516
- all `worktimes` subcommands
1617
- `calendar overview`
18+
- `calendar detailed`
1719
- `holidays`
1820
- `absence options`
1921
- `absence browse`
@@ -31,6 +33,21 @@ For scriptability, command output is deterministic.
3133
- top-level object: `ok`, `command`, `data`
3234
- `data` fields are command-specific
3335
- raw API payload is included where API schemas vary by tenant
36+
- `worktimes list/browse/report` payloads are worktime-only and never include absences
37+
- use `absence browse` or `calendar detailed/overview` when absence-aware schedule output is needed
38+
- minute-based read commands support global `--duration-format` (`minutes|hours|days|hhmm`)
39+
- duration conversion basis for `days` is fixed: `1 day = 24h = 1440 minutes`
40+
41+
## Duration Fields
42+
43+
When a command includes minute totals, JSON responses include:
44+
45+
- `duration_format`: normalized selected format
46+
- duration summary objects (for example `total_duration`, `worktime_duration`, `absence_duration`, or `durations.*`) with:
47+
- `format`
48+
- `minutes` (raw canonical value)
49+
- `value` (converted value)
50+
- `text` (human-readable representation)
3451

3552
Example (`worktimes list`):
3653

@@ -77,7 +94,14 @@ Example (`absence browse`, excerpt):
7794
"to": "2026-02-28",
7895
"days": 28,
7996
"count": 1,
80-
"total_minutes": 450
97+
"total_minutes": 450,
98+
"duration_format": "hours",
99+
"total_duration": {
100+
"format": "hours",
101+
"minutes": 450,
102+
"value": 7.5,
103+
"text": "7.50 hours"
104+
}
81105
}
82106
}
83107
```
@@ -100,6 +124,45 @@ Example (`calendar overview`, excerpt):
100124
}
101125
```
102126

127+
Example (`calendar detailed`, excerpt):
128+
129+
```json
130+
{
131+
"ok": true,
132+
"command": "calendar detailed",
133+
"data": {
134+
"from": "2026-02-01",
135+
"to": "2026-02-28",
136+
"worktime_group_id": 17910737,
137+
"totals": {
138+
"day_off_days": 8,
139+
"celebration_days": 0
140+
}
141+
}
142+
}
143+
```
144+
145+
Example (`saldo`, excerpt):
146+
147+
```json
148+
{
149+
"ok": true,
150+
"command": "saldo",
151+
"data": {
152+
"user_id": 24352445,
153+
"from": "2024-09-01",
154+
"to": "2026-02-21",
155+
"cumulative_saldo_minutes": 1463,
156+
"cumulative_saldo_duration": {
157+
"format": "hours",
158+
"minutes": 1463,
159+
"value": 24.3833,
160+
"text": "24.38 hours"
161+
}
162+
}
163+
}
164+
```
165+
103166
## Error Output
104167

105168
- errors are returned as plain text on `stderr`

docs/cli-overview.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ otta status
3131
- `otta --version`: print CLI build version.
3232
- `otta auth login`: authenticate and store token/config data.
3333
- `otta status`: validate auth and refresh cached user/worktimegroup metadata.
34+
- `otta saldo`: return current cumulative saldo for the resolved user id.
3435
- `otta config path`: print resolved config path.
3536
- `otta config cache-path`: print resolved cache path.
3637
- `otta worktimes list`: list entries for a specific date.
@@ -40,30 +41,54 @@ otta status
4041
- `otta worktimes add`: create an entry.
4142
- `otta worktimes update`: update an existing entry.
4243
- `otta worktimes delete`: delete an entry.
43-
- `otta calendar overview`: generate combined day-by-day report (worktimes + absences + holidays).
44+
- `otta calendar overview`: generate combined calendar totals with day rows.
45+
- `otta calendar detailed`: generate full day-by-day detailed calendar report.
4446
- `otta holidays`: fetch workday calendar/holiday rows.
4547
- `otta absence options`: fetch absence type/user options.
4648
- `otta absence browse`: aggregate absence entries across a date range.
4749
- `otta absence comment`: generate absence comment text.
4850

51+
Important: `worktimes list/browse/report` do not return absences.
52+
For complete schedule interpretation, prefer `calendar detailed` (or `calendar overview` for lighter totals/day rows).
53+
54+
## Duration Formatting
55+
56+
Use global `--duration-format` on read commands with minute totals:
57+
58+
- values: `minutes` (default), `hours`, `days`, `hhmm`
59+
- applies to: `worktimes list/browse/report`, `absence browse`, `saldo`, `holidays`, `calendar overview`, `calendar detailed`
60+
- conversion basis for `days`: `1 day = 24h = 1440 minutes`
61+
62+
Examples:
63+
64+
```bash
65+
otta worktimes browse --from 2026-02-01 --to 2026-02-28 --format json --duration-format hours
66+
otta saldo --format json --duration-format hhmm
67+
otta calendar detailed --from 2026-02-01 --to 2026-02-28 --format json --duration-format hhmm
68+
```
69+
4970
## Practical First-Run Sequence
5071

5172
```bash
5273
otta auth login --username <username> --password <password>
5374
otta status --format json
5475
otta worktimes options --date 2026-02-20 --format json
76+
otta saldo --format json
5577
otta worktimes list --date 2026-02-20 --format json
5678
otta absence browse --from 2026-02-01 --to 2026-02-28 --format json
5779
otta calendar overview --from 2026-02-01 --to 2026-02-28 --format json
80+
otta calendar detailed --from 2026-02-01 --to 2026-02-28 --format json
5881
```
5982

6083
## Validation Note
6184

6285
A real terminal E2E sweep was run on 2026-02-22 against the live API, covering:
6386

6487
- auth and status
88+
- saldo
6589
- config path commands
6690
- worktimes list/browse/report/options/add/update/delete
6791
- calendar overview
92+
- calendar detailed
6893
- holidays
6994
- absence options/browse/comment

docs/cli-roadmap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ sidebar_position: 6
88

99
- Added `absence browse` range command (`/ttapi/absence/split`).
1010
- Added `calendar overview` combined day-by-day report (worktimes + absences + holidays).
11+
- Added `calendar detailed` full day-by-day report with day-off reasons and celebrations.
1112

1213
## Next
1314

docs/cli-timesheets.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ Before running these commands:
1111

1212
## Worktimes
1313

14+
Important: `worktimes list`, `worktimes browse`, and `worktimes report` return only worktime rows.
15+
They do not include absences. Use `absence browse` or `calendar detailed` when schedule context matters.
16+
17+
Duration display on read commands can be changed with global `--duration-format`:
18+
19+
- supported formats: `minutes` (default), `hours`, `days`, `hhmm`
20+
- `days` uses fixed conversion `1 day = 24h = 1440 minutes`
21+
- for AI schedule analysis, prefer `calendar detailed --format json --duration-format <format>`
22+
1423
Collect worktimes for a day:
1524

1625
```bash
@@ -122,6 +131,27 @@ Fallback order for `--worktimegroup`:
122131
- `OTTA_CLI_WORKTIMEGROUP_ID`
123132
- cached user profile (`~/.otta-cli/cache.json` by default)
124133

134+
## Saldo
135+
136+
Fetch current cumulative saldo:
137+
138+
```bash
139+
otta saldo --format json
140+
```
141+
142+
Optional explicit user id:
143+
144+
```bash
145+
otta saldo --user <user-id> --format json
146+
```
147+
148+
If `--user` is omitted in `saldo`, fallback order is:
149+
150+
- `OTTA_CLI_USER_ID`
151+
- cached user profile (`~/.otta-cli/cache.json` by default)
152+
153+
`saldo` also supports `--duration-format` for converted saldo output.
154+
125155
## Absences
126156

127157
Browse absences across a date range (calendar-compatible split rows):
@@ -150,7 +180,15 @@ otta calendar overview --from 2026-02-01 --to 2026-02-28 --format json
150180

151181
`calendar overview` returns one `items[]` row per day in range, including weekends and days without entries.
152182

153-
If `--worktimegroup` is omitted for `calendar overview`, fallback order is:
183+
Generate detailed calendar view (day-by-day with events, day-off reasons, and celebrations):
184+
185+
```bash
186+
otta calendar detailed --from 2026-02-01 --to 2026-02-28 --format json
187+
```
188+
189+
For AI/automation schedule checks, prefer `calendar detailed --format json` first.
190+
191+
If `--worktimegroup` is omitted for `calendar overview` or `calendar detailed`, fallback order is:
154192

155193
- `OTTA_CLI_WORKTIMEGROUP_ID`
156194
- cached user profile (`~/.otta-cli/cache.json` by default)

internal/cli/absence_browse_command.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func newAbsenceBrowseCommand() *cobra.Command {
2828
if err != nil {
2929
return err
3030
}
31+
durationFormat, err := resolveDurationFormat(cmd)
32+
if err != nil {
33+
return err
34+
}
3135

3236
fromDate, toDate, err := parseWorktimesDateRange(dateFrom, dateTo)
3337
if err != nil {
@@ -54,15 +58,17 @@ func newAbsenceBrowseCommand() *cobra.Command {
5458
OK: true,
5559
Command: "absence browse",
5660
Data: map[string]any{
57-
"from": report.From,
58-
"to": report.To,
59-
"days": report.Days,
60-
"count": report.Count,
61-
"total_minutes": report.TotalMinutes,
62-
"total_hours": report.TotalHours,
63-
"items": report.Items,
64-
"responses": report.Responses,
65-
"raw": report.Raw,
61+
"from": report.From,
62+
"to": report.To,
63+
"days": report.Days,
64+
"count": report.Count,
65+
"total_minutes": report.TotalMinutes,
66+
"total_hours": report.TotalHours,
67+
"duration_format": durationFormat,
68+
"total_duration": durationSummary(report.TotalMinutes, durationFormat),
69+
"items": report.Items,
70+
"responses": report.Responses,
71+
"raw": report.Raw,
6672
},
6773
})
6874
}
@@ -73,6 +79,7 @@ func newAbsenceBrowseCommand() *cobra.Command {
7379
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "entries: %d\n", report.Count)
7480
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "total_minutes: %d\n", report.TotalMinutes)
7581
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "total_hours: %.2f\n", report.TotalHours)
82+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "total_duration: %s\n", formatDurationForText(report.TotalMinutes, durationFormat))
7683
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "use --format json for full payload")
7784
return nil
7885
},

internal/cli/calendar_command.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ func newCalendarCommand() *cobra.Command {
1111
},
1212
}
1313

14+
calendarCmd.AddCommand(newCalendarDetailedCommand())
1415
calendarCmd.AddCommand(newCalendarOverviewCommand())
1516
return calendarCmd
1617
}

0 commit comments

Comments
 (0)