stockor-core 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,23 @@
|
|
1
|
+
module Skr
|
2
|
+
class SequentialId < Skr::Model
|
3
|
+
FUNCTION_NAME="#{Skr::Core.config.table_prefix}next_sequential_id"
|
4
|
+
|
5
|
+
self.primary_key = 'name'
|
6
|
+
|
7
|
+
locked_fields :name, :current_value
|
8
|
+
|
9
|
+
def self.next_for( klass )
|
10
|
+
begin
|
11
|
+
res=ActiveRecord::Base.connection.raw_connection.exec( "select #{FUNCTION_NAME}( $1 )", [ klass.to_s ] )
|
12
|
+
res.getvalue(0,0).to_i
|
13
|
+
ensure
|
14
|
+
res.clear if res
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.set_next( klass, value )
|
19
|
+
self.connection.raw_connection.exec( "update #{table_name} set current_value = $1 where name = $2", [ value, klass.to_s ] )
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/skr/sku.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Skr
|
2
|
+
|
3
|
+
#### A (S)tock (K)eeping (U)nit (SKU) is the cornerstone of Stockor
|
4
|
+
#
|
5
|
+
# At it's simplest form a SKU tracks a *resource* that the company controlls.
|
6
|
+
# It can be manufactured (by combining other SKUs), purchased, stored, and sold.
|
7
|
+
#
|
8
|
+
# Although SKUs usually refer to physical item, it may also track intangibles
|
9
|
+
# such as "Labor", "Handling", or "Freight"
|
10
|
+
#
|
11
|
+
class Sku < Skr::Model
|
12
|
+
|
13
|
+
has_code_identifier :from=>'description'
|
14
|
+
|
15
|
+
belongs_to :default_vendor, class_name: 'Skr::Vendor', export: true
|
16
|
+
belongs_to :gl_asset_account, class_name: 'Skr::GlAccount', export: true
|
17
|
+
belongs_to :default_vendor, class_name: 'Skr::Vendor', export: true
|
18
|
+
|
19
|
+
has_many :sku_locs, ->{extending Concerns::Sku::Locations },
|
20
|
+
inverse_of: :sku, dependent: :destroy,
|
21
|
+
export: { writable:true }
|
22
|
+
|
23
|
+
has_many :sku_vendors, ->{ extending Concerns::Sku::Vendors },
|
24
|
+
dependent: :destroy, inverse_of: :sku,
|
25
|
+
export: { writable: true, allow_destroy: true }
|
26
|
+
|
27
|
+
has_many :uoms, ->{ order(:size); extending(Concerns::Sku::Uoms) },
|
28
|
+
dependent: :destroy, inverse_of: :sku,
|
29
|
+
export: { writable: true, allow_destroy: true }
|
30
|
+
|
31
|
+
validates :uoms, presence: true, :on => :update
|
32
|
+
validates :description, presence: true
|
33
|
+
validates :gl_asset_account, set: true
|
34
|
+
validates :default_vendor, set: true
|
35
|
+
validates :default_uom_code, presence: true, :on => :update
|
36
|
+
validate :ensure_default_uom_exists
|
37
|
+
|
38
|
+
before_validation :set_defaults, :on=>:create
|
39
|
+
after_create :create_associated_records
|
40
|
+
|
41
|
+
scope :with_vendor_part_code, lambda { | vendor_sku |
|
42
|
+
joins(:sku_vendors).where( SkuVendor.arel_table[:part_code].matches( vendor_sku ) )
|
43
|
+
}, :export=>true
|
44
|
+
|
45
|
+
scope :in_location, lambda { | location_id |
|
46
|
+
joins(:sku_locs).where( SkuLoc.table_name => { location_id: location_id } )
|
47
|
+
}, :export=>true
|
48
|
+
|
49
|
+
scope :with_qty_details, lambda { | *args |
|
50
|
+
compose_query_using_detail_view( view: 'sku_qty_details', join_to: 'sku_id' )
|
51
|
+
}, export: { :join_table=>:details }
|
52
|
+
|
53
|
+
scope :only_back_ordered, lambda{ | *args |
|
54
|
+
with_qty_details.where("details.qty_on_order > details.qty_on_hand")
|
55
|
+
}, export: true
|
56
|
+
|
57
|
+
|
58
|
+
# Rebuilding is sometimes needed for cases where the location's
|
59
|
+
# allocation/on order/reserved counts get out of sync with the
|
60
|
+
# SalesOrder counts. This forces recalculation of the cached values
|
61
|
+
def rebuild!
|
62
|
+
sku_locs.each(&:rebuild!)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# If the default uom code was changed, make sure the UOM
|
68
|
+
# is actually present on the uoms list
|
69
|
+
def ensure_default_uom_exists
|
70
|
+
if default_uom_code_changed? && uoms.default.nil?
|
71
|
+
errors.add( :default_uom_code, "does not exist on UOMs" )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Setup the associations after create
|
76
|
+
def create_associated_records
|
77
|
+
if sku_locs.empty?
|
78
|
+
self.sku_locs.create({ sku: self, location: Location.default })
|
79
|
+
end
|
80
|
+
true # don't cancel save op
|
81
|
+
end
|
82
|
+
|
83
|
+
# Set the default values for the Sku if they are not present
|
84
|
+
def set_defaults
|
85
|
+
if self.default_vendor.blank? && self.sku_vendors.any?
|
86
|
+
self.default_vendor = self.sku_vendors.at(0).vendor
|
87
|
+
end
|
88
|
+
self.uoms << Uom.ea if self.uoms.empty?
|
89
|
+
|
90
|
+
self.can_backorder = Core.config.skus_backorder_default if self.can_backorder.nil?
|
91
|
+
self.gl_asset_account ||= GlAccount.default_for(:asset)
|
92
|
+
self.default_uom_code ||= self.uoms.first.code
|
93
|
+
|
94
|
+
true # don't cancel save op
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end # Skr module
|
data/lib/skr/sku_loc.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Skr
|
2
|
+
|
3
|
+
# Next to the {Sku} class, SkuLoc is the second most integral model in Stockor. It tracks
|
4
|
+
# which Skus are setup in each location and the related quantity information about them
|
5
|
+
#
|
6
|
+
# It is also the model that is linked to by other models that need to refer to a sku's location
|
7
|
+
# such as the lines on {PurchaseOrder}, Quotes, {SalesOrder}, PickTickets, and Invoices
|
8
|
+
class SkuLoc < Skr::Model
|
9
|
+
|
10
|
+
belongs_to :sku, export: true
|
11
|
+
belongs_to :location, export: true
|
12
|
+
|
13
|
+
has_many :so_lines, inverse_of: :sku_loc, extend: Concerns::SO::Lines, listen: { qty_change: :update_so_qty }
|
14
|
+
has_many :pt_lines, :inverse_of=>:sku_loc, extend: Concerns::PT::Lines, listen: { save: :update_qty_picking }
|
15
|
+
|
16
|
+
has_many :sku_vendors, :primary_key=>:sku_id, :foreign_key=>:sku_id
|
17
|
+
|
18
|
+
delegate_and_export :location_name, :location_code
|
19
|
+
delegate_and_export :sku_code, :sku_description
|
20
|
+
|
21
|
+
validates :mac, numericality: true
|
22
|
+
validates :sku, :location, presence: true
|
23
|
+
validates :sku, uniqueness: { scope: :location_id, message: "SKU may not be in the same location twice" }
|
24
|
+
|
25
|
+
export_methods :qty_available
|
26
|
+
|
27
|
+
locked_fields :qty, :mac
|
28
|
+
|
29
|
+
has_additional_events :qty_change
|
30
|
+
|
31
|
+
# @return [BigDecimal] the value of inventory for {Sku} in this {Location}
|
32
|
+
def onhand_mac_value
|
33
|
+
qty*mac
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Fixnum] the qty that is not allocated, picking or reserved
|
37
|
+
def qty_available
|
38
|
+
qty - qty_allocated - qty_picking - qty_reserved
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adjust the on hand qty. Can only be called while qty is unlocked
|
42
|
+
# @example
|
43
|
+
# sl = SkuLoc.first
|
44
|
+
# sl.unlock_fields( :qty ) do
|
45
|
+
# sl.adjust_qty( 10 )
|
46
|
+
# sl.save!
|
47
|
+
# end
|
48
|
+
# @param [Fixnum] qty the amount to adjust the onhand qty by
|
49
|
+
# @return [Fixnum] new qty on hand
|
50
|
+
def adjust_qty( qty )
|
51
|
+
self.qty += qty
|
52
|
+
end
|
53
|
+
|
54
|
+
# Rebuilding is sometimes needed for cases where the location's
|
55
|
+
# allocation/on order/reserved counts get out of sync with the
|
56
|
+
# SalesOrder counts. This forces recalculation of the cached values
|
57
|
+
def rebuild!
|
58
|
+
self.update_attributes({
|
59
|
+
qty_picking: pt_lines.pt_lines.picking_qty,
|
60
|
+
qty_allocated: self.so_lines.open.allocated.eq_qty_allocated
|
61
|
+
})
|
62
|
+
end
|
63
|
+
|
64
|
+
# Allocate the maximum available quantity to {SalesOrder}
|
65
|
+
# that are not currrently allocated
|
66
|
+
def allocate_available_qty!
|
67
|
+
update_so_qty
|
68
|
+
so_lines.unallocated.order(:created_at).each do | sol |
|
69
|
+
sol.sku_loc = self
|
70
|
+
sol.allocate_max_available
|
71
|
+
sol.save
|
72
|
+
break if qty_allocated <= 0
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def fire_after_save_events
|
79
|
+
fire_event(:qty_change) if qty_changed?
|
80
|
+
end
|
81
|
+
|
82
|
+
# Caches the qty of skus that are allocated to sales orders in the {#qty_allocated} field
|
83
|
+
def update_so_qty( so_line=nil )
|
84
|
+
self.update_attributes({ qty_allocated: self.so_lines.open.allocated.eq_qty_allocated })
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_qty_picking( pt=nil )
|
88
|
+
update_attributes( :qty_picking=> self.pt_lines.picking.ea_picking_qty )
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
end # Skr module
|
data/lib/skr/sku_tran.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module Skr
|
2
|
+
|
3
|
+
# In Stockor, inventory related transactions are not performed directly on the model(s)
|
4
|
+
#
|
5
|
+
# Instead a SkuTran is created, and it is responsible for adjusting
|
6
|
+
# either the cost or qty of the inventory. By doing so, all inventory
|
7
|
+
# changes is logged and can be referred to in order to audit
|
8
|
+
# changes.
|
9
|
+
class SkuTran < Skr::Model
|
10
|
+
|
11
|
+
acts_as_uom
|
12
|
+
|
13
|
+
is_immutable
|
14
|
+
|
15
|
+
belongs_to :sku_loc
|
16
|
+
has_one :location, :through => :sku_loc
|
17
|
+
|
18
|
+
belongs_to :origin, :polymorphic=>true
|
19
|
+
|
20
|
+
has_one :gl_transaction, :as=>:source, :inverse_of=>:source
|
21
|
+
|
22
|
+
validates :sku_loc, :set=>true
|
23
|
+
validates :origin_description, :presence=>true
|
24
|
+
validates :prior_mac, :numericality=>true
|
25
|
+
|
26
|
+
validate :ensure_cost_and_qty_present
|
27
|
+
|
28
|
+
attr_accessor :credit_gl_account
|
29
|
+
attr_accessor :debit_gl_account
|
30
|
+
attr_accessor :gl_tran_description_text
|
31
|
+
|
32
|
+
after_save :adjust_sku_loc_values
|
33
|
+
after_save :create_needed_gl_transaction
|
34
|
+
before_save :calculate_mac
|
35
|
+
|
36
|
+
attr_accessor :allocate_after_save
|
37
|
+
|
38
|
+
# @param sl [SkuLoc] set's the sku loc and also sets {#prior_qty} and {#prior_mac}
|
39
|
+
def sku_loc=(sl)
|
40
|
+
super
|
41
|
+
self.prior_qty = sl.qty
|
42
|
+
self.prior_mac = sl.mac
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Fixnum] {#qty} expressed in terms of single UOM
|
46
|
+
def ea_qty
|
47
|
+
self.qty * ( self.uom_size || 1 )
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String] a description intended for use by the #{GlTransaction}
|
51
|
+
# def description_for_gl_transaction(gl)
|
52
|
+
# self.origin_description
|
53
|
+
# end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# sets {#mac} to the correct amount for the {SkuLoc}.
|
58
|
+
# To calculate the MAC, the {SkuLoc#onhand_mac_value} is added to {#cost}
|
59
|
+
# and then divided by #{SkuLoc#qty} + {#ea_qty}
|
60
|
+
def calculate_mac
|
61
|
+
new_qty = sku_loc.qty + self.ea_qty
|
62
|
+
return true if self.mac.present?
|
63
|
+
if new_qty.zero?
|
64
|
+
self.mac = BigDecimal.new(0)
|
65
|
+
elsif cost
|
66
|
+
self.mac = ( sku_loc.onhand_mac_value + cost ) / new_qty
|
67
|
+
else
|
68
|
+
self.mac = sku_loc.onhand_mac_value
|
69
|
+
end
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
# If {#cost} is non-zero, then create a {GlTransaction}
|
74
|
+
def create_needed_gl_transaction
|
75
|
+
Skr::Core.logger.debug "Recording SkuTran in GL, mac is: #{self.mac}, cost = #{cost}"
|
76
|
+
return if self.cost.nil? || self.cost.zero?
|
77
|
+
GlTransaction.push_or_save(
|
78
|
+
owner: self, amount: cost,
|
79
|
+
debit: debit_gl_account, credit: credit_gl_account
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Adjusts {SkuLoc#qty} by {#ea_qty}
|
84
|
+
def adjust_sku_loc_values
|
85
|
+
sl = self.sku_loc
|
86
|
+
Skr::Core.logger.debug "Adj +#{ea_qty} Sku #{sl.sku.code} location #{location.code} " +
|
87
|
+
"from MAC: #{sl.mac} to #{self.mac}, qty: #{sl.qty} += #{ea_qty} #{combined_uom}"
|
88
|
+
sl.unlock_fields( :qty, :mac ) do
|
89
|
+
sl.mac = self.mac unless self.mac.nan? or self.mac.zero?
|
90
|
+
sl.adjust_qty( ea_qty )
|
91
|
+
sl.save!
|
92
|
+
end
|
93
|
+
sl.reload
|
94
|
+
sl.allocate_available_qty! if self.allocate_after_save
|
95
|
+
|
96
|
+
Skr::Core.logger.debug "After Adj Qty #{sl.qty}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def ensure_cost_and_qty_present
|
100
|
+
if ea_qty.zero?
|
101
|
+
errors.add( :base, "Transaction has no effect, must change inventory onhand value")
|
102
|
+
end
|
103
|
+
if cost.present? && cost.nonzero? && debit_gl_account.nil?
|
104
|
+
errors.add( :debit_gl_account, "was not specified even though we need to adjust the GL by #{cost}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end # Skr module
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Skr
|
2
|
+
|
3
|
+
class SkuVendor < Skr::Model
|
4
|
+
|
5
|
+
acts_as_uom
|
6
|
+
|
7
|
+
belongs_to :sku, inverse_of: :sku_vendors, export: true
|
8
|
+
belongs_to :vendor, inverse_of: :sku_vendors, export: true
|
9
|
+
has_many :sku_locs, primary_key: :sku_id, export: true
|
10
|
+
|
11
|
+
delegate_and_export :vendor_code, :vendor_name
|
12
|
+
delegate_and_export :sku_code, :sku_description
|
13
|
+
|
14
|
+
validates :list_price, :cost, :uom_size, :numericality=>true, :presence=>true
|
15
|
+
validates :uom_code, :part_code, :presence=>true
|
16
|
+
validates :sku, :uniqueness=>{ scope: :part_code }
|
17
|
+
|
18
|
+
scope :in_location, lambda { | location |
|
19
|
+
location_id = location.is_a?(Numeric) ? location : location.id
|
20
|
+
includes(:sku_locs).references(:sku_locs).where(['sku_locs.location_id=?',location_id])
|
21
|
+
}, :export=>true
|
22
|
+
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Skr module
|
data/lib/skr/so_line.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
module Skr
|
2
|
+
|
3
|
+
class SoLine < Skr::Model
|
4
|
+
|
5
|
+
acts_as_uom
|
6
|
+
is_sku_loc_line parent: 'sales_order'
|
7
|
+
|
8
|
+
belongs_to :sales_order
|
9
|
+
belongs_to :sku_loc, export: true
|
10
|
+
has_one :sku, :through => :sku_loc, export: true
|
11
|
+
has_one :location, :through => :sales_order
|
12
|
+
has_many :pt_lines, :before_add=>:setup_new_pt_line, :inverse_of=>:so_line,
|
13
|
+
extend: Concerns::PT::Lines, :listen=>{save:'update_qty_picking'}
|
14
|
+
|
15
|
+
# has_many :inv_lines, :before_add=>:setup_new_inv_line, :inverse_of=>:so_line
|
16
|
+
|
17
|
+
validates :sales_order, :sku_loc, set: true
|
18
|
+
|
19
|
+
validates :price, :qty, :numericality=>true
|
20
|
+
validates :qty_allocated, :numericality=>{ :greater_than_or_equal_to=>0 }
|
21
|
+
validate :ensure_allocation_is_correct
|
22
|
+
validate :ensure_so_is_open, on: :create
|
23
|
+
|
24
|
+
has_additional_events :qty_change
|
25
|
+
|
26
|
+
before_validation :set_defaults_from_associations
|
27
|
+
before_create :allocate_max_available
|
28
|
+
before_destroy :ensure_deleteable
|
29
|
+
|
30
|
+
scope :open, ->{
|
31
|
+
joins(:sales_order).merge(SalesOrder.open).where( arel_table[:qty].gt( arel_table[:qty_invoiced] ) )
|
32
|
+
}
|
33
|
+
scope :allocated, ->{ where( arel_table[:qty_allocated].gt( 0 ) ) }
|
34
|
+
scope :unallocated, ->{
|
35
|
+
t = table_name; where( "#{t}.qty_allocated < #{t}.qty - #{t}.qty_invoiced - #{t}.qty_canceled" )
|
36
|
+
}
|
37
|
+
scope :unshipped, lambda {|unused=nil|
|
38
|
+
t = table_name; where( "#{t}.qty > #{t}.qty_invoiced + #{t}.qty_canceled" )
|
39
|
+
}
|
40
|
+
scope :pickable, ->{ where( arel_table[:qty_allocated].gt( arel_table[:qty_picking] ) ) }
|
41
|
+
|
42
|
+
def location=(location)
|
43
|
+
self.cancel!
|
44
|
+
self.sku_loc = self.sku.sku_locs.find_or_create_for( location )
|
45
|
+
self.allocate_max_available
|
46
|
+
self.save!
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# allocate the maximum available qty to the line
|
51
|
+
def allocate_max_available
|
52
|
+
self.qty_allocated = [ 0, [ sku_loc.qty_available+qty_allocated, qty ].min ].max if self.sku.does_track_inventory?
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# A line is fully allocated if the qty_allocated is less than the qty ordered - the qty invoiced - the qty canceled
|
57
|
+
# @return [Boolean]
|
58
|
+
def is_fully_allocated?
|
59
|
+
self.qty_allocated >= qty - qty_invoiced - qty_canceled
|
60
|
+
end
|
61
|
+
|
62
|
+
# The pickable qty is the qty allocated - the qty already on pick tickets
|
63
|
+
# @return [Fixnum]
|
64
|
+
def pickable_qty
|
65
|
+
qty_allocated - qty_picking
|
66
|
+
end
|
67
|
+
|
68
|
+
def cancel!
|
69
|
+
self.update_attributes :qty_allocated => 0, :qty_picking=> 0
|
70
|
+
pt_lines.picking.each{ |ptl| ptl.cancel! }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def update_qty_shipped
|
76
|
+
inv_qty = self.inv_lines.sum(:qty)
|
77
|
+
update_attributes( :qty_invoiced=> inv_qty, :qty_allocated => [ qty_allocated - inv_qty, 0 ].max )
|
78
|
+
end
|
79
|
+
def update_qty_picking( pt=nil )
|
80
|
+
update_attributes( :qty_picking=> pt_lines.ea_picking_qty/uom_size )
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def fire_after_save_events
|
85
|
+
%w{ allocated picking invoiced canceled }.each do | event |
|
86
|
+
if changes[ "qty_#{event}" ]
|
87
|
+
fire_event( :qty_change )
|
88
|
+
break
|
89
|
+
end
|
90
|
+
end
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_defaults_from_associations
|
95
|
+
self.uom = sku.uoms.default if self.uom_code.blank?
|
96
|
+
self.description = sku.description if self.description.blank?
|
97
|
+
self.sku_code = sku.code if self.sku_code.blank?
|
98
|
+
if !price && sales_order && sales_order.customer && sku_loc && uom.present?
|
99
|
+
self.price = Core.config.pricing_provider.price(sku_loc:sku_loc, customer:sales_order.customer, uom:uom, qty:qty)
|
100
|
+
end
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def setup_new_inv_line( line )
|
105
|
+
line.qty = self.sku.is_other_charge? ? self.qty : self.qty_allocated
|
106
|
+
setup_new_line(line)
|
107
|
+
end
|
108
|
+
def setup_new_pt_line( line )
|
109
|
+
line.qty = self.sku.is_other_charge? ? self.qty : self.pickable_qty
|
110
|
+
setup_new_line(line)
|
111
|
+
end
|
112
|
+
|
113
|
+
def setup_new_line(line)
|
114
|
+
line.price = self.price
|
115
|
+
line.sku_loc = self.sku_loc
|
116
|
+
line.uom = self.uom
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def ensure_allocation_is_correct
|
121
|
+
return true unless qty_allocated_changed?
|
122
|
+
diff = qty_allocated - qty_allocated_was
|
123
|
+
if qty_allocated > qty
|
124
|
+
errors.add(:qty_allocated, "must be less than qty ordered (#{qty})")
|
125
|
+
end
|
126
|
+
if diff > 0 && sku_loc && diff > sku_loc.qty_available
|
127
|
+
errors.add(:qty_allocated, "new allocation (#{qty_allocated}) - old allocation (#{qty_allocated_was}) can't be more than qty available (#{ sku_loc.qty_available })")
|
128
|
+
return false
|
129
|
+
end
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
def ensure_so_is_open
|
135
|
+
unless self.sales_order.open?
|
136
|
+
errors.add(:base,"Cannot add item #{self.sku_code} to non-open Sales Order")
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
def ensure_deleteable
|
143
|
+
if qty_allocated > 0
|
144
|
+
errors.add(:base,'Cannot delete line when allocated')
|
145
|
+
return false
|
146
|
+
end
|
147
|
+
if qty_invoiced > 0
|
148
|
+
errors.add(:base,"Cannot delete line after it's shipped")
|
149
|
+
return false
|
150
|
+
end
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
end # Skr module
|