pacioli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/config.ru +7 -0
  8. data/lib/generators/active_record/pacioli_generator.rb +30 -0
  9. data/lib/generators/active_record/templates/migration.rb +60 -0
  10. data/lib/pacioli.rb +34 -0
  11. data/lib/pacioli/account.rb +25 -0
  12. data/lib/pacioli/asset_account.rb +9 -0
  13. data/lib/pacioli/company.rb +91 -0
  14. data/lib/pacioli/credit.rb +5 -0
  15. data/lib/pacioli/customer.rb +31 -0
  16. data/lib/pacioli/debit.rb +5 -0
  17. data/lib/pacioli/equity_account.rb +9 -0
  18. data/lib/pacioli/exception.rb +4 -0
  19. data/lib/pacioli/expense_account.rb +9 -0
  20. data/lib/pacioli/income_account.rb +9 -0
  21. data/lib/pacioli/journal_entry.rb +61 -0
  22. data/lib/pacioli/liability_account.rb +9 -0
  23. data/lib/pacioli/posting_rule.rb +34 -0
  24. data/lib/pacioli/transaction.rb +7 -0
  25. data/lib/pacioli/validations/journal_entry_validator.rb +17 -0
  26. data/lib/pacioli/validations/posting_rule_validator.rb +15 -0
  27. data/lib/pacioli/version.rb +3 -0
  28. data/pacioli.gemspec +25 -0
  29. data/spec/internal/config/database.yml +3 -0
  30. data/spec/internal/config/routes.rb +3 -0
  31. data/spec/internal/db/pacioli_test.sqlite +0 -0
  32. data/spec/internal/db/schema.rb +58 -0
  33. data/spec/internal/log/.gitignore +1 -0
  34. data/spec/internal/public/favicon.ico +0 -0
  35. data/spec/lib/pacioli/account_spec.rb +43 -0
  36. data/spec/lib/pacioli/company_spec.rb +19 -0
  37. data/spec/lib/pacioli/customer_spec.rb +23 -0
  38. data/spec/lib/pacioli/journal_entry_spec.rb +199 -0
  39. data/spec/lib/pacioli/posting_rule_spec.rb +40 -0
  40. data/spec/spec_helper.rb +19 -0
  41. data/spec/support/helpers.rb +11 -0
  42. metadata +143 -0
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@pacioli --create
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pacioli.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rspec'
8
+ gem 'rspec-rails'
9
+ gem 'sqlite3'
10
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jeffrey van Aswegen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Pacioli
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pacioli'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pacioli
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize!
7
+ run Combustion::Application
@@ -0,0 +1,30 @@
1
+ module Pacioli
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+
6
+ source_root File.expand_path("../templates", __FILE__)
7
+
8
+ desc <<-CONTENT
9
+ Copies the pacioli migration file to the migrations
10
+ folder.
11
+
12
+ Please run rake db:migrate once the installer is
13
+ complete.
14
+
15
+ CONTENT
16
+
17
+ def self.next_migration_number(dirname) #:nodoc:
18
+ if ActiveRecord::Base.timestamped_migrations
19
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
20
+ else
21
+ "%.3d" % (current_migration_number(dirname) + 1)
22
+ end
23
+ end
24
+
25
+ def create_migration_file
26
+ migration_template 'migration.rb', 'db/migrate/create_pacioli_tables.rb'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ class CreatePacioliTables < ActiveRecord::Migration
2
+ def self.change
3
+
4
+ create_table :pacioli_companies, :force => true do |t|
5
+ t.integer :companyable_id
6
+ t.string :companyable_type
7
+ t.string :name
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ create_table :pacioli_customers, :force => true do |t|
13
+ t.references :pacioli_company
14
+ t.integer :customerable_id
15
+ t.string :customerable_type
16
+ t.string :name
17
+
18
+ t.timestamps
19
+ end
20
+
21
+ create_table :pacioli_accounts, :force => true do |t|
22
+ t.references :pacioli_company
23
+ t.string :code
24
+ t.string :name
25
+ t.string :description
26
+ t.string :type
27
+
28
+ t.timestamps
29
+ end
30
+
31
+ create_table :pacioli_journal_entries, :force => true do |t|
32
+ t.string :journal_type
33
+ t.string :description
34
+ t.references :pacioli_company
35
+ t.decimal :amount, :precision => 20, :scale => 10
36
+
37
+ t.datetime :dated
38
+ t.timestamps
39
+ end
40
+
41
+ create_table :pacioli_transactions, :force => true do |t|
42
+ t.references :pacioli_journal_entry
43
+ t.references :pacioli_account
44
+ t.references :pacioli_customer
45
+
46
+ t.string :type
47
+ t.decimal :amount, :precision => 20, :scale => 10
48
+
49
+ t.datetime :dated
50
+ t.timestamps
51
+ end
52
+
53
+ create_table :pacioli_posting_rules, :force => true do |t|
54
+ t.references :pacioli_company
55
+ t.string :name
56
+ t.text :rules
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ require "active_record"
2
+ require "pacioli/version"
3
+
4
+ module Pacioli
5
+ def self.table_name_prefix
6
+ 'pacioli_'
7
+ end
8
+
9
+ def self.register_company(&block)
10
+ company = Company.new
11
+ company.instance_eval(&block)
12
+ company.save!
13
+ company
14
+ end
15
+
16
+ end
17
+
18
+ require "pacioli/company"
19
+ require "pacioli/customer"
20
+ require "pacioli/account"
21
+ require "pacioli/asset_account"
22
+ require "pacioli/liability_account"
23
+ require "pacioli/equity_account"
24
+ require "pacioli/income_account"
25
+ require "pacioli/expense_account"
26
+ require "pacioli/journal_entry"
27
+ require "pacioli/transaction"
28
+ require "pacioli/credit"
29
+ require "pacioli/debit"
30
+ require "pacioli/posting_rule"
31
+ require "pacioli/exception"
32
+
33
+ require "pacioli/validations/journal_entry_validator"
34
+ require "pacioli/validations/posting_rule_validator"
@@ -0,0 +1,25 @@
1
+ module Pacioli
2
+ class Account < ActiveRecord::Base
3
+ has_many :transactions, foreign_key: :pacioli_account_id
4
+
5
+ def asset?
6
+ false
7
+ end
8
+
9
+ def liability?
10
+ false
11
+ end
12
+
13
+ def equity?
14
+ false
15
+ end
16
+
17
+ def income?
18
+ false
19
+ end
20
+
21
+ def expense?
22
+ false
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Pacioli
2
+ class AssetAccount < Account
3
+
4
+ def asset?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,91 @@
1
+ module Pacioli
2
+ class Company < ActiveRecord::Base
3
+ belongs_to :companyable, polymorphic: true
4
+ has_many :accounts, foreign_key: :pacioli_company_id, dependent: :destroy
5
+ has_many :chart_of_accounts, through: :accounts
6
+ has_many :journal_entries, foreign_key: :pacioli_company_id, dependent: :destroy
7
+ has_many :posting_rules, foreign_key: :pacioli_company_id, dependent: :destroy
8
+ has_many :customers, foreign_key: :pacioli_company_id, dependent: :destroy
9
+
10
+ def self.for(company)
11
+ Company.where(companyable_type: company.class.name, companyable_id: company.id).first || Company.create!(companyable_type: company.class.name, companyable_id: company.id)
12
+ end
13
+
14
+ def with_name(company_name)
15
+ self.name = company_name
16
+ end
17
+
18
+ def with_source(companyable_object)
19
+ self.companyable = companyable_object
20
+ end
21
+
22
+ def add_asset_account(options={})
23
+ account = AssetAccount.create!(options)
24
+ self.accounts << account
25
+ account
26
+ end
27
+
28
+ def add_equity_account(options={})
29
+ account = EquityAccount.create!(options)
30
+ self.accounts << account
31
+ account
32
+ end
33
+
34
+ def add_liability_account(options={})
35
+ account = LiabilityAccount.create!(options)
36
+ self.accounts << account
37
+ account
38
+ end
39
+
40
+ def add_income_account(options={})
41
+ account = IncomeAccount.create!(options)
42
+ self.accounts << account
43
+ account
44
+ end
45
+
46
+ def add_expense_account(options={})
47
+ account = ExpenseAccount.create!(options)
48
+ self.accounts << account
49
+ account
50
+ end
51
+
52
+ def record_journal_entry(&block)
53
+ self.transaction do
54
+ journal_entry = JournalEntry.new
55
+ self.journal_entries << journal_entry
56
+ journal_entry.instance_eval(&block)
57
+
58
+ journal_entry.execute_posting_rules
59
+
60
+ JournalEntryValidator.for(journal_entry).execute
61
+
62
+ journal_entry.amount = journal_entry.calculate_amount if journal_entry.amount.blank?
63
+ journal_entry.save!
64
+ journal_entry
65
+ end
66
+ end
67
+
68
+ def create_posting_rule(&block)
69
+ self.transaction do
70
+ posting_rule = PostingRule.new
71
+ self.posting_rules << posting_rule
72
+ posting_rule.instance_eval(&block)
73
+
74
+ PostingRuleValidator.for(posting_rule).execute
75
+
76
+ posting_rule.save!
77
+ posting_rule
78
+ end
79
+ end
80
+
81
+ def register_customer(&block)
82
+ self.transaction do
83
+ customer = Customer.new
84
+ self.customers << customer
85
+ customer.instance_eval(&block)
86
+ customer.save!
87
+ customer
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,5 @@
1
+ module Pacioli
2
+ class Credit < Transaction
3
+
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ module Pacioli
2
+ class Customer < ActiveRecord::Base
3
+ belongs_to :customerable, polymorphic: true
4
+ belongs_to :company, foreign_key: :pacioli_company_id
5
+ has_many :transactions, foreign_key: :pacioli_customer_id
6
+
7
+ def self.for(customer)
8
+ Customer.where(customerable_type: customer.class.name, customerable_id: customer.id).first || Customer.create!(customerable_type: customer.class.name, customerable_id: customer.id)
9
+ end
10
+
11
+ def with_name(customer_name)
12
+ self.name = customer_name
13
+ end
14
+
15
+ def with_source(customerable_object)
16
+ self.customerable = customerable_object
17
+ end
18
+
19
+ def debits
20
+ transactions.where(type: 'Pacioli::Debit')
21
+ end
22
+
23
+ def credits
24
+ transactions.where(type: 'Pacioli::Credit')
25
+ end
26
+
27
+ def balance
28
+ debits.sum(&:amount) - credits.sum(&:amount)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module Pacioli
2
+ class Debit < Transaction
3
+
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Pacioli
2
+ class EquityAccount < Account
3
+
4
+ def equity?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Pacioli
2
+ class AccountsNotBalancedException < Exception; end
3
+ class PostingRuleNotBalancedException < Exception; end
4
+ end
@@ -0,0 +1,9 @@
1
+ module Pacioli
2
+ class ExpenseAccount < Account
3
+
4
+ def expense?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Pacioli
2
+ class IncomeAccount < Account
3
+
4
+ def income?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,61 @@
1
+ module Pacioli
2
+ class JournalEntry < ActiveRecord::Base
3
+ belongs_to :company, foreign_key: :pacioli_company_id
4
+ has_many :transactions, foreign_key: :pacioli_journal_entry_id
5
+
6
+ def with_description(desc)
7
+ self.description = desc
8
+ end
9
+
10
+ def with_amount(amt)
11
+ self.amount = amt
12
+ end
13
+
14
+ def type(type=nil)
15
+ self.journal_type = type
16
+ end
17
+
18
+ def debit(options={})
19
+ account = self.company.accounts.where(name: options[:account]).first
20
+ debit = Debit.new(journal_entry: self, account: account, amount: options[:amount])
21
+ debit.customer = options[:against_customer] if options.has_key? :against_customer
22
+ self.transactions << debit
23
+ end
24
+
25
+ def credit(options={})
26
+ account = self.company.accounts.where(name: options[:account]).first
27
+ credit = Credit.new(journal_entry: self, account: account, amount: options[:amount])
28
+ credit.customer = options[:against_customer] if options.has_key? :against_customer
29
+ self.transactions << credit
30
+ end
31
+
32
+ def balanced?
33
+ debits.sum(&:amount) == credits.sum(&:amount)
34
+ end
35
+
36
+ def debits
37
+ transactions.where(type: 'Pacioli::Debit')
38
+ end
39
+
40
+ def credits
41
+ transactions.where(type: 'Pacioli::Credit')
42
+ end
43
+
44
+ def calculate_amount
45
+ credits.sum(&:amount)
46
+ end
47
+
48
+ def execute_posting_rules
49
+ return if journal_type.blank?
50
+ posting_rule = self.company.posting_rules.where(name: self.journal_type).first
51
+
52
+ posting_rule.rules[:debits].each do |debit|
53
+ self.debit(account: debit[:account], amount: (self.amount / 100) * debit[:percentage])
54
+ end
55
+
56
+ posting_rule.rules[:credits].each do |credit|
57
+ self.credit(account: credit[:account], amount: (self.amount / 100) * credit[:percentage])
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ module Pacioli
2
+ class LiabilityAccount < Account
3
+
4
+ def liability?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ module Pacioli
2
+ class PostingRule < ActiveRecord::Base
3
+ belongs_to :company, foreign_key: :pacioli_company_id
4
+
5
+ serialize :rules
6
+
7
+ def with_name(name)
8
+ self.name = name
9
+ end
10
+
11
+ def debit(account_name, options={})
12
+ self.rules ||= {}
13
+ self.rules[:debits] ||= []
14
+ self.rules[:debits] << prepare_rules(account_name, options)
15
+ end
16
+
17
+ def credit(account_name, options={})
18
+ self.rules ||= {}
19
+ self.rules[:credits] ||= []
20
+ self.rules[:credits] << prepare_rules(account_name, options)
21
+ end
22
+
23
+ def prepare_rules(account_name, options={})
24
+ options[:percentage] = 100 if options.empty?
25
+ options[:account] = account_name
26
+ options
27
+ end
28
+
29
+ def balanced?
30
+ self.rules[:credits].inject(0) { |sum, cr| sum += cr[:percentage] } == self.rules[:debits].inject(0) { |sum, dr| sum += dr[:percentage] }
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ module Pacioli
2
+ class Transaction < ActiveRecord::Base
3
+ belongs_to :journal_entry, foreign_key: :pacioli_journal_entry_id
4
+ belongs_to :account, foreign_key: :pacioli_account_id
5
+ belongs_to :customer, foreign_key: :pacioli_customer_id
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module Pacioli
2
+ class JournalEntryValidator
3
+ attr_accessor :journal_entry
4
+
5
+ def self.for(journal_entry)
6
+ validator = new
7
+ validator.journal_entry = journal_entry
8
+ validator
9
+ end
10
+
11
+ def execute
12
+ unless self.journal_entry.balanced?
13
+ raise Pacioli::AccountsNotBalancedException, "The aggregate balance of all accounts having positive balances must be equal to the aggregate balance of all accounts having negative balances."
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Pacioli
2
+ class PostingRuleValidator
3
+ attr_accessor :posting_rule
4
+
5
+ def self.for(posting_rule)
6
+ validator = new
7
+ validator.posting_rule = posting_rule
8
+ validator
9
+ end
10
+
11
+ def execute
12
+ raise Pacioli::PostingRuleNotBalancedException, "The aggregate balance of debits and credits must be equal" unless self.posting_rule.balanced?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Pacioli
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pacioli/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "pacioli"
8
+ gem.version = Pacioli::VERSION
9
+ gem.authors = ["Jeffrey van Aswegen"]
10
+ gem.email = ["jeffmess@gmail.com"]
11
+ gem.description = %q{A double-entry bookkeeping system for Ruby on Rails}
12
+ gem.summary = %q{A double-entry bookkeeping system for Ruby on Rails}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "activesupport"
21
+ gem.add_dependency "activerecord", "~> 3.0"
22
+ gem.add_dependency "i18n"
23
+
24
+ gem.add_development_dependency 'combustion', '~> 0.3.1'
25
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/pacioli_test.sqlite
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ #
3
+ end
@@ -0,0 +1,58 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table :pacioli_companies, :force => true do |t|
4
+ t.integer :companyable_id
5
+ t.string :companyable_type
6
+ t.string :name
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ create_table :pacioli_customers, :force => true do |t|
12
+ t.references :pacioli_company
13
+ t.integer :customerable_id
14
+ t.string :customerable_type
15
+ t.string :name
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ create_table :pacioli_accounts, :force => true do |t|
21
+ t.references :pacioli_company
22
+ t.string :code
23
+ t.string :name
24
+ t.string :description
25
+ t.string :type
26
+
27
+ t.timestamps
28
+ end
29
+
30
+ create_table :pacioli_journal_entries, :force => true do |t|
31
+ t.string :journal_type
32
+ t.string :description
33
+ t.references :pacioli_company
34
+ t.decimal :amount, :precision => 20, :scale => 10
35
+
36
+ t.datetime :dated
37
+ t.timestamps
38
+ end
39
+
40
+ create_table :pacioli_transactions, :force => true do |t|
41
+ t.references :pacioli_journal_entry
42
+ t.references :pacioli_account
43
+ t.references :pacioli_customer
44
+
45
+ t.string :type
46
+ t.decimal :amount, :precision => 20, :scale => 10
47
+
48
+ t.datetime :dated
49
+ t.timestamps
50
+ end
51
+
52
+ create_table :pacioli_posting_rules, :force => true do |t|
53
+ t.references :pacioli_company
54
+ t.string :name
55
+ t.text :rules
56
+ end
57
+
58
+ end
@@ -0,0 +1 @@
1
+ *.log
File without changes
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacioli::Account do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ @company = Pacioli::register_company do
10
+ with_name "Coca-Cola"
11
+ end
12
+ end
13
+
14
+ context "Create Accounts" do
15
+
16
+ it "should be able to create an asset account" do
17
+ account = @company.add_asset_account name: "Accounts Receivable"
18
+ account.should be_asset
19
+ end
20
+
21
+ it "should be able to create an equity account" do
22
+ account = @company.add_equity_account name: "Shareholders Capital"
23
+ account.should be_equity
24
+ end
25
+
26
+ it "should be able to create a liability account" do
27
+ account = @company.add_liability_account name: "Accounts Payable"
28
+ account.should be_liability
29
+ end
30
+
31
+ it "should be able to create an expense account" do
32
+ account = @company.add_expense_account name: "Entertainment"
33
+ account.should be_expense
34
+ end
35
+
36
+ it "should be able to create an income account" do
37
+ account = @company.add_income_account name: "Sales"
38
+ account.should be_income
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacioli::Company do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ @company = Pacioli::register_company do
10
+ with_name "Coca-Cola"
11
+ end
12
+ end
13
+
14
+ it "should create a company" do
15
+ Pacioli::Company.all.count.should == 1
16
+ Pacioli::Company.first.name.should == "Coca-Cola"
17
+ end
18
+
19
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacioli::Customer do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ @company = Pacioli::register_company do
10
+ with_name "Coca-Cola"
11
+ end
12
+
13
+ @customer = @company.register_customer do
14
+ with_name "Leonardo da Vinci"
15
+ end
16
+ end
17
+
18
+ it "should create a customer" do
19
+ Pacioli::Customer.all.count.should == 1
20
+ @customer.name.should == "Leonardo da Vinci"
21
+ @customer.company.should == @company
22
+ end
23
+ end
@@ -0,0 +1,199 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacioli::Account do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ @company = Pacioli::register_company do
10
+ with_name "Coca-Cola"
11
+ add_asset_account name: "Accounts Receivable", code: "100"
12
+ add_asset_account name: "Cash in Bank", code: "101"
13
+ add_income_account name: "Sales", code: "301"
14
+ add_liability_account name: "Sales Tax", code: "401"
15
+ end
16
+ end
17
+
18
+ context "Recording a journal entry" do
19
+ before(:each) do
20
+ @company.record_journal_entry do
21
+ with_description "Invoice 123 for November Rent"
22
+ debit account: "Accounts Receivable", amount: 4500.00
23
+ credit account: "Sales", amount: 4500.00
24
+ end
25
+ end
26
+
27
+ it "should record a journal entry" do
28
+ Pacioli::JournalEntry.all.count.should == 1
29
+ end
30
+
31
+ it "should record 1 transaction against Accounts Receivable Account" do
32
+ @company.accounts.where(name: "Accounts Receivable").first.transactions.count.should == 1
33
+ end
34
+
35
+ it "should have recorded 1 transaction against the Sales Account" do
36
+ @company.accounts.where(name: "Sales").first.transactions.count.should == 1
37
+ end
38
+
39
+ it "should balance" do
40
+ Pacioli::JournalEntry.last.should be_balanced
41
+ end
42
+
43
+ it "should record the amount of 4500.00 for the journal entry" do
44
+ Pacioli::JournalEntry.last.amount.should == 4500.00
45
+ end
46
+ end
47
+
48
+ context "Recording a journal entry where the T-tables do not balance" do
49
+ it "should raise an AccountsNotBalancedException error" do
50
+ lambda {
51
+ @company.record_journal_entry do
52
+ with_description "Invoice 123 for November Rent"
53
+ debit account: "Accounts Receivable", amount: 4500.00
54
+ end
55
+ }.should raise_error(Pacioli::AccountsNotBalancedException, "The aggregate balance of all accounts having positive balances must be equal to the aggregate balance of all accounts having negative balances.")
56
+
57
+ Pacioli::JournalEntry.all.should be_blank
58
+ end
59
+ end
60
+
61
+ context "Recording specific types of journal entries using posting rules" do
62
+ context "Against 2 accounts" do
63
+ before(:each) do
64
+ @company.create_posting_rule do
65
+ with_name :sale
66
+ debit "Accounts Receivable"
67
+ credit "Sales"
68
+ end
69
+
70
+ @sales = @company.record_journal_entry do
71
+ with_amount 5000.00
72
+ with_description "Invoice 123 for November Rent"
73
+ type :sale
74
+ end
75
+ end
76
+
77
+ it "should create a debit transaction against the Accounts Receivable account for 5000.00" do
78
+ account = @company.accounts.where(name: "Accounts Receivable").first
79
+ account.transactions.count.should == 1
80
+ end
81
+
82
+ it "should create a credit transaction against the Sales account for 5000.00" do
83
+ account = @company.accounts.where(name: "Sales").first
84
+ account.transactions.count.should == 1
85
+ end
86
+
87
+ it "should be balanced" do
88
+ @sales.should be_balanced
89
+ end
90
+ end
91
+
92
+ context "Against multiple accounts" do
93
+ before(:each) do
94
+ @company2 = Pacioli::register_company do
95
+ with_name "Pepsi"
96
+ add_expense_account name: "Computer Equipment"
97
+ add_expense_account name: "Fund Balance"
98
+ add_liability_account name: "Account Payable"
99
+ add_liability_account name: "Capital Equipment"
100
+ end
101
+
102
+ @company2.create_posting_rule do
103
+ with_name :purchase_equipment_and_cover_replacements
104
+ debit "Computer Equipment"
105
+ credit "Account Payable"
106
+ debit "Fund Balance"
107
+ credit "Capital Equipment"
108
+ end
109
+
110
+ @purchase = @company2.record_journal_entry do
111
+ with_amount 10000.00
112
+ with_description "Purchase of Computer Equipment"
113
+ type :purchase_equipment_and_cover_replacements
114
+ end
115
+ end
116
+
117
+ it "should be balanced" do
118
+ @purchase.should be_balanced
119
+ end
120
+
121
+ it "should have 2 debit transactions" do
122
+ @purchase.debits.count.should == 2
123
+ end
124
+
125
+ it "should have 2 credit transactions" do
126
+ @purchase.credits.count.should == 2
127
+ end
128
+ end
129
+
130
+ context "Against multiple accounts and a percentage of an amount" do
131
+ before(:each) do
132
+ @company.create_posting_rule do
133
+ with_name :sale
134
+ debit "Accounts Receivable"
135
+ credit "Sales", percentage: 86
136
+ credit "Sales Tax", percentage: 14
137
+ end
138
+
139
+ @sales = @company.record_journal_entry do
140
+ with_amount 100.00
141
+ with_description "Invoice 123 for November Rent"
142
+ type :sale
143
+ end
144
+ end
145
+
146
+ it "should create a credit transaction against Sales for 86.00 and Sales Taxes for 14.00" do
147
+ @sales.credits.map(&:amount).should == [BigDecimal('86.00'), BigDecimal('14.00')]
148
+ end
149
+
150
+ it "should create a debit transaction against Accounts Receivable" do
151
+ @sales.debits.map(&:amount).should == [BigDecimal('100.00')]
152
+ end
153
+ end
154
+
155
+ context "Recording against a customer" do
156
+ before(:each) do
157
+ @customer = @company.register_customer do
158
+ with_name "Leonardo da Vinci"
159
+ end
160
+ end
161
+
162
+ context "Normal transactions where customer is invoiced" do
163
+ before(:each) do
164
+ customer = @customer
165
+
166
+ @company.record_journal_entry do
167
+ with_description "Invoice 123 for November Rent"
168
+ debit account: "Accounts Receivable", amount: 4500.00, against_customer: customer
169
+ credit account: "Sales", amount: 4500.00
170
+ end
171
+ end
172
+
173
+ it "should have a balance of 4500.00" do
174
+ @customer.balance.should == 4500.00
175
+ end
176
+
177
+ context "Customer makes a payment against the invoice" do
178
+ before(:each) do
179
+ customer = @customer
180
+
181
+ @company.record_journal_entry do
182
+ with_description "PMT Received"
183
+ debit account: "Cash in Bank", amount: 4500.00
184
+ credit account: "Accounts Receivable", amount: 4500.00, against_customer: customer
185
+ end
186
+ end
187
+
188
+ it "should have a balance of 0.00" do
189
+ @customer.balance.should == 0.00
190
+ end
191
+ end
192
+ end
193
+
194
+ context "Transactions using posting rules" do
195
+
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacioli::Account do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ @company = Pacioli::register_company do
10
+ with_name "Coca-Cola"
11
+ add_asset_account name: "Accounts Receivable", code: "100"
12
+ add_income_account name: "Sales", code: "301"
13
+ end
14
+
15
+ @posting_rule = @company.create_posting_rule do
16
+ with_name :sale
17
+ debit "Accounts Receivable"
18
+ credit "Sales"
19
+ end
20
+ end
21
+
22
+ it "should store the rules" do
23
+ @posting_rule.rules.should == {
24
+ credits: [
25
+ {account: "Sales", percentage: 100}
26
+ ],
27
+ debits: [
28
+ {account: "Accounts Receivable", percentage: 100}
29
+ ]
30
+ }
31
+
32
+ @posting_rule.name.should == :sale
33
+ end
34
+
35
+ it "should be balanced" do
36
+ @posting_rule.should be_balanced
37
+ end
38
+
39
+
40
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize! :active_record
7
+
8
+ require 'rspec/rails'
9
+
10
+ require './spec/support/helpers.rb'
11
+
12
+ RSpec.configure do |config|
13
+ config.use_transactional_fixtures = true
14
+ config.color_enabled = true
15
+ config.formatter = :documentation # :progress, :html, :textmate
16
+ #config.include FactoryGirl::Syntax::Methods
17
+ end
18
+
19
+ #FactoryGirl.find_definitions
@@ -0,0 +1,11 @@
1
+ module Helpers
2
+ def tear_it_down
3
+ Pacioli::Company.delete_all
4
+ Pacioli::Account.delete_all
5
+ Pacioli::JournalEntry.delete_all
6
+ Pacioli::Transaction.delete_all
7
+ Pacioli::PostingRule.destroy_all
8
+ Pacioli::Customer.destroy_all
9
+ end
10
+ end
11
+
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pacioli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeffrey van Aswegen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-09 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &70253548011320 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70253548011320
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ requirement: &70253548010820 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70253548010820
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ requirement: &70253548010400 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70253548010400
47
+ - !ruby/object:Gem::Dependency
48
+ name: combustion
49
+ requirement: &70253548009860 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.1
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70253548009860
58
+ description: A double-entry bookkeeping system for Ruby on Rails
59
+ email:
60
+ - jeffmess@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .rvmrc
67
+ - Gemfile
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - config.ru
72
+ - lib/generators/active_record/pacioli_generator.rb
73
+ - lib/generators/active_record/templates/migration.rb
74
+ - lib/pacioli.rb
75
+ - lib/pacioli/account.rb
76
+ - lib/pacioli/asset_account.rb
77
+ - lib/pacioli/company.rb
78
+ - lib/pacioli/credit.rb
79
+ - lib/pacioli/customer.rb
80
+ - lib/pacioli/debit.rb
81
+ - lib/pacioli/equity_account.rb
82
+ - lib/pacioli/exception.rb
83
+ - lib/pacioli/expense_account.rb
84
+ - lib/pacioli/income_account.rb
85
+ - lib/pacioli/journal_entry.rb
86
+ - lib/pacioli/liability_account.rb
87
+ - lib/pacioli/posting_rule.rb
88
+ - lib/pacioli/transaction.rb
89
+ - lib/pacioli/validations/journal_entry_validator.rb
90
+ - lib/pacioli/validations/posting_rule_validator.rb
91
+ - lib/pacioli/version.rb
92
+ - pacioli.gemspec
93
+ - spec/internal/config/database.yml
94
+ - spec/internal/config/routes.rb
95
+ - spec/internal/db/pacioli_test.sqlite
96
+ - spec/internal/db/schema.rb
97
+ - spec/internal/log/.gitignore
98
+ - spec/internal/public/favicon.ico
99
+ - spec/lib/pacioli/account_spec.rb
100
+ - spec/lib/pacioli/company_spec.rb
101
+ - spec/lib/pacioli/customer_spec.rb
102
+ - spec/lib/pacioli/journal_entry_spec.rb
103
+ - spec/lib/pacioli/posting_rule_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/support/helpers.rb
106
+ homepage: ''
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.6
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: A double-entry bookkeeping system for Ruby on Rails
130
+ test_files:
131
+ - spec/internal/config/database.yml
132
+ - spec/internal/config/routes.rb
133
+ - spec/internal/db/pacioli_test.sqlite
134
+ - spec/internal/db/schema.rb
135
+ - spec/internal/log/.gitignore
136
+ - spec/internal/public/favicon.ico
137
+ - spec/lib/pacioli/account_spec.rb
138
+ - spec/lib/pacioli/company_spec.rb
139
+ - spec/lib/pacioli/customer_spec.rb
140
+ - spec/lib/pacioli/journal_entry_spec.rb
141
+ - spec/lib/pacioli/posting_rule_spec.rb
142
+ - spec/spec_helper.rb
143
+ - spec/support/helpers.rb