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,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
|