xero-kiwi 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Rakefile +1 -1
- data/docs/plans/attribute-dsl-refactor.md +301 -0
- data/lib/xero_kiwi/accounting/address.rb +12 -43
- data/lib/xero_kiwi/accounting/allocation.rb +7 -52
- data/lib/xero_kiwi/accounting/branding_theme.rb +9 -63
- data/lib/xero_kiwi/accounting/contact.rb +48 -134
- data/lib/xero_kiwi/accounting/contact_group.rb +7 -42
- data/lib/xero_kiwi/accounting/contact_person.rb +5 -31
- data/lib/xero_kiwi/accounting/credit_note.rb +27 -100
- data/lib/xero_kiwi/accounting/external_link.rb +3 -26
- data/lib/xero_kiwi/accounting/hydrator.rb +85 -0
- data/lib/xero_kiwi/accounting/invoice.rb +40 -127
- data/lib/xero_kiwi/accounting/line_item.rb +15 -51
- data/lib/xero_kiwi/accounting/organisation.rb +40 -117
- data/lib/xero_kiwi/accounting/overpayment.rb +23 -92
- data/lib/xero_kiwi/accounting/payment.rb +23 -92
- data/lib/xero_kiwi/accounting/payment_terms.rb +13 -22
- data/lib/xero_kiwi/accounting/phone.rb +5 -30
- data/lib/xero_kiwi/accounting/prepayment.rb +24 -94
- data/lib/xero_kiwi/accounting/resource.rb +153 -0
- data/lib/xero_kiwi/accounting/tracking_category.rb +5 -30
- data/lib/xero_kiwi/accounting/user.rb +10 -65
- data/lib/xero_kiwi/version.rb +1 -1
- data/lib/xero_kiwi.rb +2 -0
- data/llms-full.txt +74 -50
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4deb1de10e85de555f19546d2d2243135b2f95fff462e45a2cac72e8622a9e31
|
|
4
|
+
data.tar.gz: 46dfc6f3e6eeeda92546e95b3161aea9802fb401e823d0f2ca67d91bab3555d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 781f5c1cfde039f7746984185d218d2fa9e07ceb66cb660d7227291f7e45210f97f590a97cb0d24dfa7c265843c98a37b3d4fd857b41d6cd6ab7e53d1db8d646
|
|
7
|
+
data.tar.gz: 727152993b2424d6513c44947db8b75cd4b1a8734b000e390644c59154ba72804cd48cf16b36e0df299df250b1b6a5eebeace82aba0319adc924a30125f5bdc6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.1] - 2026-04-17
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Internal refactor of the accounting resource classes. Each resource now declares its fields through a shared `attribute` DSL (`lib/xero_kiwi/accounting/resource.rb`) rather than an `ATTRIBUTES` constant + hand-written `initialize`. Hydration logic (including the `/Date(ms)/` and ISO 8601 parsing previously duplicated across nine files) lives in a single `XeroKiwi::Accounting::Hydrator` module. The mixin also provides default `==` / `eql?` / `hash` (via an `identity :xxx_id` declaration for resources with a server-side primary key, structural `to_h`-based otherwise) and an ActiveRecord-style `inspect` that shows every attribute inline — nested objects collapse to a one-line reference and collections to a `[N items]` summary. No public API changes — constructor signatures and return types are preserved.
|
|
8
|
+
|
|
3
9
|
## [0.2.0] - 2026-04-15
|
|
4
10
|
|
|
5
11
|
### Added
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Attribute DSL Refactor
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Every accounting resource class in kiwi repeats the same pattern: an
|
|
6
|
+
`ATTRIBUTES` name-map constant, an `attr_reader(*ATTRIBUTES.keys)` call, and a
|
|
7
|
+
hand-written `initialize` that duplicates hydration logic across nine files.
|
|
8
|
+
`parse_time` alone is copy-pasted into every resource. Nested-object and
|
|
9
|
+
collection hydration (`Contact.new(attrs, reference: true)`,
|
|
10
|
+
`(attrs["LineItems"] || []).map { … }`) follows an identical shape in every
|
|
11
|
+
class but is written by hand each time.
|
|
12
|
+
|
|
13
|
+
This is cheap today but will bite us soon. The upcoming query work
|
|
14
|
+
(filtering/sorting/pagination) needs field-type metadata per attribute — adding
|
|
15
|
+
a second `FIELDS` constant next to `ATTRIBUTES` would double the duplication.
|
|
16
|
+
Writer support down the line will need serialisation metadata too. Both extend
|
|
17
|
+
cleanly from a single attribute declaration.
|
|
18
|
+
|
|
19
|
+
This plan extracts a small `attribute` DSL and migrates every accounting
|
|
20
|
+
resource to it. No behaviour change, no public API change — constructors keep
|
|
21
|
+
the same signature, existing specs keep passing. It's pure preparation for the
|
|
22
|
+
0.3.0 querying work (tracked separately) and for writer support later.
|
|
23
|
+
|
|
24
|
+
## Decisions locked in
|
|
25
|
+
|
|
26
|
+
- **Reader-only.** No serialisation/writer concerns in this refactor. DSL
|
|
27
|
+
naming (`attribute`, not `reader` or `field`) leaves room for it later.
|
|
28
|
+
- **No query metadata yet.** The `query: true` flag and `query_fields` map
|
|
29
|
+
come with the querying plan — not now. One concern at a time.
|
|
30
|
+
- **Public API unchanged.** `Invoice.new(hash, reference: …)` keeps working
|
|
31
|
+
identically. `to_h`, `attr_reader`s, `==`, `hash`, `inspect`, resource-specific
|
|
32
|
+
helper methods (`accounts_receivable?`, etc.) all stay.
|
|
33
|
+
- **Every accounting class migrates** — including nested value types
|
|
34
|
+
(`Address`, `Phone`, `LineItem`, `ContactPerson`, `ExternalLink`,
|
|
35
|
+
`PaymentTerm`, any others). Partial migration would leave two patterns
|
|
36
|
+
coexisting, which is worse than either alone.
|
|
37
|
+
- **Version: flagged, not decided.** Internal refactor with no public API
|
|
38
|
+
change — defensibly a patch (`0.2.1`). Could also bundle under `0.3.0` since
|
|
39
|
+
that's the version the querying work will ship under and this is its
|
|
40
|
+
foundation. Confirm before bumping.
|
|
41
|
+
|
|
42
|
+
## Design
|
|
43
|
+
|
|
44
|
+
### 1. `XeroKiwi::Accounting::Resource` mixin
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# lib/xero_kiwi/accounting/resource.rb
|
|
48
|
+
module XeroKiwi
|
|
49
|
+
module Accounting
|
|
50
|
+
module Resource
|
|
51
|
+
def self.included(base) = base.extend(ClassMethods)
|
|
52
|
+
|
|
53
|
+
module ClassMethods
|
|
54
|
+
def payload_key(key) = @payload_key = key
|
|
55
|
+
|
|
56
|
+
def attribute(name, xero:, type: :string, of: nil, hydrate: nil)
|
|
57
|
+
attributes[name] = { xero: xero, type: type, of: of, hydrate: hydrate }
|
|
58
|
+
attr_reader name
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def attributes = (@attributes ||= {})
|
|
62
|
+
|
|
63
|
+
def from_response(payload)
|
|
64
|
+
return [] if payload.nil?
|
|
65
|
+
|
|
66
|
+
items = payload[@payload_key]
|
|
67
|
+
return [] if items.nil?
|
|
68
|
+
|
|
69
|
+
items.map { |attrs| new(attrs) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(attrs, reference: false)
|
|
74
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
75
|
+
@is_reference = reference
|
|
76
|
+
|
|
77
|
+
self.class.attributes.each do |name, spec|
|
|
78
|
+
value = Hydrator.call(attrs[spec[:xero]], spec)
|
|
79
|
+
instance_variable_set("@#{name}", value)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def reference? = @is_reference
|
|
84
|
+
|
|
85
|
+
def to_h
|
|
86
|
+
self.class.attributes.keys.to_h { |k| [k, public_send(k)] }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. `XeroKiwi::Accounting::Hydrator`
|
|
94
|
+
|
|
95
|
+
Shared hydration dispatch and the single home for `parse_time`.
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# lib/xero_kiwi/accounting/hydrator.rb
|
|
99
|
+
module XeroKiwi
|
|
100
|
+
module Accounting
|
|
101
|
+
module Hydrator
|
|
102
|
+
module_function
|
|
103
|
+
|
|
104
|
+
def call(raw, spec)
|
|
105
|
+
return spec[:hydrate].call(raw) if spec[:hydrate]
|
|
106
|
+
return [] if spec[:type] == :collection && raw.nil?
|
|
107
|
+
return nil if raw.nil?
|
|
108
|
+
|
|
109
|
+
case spec[:type]
|
|
110
|
+
when :string, :enum, :guid, :bool, :decimal
|
|
111
|
+
raw
|
|
112
|
+
when :date
|
|
113
|
+
parse_time(raw)
|
|
114
|
+
when :object
|
|
115
|
+
spec[:of].new(raw, reference: true)
|
|
116
|
+
when :collection
|
|
117
|
+
raw.map { |item| spec[:of].new(item) }
|
|
118
|
+
else
|
|
119
|
+
raise ArgumentError, "unknown attribute type: #{spec[:type]}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def parse_time(value)
|
|
124
|
+
return nil if value.nil?
|
|
125
|
+
|
|
126
|
+
str = value.to_s.strip
|
|
127
|
+
return nil if str.empty?
|
|
128
|
+
|
|
129
|
+
if (match = str.match(%r{\A/Date\((\d+)([+-]\d{4})?\)/\z}))
|
|
130
|
+
Time.at(match[1].to_i / 1000.0).utc
|
|
131
|
+
else
|
|
132
|
+
str = "#{str}Z" unless str.match?(/[Zz]\z|[+-]\d{2}:?\d{2}\z/)
|
|
133
|
+
Time.iso8601(str)
|
|
134
|
+
end
|
|
135
|
+
rescue ArgumentError
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 3. Supported attribute types
|
|
144
|
+
|
|
145
|
+
| Type | Hydrates to | Notes |
|
|
146
|
+
|---------------|-------------------------------------------------|--------------------------------------------|
|
|
147
|
+
| `:string` | value as-is | default |
|
|
148
|
+
| `:enum` | value as-is | semantic marker for future validation |
|
|
149
|
+
| `:guid` | value as-is | semantic marker for future query typing |
|
|
150
|
+
| `:bool` | value as-is | |
|
|
151
|
+
| `:decimal` | value as-is | Xero returns these as numbers or strings |
|
|
152
|
+
| `:date` | `Time` via `Hydrator.parse_time` | handles `/Date(ms)/` and ISO8601 |
|
|
153
|
+
| `:object` | `spec[:of].new(raw, reference: true)` | requires `of:` |
|
|
154
|
+
| `:collection` | `raw.map { spec[:of].new(item) }`, `nil` → `[]` | requires `of:` |
|
|
155
|
+
|
|
156
|
+
Escape hatch: `hydrate: ->(raw) { … }` for one-off fields the built-in types
|
|
157
|
+
can't express. Runs before dispatch.
|
|
158
|
+
|
|
159
|
+
### 4. Before / after (example)
|
|
160
|
+
|
|
161
|
+
Invoice before — 40+ lines of `initialize`, `parse_time` method, `ATTRIBUTES`
|
|
162
|
+
constant duplicated:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
ATTRIBUTES = { invoice_id: "InvoiceID", … }.freeze
|
|
166
|
+
attr_reader(*ATTRIBUTES.keys)
|
|
167
|
+
|
|
168
|
+
def initialize(attrs, reference: false)
|
|
169
|
+
attrs = attrs.transform_keys(&:to_s)
|
|
170
|
+
@is_reference = reference
|
|
171
|
+
@invoice_id = attrs["InvoiceID"]
|
|
172
|
+
@invoice_number = attrs["InvoiceNumber"]
|
|
173
|
+
@contact = attrs["Contact"] ? Contact.new(attrs["Contact"], reference: true) : nil
|
|
174
|
+
@date = parse_time(attrs["Date"])
|
|
175
|
+
@line_items = (attrs["LineItems"] || []).map { |li| LineItem.new(li) }
|
|
176
|
+
# … 30 more lines …
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def parse_time(value)
|
|
182
|
+
# … 15 lines, duplicated in 9 files …
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
After — one declaration per field, no `initialize`, no `parse_time`:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
include Accounting::Resource
|
|
190
|
+
|
|
191
|
+
payload_key "Invoices"
|
|
192
|
+
|
|
193
|
+
attribute :invoice_id, xero: "InvoiceID", type: :guid
|
|
194
|
+
attribute :invoice_number, xero: "InvoiceNumber", type: :string
|
|
195
|
+
attribute :contact, xero: "Contact", type: :object, of: Contact
|
|
196
|
+
attribute :date, xero: "Date", type: :date
|
|
197
|
+
attribute :line_items, xero: "LineItems", type: :collection, of: LineItem
|
|
198
|
+
# …
|
|
199
|
+
|
|
200
|
+
def accounts_receivable? = type == "ACCREC"
|
|
201
|
+
def accounts_payable? = type == "ACCPAY"
|
|
202
|
+
|
|
203
|
+
def ==(other) = other.is_a?(Invoice) && other.invoice_id == invoice_id
|
|
204
|
+
alias eql? ==
|
|
205
|
+
def hash = [self.class, invoice_id].hash
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 5. Edge cases to preserve
|
|
209
|
+
|
|
210
|
+
- **Raw pass-through fields** that are currently kept as plain hashes/arrays
|
|
211
|
+
(e.g. `Invoice#invoice_addresses`, `Contact#bank_account_details`) — use
|
|
212
|
+
`type: :string` (misnomer but harmless) or the `hydrate:` escape hatch with
|
|
213
|
+
an identity lambda. Audit each during migration.
|
|
214
|
+
- **`reference: true` semantics** — nested `:object` attributes always hydrate
|
|
215
|
+
with `reference: true`; nested `:collection` attributes hydrate without it
|
|
216
|
+
(full objects). This matches today's behaviour per-file. If any class
|
|
217
|
+
currently deviates, that deviation gets a `hydrate:` escape hatch.
|
|
218
|
+
- **Resource-specific helpers** (`accounts_receivable?`, `reference?`,
|
|
219
|
+
`inspect`, `==`, `hash`) stay inline on each class.
|
|
220
|
+
- **Classes without list endpoints** (nested value types — `Address`, `Phone`,
|
|
221
|
+
etc.) use the DSL but don't call `payload_key`. `from_response` on them isn't
|
|
222
|
+
called; the method existing but unused is harmless.
|
|
223
|
+
|
|
224
|
+
## Files
|
|
225
|
+
|
|
226
|
+
### Create
|
|
227
|
+
|
|
228
|
+
- `lib/xero_kiwi/accounting/resource.rb` — the DSL mixin.
|
|
229
|
+
- `lib/xero_kiwi/accounting/hydrator.rb` — shared hydration + `parse_time`.
|
|
230
|
+
- `spec/xero_kiwi/accounting/resource_spec.rb` — DSL unit tests.
|
|
231
|
+
- `spec/xero_kiwi/accounting/hydrator_spec.rb` — hydrator unit tests.
|
|
232
|
+
|
|
233
|
+
### Modify
|
|
234
|
+
|
|
235
|
+
- `lib/xero_kiwi.rb` — `require` the new files before any accounting class.
|
|
236
|
+
- Every `lib/xero_kiwi/accounting/*.rb` — migrate to `attribute` DSL, drop
|
|
237
|
+
`ATTRIBUTES`, drop hand-written `initialize`, drop private `parse_time`.
|
|
238
|
+
Concrete list: `contact.rb`, `contact_group.rb`, `contact_person.rb`,
|
|
239
|
+
`invoice.rb`, `credit_note.rb`, `prepayment.rb`, `overpayment.rb`,
|
|
240
|
+
`payment.rb`, `user.rb`, `branding_theme.rb`, `organisation.rb`, `address.rb`,
|
|
241
|
+
`phone.rb`, `external_link.rb`, `payment_term.rb`, `line_item.rb`, and any
|
|
242
|
+
other value classes in `lib/xero_kiwi/accounting/`.
|
|
243
|
+
- `lib/xero_kiwi/version.rb` — bump (0.2.1 or 0.3.0 — confirm).
|
|
244
|
+
- `CHANGELOG.md` — **Changed** entry describing the internal refactor.
|
|
245
|
+
- `Gemfile.lock` — rebuild after version bump.
|
|
246
|
+
|
|
247
|
+
## Testing strategy
|
|
248
|
+
|
|
249
|
+
- **Hydrator unit specs:** each type dispatches correctly. `/Date(ms)/` parses.
|
|
250
|
+
ISO8601 with and without timezone parses. Empty/invalid strings return
|
|
251
|
+
`nil`. `:object` hydrates nested reference. `:collection` with `nil` →
|
|
252
|
+
`[]`, populated → `map`ped. `:collection` with `nil` + `hydrate:` runs the
|
|
253
|
+
lambda. Unknown type raises.
|
|
254
|
+
- **Resource unit specs:** declare a throwaway class inside the spec with a
|
|
255
|
+
couple of attributes of each type, hydrate a fixture hash, assert every
|
|
256
|
+
reader returns the expected value, `to_h` returns a keyed hash, `payload_key`
|
|
257
|
+
+ `from_response` parse an envelope.
|
|
258
|
+
- **Existing accounting resource specs carry the real load.** They already
|
|
259
|
+
call `Class.new(fixture_hash)` and assert every reader — they should pass
|
|
260
|
+
unchanged. A regression in the DSL surfaces as real-resource spec failures,
|
|
261
|
+
which is exactly what we want.
|
|
262
|
+
- **Client integration specs** — untouched, must still pass green.
|
|
263
|
+
|
|
264
|
+
## Verification
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
bundle install
|
|
268
|
+
bundle exec rspec # full suite green
|
|
269
|
+
bundle exec rspec spec/xero_kiwi/accounting # DSL + hydrator + every resource
|
|
270
|
+
bundle exec rspec spec/xero_kiwi/client_spec.rb # list methods unaffected
|
|
271
|
+
bundle exec rubocop # style still clean
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Smoke check in IRB against a recorded fixture or live tenant:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
require "xero_kiwi"
|
|
278
|
+
|
|
279
|
+
client = XeroKiwi::Client.new(access_token: ENV.fetch("XERO_TOKEN"))
|
|
280
|
+
invoices = client.invoices(tenant)
|
|
281
|
+
inv = invoices.first
|
|
282
|
+
|
|
283
|
+
inv.invoice_id # string
|
|
284
|
+
inv.date # Time
|
|
285
|
+
inv.contact # Accounting::Contact, reference? == true
|
|
286
|
+
inv.line_items # Array<Accounting::LineItem>
|
|
287
|
+
inv.to_h.keys # every attribute name
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Follow-up
|
|
291
|
+
|
|
292
|
+
Once this lands, the 0.3.0 querying plan picks up by:
|
|
293
|
+
|
|
294
|
+
1. Adding a `query: true` option to `attribute`.
|
|
295
|
+
2. Auto-populating `query_fields` on the class from attributes flagged
|
|
296
|
+
queryable.
|
|
297
|
+
3. Building `Query::Filter` / `Query::Order` compilers against that map.
|
|
298
|
+
4. Adding the `Page` return type, `each_*` helpers, and `modified_since`
|
|
299
|
+
support on the client.
|
|
300
|
+
|
|
301
|
+
None of which requires re-touching the resource files.
|
|
@@ -6,53 +6,22 @@ module XeroKiwi
|
|
|
6
6
|
#
|
|
7
7
|
# See: https://developer.xero.com/documentation/api/accounting/types#addresses
|
|
8
8
|
class Address
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
attr_reader(*ATTRIBUTES.keys)
|
|
23
|
-
|
|
24
|
-
def initialize(attrs)
|
|
25
|
-
attrs = attrs.transform_keys(&:to_s)
|
|
26
|
-
@address_type = attrs["AddressType"]
|
|
27
|
-
@address_line_1 = attrs["AddressLine1"]
|
|
28
|
-
@address_line_2 = attrs["AddressLine2"]
|
|
29
|
-
@address_line_3 = attrs["AddressLine3"]
|
|
30
|
-
@address_line_4 = attrs["AddressLine4"]
|
|
31
|
-
@city = attrs["City"]
|
|
32
|
-
@region = attrs["Region"]
|
|
33
|
-
@postal_code = attrs["PostalCode"]
|
|
34
|
-
@country = attrs["Country"]
|
|
35
|
-
@attention_to = attrs["AttentionTo"]
|
|
36
|
-
end
|
|
9
|
+
include Resource
|
|
10
|
+
|
|
11
|
+
attribute :address_type, xero: "AddressType"
|
|
12
|
+
attribute :address_line_1, xero: "AddressLine1"
|
|
13
|
+
attribute :address_line_2, xero: "AddressLine2"
|
|
14
|
+
attribute :address_line_3, xero: "AddressLine3"
|
|
15
|
+
attribute :address_line_4, xero: "AddressLine4"
|
|
16
|
+
attribute :city, xero: "City"
|
|
17
|
+
attribute :region, xero: "Region"
|
|
18
|
+
attribute :postal_code, xero: "PostalCode"
|
|
19
|
+
attribute :country, xero: "Country"
|
|
20
|
+
attribute :attention_to, xero: "AttentionTo"
|
|
37
21
|
|
|
38
22
|
def street? = address_type == "STREET"
|
|
39
23
|
def pobox? = address_type == "POBOX"
|
|
40
24
|
def delivery? = address_type == "DELIVERY"
|
|
41
|
-
|
|
42
|
-
def to_h
|
|
43
|
-
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def ==(other)
|
|
47
|
-
other.is_a?(Address) && to_h == other.to_h
|
|
48
|
-
end
|
|
49
|
-
alias eql? ==
|
|
50
|
-
|
|
51
|
-
def hash = to_h.hash
|
|
52
|
-
|
|
53
|
-
def inspect
|
|
54
|
-
"#<#{self.class} type=#{address_type.inspect} city=#{city.inspect} country=#{country.inspect}>"
|
|
55
|
-
end
|
|
56
25
|
end
|
|
57
26
|
end
|
|
58
27
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "time"
|
|
4
|
-
|
|
5
3
|
module XeroKiwi
|
|
6
4
|
module Accounting
|
|
7
5
|
# Represents an allocation of a credit note, prepayment, or overpayment
|
|
@@ -9,58 +7,15 @@ module XeroKiwi
|
|
|
9
7
|
#
|
|
10
8
|
# See: https://developer.xero.com/documentation/api/accounting/overpayments
|
|
11
9
|
class Allocation
|
|
12
|
-
|
|
13
|
-
allocation_id: "AllocationID",
|
|
14
|
-
amount: "Amount",
|
|
15
|
-
date: "Date",
|
|
16
|
-
invoice: "Invoice",
|
|
17
|
-
is_deleted: "IsDeleted"
|
|
18
|
-
}.freeze
|
|
19
|
-
|
|
20
|
-
attr_reader(*ATTRIBUTES.keys)
|
|
21
|
-
|
|
22
|
-
def initialize(attrs)
|
|
23
|
-
attrs = attrs.transform_keys(&:to_s)
|
|
24
|
-
@allocation_id = attrs["AllocationID"]
|
|
25
|
-
@amount = attrs["Amount"]
|
|
26
|
-
@date = parse_time(attrs["Date"])
|
|
27
|
-
@invoice = attrs["Invoice"] ? Invoice.new(attrs["Invoice"], reference: true) : nil
|
|
28
|
-
@is_deleted = attrs["IsDeleted"]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def to_h
|
|
32
|
-
ATTRIBUTES.keys.to_h { |key| [key, public_send(key)] }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def ==(other)
|
|
36
|
-
other.is_a?(Allocation) && other.allocation_id == allocation_id
|
|
37
|
-
end
|
|
38
|
-
alias eql? ==
|
|
39
|
-
|
|
40
|
-
def hash = [self.class, allocation_id].hash
|
|
41
|
-
|
|
42
|
-
def inspect
|
|
43
|
-
"#<#{self.class} allocation_id=#{allocation_id.inspect} " \
|
|
44
|
-
"amount=#{amount.inspect}>"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def parse_time(value)
|
|
50
|
-
return nil if value.nil?
|
|
10
|
+
include Resource
|
|
51
11
|
|
|
52
|
-
|
|
53
|
-
return nil if str.empty?
|
|
12
|
+
identity :allocation_id
|
|
54
13
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
rescue ArgumentError
|
|
62
|
-
nil
|
|
63
|
-
end
|
|
14
|
+
attribute :allocation_id, xero: "AllocationID", type: :guid
|
|
15
|
+
attribute :amount, xero: "Amount", type: :decimal
|
|
16
|
+
attribute :date, xero: "Date", type: :date
|
|
17
|
+
attribute :invoice, xero: "Invoice", type: :object, of: Invoice, reference: true
|
|
18
|
+
attribute :is_deleted, xero: "IsDeleted", type: :bool
|
|
64
19
|
end
|
|
65
20
|
end
|
|
66
21
|
end
|
|
@@ -1,76 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "time"
|
|
4
|
-
|
|
5
3
|
module XeroKiwi
|
|
6
4
|
module Accounting
|
|
7
5
|
# Represents a Xero Branding Theme returned by the Accounting API.
|
|
8
6
|
#
|
|
9
7
|
# See: https://developer.xero.com/documentation/api/accounting/brandingthemes
|
|
10
8
|
class BrandingTheme
|
|
11
|
-
|
|
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?
|
|
9
|
+
include Resource
|
|
61
10
|
|
|
62
|
-
|
|
63
|
-
|
|
11
|
+
payload_key "BrandingThemes"
|
|
12
|
+
identity :branding_theme_id
|
|
64
13
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
rescue ArgumentError
|
|
72
|
-
nil
|
|
73
|
-
end
|
|
14
|
+
attribute :branding_theme_id, xero: "BrandingThemeID", type: :guid
|
|
15
|
+
attribute :name, xero: "Name"
|
|
16
|
+
attribute :logo_url, xero: "LogoUrl"
|
|
17
|
+
attribute :type, xero: "Type"
|
|
18
|
+
attribute :sort_order, xero: "SortOrder"
|
|
19
|
+
attribute :created_date_utc, xero: "CreatedDateUTC", type: :date
|
|
74
20
|
end
|
|
75
21
|
end
|
|
76
22
|
end
|