Skip to content

Commit b4104a2

Browse files
committed
Add command to list invoices.
1 parent 077753c commit b4104a2

2 files changed

Lines changed: 91 additions & 3 deletions

File tree

exe/fa

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,69 @@ module Freeagent
132132
end
133133
end
134134

135+
class Invoices < Thor
136+
no_commands do
137+
def api
138+
@api ||= API.new
139+
end
140+
141+
def contacts
142+
@contacts ||= Hash.new {|cache, url| cache[url] = api.contact url.split('/').last }
143+
end
144+
145+
def projects
146+
@projects ||= Hash.new {|cache, url| cache[url] = api.project url.split('/').last }
147+
end
148+
149+
def human invoice
150+
[
151+
Date::parse(invoice.dated_on).strftime('%a %d %b %Y'),
152+
Date::parse(invoice.due_on).strftime('%a %d %b %Y'),
153+
invoice.contact_name,
154+
projects[invoice.project].name,
155+
invoice.reference,
156+
invoice.total_value,
157+
invoice.net_value,
158+
].join("\t")
159+
end
160+
end
161+
162+
STATUSES = %i[ all recent_open_or_overdue open overdue open_or_overdue draft paid scheduled_to_email thank_you_emails reminder_emails ]
163+
164+
desc 'list [-C CONTACT] [-P PROJECT] [-S STATUS] [-f FROM] [-t TO]', "List invoices"
165+
method_option :contact, aliases: '-C', type: :string, default: nil, desc: "Limit to a contact, identified by name or email"
166+
method_option :project, aliases: '-P', type: :string, default: nil, desc: "Limit to a project, identified by name"
167+
method_option :status, aliases: '-S', type: :string, default: nil, desc: "Invoices that match a status, one of: #{STATUSES.join(', ')}"
168+
method_option :from, aliases: '-f', type: :string, default: nil, desc: 'Date to list invoices from'
169+
method_option :to, aliases: '-t', type: :string, default: nil, desc: 'Date to list invoices to'
170+
method_option :format, type: :string, default: 'human', desc: 'Output format, one of human or json'
171+
def list
172+
from = options[:from].nil? ? nil : Date::parse(options[:from])
173+
to = options[:to].nil? ? nil : Date::parse(options[:to])
174+
status = options[:status].nil? ? nil : match(:status, STATUSES, options[:status], :to_s)
175+
contact = options[:contact].nil? ? nil : match(:contact, api.contacts, options[:contact], :first_name, :last_name, :organisation_name, :email, :billing_email)
176+
project = options[:project].nil? ? nil : match(:project, api.projects, options[:project], :name)
177+
178+
contacts[contact.url] = contact unless contact.nil?
179+
projects[project.url] = project unless project.nil?
180+
api.invoices(contact: contact, project: project, view: status).select do |invoice|
181+
from.nil? || Date::parse(invoice.dated_on) >= from
182+
end.each do |invoice|
183+
puts case options[:format].downcase
184+
when 'human'; human(invoice)
185+
when 'json'; invoice.to_json
186+
else raise ArgumentError, "unknown output format #{options[:format]}"
187+
end
188+
end
189+
end
190+
end
191+
135192
class Command < Thor
136193
desc "timeslips", "Create, read, update and delete timeslips"
137194
subcommand "timeslips", Timeslips
195+
196+
desc "invoices", "Create, read, update and delete invoices"
197+
subcommand "invoices", Invoices
138198
end
139199
end
140200

lib/freeagent.rb

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def authorize
6363
def get *parts, **params
6464
relatives = parts.map(&:to_s).join('/')
6565
uri = URI.parse(relatives)
66-
puts "GET #{uri}"
66+
STDERR.puts "GET #{uri}#{params.any? ? '?' + URI.encode_www_form(params) : ''}"
6767
res = @token.get(uri, params: params, headers: {'Accept': 'application/json'})
6868
data = res.parsed
6969
if res.headers.include? 'Link'
@@ -97,15 +97,15 @@ def post *parts, **params
9797
data = parts.pop
9898
relatives = parts.map(&:to_s).join('/')
9999
uri = URI.parse(relatives)
100-
puts "POST #{uri}"
100+
STDERR.puts "POST #{uri}"
101101
body = data.to_json
102102
@token.post(uri, body: body, params: params, headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}).parsed
103103
end
104104

105105
def delete *parts, **params
106106
relatives = parts.map(&:to_s).join('/')
107107
uri = URI.parse(relatives)
108-
puts "DELETE #{uri}"
108+
STDERR.puts "DELETE #{uri}"
109109
@token.delete(uri, params: params, headers: {'Accept': 'application/json'})
110110
end
111111

@@ -172,6 +172,21 @@ def delete_timeslip timeslip
172172
delete('timeslips', id)
173173
end
174174

175+
def invoices contact: nil, project: nil, view: nil, updated_since: nil, sort: nil
176+
params = [
177+
[:contact, contact && contact.url],
178+
[:project, project && project.url],
179+
[:view, view && view.to_s],
180+
[:updated_since, updated_since && updated_since.to_s],
181+
[:sort, sort && sort.to_s],
182+
].reject {|k, v| v.nil? }.to_h
183+
get_pages('invoices', **params).flat_map(&:invoices)
184+
end
185+
186+
def invoice id
187+
get('invoices', id).invoice
188+
end
189+
175190
def users
176191
get_pages('users').flat_map(&:users)
177192
end
@@ -180,6 +195,19 @@ def user id
180195
get('users', id).user
181196
end
182197

198+
def contacts view: nil, updated_since: nil, sort: nil
199+
params = [
200+
[:view, view && view.to_s],
201+
[:updated_since, updated_since && updated_since.to_s],
202+
[:sort, sort && sort.to_s],
203+
].reject {|k, v| v.nil? }.to_h
204+
get_pages('contacts', **params).flat_map(&:contacts)
205+
end
206+
207+
def contact
208+
get('contacts', id).contact
209+
end
210+
183211
def profile year, user
184212
get('payroll_profiles', year, user: user)
185213
end

0 commit comments

Comments
 (0)