Permalink
Browse files

Starting invoices

  • Loading branch information...
1 parent ea5aa68 commit 2f497ebcaf7a248da0f2910e195bbf92be79b623 @zmoazeni zmoazeni committed Jun 23, 2011
View
@@ -9,4 +9,4 @@ gem "vcr"
gem 'jeweler', :require => false
gem 'httparty'
gem 'hashie', '~> 1'
-gem 'json'
+gem 'json'
View
@@ -37,4 +37,7 @@ task 'clean_remote' do
require 'harvested'
require "spec/support/harvested_helpers"
HarvestedHelpers.clean_remote
+end
+
+task :foo do
end
@@ -0,0 +1,26 @@
+module Harvest
+ module API
+ class InvoiceCategories < Base
+ api_model Harvest::InvoiceCategory
+ include Harvest::Behavior::Crud
+
+ def find(*)
+ raise "find is unsupported for InvoiceCategories"
+ end
+
+ def create(model)
+ model = api_model.wrap(model)
+ response = request(:post, credentials, "#{api_model.api_path}", :body => model.to_json)
+ id = response.headers["location"].match(/\/.*\/(\d+)/)[1]
+ all.detect {|c| c.id == id.to_i }
+ end
+
+ def update(model, user = nil)
+ model = api_model.wrap(model)
+ request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => model.to_json, :query => of_user_query(user))
+ all.detect {|c| c.id == model.id }
+ end
+
+ end
+ end
+end
@@ -0,0 +1,8 @@
+module Harvest
+ module API
+ class Invoices < Base
+ api_model Harvest::Invoice
+ include Harvest::Behavior::Crud
+ end
+ end
+end
View
@@ -254,5 +254,13 @@ def time
def reports
@reports ||= Harvest::API::Reports.new(credentials)
end
+
+ def invoice_categories
+ @invoice_categories ||= Harvest::API::InvoiceCategories.new(credentials)
+ end
+
+ def invoices
+ @invoices ||= Harvest::API::Invoices.new(credentials)
+ end
end
end
@@ -36,7 +36,8 @@ def create(model)
# @param [Harvest::BaseModel] model the model you want to update
# @return [Harvest::BaseModel] the created model depending on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
def update(model, user = nil)
- request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => api_model.wrap(model).to_json, :query => of_user_query(user))
+ model = api_model.wrap(model)
+ request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => model.to_json, :query => of_user_query(user))
find(model.id)
end
View
@@ -0,0 +1,89 @@
+module Harvest
+ class Invoice < Hashie::Dash
+ include Harvest::Model
+
+ attr_reader :line_items
+
+ property :id
+ property :subject
+ property :number
+ property :created_at
+ property :updated_at
+ property :issued_at
+ property :due_at
+ property :due_at_human_format
+ property :due_amount
+ property :notes
+ property :recurring_invoice_id
+ property :period_start
+ property :period_end
+ property :discount
+ property :discount_amount
+ property :client_key
+ property :amount
+ property :tax
+ property :tax2
+ property :tax_amount
+ property :tax2_amount
+ property :csv_line_items
+ property :client_id
+ property :estimate_id
+ property :purchase_order
+ property :retainer_id
+ property :currency
+ property :state
+ property :kind
+ property :import_hours
+ property :import_expenses
+
+ def self.json_root; "doc"; end
+ # skip_json_root true
+
+ def initialize(args = {})
+ @line_items = []
+ args = args.stringify_keys
+ self.line_items = args.delete("csv_line_items")
+ self.line_items = args.delete("line_items")
+ super
+ end
+
+ def line_items=(raw_or_rich)
+ unless raw_or_rich.nil?
+ @line_items = case raw_or_rich
+ when String
+ @line_items = decode_csv(raw_or_rich).map {|row| Harvest::LineItem.new(row) }
+ else
+ raw_or_rich
+ end
+ end
+ end
+
+ # def as_json(*options)
+ # json = super(*options)
+ # json[json_root]["csv_line_items"] = encode_csv(@line_items)
+ # json
+ # end
+
+ private
+ def decode_csv(string)
+ csv = CSV.parse(string)
+ headers = csv.shift
+ csv.map! {|row| headers.zip(row) }
+ csv.map {|row| row.inject({}) {|h, tuple| h.update(tuple[0] => tuple[1]) } }
+ end
+
+ def encode_csv(line_items)
+ if line_items.empty?
+ ""
+ else
+ CSV.generate do |csv|
+ header = %w(kind description quantity unit_price amount taxed taxed2 project_id)
+ csv << header
+ line_items.each do |item|
+ csv << header.inject([]) {|row, attr| row << item[attr] }
+ end
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,18 @@
+module Harvest
+ class InvoiceCategory < Hashie::Dash
+ include Harvest::Model
+
+ api_path '/invoice_item_categories'
+ def self.json_root; "category"; end
+
+ property :id
+ property :name
+ property :created_at
+ property :updated_at
+ property :use_as_expense
+ property :use_as_service
+
+ alias_method :use_as_expense?, :use_as_expense
+ alias_method :use_as_service?, :use_as_service
+ end
+end
View
@@ -0,0 +1,12 @@
+module Harvest
+ class LineItem < Hashie::Dash
+ property :kind
+ property :description
+ property :quantity
+ property :unit_price
+ property :amount
+ property :taxed
+ property :taxed2
+ property :project_id
+ end
+end
View
@@ -4,6 +4,7 @@
require 'hashie'
require 'json'
require 'time'
+require 'csv'
require 'ext/array'
require 'ext/hash'
@@ -18,8 +19,8 @@
require 'harvest/base'
%w(crud activatable).each {|a| require "harvest/behavior/#{a}"}
-%w(model client contact project task user rate_limit_status task_assignment user_assignment expense_category expense time_entry).each {|a| require "harvest/#{a}"}
-%w(base account clients contacts projects tasks users task_assignments user_assignments expense_categories expenses time reports).each {|a| require "harvest/api/#{a}"}
+%w(model client contact project task user rate_limit_status task_assignment user_assignment expense_category expense time_entry invoice_category line_item invoice).each {|a| require "harvest/#{a}"}
+%w(base account clients contacts projects tasks users task_assignments user_assignments expense_categories expenses time reports invoice_categories invoices).each {|a| require "harvest/api/#{a}"}
module Harvest
VERSION = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'VERSION'))).strip
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe 'harvest invoices' do
+ it 'allows adding, updating and removing categories' do
+ cassette('invoice1') do
+ cat = harvest.invoice_categories.create("name" => "New Category")
+ cat.name.should == "New Category"
+
+ cat.name = "Updated Category"
+ cat = harvest.invoice_categories.update(cat)
+ cat.name.should == "Updated Category"
+
+ harvest.invoice_categories.delete(cat)
+ harvest.invoice_categories.all.select {|c| c.name == "Updated Category" }.should == []
+ end
+ end
+
+ it 'allows adding, updating and removing invoices' do
+ cassette('invoice2') do
+ client = harvest.clients.create("name" => "Frannie's Factory")
+ project = harvest.projects.create("name" => "Invoiced Project1", "client_id" => client.id)
+
+ # invoice = harvest.invoices.create(
+ invoice = Harvest::Invoice.new(
+ "subject" => "Invoice for Frannie's Widgets",
+ "client_id" => client.id,
+ "issued_at" => "2008-02-06",
+ "due_at" => "2008-02-06",
+ "due_at_human_format" => "upon receipt",
+
+ "currency" => "United States Dollars - USD",
+ "number" => 1000,
+ "notes" => "Some notes go here",
+ "period_end" => "2008-03-31",
+ "period_start" => "2007-06-26",
+ "state" => "draft",
+ "purchase_order" => nil,
+ "tax" => nil,
+ "tax2" => nil,
+ "kind" => "free_form",
+ "import_hours" => "no",
+ "import_expenses" => "no"
+ # "line_items" => [Harvest::LineItem.new("kind" => "Service", "description" => "One item", "quantity" => 200, "unit_price" => "12.00")]
+ )
+ p invoice.to_json
+ invoice = harvest.invoices.create(invoice)
+
+ invoice.subject.should == "Invoice for Frannie's Widgets"
+ invoice.amount.should == "2400.0"
+ invoice.line_items.size.should == 1
+
+ invoice.subject = "Updated Invoice for Frannie"
+ invoice.line_items << Harvest::LineItem.new("kind" => "Service", "description" => "Two item", "quantity" => 10, "unit_price" => "2.00", "amount" => "20.0")
+
+ invoice = harvest.invoices.update(invoice)
+ invoice.subject.should == "Updated Invoice for Frannie"
+ invoice.amount.should == "2420.0"
+ invoice.line_items.size.should == 2
+
+ harvest.invoices.delete(invoice)
+ harvest.invoices.all.select {|p| p.number == "1000"}.should == []
+ end
+ end
+end
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Harvest::Invoice do
+ context 'line_items' do
+ it 'parses csv into objects' do
+ invoice = Harvest::Invoice.new(:line_items => "kind,description,quantity,unit_price,amount,taxed,taxed2,project_id\nService,Abc,200,12.00,2400.0,false,false,100\nService,def,1.00,20.00,20.0,false,false,101\n")
+ invoice.line_items.count.should == 2
+ line_item = invoice.line_items.first
+
+ line_item.kind.should == "Service"
+ line_item.project_id.should == "100"
+ end
+
+ it 'parses csv into objects w/o projects' do
+ invoice = Harvest::Invoice.new(:line_items => "kind,description,quantity,unit_price,amount,taxed,taxed2,project_id\nService,Abc,200,12.00,2400.0,false,false,\nService,def,1.00,20.00,20.0,false,false,\n")
+ invoice.line_items.count.should == 2
+ line_item = invoice.line_items.first
+
+ line_item.kind.should == "Service"
+ line_item.description.should == "Abc"
+ end
+
+ it 'parses empty strings' do
+ invoice = Harvest::Invoice.new(:line_items => "")
+ invoice.line_items.should == []
+ end
+
+ it 'accepts rich objects' do
+ Harvest::Invoice.new(:line_items => [Harvest::LineItem.new(:kind => "Service")]).line_items.count.should == 1
+ Harvest::Invoice.new(:line_items => Harvest::LineItem.new(:kind => "Service")).line_items.count.should == 1
+ end
+
+ it 'accepts nil' do
+ Harvest::Invoice.new(:line_items => nil).line_items.should == []
+ end
+ end
+
+ context "as_json" do
+ it 'encodes line items csv' do
+ invoice = Harvest::Invoice.new(:line_items => "kind,description,quantity,unit_price,amount,taxed,taxed2,project_id\nService,Abc,200,12.00,2400.0,false,false,\nService,def,1.00,20.00,20.0,false,false,\n")
+ invoice.line_items.count.should == 2
+ invoice.line_items.first.kind.should == "Service"
+
+ invoice.as_json["doc"]["csv_line_items"].should == "kind,description,quantity,unit_price,amount,taxed,taxed2,project_id\nService,Abc,200,12.00,2400.0,false,false,\nService,def,1.00,20.00,20.0,false,false,\n"
+ end
+ end
+end

0 comments on commit 2f497eb

Please sign in to comment.