pacioli 0.0.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.
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/config.ru +7 -0
- data/lib/generators/active_record/pacioli_generator.rb +30 -0
- data/lib/generators/active_record/templates/migration.rb +60 -0
- data/lib/pacioli.rb +34 -0
- data/lib/pacioli/account.rb +25 -0
- data/lib/pacioli/asset_account.rb +9 -0
- data/lib/pacioli/company.rb +91 -0
- data/lib/pacioli/credit.rb +5 -0
- data/lib/pacioli/customer.rb +31 -0
- data/lib/pacioli/debit.rb +5 -0
- data/lib/pacioli/equity_account.rb +9 -0
- data/lib/pacioli/exception.rb +4 -0
- data/lib/pacioli/expense_account.rb +9 -0
- data/lib/pacioli/income_account.rb +9 -0
- data/lib/pacioli/journal_entry.rb +61 -0
- data/lib/pacioli/liability_account.rb +9 -0
- data/lib/pacioli/posting_rule.rb +34 -0
- data/lib/pacioli/transaction.rb +7 -0
- data/lib/pacioli/validations/journal_entry_validator.rb +17 -0
- data/lib/pacioli/validations/posting_rule_validator.rb +15 -0
- data/lib/pacioli/version.rb +3 -0
- data/pacioli.gemspec +25 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/pacioli_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +58 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/pacioli/account_spec.rb +43 -0
- data/spec/lib/pacioli/company_spec.rb +19 -0
- data/spec/lib/pacioli/customer_spec.rb +23 -0
- data/spec/lib/pacioli/journal_entry_spec.rb +199 -0
- data/spec/lib/pacioli/posting_rule_spec.rb +40 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/helpers.rb +11 -0
- metadata +143 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@pacioli --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/config.ru
ADDED
@@ -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
|
data/lib/pacioli.rb
ADDED
@@ -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,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,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,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,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,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
|
data/pacioli.gemspec
ADDED
@@ -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
|
Binary file
|
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
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
|