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.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +14 -10
  3. data/Gemfile +2 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +3 -3
  6. data/Rakefile +3 -1
  7. data/ci/Gemfile-rails-4-2 +4 -4
  8. data/ci/Gemfile-rails-5-0 +4 -4
  9. data/ci/Gemfile-rails-5-1 +12 -0
  10. data/ci/Gemfile-rails-5-2 +12 -0
  11. data/ci/{Gemfile-rails-4-1 → Gemfile-rails-6-0} +4 -4
  12. data/keepr.gemspec +15 -14
  13. data/lib/generators/keepr/migration/migration_generator.rb +5 -3
  14. data/lib/generators/keepr/migration/templates/migration.rb +27 -25
  15. data/lib/keepr.rb +8 -0
  16. data/lib/keepr/account.rb +66 -61
  17. data/lib/keepr/account_export.rb +10 -14
  18. data/lib/keepr/active_record_extension.rb +10 -8
  19. data/lib/keepr/contact_export.rb +6 -7
  20. data/lib/keepr/cost_center.rb +3 -1
  21. data/lib/keepr/group.rb +25 -16
  22. data/lib/keepr/groups_creator.rb +11 -11
  23. data/lib/keepr/journal.rb +19 -16
  24. data/lib/keepr/journal_export.rb +10 -7
  25. data/lib/keepr/posting.rb +26 -21
  26. data/lib/keepr/tax.rb +4 -2
  27. data/lib/keepr/version.rb +3 -1
  28. data/spec/factories/account.rb +6 -4
  29. data/spec/factories/cost_center.rb +5 -3
  30. data/spec/factories/group.rb +5 -3
  31. data/spec/factories/tax.rb +7 -5
  32. data/spec/keepr/account_export_spec.rb +30 -27
  33. data/spec/keepr/account_spec.rb +92 -87
  34. data/spec/keepr/active_record_extension_spec.rb +38 -36
  35. data/spec/keepr/contact_export_spec.rb +20 -17
  36. data/spec/keepr/cost_center_spec.rb +9 -7
  37. data/spec/keepr/group_spec.rb +53 -49
  38. data/spec/keepr/groups_creator_spec.rb +7 -4
  39. data/spec/keepr/journal_export_spec.rb +76 -75
  40. data/spec/keepr/journal_spec.rb +48 -46
  41. data/spec/keepr/posting_spec.rb +25 -23
  42. data/spec/keepr/tax_spec.rb +21 -14
  43. data/spec/spec_helper.rb +13 -11
  44. data/spec/support/contact.rb +2 -0
  45. data/spec/support/document.rb +2 -0
  46. data/spec/support/ledger.rb +2 -0
  47. data/spec/support/spec_migration.rb +3 -1
  48. metadata +23 -22
@@ -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
- def to_s
9
- export.to_s
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
- { 'Konto' => account.number,
32
- 'Kontenbeschriftung' => account.name.slice(0,40)
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, :class_name => 'Keepr::Account', :as => :accountable, :dependent => :restrict_with_error
9
- has_many :keepr_postings, :class_name => 'Keepr::Posting', :through => :keepr_account, :dependent => :restrict_with_error
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, :class_name => 'Keepr::Account', :as => :accountable, :dependent => :restrict_with_error
14
- has_many :keepr_postings, :class_name => 'Keepr::Posting', :through => :keepr_accounts, :dependent => :restrict_with_error
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, :class_name => 'Keepr::Journal', :as => :accountable, :dependent => :restrict_with_error
20
+ has_many :keepr_journals, class_name: 'Keepr::Journal', as: :accountable, dependent: :restrict_with_error
19
21
 
20
- class_eval <<-EOT
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
- EOT
32
+ CODE
31
33
  end
32
34
 
33
35
  def has_keepr_postings
34
- has_many :keepr_postings, :class_name => 'Keepr::Posting', :as => :accountable, :dependent => :restrict_with_error
36
+ has_many :keepr_postings, class_name: 'Keepr::Posting', as: :accountable, dependent: :restrict_with_error
35
37
  end
36
38
  end
37
39
  end
@@ -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
@@ -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, :class_name => 'Keepr::Posting', :foreign_key => 'keepr_cost_center_id', :dependent => :restrict_with_error
9
+ has_many :keepr_postings, class_name: 'Keepr::Posting', foreign_key: 'keepr_cost_center_id', dependent: :restrict_with_error
8
10
  end
@@ -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 :orphan_strategy => :restrict
6
+ has_ancestry orphan_strategy: :restrict
5
7
 
6
- enum :target => [ :asset, :liability, :profit_and_loss ]
8
+ enum target: %i[asset liability profit_and_loss]
7
9
 
8
10
  validates_presence_of :name
9
11
 
10
- has_many :keepr_accounts, :class_name => 'Keepr::Account', :foreign_key => 'keepr_group_id', :dependent => :restrict_with_error
12
+ has_many :keepr_accounts, class_name: 'Keepr::Account', foreign_key: 'keepr_group_id', dependent: :restrict_with_error
11
13
 
12
- before_validation :get_from_parent
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(:is_result => true).first
19
+ where(is_result: true).first
18
20
  end
19
21
 
20
22
  def keepr_postings
21
23
  if is_result
22
- Keepr::Posting.joins(:keepr_account).where(:keepr_accounts => { :kind => [ Keepr::Account.kinds[:revenue],
23
- Keepr::Account.kinds[:expense] ] })
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.joins(:keepr_account => :keepr_group).merge(self.subtree)
31
+ Keepr::Posting
32
+ .joins(keepr_account: :keepr_group)
33
+ .merge(subtree)
26
34
  end
27
35
  end
28
36
 
29
- private
30
- def get_from_parent
31
- if self.parent
32
- self.target = self.parent.target
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
- if is_result
38
- # Attribute `is_result` allowed for liability target only
39
- errors.add :base, :liability_needed_for_result unless liability?
40
- end
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
@@ -1,7 +1,8 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  class Keepr::GroupsCreator
3
4
  def initialize(target)
4
- raise ArgumentError unless [ :balance, :profit_and_loss ].include?(target)
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', :target => :asset
13
- load 'liability.txt', :target => :liability
13
+ load 'asset.txt', target: :asset
14
+ load 'liability.txt', target: :liability
14
15
  when :profit_and_loss
15
- load 'profit_and_loss.txt', :target => :profit_and_loss
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(:name => name, :number => number)
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 == 0
38
+ if depth.zero?
39
39
  parents = []
40
40
  group = Keepr::Group.create!(attributes)
41
41
  else
@@ -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, :allow_blank => true
7
+ validates_uniqueness_of :number, allow_blank: true
6
8
 
7
- has_many :keepr_postings, -> { order(:amount => :desc) },
8
- :class_name => 'Keepr::Posting', :foreign_key => 'keepr_journal_id', :dependent => :destroy
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, :polymorphic => true
12
+ belongs_to :accountable, polymorphic: true
11
13
 
12
- accepts_nested_attributes_for :keepr_postings, :allow_destroy => true, :reject_if => :all_blank
14
+ accepts_nested_attributes_for :keepr_postings, allow_destroy: true, reject_if: :all_blank
13
15
 
14
- default_scope { order({:date => :desc}, {:id => :desc}) }
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
- if self.permanent_was
55
- # If marked as permanent, no changes are allowed
56
- errors.add :base, :changes_not_allowed
57
-
58
- if ActiveRecord::VERSION::MAJOR < 5
59
- false
60
- else
61
- throw :abort
62
- end
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
@@ -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(:keepr_postings => :keepr_account).reorder(:date, :id).each do |journal|
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.sort_by(&:amount).last
34
+ main_posting ||= journal.keepr_postings.max_by(&:amount)
33
35
 
34
- journal.keepr_postings.sort_by { |p| [ p.side == main_posting.side ? 1 : 0, -p.amount ] }.map do |posting|
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
- { 'Umsatz (ohne Soll/Haben-Kz)' => posting.amount,
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
@@ -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, :class_name => 'Keepr::Account'
8
- belongs_to :keepr_journal, :class_name => 'Keepr::Journal'
9
- belongs_to :keepr_cost_center, :class_name => 'Keepr::CostCenter'
10
- belongs_to :accountable, :polymorphic => true
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 < 0 ? SIDE_CREDIT : SIDE_DEBIT) if 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 if amount
33
+ self.raw_amount = -amount.to_d
29
34
  elsif debit?
30
- self.raw_amount = amount if 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
- if credit?
61
- self.raw_amount = -value
62
- else
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
- if keepr_cost_center
70
- unless keepr_account.profit_and_loss?
71
- # allowed for expense or revenue accounts only
72
- errors.add :keepr_cost_center_id, :allowed_for_expense_or_revenue_only
73
- end
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
@@ -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, :class_name => 'Keepr::Account'
8
- has_many :keepr_accounts, :class_name => 'Keepr::Account', :foreign_key => 'keepr_tax_id', :dependent => :restrict_with_error
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