stockor-core 0.2

Sign up to get free protection for your applications and to get access to all the features.
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