keepr 0.3.0 → 0.6.0
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.
- 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
|