keepr 0.3.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +14 -10
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +3 -3
- data/Rakefile +3 -1
- data/ci/Gemfile-rails-4-2 +4 -4
- data/ci/Gemfile-rails-5-0 +4 -4
- data/ci/Gemfile-rails-5-1 +12 -0
- data/ci/Gemfile-rails-5-2 +12 -0
- data/ci/{Gemfile-rails-4-1 → Gemfile-rails-6-0} +4 -4
- data/keepr.gemspec +15 -14
- data/lib/generators/keepr/migration/migration_generator.rb +5 -3
- data/lib/generators/keepr/migration/templates/migration.rb +27 -25
- data/lib/keepr.rb +8 -0
- data/lib/keepr/account.rb +66 -61
- data/lib/keepr/account_export.rb +10 -14
- data/lib/keepr/active_record_extension.rb +10 -8
- data/lib/keepr/contact_export.rb +6 -7
- data/lib/keepr/cost_center.rb +3 -1
- data/lib/keepr/group.rb +25 -16
- data/lib/keepr/groups_creator.rb +11 -11
- data/lib/keepr/journal.rb +19 -16
- data/lib/keepr/journal_export.rb +10 -7
- data/lib/keepr/posting.rb +26 -21
- data/lib/keepr/tax.rb +4 -2
- data/lib/keepr/version.rb +3 -1
- data/spec/factories/account.rb +6 -4
- data/spec/factories/cost_center.rb +5 -3
- data/spec/factories/group.rb +5 -3
- data/spec/factories/tax.rb +7 -5
- data/spec/keepr/account_export_spec.rb +30 -27
- data/spec/keepr/account_spec.rb +92 -87
- data/spec/keepr/active_record_extension_spec.rb +38 -36
- data/spec/keepr/contact_export_spec.rb +20 -17
- data/spec/keepr/cost_center_spec.rb +9 -7
- data/spec/keepr/group_spec.rb +53 -49
- data/spec/keepr/groups_creator_spec.rb +7 -4
- data/spec/keepr/journal_export_spec.rb +76 -75
- data/spec/keepr/journal_spec.rb +48 -46
- data/spec/keepr/posting_spec.rb +25 -23
- data/spec/keepr/tax_spec.rb +21 -14
- data/spec/spec_helper.rb +13 -11
- data/spec/support/contact.rb +2 -0
- data/spec/support/document.rb +2 -0
- data/spec/support/ledger.rb +2 -0
- data/spec/support/spec_migration.rb +3 -1
- metadata +23 -22
data/lib/keepr/account_export.rb
CHANGED
@@ -1,35 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::AccountExport
|
2
|
-
def initialize(accounts, header_options={}, &block)
|
4
|
+
def initialize(accounts, header_options = {}, &block)
|
3
5
|
@accounts = accounts
|
4
6
|
@header_options = header_options
|
5
7
|
@block = block
|
6
8
|
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_file(filename)
|
13
|
-
export.to_file(filename)
|
14
|
-
end
|
10
|
+
delegate :to_s, :to_file,
|
11
|
+
to: :export
|
15
12
|
|
16
|
-
private
|
13
|
+
private
|
17
14
|
|
18
15
|
def export
|
19
16
|
export = Datev::AccountExport.new(@header_options)
|
20
17
|
|
21
18
|
@accounts.reorder(:number).each do |account|
|
22
|
-
unless account.debtor? || account.creditor?
|
23
|
-
export << to_datev(account)
|
24
|
-
end
|
19
|
+
export << to_datev(account) unless account.debtor? || account.creditor?
|
25
20
|
end
|
26
21
|
|
27
22
|
export
|
28
23
|
end
|
29
24
|
|
30
25
|
def to_datev(account)
|
31
|
-
{
|
32
|
-
'
|
26
|
+
{
|
27
|
+
'Konto' => account.number,
|
28
|
+
'Kontenbeschriftung' => account.name.slice(0, 40)
|
33
29
|
}.merge(@block ? @block.call(account) : {})
|
34
30
|
end
|
35
31
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Keepr::ActiveRecordExtension
|
2
4
|
def self.included(base)
|
3
5
|
base.extend ClassMethods
|
@@ -5,19 +7,19 @@ module Keepr::ActiveRecordExtension
|
|
5
7
|
|
6
8
|
module ClassMethods
|
7
9
|
def has_one_keepr_account
|
8
|
-
has_one :keepr_account, :
|
9
|
-
has_many :keepr_postings, :
|
10
|
+
has_one :keepr_account, class_name: 'Keepr::Account', as: :accountable, dependent: :restrict_with_error
|
11
|
+
has_many :keepr_postings, class_name: 'Keepr::Posting', through: :keepr_account, dependent: :restrict_with_error
|
10
12
|
end
|
11
13
|
|
12
14
|
def has_many_keepr_accounts
|
13
|
-
has_many :keepr_accounts, :
|
14
|
-
has_many :keepr_postings, :
|
15
|
+
has_many :keepr_accounts, class_name: 'Keepr::Account', as: :accountable, dependent: :restrict_with_error
|
16
|
+
has_many :keepr_postings, class_name: 'Keepr::Posting', through: :keepr_accounts, dependent: :restrict_with_error
|
15
17
|
end
|
16
18
|
|
17
19
|
def has_keepr_journals
|
18
|
-
has_many :keepr_journals, :
|
20
|
+
has_many :keepr_journals, class_name: 'Keepr::Journal', as: :accountable, dependent: :restrict_with_error
|
19
21
|
|
20
|
-
class_eval <<-
|
22
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
21
23
|
def keepr_booked?
|
22
24
|
keepr_journals.exists?
|
23
25
|
end
|
@@ -27,11 +29,11 @@ module Keepr::ActiveRecordExtension
|
|
27
29
|
where('keepr_journals.id' => nil)
|
28
30
|
}
|
29
31
|
scope :keepr_booked, -> { joins(:keepr_journals) }
|
30
|
-
|
32
|
+
CODE
|
31
33
|
end
|
32
34
|
|
33
35
|
def has_keepr_postings
|
34
|
-
has_many :keepr_postings, :
|
36
|
+
has_many :keepr_postings, class_name: 'Keepr::Posting', as: :accountable, dependent: :restrict_with_error
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/keepr/contact_export.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::ContactExport
|
2
|
-
def initialize(accounts, header_options={}, &block)
|
4
|
+
def initialize(accounts, header_options = {}, &block)
|
3
5
|
raise ArgumentError unless block_given?
|
4
6
|
|
5
7
|
@accounts = accounts
|
@@ -15,22 +17,19 @@ class Keepr::ContactExport
|
|
15
17
|
export.to_file(filename)
|
16
18
|
end
|
17
19
|
|
18
|
-
private
|
20
|
+
private
|
19
21
|
|
20
22
|
def export
|
21
23
|
export = Datev::ContactExport.new(@header_options)
|
22
24
|
|
23
25
|
@accounts.reorder(:number).each do |account|
|
24
|
-
if account.debtor? || account.creditor?
|
25
|
-
export << to_datev(account)
|
26
|
-
end
|
26
|
+
export << to_datev(account) if account.debtor? || account.creditor?
|
27
27
|
end
|
28
28
|
|
29
29
|
export
|
30
30
|
end
|
31
31
|
|
32
32
|
def to_datev(account)
|
33
|
-
{ 'Konto' => account.number
|
34
|
-
}.merge(@block.call(account))
|
33
|
+
{ 'Konto' => account.number }.merge(@block.call(account))
|
35
34
|
end
|
36
35
|
end
|
data/lib/keepr/cost_center.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::CostCenter < ActiveRecord::Base
|
2
4
|
self.table_name = 'keepr_cost_centers'
|
3
5
|
|
4
6
|
validates_presence_of :number, :name
|
5
7
|
validates_uniqueness_of :number
|
6
8
|
|
7
|
-
has_many :keepr_postings, :
|
9
|
+
has_many :keepr_postings, class_name: 'Keepr::Posting', foreign_key: 'keepr_cost_center_id', dependent: :restrict_with_error
|
8
10
|
end
|
data/lib/keepr/group.rb
CHANGED
@@ -1,42 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::Group < ActiveRecord::Base
|
2
4
|
self.table_name = 'keepr_groups'
|
3
5
|
|
4
|
-
has_ancestry :
|
6
|
+
has_ancestry orphan_strategy: :restrict
|
5
7
|
|
6
|
-
enum :
|
8
|
+
enum target: %i[asset liability profit_and_loss]
|
7
9
|
|
8
10
|
validates_presence_of :name
|
9
11
|
|
10
|
-
has_many :keepr_accounts, :
|
12
|
+
has_many :keepr_accounts, class_name: 'Keepr::Account', foreign_key: 'keepr_group_id', dependent: :restrict_with_error
|
11
13
|
|
12
|
-
before_validation :
|
14
|
+
before_validation :set_target_from_parent
|
13
15
|
|
14
16
|
validate :check_result_and_target
|
15
17
|
|
16
18
|
def self.result
|
17
|
-
where(:
|
19
|
+
where(is_result: true).first
|
18
20
|
end
|
19
21
|
|
20
22
|
def keepr_postings
|
21
23
|
if is_result
|
22
|
-
Keepr::Posting
|
23
|
-
|
24
|
+
Keepr::Posting
|
25
|
+
.joins(:keepr_account)
|
26
|
+
.where(keepr_accounts: { kind: [
|
27
|
+
Keepr::Account.kinds[:revenue],
|
28
|
+
Keepr::Account.kinds[:expense]
|
29
|
+
] })
|
24
30
|
else
|
25
|
-
Keepr::Posting
|
31
|
+
Keepr::Posting
|
32
|
+
.joins(keepr_account: :keepr_group)
|
33
|
+
.merge(subtree)
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
29
|
-
private
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
private
|
38
|
+
|
39
|
+
def set_target_from_parent
|
40
|
+
self.class.unscoped do
|
41
|
+
self.target = parent.target if parent
|
33
42
|
end
|
34
43
|
end
|
35
44
|
|
36
45
|
def check_result_and_target
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
46
|
+
return unless is_result
|
47
|
+
|
48
|
+
# Attribute `is_result` allowed for liability target only
|
49
|
+
errors.add :base, :liability_needed_for_result unless liability?
|
41
50
|
end
|
42
51
|
end
|
data/lib/keepr/groups_creator.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Keepr::GroupsCreator
|
3
4
|
def initialize(target)
|
4
|
-
raise ArgumentError unless [
|
5
|
+
raise ArgumentError unless %i[balance profit_and_loss].include?(target)
|
5
6
|
|
6
7
|
@target = target
|
7
8
|
end
|
@@ -9,14 +10,15 @@ class Keepr::GroupsCreator
|
|
9
10
|
def run
|
10
11
|
case @target
|
11
12
|
when :balance then
|
12
|
-
load 'asset.txt', :
|
13
|
-
load 'liability.txt', :
|
13
|
+
load 'asset.txt', target: :asset
|
14
|
+
load 'liability.txt', target: :liability
|
14
15
|
when :profit_and_loss
|
15
|
-
load 'profit_and_loss.txt', :
|
16
|
+
load 'profit_and_loss.txt', target: :profit_and_loss
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
private
|
20
|
+
private
|
21
|
+
|
20
22
|
def load(filename, options)
|
21
23
|
full_filename = File.join(File.dirname(__FILE__), "groups_creator/#{filename}".downcase)
|
22
24
|
lines = File.readlines(full_filename)
|
@@ -30,12 +32,10 @@ private
|
|
30
32
|
# Remove leading spaces and separate number and name
|
31
33
|
number, name = line.lstrip.match(/^(.*?)\s(.+)$/).to_a[1..-1]
|
32
34
|
|
33
|
-
attributes = options.merge(:
|
34
|
-
if @target == :balance && name == 'Jahresüberschuss/Jahresfehlbetrag'
|
35
|
-
attributes[:is_result] = true
|
36
|
-
end
|
35
|
+
attributes = options.merge(name: name, number: number)
|
36
|
+
attributes[:is_result] = true if @target == :balance && name == 'Jahresüberschuss/Jahresfehlbetrag'
|
37
37
|
|
38
|
-
if depth
|
38
|
+
if depth.zero?
|
39
39
|
parents = []
|
40
40
|
group = Keepr::Group.create!(attributes)
|
41
41
|
else
|
data/lib/keepr/journal.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::Journal < ActiveRecord::Base
|
2
4
|
self.table_name = 'keepr_journals'
|
3
5
|
|
4
6
|
validates_presence_of :date
|
5
|
-
validates_uniqueness_of :number, :
|
7
|
+
validates_uniqueness_of :number, allow_blank: true
|
6
8
|
|
7
|
-
has_many :keepr_postings, -> { order(:
|
8
|
-
:
|
9
|
+
has_many :keepr_postings, -> { order(amount: :desc) },
|
10
|
+
class_name: 'Keepr::Posting', foreign_key: 'keepr_journal_id', dependent: :destroy
|
9
11
|
|
10
|
-
belongs_to :accountable, :
|
12
|
+
belongs_to :accountable, polymorphic: true
|
11
13
|
|
12
|
-
accepts_nested_attributes_for :keepr_postings, :
|
14
|
+
accepts_nested_attributes_for :keepr_postings, allow_destroy: true, reject_if: :all_blank
|
13
15
|
|
14
|
-
default_scope { order({:
|
16
|
+
default_scope { order({ date: :desc }, id: :desc) }
|
15
17
|
|
16
18
|
validate :validate_postings
|
17
19
|
|
@@ -31,7 +33,8 @@ class Keepr::Journal < ActiveRecord::Base
|
|
31
33
|
before_update :check_permanent
|
32
34
|
before_destroy :check_permanent
|
33
35
|
|
34
|
-
private
|
36
|
+
private
|
37
|
+
|
35
38
|
def existing_postings
|
36
39
|
keepr_postings.to_a.delete_if(&:marked_for_destruction?)
|
37
40
|
end
|
@@ -51,15 +54,15 @@ private
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def check_permanent
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
return unless permanent_was
|
58
|
+
|
59
|
+
# If marked as permanent, no changes are allowed
|
60
|
+
errors.add :base, :changes_not_allowed
|
61
|
+
|
62
|
+
if ActiveRecord::VERSION::MAJOR < 5
|
63
|
+
false
|
64
|
+
else
|
65
|
+
throw :abort
|
63
66
|
end
|
64
67
|
end
|
65
68
|
end
|
data/lib/keepr/journal_export.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::JournalExport
|
2
|
-
def initialize(journals, header_options={}, &block)
|
4
|
+
def initialize(journals, header_options = {}, &block)
|
3
5
|
@journals = journals
|
4
6
|
@header_options = header_options
|
5
7
|
@block = block
|
@@ -13,12 +15,12 @@ class Keepr::JournalExport
|
|
13
15
|
export.to_file(filename)
|
14
16
|
end
|
15
17
|
|
16
|
-
private
|
18
|
+
private
|
17
19
|
|
18
20
|
def export
|
19
21
|
export = Datev::BookingExport.new(@header_options)
|
20
22
|
|
21
|
-
@journals.includes(:
|
23
|
+
@journals.includes(keepr_postings: :keepr_account).reorder(:date, :id).each do |journal|
|
22
24
|
to_datev(journal).each do |hash|
|
23
25
|
export << hash
|
24
26
|
end
|
@@ -29,19 +31,20 @@ private
|
|
29
31
|
|
30
32
|
def to_datev(journal)
|
31
33
|
main_posting = journal.keepr_postings.find { |p| p.keepr_account.debtor? || p.keepr_account.creditor? }
|
32
|
-
main_posting ||= journal.keepr_postings.
|
34
|
+
main_posting ||= journal.keepr_postings.max_by(&:amount)
|
33
35
|
|
34
|
-
journal.keepr_postings.sort_by { |p| [
|
36
|
+
journal.keepr_postings.sort_by { |p| [p.side == main_posting.side ? 1 : 0, -p.amount] }.map do |posting|
|
35
37
|
next if posting == main_posting
|
36
38
|
|
37
|
-
{
|
39
|
+
{
|
40
|
+
'Umsatz (ohne Soll/Haben-Kz)' => posting.amount,
|
38
41
|
'Soll/Haben-Kennzeichen' => 'S',
|
39
42
|
'Konto' => posting.debit? ? posting.keepr_account.number : main_posting.keepr_account.number,
|
40
43
|
'Gegenkonto (ohne BU-Schlüssel)' => posting.credit? ? posting.keepr_account.number : main_posting.keepr_account.number,
|
41
44
|
'BU-Schlüssel' => '40', # Steuerautomatik deaktivieren
|
42
45
|
'Belegdatum' => journal.date,
|
43
46
|
'Belegfeld 1' => journal.number,
|
44
|
-
'Buchungstext' => journal.subject.slice(0,60),
|
47
|
+
'Buchungstext' => journal.subject.slice(0, 60),
|
45
48
|
'Festschreibung' => journal.permanent
|
46
49
|
}.merge(@block ? @block.call(posting) : {})
|
47
50
|
end.compact
|
data/lib/keepr/posting.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::Posting < ActiveRecord::Base
|
2
4
|
self.table_name = 'keepr_postings'
|
3
5
|
|
4
6
|
validates_presence_of :keepr_account_id, :amount
|
5
7
|
validate :cost_center_validation
|
6
8
|
|
7
|
-
belongs_to :keepr_account, :
|
8
|
-
belongs_to :keepr_journal, :
|
9
|
-
belongs_to :keepr_cost_center, :
|
10
|
-
belongs_to :accountable, :
|
9
|
+
belongs_to :keepr_account, class_name: 'Keepr::Account'
|
10
|
+
belongs_to :keepr_journal, class_name: 'Keepr::Journal'
|
11
|
+
belongs_to :keepr_cost_center, class_name: 'Keepr::CostCenter'
|
12
|
+
belongs_to :accountable, polymorphic: true
|
11
13
|
|
12
14
|
SIDE_DEBIT = 'debit'
|
13
15
|
SIDE_CREDIT = 'credit'
|
@@ -17,19 +19,20 @@ class Keepr::Posting < ActiveRecord::Base
|
|
17
19
|
|
18
20
|
def side
|
19
21
|
@side || begin
|
20
|
-
(raw_amount
|
22
|
+
(raw_amount.negative? ? SIDE_CREDIT : SIDE_DEBIT) if raw_amount
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def side=(value)
|
27
|
+
raise ArgumentError unless [SIDE_DEBIT, SIDE_CREDIT].include?(value)
|
28
|
+
|
25
29
|
@side = value
|
30
|
+
return unless amount
|
26
31
|
|
27
32
|
if credit?
|
28
|
-
self.raw_amount = -amount
|
33
|
+
self.raw_amount = -amount.to_d
|
29
34
|
elsif debit?
|
30
|
-
self.raw_amount = amount
|
31
|
-
else
|
32
|
-
raise ArgumentError
|
35
|
+
self.raw_amount = amount.to_d
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
@@ -54,23 +57,25 @@ class Keepr::Posting < ActiveRecord::Base
|
|
54
57
|
end
|
55
58
|
|
56
59
|
def amount=(value)
|
57
|
-
raise ArgumentError.new('Negative amount not allowed!') if value.to_f < 0
|
58
60
|
@side ||= SIDE_DEBIT
|
59
61
|
|
60
|
-
|
61
|
-
self.raw_amount =
|
62
|
-
|
63
|
-
self.raw_amount = value
|
62
|
+
unless value
|
63
|
+
self.raw_amount = nil
|
64
|
+
return
|
64
65
|
end
|
66
|
+
|
67
|
+
raise ArgumentError, 'Negative amount not allowed!' if value.to_d.negative?
|
68
|
+
|
69
|
+
self.raw_amount = credit? ? -value.to_d : value.to_d
|
65
70
|
end
|
66
71
|
|
67
|
-
private
|
72
|
+
private
|
73
|
+
|
68
74
|
def cost_center_validation
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
75
|
+
return unless keepr_cost_center
|
76
|
+
return if keepr_account.profit_and_loss?
|
77
|
+
|
78
|
+
# allowed for expense or revenue accounts only
|
79
|
+
errors.add :keepr_cost_center_id, :allowed_for_expense_or_revenue_only
|
75
80
|
end
|
76
81
|
end
|
data/lib/keepr/tax.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Keepr::Tax < ActiveRecord::Base
|
2
4
|
self.table_name = 'keepr_taxes'
|
3
5
|
|
4
6
|
validates_presence_of :name, :value, :keepr_account_id
|
5
7
|
validates_numericality_of :value
|
6
8
|
|
7
|
-
belongs_to :keepr_account, :
|
8
|
-
has_many :keepr_accounts, :
|
9
|
+
belongs_to :keepr_account, class_name: 'Keepr::Account'
|
10
|
+
has_many :keepr_accounts, class_name: 'Keepr::Account', foreign_key: 'keepr_tax_id', dependent: :restrict_with_error
|
9
11
|
|
10
12
|
validate do |tax|
|
11
13
|
tax.errors.add(:keepr_account_id, :circular_reference) if tax.keepr_account.try(:keepr_tax) == tax
|