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 +4 -4
- data/README.md +14 -2
- data/bin/harvest-work-entry +6 -0
- data/lib/harvest_time_off/version.rb +1 -1
- data/lib/harvest_time_off/work_entry_cli.rb +87 -0
- data/lib/harvest_time_off.rb +3 -1
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1631545c9a5c2ccf1baccf699c3ae7ed0b4ad6bbc2d10b32f3b978f529817252
|
|
4
|
+
data.tar.gz: a93eafe0be2d87101573738d351ead61cfd78b08fab9e2333a97120a369d327f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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,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
|
data/lib/harvest_time_off.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
90
|
+
summary: Create Harvest time-off and reviewed OMP Project Time entries.
|
|
88
91
|
test_files: []
|