harvest-time-off 0.2.0 → 0.3.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: 281fe13df87f06f4e9ffc0cd8cbd3df85e23f29400460dfbc0cca59182f8b641
4
+ data.tar.gz: 2512474000205216fb1ecabe4da817766a85ce4b5a6a088f7ec0c41be5dc51c7
5
5
  SHA512:
6
- metadata.gz: e29d62624542e6cbc648138d9ec29e998bf16469e965bc00d03b8e5b82760b21ee73848b4d0fd7c216b79239790cd3033980f85225d50a65cc8a67ccd3b63b19
7
- data.tar.gz: c0c27dc68af09a0e8f6a5d1e983054ce83205b6ab0035e03d086b505d1bd7258ed600b558c60b7c7428d1bffca065855420467eba17c91cb0dc8430d6192a4ca
6
+ metadata.gz: 7c0a91b9787d899a64ceb1186935c5e196d3c75a14b0a71a027209b225174088fea6fc1aa8f60a4ad2a4ec975a96994969e4495232d523d5b5aee899aff5456f
7
+ data.tar.gz: d7b776f47216657ec923475ab54cb0d4f34fa1c2ba35f06bf1b207b11f495490a713601a6912e02bd6c8ac4ab218cd84acea59c32e4a998a47f914714e3f71cd
data/README.md CHANGED
@@ -38,10 +38,21 @@ A time-off block never creates weekend entries. The underlying [`marlens-harvest
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.3.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
@@ -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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marlen Brunner
@@ -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: []