rubill 0.1.2
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 +7 -0
- data/lib/rubill/base.rb +90 -0
- data/lib/rubill/bill.rb +11 -0
- data/lib/rubill/customer.rb +15 -0
- data/lib/rubill/customer_contact.rb +20 -0
- data/lib/rubill/invoice.rb +46 -0
- data/lib/rubill/query.rb +86 -0
- data/lib/rubill/received_payment.rb +32 -0
- data/lib/rubill/sent_payment.rb +32 -0
- data/lib/rubill/session.rb +103 -0
- data/lib/rubill/vendor.rb +14 -0
- data/lib/rubill.rb +58 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d9b71f0d60dd41ff424f2e915cb7ed2def75c2c1
|
4
|
+
data.tar.gz: 51aef8f5bed4e369786e51d655e9afa65ab7ea2e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f57a53a8388bbea6fd2b515bef3c30dd4e7a0675dfc3828ed541fe1aa162e4b7ce1eec365e28acf8105e672da1f5fb7de05bb2833df5633de84b81c9fc7fc6a
|
7
|
+
data.tar.gz: eb04ddf63780996914870264f0bf58a420abe1d1e88abf911684ff956aa1ecf7bbc39d03fd9866d49bb48f3cbaf8819aad7c49a00631f7d8130a1bd740e3d652
|
data/lib/rubill/base.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module Rubill
|
2
|
+
class Base
|
3
|
+
attr_accessor :remote_record
|
4
|
+
|
5
|
+
class NotFound < StandardError; end
|
6
|
+
|
7
|
+
def initialize(remote)
|
8
|
+
self.remote_record = remote
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
remote_record.send(:[], key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key, value)
|
16
|
+
remote_record.send(:[]=, key, value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.active
|
20
|
+
where([Query::Filter.new("isActive", "=", "1")])
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
remote_record[:id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
self.class.update(remote_record)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete
|
32
|
+
self.class.delete(id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.find(id)
|
36
|
+
new(Query.read(remote_class_name, id))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.create(data)
|
40
|
+
new(Query.create(remote_class_name, data.merge({entity: remote_class_name})))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.update(data)
|
44
|
+
Query.update(remote_class_name, data.merge({entity: remote_class_name}))
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.delete(id)
|
48
|
+
Query.delete(remote_class_name, id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.where(filters=[])
|
52
|
+
raise ArgumentError unless filters.is_a?(Enumerable)
|
53
|
+
raise ArgumentError if !filters.is_a?(Hash) && !filters.all? { |f| f.is_a?(Query::Filter) }
|
54
|
+
|
55
|
+
if filters.is_a?(Hash)
|
56
|
+
filters = filters.map do |field, value|
|
57
|
+
Query::Filter.new(field.to_s, "=", value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
result = []
|
62
|
+
start = 0
|
63
|
+
step = 999
|
64
|
+
loop do
|
65
|
+
chunk = Query.list(remote_class_name, start, step, filters)
|
66
|
+
|
67
|
+
if !chunk.empty?
|
68
|
+
records = chunk.map { |r| new(r) }
|
69
|
+
result += records
|
70
|
+
start += step
|
71
|
+
end
|
72
|
+
|
73
|
+
break if chunk.length < step
|
74
|
+
end
|
75
|
+
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.all
|
80
|
+
# Note: this method returns ALL of desired entity, including inactive
|
81
|
+
where([])
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def self.remote_class_name
|
87
|
+
raise NotImplementedError
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/rubill/bill.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rubill
|
2
|
+
class Customer < Base
|
3
|
+
def self.find_by_name(name)
|
4
|
+
where([Query::Filter.new("name", "=", name)]).first
|
5
|
+
end
|
6
|
+
|
7
|
+
def contacts
|
8
|
+
CustomerContact.active_by_customer(id)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.remote_class_name
|
12
|
+
"Customer"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rubill
|
2
|
+
class CustomerContact < Base
|
3
|
+
def self.find_by_customer(customer_id)
|
4
|
+
where([Query::Filter.new("customerId", "=", customer_id)])
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.active_by_customer(customer_id)
|
8
|
+
where(
|
9
|
+
[
|
10
|
+
Query::Filter.new("customerId", "=", customer_id),
|
11
|
+
Query::Filter.new("isActive", "=", "1"),
|
12
|
+
]
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.remote_class_name
|
17
|
+
"CustomerContact"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Rubill
|
2
|
+
class Invoice < Base
|
3
|
+
def amount_paid
|
4
|
+
amount - amount_due
|
5
|
+
end
|
6
|
+
|
7
|
+
def amount
|
8
|
+
remote_record[:amount]
|
9
|
+
end
|
10
|
+
|
11
|
+
def send_email(subject, body, contact_emails)
|
12
|
+
Query.execute(
|
13
|
+
"/SendInvoice.json",
|
14
|
+
{
|
15
|
+
invoiceId: id,
|
16
|
+
headers: {
|
17
|
+
subject: subject,
|
18
|
+
toEmailAddresses: contact_emails
|
19
|
+
},
|
20
|
+
content: {
|
21
|
+
body: body
|
22
|
+
}
|
23
|
+
}
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def amount_due
|
28
|
+
remote_record[:amountDue]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.line_item(amount, description, item_id)
|
32
|
+
{
|
33
|
+
entity: "InvoiceLineItem",
|
34
|
+
quantity: 1,
|
35
|
+
itemId: item_id,
|
36
|
+
# must to_f amount otherwise decimal will be converted to string in JSON
|
37
|
+
price: amount.to_f,
|
38
|
+
description: description,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.remote_class_name
|
43
|
+
"Invoice"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/rubill/query.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Rubill
|
2
|
+
class Query
|
3
|
+
attr_accessor :url
|
4
|
+
attr_accessor :options
|
5
|
+
|
6
|
+
def initialize(url, opts={})
|
7
|
+
self.url = url
|
8
|
+
self.options = opts
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.list(entity, start=0, step=999, filters=[])
|
12
|
+
execute(
|
13
|
+
"/List/#{entity}.json",
|
14
|
+
start: start,
|
15
|
+
max: step,
|
16
|
+
filters: filters.map(&:to_hash),
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.read(entity, id)
|
21
|
+
execute("/Crud/Read/#{entity}.json", id: id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.create(entity, object={})
|
25
|
+
execute("/Crud/Create/#{entity}.json", obj: object)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.update(entity, object={})
|
29
|
+
execute("/Crud/Update/#{entity}.json", obj: object)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.delete(entity, id)
|
33
|
+
execute("/Crud/Delete/#{entity}.json", id: id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.pay_bills(opts={})
|
37
|
+
execute("/PayBills.json", opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.receive_payment(opts={})
|
41
|
+
execute("/RecordARPayment.json", opts)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.send_payment(opts={})
|
45
|
+
execute("/RecordAPPayment.json", opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.upload_attachment(opts={})
|
49
|
+
execute("/UploadAttachment.json", opts)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.void_sent_payment(id)
|
53
|
+
execute("/VoidAPPayment.json", sentPayId: id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.void_received_payment(id)
|
57
|
+
execute("/VoidARPayment.json", id: id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def execute
|
61
|
+
Session.instance.execute(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.execute(url, options)
|
65
|
+
new(url, options).execute
|
66
|
+
end
|
67
|
+
|
68
|
+
class Filter
|
69
|
+
attr_accessor :field
|
70
|
+
attr_accessor :op
|
71
|
+
attr_accessor :value
|
72
|
+
|
73
|
+
def initialize(field, op, value)
|
74
|
+
self.field, self.op, self.value = field, op, value
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_hash
|
78
|
+
{
|
79
|
+
field: field,
|
80
|
+
op: op,
|
81
|
+
value: value,
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rubill
|
2
|
+
class ReceivedPayment < Base
|
3
|
+
def self.create(opts)
|
4
|
+
Query.receive_payment(opts)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.active
|
8
|
+
where([Query::Filter.new("status", "!=", "1")])
|
9
|
+
end
|
10
|
+
|
11
|
+
def void
|
12
|
+
delete
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete
|
16
|
+
self.class.delete(id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.delete(id)
|
20
|
+
# To overwrite delete method in superclass
|
21
|
+
void(id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.void(id)
|
25
|
+
Query.void_received_payment(id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.remote_class_name
|
29
|
+
"ReceivedPay"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rubill
|
2
|
+
class SentPayment < Base
|
3
|
+
def self.create(opts)
|
4
|
+
Query.send_payment(opts)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.active
|
8
|
+
where([Query::Filter.new("status", "!=", "4")])
|
9
|
+
end
|
10
|
+
|
11
|
+
def void
|
12
|
+
delete
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete
|
16
|
+
self.class.delete(id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.delete(id)
|
20
|
+
# To overwrite delete method in superclass
|
21
|
+
void(id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.void(id)
|
25
|
+
Query.void_sent_payment(id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.remote_class_name
|
29
|
+
"SentPay"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "httmultiparty"
|
2
|
+
require "json"
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Rubill
|
6
|
+
class APIError < StandardError; end
|
7
|
+
|
8
|
+
class Session
|
9
|
+
include HTTParty
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
attr_accessor :id
|
13
|
+
|
14
|
+
base_uri "https://api.bill.com/api/v2"
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
config = self.class.configuration
|
18
|
+
if missing = (!config.missing_keys.empty? && config.missing_keys)
|
19
|
+
raise "Missing key(s) in configuration: #{missing}"
|
20
|
+
end
|
21
|
+
|
22
|
+
if config.sandbox
|
23
|
+
self.class.base_uri "https://api-stage.bill.com/api/v2"
|
24
|
+
end
|
25
|
+
|
26
|
+
login
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute(query)
|
30
|
+
_post(query.url, query.options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def login
|
34
|
+
self.id = self.class.login
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.login
|
38
|
+
login_options = {
|
39
|
+
password: configuration.password,
|
40
|
+
userName: configuration.user_name,
|
41
|
+
devKey: configuration.dev_key,
|
42
|
+
orgId: configuration.org_id,
|
43
|
+
}
|
44
|
+
login = _post("/Login.json", login_options)
|
45
|
+
login[:sessionId]
|
46
|
+
end
|
47
|
+
|
48
|
+
def options(data={})
|
49
|
+
{
|
50
|
+
sessionId: id,
|
51
|
+
devKey: self.class.configuration.dev_key,
|
52
|
+
data: data.to_json,
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.default_headers
|
57
|
+
{"Content-Type" => "application/x-www-form-urlencoded"}
|
58
|
+
end
|
59
|
+
|
60
|
+
def _post(url, data, retries=0)
|
61
|
+
begin
|
62
|
+
self.class._post(url, options(data))
|
63
|
+
rescue APIError => e
|
64
|
+
if e.message =~ /Session is invalid/ && retries < 3
|
65
|
+
login
|
66
|
+
_post(url, data, retries + 1)
|
67
|
+
else
|
68
|
+
raise
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self._post(url, options)
|
74
|
+
if options.key?(:fileName)
|
75
|
+
file = StringIO.new(options.delete(:content))
|
76
|
+
end
|
77
|
+
|
78
|
+
post_options = {
|
79
|
+
body: options,
|
80
|
+
headers: default_headers,
|
81
|
+
}
|
82
|
+
|
83
|
+
post_options[:file] = file if file
|
84
|
+
|
85
|
+
if self.configuration.debug
|
86
|
+
post_options[:debug_output] = $stdout
|
87
|
+
end
|
88
|
+
|
89
|
+
response = post(url, post_options)
|
90
|
+
result = JSON.parse(response.body, symbolize_names: true)
|
91
|
+
|
92
|
+
unless result[:response_status] == 0
|
93
|
+
raise APIError.new(result[:response_data][:error_message])
|
94
|
+
end
|
95
|
+
|
96
|
+
result[:response_data]
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.configuration
|
100
|
+
Rubill::configuration
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/rubill.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Rubill
|
2
|
+
attr_writer :configuration
|
3
|
+
|
4
|
+
def self.configure(&block)
|
5
|
+
yield(configuration)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configuration
|
9
|
+
@configuration ||= Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class Configuration
|
13
|
+
attr_accessor :user_name
|
14
|
+
attr_accessor :password
|
15
|
+
attr_accessor :dev_key
|
16
|
+
attr_accessor :org_id
|
17
|
+
attr_writer :debug
|
18
|
+
attr_writer :sandbox
|
19
|
+
|
20
|
+
def required_keys
|
21
|
+
%w(user_name password dev_key org_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
required_keys.each_with_object({}) do |k, h|
|
26
|
+
h[k] = send(k.to_sym)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def missing_keys
|
31
|
+
required_keys.reject do |k|
|
32
|
+
to_hash[k]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug
|
37
|
+
@debug || false
|
38
|
+
end
|
39
|
+
|
40
|
+
def sandbox
|
41
|
+
@sandbox || false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require "rubill/session"
|
47
|
+
require "rubill/query"
|
48
|
+
require "rubill/base"
|
49
|
+
require "rubill/bill"
|
50
|
+
require "rubill/bill_payment"
|
51
|
+
require "rubill/invoice"
|
52
|
+
require "rubill/attachment"
|
53
|
+
require "rubill/sent_payment"
|
54
|
+
require "rubill/sent_bill_payment"
|
55
|
+
require "rubill/received_payment"
|
56
|
+
require "rubill/vendor"
|
57
|
+
require "rubill/customer"
|
58
|
+
require "rubill/customer_contact"
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Taber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httmultiparty
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A Ruby interface to Bill.com's API
|
70
|
+
email: andrew.e.taber@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- lib/rubill.rb
|
76
|
+
- lib/rubill/base.rb
|
77
|
+
- lib/rubill/bill.rb
|
78
|
+
- lib/rubill/customer.rb
|
79
|
+
- lib/rubill/customer_contact.rb
|
80
|
+
- lib/rubill/invoice.rb
|
81
|
+
- lib/rubill/query.rb
|
82
|
+
- lib/rubill/received_payment.rb
|
83
|
+
- lib/rubill/sent_payment.rb
|
84
|
+
- lib/rubill/session.rb
|
85
|
+
- lib/rubill/vendor.rb
|
86
|
+
homepage: http://rubygems.org/gems/rubill
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.6
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Interface with Bill.com
|
110
|
+
test_files: []
|