stockor 0.1.5
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 +4 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +220 -0
- data/Guardfile +13 -0
- data/README.md +7 -0
- data/Rakefile +7 -0
- data/client/skr/Extension.coffee +12 -0
- data/client/skr/components/.gitkeep +0 -0
- data/client/skr/components/address/Address.coffee +21 -0
- data/client/skr/components/address/address.html +20 -0
- data/client/skr/index.js +21 -0
- data/client/skr/models/Address.coffee +17 -0
- data/client/skr/models/Base.coffee +8 -0
- data/client/skr/models/Customer.coffee +26 -0
- data/client/skr/models/GlAccount.coffee +10 -0
- data/client/skr/models/GlManualEntry.coffee +11 -0
- data/client/skr/models/GlPeriod.coffee +10 -0
- data/client/skr/models/GlPosting.coffee +15 -0
- data/client/skr/models/GlTransaction.coffee +16 -0
- data/client/skr/models/IaLine.coffee +19 -0
- data/client/skr/models/IaReason.coffee +12 -0
- data/client/skr/models/InvLine.coffee +27 -0
- data/client/skr/models/InventoryAdjustment.coffee +17 -0
- data/client/skr/models/Invoice.coffee +31 -0
- data/client/skr/models/Location.coffee +15 -0
- data/client/skr/models/PaymentTerm.coffee +11 -0
- data/client/skr/models/PickTicket.coffee +19 -0
- data/client/skr/models/PoLine.coffee +27 -0
- data/client/skr/models/PoReceipt.coffee +20 -0
- data/client/skr/models/PorLine.coffee +26 -0
- data/client/skr/models/PtLine.coffee +27 -0
- data/client/skr/models/PurchaseOrder.coffee +23 -0
- data/client/skr/models/SalesOrder.coffee +32 -0
- data/client/skr/models/Sku.coffee +21 -0
- data/client/skr/models/SkuLoc.coffee +21 -0
- data/client/skr/models/SkuTran.coffee +23 -0
- data/client/skr/models/SkuVendor.coffee +19 -0
- data/client/skr/models/SoLine.coffee +27 -0
- data/client/skr/models/Uom.coffee +17 -0
- data/client/skr/models/Vendor.coffee +28 -0
- data/client/skr/models/VoLine.coffee +23 -0
- data/client/skr/models/Voucher.coffee +22 -0
- data/client/skr/models/mixins/CodeField.coffee +5 -0
- data/client/skr/screens/.gitkeep +0 -0
- data/client/skr/screens/Base.coffee +3 -0
- data/client/skr/screens/base/index.js +5 -0
- data/client/skr/screens/base/index.scss +9 -0
- data/client/skr/screens/base/layout.html +3 -0
- data/client/skr/screens/customer-maint/CustomerMaint.coffee +49 -0
- data/client/skr/screens/customer-maint/index.js +5 -0
- data/client/skr/screens/customer-maint/index.scss +9 -0
- data/client/skr/screens/customer-maint/layout.html +32 -0
- data/client/skr/styles.scss +1 -0
- data/client/skr/views/.gitkeep +0 -0
- data/client/skr/views/Base.coffee +5 -0
- data/config/database.yml +9 -0
- data/config/lanes.rb +7 -0
- data/config/routes.rb +39 -0
- data/config/screens.rb +17 -0
- data/config.ru +5 -0
- data/db/.gitkeep +0 -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/db/schema.sql +2662 -0
- data/db/seed/chart_of_accounts.yml +168 -0
- data/db/seed/payment_terms.yml +60 -0
- data/db/seed.rb +29 -0
- data/lib/skr/access_roles.rb +28 -0
- data/lib/skr/concerns/acts_as_uom.rb +47 -0
- data/lib/skr/concerns/code_identifier.rb +43 -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/locked_fields.rb +84 -0
- data/lib/skr/concerns/pt_extensions.rb +22 -0
- data/lib/skr/concerns/random_hash_code.rb +40 -0
- data/lib/skr/concerns/so_extensions.rb +30 -0
- data/lib/skr/concerns/state_machine.rb +61 -0
- data/lib/skr/concerns/visible_id_identifier.rb +53 -0
- data/lib/skr/configuration.rb +68 -0
- data/lib/skr/db/migration_helpers.rb +178 -0
- data/lib/skr/extension.rb +23 -0
- data/lib/skr/model.rb +19 -0
- data/lib/skr/models/address.rb +97 -0
- data/lib/skr/models/business_entity.rb +29 -0
- data/lib/skr/models/customer.rb +35 -0
- data/lib/skr/models/gl_account.rb +56 -0
- data/lib/skr/models/gl_manual_entry.rb +31 -0
- data/lib/skr/models/gl_period.rb +13 -0
- data/lib/skr/models/gl_posting.rb +54 -0
- data/lib/skr/models/gl_transaction.rb +175 -0
- data/lib/skr/models/ia_line.rb +129 -0
- data/lib/skr/models/ia_reason.rb +16 -0
- data/lib/skr/models/inv_line.rb +90 -0
- data/lib/skr/models/inventory_adjustment.rb +60 -0
- data/lib/skr/models/invoice.rb +159 -0
- data/lib/skr/models/location.rb +31 -0
- data/lib/skr/models/payment_term.rb +30 -0
- data/lib/skr/models/pick_ticket.rb +71 -0
- data/lib/skr/models/po_line.rb +69 -0
- data/lib/skr/models/po_receipt.rb +51 -0
- data/lib/skr/models/por_line.rb +80 -0
- data/lib/skr/models/pt_line.rb +74 -0
- data/lib/skr/models/purchase_order.rb +112 -0
- data/lib/skr/models/sales_order.rb +159 -0
- data/lib/skr/models/sequential_id.rb +23 -0
- data/lib/skr/models/sku.rb +99 -0
- data/lib/skr/models/sku_loc.rb +94 -0
- data/lib/skr/models/sku_tran.rb +111 -0
- data/lib/skr/models/sku_vendor.rb +26 -0
- data/lib/skr/models/so_line.rb +159 -0
- data/lib/skr/models/uom.rb +63 -0
- data/lib/skr/models/user_proxy.rb +60 -0
- data/lib/skr/models/vendor.rb +33 -0
- data/lib/skr/models/vo_line.rb +35 -0
- data/lib/skr/models/voucher.rb +119 -0
- data/lib/skr/standard_pricing_provider.rb +14 -0
- data/lib/skr/version.rb +3 -0
- data/lib/skr.rb +18 -0
- data/lib/stockor.rb +4 -0
- data/lib/tasks/debug-activity.rake +58 -0
- data/log/test.log +0 -0
- data/spec/fixtures/skr/address.yml +2 -0
- data/spec/fixtures/skr/customer.yml +2 -0
- data/spec/fixtures/skr/gl_account.yml +2 -0
- data/spec/fixtures/skr/gl_manual_entry.yml +2 -0
- data/spec/fixtures/skr/gl_period.yml +2 -0
- data/spec/fixtures/skr/gl_posting.yml +2 -0
- data/spec/fixtures/skr/gl_transaction.yml +2 -0
- data/spec/fixtures/skr/ia_line.yml +2 -0
- data/spec/fixtures/skr/ia_reason.yml +2 -0
- data/spec/fixtures/skr/inv_line.yml +2 -0
- data/spec/fixtures/skr/inventory_adjustment.yml +2 -0
- data/spec/fixtures/skr/invoice.yml +2 -0
- data/spec/fixtures/skr/location.yml +2 -0
- data/spec/fixtures/skr/payment_term.yml +2 -0
- data/spec/fixtures/skr/pick_ticket.yml +2 -0
- data/spec/fixtures/skr/po_line.yml +2 -0
- data/spec/fixtures/skr/po_receipt.yml +2 -0
- data/spec/fixtures/skr/por_line.yml +2 -0
- data/spec/fixtures/skr/pt_line.yml +2 -0
- data/spec/fixtures/skr/purchase_order.yml +2 -0
- data/spec/fixtures/skr/sales_order.yml +2 -0
- data/spec/fixtures/skr/sku.yml +2 -0
- data/spec/fixtures/skr/sku_loc.yml +2 -0
- data/spec/fixtures/skr/sku_tran.yml +2 -0
- data/spec/fixtures/skr/sku_vendor.yml +2 -0
- data/spec/fixtures/skr/so_line.yml +2 -0
- data/spec/fixtures/skr/uom.yml +2 -0
- data/spec/fixtures/skr/vendor.yml +2 -0
- data/spec/fixtures/skr/vo_line.yml +2 -0
- data/spec/fixtures/skr/voucher.yml +2 -0
- data/spec/skr/address.rb +10 -0
- data/spec/skr/concerns/code_identifier_spec.rb +45 -0
- data/spec/skr/customer.rb +10 -0
- data/spec/skr/gl_account.rb +10 -0
- data/spec/skr/gl_manual_entry.rb +10 -0
- data/spec/skr/gl_period.rb +10 -0
- data/spec/skr/gl_posting.rb +10 -0
- data/spec/skr/gl_transaction.rb +10 -0
- data/spec/skr/ia_line.rb +10 -0
- data/spec/skr/ia_reason.rb +10 -0
- data/spec/skr/inv_line.rb +10 -0
- data/spec/skr/inventory_adjustment.rb +10 -0
- data/spec/skr/invoice.rb +10 -0
- data/spec/skr/location.rb +10 -0
- data/spec/skr/models/AddressSpec.coffee +5 -0
- data/spec/skr/models/CustomerSpec.coffee +5 -0
- data/spec/skr/models/GlAccountSpec.coffee +5 -0
- data/spec/skr/models/GlManualEntrySpec.coffee +5 -0
- data/spec/skr/models/GlPeriodSpec.coffee +5 -0
- data/spec/skr/models/GlPostingSpec.coffee +5 -0
- data/spec/skr/models/GlTransactionSpec.coffee +5 -0
- data/spec/skr/models/IaLineSpec.coffee +5 -0
- data/spec/skr/models/IaReasonSpec.coffee +5 -0
- data/spec/skr/models/InvLineSpec.coffee +5 -0
- data/spec/skr/models/InventoryAdjustmentSpec.coffee +5 -0
- data/spec/skr/models/InvoiceSpec.coffee +5 -0
- data/spec/skr/models/LocationSpec.coffee +5 -0
- data/spec/skr/models/PaymentTermSpec.coffee +5 -0
- data/spec/skr/models/PickTicketSpec.coffee +5 -0
- data/spec/skr/models/PoLineSpec.coffee +5 -0
- data/spec/skr/models/PoReceiptSpec.coffee +5 -0
- data/spec/skr/models/PorLineSpec.coffee +5 -0
- data/spec/skr/models/PtLineSpec.coffee +5 -0
- data/spec/skr/models/PurchaseOrderSpec.coffee +5 -0
- data/spec/skr/models/SalesOrderSpec.coffee +5 -0
- data/spec/skr/models/SkuLocSpec.coffee +5 -0
- data/spec/skr/models/SkuSpec.coffee +5 -0
- data/spec/skr/models/SkuTranSpec.coffee +5 -0
- data/spec/skr/models/SkuVendorSpec.coffee +5 -0
- data/spec/skr/models/SoLineSpec.coffee +5 -0
- data/spec/skr/models/UomSpec.coffee +5 -0
- data/spec/skr/models/VendorSpec.coffee +5 -0
- data/spec/skr/models/VoLineSpec.coffee +5 -0
- data/spec/skr/models/VoucherSpec.coffee +5 -0
- data/spec/skr/payment_term.rb +10 -0
- data/spec/skr/pick_ticket.rb +10 -0
- data/spec/skr/po_line.rb +10 -0
- data/spec/skr/po_receipt.rb +10 -0
- data/spec/skr/por_line.rb +10 -0
- data/spec/skr/pt_line.rb +10 -0
- data/spec/skr/purchase_order.rb +10 -0
- data/spec/skr/sales_order.rb +10 -0
- data/spec/skr/screens/Base.coffee +7 -0
- data/spec/skr/screens/CustomerMaint.coffee +7 -0
- data/spec/skr/screens/vendor-maint/VendorMaintSpec.coffee +5 -0
- data/spec/skr/sku.rb +10 -0
- data/spec/skr/sku_loc.rb +10 -0
- data/spec/skr/sku_tran.rb +10 -0
- data/spec/skr/sku_vendor.rb +10 -0
- data/spec/skr/so_line.rb +10 -0
- data/spec/skr/spec_helper.rb +26 -0
- data/spec/skr/uom.rb +10 -0
- data/spec/skr/vendor.rb +10 -0
- data/spec/skr/views/AddressSpec.coffee +5 -0
- data/spec/skr/vo_line.rb +10 -0
- data/spec/skr/voucher.rb +10 -0
- data/stockor.gemspec +38 -0
- data/tmp/.gitkeep +0 -0
- metadata +414 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
module BusinessEntity
|
|
4
|
+
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
has_random_hash_code
|
|
9
|
+
has_code_identifier from: 'name'
|
|
10
|
+
|
|
11
|
+
belongs_to :billing_address, class_name: 'Skr::Address', export: { writable: true }
|
|
12
|
+
belongs_to :shipping_address, class_name: 'Skr::Address', export: { writable: true }
|
|
13
|
+
belongs_to :terms, class_name: 'Skr::PaymentTerm', export: { writable: true }
|
|
14
|
+
|
|
15
|
+
delegate_and_export :terms_code, :terms_description
|
|
16
|
+
|
|
17
|
+
validates :name, presence: true
|
|
18
|
+
validates :terms, :billing_address, :shipping_address, set: true
|
|
19
|
+
|
|
20
|
+
before_validation :set_defaults, :on=>:create
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def set_defaults
|
|
24
|
+
self.terms ||= PaymentTerm.find_by_code(Skr.config.customer_terms_code)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
# Customers are Companies (or individuals) that purchase {Sku}s.
|
|
4
|
+
# They have both a billing and shipping address,
|
|
5
|
+
# a gl account that payments should be applied against, and a payment term.
|
|
6
|
+
#
|
|
7
|
+
class Customer < Skr::Model
|
|
8
|
+
|
|
9
|
+
# Common code shared with {Vendor}
|
|
10
|
+
include BusinessEntity
|
|
11
|
+
|
|
12
|
+
belongs_to :gl_receivables_account, class_name: 'Skr::GlAccount', export: true
|
|
13
|
+
|
|
14
|
+
has_many :sales_orders, inverse_of: :customer
|
|
15
|
+
has_many :invoices, inverse_of: :customer, listen: { save: :update_balance! }
|
|
16
|
+
|
|
17
|
+
delegate_and_export :gl_receivables_account_number
|
|
18
|
+
|
|
19
|
+
validates :gl_receivables_account, set: true
|
|
20
|
+
|
|
21
|
+
# Updates the amount the customer owes, which is the sum of the amount unpaid on open invoices
|
|
22
|
+
def update_balance!(*)
|
|
23
|
+
update_attributes open_balance: invoices.open_for_customer(self)
|
|
24
|
+
.with_details.sum('details.total')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def set_defaults
|
|
30
|
+
super
|
|
31
|
+
self.gl_receivables_account ||= GlAccount.default_for( :ar )
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end # Skr module
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
# A GlAccount *(short for General Ledger Account)* is used
|
|
4
|
+
# to define each class of items for
|
|
5
|
+
# which money or the equivalent is spent or received.
|
|
6
|
+
class GlAccount < Skr::Model
|
|
7
|
+
|
|
8
|
+
# @private
|
|
9
|
+
DEFAULT_ACCOUNTS = {}
|
|
10
|
+
|
|
11
|
+
is_immutable except: [:name, :is_active]
|
|
12
|
+
|
|
13
|
+
validates :name, :description, :presence => true
|
|
14
|
+
|
|
15
|
+
# @!attribute description
|
|
16
|
+
# A short description of the GL Account
|
|
17
|
+
|
|
18
|
+
# A future improvement would be to allow arbitrary account masks. For now, keep it simple
|
|
19
|
+
# with mandatory 4 characters + 2 char branch code
|
|
20
|
+
validates :number, :presence => true, :numericality=>true, :length=>{ :is=>4 }
|
|
21
|
+
|
|
22
|
+
# @return [GlAccount] the default account for the key from {Skr::Core::Configuration.default_gl_accounts}
|
|
23
|
+
def self.default_for( lookup )
|
|
24
|
+
number = Skr.config.default_gl_accounts[ lookup ]
|
|
25
|
+
raise RuntimeError.new("Unkown GL default account lookup code: {lookup}") unless number
|
|
26
|
+
DEFAULT_ACCOUNTS[ lookup ] ||= GlAccount.find_by_number( number )
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [String] the account number combined with location branch code
|
|
30
|
+
def number_for_location( location )
|
|
31
|
+
self.number + location.gl_branch_code
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [String] the account number combined with the default branch code
|
|
35
|
+
def default_number
|
|
36
|
+
self.number + Skr.config.default_branch_code
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [BigDecimal] The balance for the current period
|
|
40
|
+
def trial_balance
|
|
41
|
+
balance_for( GlPeriod.current )
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [String] the account number suitable for querying all branches
|
|
45
|
+
def account_mask
|
|
46
|
+
number + '%'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [BigDecimal] the balance for a given period
|
|
50
|
+
def balance_for( period, mask = self.account_mask )
|
|
51
|
+
GlPosting.matching( period, account_mask ).sum(:amount)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
@@ -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,175 @@
|
|
|
1
|
+
|
|
2
|
+
module Skr
|
|
3
|
+
|
|
4
|
+
# A transaction is a record of a business event that has financial consequences.
|
|
5
|
+
# It consists of an at least one credit and at least one debit
|
|
6
|
+
# Transactions can be nested, with each level compacting all the entries that were made on it
|
|
7
|
+
#
|
|
8
|
+
# require 'skr/core'
|
|
9
|
+
# customer = Customer.find_by_code "MONEYBAGS"
|
|
10
|
+
# GlTransaction.record( source: invoice, description: "Invoice Example" ) do | transaction |
|
|
11
|
+
# transaction.location = Location.default # <- could also specify in record's options
|
|
12
|
+
# Sku.where( code: ['HAT','STRING'] ).each do | sku |
|
|
13
|
+
# transaction.add_posting( amount: sku.default_price,
|
|
14
|
+
# debit: sku.gl_asset_account,
|
|
15
|
+
# credit: customer.gl_receivables_account )
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
class GlTransaction < Skr::Model
|
|
20
|
+
|
|
21
|
+
is_immutable
|
|
22
|
+
|
|
23
|
+
# A Transaction must refer to another record, such as Invoice, Inventory Adjustment, or a Manual Posting.
|
|
24
|
+
belongs_to :source, :polymorphic=>true
|
|
25
|
+
|
|
26
|
+
# Each transaction belongs to an accounting period
|
|
27
|
+
belongs_to :period, :class_name=>'Skr::GlPeriod', export: true
|
|
28
|
+
|
|
29
|
+
has_many :credits, ->{ where({ is_debit: false }) }, class_name: 'Skr::GlPosting',
|
|
30
|
+
extend: Concerns::GlTran::Postings,
|
|
31
|
+
inverse_of: :gl_transaction, export: { writable: true }
|
|
32
|
+
|
|
33
|
+
# Must equal credits, checked by the {#ensure_postings_correct} validation
|
|
34
|
+
has_many :debits, ->{ where({ is_debit: true }) }, class_name: 'Skr::GlPosting',
|
|
35
|
+
extend: Concerns::GlTran::Postings,
|
|
36
|
+
inverse_of: :gl_transaction, export: { writable: true }
|
|
37
|
+
|
|
38
|
+
before_validation :set_defaults
|
|
39
|
+
validate :ensure_postings_correct
|
|
40
|
+
validates :source, :period, :set=>true
|
|
41
|
+
validates :description, :presence=>true
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Add a debit/credit pair to the transaction with amount
|
|
45
|
+
# @param amount [BigDecimal] the amount to apply to each posting
|
|
46
|
+
# @param debit [GlAccount]
|
|
47
|
+
# @param credit [GlAccount]
|
|
48
|
+
def add_posting( amount: nil, debit: nil, credit: nil )
|
|
49
|
+
Skr::Core.logger.debug "GlTransaction add_posting #{debit} : #{credit}"
|
|
50
|
+
self.credits.build( location: @location, is_debit: false,
|
|
51
|
+
account: credit, amount: amount )
|
|
52
|
+
self.debits.build( location: @location, is_debit: true,
|
|
53
|
+
account: debit, amount: amount * -1 )
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Passes the location onto the postings.
|
|
57
|
+
# @param location [Location]
|
|
58
|
+
def location=(location)
|
|
59
|
+
@location = location
|
|
60
|
+
each_posting do | posting |
|
|
61
|
+
posting.location = location
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @yield [GlPosting] each posting associated with the Transaction
|
|
66
|
+
def each_posting
|
|
67
|
+
self.credits.each{ |posting| yield posting }
|
|
68
|
+
self.debits.each{ |posting| yield posting }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [GlTransaction] the current transaction that's in progress
|
|
72
|
+
def self.current
|
|
73
|
+
glt = Thread.current[:gl_transaction]
|
|
74
|
+
glt ? glt.last : nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Start a new nested GlTransaction
|
|
78
|
+
# When a transaction is created, it can have
|
|
79
|
+
# @return [GlTransaction] new transaction
|
|
80
|
+
# @yield [GlTransaction] new transaction
|
|
81
|
+
def self.record( attributes={} )
|
|
82
|
+
Thread.current[:gl_transaction] ||= []
|
|
83
|
+
glt = GlTransaction.new( attributes )
|
|
84
|
+
Thread.current[:gl_transaction].push( glt )
|
|
85
|
+
Skr::Core.logger.debug "B4 GlTransaction"
|
|
86
|
+
results = yield glt
|
|
87
|
+
Thread.current[:gl_transaction].pop
|
|
88
|
+
if results
|
|
89
|
+
if results.is_a?(Hash) && results.has_key?(:attributes)
|
|
90
|
+
glt.assign_attributes( results[:attributes] )
|
|
91
|
+
end
|
|
92
|
+
glt._save_recorded
|
|
93
|
+
Skr::Core.logger.debug "AF GlTransaction new=#{glt.new_record?} #{glt.errors.full_messages}"
|
|
94
|
+
end
|
|
95
|
+
return glt
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param owner [Skr::Model]
|
|
99
|
+
# @param amount [BigDecimal]
|
|
100
|
+
# @param debit [GlAccount]
|
|
101
|
+
# @param credit [GlAccount]
|
|
102
|
+
# @param options [Hash] options to pass to the [GlTransaction] if one is created
|
|
103
|
+
def self.push_or_save( owner: nil, amount: nil, debit:nil, credit:nil, options:{} )
|
|
104
|
+
if glt = self.current # we push
|
|
105
|
+
glt.add_posting( amount: amount, debit: debit, credit: credit )
|
|
106
|
+
else
|
|
107
|
+
options.merge!({
|
|
108
|
+
source: owner,
|
|
109
|
+
location: options[:location] || owner.location
|
|
110
|
+
})
|
|
111
|
+
glt = GlTransaction.new( options )
|
|
112
|
+
glt.add_posting( amount: amount, debit: debit, credit: credit )
|
|
113
|
+
glt.save
|
|
114
|
+
end
|
|
115
|
+
glt
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @private
|
|
119
|
+
def _save_recorded
|
|
120
|
+
compact( 'debits' )
|
|
121
|
+
compact( 'credits' )
|
|
122
|
+
self.save if self.credits.any? || self.debits.any?
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def compact( assoc_name )
|
|
129
|
+
accounts = self.send( assoc_name ).to_a
|
|
130
|
+
self.send( assoc_name + "=", [] )
|
|
131
|
+
account_numbers = accounts.group_by{ |posting| posting.account_number }
|
|
132
|
+
account_numbers.each do | number, matching |
|
|
133
|
+
amount = matching.sum(&:amount)
|
|
134
|
+
self.send( assoc_name ).build({
|
|
135
|
+
account_number: number,
|
|
136
|
+
is_debit: ( assoc_name == "debits" ),
|
|
137
|
+
amount: amount,
|
|
138
|
+
})
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def ensure_postings_correct
|
|
143
|
+
if debits.total != ( -1 * credits.total )
|
|
144
|
+
self.errors.add(:credits, "must equal debits")
|
|
145
|
+
self.errors.add(:debits, "must equal credits")
|
|
146
|
+
return false
|
|
147
|
+
end
|
|
148
|
+
true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def set_defaults
|
|
152
|
+
self.period ||= GlPeriod.current
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# def ensure_postings_exist
|
|
156
|
+
# if self.new_record? and self.postings.empty?
|
|
157
|
+
# 2.times { self.postings.build }
|
|
158
|
+
# end
|
|
159
|
+
# end
|
|
160
|
+
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
__END__
|
|
165
|
+
|
|
166
|
+
# export_join_tables :details
|
|
167
|
+
# export_scope :with_details_for, lambda { | acct |
|
|
168
|
+
# acct = acct.gsub(/\D/,'') + '%'
|
|
169
|
+
# cs = "case when details.debit_account_number like '#{acct}' then details.debit_amount " +
|
|
170
|
+
# "when details.credit_account_number like '#{acct}' then details.credit_amount else 0 end"
|
|
171
|
+
# window = "#{cs} as amount, sum(#{cs}) over (order by created_at) as balance"
|
|
172
|
+
# joins('join gl_transaction_details as details on details.gl_transaction_id = gl_transactions.id')
|
|
173
|
+
# .select("gl_transactions.*, details.*, #{window}")
|
|
174
|
+
# .where("debit_account_number like :acct or credit_account_number like :acct", acct: acct)
|
|
175
|
+
# }
|
|
@@ -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
|
|
@@ -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 = Skr.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
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Skr
|
|
2
|
+
|
|
3
|
+
class InventoryAdjustment < Skr::Model
|
|
4
|
+
|
|
5
|
+
has_visible_id
|
|
6
|
+
has_gl_transaction if: :should_apply_gl?
|
|
7
|
+
|
|
8
|
+
has_one :gl_transaction, :as=>:source, :inverse_of=>:source
|
|
9
|
+
|
|
10
|
+
belongs_to :location, export: true
|
|
11
|
+
belongs_to :reason, :class_name=>'Skr::IaReason', export: true
|
|
12
|
+
|
|
13
|
+
has_many :lines, :class_name=>'Skr::IaLine', :inverse_of=>:inventory_adjustment, export: { writable:true }
|
|
14
|
+
|
|
15
|
+
validates :reason, :location, :set=>true
|
|
16
|
+
validate :ensure_state_is_savable
|
|
17
|
+
validates_associated :lines
|
|
18
|
+
|
|
19
|
+
delegate_and_export :location_code
|
|
20
|
+
delegate_and_export :reason_code
|
|
21
|
+
|
|
22
|
+
state_machine do
|
|
23
|
+
state :pending , :initial=>true
|
|
24
|
+
state :applied
|
|
25
|
+
event :mark_applied do
|
|
26
|
+
transitions from: :pending, to: :applied
|
|
27
|
+
before :apply_adjustment
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def should_apply_gl?
|
|
34
|
+
state_event == :mark_applied
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def attributes_for_gl_transaction
|
|
38
|
+
{ source: self, location: location,
|
|
39
|
+
description: "IA #{self.visible_id}" }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ensure_state_is_savable
|
|
43
|
+
if applied? && state_was == 'applied'
|
|
44
|
+
errors.add('base' , "Cannot update record once it's approved and applied")
|
|
45
|
+
return false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def apply_adjustment
|
|
50
|
+
self.lines.each do | line |
|
|
51
|
+
next if line.is_applied?
|
|
52
|
+
line.unlock_adjusting{ line.adjust_qty! }
|
|
53
|
+
end
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
end # Skr module
|