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.
Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +6 -0
  4. data/Guardfile +13 -0
  5. data/LICENSE.txt +674 -0
  6. data/README.md +88 -0
  7. data/Rakefile +58 -0
  8. data/config/database.yml +9 -0
  9. data/db/migrate/20120110142845_create_skr_sequential_ids.rb +35 -0
  10. data/db/migrate/20140202185309_create_skr_gl_accounts.rb +15 -0
  11. data/db/migrate/20140202193316_create_skr_gl_periods.rb +16 -0
  12. data/db/migrate/20140202193318_create_skr_gl_transactions.rb +14 -0
  13. data/db/migrate/20140202193319_create_skr_gl_postings.rb +16 -0
  14. data/db/migrate/20140202193700_create_skr_gl_manual_entries.rb +13 -0
  15. data/db/migrate/20140213040608_create_skr_payment_terms.rb +16 -0
  16. data/db/migrate/20140220031700_create_skr_addresses.rb +19 -0
  17. data/db/migrate/20140220031800_create_skr_locations.rb +19 -0
  18. data/db/migrate/20140220190836_create_skr_vendors.rb +22 -0
  19. data/db/migrate/20140220203029_create_skr_customers.rb +22 -0
  20. data/db/migrate/20140224034759_create_skr_skus.rb +22 -0
  21. data/db/migrate/20140225032853_create_skr_sku_locs.rb +21 -0
  22. data/db/migrate/20140320030501_create_skr_uoms.rb +19 -0
  23. data/db/migrate/20140321031604_create_skr_sku_vendors.rb +18 -0
  24. data/db/migrate/20140322012143_create_skr_ia_reasons.rb +14 -0
  25. data/db/migrate/20140322014401_create_skr_inventory_adjustments.rb +16 -0
  26. data/db/migrate/20140322023453_create_skr_ia_lines.rb +18 -0
  27. data/db/migrate/20140322035024_create_skr_sku_trans.rb +21 -0
  28. data/db/migrate/20140322223912_create_skr_sales_orders.rb +27 -0
  29. data/db/migrate/20140322223920_create_skr_so_lines.rb +25 -0
  30. data/db/migrate/20140323001446_create_so_details_view.rb +81 -0
  31. data/db/migrate/20140327202102_create_skr_purchase_orders.rb +20 -0
  32. data/db/migrate/20140327202107_create_skr_po_lines.rb +25 -0
  33. data/db/migrate/20140327202207_create_skr_pick_tickets.rb +16 -0
  34. data/db/migrate/20140327202209_create_skr_pt_lines.rb +23 -0
  35. data/db/migrate/20140327224000_create_skr_invoices.rb +25 -0
  36. data/db/migrate/20140327224002_create_skr_inv_lines.rb +23 -0
  37. data/db/migrate/20140330232808_create_skr_sku_loc_details_view.rb +31 -0
  38. data/db/migrate/20140330232810_create_skr_sku_qty_details_view.rb +48 -0
  39. data/db/migrate/20140400164729_create_skr_vouchers.rb +22 -0
  40. data/db/migrate/20140400164733_create_skr_vo_lines.rb +21 -0
  41. data/db/migrate/20140401164729_create_skr_po_receipt.rb +16 -0
  42. data/db/migrate/20140401164740_create_skr_por_line.rb +21 -0
  43. data/db/migrate/20140422024010_create_skr_inv_details_view.rb +42 -0
  44. data/lib/generators/stockor/migrations/install_generator.rb +42 -0
  45. data/lib/skr/address.rb +97 -0
  46. data/lib/skr/business_entity.rb +25 -0
  47. data/lib/skr/concerns/acts_as_uom.rb +47 -0
  48. data/lib/skr/concerns/all.rb +30 -0
  49. data/lib/skr/concerns/association_extensions.rb +85 -0
  50. data/lib/skr/concerns/attr_accessor_with_default.rb +54 -0
  51. data/lib/skr/concerns/code_identifier.rb +43 -0
  52. data/lib/skr/concerns/export_associations.rb +52 -0
  53. data/lib/skr/concerns/export_join_tables.rb +39 -0
  54. data/lib/skr/concerns/export_methods.rb +104 -0
  55. data/lib/skr/concerns/export_scope.rb +66 -0
  56. data/lib/skr/concerns/exported_limit_evaluator.rb +17 -0
  57. data/lib/skr/concerns/gl_tran_extensions.rb +18 -0
  58. data/lib/skr/concerns/has_gl_transaction.rb +67 -0
  59. data/lib/skr/concerns/has_sku_loc_lines.rb +47 -0
  60. data/lib/skr/concerns/immutable_model.rb +32 -0
  61. data/lib/skr/concerns/inv_extensions.rb +24 -0
  62. data/lib/skr/concerns/is_order_like.rb +47 -0
  63. data/lib/skr/concerns/is_sku_loc_line.rb +65 -0
  64. data/lib/skr/concerns/json_attribute_access.rb +55 -0
  65. data/lib/skr/concerns/locked_fields.rb +84 -0
  66. data/lib/skr/concerns/pt_extensions.rb +22 -0
  67. data/lib/skr/concerns/pub_sub.rb +105 -0
  68. data/lib/skr/concerns/queries.rb +20 -0
  69. data/lib/skr/concerns/random_hash_code.rb +40 -0
  70. data/lib/skr/concerns/sanitize_json.rb +49 -0
  71. data/lib/skr/concerns/sku_extensions.rb +52 -0
  72. data/lib/skr/concerns/so_extensions.rb +30 -0
  73. data/lib/skr/concerns/state_machine.rb +62 -0
  74. data/lib/skr/concerns/track_modifications.rb +48 -0
  75. data/lib/skr/concerns/visible_id_identifier.rb +53 -0
  76. data/lib/skr/core.rb +30 -0
  77. data/lib/skr/core/configuration.rb +87 -0
  78. data/lib/skr/core/db.rb +82 -0
  79. data/lib/skr/core/db/migration_helpers.rb +178 -0
  80. data/lib/skr/core/db/migrations.rb +15 -0
  81. data/lib/skr/core/db/seed.rb +46 -0
  82. data/lib/skr/core/db/seed/chart_of_accounts.yml +168 -0
  83. data/lib/skr/core/db/seed/payment_terms.yml +60 -0
  84. data/lib/skr/core/logger.rb +36 -0
  85. data/lib/skr/core/numbers.rb +71 -0
  86. data/lib/skr/core/rails_engine.rb +5 -0
  87. data/lib/skr/core/standard_pricing_provider.rb +15 -0
  88. data/lib/skr/core/strings.rb +57 -0
  89. data/lib/skr/core/testing.rb +11 -0
  90. data/lib/skr/core/testing/assertions.rb +44 -0
  91. data/lib/skr/core/testing/fixtures.rb +25 -0
  92. data/lib/skr/core/testing/fixtures/skr/addresses.yml +53 -0
  93. data/lib/skr/core/testing/fixtures/skr/customers.yml +43 -0
  94. data/lib/skr/core/testing/fixtures/skr/gl_accounts.yml +86 -0
  95. data/lib/skr/core/testing/fixtures/skr/gl_manual_entries.yml +4 -0
  96. data/lib/skr/core/testing/fixtures/skr/gl_periods.yml +26 -0
  97. data/lib/skr/core/testing/fixtures/skr/gl_postings.yml +88 -0
  98. data/lib/skr/core/testing/fixtures/skr/gl_transactions.yml +35 -0
  99. data/lib/skr/core/testing/fixtures/skr/ia_lines.yml +7 -0
  100. data/lib/skr/core/testing/fixtures/skr/ia_reasons.yml +15 -0
  101. data/lib/skr/core/testing/fixtures/skr/inv_lines.yml +22 -0
  102. data/lib/skr/core/testing/fixtures/skr/inventory_adjustments.yml +6 -0
  103. data/lib/skr/core/testing/fixtures/skr/invoices.yml +10 -0
  104. data/lib/skr/core/testing/fixtures/skr/locations.yml +24 -0
  105. data/lib/skr/core/testing/fixtures/skr/payment_terms.yml +42 -0
  106. data/lib/skr/core/testing/fixtures/skr/pick_tickets.yml +4 -0
  107. data/lib/skr/core/testing/fixtures/skr/po_lines.yml +44 -0
  108. data/lib/skr/core/testing/fixtures/skr/po_receipts.yml +5 -0
  109. data/lib/skr/core/testing/fixtures/skr/por_lines.yml +13 -0
  110. data/lib/skr/core/testing/fixtures/skr/pt_lines.yml +12 -0
  111. data/lib/skr/core/testing/fixtures/skr/purchase_orders.yml +16 -0
  112. data/lib/skr/core/testing/fixtures/skr/sales_orders.yml +32 -0
  113. data/lib/skr/core/testing/fixtures/skr/sku_locs.yml +78 -0
  114. data/lib/skr/core/testing/fixtures/skr/sku_trans.yml +9 -0
  115. data/lib/skr/core/testing/fixtures/skr/sku_vendors.yml +35 -0
  116. data/lib/skr/core/testing/fixtures/skr/skus.yml +50 -0
  117. data/lib/skr/core/testing/fixtures/skr/so_lines.yml +71 -0
  118. data/lib/skr/core/testing/fixtures/skr/uoms.yml +61 -0
  119. data/lib/skr/core/testing/fixtures/skr/vendors.yml +48 -0
  120. data/lib/skr/core/testing/fixtures/skr/vo_lines.yml +12 -0
  121. data/lib/skr/core/testing/fixtures/skr/vouchers.yml +8 -0
  122. data/lib/skr/core/testing/helper.rb +18 -0
  123. data/lib/skr/core/testing/test_case.rb +17 -0
  124. data/lib/skr/core/version.rb +5 -0
  125. data/lib/skr/customer.rb +34 -0
  126. data/lib/skr/gl_account.rb +56 -0
  127. data/lib/skr/gl_manual_entry.rb +31 -0
  128. data/lib/skr/gl_period.rb +13 -0
  129. data/lib/skr/gl_posting.rb +54 -0
  130. data/lib/skr/gl_transaction.rb +174 -0
  131. data/lib/skr/ia_line.rb +129 -0
  132. data/lib/skr/ia_reason.rb +16 -0
  133. data/lib/skr/inv_line.rb +90 -0
  134. data/lib/skr/inventory_adjustment.rb +60 -0
  135. data/lib/skr/invoice.rb +159 -0
  136. data/lib/skr/location.rb +31 -0
  137. data/lib/skr/model.rb +37 -0
  138. data/lib/skr/null_user.rb +30 -0
  139. data/lib/skr/payment_term.rb +30 -0
  140. data/lib/skr/pick_ticket.rb +71 -0
  141. data/lib/skr/po_line.rb +69 -0
  142. data/lib/skr/po_receipt.rb +51 -0
  143. data/lib/skr/por_line.rb +80 -0
  144. data/lib/skr/pt_line.rb +74 -0
  145. data/lib/skr/purchase_order.rb +112 -0
  146. data/lib/skr/sales_order.rb +159 -0
  147. data/lib/skr/sequential_id.rb +23 -0
  148. data/lib/skr/sku.rb +99 -0
  149. data/lib/skr/sku_loc.rb +94 -0
  150. data/lib/skr/sku_tran.rb +111 -0
  151. data/lib/skr/sku_vendor.rb +26 -0
  152. data/lib/skr/so_line.rb +159 -0
  153. data/lib/skr/uom.rb +63 -0
  154. data/lib/skr/user_proxy.rb +60 -0
  155. data/lib/skr/validators/all.rb +2 -0
  156. data/lib/skr/validators/email.rb +17 -0
  157. data/lib/skr/validators/set.rb +18 -0
  158. data/lib/skr/vendor.rb +33 -0
  159. data/lib/skr/vo_line.rb +35 -0
  160. data/lib/skr/voucher.rb +119 -0
  161. data/lib/stockor/core.rb +9 -0
  162. data/stockor-core.gemspec +37 -0
  163. data/tasks/migrations.rake +23 -0
  164. data/tasks/publish.rake +8 -0
  165. data/test/address_test.rb +57 -0
  166. data/test/concerns/attr_with_default_test.rb +45 -0
  167. data/test/concerns/code_identifier_test.rb +46 -0
  168. data/test/concerns/export_associations_test.rb +7 -0
  169. data/test/concerns/export_methods_test.rb +31 -0
  170. data/test/concerns/export_scope_test.rb +16 -0
  171. data/test/concerns/exported_limits_test.rb +47 -0
  172. data/test/concerns/json_attribute_access_test.rb +27 -0
  173. data/test/concerns/pub_sub_test.rb +83 -0
  174. data/test/concerns/sanitize_json_test.rb +47 -0
  175. data/test/core/configuration_test.rb +24 -0
  176. data/test/core/numbers_test.rb +26 -0
  177. data/test/core/strings_test.rb +41 -0
  178. data/test/customer_test.rb +34 -0
  179. data/test/gl_account_test.rb +23 -0
  180. data/test/gl_manual_entry_test.rb +17 -0
  181. data/test/gl_period_test.rb +12 -0
  182. data/test/gl_posting_test.rb +39 -0
  183. data/test/gl_transaction_test.rb +58 -0
  184. data/test/ia_line_test.rb +68 -0
  185. data/test/ia_reason_test.rb +11 -0
  186. data/test/inv_line_test.rb +58 -0
  187. data/test/inventory_adjustment_test.rb +72 -0
  188. data/test/invoice_test.rb +63 -0
  189. data/test/location_test.rb +10 -0
  190. data/test/payment_term_test.rb +25 -0
  191. data/test/pick_ticket_test.rb +27 -0
  192. data/test/po_line_test.rb +36 -0
  193. data/test/po_receipt_test.rb +93 -0
  194. data/test/por_line_test.rb +79 -0
  195. data/test/pt_line_test.rb +29 -0
  196. data/test/purchase_order_test.rb +44 -0
  197. data/test/sales_order_test.rb +74 -0
  198. data/test/sku_loc_test.rb +50 -0
  199. data/test/sku_test.rb +45 -0
  200. data/test/sku_tran_test.rb +21 -0
  201. data/test/sku_vendor_test.rb +13 -0
  202. data/test/so_line_test.rb +89 -0
  203. data/test/test_helper.rb +1 -0
  204. data/test/uom_test.rb +14 -0
  205. data/test/vendor_test.rb +35 -0
  206. data/test/vo_line_test.rb +20 -0
  207. data/test/voucher_test.rb +35 -0
  208. data/yard_ext/all.rb +9 -0
  209. data/yard_ext/code_identifier_handler.rb +33 -0
  210. data/yard_ext/concern_meta_methods.rb +60 -0
  211. data/yard_ext/config_options.rb +27 -0
  212. data/yard_ext/exported_scope.rb +4 -0
  213. data/yard_ext/immutable_handler.rb +17 -0
  214. data/yard_ext/json_attr_accessor.rb +22 -0
  215. data/yard_ext/locked_fields_handler.rb +21 -0
  216. data/yard_ext/templates/default/layout/html/layout.erb +20 -0
  217. data/yard_ext/templates/default/method_details/html/github_link.erb +1 -0
  218. data/yard_ext/templates/default/method_details/setup.rb +3 -0
  219. data/yard_ext/validators.rb +1 -0
  220. data/yard_ext/visible_id_handler.rb +38 -0
  221. 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
@@ -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
@@ -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
@@ -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