harvesting 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env.sample +5 -2
- data/.gitignore +5 -0
- data/.travis.yml +5 -1
- data/Gemfile.lock +5 -5
- data/README.md +70 -33
- data/RELEASE_NOTES.md +19 -1
- data/harvesting.gemspec +3 -3
- data/lib/harvesting.rb +7 -2
- data/lib/harvesting/client.rb +59 -13
- data/lib/harvesting/enumerable.rb +3 -0
- data/lib/harvesting/models/base.rb +73 -20
- data/lib/harvesting/models/client.rb +3 -0
- data/lib/harvesting/models/contact.rb +4 -1
- data/lib/harvesting/models/harvest_record_collection.rb +2 -0
- data/lib/harvesting/models/invoice.rb +8 -1
- data/lib/harvesting/models/invoices.rb +17 -0
- data/lib/harvesting/models/line_item.rb +19 -0
- data/lib/harvesting/models/project.rb +20 -1
- data/lib/harvesting/models/{task_assignment.rb → project_task_assignment.rb} +5 -3
- data/lib/harvesting/models/project_task_assignments.rb +18 -0
- data/lib/harvesting/models/project_user_assignment.rb +25 -0
- data/lib/harvesting/models/project_user_assignments.rb +19 -0
- data/lib/harvesting/models/projects.rb +1 -1
- data/lib/harvesting/models/task.rb +4 -1
- data/lib/harvesting/models/tasks.rb +1 -1
- data/lib/harvesting/models/time_entries.rb +1 -1
- data/lib/harvesting/models/time_entry.rb +6 -3
- data/lib/harvesting/models/user.rb +8 -0
- data/lib/harvesting/models/users.rb +1 -1
- data/lib/harvesting/version.rb +1 -1
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4452c68eed7a6097355f504450d77cf13e72424ca0ec6915fb041dd8abb155f7
|
4
|
+
data.tar.gz: 82e16401985e4e19cd14ac1fb57f13b9f87f2d29188800e584516a68c3412ca7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf1b81f56a3ec9c5efc9c19816ea6da487927fc00f49d255e58f647d918de5e9ce5a54b5e6bf1c848fb0002e3239f1d25590720acaf0c84bbbc12247ef634b84
|
7
|
+
data.tar.gz: a40d1ec6516ef528129aedc4747943189c19b3da0b5cf4ac950be1e735c06814008a192a303d2cf9a2f93b0336d0fae988be72fcaa8f5d1953511453a08b1a0d
|
data/.env.sample
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
HARVEST_FIRST_NAME
|
2
|
-
HARVEST_LAST_NAME
|
1
|
+
HARVEST_FIRST_NAME=<replace me with a test account>
|
2
|
+
HARVEST_LAST_NAME=<replace me with a test account>
|
3
3
|
HARVEST_ACCOUNT_ID=<replace me with a test account>
|
4
4
|
HARVEST_NON_ADMIN_ACCOUNT_ID=<replace me with a test account>
|
5
5
|
HARVEST_ACCESS_TOKEN=<replace me with a test account>
|
6
6
|
HARVEST_NON_ADMIN_ACCESS_TOKEN=<replace me with a test account>
|
7
7
|
HARVEST_ADMIN_FULL_NAME=<replace me with a test account>
|
8
|
+
HARVEST_NON_ADMIN_FULL_NAME=<replace me with a test account>
|
9
|
+
HARVEST_ADMIN_FIRST_NAME=<replace me with a test account>
|
10
|
+
HARVEST_ADMIN_LAST_NAME=<replace me with a test account>
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -2,4 +2,8 @@ sudo: false
|
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
4
|
- 2.4.1
|
5
|
-
|
5
|
+
- 2.5.1
|
6
|
+
env:
|
7
|
+
- HARVEST_FIRST_NAME=Aaron HARVEST_LAST_NAME=Burr HARVEST_ACCOUNT_ID=112341234 HARVEST_NON_ADMIN_ACCOUNT_ID=112341234 HARVEST_ACCESS_TOKEN=112341234 HARVEST_NON_ADMIN_ACCESS_TOKEN=112341234 HARVEST_ADMIN_FULL_NAME=112341234
|
8
|
+
|
9
|
+
before_install: gem install bundler -v 1.17.3
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
harvesting (0.
|
4
|
+
harvesting (0.3.0)
|
5
5
|
http (~> 3.3, >= 3.3)
|
6
6
|
|
7
7
|
GEM
|
@@ -17,7 +17,7 @@ GEM
|
|
17
17
|
domain_name (0.5.20180417)
|
18
18
|
unf (>= 0.0.5, < 1.0.0)
|
19
19
|
dotenv (2.5.0)
|
20
|
-
ffi (1.
|
20
|
+
ffi (1.12.2)
|
21
21
|
formatador (0.2.5)
|
22
22
|
guard (2.14.2)
|
23
23
|
formatador (>= 0.2.4)
|
@@ -57,7 +57,7 @@ GEM
|
|
57
57
|
coderay (~> 1.1.0)
|
58
58
|
method_source (~> 0.9.0)
|
59
59
|
public_suffix (3.0.2)
|
60
|
-
rake (
|
60
|
+
rake (13.0.1)
|
61
61
|
rb-fsevent (0.10.3)
|
62
62
|
rb-inotify (0.9.10)
|
63
63
|
ffi (>= 0.5.0, < 2)
|
@@ -96,10 +96,10 @@ DEPENDENCIES
|
|
96
96
|
dotenv (~> 2.5, >= 2.5)
|
97
97
|
guard-rspec (~> 4.7, >= 4.7)
|
98
98
|
harvesting!
|
99
|
-
rake (~>
|
99
|
+
rake (~> 13.0)
|
100
100
|
rspec (~> 3.0)
|
101
101
|
vcr (~> 4.0, >= 4.0)
|
102
102
|
webmock (~> 3.4, >= 3.4)
|
103
103
|
|
104
104
|
BUNDLED WITH
|
105
|
-
1.
|
105
|
+
1.17.2
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Harvesting
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/ombulabs/harvesting.svg?branch=master)](https://travis-ci.org/ombulabs/harvesting)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/ombulabs/harvesting/badges/gpa.svg)](https://codeclimate.com/github/ombulabs/harvesting)
|
5
|
+
|
3
6
|
A Ruby gem to interact with the Harvest API v2.0 and forward.
|
4
7
|
|
5
8
|
## Installation
|
@@ -39,69 +42,103 @@ to these environment variables:
|
|
39
42
|
|
40
43
|
That means that you could build a client like this:
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
```ruby
|
46
|
+
# $ export HARVEST_ACCESS_TOKEN=abc
|
47
|
+
# $ export HARVEST_ACCOUNT_ID=12345678
|
48
|
+
client = Harvesting::Client.new
|
49
|
+
client.me
|
50
|
+
# => #<Harvesting::Models::User:0x007ff8830658f0 @attributes={"id"=>2108614, "first_name"=>"Ernesto", "last_name"=>"Tagwerker", ... >
|
51
|
+
```
|
47
52
|
|
48
53
|
If you don't specify a valid combination of token and account id, your code will
|
49
54
|
raise this error:
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
```ruby
|
57
|
+
client = Harvesting::Client.new(access_token: "foo", account_id: "bar")
|
58
|
+
client.me
|
59
|
+
# Harvesting::AuthenticationError: {"error":"invalid_token","error_description":"The access token provided is expired, revoked, malformed or invalid for other reasons."}
|
60
|
+
```
|
54
61
|
|
55
62
|
If your personal token and account id are valid, you should see something like
|
56
63
|
this:
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
```ruby
|
66
|
+
client = Harvesting::Client.new(access_token: "<your token here>", account_id: "<your account id here>")
|
67
|
+
user = client.me
|
68
|
+
# => #<Harvesting::Models::User:0x007ff8830658f0 @attributes={"id"=>2108614, "first_name"=>"Ernesto", "last_name"=>"Tagwerker", ... >
|
61
69
|
|
62
|
-
|
63
|
-
|
70
|
+
user.id
|
71
|
+
# => 2108614
|
72
|
+
```
|
64
73
|
|
65
74
|
### Clients
|
66
75
|
|
67
|
-
|
68
|
-
|
76
|
+
```ruby
|
77
|
+
client.clients
|
78
|
+
# => [#<Harvesting::Models::Client:0x007ff718d65fd0 @attributes={"id"=>6760580, "name"=>"Toto", "is_active"=>true, "address"=>"" ... >
|
69
79
|
|
70
|
-
|
71
|
-
|
72
|
-
|
80
|
+
client = client.clients.first
|
81
|
+
# => #<Harvesting::Models::Client:0x007ff718cf5fc8 @attributes={"id"=>6760580, "name"=>"Toto",
|
82
|
+
# ... >
|
83
|
+
```
|
73
84
|
|
74
85
|
### Time Entries
|
75
86
|
|
76
|
-
|
77
|
-
|
87
|
+
```ruby
|
88
|
+
time_entries = client.time_entries
|
89
|
+
# => #<Harvesting::Models::TimeEntries:0x007ff71913e3a0 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>14, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/time_entries?limit=1&page=1&per_page=100", "next"=>nil, "previous"=>nil, "last"=>"https://api.harvestapp.com/v2/time_entries?limit=1&page=1&per_page=100"}}, ... >
|
78
90
|
|
79
|
-
|
80
|
-
|
91
|
+
entry = time_entries.first
|
92
|
+
# => #<Harvesting::Models::TimeEntry:0x007ff71913dfe0 @attributes={"id"=>792860513, "spent_date"=>"2018-05-14", "hours"=>1.0, "notes"=>"hacked the things", "is_locked"=>false, "locked_reason"=>nil, "is_closed"=>false, "is_billed"=>false, "timer_started_at"=>nil, "started_time"=>nil, "ended_time"=>nil, "is_running"=>false, "billable"=>true, "budgeted"=>false, "billable_rate"=>nil, "cost_rate ... >
|
93
|
+
```
|
81
94
|
|
82
95
|
### Tasks
|
83
96
|
|
84
|
-
|
85
|
-
|
97
|
+
```ruby
|
98
|
+
tasks = client.tasks
|
99
|
+
# => #<Harvesting::Models::Tasks:0x007ff718897990 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>6, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/tasks?page=1&per_page=100", "next"=>nil, ... >
|
100
|
+
```
|
86
101
|
|
87
102
|
### Projects
|
88
103
|
|
89
|
-
|
90
|
-
|
104
|
+
```ruby
|
105
|
+
projects = client.projects
|
106
|
+
# => #<Harvesting::Models::Projects:0x007ff718e1c8e8 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>1, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/projects?page=1&per_page=100", ... >
|
107
|
+
|
108
|
+
project = projects.first
|
109
|
+
# => #<Harvesting::Models::Project:0x007ff718e1c618 @attributes={"id"=>17367712, "name"=>"Foo", "code"=>"", "is_active"=>true, "is_billable"=>true, "is_fixed_fee"=>false, "bill_by"=>"none", "budget"=>nil, "budget_by"=>"none", "budget_is_monthly"=>false, "notify_when_over_budget"=>false, "over_budget_notification_percentage"=>80.0, "show_budget_to_all"=>false, "created_at"=>"2018-05-13T03:30:06Z", ... >
|
110
|
+
```
|
111
|
+
|
112
|
+
### Invoices
|
113
|
+
```ruby
|
114
|
+
invoices = client.invoices
|
115
|
+
# => #<Harvesting::Models::Invoices:0x00007fc8905671f0 @models={}, @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>3, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/invoices?page=1&per_page=100", ... >
|
116
|
+
|
117
|
+
invoice = invoices.first
|
118
|
+
# => #<Harvesting::Models::Invoice:0x00007f8c37eb6d18 @models={}, @attributes={"id"=>23831208, "client_key"=>"73688e97a43ed497ace45939eb76db6b18427b80", "number"=>"3", "purchase_order"=>"", "amount"=>750.0, "due_amount"=>750.0, "tax"=>nil, "tax_amount"=>0.0, "tax2"=>nil, "tax2_amount"=>0.0, "discount"=>nil, "discount_amount"=>0.0, "subject"=>"", "notes"=>"", "state"=>"draft", "period_start"=>nil, ... >
|
119
|
+
```
|
120
|
+
|
121
|
+
An invoice can have many line items:
|
122
|
+
```ruby
|
123
|
+
line_items = invoice.line_items
|
124
|
+
# => [#<Harvesting::Models::LineItem:0x00007f92617ce8e0 @models={}, @attributes={"id"=>109677268, "kind"=>"Service", "description"=>"", "quantity"=>3.0, "unit_price"=>250.0, "amount"=>750.0, "taxed"=>false, "taxed2"=>false, "project"=>{"id"=>24566828, "name"=>"Harvest Billing Automation", "code"=>""}}, ... >]
|
125
|
+
```
|
91
126
|
|
92
|
-
|
93
|
-
> => #<Harvesting::Models::Project:0x007ff718e1c618 @attributes={"id"=>17367712, "name"=>"Foo", "code"=>"", "is_active"=>true, "is_billable"=>true, "is_fixed_fee"=>false, "bill_by"=>"none", "budget"=>nil, "budget_by"=>"none", "budget_is_monthly"=>false, "notify_when_over_budget"=>false, "over_budget_notification_percentage"=>80.0, "show_budget_to_all"=>false, "created_at"=>"2018-05-13T03:30:06Z", ... >
|
127
|
+
You can filter invoices by various attributes. E.g. `client.invoices(state: "draft")` only returns invoices in a draft state.
|
94
128
|
|
95
129
|
### Nested Attributes
|
96
130
|
|
97
131
|
The Harvest v2 API embeds some data in JSON objects. You can access nested attributes quite naturally.
|
98
132
|
For example, to access the user id for a time entry instance, `entry`, use:
|
99
133
|
|
100
|
-
|
134
|
+
```ruby
|
135
|
+
entry.user.id
|
136
|
+
```
|
101
137
|
|
102
138
|
Or to access the name of the client on a project instance, `project`:
|
103
|
-
|
104
|
-
|
139
|
+
```ruby
|
140
|
+
project.client.name
|
141
|
+
```
|
105
142
|
|
106
143
|
## Tips
|
107
144
|
|
@@ -109,7 +146,7 @@ Or to access the name of the client on a project instance, `project`:
|
|
109
146
|
|
110
147
|
When you need to delete all items, care needs to be taken, because the API uses pagination. The following code will only delete data from _every other_ page.
|
111
148
|
|
112
|
-
```
|
149
|
+
```ruby
|
113
150
|
# WARNING - only deletes every other page
|
114
151
|
client.time_entries.each do |time_entry|
|
115
152
|
time_entry.delete
|
@@ -120,7 +157,7 @@ While iterating over items from the first page, all of those items will be delet
|
|
120
157
|
|
121
158
|
Instead you need to make sure you get access to all of the time entry objects before you try to delete any of them. The easiest way to do this is to convert the `Enumerable` instance into an `Array`, by calling `#to_a`, before you iterate over it.
|
122
159
|
|
123
|
-
```
|
160
|
+
```ruby
|
124
161
|
# GOOD - This should do what you want
|
125
162
|
client.time_entries.to_a.each do |time_entry|
|
126
163
|
time_entry.delete
|
data/RELEASE_NOTES.md
CHANGED
@@ -1,6 +1,24 @@
|
|
1
1
|
# RELEASE NOTES
|
2
2
|
|
3
|
-
### Version 0.
|
3
|
+
### Version 0.4.0 - June 6, 2020
|
4
|
+
|
5
|
+
**Notes**
|
6
|
+
- Added Ruby 2.5.1 to version matrix in Travis: https://github.com/ombulabs/harvesting/pull/31
|
7
|
+
- Associated time entries for project: https://github.com/ombulabs/harvesting/pull/32
|
8
|
+
- Add require forwardable in havest_record_collection model: https://github.com/ombulabs/harvesting/pull/40
|
9
|
+
- Add syntax highlighting to readme examples: https://github.com/ombulabs/harvesting/pull/41
|
10
|
+
- Rename the client key as harvest_client to avoid confusion: https://github.com/ombulabs/harvesting/pull/43
|
11
|
+
- Update rake requirement from ~> 10.0 to ~> 13.0: https://github.com/ombulabs/harvesting/pull/44
|
12
|
+
- Bump ffi from 1.9.23 to 1.12.2: https://github.com/ombulabs/harvesting/pull/45
|
13
|
+
- Ability to supply filter options to the invoice end point and a model for line items on an invoice.: https://github.com/ombulabs/harvesting/pull/46
|
14
|
+
|
15
|
+
**Bug Fixes**
|
16
|
+
|
17
|
+
- Complete pending test: https://github.com/ombulabs/harvesting/pull/28
|
18
|
+
- Fixed Code Climate link: https://github.com/ombulabs/harvesting/pull/38
|
19
|
+
|
20
|
+
|
21
|
+
### Version 0.3.0 - Jan 22, 2019
|
4
22
|
|
5
23
|
**Notes**
|
6
24
|
|
data/harvesting.gemspec
CHANGED
@@ -6,8 +6,8 @@ require "harvesting/version"
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "harvesting"
|
8
8
|
spec.version = Harvesting::VERSION
|
9
|
-
spec.authors = ["Ernesto Tagwerker"]
|
10
|
-
spec.email = ["ernesto+github@ombulabs.com"]
|
9
|
+
spec.authors = ["Ernesto Tagwerker", "M. Scott Ford"]
|
10
|
+
spec.email = ["ernesto+github@ombulabs.com", "scott@mscottford.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{Ruby wrapper for the Harvest API v2.0}
|
13
13
|
spec.description = %q{Interact with the Harvest API v2.0 from your Ruby application}
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency "http", "~> 3.3", ">= 3.3"
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.16"
|
26
|
-
spec.add_development_dependency "rake", "~>
|
26
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
27
27
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
28
|
spec.add_development_dependency "guard-rspec", "~> 4.7", ">= 4.7"
|
29
29
|
spec.add_development_dependency "byebug", "~> 10.0", ">= 10.0"
|
data/lib/harvesting.rb
CHANGED
@@ -9,9 +9,11 @@ require "harvesting/models/harvest_record_collection"
|
|
9
9
|
require "harvesting/models/client"
|
10
10
|
require "harvesting/models/user"
|
11
11
|
require "harvesting/models/project"
|
12
|
-
require "harvesting/models/invoice"
|
13
12
|
require "harvesting/models/task"
|
14
|
-
require "harvesting/models/
|
13
|
+
require "harvesting/models/project_user_assignment"
|
14
|
+
require "harvesting/models/project_task_assignment"
|
15
|
+
require "harvesting/models/invoice"
|
16
|
+
require "harvesting/models/line_item"
|
15
17
|
require "harvesting/models/time_entry"
|
16
18
|
# harvest record collections
|
17
19
|
require "harvesting/models/tasks"
|
@@ -19,6 +21,9 @@ require "harvesting/models/users"
|
|
19
21
|
require "harvesting/models/contact"
|
20
22
|
require "harvesting/models/time_entries"
|
21
23
|
require "harvesting/models/projects"
|
24
|
+
require "harvesting/models/project_user_assignments"
|
25
|
+
require "harvesting/models/project_task_assignments"
|
26
|
+
require "harvesting/models/invoices"
|
22
27
|
# API client
|
23
28
|
require "harvesting/client"
|
24
29
|
|
data/lib/harvesting/client.rb
CHANGED
@@ -3,12 +3,20 @@ require "http"
|
|
3
3
|
require "json"
|
4
4
|
|
5
5
|
module Harvesting
|
6
|
+
|
7
|
+
# A client for the Harvest API (version 2.0)
|
6
8
|
class Client
|
7
9
|
DEFAULT_HOST = "https://api.harvestapp.com/v2"
|
8
10
|
|
9
11
|
attr_accessor :access_token, :account_id
|
10
12
|
|
11
|
-
#
|
13
|
+
# Returns a new instance of `Client`
|
14
|
+
#
|
15
|
+
# client = Client.new(access_token: "12345678", account_id: "98764")
|
16
|
+
#
|
17
|
+
# @param [Hash] opts the options to create an API client
|
18
|
+
# @option opts [String] :access_token Harvest access token
|
19
|
+
# @option opts [String] :account_id Harvest account id
|
12
20
|
def initialize(access_token: ENV['HARVEST_ACCESS_TOKEN'], account_id: ENV['HARVEST_ACCOUNT_ID'])
|
13
21
|
@access_token = access_token.to_s
|
14
22
|
@account_id = account_id.to_s
|
@@ -18,45 +26,68 @@ module Harvesting
|
|
18
26
|
end
|
19
27
|
end
|
20
28
|
|
29
|
+
# @return [Harvesting::Models::User]
|
21
30
|
def me
|
22
|
-
Harvesting::Models::User.new(get("users/me"),
|
31
|
+
Harvesting::Models::User.new(get("users/me"), harvest_client: self)
|
23
32
|
end
|
24
33
|
|
34
|
+
# @return [Array<Harvesting::Models::Client>]
|
25
35
|
def clients
|
26
36
|
get("clients")["clients"].map do |result|
|
27
|
-
Harvesting::Models::Client.new(result,
|
37
|
+
Harvesting::Models::Client.new(result, harvest_client: self)
|
28
38
|
end
|
29
39
|
end
|
30
40
|
|
41
|
+
# @return [Array<Harvesting::Models::Contact>]
|
31
42
|
def contacts
|
32
43
|
get("contacts")["contacts"].map do |result|
|
33
|
-
Harvesting::Models::Contact.new(result,
|
44
|
+
Harvesting::Models::Contact.new(result, harvest_client: self)
|
34
45
|
end
|
35
46
|
end
|
36
47
|
|
48
|
+
# @return [Harvesting::Models::TimeEntries]
|
37
49
|
def time_entries(opts = {})
|
38
|
-
Harvesting::Models::TimeEntries.new(get("time_entries", opts), opts,
|
50
|
+
Harvesting::Models::TimeEntries.new(get("time_entries", opts), opts, harvest_client: self)
|
39
51
|
end
|
40
52
|
|
53
|
+
# @return [Harvesting::Models::Projects]
|
41
54
|
def projects(opts = {})
|
42
|
-
Harvesting::Models::Projects.new(get("projects", opts), opts,
|
55
|
+
Harvesting::Models::Projects.new(get("projects", opts), opts, harvest_client: self)
|
43
56
|
end
|
44
57
|
|
58
|
+
# @return [Harvesting::Models::Tasks]
|
45
59
|
def tasks(opts = {})
|
46
|
-
Harvesting::Models::Tasks.new(get("tasks", opts), opts,
|
60
|
+
Harvesting::Models::Tasks.new(get("tasks", opts), opts, harvest_client: self)
|
47
61
|
end
|
48
62
|
|
49
|
-
|
63
|
+
# @return [Harvesting::Models::Users]
|
50
64
|
def users(opts = {})
|
51
|
-
Harvesting::Models::Users.new(get("users", opts), opts,
|
65
|
+
Harvesting::Models::Users.new(get("users", opts), opts, harvest_client: self)
|
52
66
|
end
|
53
67
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
68
|
+
# @return [Array<Harvesting::Models::Invoice>]
|
69
|
+
def invoices(opts = {})
|
70
|
+
Harvesting::Models::Invoices.new(get("invoices", opts), opts, harvest_client: self)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Harvesting::Models::ProjectUserAssignments]
|
74
|
+
def user_assignments(opts = {})
|
75
|
+
project_id = opts.delete(:project_id)
|
76
|
+
path = project_id.nil? ? "user_assignments" : "projects/#{project_id}/user_assignments"
|
77
|
+
Harvesting::Models::ProjectUserAssignments.new(get(path, opts), opts, harvest_client: self)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Harvesting::Models::ProjectTaskAssignments]
|
81
|
+
def task_assignments(opts = {})
|
82
|
+
project_id = opts.delete(:project_id)
|
83
|
+
path = project_id.nil? ? "task_assignments" : "projects/#{project_id}/task_assignments"
|
84
|
+
Harvesting::Models::ProjectTaskAssignments.new(get(path, opts), opts, harvest_client: self)
|
58
85
|
end
|
59
86
|
|
87
|
+
# Creates an `entity` in your Harvest account.
|
88
|
+
#
|
89
|
+
# @param entity [Harvesting::Models::Base] A new record in your Harvest account
|
90
|
+
# @return [Harvesting::Models::Base] A subclass of `Harvesting::Models::Base` updated with the response from Harvest
|
60
91
|
def create(entity)
|
61
92
|
url = "#{DEFAULT_HOST}/#{entity.path}"
|
62
93
|
uri = URI(url)
|
@@ -65,6 +96,10 @@ module Harvesting
|
|
65
96
|
entity
|
66
97
|
end
|
67
98
|
|
99
|
+
# Updates an `entity` in your Harvest account.
|
100
|
+
#
|
101
|
+
# @param entity [Harvesting::Models::Base] An existing record in your Harvest account
|
102
|
+
# @return [Harvesting::Models::Base] A subclass of `Harvesting::Models::Base` updated with the response from Harvest
|
68
103
|
def update(entity)
|
69
104
|
url = "#{DEFAULT_HOST}/#{entity.path}"
|
70
105
|
uri = URI(url)
|
@@ -73,13 +108,24 @@ module Harvesting
|
|
73
108
|
entity
|
74
109
|
end
|
75
110
|
|
111
|
+
# It removes an `entity` from your Harvest account.
|
112
|
+
#
|
113
|
+
# @param entity [Harvesting::Models::Base] A record to be removed from your Harvest account
|
114
|
+
# @return [Hash]
|
115
|
+
# @raise [UnprocessableRequest] When HTTP response is not 200 OK
|
76
116
|
def delete(entity)
|
77
117
|
url = "#{DEFAULT_HOST}/#{entity.path}"
|
78
118
|
uri = URI(url)
|
79
119
|
response = http_response(:delete, uri)
|
80
120
|
raise UnprocessableRequest(response.to_s) unless response.code.to_i == 200
|
121
|
+
JSON.parse(response.body)
|
81
122
|
end
|
82
123
|
|
124
|
+
# Performs a GET request and returned the parsed JSON as a Hash.
|
125
|
+
#
|
126
|
+
# @param path [String] path to be combined with `DEFAULT_HOST`
|
127
|
+
# @param opts [Hash] key/values will get passed as HTTP (GET) parameters
|
128
|
+
# @return [Hash]
|
83
129
|
def get(path, opts = {})
|
84
130
|
url = "#{DEFAULT_HOST}/#{path}"
|
85
131
|
url += "?#{opts.map {|k, v| "#{k}=#{v}"}.join("&")}" if opts.any?
|
@@ -1,54 +1,62 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
3
|
class Base
|
4
|
+
# @return [Hash]
|
4
5
|
attr_accessor :attributes
|
6
|
+
# @return [Harvesting::Model::Client]
|
5
7
|
attr_reader :harvest_client
|
6
8
|
|
7
9
|
def initialize(attrs, opts = {})
|
8
10
|
@models = {}
|
9
11
|
@attributes = attrs.dup
|
10
|
-
@harvest_client = opts[:
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.attributed(*attribute_names)
|
14
|
-
attribute_names.each do |attribute_name|
|
15
|
-
define_method(attribute_name) do
|
16
|
-
@attributes[__method__.to_s]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.modeled(opts = {})
|
22
|
-
opts.each do |attribute_name, model|
|
23
|
-
attribute_name_string = attribute_name.to_s
|
24
|
-
Harvesting::Models::Base.send :define_method, attribute_name_string do
|
25
|
-
@models[attribute_name_string] ||= model.new(@attributes[attribute_name_string] || {}, client: harvest_client)
|
26
|
-
end
|
27
|
-
end
|
12
|
+
@harvest_client = opts[:harvest_client] || Harvesting::Client.new(opts)
|
28
13
|
end
|
29
14
|
|
15
|
+
# It calls `create` or `update` depending on the record's ID. If the ID
|
16
|
+
# is present, then it calls `update`. Otherwise it calls `create`
|
17
|
+
#
|
18
|
+
# @see Client#create
|
19
|
+
# @see Client#update
|
30
20
|
def save
|
31
21
|
id.nil? ? create : update
|
32
22
|
end
|
33
23
|
|
24
|
+
# It creates the record.
|
25
|
+
#
|
26
|
+
# @see Client#create
|
27
|
+
# @return [Harvesting::Models::Base]
|
34
28
|
def create
|
35
29
|
@harvest_client.create(self)
|
36
30
|
end
|
37
31
|
|
32
|
+
# It updates the record.
|
33
|
+
#
|
34
|
+
# @see Client#update
|
35
|
+
# @return [Harvesting::Models::Base]
|
38
36
|
def update
|
39
37
|
@harvest_client.update(self)
|
40
38
|
end
|
41
39
|
|
40
|
+
# It removes the record.
|
41
|
+
#
|
42
|
+
# @see Client#delete
|
43
|
+
# @return [Harvesting::Models::Base]
|
42
44
|
def delete
|
43
45
|
@harvest_client.delete(self)
|
44
46
|
end
|
45
47
|
|
48
|
+
# It returns keys and values for all the attributes of this record.
|
49
|
+
#
|
50
|
+
# @return [Hash]
|
46
51
|
def to_hash
|
47
52
|
@attributes
|
48
53
|
end
|
49
54
|
|
55
|
+
# It loads a new record from your Harvest account.
|
56
|
+
#
|
57
|
+
# @return [Harvesting::Models::Base]
|
50
58
|
def fetch
|
51
|
-
self.class.new(@harvest_client.get(path),
|
59
|
+
self.class.new(@harvest_client.get(path), harvest_client: @harvest_client)
|
52
60
|
end
|
53
61
|
|
54
62
|
# Retrieves an instance of the object by ID
|
@@ -57,9 +65,54 @@ module Harvesting
|
|
57
65
|
# @param opts [Hash] options to pass along to the `Harvesting::Client`
|
58
66
|
# instance
|
59
67
|
def self.get(id, opts = {})
|
60
|
-
client = opts[:
|
68
|
+
client = opts[:harvest_client] || Harvesting::Client.new(opts)
|
61
69
|
self.new({ 'id' => id }, opts).fetch
|
62
70
|
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
# Class method to define attribute methods for accessing attributes for
|
75
|
+
# a record
|
76
|
+
#
|
77
|
+
# It needs to be used like this:
|
78
|
+
#
|
79
|
+
# class Contact < HarvestRecord
|
80
|
+
# attributed :id,
|
81
|
+
# :title,
|
82
|
+
# :first_name
|
83
|
+
# ...
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# @param attribute_names [Array] A list of attributes
|
87
|
+
def self.attributed(*attribute_names)
|
88
|
+
attribute_names.each do |attribute_name|
|
89
|
+
define_method(attribute_name) do
|
90
|
+
@attributes[__method__.to_s]
|
91
|
+
end
|
92
|
+
define_method("#{attribute_name}=") do |value|
|
93
|
+
@attributes[__method__.to_s.chop] = value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Class method to define nested resources for a record.
|
99
|
+
#
|
100
|
+
# It needs to be used like this:
|
101
|
+
#
|
102
|
+
# class Contact < HarvestRecord
|
103
|
+
# modeled client: Client
|
104
|
+
# ...
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# @param opts [Hash] key = symbol that needs to be the same as the one returned by the Harvest API. value = model class for the nested resource.
|
108
|
+
def self.modeled(opts = {})
|
109
|
+
opts.each do |attribute_name, model|
|
110
|
+
attribute_name_string = attribute_name.to_s
|
111
|
+
Harvesting::Models::Base.send :define_method, attribute_name_string do
|
112
|
+
@models[attribute_name_string] ||= model.new(@attributes[attribute_name_string] || {}, harvest_client: harvest_client)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
63
116
|
end
|
64
117
|
end
|
65
118
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
+
# A contact record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/clients-api/clients/contacts/
|
3
6
|
class Contact < HarvestRecord
|
4
7
|
attributed :id,
|
5
8
|
:title,
|
@@ -17,7 +20,7 @@ module Harvesting
|
|
17
20
|
def path
|
18
21
|
@attributes['id'].nil? ? "contacts" : "contacts/#{@attributes['id']}"
|
19
22
|
end
|
20
|
-
|
23
|
+
|
21
24
|
def to_hash
|
22
25
|
{ client_id: client.id }.merge(super)
|
23
26
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
+
# An invoice record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/invoices-api/invoices/invoices/
|
3
6
|
class Invoice < HarvestRecord
|
4
7
|
attributed :id,
|
5
8
|
:client_key,
|
6
|
-
:line_items,
|
7
9
|
:number,
|
8
10
|
:purchase_order,
|
9
11
|
:amount,
|
@@ -29,6 +31,11 @@ module Harvesting
|
|
29
31
|
:closed_at,
|
30
32
|
:created_at,
|
31
33
|
:updated_at
|
34
|
+
|
35
|
+
def line_items
|
36
|
+
@line_items ||= @attributes['line_items'].map { |line_item_attributes| LineItem.new line_item_attributes, { harvest_client: harvest_client } }
|
37
|
+
end
|
38
|
+
|
32
39
|
def path
|
33
40
|
@attributes['id'].nil? ? "invoices" : "invoices/#{@attributes['id']}"
|
34
41
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Harvesting
|
2
|
+
module Models
|
3
|
+
class Invoices < HarvestRecordCollection
|
4
|
+
def initialize(attrs, query_opts = {}, opts = {})
|
5
|
+
super(attrs.reject {|k,v| k == "invoices" }, query_opts, opts)
|
6
|
+
@entries = attrs["invoices"].map do |entry|
|
7
|
+
Invoice.new(entry, harvest_client: opts[:harvest_client])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_next_page
|
12
|
+
@entries += harvest_client.invoices(next_page_query_opts).entries
|
13
|
+
@attributes['page'] = page + 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Harvesting
|
2
|
+
module Models
|
3
|
+
# A line item on an invoice from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/invoices-api/invoices/invoices/
|
6
|
+
class LineItem < HarvestRecord
|
7
|
+
attributed :id,
|
8
|
+
:kind,
|
9
|
+
:description,
|
10
|
+
:quantity,
|
11
|
+
:unit_price,
|
12
|
+
:amount,
|
13
|
+
:taxed,
|
14
|
+
:taxed2
|
15
|
+
|
16
|
+
modeled project: Project
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
+
# A project record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/projects-api/projects/projects/
|
3
6
|
class Project < HarvestRecord
|
4
7
|
attributed :id,
|
5
8
|
:name,
|
@@ -30,10 +33,26 @@ module Harvesting
|
|
30
33
|
def path
|
31
34
|
@attributes['id'].nil? ? "projects" : "projects/#{@attributes['id']}"
|
32
35
|
end
|
33
|
-
|
36
|
+
|
34
37
|
def to_hash
|
35
38
|
{ client_id: client.id }.merge(super)
|
36
39
|
end
|
40
|
+
|
41
|
+
def time_entries
|
42
|
+
harvest_client.time_entries(project_id: self.id)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Provides access to the user assignments that are associated with this
|
46
|
+
# project.
|
47
|
+
def user_assignments
|
48
|
+
harvest_client.user_assignments(project_id: self.id)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Provides access to the task assignments that are associated with this
|
52
|
+
# project.
|
53
|
+
def task_assignments
|
54
|
+
harvest_client.task_assignments(project_id: self.id)
|
55
|
+
end
|
37
56
|
end
|
38
57
|
end
|
39
58
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
-
|
3
|
+
# A task assignment record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/projects-api/projects/task-assignments/
|
6
|
+
class ProjectTaskAssignment < HarvestRecord
|
4
7
|
attributed :id,
|
5
8
|
:is_active,
|
6
9
|
:billable,
|
@@ -21,11 +24,10 @@ module Harvesting
|
|
21
24
|
# # TODO: handle case where project's id is part of json object
|
22
25
|
# @attributes["project_id"]
|
23
26
|
# end
|
24
|
-
|
27
|
+
|
25
28
|
def to_hash
|
26
29
|
{ project_id: project.id, task_id: task.id }.merge(super)
|
27
30
|
end
|
28
|
-
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Harvesting
|
2
|
+
module Models
|
3
|
+
class ProjectTaskAssignments < HarvestRecordCollection
|
4
|
+
def initialize(attrs, query_opts = {}, opts = {})
|
5
|
+
super(attrs.reject {|k,v| k == "task_assignments" }, query_opts, opts)
|
6
|
+
@entries = attrs["task_assignments"].map do |entry|
|
7
|
+
ProjectTaskAssignment.new(entry, harvest_client: opts[:harvest_client])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_next_page
|
12
|
+
@entries += harvest_client.task_assignments(next_page_query_opts).entries
|
13
|
+
@attributes['page'] = page + 1
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Harvesting
|
2
|
+
module Models
|
3
|
+
class ProjectUserAssignment < HarvestRecord
|
4
|
+
attributed :id,
|
5
|
+
:is_active,
|
6
|
+
:is_project_manager,
|
7
|
+
:hourly_rate,
|
8
|
+
:budget,
|
9
|
+
:created_at,
|
10
|
+
:updated_at
|
11
|
+
|
12
|
+
modeled project: Project,
|
13
|
+
user: User
|
14
|
+
|
15
|
+
def path
|
16
|
+
base_url = "projects/#{project.id}/user_assignments"
|
17
|
+
@attributes['id'].nil? ? base_url : "#{base_url}/#{@attributes['id']}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{ project_id: project.id, user_id: user.id }.merge(super)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Harvesting
|
2
|
+
module Models
|
3
|
+
class ProjectUserAssignments < HarvestRecordCollection
|
4
|
+
|
5
|
+
def initialize(attrs, query_opts = {}, opts = {})
|
6
|
+
super(attrs.reject {|k,v| k == "user_assignments" }, query_opts, opts)
|
7
|
+
@entries = attrs["user_assignments"].map do |entry|
|
8
|
+
ProjectUserAssignment.new(entry, harvest_client: opts[:harvest_client])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch_next_page
|
13
|
+
@entries += harvest_client.user_assignments(next_page_query_opts).entries
|
14
|
+
@attributes['page'] = page + 1
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -5,7 +5,7 @@ module Harvesting
|
|
5
5
|
def initialize(attrs, query_opts = {}, opts = {})
|
6
6
|
super(attrs.reject {|k,v| k == "projects" }, query_opts, opts)
|
7
7
|
@entries = attrs["projects"].map do |entry|
|
8
|
-
Project.new(entry,
|
8
|
+
Project.new(entry, harvest_client: opts[:harvest_client])
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -1,9 +1,12 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
+
# A task record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/tasks-api/tasks/tasks/
|
3
6
|
class Task < HarvestRecord
|
4
7
|
attributed :id,
|
5
8
|
:name,
|
6
|
-
:billable_by_default,
|
9
|
+
:billable_by_default,
|
7
10
|
:default_hourly_rate,
|
8
11
|
:is_default,
|
9
12
|
:is_active,
|
@@ -5,7 +5,7 @@ module Harvesting
|
|
5
5
|
def initialize(attrs, query_opts = {}, opts = {})
|
6
6
|
super(attrs.reject {|k,v| k == "tasks" }, query_opts, opts)
|
7
7
|
@entries = attrs["tasks"].map do |entry|
|
8
|
-
Task.new(entry,
|
8
|
+
Task.new(entry, harvest_client: opts[:harvest_client])
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -5,7 +5,7 @@ module Harvesting
|
|
5
5
|
def initialize(attrs, query_opts = {}, opts = {})
|
6
6
|
super(attrs.reject {|k,v| k == "time_entries" }, query_opts, opts)
|
7
7
|
@entries = attrs["time_entries"].map do |entry|
|
8
|
-
TimeEntry.new(entry,
|
8
|
+
TimeEntry.new(entry, harvest_client: opts[:harvest_client])
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
+
# A time entry record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/
|
3
6
|
class TimeEntry < HarvestRecord
|
4
7
|
attributed :id,
|
5
8
|
:spent_date,
|
@@ -20,14 +23,14 @@ module Harvesting
|
|
20
23
|
:invoice,
|
21
24
|
:external_reference,
|
22
25
|
:created_at,
|
23
|
-
:updated_at
|
24
|
-
:user_assignment # temporarily return the hash itself until the model is added
|
26
|
+
:updated_at
|
25
27
|
|
26
28
|
modeled project: Project,
|
27
29
|
user: User,
|
28
30
|
task: Task,
|
29
31
|
client: Client,
|
30
|
-
task_assignment:
|
32
|
+
task_assignment: ProjectTaskAssignment,
|
33
|
+
user_assignment: ProjectUserAssignment
|
31
34
|
|
32
35
|
|
33
36
|
def path
|
@@ -1,7 +1,11 @@
|
|
1
1
|
module Harvesting
|
2
2
|
module Models
|
3
|
+
# An user record from your Harvest account.
|
4
|
+
#
|
5
|
+
# For more information: https://help.getharvest.com/api-v2/users-api/users/users/
|
3
6
|
class User < HarvestRecord
|
4
7
|
attributed :id,
|
8
|
+
:name,
|
5
9
|
:first_name,
|
6
10
|
:last_name,
|
7
11
|
:email,
|
@@ -26,6 +30,10 @@ module Harvesting
|
|
26
30
|
def path
|
27
31
|
@attributes['id'].nil? ? "users" : "users/#{@attributes['id']}"
|
28
32
|
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
@attributes['name'].nil? ? "#{first_name} #{last_name}" : @attributes['name']
|
36
|
+
end
|
29
37
|
end
|
30
38
|
end
|
31
39
|
end
|
@@ -5,7 +5,7 @@ module Harvesting
|
|
5
5
|
def initialize(attrs, query_opts = {}, opts = {})
|
6
6
|
super(attrs.reject {|k,v| k == "users" }, query_opts, opts)
|
7
7
|
@entries = attrs["users"].map do |entry|
|
8
|
-
User.new(entry,
|
8
|
+
User.new(entry, harvest_client: opts[:harvest_client])
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
data/lib/harvesting/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harvesting
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ernesto Tagwerker
|
8
|
+
- M. Scott Ford
|
8
9
|
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2020-06-06 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: http
|
@@ -50,14 +51,14 @@ dependencies:
|
|
50
51
|
requirements:
|
51
52
|
- - "~>"
|
52
53
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
54
|
+
version: '13.0'
|
54
55
|
type: :development
|
55
56
|
prerelease: false
|
56
57
|
version_requirements: !ruby/object:Gem::Requirement
|
57
58
|
requirements:
|
58
59
|
- - "~>"
|
59
60
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
61
|
+
version: '13.0'
|
61
62
|
- !ruby/object:Gem::Dependency
|
62
63
|
name: rspec
|
63
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,6 +176,7 @@ dependencies:
|
|
175
176
|
description: Interact with the Harvest API v2.0 from your Ruby application
|
176
177
|
email:
|
177
178
|
- ernesto+github@ombulabs.com
|
179
|
+
- scott@mscottford.com
|
178
180
|
executables: []
|
179
181
|
extensions: []
|
180
182
|
extra_rdoc_files: []
|
@@ -206,10 +208,15 @@ files:
|
|
206
208
|
- lib/harvesting/models/harvest_record.rb
|
207
209
|
- lib/harvesting/models/harvest_record_collection.rb
|
208
210
|
- lib/harvesting/models/invoice.rb
|
211
|
+
- lib/harvesting/models/invoices.rb
|
212
|
+
- lib/harvesting/models/line_item.rb
|
209
213
|
- lib/harvesting/models/project.rb
|
214
|
+
- lib/harvesting/models/project_task_assignment.rb
|
215
|
+
- lib/harvesting/models/project_task_assignments.rb
|
216
|
+
- lib/harvesting/models/project_user_assignment.rb
|
217
|
+
- lib/harvesting/models/project_user_assignments.rb
|
210
218
|
- lib/harvesting/models/projects.rb
|
211
219
|
- lib/harvesting/models/task.rb
|
212
|
-
- lib/harvesting/models/task_assignment.rb
|
213
220
|
- lib/harvesting/models/tasks.rb
|
214
221
|
- lib/harvesting/models/time_entries.rb
|
215
222
|
- lib/harvesting/models/time_entry.rb
|
@@ -235,8 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
242
|
- !ruby/object:Gem::Version
|
236
243
|
version: '0'
|
237
244
|
requirements: []
|
238
|
-
|
239
|
-
rubygems_version: 2.7.8
|
245
|
+
rubygems_version: 3.0.8
|
240
246
|
signing_key:
|
241
247
|
specification_version: 4
|
242
248
|
summary: Ruby wrapper for the Harvest API v2.0
|