stockor-core 0.2
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +6 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +674 -0
- data/README.md +88 -0
- data/Rakefile +58 -0
- data/config/database.yml +9 -0
- data/db/migrate/20120110142845_create_skr_sequential_ids.rb +35 -0
- data/db/migrate/20140202185309_create_skr_gl_accounts.rb +15 -0
- data/db/migrate/20140202193316_create_skr_gl_periods.rb +16 -0
- data/db/migrate/20140202193318_create_skr_gl_transactions.rb +14 -0
- data/db/migrate/20140202193319_create_skr_gl_postings.rb +16 -0
- data/db/migrate/20140202193700_create_skr_gl_manual_entries.rb +13 -0
- data/db/migrate/20140213040608_create_skr_payment_terms.rb +16 -0
- data/db/migrate/20140220031700_create_skr_addresses.rb +19 -0
- data/db/migrate/20140220031800_create_skr_locations.rb +19 -0
- data/db/migrate/20140220190836_create_skr_vendors.rb +22 -0
- data/db/migrate/20140220203029_create_skr_customers.rb +22 -0
- data/db/migrate/20140224034759_create_skr_skus.rb +22 -0
- data/db/migrate/20140225032853_create_skr_sku_locs.rb +21 -0
- data/db/migrate/20140320030501_create_skr_uoms.rb +19 -0
- data/db/migrate/20140321031604_create_skr_sku_vendors.rb +18 -0
- data/db/migrate/20140322012143_create_skr_ia_reasons.rb +14 -0
- data/db/migrate/20140322014401_create_skr_inventory_adjustments.rb +16 -0
- data/db/migrate/20140322023453_create_skr_ia_lines.rb +18 -0
- data/db/migrate/20140322035024_create_skr_sku_trans.rb +21 -0
- data/db/migrate/20140322223912_create_skr_sales_orders.rb +27 -0
- data/db/migrate/20140322223920_create_skr_so_lines.rb +25 -0
- data/db/migrate/20140323001446_create_so_details_view.rb +81 -0
- data/db/migrate/20140327202102_create_skr_purchase_orders.rb +20 -0
- data/db/migrate/20140327202107_create_skr_po_lines.rb +25 -0
- data/db/migrate/20140327202207_create_skr_pick_tickets.rb +16 -0
- data/db/migrate/20140327202209_create_skr_pt_lines.rb +23 -0
- data/db/migrate/20140327224000_create_skr_invoices.rb +25 -0
- data/db/migrate/20140327224002_create_skr_inv_lines.rb +23 -0
- data/db/migrate/20140330232808_create_skr_sku_loc_details_view.rb +31 -0
- data/db/migrate/20140330232810_create_skr_sku_qty_details_view.rb +48 -0
- data/db/migrate/20140400164729_create_skr_vouchers.rb +22 -0
- data/db/migrate/20140400164733_create_skr_vo_lines.rb +21 -0
- data/db/migrate/20140401164729_create_skr_po_receipt.rb +16 -0
- data/db/migrate/20140401164740_create_skr_por_line.rb +21 -0
- data/db/migrate/20140422024010_create_skr_inv_details_view.rb +42 -0
- data/lib/generators/stockor/migrations/install_generator.rb +42 -0
- data/lib/skr/address.rb +97 -0
- data/lib/skr/business_entity.rb +25 -0
- data/lib/skr/concerns/acts_as_uom.rb +47 -0
- data/lib/skr/concerns/all.rb +30 -0
- data/lib/skr/concerns/association_extensions.rb +85 -0
- data/lib/skr/concerns/attr_accessor_with_default.rb +54 -0
- data/lib/skr/concerns/code_identifier.rb +43 -0
- data/lib/skr/concerns/export_associations.rb +52 -0
- data/lib/skr/concerns/export_join_tables.rb +39 -0
- data/lib/skr/concerns/export_methods.rb +104 -0
- data/lib/skr/concerns/export_scope.rb +66 -0
- data/lib/skr/concerns/exported_limit_evaluator.rb +17 -0
- data/lib/skr/concerns/gl_tran_extensions.rb +18 -0
- data/lib/skr/concerns/has_gl_transaction.rb +67 -0
- data/lib/skr/concerns/has_sku_loc_lines.rb +47 -0
- data/lib/skr/concerns/immutable_model.rb +32 -0
- data/lib/skr/concerns/inv_extensions.rb +24 -0
- data/lib/skr/concerns/is_order_like.rb +47 -0
- data/lib/skr/concerns/is_sku_loc_line.rb +65 -0
- data/lib/skr/concerns/json_attribute_access.rb +55 -0
- data/lib/skr/concerns/locked_fields.rb +84 -0
- data/lib/skr/concerns/pt_extensions.rb +22 -0
- data/lib/skr/concerns/pub_sub.rb +105 -0
- data/lib/skr/concerns/queries.rb +20 -0
- data/lib/skr/concerns/random_hash_code.rb +40 -0
- data/lib/skr/concerns/sanitize_json.rb +49 -0
- data/lib/skr/concerns/sku_extensions.rb +52 -0
- data/lib/skr/concerns/so_extensions.rb +30 -0
- data/lib/skr/concerns/state_machine.rb +62 -0
- data/lib/skr/concerns/track_modifications.rb +48 -0
- data/lib/skr/concerns/visible_id_identifier.rb +53 -0
- data/lib/skr/core.rb +30 -0
- data/lib/skr/core/configuration.rb +87 -0
- data/lib/skr/core/db.rb +82 -0
- data/lib/skr/core/db/migration_helpers.rb +178 -0
- data/lib/skr/core/db/migrations.rb +15 -0
- data/lib/skr/core/db/seed.rb +46 -0
- data/lib/skr/core/db/seed/chart_of_accounts.yml +168 -0
- data/lib/skr/core/db/seed/payment_terms.yml +60 -0
- data/lib/skr/core/logger.rb +36 -0
- data/lib/skr/core/numbers.rb +71 -0
- data/lib/skr/core/rails_engine.rb +5 -0
- data/lib/skr/core/standard_pricing_provider.rb +15 -0
- data/lib/skr/core/strings.rb +57 -0
- data/lib/skr/core/testing.rb +11 -0
- data/lib/skr/core/testing/assertions.rb +44 -0
- data/lib/skr/core/testing/fixtures.rb +25 -0
- data/lib/skr/core/testing/fixtures/skr/addresses.yml +53 -0
- data/lib/skr/core/testing/fixtures/skr/customers.yml +43 -0
- data/lib/skr/core/testing/fixtures/skr/gl_accounts.yml +86 -0
- data/lib/skr/core/testing/fixtures/skr/gl_manual_entries.yml +4 -0
- data/lib/skr/core/testing/fixtures/skr/gl_periods.yml +26 -0
- data/lib/skr/core/testing/fixtures/skr/gl_postings.yml +88 -0
- data/lib/skr/core/testing/fixtures/skr/gl_transactions.yml +35 -0
- data/lib/skr/core/testing/fixtures/skr/ia_lines.yml +7 -0
- data/lib/skr/core/testing/fixtures/skr/ia_reasons.yml +15 -0
- data/lib/skr/core/testing/fixtures/skr/inv_lines.yml +22 -0
- data/lib/skr/core/testing/fixtures/skr/inventory_adjustments.yml +6 -0
- data/lib/skr/core/testing/fixtures/skr/invoices.yml +10 -0
- data/lib/skr/core/testing/fixtures/skr/locations.yml +24 -0
- data/lib/skr/core/testing/fixtures/skr/payment_terms.yml +42 -0
- data/lib/skr/core/testing/fixtures/skr/pick_tickets.yml +4 -0
- data/lib/skr/core/testing/fixtures/skr/po_lines.yml +44 -0
- data/lib/skr/core/testing/fixtures/skr/po_receipts.yml +5 -0
- data/lib/skr/core/testing/fixtures/skr/por_lines.yml +13 -0
- data/lib/skr/core/testing/fixtures/skr/pt_lines.yml +12 -0
- data/lib/skr/core/testing/fixtures/skr/purchase_orders.yml +16 -0
- data/lib/skr/core/testing/fixtures/skr/sales_orders.yml +32 -0
- data/lib/skr/core/testing/fixtures/skr/sku_locs.yml +78 -0
- data/lib/skr/core/testing/fixtures/skr/sku_trans.yml +9 -0
- data/lib/skr/core/testing/fixtures/skr/sku_vendors.yml +35 -0
- data/lib/skr/core/testing/fixtures/skr/skus.yml +50 -0
- data/lib/skr/core/testing/fixtures/skr/so_lines.yml +71 -0
- data/lib/skr/core/testing/fixtures/skr/uoms.yml +61 -0
- data/lib/skr/core/testing/fixtures/skr/vendors.yml +48 -0
- data/lib/skr/core/testing/fixtures/skr/vo_lines.yml +12 -0
- data/lib/skr/core/testing/fixtures/skr/vouchers.yml +8 -0
- data/lib/skr/core/testing/helper.rb +18 -0
- data/lib/skr/core/testing/test_case.rb +17 -0
- data/lib/skr/core/version.rb +5 -0
- data/lib/skr/customer.rb +34 -0
- data/lib/skr/gl_account.rb +56 -0
- data/lib/skr/gl_manual_entry.rb +31 -0
- data/lib/skr/gl_period.rb +13 -0
- data/lib/skr/gl_posting.rb +54 -0
- data/lib/skr/gl_transaction.rb +174 -0
- data/lib/skr/ia_line.rb +129 -0
- data/lib/skr/ia_reason.rb +16 -0
- data/lib/skr/inv_line.rb +90 -0
- data/lib/skr/inventory_adjustment.rb +60 -0
- data/lib/skr/invoice.rb +159 -0
- data/lib/skr/location.rb +31 -0
- data/lib/skr/model.rb +37 -0
- data/lib/skr/null_user.rb +30 -0
- data/lib/skr/payment_term.rb +30 -0
- data/lib/skr/pick_ticket.rb +71 -0
- data/lib/skr/po_line.rb +69 -0
- data/lib/skr/po_receipt.rb +51 -0
- data/lib/skr/por_line.rb +80 -0
- data/lib/skr/pt_line.rb +74 -0
- data/lib/skr/purchase_order.rb +112 -0
- data/lib/skr/sales_order.rb +159 -0
- data/lib/skr/sequential_id.rb +23 -0
- data/lib/skr/sku.rb +99 -0
- data/lib/skr/sku_loc.rb +94 -0
- data/lib/skr/sku_tran.rb +111 -0
- data/lib/skr/sku_vendor.rb +26 -0
- data/lib/skr/so_line.rb +159 -0
- data/lib/skr/uom.rb +63 -0
- data/lib/skr/user_proxy.rb +60 -0
- data/lib/skr/validators/all.rb +2 -0
- data/lib/skr/validators/email.rb +17 -0
- data/lib/skr/validators/set.rb +18 -0
- data/lib/skr/vendor.rb +33 -0
- data/lib/skr/vo_line.rb +35 -0
- data/lib/skr/voucher.rb +119 -0
- data/lib/stockor/core.rb +9 -0
- data/stockor-core.gemspec +37 -0
- data/tasks/migrations.rake +23 -0
- data/tasks/publish.rake +8 -0
- data/test/address_test.rb +57 -0
- data/test/concerns/attr_with_default_test.rb +45 -0
- data/test/concerns/code_identifier_test.rb +46 -0
- data/test/concerns/export_associations_test.rb +7 -0
- data/test/concerns/export_methods_test.rb +31 -0
- data/test/concerns/export_scope_test.rb +16 -0
- data/test/concerns/exported_limits_test.rb +47 -0
- data/test/concerns/json_attribute_access_test.rb +27 -0
- data/test/concerns/pub_sub_test.rb +83 -0
- data/test/concerns/sanitize_json_test.rb +47 -0
- data/test/core/configuration_test.rb +24 -0
- data/test/core/numbers_test.rb +26 -0
- data/test/core/strings_test.rb +41 -0
- data/test/customer_test.rb +34 -0
- data/test/gl_account_test.rb +23 -0
- data/test/gl_manual_entry_test.rb +17 -0
- data/test/gl_period_test.rb +12 -0
- data/test/gl_posting_test.rb +39 -0
- data/test/gl_transaction_test.rb +58 -0
- data/test/ia_line_test.rb +68 -0
- data/test/ia_reason_test.rb +11 -0
- data/test/inv_line_test.rb +58 -0
- data/test/inventory_adjustment_test.rb +72 -0
- data/test/invoice_test.rb +63 -0
- data/test/location_test.rb +10 -0
- data/test/payment_term_test.rb +25 -0
- data/test/pick_ticket_test.rb +27 -0
- data/test/po_line_test.rb +36 -0
- data/test/po_receipt_test.rb +93 -0
- data/test/por_line_test.rb +79 -0
- data/test/pt_line_test.rb +29 -0
- data/test/purchase_order_test.rb +44 -0
- data/test/sales_order_test.rb +74 -0
- data/test/sku_loc_test.rb +50 -0
- data/test/sku_test.rb +45 -0
- data/test/sku_tran_test.rb +21 -0
- data/test/sku_vendor_test.rb +13 -0
- data/test/so_line_test.rb +89 -0
- data/test/test_helper.rb +1 -0
- data/test/uom_test.rb +14 -0
- data/test/vendor_test.rb +35 -0
- data/test/vo_line_test.rb +20 -0
- data/test/voucher_test.rb +35 -0
- data/yard_ext/all.rb +9 -0
- data/yard_ext/code_identifier_handler.rb +33 -0
- data/yard_ext/concern_meta_methods.rb +60 -0
- data/yard_ext/config_options.rb +27 -0
- data/yard_ext/exported_scope.rb +4 -0
- data/yard_ext/immutable_handler.rb +17 -0
- data/yard_ext/json_attr_accessor.rb +22 -0
- data/yard_ext/locked_fields_handler.rb +21 -0
- data/yard_ext/templates/default/layout/html/layout.erb +20 -0
- data/yard_ext/templates/default/method_details/html/github_link.erb +1 -0
- data/yard_ext/templates/default/method_details/setup.rb +3 -0
- data/yard_ext/validators.rb +1 -0
- data/yard_ext/visible_id_handler.rb +38 -0
- metadata +448 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
# Records a manual entry into the General Leger.
|
|
4
|
+
# Most GL entries are triggered by system events such as an Inventory Receipt, or Invoice payment.
|
|
5
|
+
# A manual entry differs in that it's performed, well *Manually*.
|
|
6
|
+
# It's usually used to balance ledger accounts or as part of closing an accounting period.
|
|
7
|
+
class GlManualEntry < Skr::Model
|
|
8
|
+
|
|
9
|
+
has_visible_id
|
|
10
|
+
|
|
11
|
+
is_immutable
|
|
12
|
+
|
|
13
|
+
has_one :gl_transaction, :as=>:source,
|
|
14
|
+
inverse_of: :source, export: { writable: true }
|
|
15
|
+
|
|
16
|
+
validates :gl_transaction, presence: true
|
|
17
|
+
|
|
18
|
+
before_create :copy_notes_to_transaction
|
|
19
|
+
|
|
20
|
+
def gl_transaction=( transaction )
|
|
21
|
+
super
|
|
22
|
+
copy_notes_to_transaction
|
|
23
|
+
end
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def copy_notes_to_transaction
|
|
27
|
+
self.gl_transaction.description = self.notes[0..100]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
class GlPosting < Skr::Model
|
|
3
|
+
|
|
4
|
+
is_immutable
|
|
5
|
+
|
|
6
|
+
belongs_to :gl_transaction
|
|
7
|
+
|
|
8
|
+
before_validation :cache_related_attributes
|
|
9
|
+
|
|
10
|
+
validates :gl_transaction, set: true
|
|
11
|
+
validates :account_number, numericality: true, length: { :is=>6 }
|
|
12
|
+
validates :amount, numericality: true, presence: true
|
|
13
|
+
validate :ensure_accounting_validity, on: :create
|
|
14
|
+
|
|
15
|
+
scope :applying_to_period, ->(period){ where( '(period <= :period and year = :year) or (year < :year)',
|
|
16
|
+
{ period: period.period, year: period.year } ) }
|
|
17
|
+
scope :matching, ->(period, account_mask){
|
|
18
|
+
applying_to_period( period ).where('account_number like ?', account_mask )
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def account=(acct)
|
|
22
|
+
@account = acct
|
|
23
|
+
assign_account_number
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def location=(location)
|
|
27
|
+
@location = location
|
|
28
|
+
assign_account_number
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def assign_account_number
|
|
34
|
+
self.account_number = @account.number_for_location(@location) if @account && @location
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def ensure_accounting_validity
|
|
38
|
+
unless self.gl_transaction.new_record? #postings_create_ok?
|
|
39
|
+
self.errors.add( :gl_transaction, "does not accept new postings" )
|
|
40
|
+
end
|
|
41
|
+
if @account && ! @account.is_active?
|
|
42
|
+
self.errors.add(:account, "is not active")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cache_related_attributes
|
|
48
|
+
assign_account_number
|
|
49
|
+
self.year = gl_transaction.period.year
|
|
50
|
+
self.period = gl_transaction.period.period
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
# A transaction is a record of a business event that has financial consequences.
|
|
4
|
+
# It consists of an at least one credit and at least one debit
|
|
5
|
+
# Transactions can be nested, with each level compacting all the entries that were made on it
|
|
6
|
+
#
|
|
7
|
+
# require 'skr/core'
|
|
8
|
+
# customer = Customer.find_by_code "MONEYBAGS"
|
|
9
|
+
# GlTransaction.record( source: invoice, description: "Invoice Example" ) do | transaction |
|
|
10
|
+
# transaction.location = Location.default # <- could also specify in record's options
|
|
11
|
+
# Sku.where( code: ['HAT','STRING'] ).each do | sku |
|
|
12
|
+
# transaction.add_posting( amount: sku.default_price,
|
|
13
|
+
# debit: sku.gl_asset_account,
|
|
14
|
+
# credit: customer.gl_receivables_account )
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
class GlTransaction < Skr::Model
|
|
19
|
+
|
|
20
|
+
is_immutable
|
|
21
|
+
|
|
22
|
+
# A Transaction must refer to another record, such as Invoice, Inventory Adjustment, or a Manual Posting.
|
|
23
|
+
belongs_to :source, :polymorphic=>true
|
|
24
|
+
|
|
25
|
+
# Each transaction belongs to an accounting period
|
|
26
|
+
belongs_to :period, :class_name=>'Skr::GlPeriod', export: true
|
|
27
|
+
|
|
28
|
+
has_many :credits, ->{ where({ is_debit: false }) }, class_name: 'Skr::GlPosting',
|
|
29
|
+
extend: Concerns::GlTran::Postings,
|
|
30
|
+
inverse_of: :gl_transaction, export: { writable: true }
|
|
31
|
+
|
|
32
|
+
# Must equal credits, checked by the {#ensure_postings_correct} validation
|
|
33
|
+
has_many :debits, ->{ where({ is_debit: true }) }, class_name: 'Skr::GlPosting',
|
|
34
|
+
extend: Concerns::GlTran::Postings,
|
|
35
|
+
inverse_of: :gl_transaction, export: { writable: true }
|
|
36
|
+
|
|
37
|
+
before_validation :set_defaults
|
|
38
|
+
validate :ensure_postings_correct
|
|
39
|
+
validates :source, :period, :set=>true
|
|
40
|
+
validates :description, :presence=>true
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Add a debit/credit pair to the transaction with amount
|
|
44
|
+
# @param amount [BigDecimal] the amount to apply to each posting
|
|
45
|
+
# @param debit [GlAccount]
|
|
46
|
+
# @param credit [GlAccount]
|
|
47
|
+
def add_posting( amount: nil, debit: nil, credit: nil )
|
|
48
|
+
Skr::Core.logger.debug "GlTransaction add_posting #{debit} : #{credit}"
|
|
49
|
+
self.credits.build( location: @location, is_debit: false,
|
|
50
|
+
account: credit, amount: amount )
|
|
51
|
+
self.debits.build( location: @location, is_debit: true,
|
|
52
|
+
account: debit, amount: amount * -1 )
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Passes the location onto the postings.
|
|
56
|
+
# @param location [Location]
|
|
57
|
+
def location=(location)
|
|
58
|
+
@location = location
|
|
59
|
+
each_posting do | posting |
|
|
60
|
+
posting.location = location
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @yield [GlPosting] each posting associated with the Transaction
|
|
65
|
+
def each_posting
|
|
66
|
+
self.credits.each{ |posting| yield posting }
|
|
67
|
+
self.debits.each{ |posting| yield posting }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [GlTransaction] the current transaction that's in progress
|
|
71
|
+
def self.current
|
|
72
|
+
glt = Thread.current[:gl_transaction]
|
|
73
|
+
glt ? glt.last : nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Start a new nested GlTransaction
|
|
77
|
+
# When a transaction is created, it can have
|
|
78
|
+
# @return [GlTransaction] new transaction
|
|
79
|
+
# @yield [GlTransaction] new transaction
|
|
80
|
+
def self.record( attributes={} )
|
|
81
|
+
Thread.current[:gl_transaction] ||= []
|
|
82
|
+
glt = GlTransaction.new( attributes )
|
|
83
|
+
Thread.current[:gl_transaction].push( glt )
|
|
84
|
+
Skr::Core.logger.debug "B4 GlTransaction"
|
|
85
|
+
results = yield glt
|
|
86
|
+
Thread.current[:gl_transaction].pop
|
|
87
|
+
if results
|
|
88
|
+
if results.is_a?(Hash) && results.has_key?(:attributes)
|
|
89
|
+
glt.assign_attributes( results[:attributes] )
|
|
90
|
+
end
|
|
91
|
+
glt._save_recorded
|
|
92
|
+
Skr::Core.logger.debug "AF GlTransaction new=#{glt.new_record?} #{glt.errors.full_messages}"
|
|
93
|
+
end
|
|
94
|
+
return glt
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param owner [Skr::Model]
|
|
98
|
+
# @param amount [BigDecimal]
|
|
99
|
+
# @param debit [GlAccount]
|
|
100
|
+
# @param credit [GlAccount]
|
|
101
|
+
# @param options [Hash] options to pass to the [GlTransaction] if one is created
|
|
102
|
+
def self.push_or_save( owner: nil, amount: nil, debit:nil, credit:nil, options:{} )
|
|
103
|
+
if glt = self.current # we push
|
|
104
|
+
glt.add_posting( amount: amount, debit: debit, credit: credit )
|
|
105
|
+
else
|
|
106
|
+
options.merge!({
|
|
107
|
+
source: owner,
|
|
108
|
+
location: options[:location] || owner.location
|
|
109
|
+
})
|
|
110
|
+
glt = GlTransaction.new( options )
|
|
111
|
+
glt.add_posting( amount: amount, debit: debit, credit: credit )
|
|
112
|
+
glt.save
|
|
113
|
+
end
|
|
114
|
+
glt
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @private
|
|
118
|
+
def _save_recorded
|
|
119
|
+
compact( 'debits' )
|
|
120
|
+
compact( 'credits' )
|
|
121
|
+
self.save if self.credits.any? || self.debits.any?
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def compact( assoc_name )
|
|
128
|
+
accounts = self.send( assoc_name ).to_a
|
|
129
|
+
self.send( assoc_name + "=", [] )
|
|
130
|
+
account_numbers = accounts.group_by{ |posting| posting.account_number }
|
|
131
|
+
account_numbers.each do | number, matching |
|
|
132
|
+
amount = matching.sum(&:amount)
|
|
133
|
+
self.send( assoc_name ).build({
|
|
134
|
+
account_number: number,
|
|
135
|
+
is_debit: ( assoc_name == "debits" ),
|
|
136
|
+
amount: amount,
|
|
137
|
+
})
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def ensure_postings_correct
|
|
142
|
+
if debits.total != ( -1 * credits.total )
|
|
143
|
+
self.errors.add(:credits, "must equal debits")
|
|
144
|
+
self.errors.add(:debits, "must equal credits")
|
|
145
|
+
return false
|
|
146
|
+
end
|
|
147
|
+
true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def set_defaults
|
|
151
|
+
self.period ||= GlPeriod.current
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# def ensure_postings_exist
|
|
155
|
+
# if self.new_record? and self.postings.empty?
|
|
156
|
+
# 2.times { self.postings.build }
|
|
157
|
+
# end
|
|
158
|
+
# end
|
|
159
|
+
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
__END__
|
|
164
|
+
|
|
165
|
+
# export_join_tables :details
|
|
166
|
+
# export_scope :with_details_for, lambda { | acct |
|
|
167
|
+
# acct = acct.gsub(/\D/,'') + '%'
|
|
168
|
+
# cs = "case when details.debit_account_number like '#{acct}' then details.debit_amount " +
|
|
169
|
+
# "when details.credit_account_number like '#{acct}' then details.credit_amount else 0 end"
|
|
170
|
+
# window = "#{cs} as amount, sum(#{cs}) over (order by created_at) as balance"
|
|
171
|
+
# joins('join gl_transaction_details as details on details.gl_transaction_id = gl_transactions.id')
|
|
172
|
+
# .select("gl_transactions.*, details.*, #{window}")
|
|
173
|
+
# .where("debit_account_number like :acct or credit_account_number like :acct", acct: acct)
|
|
174
|
+
# }
|
data/lib/skr/ia_line.rb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
# An Inventory Adjustment Line.
|
|
4
|
+
# Each model contains the {SkuLoc}, cost, UOM, and qty for a {Sku} to adjust
|
|
5
|
+
#
|
|
6
|
+
# An adjustment starts out in the "pending" state, then when it moves to the "applied"
|
|
7
|
+
# state, each line creates an {SkuTran} record that adjusts the inventory in or out.
|
|
8
|
+
class IaLine < Skr::Model
|
|
9
|
+
|
|
10
|
+
acts_as_uom
|
|
11
|
+
|
|
12
|
+
belongs_to :inventory_adjustment
|
|
13
|
+
belongs_to :sku_loc, export: true
|
|
14
|
+
has_one :sku, :through => :sku_loc, export: true
|
|
15
|
+
|
|
16
|
+
has_one :sku_tran, :as=>:origin
|
|
17
|
+
scope :unapplied, lambda { includes(:sku_tran).references(:sku_tran).where( SkuTran.table_name+'.id is null' ) }
|
|
18
|
+
|
|
19
|
+
export_methods :sku_loc_qty, :sku_loc_mac, :optional=>false
|
|
20
|
+
|
|
21
|
+
validates :sku_loc, set: true
|
|
22
|
+
validates :qty, numericality: true
|
|
23
|
+
validates :uom_code, :uom_size, presence: true
|
|
24
|
+
validate :ensure_cost_set_properly
|
|
25
|
+
|
|
26
|
+
before_save :set_cost_from_sku_loc
|
|
27
|
+
before_destroy :ensure_adjustment_isnt_applied
|
|
28
|
+
before_update :ensure_adjustment_isnt_applied
|
|
29
|
+
delegate_and_export :sku_code, :sku_description
|
|
30
|
+
|
|
31
|
+
has_locks :adjusting
|
|
32
|
+
|
|
33
|
+
# @return [Fixnum] The qty available on the location, expressed in terms of the UOM
|
|
34
|
+
def sku_loc_qty
|
|
35
|
+
( self.uom_size && self.sku_loc ) ? BigDecimal.new( self.sku_loc.qty ) / self.uom_size : 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [BigDecimal] the current moving average cost (mac) on the location, expressed in terms of the UOM
|
|
39
|
+
def sku_loc_mac
|
|
40
|
+
self.sku_loc ? ( self.sku_loc.mac * self.uom_size ) : 0
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Boolean] has the line been applied
|
|
44
|
+
def is_applied?
|
|
45
|
+
sku_tran.present?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] is the qty negative?
|
|
49
|
+
def is_removing_qty?
|
|
50
|
+
qty && qty <=0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The qty for the line expressed in terms of the single UOM
|
|
54
|
+
def ea_qty
|
|
55
|
+
self.uom_size * self.qty
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [BigDecimal] the total value of the line
|
|
59
|
+
def total
|
|
60
|
+
self.ledger_cost * self.qty
|
|
61
|
+
end
|
|
62
|
+
#alias_method :ledger_value, :total
|
|
63
|
+
|
|
64
|
+
# @return [BigDecimal] either the current MAC for the sku's location or the cost that was manually set
|
|
65
|
+
def ledger_cost
|
|
66
|
+
if cost_was_set? || is_applied?
|
|
67
|
+
self.cost
|
|
68
|
+
else
|
|
69
|
+
self.sku_loc_mac
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# copies the cost from the sku_loc to the {#cost} field
|
|
74
|
+
def set_cost_from_sku_loc
|
|
75
|
+
if ! cost_was_set?
|
|
76
|
+
self.cost = self.sku_loc_mac
|
|
77
|
+
end
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Perform the adjustment. Requires adjusting to be unlocked and {#is_applied?} must be false
|
|
82
|
+
#
|
|
83
|
+
# It creates a {SkuTran} to adjust the inventory, and allocates available qty to the {SoLine}
|
|
84
|
+
def adjust_qty!
|
|
85
|
+
if ! is_adjusting_unlocked? || is_applied?
|
|
86
|
+
raise "Unable to apply line, either not approved or previously applied"
|
|
87
|
+
end
|
|
88
|
+
set_cost_from_sku_loc
|
|
89
|
+
Core.logger.debug( "Adjusting #{self.qty} #{combined_uom} of #{sku_code} into stock")
|
|
90
|
+
self.build_sku_tran({
|
|
91
|
+
:origin=>self, :qty => self.qty, :sku_loc=>self.sku_loc,
|
|
92
|
+
origin_description: "IA #{self.inventory_adjustment.visible_id}:#{self.sku.code}",
|
|
93
|
+
cost: total, uom: self.uom,
|
|
94
|
+
allocate_after_save: true,
|
|
95
|
+
debit_gl_account: self.inventory_adjustment.reason.gl_account,
|
|
96
|
+
credit_gl_account: self.sku.gl_asset_account
|
|
97
|
+
})
|
|
98
|
+
self.sku_tran.save unless self.new_record?
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Cost cannot be set if the qty is negative
|
|
106
|
+
def ensure_cost_set_properly
|
|
107
|
+
if cost_was_set? && is_removing_qty?
|
|
108
|
+
errors.add(:cost,'cannot be set if removing qty')
|
|
109
|
+
return false
|
|
110
|
+
end
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def ensure_adjustment_isnt_applied
|
|
115
|
+
if inventory_adjustment.applied?
|
|
116
|
+
errors.add(:base,'cannot be modified after adjustment is approved and applied')
|
|
117
|
+
return false
|
|
118
|
+
else
|
|
119
|
+
return true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
end # Skr module
|
data/lib/skr/inv_line.rb
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
class InvLine < Skr::Model
|
|
4
|
+
|
|
5
|
+
acts_as_uom
|
|
6
|
+
is_immutable
|
|
7
|
+
is_sku_loc_line parent: "invoice"
|
|
8
|
+
|
|
9
|
+
locked_fields :qty, :sku_loc_id, :so_line
|
|
10
|
+
belongs_to :invoice
|
|
11
|
+
|
|
12
|
+
belongs_to :so_line, export: true
|
|
13
|
+
belongs_to :pt_line, :inverse_of=>:inv_line, export: true
|
|
14
|
+
belongs_to :sku_loc, export: true
|
|
15
|
+
|
|
16
|
+
has_one :sku, :through => :sku_loc, export: true
|
|
17
|
+
has_one :location, :through => :sku_loc
|
|
18
|
+
has_one :sku_tran, :as=>:origin
|
|
19
|
+
|
|
20
|
+
validates :sku_loc, set: true
|
|
21
|
+
|
|
22
|
+
validates :price, :qty, :numericality=>true
|
|
23
|
+
validates :uom_code, :sku_code, :description, :uom_size, :presence=>true
|
|
24
|
+
|
|
25
|
+
before_validation :set_defaults
|
|
26
|
+
before_save :adjust_inventory
|
|
27
|
+
|
|
28
|
+
scope :with_details, lambda { |should_use=true |
|
|
29
|
+
compose_query_using_detail_view( view: 'inv_details', join_to: 'inv_line_id' ) if should_use
|
|
30
|
+
}, export: true
|
|
31
|
+
|
|
32
|
+
scope :summarize_by, lambda { | values |
|
|
33
|
+
select( "sum(inv_lines.qty) as qty, count(*) as num_sales, sum(inv_lines.price) as price, avg(inv_lines.price) as avg_price, s.code as sku_code, s.description, s.id as sku_id, 1 as uom_size, 'EA' as uom_code")
|
|
34
|
+
.joins("join sku_locs sl on sl.id = inv_lines.sku_loc_id join skus s on s.id = sl.sku_id")
|
|
35
|
+
.where(["inv_lines.created_at between ? and ?",values['from'], values['to'] ])
|
|
36
|
+
.group('s.code,s.description, s.id')
|
|
37
|
+
}, export: true
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_defaults
|
|
43
|
+
# puts "set_defaults #{self}"
|
|
44
|
+
line = [ self.pt_line, self.so_line ].detect{ |l| ! l.blank? }
|
|
45
|
+
if line
|
|
46
|
+
self.uom = line.uom if self.uom.blank?
|
|
47
|
+
self.sku_code ||= line.sku_code
|
|
48
|
+
self.description ||= line.description
|
|
49
|
+
self.sku_loc ||= line.sku_loc
|
|
50
|
+
self.qty ||= line.qty
|
|
51
|
+
self.so_line ||= line.so_line
|
|
52
|
+
elsif sku
|
|
53
|
+
self.uom = sku.uoms.default if self.uom_code.blank?
|
|
54
|
+
self.sku_code ||= sku.code
|
|
55
|
+
self.description ||= sku.description
|
|
56
|
+
self.sku_loc_id ||= sku.sku_locs.for_location( self.invoice.location ).id if self.invoice && self.invoice.location
|
|
57
|
+
end
|
|
58
|
+
if !price && invoice && invoice.customer && sku_loc && uom.present?
|
|
59
|
+
self.price = Core.config.pricing_provider.price(
|
|
60
|
+
sku_loc:sku_loc, customer:invoice.customer,
|
|
61
|
+
uom:uom, qty: qty )
|
|
62
|
+
end
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def adjust_inventory
|
|
67
|
+
debit = self.sku.gl_asset_account
|
|
68
|
+
credit = self.invoice.customer.gl_receivables_account
|
|
69
|
+
if self.sku.does_track_inventory?
|
|
70
|
+
self.build_sku_tran({
|
|
71
|
+
origin: self, qty: self.qty*-1, sku_loc: self.sku_loc, uom: self.uom,
|
|
72
|
+
mac: 0, cost: self.extended_price,
|
|
73
|
+
origin_description: "INV #{self.invoice.visible_id}:#{self.sku.code}",
|
|
74
|
+
debit_gl_account: debit, credit_gl_account: credit
|
|
75
|
+
})
|
|
76
|
+
else
|
|
77
|
+
GlTransaction.push_or_save(
|
|
78
|
+
owner: self, amount: self.total,
|
|
79
|
+
debit: debit, credit: credit
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
end # Skr module
|