harvest-time-off 0.2.0 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d14f2defb88e655452e5c21a45c28586127977e4e38976628cc9caf2b4fa88d2
4
- data.tar.gz: eb1f5d5c3f25a4a65e24d74aeef9a6d186248134d96c542496ac476cbe82748c
3
+ metadata.gz: 1631545c9a5c2ccf1baccf699c3ae7ed0b4ad6bbc2d10b32f3b978f529817252
4
+ data.tar.gz: a93eafe0be2d87101573738d351ead61cfd78b08fab9e2333a97120a369d327f
5
5
  SHA512:
6
- metadata.gz: e29d62624542e6cbc648138d9ec29e998bf16469e965bc00d03b8e5b82760b21ee73848b4d0fd7c216b79239790cd3033980f85225d50a65cc8a67ccd3b63b19
7
- data.tar.gz: c0c27dc68af09a0e8f6a5d1e983054ce83205b6ab0035e03d086b505d1bd7258ed600b558c60b7c7428d1bffca065855420467eba17c91cb0dc8430d6192a4ca
6
+ metadata.gz: 57a4bd4971c05b9a3364f6fe19be32d4f3be3a3011108b1250d3619d331815ad98d600e08e79de8560a8d617750f1401dd65b084705e63d00aeeb987231874ae
7
+ data.tar.gz: 6cd29f602c78ae6f3abc52708421d37859c4badc16a3b2ef33043280df4fe5322ac30826dd0deddfd95750cf4d58fd1333c851875996afd63527f0cc53fe7837
data/README.md CHANGED
@@ -31,17 +31,28 @@ harvest-time-off 2026-08-17 2026-08-28 \
31
31
 
32
32
  The default holiday region is `ca_yt`. Add `--holiday-region REGION` or set `HARVEST_HOLIDAY_REGIONS` to include other [Holidays](https://github.com/holidays/holidays) regions. `business_time` calculates business days and `holidays` supplies observed statutory holidays.
33
33
 
34
- A time-off block never creates weekend entries. The underlying [`marlens-harvest-api-v2`](https://github.com/klondikemarlen/harvest-api-v2) client accepts any ISO date, including weekends, for ordinary work entries.
34
+ A time-off block never creates weekend entries. When names are supplied, it resolves them from your active personal Harvest assignments, so a Project Manager role is not required. The underlying [`marlens-harvest-api-v2`](https://github.com/klondikemarlen/harvest-api-v2) client accepts any ISO date, including weekends, for ordinary work entries.
35
35
 
36
36
  ## OMP settings
37
37
 
38
38
  - `defaultHours`: hours per business day when a tool call omits `hours`; defaults to `7`.
39
39
  - `holidayRegions`: comma-separated Holidays regions; defaults to `ca_yt`.
40
40
  - `command`: direct path to the `harvest-time-off` executable.
41
+ - `workEntryCommand`: direct path to the `harvest-work-entry` executable.
42
+ - `projectTimeMappings`: JSON mapping from OMP Project Time project names to Harvest project/task names.
43
+ - `projectTimeLogPath`: optional override for `~/.omp/project-time/time-log.json`.
41
44
 
42
45
  ## V2: OMP Project Time integration
43
46
 
44
- [Issue #3](https://github.com/klondikemarlen/harvest-time-off/issues/3) tracks the next scope: turn reviewed `omp-project-time` output into Harvest work entries with configured project/task mappings, generated descriptions, duplicate/lock checks, preview, and approval. V1 remains deliberately focused on time off.
47
+ Configure `projectTimeMappings` with the recorded OMP Project Time project name as the key:
48
+
49
+ ```json
50
+ {
51
+ "Harvest API": { "project": "Internal", "task": "Development" }
52
+ }
53
+ ```
54
+
55
+ `harvest_preview_project_time_entries` reads the configured time log, splits sessions across local dates, generates descriptions from the project and repository, and checks Harvest for existing or locked entries without writing. `harvest_record_project_time_entries` performs the same preflight then creates only new entries; OMP treats it as a write requiring approval. Unmapped sessions are reported and never written.
45
56
 
46
57
  ## Release
47
58
 
@@ -60,4 +71,5 @@ gem push harvest-time-off-<version>.gem
60
71
  omp plugin install --force github:klondikemarlen/harvest-time-off
61
72
  omp plugin list
62
73
  harvest-time-off --help
74
+ harvest-work-entry --help
63
75
  ```
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "harvest_time_off"
5
+
6
+ exit HarvestTimeOff::WorkEntryCLI.run(ARGV)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HarvestTimeOff
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarvestTimeOff
4
+ class WorkEntryCLI
5
+ EXISTING_ENTRY = 2
6
+ LOCKED_ENTRY = 3
7
+
8
+ def self.run(arguments, output: $stdout, error: $stderr, client: nil)
9
+ options = { dry_run: false }
10
+ parser = option_parser(options)
11
+ dates = parser.parse(arguments)
12
+ validate!(dates, options)
13
+
14
+ spent_date = Date.iso8601(dates.first)
15
+ client ||= Marlens::HarvestApiV2::Client.from_environment
16
+ project_id, task_id = if options[:project_id]
17
+ [options[:project_id], options[:task_id]]
18
+ else
19
+ CLI.resolve_assignment(client, options[:project], options[:task])
20
+ end
21
+ existing_entries = client.request(
22
+ :get,
23
+ "/v2/time_entries",
24
+ params: { project_id:, task_id:, from: spent_date.iso8601, to: spent_date.iso8601 }
25
+ ).fetch("time_entries")
26
+
27
+ if existing_entries.any? { |entry| entry["is_locked"] }
28
+ output.puts "Locked existing Harvest entry on #{spent_date.iso8601}; skipped"
29
+ return LOCKED_ENTRY
30
+ end
31
+
32
+ if existing_entries.any?
33
+ output.puts "Existing Harvest entry on #{spent_date.iso8601}; skipped"
34
+ return EXISTING_ENTRY
35
+ end
36
+
37
+ if options[:dry_run]
38
+ output.puts "Would create #{spent_date.iso8601}: #{HarvestTimeOff.display_hours(options[:hours])}h on #{options[:project]} / #{options[:task]}; #{options[:notes]}"
39
+ return 0
40
+ end
41
+
42
+ entry = client.create_time_entry(
43
+ project_id:,
44
+ task_id:,
45
+ spent_date:,
46
+ hours: options[:hours],
47
+ notes: options[:notes]
48
+ )
49
+ output.puts "Created #{spent_date.iso8601}: #{HarvestTimeOff.display_hours(options[:hours])}h (entry ##{entry.fetch("id")})"
50
+ 0
51
+ rescue Error, Marlens::HarvestApiV2::Error, OptionParser::ParseError, Date::Error => e
52
+ error.puts "Error: #{e.message}"
53
+ error.puts parser if parser
54
+ 1
55
+ end
56
+
57
+ def self.option_parser(options)
58
+ OptionParser.new do |opts|
59
+ opts.banner = "Usage: harvest-work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [--dry-run]"
60
+ opts.on("--project NAME", "Harvest project name") { |value| options[:project] = value }
61
+ opts.on("--task NAME", "Harvest task name") { |value| options[:task] = value }
62
+ opts.on("--project-id ID", Integer, "Harvest project ID") { |value| options[:project_id] = value }
63
+ opts.on("--task-id ID", Integer, "Harvest task ID") { |value| options[:task_id] = value }
64
+ opts.on("--hours HOURS", Float, "Hours for this entry") { |value| options[:hours] = value }
65
+ opts.on("--notes NOTES", "Entry description") { |value| options[:notes] = value }
66
+ opts.on("--dry-run", "Check for existing entries without writing") { options[:dry_run] = true }
67
+ opts.on("-h", "--help", "Show this help") do
68
+ puts opts
69
+ exit 0
70
+ end
71
+ end
72
+ end
73
+
74
+ def self.validate!(dates, options)
75
+ raise Error, "DATE is required" unless dates.length == 1
76
+ raise Error, "--hours must be a positive finite number" unless options[:hours]&.positive? && options[:hours].finite?
77
+ raise Error, "--notes is required" unless options[:notes] && !options[:notes].empty?
78
+
79
+ names_provided = options[:project] || options[:task]
80
+ ids_provided = options[:project_id] || options[:task_id]
81
+ valid_names = options[:project] && options[:task]
82
+ valid_ids = options[:project_id] && options[:task_id]
83
+ raise Error, "supply --project and --task, or --project-id and --task-id" unless valid_names || valid_ids
84
+ raise Error, "do not combine project/task names with IDs" if names_provided && ids_provided
85
+ end
86
+ end
87
+ end
@@ -109,7 +109,7 @@ module HarvestTimeOff
109
109
 
110
110
  def self.resolve_assignment(client, project_name, task_name)
111
111
  # ponytail: one 2,000-item page covers personal assignments; follow cursor pagination if that ceiling is exceeded.
112
- matches = client.active_task_assignments.select do |assignment|
112
+ matches = client.active_personal_task_assignments.select do |assignment|
113
113
  assignment.dig("project", "name")&.casecmp?(project_name) &&
114
114
  assignment.dig("task", "name")&.casecmp?(task_name)
115
115
  end
@@ -132,3 +132,5 @@ module HarvestTimeOff
132
132
  end
133
133
  end
134
134
  end
135
+
136
+ require "harvest_time_off/work_entry_cli"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harvest-time-off
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marlen Brunner
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.1'
18
+ version: '0.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.1'
25
+ version: '0.2'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: business_time
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -55,14 +55,17 @@ email:
55
55
  - klondikemarlen@gmail.com
56
56
  executables:
57
57
  - harvest-time-off
58
+ - harvest-work-entry
58
59
  extensions: []
59
60
  extra_rdoc_files: []
60
61
  files:
61
62
  - README.md
62
63
  - bin/harvest-time-off
64
+ - bin/harvest-work-entry
63
65
  - harvest-time-off.rb
64
66
  - lib/harvest_time_off.rb
65
67
  - lib/harvest_time_off/version.rb
68
+ - lib/harvest_time_off/work_entry_cli.rb
66
69
  homepage: https://github.com/klondikemarlen/harvest-time-off
67
70
  licenses:
68
71
  - MIT
@@ -84,5 +87,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
87
  requirements: []
85
88
  rubygems_version: 4.0.3
86
89
  specification_version: 4
87
- summary: Create Harvest time-off entries over a date range.
90
+ summary: Create Harvest time-off and reviewed OMP Project Time entries.
88
91
  test_files: []