xero-kiwi 0.1.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 +7 -0
- data/.env.example +2 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +89 -0
- data/Rakefile +89 -0
- data/docs/accounting/address.md +54 -0
- data/docs/accounting/branding-theme.md +92 -0
- data/docs/accounting/contact-group.md +91 -0
- data/docs/accounting/contact.md +166 -0
- data/docs/accounting/credit-note.md +97 -0
- data/docs/accounting/external-link.md +33 -0
- data/docs/accounting/invoice.md +134 -0
- data/docs/accounting/organisation.md +119 -0
- data/docs/accounting/overpayment.md +94 -0
- data/docs/accounting/payment-terms.md +58 -0
- data/docs/accounting/payment.md +99 -0
- data/docs/accounting/phone.md +45 -0
- data/docs/accounting/prepayment.md +111 -0
- data/docs/accounting/user.md +109 -0
- data/docs/client.md +174 -0
- data/docs/connections.md +166 -0
- data/docs/errors.md +271 -0
- data/docs/getting-started.md +138 -0
- data/docs/oauth.md +508 -0
- data/docs/retries-and-rate-limits.md +224 -0
- data/docs/tokens.md +339 -0
- data/lib/xero_kiwi/accounting/address.rb +58 -0
- data/lib/xero_kiwi/accounting/allocation.rb +66 -0
- data/lib/xero_kiwi/accounting/branding_theme.rb +76 -0
- data/lib/xero_kiwi/accounting/contact.rb +153 -0
- data/lib/xero_kiwi/accounting/contact_group.rb +57 -0
- data/lib/xero_kiwi/accounting/contact_person.rb +45 -0
- data/lib/xero_kiwi/accounting/credit_note.rb +115 -0
- data/lib/xero_kiwi/accounting/external_link.rb +38 -0
- data/lib/xero_kiwi/accounting/invoice.rb +142 -0
- data/lib/xero_kiwi/accounting/line_item.rb +64 -0
- data/lib/xero_kiwi/accounting/organisation.rb +138 -0
- data/lib/xero_kiwi/accounting/overpayment.rb +107 -0
- data/lib/xero_kiwi/accounting/payment.rb +105 -0
- data/lib/xero_kiwi/accounting/payment_terms.rb +77 -0
- data/lib/xero_kiwi/accounting/phone.rb +46 -0
- data/lib/xero_kiwi/accounting/prepayment.rb +109 -0
- data/lib/xero_kiwi/accounting/tracking_category.rb +42 -0
- data/lib/xero_kiwi/accounting/user.rb +80 -0
- data/lib/xero_kiwi/client.rb +576 -0
- data/lib/xero_kiwi/connection.rb +78 -0
- data/lib/xero_kiwi/errors.rb +34 -0
- data/lib/xero_kiwi/identity.rb +40 -0
- data/lib/xero_kiwi/oauth/id_token.rb +102 -0
- data/lib/xero_kiwi/oauth/pkce.rb +51 -0
- data/lib/xero_kiwi/oauth.rb +232 -0
- data/lib/xero_kiwi/token.rb +99 -0
- data/lib/xero_kiwi/token_refresher.rb +53 -0
- data/lib/xero_kiwi/version.rb +5 -0
- data/lib/xero_kiwi.rb +33 -0
- data/llms-full.txt +3351 -0
- data/llms.txt +56 -0
- data/sig/xero_kiwi.rbs +4 -0
- metadata +164 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module XeroKiwi
|
|
6
|
+
module Accounting
|
|
7
|
+
# Represents a Xero Branding Theme returned by the Accounting API.
|
|
8
|
+
#
|
|
9
|
+
# See: https://developer.xero.com/documentation/api/accounting/brandingthemes
|
|
10
|
+
class BrandingTheme
|
|
11
|
+
ATTRIBUTES = {
|
|
12
|
+
branding_theme_id: "BrandingThemeID",
|
|
13
|
+
name: "Name",
|
|
14
|
+
logo_url: "LogoUrl",
|
|
15
|
+
type: "Type",
|
|
16
|
+
sort_order: "SortOrder",
|
|
17
|
+
created_date_utc: "CreatedDateUTC"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
21
|
+
|
|
22
|
+
def self.from_response(payload)
|
|
23
|
+
return [] if payload.nil?
|
|
24
|
+
|
|
25
|
+
items = payload["BrandingThemes"]
|
|
26
|
+
return [] if items.nil?
|
|
27
|
+
|
|
28
|
+
items.map { |attrs| new(attrs) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(attrs)
|
|
32
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
33
|
+
@branding_theme_id = attrs["BrandingThemeID"]
|
|
34
|
+
@name = attrs["Name"]
|
|
35
|
+
@logo_url = attrs["LogoUrl"]
|
|
36
|
+
@type = attrs["Type"]
|
|
37
|
+
@sort_order = attrs["SortOrder"]
|
|
38
|
+
@created_date_utc = parse_time(attrs["CreatedDateUTC"])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_h
|
|
42
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ==(other)
|
|
46
|
+
other.is_a?(BrandingTheme) && other.branding_theme_id == branding_theme_id
|
|
47
|
+
end
|
|
48
|
+
alias eql? ==
|
|
49
|
+
|
|
50
|
+
def hash = [self.class, branding_theme_id].hash
|
|
51
|
+
|
|
52
|
+
def inspect
|
|
53
|
+
"#<#{self.class} branding_theme_id=#{branding_theme_id.inspect} " \
|
|
54
|
+
"name=#{name.inspect} type=#{type.inspect}>"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def parse_time(value)
|
|
60
|
+
return nil if value.nil?
|
|
61
|
+
|
|
62
|
+
str = value.to_s.strip
|
|
63
|
+
return nil if str.empty?
|
|
64
|
+
|
|
65
|
+
if (match = str.match(%r{\A/Date\((\d+)([+-]\d{4})?\)/\z}))
|
|
66
|
+
Time.at(match[1].to_i / 1000.0).utc
|
|
67
|
+
else
|
|
68
|
+
str = "#{str}Z" unless str.match?(/[Zz]\z|[+-]\d{2}:?\d{2}\z/)
|
|
69
|
+
Time.iso8601(str)
|
|
70
|
+
end
|
|
71
|
+
rescue ArgumentError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module XeroKiwi
|
|
6
|
+
module Accounting
|
|
7
|
+
# Represents a Xero Contact returned by the Accounting API.
|
|
8
|
+
#
|
|
9
|
+
# See: https://developer.xero.com/documentation/api/accounting/contacts
|
|
10
|
+
class Contact
|
|
11
|
+
ATTRIBUTES = {
|
|
12
|
+
contact_id: "ContactID",
|
|
13
|
+
contact_number: "ContactNumber",
|
|
14
|
+
account_number: "AccountNumber",
|
|
15
|
+
contact_status: "ContactStatus",
|
|
16
|
+
name: "Name",
|
|
17
|
+
first_name: "FirstName",
|
|
18
|
+
last_name: "LastName",
|
|
19
|
+
email_address: "EmailAddress",
|
|
20
|
+
bank_account_details: "BankAccountDetails",
|
|
21
|
+
company_number: "CompanyNumber",
|
|
22
|
+
tax_number: "TaxNumber",
|
|
23
|
+
tax_number_type: "TaxNumberType",
|
|
24
|
+
accounts_receivable_tax_type: "AccountsReceivableTaxType",
|
|
25
|
+
accounts_payable_tax_type: "AccountsPayableTaxType",
|
|
26
|
+
addresses: "Addresses",
|
|
27
|
+
phones: "Phones",
|
|
28
|
+
is_supplier: "IsSupplier",
|
|
29
|
+
is_customer: "IsCustomer",
|
|
30
|
+
default_currency: "DefaultCurrency",
|
|
31
|
+
updated_date_utc: "UpdatedDateUTC",
|
|
32
|
+
contact_persons: "ContactPersons",
|
|
33
|
+
xero_network_key: "XeroNetworkKey",
|
|
34
|
+
merged_to_contact_id: "MergedToContactID",
|
|
35
|
+
sales_default_account_code: "SalesDefaultAccountCode",
|
|
36
|
+
purchases_default_account_code: "PurchasesDefaultAccountCode",
|
|
37
|
+
sales_tracking_categories: "SalesTrackingCategories",
|
|
38
|
+
purchases_tracking_categories: "PurchasesTrackingCategories",
|
|
39
|
+
sales_default_line_amount_type: "SalesDefaultLineAmountType",
|
|
40
|
+
purchases_default_line_amount_type: "PurchasesDefaultLineAmountType",
|
|
41
|
+
tracking_category_name: "TrackingCategoryName",
|
|
42
|
+
tracking_option_name: "TrackingOptionName",
|
|
43
|
+
payment_terms: "PaymentTerms",
|
|
44
|
+
contact_groups: "ContactGroups",
|
|
45
|
+
website: "Website",
|
|
46
|
+
branding_theme: "BrandingTheme",
|
|
47
|
+
batch_payments: "BatchPayments",
|
|
48
|
+
discount: "Discount",
|
|
49
|
+
balances: "Balances",
|
|
50
|
+
has_attachments: "HasAttachments"
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
54
|
+
|
|
55
|
+
def self.from_response(payload)
|
|
56
|
+
return [] if payload.nil?
|
|
57
|
+
|
|
58
|
+
items = payload["Contacts"]
|
|
59
|
+
return [] if items.nil?
|
|
60
|
+
|
|
61
|
+
items.map { |attrs| new(attrs) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def initialize(attrs, reference: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
65
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
66
|
+
@is_reference = reference
|
|
67
|
+
@contact_id = attrs["ContactID"]
|
|
68
|
+
@contact_number = attrs["ContactNumber"]
|
|
69
|
+
@account_number = attrs["AccountNumber"]
|
|
70
|
+
@contact_status = attrs["ContactStatus"]
|
|
71
|
+
@name = attrs["Name"]
|
|
72
|
+
@first_name = attrs["FirstName"]
|
|
73
|
+
@last_name = attrs["LastName"]
|
|
74
|
+
@email_address = attrs["EmailAddress"]
|
|
75
|
+
@bank_account_details = attrs["BankAccountDetails"]
|
|
76
|
+
@company_number = attrs["CompanyNumber"]
|
|
77
|
+
@tax_number = attrs["TaxNumber"]
|
|
78
|
+
@tax_number_type = attrs["TaxNumberType"]
|
|
79
|
+
@accounts_receivable_tax_type = attrs["AccountsReceivableTaxType"]
|
|
80
|
+
@accounts_payable_tax_type = attrs["AccountsPayableTaxType"]
|
|
81
|
+
@addresses = (attrs["Addresses"] || []).map { |a| Address.new(a) }
|
|
82
|
+
@phones = (attrs["Phones"] || []).map { |p| Phone.new(p) }
|
|
83
|
+
@is_supplier = attrs["IsSupplier"]
|
|
84
|
+
@is_customer = attrs["IsCustomer"]
|
|
85
|
+
@default_currency = attrs["DefaultCurrency"]
|
|
86
|
+
@updated_date_utc = parse_time(attrs["UpdatedDateUTC"])
|
|
87
|
+
@contact_persons = (attrs["ContactPersons"] || []).map { |cp| ContactPerson.new(cp) }
|
|
88
|
+
@xero_network_key = attrs["XeroNetworkKey"]
|
|
89
|
+
@merged_to_contact_id = attrs["MergedToContactID"]
|
|
90
|
+
@sales_default_account_code = attrs["SalesDefaultAccountCode"]
|
|
91
|
+
@purchases_default_account_code = attrs["PurchasesDefaultAccountCode"]
|
|
92
|
+
@sales_tracking_categories = (attrs["SalesTrackingCategories"] || []).map { |t| TrackingCategory.new(t) }
|
|
93
|
+
@purchases_tracking_categories = (attrs["PurchasesTrackingCategories"] || []).map { |t| TrackingCategory.new(t) }
|
|
94
|
+
@sales_default_line_amount_type = attrs["SalesDefaultLineAmountType"]
|
|
95
|
+
@purchases_default_line_amount_type = attrs["PurchasesDefaultLineAmountType"]
|
|
96
|
+
@tracking_category_name = attrs["TrackingCategoryName"]
|
|
97
|
+
@tracking_option_name = attrs["TrackingOptionName"]
|
|
98
|
+
@payment_terms = PaymentTerms.from_hash(attrs["PaymentTerms"])
|
|
99
|
+
@contact_groups = (attrs["ContactGroups"] || []).map { |cg| ContactGroup.new(cg, reference: true) }
|
|
100
|
+
@website = attrs["Website"]
|
|
101
|
+
@branding_theme = attrs["BrandingTheme"] ? BrandingTheme.new(attrs["BrandingTheme"]) : nil
|
|
102
|
+
@batch_payments = attrs["BatchPayments"]
|
|
103
|
+
@discount = attrs["Discount"]
|
|
104
|
+
@balances = attrs["Balances"]
|
|
105
|
+
@has_attachments = attrs["HasAttachments"]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def reference? = @is_reference
|
|
109
|
+
|
|
110
|
+
def supplier? = is_supplier == true
|
|
111
|
+
|
|
112
|
+
def customer? = is_customer == true
|
|
113
|
+
|
|
114
|
+
def active? = contact_status == "ACTIVE"
|
|
115
|
+
|
|
116
|
+
def archived? = contact_status == "ARCHIVED"
|
|
117
|
+
|
|
118
|
+
def to_h
|
|
119
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def ==(other)
|
|
123
|
+
other.is_a?(Contact) && other.contact_id == contact_id
|
|
124
|
+
end
|
|
125
|
+
alias eql? ==
|
|
126
|
+
|
|
127
|
+
def hash = [self.class, contact_id].hash
|
|
128
|
+
|
|
129
|
+
def inspect
|
|
130
|
+
"#<#{self.class} contact_id=#{contact_id.inspect} " \
|
|
131
|
+
"name=#{name.inspect} contact_status=#{contact_status.inspect}>"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def parse_time(value)
|
|
137
|
+
return nil if value.nil?
|
|
138
|
+
|
|
139
|
+
str = value.to_s.strip
|
|
140
|
+
return nil if str.empty?
|
|
141
|
+
|
|
142
|
+
if (match = str.match(%r{\A/Date\((\d+)([+-]\d{4})?\)/\z}))
|
|
143
|
+
Time.at(match[1].to_i / 1000.0).utc
|
|
144
|
+
else
|
|
145
|
+
str = "#{str}Z" unless str.match?(/[Zz]\z|[+-]\d{2}:?\d{2}\z/)
|
|
146
|
+
Time.iso8601(str)
|
|
147
|
+
end
|
|
148
|
+
rescue ArgumentError
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XeroKiwi
|
|
4
|
+
module Accounting
|
|
5
|
+
# Represents a Xero Contact Group returned by the Accounting API.
|
|
6
|
+
#
|
|
7
|
+
# See: https://developer.xero.com/documentation/api/accounting/contactgroups
|
|
8
|
+
class ContactGroup
|
|
9
|
+
ATTRIBUTES = {
|
|
10
|
+
contact_group_id: "ContactGroupID",
|
|
11
|
+
name: "Name",
|
|
12
|
+
status: "Status",
|
|
13
|
+
contacts: "Contacts"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
17
|
+
|
|
18
|
+
def self.from_response(payload)
|
|
19
|
+
return [] if payload.nil?
|
|
20
|
+
|
|
21
|
+
items = payload["ContactGroups"]
|
|
22
|
+
return [] if items.nil?
|
|
23
|
+
|
|
24
|
+
items.map { |attrs| new(attrs) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(attrs, reference: false)
|
|
28
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
29
|
+
@is_reference = reference
|
|
30
|
+
@contact_group_id = attrs["ContactGroupID"]
|
|
31
|
+
@name = attrs["Name"]
|
|
32
|
+
@status = attrs["Status"]
|
|
33
|
+
@contacts = (attrs["Contacts"] || []).map { |c| Contact.new(c, reference: true) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reference? = @is_reference
|
|
37
|
+
|
|
38
|
+
def active? = status == "ACTIVE"
|
|
39
|
+
|
|
40
|
+
def to_h
|
|
41
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ==(other)
|
|
45
|
+
other.is_a?(ContactGroup) && other.contact_group_id == contact_group_id
|
|
46
|
+
end
|
|
47
|
+
alias eql? ==
|
|
48
|
+
|
|
49
|
+
def hash = [self.class, contact_group_id].hash
|
|
50
|
+
|
|
51
|
+
def inspect
|
|
52
|
+
"#<#{self.class} contact_group_id=#{contact_group_id.inspect} " \
|
|
53
|
+
"name=#{name.inspect} status=#{status.inspect}>"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XeroKiwi
|
|
4
|
+
module Accounting
|
|
5
|
+
# Represents a contact person nested within a Xero Contact.
|
|
6
|
+
#
|
|
7
|
+
# See: https://developer.xero.com/documentation/api/accounting/contacts
|
|
8
|
+
class ContactPerson
|
|
9
|
+
ATTRIBUTES = {
|
|
10
|
+
first_name: "FirstName",
|
|
11
|
+
last_name: "LastName",
|
|
12
|
+
email_address: "EmailAddress",
|
|
13
|
+
include_in_emails: "IncludeInEmails"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
17
|
+
|
|
18
|
+
def initialize(attrs)
|
|
19
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
20
|
+
@first_name = attrs["FirstName"]
|
|
21
|
+
@last_name = attrs["LastName"]
|
|
22
|
+
@email_address = attrs["EmailAddress"]
|
|
23
|
+
@include_in_emails = attrs["IncludeInEmails"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def include_in_emails? = include_in_emails == true
|
|
27
|
+
|
|
28
|
+
def to_h
|
|
29
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ==(other)
|
|
33
|
+
other.is_a?(ContactPerson) && to_h == other.to_h
|
|
34
|
+
end
|
|
35
|
+
alias eql? ==
|
|
36
|
+
|
|
37
|
+
def hash = to_h.hash
|
|
38
|
+
|
|
39
|
+
def inspect
|
|
40
|
+
"#<#{self.class} first_name=#{first_name.inspect} " \
|
|
41
|
+
"last_name=#{last_name.inspect} email_address=#{email_address.inspect}>"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module XeroKiwi
|
|
6
|
+
module Accounting
|
|
7
|
+
# Represents a Xero Credit Note returned by the Accounting API.
|
|
8
|
+
#
|
|
9
|
+
# See: https://developer.xero.com/documentation/api/accounting/creditnotes
|
|
10
|
+
class CreditNote
|
|
11
|
+
ATTRIBUTES = {
|
|
12
|
+
credit_note_id: "CreditNoteID",
|
|
13
|
+
credit_note_number: "CreditNoteNumber",
|
|
14
|
+
type: "Type",
|
|
15
|
+
contact: "Contact",
|
|
16
|
+
date: "Date",
|
|
17
|
+
status: "Status",
|
|
18
|
+
line_amount_types: "LineAmountTypes",
|
|
19
|
+
line_items: "LineItems",
|
|
20
|
+
sub_total: "SubTotal",
|
|
21
|
+
total_tax: "TotalTax",
|
|
22
|
+
total: "Total",
|
|
23
|
+
cis_deduction: "CISDeduction",
|
|
24
|
+
updated_date_utc: "UpdatedDateUTC",
|
|
25
|
+
currency_code: "CurrencyCode",
|
|
26
|
+
currency_rate: "CurrencyRate",
|
|
27
|
+
fully_paid_on_date: "FullyPaidOnDate",
|
|
28
|
+
reference: "Reference",
|
|
29
|
+
sent_to_contact: "SentToContact",
|
|
30
|
+
remaining_credit: "RemainingCredit",
|
|
31
|
+
allocations: "Allocations",
|
|
32
|
+
branding_theme_id: "BrandingThemeID",
|
|
33
|
+
has_attachments: "HasAttachments"
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
37
|
+
|
|
38
|
+
def self.from_response(payload)
|
|
39
|
+
return [] if payload.nil?
|
|
40
|
+
|
|
41
|
+
items = payload["CreditNotes"]
|
|
42
|
+
return [] if items.nil?
|
|
43
|
+
|
|
44
|
+
items.map { |attrs| new(attrs) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(attrs, reference: false) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
48
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
49
|
+
@is_reference = reference
|
|
50
|
+
@credit_note_id = attrs["CreditNoteID"]
|
|
51
|
+
@credit_note_number = attrs["CreditNoteNumber"]
|
|
52
|
+
@type = attrs["Type"]
|
|
53
|
+
@contact = attrs["Contact"] ? Contact.new(attrs["Contact"], reference: true) : nil
|
|
54
|
+
@date = parse_time(attrs["Date"])
|
|
55
|
+
@status = attrs["Status"]
|
|
56
|
+
@line_amount_types = attrs["LineAmountTypes"]
|
|
57
|
+
@line_items = (attrs["LineItems"] || []).map { |li| LineItem.new(li) }
|
|
58
|
+
@sub_total = attrs["SubTotal"]
|
|
59
|
+
@total_tax = attrs["TotalTax"]
|
|
60
|
+
@total = attrs["Total"]
|
|
61
|
+
@cis_deduction = attrs["CISDeduction"]
|
|
62
|
+
@updated_date_utc = parse_time(attrs["UpdatedDateUTC"])
|
|
63
|
+
@currency_code = attrs["CurrencyCode"]
|
|
64
|
+
@currency_rate = attrs["CurrencyRate"]
|
|
65
|
+
@fully_paid_on_date = parse_time(attrs["FullyPaidOnDate"])
|
|
66
|
+
@reference = attrs["Reference"]
|
|
67
|
+
@sent_to_contact = attrs["SentToContact"]
|
|
68
|
+
@remaining_credit = attrs["RemainingCredit"]
|
|
69
|
+
@allocations = (attrs["Allocations"] || []).map { |a| Allocation.new(a) }
|
|
70
|
+
@branding_theme_id = attrs["BrandingThemeID"]
|
|
71
|
+
@has_attachments = attrs["HasAttachments"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def accounts_receivable? = type == "ACCRECCREDIT"
|
|
75
|
+
|
|
76
|
+
def reference? = @is_reference
|
|
77
|
+
|
|
78
|
+
def accounts_payable? = type == "ACCPAYCREDIT"
|
|
79
|
+
|
|
80
|
+
def to_h
|
|
81
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def ==(other)
|
|
85
|
+
other.is_a?(CreditNote) && other.credit_note_id == credit_note_id
|
|
86
|
+
end
|
|
87
|
+
alias eql? ==
|
|
88
|
+
|
|
89
|
+
def hash = [self.class, credit_note_id].hash
|
|
90
|
+
|
|
91
|
+
def inspect
|
|
92
|
+
"#<#{self.class} credit_note_id=#{credit_note_id.inspect} " \
|
|
93
|
+
"type=#{type.inspect} status=#{status.inspect} total=#{total.inspect}>"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def parse_time(value)
|
|
99
|
+
return nil if value.nil?
|
|
100
|
+
|
|
101
|
+
str = value.to_s.strip
|
|
102
|
+
return nil if str.empty?
|
|
103
|
+
|
|
104
|
+
if (match = str.match(%r{\A/Date\((\d+)([+-]\d{4})?\)/\z}))
|
|
105
|
+
Time.at(match[1].to_i / 1000.0).utc
|
|
106
|
+
else
|
|
107
|
+
str = "#{str}Z" unless str.match?(/[Zz]\z|[+-]\d{2}:?\d{2}\z/)
|
|
108
|
+
Time.iso8601(str)
|
|
109
|
+
end
|
|
110
|
+
rescue ArgumentError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XeroKiwi
|
|
4
|
+
module Accounting
|
|
5
|
+
# A Xero external link (social/web profile). Used by Organisation.
|
|
6
|
+
#
|
|
7
|
+
# See: https://developer.xero.com/documentation/api/accounting/types#externallinks
|
|
8
|
+
class ExternalLink
|
|
9
|
+
ATTRIBUTES = {
|
|
10
|
+
link_type: "LinkType",
|
|
11
|
+
url: "Url"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
15
|
+
|
|
16
|
+
def initialize(attrs)
|
|
17
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
18
|
+
@link_type = attrs["LinkType"]
|
|
19
|
+
@url = attrs["Url"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.is_a?(ExternalLink) && to_h == other.to_h
|
|
28
|
+
end
|
|
29
|
+
alias eql? ==
|
|
30
|
+
|
|
31
|
+
def hash = to_h.hash
|
|
32
|
+
|
|
33
|
+
def inspect
|
|
34
|
+
"#<#{self.class} type=#{link_type.inspect} url=#{url.inspect}>"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module XeroKiwi
|
|
6
|
+
module Accounting
|
|
7
|
+
# Represents a Xero Invoice returned by the Accounting API.
|
|
8
|
+
#
|
|
9
|
+
# See: https://developer.xero.com/documentation/api/accounting/invoices
|
|
10
|
+
class Invoice
|
|
11
|
+
ATTRIBUTES = {
|
|
12
|
+
invoice_id: "InvoiceID",
|
|
13
|
+
invoice_number: "InvoiceNumber",
|
|
14
|
+
type: "Type",
|
|
15
|
+
contact: "Contact",
|
|
16
|
+
date: "Date",
|
|
17
|
+
due_date: "DueDate",
|
|
18
|
+
status: "Status",
|
|
19
|
+
line_amount_types: "LineAmountTypes",
|
|
20
|
+
line_items: "LineItems",
|
|
21
|
+
sub_total: "SubTotal",
|
|
22
|
+
total_tax: "TotalTax",
|
|
23
|
+
total: "Total",
|
|
24
|
+
total_discount: "TotalDiscount",
|
|
25
|
+
updated_date_utc: "UpdatedDateUTC",
|
|
26
|
+
currency_code: "CurrencyCode",
|
|
27
|
+
currency_rate: "CurrencyRate",
|
|
28
|
+
reference: "Reference",
|
|
29
|
+
branding_theme_id: "BrandingThemeID",
|
|
30
|
+
url: "Url",
|
|
31
|
+
sent_to_contact: "SentToContact",
|
|
32
|
+
expected_payment_date: "ExpectedPaymentDate",
|
|
33
|
+
planned_payment_date: "PlannedPaymentDate",
|
|
34
|
+
has_attachments: "HasAttachments",
|
|
35
|
+
repeating_invoice_id: "RepeatingInvoiceID",
|
|
36
|
+
payments: "Payments",
|
|
37
|
+
credit_notes: "CreditNotes",
|
|
38
|
+
prepayments: "Prepayments",
|
|
39
|
+
overpayments: "Overpayments",
|
|
40
|
+
amount_due: "AmountDue",
|
|
41
|
+
amount_paid: "AmountPaid",
|
|
42
|
+
amount_credited: "AmountCredited",
|
|
43
|
+
cis_deduction: "CISDeduction",
|
|
44
|
+
fully_paid_on_date: "FullyPaidOnDate",
|
|
45
|
+
sales_tax_calculation_type_code: "SalesTaxCalculationTypeCode",
|
|
46
|
+
invoice_addresses: "InvoiceAddresses"
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
50
|
+
|
|
51
|
+
def self.from_response(payload)
|
|
52
|
+
return [] if payload.nil?
|
|
53
|
+
|
|
54
|
+
items = payload["Invoices"]
|
|
55
|
+
return [] if items.nil?
|
|
56
|
+
|
|
57
|
+
items.map { |attrs| new(attrs) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def initialize(attrs, reference: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
61
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
62
|
+
@is_reference = reference
|
|
63
|
+
@invoice_id = attrs["InvoiceID"]
|
|
64
|
+
@invoice_number = attrs["InvoiceNumber"]
|
|
65
|
+
@type = attrs["Type"]
|
|
66
|
+
@contact = attrs["Contact"] ? Contact.new(attrs["Contact"], reference: true) : nil
|
|
67
|
+
@date = parse_time(attrs["Date"])
|
|
68
|
+
@due_date = parse_time(attrs["DueDate"])
|
|
69
|
+
@status = attrs["Status"]
|
|
70
|
+
@line_amount_types = attrs["LineAmountTypes"]
|
|
71
|
+
@line_items = (attrs["LineItems"] || []).map { |li| LineItem.new(li) }
|
|
72
|
+
@sub_total = attrs["SubTotal"]
|
|
73
|
+
@total_tax = attrs["TotalTax"]
|
|
74
|
+
@total = attrs["Total"]
|
|
75
|
+
@total_discount = attrs["TotalDiscount"]
|
|
76
|
+
@updated_date_utc = parse_time(attrs["UpdatedDateUTC"])
|
|
77
|
+
@currency_code = attrs["CurrencyCode"]
|
|
78
|
+
@currency_rate = attrs["CurrencyRate"]
|
|
79
|
+
@reference = attrs["Reference"]
|
|
80
|
+
@branding_theme_id = attrs["BrandingThemeID"]
|
|
81
|
+
@url = attrs["Url"]
|
|
82
|
+
@sent_to_contact = attrs["SentToContact"]
|
|
83
|
+
@expected_payment_date = parse_time(attrs["ExpectedPaymentDate"])
|
|
84
|
+
@planned_payment_date = parse_time(attrs["PlannedPaymentDate"])
|
|
85
|
+
@has_attachments = attrs["HasAttachments"]
|
|
86
|
+
@repeating_invoice_id = attrs["RepeatingInvoiceID"]
|
|
87
|
+
@payments = (attrs["Payments"] || []).map { |p| Payment.new(p, reference: true) }
|
|
88
|
+
@credit_notes = (attrs["CreditNotes"] || []).map { |cn| CreditNote.new(cn, reference: true) }
|
|
89
|
+
@prepayments = (attrs["Prepayments"] || []).map { |p| Prepayment.new(p, reference: true) }
|
|
90
|
+
@overpayments = (attrs["Overpayments"] || []).map { |o| Overpayment.new(o, reference: true) }
|
|
91
|
+
@amount_due = attrs["AmountDue"]
|
|
92
|
+
@amount_paid = attrs["AmountPaid"]
|
|
93
|
+
@amount_credited = attrs["AmountCredited"]
|
|
94
|
+
@cis_deduction = attrs["CISDeduction"]
|
|
95
|
+
@fully_paid_on_date = parse_time(attrs["FullyPaidOnDate"])
|
|
96
|
+
@sales_tax_calculation_type_code = attrs["SalesTaxCalculationTypeCode"]
|
|
97
|
+
@invoice_addresses = attrs["InvoiceAddresses"] || []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def reference? = @is_reference
|
|
101
|
+
|
|
102
|
+
def accounts_receivable? = type == "ACCREC"
|
|
103
|
+
|
|
104
|
+
def accounts_payable? = type == "ACCPAY"
|
|
105
|
+
|
|
106
|
+
def to_h
|
|
107
|
+
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def ==(other)
|
|
111
|
+
other.is_a?(Invoice) && other.invoice_id == invoice_id
|
|
112
|
+
end
|
|
113
|
+
alias eql? ==
|
|
114
|
+
|
|
115
|
+
def hash = [self.class, invoice_id].hash
|
|
116
|
+
|
|
117
|
+
def inspect
|
|
118
|
+
"#<#{self.class} invoice_id=#{invoice_id.inspect} " \
|
|
119
|
+
"invoice_number=#{invoice_number.inspect} type=#{type.inspect} " \
|
|
120
|
+
"status=#{status.inspect} total=#{total.inspect}>"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def parse_time(value)
|
|
126
|
+
return nil if value.nil?
|
|
127
|
+
|
|
128
|
+
str = value.to_s.strip
|
|
129
|
+
return nil if str.empty?
|
|
130
|
+
|
|
131
|
+
if (match = str.match(%r{\A/Date\((\d+)([+-]\d{4})?\)/\z}))
|
|
132
|
+
Time.at(match[1].to_i / 1000.0).utc
|
|
133
|
+
else
|
|
134
|
+
str = "#{str}Z" unless str.match?(/[Zz]\z|[+-]\d{2}:?\d{2}\z/)
|
|
135
|
+
Time.iso8601(str)
|
|
136
|
+
end
|
|
137
|
+
rescue ArgumentError
|
|
138
|
+
nil
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|