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,51 @@
1
+ module Skr
2
+
3
+ # Is a record of an inventory receipt
4
+ # A {PurchaseOrder} can have one or more of them
5
+
6
+ class PoReceipt < Skr::Model
7
+
8
+ has_visible_id
9
+ has_sku_loc_lines
10
+ has_gl_transaction
11
+
12
+ belongs_to :purchase_order, export: true
13
+ belongs_to :vendor, export: true
14
+ belongs_to :location, export: true
15
+
16
+ has_one :gl_transaction, :as=>:source
17
+
18
+ has_many :lines, :class_name=>'Skr::PorLine', export: { writable: true }, inverse_of: :po_receipt
19
+
20
+ validates :freight, numericality: true
21
+ validates :purchase_order, :location, presence: true
22
+
23
+ before_create :record_freight, if: ->{ freight.nonzero? }
24
+ after_create :logit
25
+
26
+ def purchase_order=(po)
27
+ super
28
+ self.location ||= purchase_order.location
29
+ self.vendor = purchase_order.vendor
30
+ end
31
+
32
+ private
33
+
34
+ def attributes_for_gl_transaction
35
+ { location: location, source: self,
36
+ description: "PO RECPT #{self.visible_id}" }
37
+ end
38
+
39
+ def logit
40
+ end
41
+
42
+ def record_freight
43
+ GlTransaction.current.add_posting( amount: self.freight,
44
+ debit: GlAccount.default_for( :inventory_receipts_clearing ),
45
+ credit: vendor.gl_freight_account
46
+ )
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,80 @@
1
+ module Skr
2
+
3
+ class PorLine < Skr::Model
4
+
5
+ acts_as_uom
6
+ is_sku_loc_line parent: 'po_receipt'
7
+
8
+ belongs_to :po_receipt
9
+ belongs_to :sku_loc, export: true
10
+ belongs_to :po_line, export: true
11
+ belongs_to :sku_vendor, export: true
12
+
13
+ has_one :sku, :through => :sku_loc, export: true
14
+
15
+ has_many :sku_trans, :as=>:origin, validate: true
16
+
17
+ before_create :adjust_inventory
18
+ # after_update :adjust_gl
19
+ validates :qty, numericality: { greater_than: 0 }
20
+
21
+ json_attr_accessor :auto_allocate
22
+
23
+ def po_line=(pol)
24
+ super
25
+ %w{ sku_code part_code description uom_code uom_size }.each do | attr |
26
+ self[ attr ] = po_line[ attr ]
27
+ end
28
+ self.sku_loc = pol.sku_loc
29
+ self.sku_vendor = pol.sku_vendor
30
+ self.uom = pol.uom
31
+ self.price = pol.price
32
+ self.qty = pol.qty_unreceived
33
+ end
34
+
35
+ private
36
+
37
+ def adjust_inventory
38
+ Core.logger.debug( "Receiving #{self.ea_qty} into stock" )
39
+ tran = self.sku_trans.build({
40
+ origin: self, qty: self.qty,
41
+ sku_loc: po_line.sku_loc,
42
+ origin_description: "PO #{self.po_line.purchase_order.visible_id}",
43
+ cost: self.extended_price, uom: self.uom,
44
+ credit_gl_account: self.sku.gl_asset_account,
45
+ debit_gl_account: GlAccount.default_for( :inventory_receipts_clearing )
46
+ })
47
+ if self.auto_allocate
48
+ tran.allocate_after_save = true
49
+ end
50
+ true
51
+ end
52
+
53
+ def ensure_qty_is_less_than_unreceived
54
+ if qty_changed? && po_line && qty > po_line.qty_unreceived
55
+ errors.add(:qty,"#{qty} must be less than unreceived qty of #{po_line.qty_unreceived}")
56
+ end
57
+ end
58
+
59
+ def adjust_gl
60
+ diff = price - price_was
61
+ return if diff.zero?
62
+
63
+ tran = self.sku_trans.build({ :origin=>self, :qty => 0, :sku_loc=>po_line.sku_loc,
64
+ :credit_gl_account => self.sku.gl_asset_account,
65
+ :uom_size=>self.uom_size, :uom_code=>self.uom_code })
66
+
67
+ tran.cost = diff * qty
68
+
69
+ tran.debit_gl_account = if voucher.confirmed?
70
+ voucher.vendor.gl_payables_account
71
+ else # otherwise it's in the clearing account
72
+ GlAccount.default_for( :inventory_receipts_clearing )
73
+ end
74
+ tran.save!
75
+
76
+ end
77
+
78
+
79
+ end
80
+ end
@@ -0,0 +1,74 @@
1
+ module Skr
2
+
3
+ class PtLine < Skr::Model
4
+
5
+ acts_as_uom
6
+ is_sku_loc_line parent: 'pick_ticket'
7
+
8
+ attr_accessor :qty_to_ship
9
+
10
+ belongs_to :pick_ticket, export: true
11
+ belongs_to :sku_loc, export: true
12
+ belongs_to :so_line
13
+
14
+ has_one :inv_line, inverse_of: :pt_line, listen: { save: :update_from_inv_line }
15
+ has_one :sales_order, :through=>:pick_ticket, export: true
16
+ has_one :sku, :through => :sku_loc, export: true
17
+
18
+ scope :picking, ->{ where({ :is_complete=>false }) }
19
+
20
+ delegate_and_export_field :pick_ticket, :visible_id
21
+ delegate_and_export_field :sales_order, :visible_id
22
+
23
+ before_create :set_defaults#, on: :create
24
+
25
+ def cancel!
26
+ self.update_attributes! :is_complete=>true
27
+ self.pick_ticket.maybe_cancel
28
+ end
29
+
30
+ def is_invoiceable?
31
+ ! self.is_complete? && self.qty_to_ship.to_i > 0
32
+ end
33
+
34
+ def total
35
+ self.price * ( self.qty_to_ship || self.qty )
36
+ end
37
+
38
+ private
39
+
40
+ def update_from_inv_line( inv_line )
41
+ self.update_attributes( qty_invoiced: inv_line.qty )
42
+ end
43
+
44
+ def set_defaults
45
+
46
+ if self.so_line.blank?
47
+ self.so_line = self.pick_ticket.sales_order.lines.where({ sku_loc_id: self.sku_loc_id }).first
48
+ end
49
+
50
+ if self.so_line.blank?
51
+ self.so_line = self.pick_ticket.sales_order.lines.create({
52
+ sku_loc: self.sku_loc, qty: self.qty, price: self.price,
53
+ uom: self.uom, description: self.description
54
+ })
55
+ unless self.so_line.valid?
56
+ self.errors.add(:so_line, "cannot be created because #{self.so_line.errors.full_messages.join(', ')}" )
57
+ return false
58
+ end
59
+ end
60
+
61
+ self.sku_loc ||= so_line.sku_loc
62
+ self.bin ||= sku_loc.bin
63
+ self.price ||= so_line.price
64
+ self.sku_code = sku_loc.sku.code if self.sku_code.blank?
65
+ self.description = so_line.description if self.description.blank?
66
+ self.uom = so_line.uom if self.uom.blank?
67
+ true
68
+ end
69
+
70
+
71
+ end
72
+
73
+
74
+ end # Skr module
@@ -0,0 +1,112 @@
1
+ module Skr
2
+
3
+ # A Purchase Order (often abbreviated as PO)
4
+ # is a record of a request to purchase a product from a Vendor.
5
+ # It contains one or more {Sku}, the quantity desired for each and the price offered for them.
6
+ #
7
+ # A Purchase Order progresses through set stages:
8
+ #
9
+ # * It starts of as "saved"
10
+ # This state indicates that the PO has been saved but no further action has been under-taken.
11
+ # * Once it has been sent to the Vendor, it transitions to Sent.
12
+ # * On reciept of a confirmation from the vendor it becomes Confirmed At this point the PO may be
13
+ # considered a binding agreement with the {Vendor}.
14
+ # * When the ordered SKUs are received, the PO will be marked as either Partial or Complete.
15
+ # * A {PoReceipt} is then created from the Purchase Order.
16
+
17
+ class PurchaseOrder < Skr::Model
18
+
19
+ has_visible_id
20
+
21
+ belongs_to :terms, export: true, class_name: 'Skr::PaymentTerm'
22
+ belongs_to :vendor, export: true
23
+ belongs_to :location, export: true
24
+ belongs_to :ship_addr, :class_name=>'Skr::Address', export: { writable: true }
25
+
26
+ has_many :lines, ->{ order(:position) }, :class_name=>'Skr::PoLine', :inverse_of=>:purchase_order, export: {writable:true}
27
+ has_many :receipts, class_name: 'Skr::PoReceipt'
28
+
29
+ before_validation :set_defaults, :on=>:create
30
+
31
+ validates :vendor, :location, :terms, :presence=>true
32
+
33
+ delegate_and_export :vendor_code, :vendor_name
34
+ delegate_and_export :location_code
35
+ delegate_and_export :terms_code, :terms_description
36
+
37
+ export_methods :total
38
+
39
+ blacklist_json_attributes :receiving_completed_at
40
+
41
+ after_save :set_maybe_completed!
42
+
43
+ export_join_tables :details
44
+ export_scope :with_details, lambda { |should_use=true |
45
+ joins('join po_details as details on details.purchase_order_id = purchase_orders.id')
46
+ .select('purchase_orders.*, details.*') if should_use
47
+ }
48
+
49
+ export_scope :only_incoming, lambda { |should_use=true|
50
+ with_details.where("state !='received'") if should_use
51
+ }
52
+
53
+ state_machine do
54
+ state :open, initial: true
55
+ state :received
56
+
57
+ event :mark_received do
58
+ transitions from: :open, to: :received
59
+ before do
60
+ self.receiving_completed_at = Time.now
61
+ end
62
+ end
63
+ end
64
+
65
+ def after_message_transmitted( msg )
66
+ self.mark_transmitted if self.can_mark_transmitted?
67
+ end
68
+
69
+ def set_maybe_completed!
70
+ self.mark_received! unless received? or self.lines.incomplete.any?
71
+ end
72
+
73
+ def to_shipping_address
74
+ Address.copy_from(self,'ship_')
75
+ end
76
+
77
+ def set_message_fields( msg )
78
+ msg.recipient = self.vendor.payments_address.email_with_name if msg.recipient.blank?
79
+ msg.subject = "Purchase Order # #{self.visible_id}"
80
+ attach = msg.attachments.build
81
+ attach.set_type_to_pdf
82
+ pdf = self.as_pdf
83
+ attach.file = pdf
84
+ end
85
+
86
+ def as_pdf( opts={} )
87
+ defaults = { :purchase_order => self, :include_received=>false }
88
+ Latex::Runner.new( 'purchase_order', defaults.merge( opts ) ).pdf( "PurchaseOrder_#{self.visible_id}.pdf" )
89
+ end
90
+
91
+ def total
92
+ if total = self.read_attribute('total')
93
+ BigDecimal.new(total)
94
+ elsif self.association(:lines).loaded?
95
+ self.lines.inject(0){|sum,line| sum + line.total }
96
+ else
97
+ BigDecimal.new( self.lines.sum('price*qty') )
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def set_defaults
104
+ self.location ||= Location.default
105
+ self.order_date ||= Date.today
106
+ self.terms ||= self.vendor.terms if self.vendor
107
+ self.ship_addr = self.location.address if self.ship_addr.blank? && self.location
108
+ end
109
+ end
110
+
111
+
112
+ end # Skr module
@@ -0,0 +1,159 @@
1
+ module Skr
2
+
3
+ # A SalesOrder is a record of a {Customer}'s desire to purchase one or more {Sku}s.
4
+ # It can be converted into an {Invoice} when the goods are delivered (or shipped)
5
+ # to the {Customer}
6
+ #
7
+ # customer = Customer.find_by_code "VIP1"
8
+ # so = SalesOrder.new( customer: customer )
9
+ # Sku.where( code: ['HAT','STRING'] ).each do | sku |
10
+ # so.lines.build( sku_loc: sku.sku_locs.default )
11
+ # end
12
+ # so.save
13
+ #
14
+ # invoice = Invoice.new( sales_order: so )
15
+ # invoice.lines.from_sales_order!
16
+ # invoice.save
17
+
18
+
19
+ class SalesOrder < Skr::Model
20
+
21
+ has_visible_id
22
+ has_random_hash_code
23
+ is_order_like
24
+
25
+ belongs_to :customer, export: true
26
+ belongs_to :location, export: true
27
+ belongs_to :terms, class_name: 'Skr::PaymentTerm', export: { writable: true }
28
+ belongs_to :billing_address, class_name: 'Skr::Address', export: { writable: true }
29
+ belongs_to :shipping_address, class_name: 'Skr::Address', export: { writable: true }
30
+
31
+ has_many :lines, ->{ order(:position) }, :class_name=>'Skr::SoLine', :inverse_of=>:sales_order,
32
+ extend: Concerns::SO::Lines, export: { writable: true }
33
+ has_many :skus, through: :lines
34
+ has_many :pick_tickets, inverse_of: :sales_order, before_add: :setup_new_pt
35
+ has_many :invoices, inverse_of: :sales_order, listen: { save: 'on_invoice' }
36
+
37
+ validates :location, :terms, :customer, set: true
38
+ validates :billing_address, :shipping_address, :order_date, presence: true
39
+ validate :ensure_location_changes_are_valid
40
+
41
+ after_save :check_if_location_changed
42
+ before_validation :set_defaults, on: :create
43
+
44
+ delegate_and_export :customer_code, :customer_name
45
+ delegate_and_export :location_code, :location_name
46
+ delegate_and_export :terms_code, :terms_description
47
+ delegate_and_export :billing_address_name
48
+
49
+
50
+ # joins the so_amount_details view which includes additional fields:
51
+ # customer_code, customer_name, bill_addr_name, total, num_lines, total_other_charge_amount,
52
+ # total_tax_amount, total_shipping_amount,subtotal_amount
53
+ scope :with_amount_details, lambda { | *args |
54
+ compose_query_using_detail_view(view: 'so_amount_details', join_to: 'sales_order_id')
55
+ }, export: true
56
+
57
+ # joins the so_allocation_details which includes the additional fields:
58
+ # number_of_lines, allocated_total, number_of_lines_allocated, number_of_lines_fully_allocated
59
+ scope :with_allocation_details, lambda {
60
+ compose_query_using_detail_view(view: 'so_allocation_details', join_to: 'sales_order_id')
61
+ }
62
+
63
+ # a open SalesOrder is one who's state is not "complete" or "canceled"
64
+ scope :open, lambda { | *args |
65
+ where( arel_table[:state].not_in ['complete', 'canceled'] )
66
+ }, export: true
67
+
68
+ # a SalesOrder is allocated if it has one or more lines with qty_allocated>0
69
+ scope :allocated, lambda { | *unused |
70
+ with_allocation_details.where('details.number_of_lines_allocated>0')
71
+ }, export: true
72
+
73
+ # a SalesOrder is fully allocated when it has all it's lines allocated
74
+ scope :fully_allocated, -> {
75
+ allocated.where('details.number_of_lines=details.number_of_lines_allocated')
76
+ }, export: true
77
+
78
+ # a SalesOrder is considered pickable if either:
79
+ # ship_partial=true and at least one line is allocated
80
+ # all lines are fully allocated
81
+ scope :pickable, ->(unused=nil){
82
+ allocated.where("( ship_partial='t' and details.number_of_lines_allocated > 0 ) " \
83
+ " or ( details.number_of_lines=details.number_of_lines_fully_allocated)")
84
+ }, export: true
85
+
86
+ # @return [Array of Array[day_ago,date, order_count,line_count,total]]
87
+ def self.sales_history( ndays )
88
+ qry = "select * from #{Skr::Core.config.table_prefix}so_dailly_sales_history where days_ago<#{ndays.to_i}"
89
+ connection.execute(qry).values
90
+ end
91
+
92
+ state_machine do
93
+ state :open, initial: true
94
+ state :complete
95
+ state :canceled
96
+
97
+ event :mark_complete do
98
+ transitions from: :open, to: :complete
99
+ end
100
+ event :mark_canceled do
101
+ transitions from: :open, to: :canceled
102
+ before :cancel_all_lines
103
+ end
104
+ end
105
+
106
+ def initialize(attributes = {})
107
+ super
108
+ self.order_date = Date.today
109
+ end
110
+
111
+ private
112
+
113
+ # When the location changes, lines need to have their sku_loc modified to point to the new location as well
114
+ def check_if_location_changed
115
+ if location_id_changed?
116
+ self.lines.each{ |l| l.location = self.location }
117
+ end
118
+ end
119
+
120
+ # The location can only be updated if all the line's skus are setup in the new location
121
+ def ensure_location_changes_are_valid
122
+ return true unless changes['location_id']
123
+ errors.add(:location, 'cannot be changed unless sales order is open') unless open?
124
+ current = self.sku_ids
125
+ setup = location.sku_locs.where( sku_id: current ).pluck('sku_id')
126
+ missing = current - setup
127
+ if missing.any?
128
+ codes = Sku.where( id: missing ).pluck('code')
129
+ errors.add(:location, "#{location.code} does not have skus #{codes.join(',')}")
130
+ end
131
+ end
132
+
133
+ # Initialize a new {PickTicket} by copying the pickable lines to it
134
+ def setup_new_pt(pt)
135
+ self.lines.each do | so_line |
136
+ pt.lines << so_line.pt_lines.build if so_line.pickable_qty > 0
137
+ end
138
+ true
139
+ end
140
+
141
+ # when the order is canceled, inform the lines
142
+ def cancel_all_lines
143
+ self.pick_tickets.each{ |pt| pt.cancel! }
144
+ self.lines.each{ | soline | soline.cancel! }
145
+ true
146
+ end
147
+
148
+ def on_invoice(inv)
149
+ self.mark_complete! if may_mark_complete? and lines.unshipped.none?
150
+ end
151
+
152
+ def set_defaults
153
+ if customer
154
+ self.billing_address = customer.billing_address if self.billing_address.blank?
155
+ self.shipping_address = customer.shipping_address if self.shipping_address.blank?
156
+ end
157
+ end
158
+ end
159
+ end # Skr module