crowdtime-cli 0.12.2__tar.gz → 0.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/PKG-INFO +1 -1
  2. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/pyproject.toml +1 -1
  3. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/__init__.py +1 -1
  4. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/timesheet_cmd.py +46 -26
  5. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/skills/crowdtime/references/commands.md +1 -1
  6. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/.gitignore +0 -0
  7. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/LICENSE +0 -0
  8. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/README.md +0 -0
  9. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/auth.py +0 -0
  10. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/client.py +0 -0
  11. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/__init__.py +0 -0
  12. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/ai_cmd.py +0 -0
  13. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/auth_cmd.py +0 -0
  14. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/billing_cmd.py +0 -0
  15. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/clients_cmd.py +0 -0
  16. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/config_cmd.py +0 -0
  17. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/expense_cmd.py +0 -0
  18. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/favorites_cmd.py +0 -0
  19. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/insights_cmd.py +0 -0
  20. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/invoice_cmd.py +0 -0
  21. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/log_cmd.py +0 -0
  22. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/org_cmd.py +0 -0
  23. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/payroll_cmd.py +0 -0
  24. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/projects_cmd.py +0 -0
  25. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/report_cmd.py +0 -0
  26. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/skill_cmd.py +0 -0
  27. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/tasks_cmd.py +0 -0
  28. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/team_cmd.py +0 -0
  29. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/timer_cmd.py +0 -0
  30. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/commands/version_cmd.py +0 -0
  31. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/config.py +0 -0
  32. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/formatters.py +0 -0
  33. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/main.py +0 -0
  34. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/models.py +0 -0
  35. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/oauth.py +0 -0
  36. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/resolvers.py +0 -0
  37. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/skills/crowdtime/SKILL.md +0 -0
  38. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/skills/crowdtime/references/workflows.md +0 -0
  39. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/utils.py +0 -0
  40. {crowdtime_cli-0.12.2 → crowdtime_cli-0.13.0}/src/crowdtime_cli/version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crowdtime-cli
3
- Version: 0.12.2
3
+ Version: 0.13.0
4
4
  Summary: AI-powered time tracking CLI — a modern, developer-friendly alternative to Harvest
5
5
  Project-URL: Homepage, https://crowdtime.lat
6
6
  Project-URL: Documentation, https://crowdtime.lat/docs
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "crowdtime-cli"
3
- version = "0.12.2"
3
+ version = "0.13.0"
4
4
  description = "AI-powered time tracking CLI — a modern, developer-friendly alternative to Harvest"
5
5
  readme = "README.md"
6
6
  license = {text = "Proprietary"}
@@ -1,3 +1,3 @@
1
1
  """CrowdTime CLI - AI-powered time tracking from the command line."""
2
2
 
3
- __version__ = "0.12.2"
3
+ __version__ = "0.13.0"
@@ -90,14 +90,21 @@ def _print_team_overview(data: dict) -> None:
90
90
  members = data.get("members", [])
91
91
  period_start = data.get("period_start", "")
92
92
  period_end = data.get("period_end", "")
93
+ project_id = data.get("project_id")
93
94
 
94
95
  console.print(f"\n[dim]Period: {period_start} to {period_end}[/dim]")
96
+ if project_id:
97
+ console.print(f"[dim]Scoped to project: {project_id}[/dim]")
95
98
 
96
99
  if not members:
97
100
  console.print("[dim]No team members found.[/dim]")
98
101
  return
99
102
 
100
- table = Table(show_header=True, header_style="bold", title="Team Timesheet Overview")
103
+ title = "Team Timesheet Overview"
104
+ if project_id:
105
+ title += " (project-scoped)"
106
+
107
+ table = Table(show_header=True, header_style="bold", title=title)
101
108
  table.add_column("Member", width=22)
102
109
  table.add_column("Hours", justify="right", width=8)
103
110
  table.add_column("Capacity", justify="right", width=10)
@@ -107,7 +114,13 @@ def _print_team_overview(data: dict) -> None:
107
114
  table.add_column("Status", width=14)
108
115
 
109
116
  for m in members:
110
- status = m.get("timesheet_status")
117
+ # When project filter is active, surface the per-project approval
118
+ # status rather than the whole-timesheet status.
119
+ if project_id:
120
+ project_status = m.get("project_approval_status")
121
+ status = project_status if project_status else m.get("timesheet_status")
122
+ else:
123
+ status = m.get("timesheet_status")
111
124
  style = _status_style(status)
112
125
 
113
126
  capacity_pct = m.get("capacity_percent", 0)
@@ -422,6 +435,12 @@ def team_overview(
422
435
  ),
423
436
  week: bool = typer.Option(False, "--week", "-w", help="This week (default if no period specified)."),
424
437
  last_week: bool = typer.Option(False, "--last-week", help="Last week."),
438
+ project: Optional[str] = typer.Option(
439
+ None,
440
+ "--project",
441
+ "-p",
442
+ help="Scope hours, breakdown, and approval status to a single project.",
443
+ ),
425
444
  output_json: bool = typer.Option(False, "--json", help="Output as JSON."),
426
445
  ) -> None:
427
446
  """Show team timesheet overview for a period (manager+).
@@ -429,10 +448,15 @@ def team_overview(
429
448
  Displays all org members with their hours and timesheet status,
430
449
  including those who haven't submitted yet.
431
450
 
451
+ With --project, hours and breakdown are scoped to that project, the status
452
+ column reflects the per-project approval status (PENDING/APPROVED/REJECTED),
453
+ and members with no story for that project are excluded.
454
+
432
455
  Examples:
433
456
  ct timesheet team
434
457
  ct timesheet team --last-week
435
458
  ct timesheet team --from 2026-03-09 --to 2026-03-15
459
+ ct timesheet team --project <project-id>
436
460
  """
437
461
  client = CrowdTimeClient(require_auth=True, require_org=True)
438
462
 
@@ -450,6 +474,8 @@ def team_overview(
450
474
  except ValueError as e:
451
475
  format_error(str(e))
452
476
  raise typer.Exit(1)
477
+ if project:
478
+ params["project_id"] = project
453
479
 
454
480
  if last_week and not period_start:
455
481
  today = date_type.today()
@@ -540,35 +566,29 @@ def bulk_approve_timesheets(
540
566
 
541
567
  try:
542
568
  if project:
543
- # Per-project bulk: loop and approve each individually
544
- approved = 0
545
- skipped = 0
546
- errors: list[str] = []
547
- for ts_id in timesheet_ids:
548
- try:
549
- data: dict = {}
550
- if notes:
551
- data["notes"] = notes
552
- client.post(
553
- f"/timesheets/{ts_id}/approvals/{project}/approve/",
554
- data=data or None,
555
- )
556
- approved += 1
557
- except APIError as e:
558
- skipped += 1
559
- errors.append(f" {ts_id}: {e.message}")
560
-
561
- result = {"total_approved": approved, "total_skipped": skipped}
569
+ # Per-project bulk via single transactional endpoint (added with
570
+ # CRO-44). One request approves N portions, with row-level locks
571
+ # and a single auto-approve pass per affected timesheet.
572
+ payload: dict = {
573
+ "items": [{"timesheet_id": ts_id, "project_id": project} for ts_id in timesheet_ids],
574
+ }
575
+ if notes:
576
+ payload["notes"] = notes
577
+ result = client.post("/timesheets/bulk-approve-projects/", data=payload)
562
578
  if output_json:
563
579
  print_json(result)
564
580
  else:
565
- format_success(f"Approved {approved} timesheet(s) for project.")
566
- if skipped:
581
+ approved = result.get("total_approved", 0)
582
+ skipped_count = result.get("total_skipped", 0)
583
+ format_success(f"Approved {approved} project portion(s).")
584
+ if skipped_count:
567
585
  console.print(
568
- f"[yellow]Skipped {skipped} (not found, not submitted, or no matching project):[/yellow]"
586
+ f"[yellow]Skipped {skipped_count} (not found, not submitted, already reviewed, self, or no permission):[/yellow]"
569
587
  )
570
- for err in errors:
571
- console.print(f"[yellow]{err}[/yellow]")
588
+ for item in result.get("skipped", []):
589
+ ts_id = item.get("timesheet_id", "")
590
+ reason = item.get("reason", "")
591
+ console.print(f"[yellow] {ts_id}: {reason}[/yellow]")
572
592
  else:
573
593
  payload: dict = {"timesheet_ids": timesheet_ids}
574
594
  if notes:
@@ -1763,7 +1763,7 @@ ct timesheet bulk-approve ID1 ID2 [ID3 ...] [--project PROJECT_ID] [--notes NOTE
1763
1763
  | `--json` | flag | JSON output |
1764
1764
 
1765
1765
  Without `--project`: approves all project portions in each timesheet (manager+ role). With `--project`: approves only that project's portion in each timesheet (PM for that project). Skips timesheets that are not found or not in submitted status.
1766
- Endpoint: `POST /timesheets/bulk-approve/` (without --project), per-project endpoint per timesheet (with --project)
1766
+ Endpoint: `POST /timesheets/bulk-approve/` (without --project), `POST /timesheets/bulk-approve-projects/` (with --project single transactional bulk request, max 200 items).
1767
1767
 
1768
1768
  ### ct timesheet bulk-reject
1769
1769
 
File without changes
File without changes