harvested2 5.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +35 -0
- data/.rspec +1 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +20 -0
- data/HISTORY.md +118 -0
- data/MIT-LICENSE +21 -0
- data/README.md +66 -0
- data/Rakefile +24 -0
- data/harvested2.gemspec +30 -0
- data/lib/ext/array.rb +52 -0
- data/lib/ext/date.rb +9 -0
- data/lib/ext/hash.rb +17 -0
- data/lib/ext/time.rb +5 -0
- data/lib/harvest/account.rb +13 -0
- data/lib/harvest/api/account.rb +25 -0
- data/lib/harvest/api/base.rb +72 -0
- data/lib/harvest/api/clients.rb +10 -0
- data/lib/harvest/api/company.rb +12 -0
- data/lib/harvest/api/contacts.rb +9 -0
- data/lib/harvest/api/expense_categories.rb +9 -0
- data/lib/harvest/api/expenses.rb +26 -0
- data/lib/harvest/api/invoice_categories.rb +9 -0
- data/lib/harvest/api/invoice_messages.rb +86 -0
- data/lib/harvest/api/invoice_payments.rb +41 -0
- data/lib/harvest/api/invoices.rb +9 -0
- data/lib/harvest/api/projects.rb +9 -0
- data/lib/harvest/api/task_assignments.rb +75 -0
- data/lib/harvest/api/tasks.rb +9 -0
- data/lib/harvest/api/time_entry.rb +19 -0
- data/lib/harvest/api/user_assignments.rb +75 -0
- data/lib/harvest/api/users.rb +10 -0
- data/lib/harvest/base.rb +333 -0
- data/lib/harvest/behavior/activatable.rb +31 -0
- data/lib/harvest/behavior/crud.rb +80 -0
- data/lib/harvest/client.rb +23 -0
- data/lib/harvest/company.rb +8 -0
- data/lib/harvest/contact.rb +20 -0
- data/lib/harvest/credentials.rb +34 -0
- data/lib/harvest/errors.rb +27 -0
- data/lib/harvest/expense.rb +54 -0
- data/lib/harvest/expense_category.rb +10 -0
- data/lib/harvest/hardy_client.rb +80 -0
- data/lib/harvest/invoice.rb +75 -0
- data/lib/harvest/invoice_category.rb +8 -0
- data/lib/harvest/invoice_message.rb +8 -0
- data/lib/harvest/invoice_payment.rb +8 -0
- data/lib/harvest/line_item.rb +21 -0
- data/lib/harvest/model.rb +133 -0
- data/lib/harvest/project.rb +41 -0
- data/lib/harvest/receipt.rb +12 -0
- data/lib/harvest/task.rb +21 -0
- data/lib/harvest/task_assignment.rb +27 -0
- data/lib/harvest/time_entry.rb +57 -0
- data/lib/harvest/timezones.rb +130 -0
- data/lib/harvest/user.rb +58 -0
- data/lib/harvest/user_assignment.rb +27 -0
- data/lib/harvest/version.rb +3 -0
- data/lib/harvested2.rb +96 -0
- data/spec/factories/client.rb +14 -0
- data/spec/factories/contact.rb +8 -0
- data/spec/factories/expense.rb +10 -0
- data/spec/factories/expenses_category.rb +7 -0
- data/spec/factories/invoice.rb +25 -0
- data/spec/factories/invoice_category.rb +5 -0
- data/spec/factories/invoice_message.rb +9 -0
- data/spec/factories/invoice_payment.rb +7 -0
- data/spec/factories/line_item.rb +9 -0
- data/spec/factories/project.rb +15 -0
- data/spec/factories/task.rb +8 -0
- data/spec/factories/task_assignment.rb +8 -0
- data/spec/factories/time_entry.rb +13 -0
- data/spec/factories/user.rb +19 -0
- data/spec/factories/user_assigment.rb +7 -0
- data/spec/functional/clients_spec.rb +105 -0
- data/spec/functional/errors_spec.rb +42 -0
- data/spec/functional/expenses_spec.rb +97 -0
- data/spec/functional/invoice_messages_spec.rb +48 -0
- data/spec/functional/invoice_payments_spec.rb +51 -0
- data/spec/functional/invoice_spec.rb +138 -0
- data/spec/functional/project_spec.rb +76 -0
- data/spec/functional/tasks_spec.rb +119 -0
- data/spec/functional/time_entries_spec.rb +87 -0
- data/spec/functional/users_spec.rb +72 -0
- data/spec/harvest/base_spec.rb +10 -0
- data/spec/harvest/basic_auth_credentials_spec.rb +12 -0
- data/spec/harvest/expense_category_spec.rb +5 -0
- data/spec/harvest/expense_spec.rb +18 -0
- data/spec/harvest/invoice_message_spec.rb +5 -0
- data/spec/harvest/invoice_payment_spec.rb +5 -0
- data/spec/harvest/invoice_spec.rb +5 -0
- data/spec/harvest/oauth_credentials_spec.rb +11 -0
- data/spec/harvest/project_spec.rb +5 -0
- data/spec/harvest/task_assignment_spec.rb +5 -0
- data/spec/harvest/task_spec.rb +5 -0
- data/spec/harvest/time_entry_spec.rb +23 -0
- data/spec/harvest/user_assignment_spec.rb +5 -0
- data/spec/harvest/user_spec.rb +34 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/factory_bot.rb +5 -0
- data/spec/support/harvested_helpers.rb +28 -0
- data/spec/support/json_examples.rb +9 -0
- metadata +238 -0
data/lib/harvest/user.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Harvest
|
2
|
+
# The model that contains information about a task
|
3
|
+
#
|
4
|
+
# == Fields
|
5
|
+
# [+id+] (READONLY) the id of the user
|
6
|
+
# [+email+] the email of the user
|
7
|
+
# [+first_name+] the first name for the user
|
8
|
+
# [+last_name+] the last name for the user
|
9
|
+
# [+telephone+] the telephone for the user
|
10
|
+
# [+department] the department for the user
|
11
|
+
# [+has_access_to_all_future_projects+] whether the user should be added to future projects by default
|
12
|
+
# [+hourly_rate+] what the default hourly rate for the user is
|
13
|
+
# [+admin?+] whether the user is an admin
|
14
|
+
# [+contractor?+] whether the user is a contractor
|
15
|
+
# [+contractor?+] whether the user is a contractor
|
16
|
+
# [+timezone+] the timezone for the user.
|
17
|
+
class User < Hashie::Mash
|
18
|
+
include Harvest::Model
|
19
|
+
|
20
|
+
skip_json_root true
|
21
|
+
api_path '/users'
|
22
|
+
|
23
|
+
delegate_methods(active?: :is_active,
|
24
|
+
admin?: :is_admin,
|
25
|
+
contractor?: :is_contractor)
|
26
|
+
|
27
|
+
def initialize(args = {}, _ = nil)
|
28
|
+
args = args.stringify_keys
|
29
|
+
args['is_admin'] = args.delete('admin') if args['admin']
|
30
|
+
self.timezone = args.delete('timezone') if args['timezone']
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the timezone for the user. This can be done in a variety of ways.
|
35
|
+
#
|
36
|
+
# == Examples
|
37
|
+
# user.timezone = :cst # the easiest way. CST, EST, MST, and PST are supported
|
38
|
+
#
|
39
|
+
# user.timezone = 'america/chicago' # a little more verbose
|
40
|
+
#
|
41
|
+
# user.timezone = 'Central Time (US & Canada)' # the most explicit way
|
42
|
+
def timezone=(timezone)
|
43
|
+
tz = timezone.to_s.downcase
|
44
|
+
case tz
|
45
|
+
when 'cst', 'cdt' then self.timezone = 'america/chicago'
|
46
|
+
when 'est', 'edt' then self.timezone = 'america/new_york'
|
47
|
+
when 'mst', 'mdt' then self.timezone = 'america/denver'
|
48
|
+
when 'pst', 'pdt' then self.timezone = 'america/los_angeles'
|
49
|
+
else
|
50
|
+
if Harvest::Timezones::MAPPING[tz]
|
51
|
+
self['timezone'] = Harvest::Timezones::MAPPING[tz]
|
52
|
+
else
|
53
|
+
self['timezone'] = timezone
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Harvest
|
2
|
+
class UserAssignment < Hashie::Mash
|
3
|
+
include Harvest::Model
|
4
|
+
|
5
|
+
skip_json_root true
|
6
|
+
|
7
|
+
def initialize(args = {}, _ = nil)
|
8
|
+
args = args.stringify_keys
|
9
|
+
self.user = args.delete('user') if args['user']
|
10
|
+
self.project = args.delete('project') if args['project']
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def user=(user)
|
16
|
+
self['user_id'] = user['id']
|
17
|
+
end
|
18
|
+
|
19
|
+
def project=(project)
|
20
|
+
self['project_id'] = project['id']
|
21
|
+
end
|
22
|
+
|
23
|
+
def active?
|
24
|
+
!deactivated
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/harvested2.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'base64'
|
3
|
+
require 'delegate'
|
4
|
+
require 'hashie'
|
5
|
+
require 'json'
|
6
|
+
require 'time'
|
7
|
+
require 'csv'
|
8
|
+
|
9
|
+
require 'ext/array'
|
10
|
+
require 'ext/hash'
|
11
|
+
require 'ext/date'
|
12
|
+
require 'ext/time'
|
13
|
+
|
14
|
+
require 'harvest/version'
|
15
|
+
require 'harvest/credentials'
|
16
|
+
require 'harvest/errors'
|
17
|
+
require 'harvest/hardy_client'
|
18
|
+
require 'harvest/timezones'
|
19
|
+
|
20
|
+
require 'harvest/base'
|
21
|
+
|
22
|
+
%w(crud activatable).each { |a| require "harvest/behavior/#{a}" }
|
23
|
+
|
24
|
+
%w(model client contact project task user task_assignment
|
25
|
+
user_assignment expense_category expense time_entry invoice_category
|
26
|
+
line_item invoice invoice_payment
|
27
|
+
invoice_message).each { |a| require "harvest/#{a}" }
|
28
|
+
|
29
|
+
%w(base account clients contacts projects tasks users task_assignments
|
30
|
+
user_assignments expense_categories expenses time_entry
|
31
|
+
invoice_categories invoices invoice_payments
|
32
|
+
invoice_messages).each { |a| require "harvest/api/#{a}" }
|
33
|
+
|
34
|
+
module Harvest
|
35
|
+
class << self
|
36
|
+
|
37
|
+
# Creates a standard client that will raise all errors it encounters
|
38
|
+
#
|
39
|
+
# == Options
|
40
|
+
# * Basic Authentication V2
|
41
|
+
# * +:access_token+ - Your Harvest access_token
|
42
|
+
# * +:account_id+ - Your Harvest account_id
|
43
|
+
# * +:client_id+ - An OAuth 2.0 access token
|
44
|
+
#
|
45
|
+
# == Examples
|
46
|
+
# Harvest.client(access_token: 'token', account_id: '123')
|
47
|
+
# Harvest.client(access_token: 'myaccesstoken', client_id: '1')
|
48
|
+
#
|
49
|
+
# @return [Harvest::Base]
|
50
|
+
def client(access_token: nil, account_id: nil, client_id: nil)
|
51
|
+
Harvest::Base.new(access_token: access_token, account_id: account_id,
|
52
|
+
client_id: client_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a hardy client that will retry common HTTP errors
|
56
|
+
# it encounters and sleep() if it determines it is over your rate limit
|
57
|
+
#
|
58
|
+
# == Options
|
59
|
+
# * Basic Authentication
|
60
|
+
# * +:access_token+ - Your Harvest access_token
|
61
|
+
# * +:account_id+ - Your Harvest account_id
|
62
|
+
# * +:client_id+ - An OAuth 2.0 access token
|
63
|
+
# * +:retry+ - How many times the hardy client should retry errors.
|
64
|
+
# Set to +5+ by default.
|
65
|
+
#
|
66
|
+
# == Examples
|
67
|
+
# Harvest.hardy_client(access_token: 'token', account_id: '123', retry: 3)
|
68
|
+
#
|
69
|
+
# Harvest.hardy_client(access_token: 'myaccesstoken', client_id: '1',
|
70
|
+
# retries: 3)
|
71
|
+
#
|
72
|
+
# == Errors
|
73
|
+
# The hardy client will retry the following errors
|
74
|
+
# * Harvest::Unavailable
|
75
|
+
# * Harvest::InformHarvest
|
76
|
+
# * Net::HTTPError
|
77
|
+
# * Net::HTTPFatalError
|
78
|
+
# * Errno::ECONNRESET
|
79
|
+
#
|
80
|
+
# == Rate Limits
|
81
|
+
# The hardy client will make as many requests as it can until it detects it
|
82
|
+
# has gone over the rate limit. Then it will +sleep()+ for the how ever
|
83
|
+
# long it takes for the limit to reset. You can find more information about
|
84
|
+
# the Rate Limiting at http://www.getharvest.com/api
|
85
|
+
#
|
86
|
+
# @return [Harvest::HardyClient] a Harvest::Base wrapped in a Harvest::HardyClient
|
87
|
+
# @see Harvest::Base
|
88
|
+
def hardy_client(access_token: nil, account_id: nil, client_id: nil, retries: 5)
|
89
|
+
Harvest::HardyClient.new(client(
|
90
|
+
access_token: access_token,
|
91
|
+
account_id: account_id,
|
92
|
+
client_id: client_id),
|
93
|
+
retries)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :invoice, class: Harvest::Invoice do
|
3
|
+
sequence(:id)
|
4
|
+
subject "Invoice for Joe's Stream Cleaning"
|
5
|
+
amount 2400.0
|
6
|
+
issued_at '2014-01-01'
|
7
|
+
due_at "2011-03-31"
|
8
|
+
due_at_human_format 'upon receipt'
|
9
|
+
currency 'United States Dollars - USD'
|
10
|
+
number '1000'
|
11
|
+
notes 'Some notes go here'
|
12
|
+
period_end '2013-03-31'
|
13
|
+
period_start '2013-02-26'
|
14
|
+
kind 'free_form'
|
15
|
+
state 'draft'
|
16
|
+
purchase_order nil
|
17
|
+
tax nil
|
18
|
+
tax2 nil
|
19
|
+
import_hours 'no'
|
20
|
+
import_expenses 'no'
|
21
|
+
|
22
|
+
client
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :user, class: Harvest::User do
|
3
|
+
sequence(:id)
|
4
|
+
first_name 'Edgar'
|
5
|
+
last_name 'Ruth'
|
6
|
+
email 'edgar@ruth.com'
|
7
|
+
timezone 'cst'
|
8
|
+
is_admin 'false'
|
9
|
+
telephone '444-4444'
|
10
|
+
|
11
|
+
trait :active do
|
12
|
+
is_active true
|
13
|
+
end
|
14
|
+
|
15
|
+
trait :deactive do
|
16
|
+
is_active false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'harvest clients' do
|
4
|
+
let(:harvest) { Harvest.client(access_token: 'mytoken', account_id: '123') }
|
5
|
+
|
6
|
+
describe 'clients' do
|
7
|
+
let(:client) { create(:client) }
|
8
|
+
let(:client_attributes) { FactoryBot.attributes_for(:client) }
|
9
|
+
|
10
|
+
context 'allows to add clients' do
|
11
|
+
before do
|
12
|
+
allow(harvest.clients).to receive(:create).and_return(client)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns true' do
|
16
|
+
expect(client.name).to eql(client_attributes[:name])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'allows to update clients' do
|
21
|
+
before do
|
22
|
+
allow(harvest.clients).to receive(:update).and_return(client)
|
23
|
+
client.name = "Joe and Frank's Steam Cleaning"
|
24
|
+
client = harvest.clients.update(client)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns true' do
|
28
|
+
expect(client.name).to eql("Joe and Frank's Steam Cleaning")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'allows to remove clients' do
|
33
|
+
before do
|
34
|
+
allow(harvest.clients).to receive(:delete).and_return([])
|
35
|
+
allow(harvest.clients).to receive(:all).and_return([])
|
36
|
+
harvest.clients.delete(client)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns true' do
|
40
|
+
expect(harvest.clients.all.select do |p|
|
41
|
+
p.name == "Joe and Frank's Steam Cleaning"
|
42
|
+
end).to eql([])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'allows activating and deactivating clients' do
|
47
|
+
let(:active_client) { create(:client, :active) }
|
48
|
+
let(:deactive_client) { create(:client, :deactive) }
|
49
|
+
|
50
|
+
before do
|
51
|
+
allow(harvest.clients).to receive(:deactivate)
|
52
|
+
.and_return(deactive_client)
|
53
|
+
allow(harvest.clients).to receive(:activate)
|
54
|
+
.and_return(active_client)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should be active' do
|
58
|
+
expect(active_client).to be_active
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should be deactive' do
|
62
|
+
client = harvest.clients.deactivate(client)
|
63
|
+
expect(deactive_client).not_to be_active
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should reactive a client' do
|
67
|
+
client = harvest.clients.activate(client)
|
68
|
+
expect(active_client).to be_active
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'contacts' do
|
74
|
+
let(:client) { create(:client) }
|
75
|
+
let(:contact) { create(:contact, client: client) }
|
76
|
+
let(:contact_attributes) { FactoryBot.attributes_for(:contact) }
|
77
|
+
|
78
|
+
context 'allows to add contacts' do
|
79
|
+
before do
|
80
|
+
allow(harvest.contacts).to receive(:create)
|
81
|
+
.and_return(contact)
|
82
|
+
contact = harvest.contacts.create(contact_attributes)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should be true' do
|
86
|
+
expect(contact.client_id).to eql(client.id)
|
87
|
+
expect(contact.email).to eql('jane@example.com')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'allows to delete contacts' do
|
92
|
+
before do
|
93
|
+
allow(harvest.contacts).to receive(:delete).and_return([])
|
94
|
+
allow(harvest.contacts).to receive(:all).and_return([])
|
95
|
+
contact = harvest.contacts.delete(contact)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should return empty' do
|
99
|
+
expect(harvest.contacts.all.select do |e|
|
100
|
+
e.email == 'jane@example.com'
|
101
|
+
end).to eql([])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|