Skip to content

Commit

Permalink
Merge 3aaebba into 2285cbd
Browse files Browse the repository at this point in the history
  • Loading branch information
rept committed Oct 15, 2015
2 parents 2285cbd + 3aaebba commit 0113c63
Show file tree
Hide file tree
Showing 32 changed files with 516 additions and 162 deletions.
11 changes: 10 additions & 1 deletion lib/zoho_invoice.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/object'
require 'faraday'
require 'faraday_middleware'
require 'nokogiri'
Expand All @@ -14,10 +15,18 @@
require 'zoho_invoice/client'
require 'zoho_invoice/version'
require 'zoho_invoice/base'
require 'zoho_invoice/currency'
# Load embeds
require 'zoho_invoice/line_item'
require 'zoho_invoice/custom_field'
require 'zoho_invoice/address'
# Load standalone
require 'zoho_invoice/customer'
require 'zoho_invoice/estimate'
require 'zoho_invoice/organization'
require 'zoho_invoice/item'
require 'zoho_invoice/invoice_item'
require 'zoho_invoice/invoice'
require 'zoho_invoice/tax'

module ZohoInvoice
class << self
Expand Down
18 changes: 18 additions & 0 deletions lib/zoho_invoice/address.rb
@@ -0,0 +1,18 @@
module ZohoInvoice
class Address < Base

READ_ATTRIBUTES = [
:address,
:city,
:state,
:zip,
:country,
:fax
]

CREATE_UPDATE_ATTRIBUTES = READ_ATTRIBUTES

define_object_attrs(*READ_ATTRIBUTES)

end
end
2 changes: 1 addition & 1 deletion lib/zoho_invoice/auth_token.rb
Expand Up @@ -13,7 +13,7 @@ def self.generate_authtoken(email_id, password)
:get,
'/apiauthtoken/nb/create',
{
:SCOPE => 'invoiceapi',
:SCOPE => ZohoInvoice.scope,
:EMAIL_ID => email_id,
:PASSWORD => password
}
Expand Down
137 changes: 80 additions & 57 deletions lib/zoho_invoice/base.rb
Expand Up @@ -24,25 +24,12 @@ def self.create(client, options = {})
self.new(client, options).save
end

# TODO need to build a class that is something like ActiveRecord::Relation
# TODO need to be able to handle associations when hydrating objects
#
def self.search(client, input_text, options = {})
result_hash = client.get("/api/view/search/#{self.to_s.split('::').last.downcase}s", :searchtext => input_text).body
objects_to_hydrate = result_hash['Response']["#{self.to_s.split('::').last}s"]["#{self.to_s.split('::').last}"]
self.process_objects(client, objects_to_hydrate)
rescue Faraday::Error::ClientError => e
if e.response[:body]
raise ZohoInvoice::Error::ClientError.from_response(e.response)
end
end

def initialize(client, options = {})
@client = client

# Assign all of the single attribtues
#
if !self.attributes.empty?
if !self.attributes.blank?
(self.attributes & options.keys).each do |attribute|
self.send("#{attribute}=", options[attribute])
end
Expand Down Expand Up @@ -76,29 +63,33 @@ def attributes
# TODO Determining the resource to use will need to change
#
def save

klass_name = self.class.to_s.split('::').last

action = 'create'
action = 'update' if !send("#{klass_name.downcase}_id").nil?

result = client.post("/api/#{klass_name.downcase + 's'}/#{action}", :XMLString => self.to_xml)

if action == 'create' && !result.body.nil? && !result.body['Response'][klass_name].nil?
self.send("#{klass_name.downcase}_id=", result.body['Response'][klass_name]["#{klass_name}ID"])
invoice_id = send("#{klass_name.downcase}_id")
if(invoice_id.blank?)
result = client.post("/api/v3/#{klass_name.downcase + 's'}", :JSONString => self.to_json)
else
result = client.put("/api/v3/#{klass_name.downcase + 's'}/#{invoice_id}", :JSONString => self.to_json)
end
if invoice_id.blank? && !result.env.blank? && !result.env.body.blank? && !result.env.body[klass_name.downcase].blank?
result.env.body[klass_name.downcase].each do |elem|
if self.attributes.include?(elem[0].to_sym)
self.send("#{elem[0].to_s}=", elem[1])
end
end
end

self
rescue Faraday::Error::ClientError => e
if e.response[:body]
if e.response && e.response[:body]
raise ZohoInvoice::Error::ClientError.from_response(e.response)
end
end

# This needs to be a Nokogiri::XML::Builder
#
def to_xml(*args)
build_attributes.to_xml(*args)
def to_json(*args)
to_hash(*args).to_json
end

def to_hash(*args)
build_attributes("#{self.class}".constantize::CREATE_UPDATE_ATTRIBUTES)["#{self.class.to_s.split('::').last}"].deep_symbolize_keys
end

def self.create_attributes(attrs)
Expand All @@ -124,65 +115,97 @@ def camel_case(str)
self.class.camel_case(str)
end

def build_attributes
Nokogiri::XML::Builder.new do |xml|
xml.send("#{self.class.to_s.split('::').last}") {
self.attributes.each do |attr|
vals = self.send(attr)
if !vals.nil? && !vals.is_a?(Array)
xml.send("#{camel_case(attr.to_s)}_", self.send(attr))
end
def build_attributes(attrs)
h = {}
attrs.each do |attr|
vals = self.send(attr)
if(vals.is_a?(Array) || vals.is_a?(Hash) || vals.is_a?(String))
h["#{attr.to_s}"] = stringify_object_values(vals) unless(vals.blank?)
else
h["#{attr.to_s}"] = stringify_object_values(vals) unless(vals.nil?)
end
end
self.reflections.each do |refl|
refl_val = self.send(refl)
if !refl_val.blank?
refl_a = []
refl_val.each {|r| refl_a << r}
h[refl] = refl_a
end
end
g = {}
g[self.class.to_s.split('::').last] = h
g
end

def stringify_object_values(obj)
return obj if obj.is_a?(String)
return(obj.to_s.gsub(/[`~!@#$%^&*()+=\\|\[{\]}'";:\/?><]/, ' ').squeeze(' ').strip) unless(obj.is_a?(Array) || obj.is_a?(Hash))
res = nil
if(obj.is_a?(Array))
res = []
obj.each do |elt|
if(elt.is_a?(Array) || elt.is_a?(Hash) || elt.is_a?(String))
res << stringify_object_values(elt) unless(elt.blank?)
else
res << stringify_object_values(elt) unless(elt.nil?)
end
self.reflections.each do |refl|
if !refl.empty?
xml.send(camel_case(refl.to_s)) {
self.send(refl).each { |x| xml << x.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION) }
}
end
end
elsif(obj.is_a?(Hash))
res = {}
obj.each do |key, elt|
if(elt.is_a?(Array) || elt.is_a?(Hash) || elt.is_a?(String))
res[key] = stringify_object_values(elt) unless(elt.blank?)
else
res[key] = stringify_object_values(elt) unless(elt.nil?)
end
}
end
end
res
end

private

def self.retrieve(client, url)
klass = self.to_s.split('::').last
def self.retrieve(client, url, plural = true)
klass_name = self.to_s.split('::').last.downcase
klass_name = klass_name.pluralize if(plural)
page = 1
query = {}
objects_to_hydrate = []

begin
result_hash = client.get(url, query).body
potential_objects = result_hash['Response'][klass + 's']
potential_objects = result_hash

if potential_objects
potential_objects = potential_objects[klass]
potential_objects = potential_objects[klass_name]
if potential_objects.is_a? Hash
potential_objects = [potential_objects]
end
objects_to_hydrate += potential_objects
end

page_context = result_hash['Response']['PageContext']
page_context = result_hash['page_context']
if page_context
num_pages = page_context['Total_Pages'].to_i
page += 1
query = { :page => page }
has_more_page = page_context['has_more_page']
if(has_more_page)
page += 1
query = { :page => page }
end
else
num_pages = nil
has_more_page = false
end
end while num_pages && page <= num_pages
end while has_more_page

self.process_objects(client, objects_to_hydrate)
rescue Faraday::Error::ClientError => e
if e.response[:body]
if e.response && e.response[:body]
raise ZohoInvoice::Error::ClientError.from_response(e.response)
end
end

def self.process_objects(client, objects_to_hydrate)
if objects_to_hydrate.nil?
if objects_to_hydrate.blank?
return []
else
if objects_to_hydrate.is_a?(Hash) #Convert hash to array if only a single object is returned
Expand All @@ -191,7 +214,7 @@ def self.process_objects(client, objects_to_hydrate)
objects_to_hydrate.map do |result|
new_hash = {}
result.each do |key, value|
new_hash[key.to_underscore.to_sym] = value if !value.is_a?(Hash) && !value.is_a?(Array)
new_hash[key.to_underscore.to_sym] = value
end
self.new(client, new_hash)
end
Expand Down
24 changes: 18 additions & 6 deletions lib/zoho_invoice/client.rb
Expand Up @@ -6,7 +6,8 @@ class Client

RESOURCES = [
:customers,
:invoices
:invoices,
:organizations
]

RESOURCES.each do |resource|
Expand All @@ -17,7 +18,16 @@ def initialize(options = {})
ZohoInvoice::Configurable.keys.each do |key|
instance_variable_set(:"@#{key}", options[key] || ZohoInvoice.instance_variable_get(:"@#{key}"))
end
@client_options = ZohoInvoice.instance_variable_get(:'@client_options').merge(options[:client_options] || {})
@client_options = ZohoInvoice.instance_variable_get(:'@client_options').merge(options[:client_options] || {})
@organization_id = @client_options.delete(:organization_id)
end

def organization=(org)
@organization_id = if org.class == Organization
org.organization_id
else
org.to_s
end
end

def get(path, params={})
Expand All @@ -28,6 +38,10 @@ def post(path, params={})
request(:post, path, credentials.merge(params))
end

def put(path, params={})
request(:put, path, credentials.merge(params))
end

def request(verb, path, params={})
connection.send(verb, path, params)
end
Expand All @@ -37,15 +51,13 @@ def request(verb, path, params={})
def connection
@connection ||= Faraday.new(@client_options) do |c|
c.use Faraday::Response::RaiseError

c.request :multipart
c.request :url_encoded

c.response :xml, :content_type => /\bxml$/

c.response :json, :content_type => /\bjson$/
c.adapter Faraday.default_adapter
end
end

end

end
7 changes: 4 additions & 3 deletions lib/zoho_invoice/configurable.rb
Expand Up @@ -39,9 +39,10 @@ def config_hash

def credentials
{
:authtoken => @authtoken,
:scope => @scope,
:apikey => @apikey
:authtoken => @authtoken,
:scope => @scope,
:apikey => @apikey,
:organization_id => @organization_id
}
end

Expand Down
48 changes: 37 additions & 11 deletions lib/zoho_invoice/contact.rb
Expand Up @@ -3,19 +3,45 @@
module ZohoInvoice
class Contact < Base

define_object_attrs :salutation,
:first_name,
:last_name,
:e_mail,
:phone,
:mobile

def self.create(client, options = {})
raise ZohoInvoice::ActionNotSupportedError
READ_ATTRIBUTES = [
:contact_id,
:contact_name,
:company_name,
:website,
:currency_id,
:first_name,
:last_name,
:address,
:email,
:phone,
:mobile,
:contact_type,
:billing_address,
:shipping_address,
:contact_persons,
:notes,
:created_time,
:last_modified_time,
:primary_contact_id,
:payment_terms,
:payment_terms_label,
:status,
:custom_fields
]

CREATE_UPDATE_ATTRIBUTES = READ_ATTRIBUTES - [:contact_id]

define_object_attrs(*READ_ATTRIBUTES)

has_many :custom_fields


def self.all(client)
retrieve(client, '/api/v3/contacts')
end

def save
raise ZohoInvoice::ActionNotSupportedError
def self.find(client, id, options={})
retrieve(client, "/api/v3/contacts/#{id}", false)
end

end
Expand Down

0 comments on commit 0113c63

Please sign in to comment.