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,47 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ module HasSkuLocLines
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+
10
+ def other_charge_lines
11
+ self.lines.select{|l| l.sku.is_other_charge? }
12
+ end
13
+
14
+ def regular_lines
15
+ self.lines.reject{|l| l.sku.is_other_charge? }
16
+ end
17
+
18
+ def regular_lines_total
19
+ self.regular_lines.sum{|l|l.extended_price}
20
+ end
21
+
22
+ def subtotal
23
+ self.regular_lines.inject(0){|sum,line| sum + line.extended_price }
24
+ end
25
+
26
+ def total
27
+ if total = self.read_attribute('total')
28
+ BigDecimal.new(total)
29
+ elsif self.new_record? || self.association(:lines).loaded?
30
+ self.lines.inject( BigDecimal.new('0') ){|sum,line| sum += line.extended_price }
31
+ else
32
+ BigDecimal.new( self.lines.sum('price*qty') )
33
+ end
34
+ end
35
+ end
36
+
37
+ module ClassMethods
38
+
39
+ def has_sku_loc_lines
40
+ self.send :include, InstanceMethods
41
+ export_methods :total
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ module ImmutableModel
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Once it's created it may not be updated or destroyed.
11
+ # @raise [ActiveRecord::ReadOnlyRecord] if destroy, delete or save is called after it's been created.
12
+ def is_immutable(options = {})
13
+
14
+ options[:except] = [*options[:except]].map{|name| name.to_s } # make sure except is present and an array
15
+
16
+ before_destroy do
17
+ raise ActiveRecord::ReadOnlyRecord.new( "Can not destroy #{self.class.model_name}, only create is allowed" )
18
+ end
19
+
20
+ before_update do
21
+ unless ( changes.keys - change_tracking_fields - options[:except] ).blank?
22
+ raise ActiveRecord::ReadOnlyRecord.new( "Can not update, only create #{self.class.model_name}" )
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ module INV
5
+
6
+ module Lines
7
+
8
+ def from_pick_ticket!
9
+ proxy_association.owner.pick_ticket.lines.each do | line |
10
+ build({ pt_line: line, qty: line.qty_to_ship })
11
+ end
12
+ end
13
+
14
+ def from_sales_order!
15
+ proxy_association.owner.sales_order.lines.each do | line |
16
+ build({ so_line: line, qty: line.qty_allocated })
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ module IsOrderLike
5
+ extend ActiveSupport::Concern
6
+
7
+ module InstanceMethods
8
+
9
+
10
+ # Set's the customer. It also defaults the terms, addresses,and tax_exempt status to the customer's defaults
11
+ # @param cust [Customer]
12
+ # @return Customer
13
+ def customer=(cust)
14
+ super
15
+ self.terms ||= cust.terms
16
+ self.is_tax_exempt = cust.is_tax_exempt if self.is_tax_exempt.nil?
17
+ self.billing_address = cust.billing_address.dup unless self.billing_address.present?
18
+ self.shipping_address = cust.shipping_address.dup unless self.shipping_address.present?
19
+ end
20
+
21
+ protected
22
+
23
+ def set_order_defaults
24
+ self.location ||= Location.default
25
+ self.terms ||= customer.terms if self.customer
26
+ true
27
+ end
28
+
29
+ end
30
+
31
+ module ClassMethods
32
+
33
+ def is_order_like
34
+ self.send :include, InstanceMethods
35
+ has_sku_loc_lines # pull in the sku_loc_lines module
36
+
37
+ validates_associated :lines
38
+
39
+ before_validation :set_order_defaults, :on=>:create
40
+
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ module IsSkuLocLine
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ module InstanceMethods
9
+ def uom_record
10
+ if self.association(:sku).loaded? && self.sku.association(:uoms).loaded?
11
+ self.sku.uoms.detect{|uom| uom.code == self.uom_code }
12
+ else
13
+ self.sku.uoms.where({ code: self.uom_code }).first
14
+ end
15
+ end
16
+
17
+ def ea_qty
18
+ self.qty*self.uom_size
19
+ end
20
+
21
+ def is_other_charge?
22
+ self.sku.is_other_charge?
23
+ end
24
+
25
+ def extended_price
26
+ self.price && self.qty ? self.price * self.qty : BigDecimal.new(0)
27
+ end
28
+
29
+ private
30
+
31
+ def ensure_sku_does_not_change
32
+ errors.add(:sku, "can not be updated") if changes['sku_code']
33
+ if change = changes['sku_loc_id']
34
+ # allow if the sku_id is the same on both old & new locations
35
+ unless 1 == SkuLoc.where( id: change ).pluck('sku_id').uniq.length
36
+ errors.add(:sku, "must be the same in both locations") if sku_code_changed?
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module ClassMethods
43
+
44
+ def is_sku_loc_line( parent: nil )
45
+ self.send :include, InstanceMethods
46
+
47
+ validate :ensure_sku_does_not_change, :on=>:update
48
+
49
+ if parent
50
+ before_create do
51
+ self.position ||= ( self.send( parent ).lines.maximum(:position) || 0 ) + 1
52
+ end
53
+ end
54
+
55
+ export_methods :extended_price, :optional=>false
56
+
57
+ end
58
+
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,55 @@
1
+ module Skr::Concerns
2
+
3
+ # @see ClassMethods
4
+ module JsonAttributeAccess
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :blacklisted_json_attributes
10
+ class_attribute :whitelisted_json_attributes
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ # An attribute accessor that allows access from the API
16
+ def json_attr_accessor( *names )
17
+ names.each do | attr |
18
+ attr_accessor attr
19
+ whitelist_json_attributes attr
20
+ end
21
+ end
22
+
23
+ # @param attributes [Array of symbols] attributes that are safe for the API to set
24
+ def whitelist_json_attributes( *attributes )
25
+ options = attributes.extract_options!
26
+ self.whitelisted_json_attributes ||= {}
27
+ attributes.each{|attr| self.whitelisted_json_attributes[ attr.to_sym ] = options }
28
+ end
29
+
30
+ # @param attributes [Array of symbols] attributes that are not safe for the API to set
31
+ def blacklist_json_attributes( *attributes )
32
+ options = attributes.extract_options!
33
+ self.blacklisted_json_attributes ||= {}
34
+ attributes.each{|attr| self.blacklisted_json_attributes[ attr.to_sym ] = options }
35
+ end
36
+
37
+ # An attribute is allowed if it's white listed
38
+ # or it's a valid attribute and not black listed
39
+ # @param name [Symbol]
40
+ # @param user [UserProxy,User] who is performing request
41
+ def json_attribute_is_allowed?(name, user = Skr::UserProxy.current)
42
+ return false unless user.can_write?(self, name)
43
+ (self.whitelisted_json_attributes && self.whitelisted_json_attributes.has_key?( name.to_sym)) ||
44
+ (
45
+ self.attribute_names.include?( name.to_s ) &&
46
+ ( self.blacklisted_json_attributes.nil? ||
47
+ ! self.blacklisted_json_attributes.has_key?( name.to_sym ) )
48
+ )
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,84 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ # @see ClassMethods
5
+ module LockedFields
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # Mark fields as locked, meaning they cannot be updated by using the
10
+ # regular attribute update methods. Instead it must be called in an unlock block
11
+ # Relies on attr_readonly internally
12
+ #
13
+ # Is used to designate sensitive fields that we want to make sure someone's thought about before updating
14
+ # Also solves the age old single equals vs double equals bug/typo.
15
+ #
16
+ # class BankAccount < Skr::Model
17
+ #
18
+ # end
19
+ #
20
+ # bank=BankAccount.find(1)
21
+ # b.mark_as_super if bank.account_balance = 42 # a bit contrived, but you get the idea
22
+ # b.save # oops, what's our balance now?
23
+ #
24
+ # Now let's try it again with locked_fields
25
+ #
26
+ # class BankAccount < Skr::Model
27
+ # attr_readonly :account_balance
28
+ # end
29
+ #
30
+ # bank=BankAccount.find(1)
31
+ # b.mark_as_super if bank.account_balance = 42
32
+ # b.save # Still not ideal since we marked the bank as super, but at least our balance is ok
33
+ #
34
+ # To update the balance we'd need to:
35
+ #
36
+ # b.unlock_fields( :account_balance ) do
37
+ # b.account_balance += 33
38
+ # end
39
+ # b.save
40
+ #
41
+ # This is still a bit contrived since we'd actually have
42
+ # an audit logger that would be involved and it'd be inside a transaction.
43
+
44
+ def locked_fields( *flds )
45
+ include InstanceMethods
46
+ attr_readonly( *flds )
47
+ end
48
+
49
+ def has_locks( *locks )
50
+ locks.each do | lock |
51
+ define_method( "unlock_#{lock}" ) do | &block |
52
+ instance_variable_set "@_lock_#{lock}_unlocked", true
53
+ block.call
54
+ remove_instance_variable "@_lock_#{lock}_unlocked"
55
+ end
56
+ define_method( "is_#{lock}_unlocked?") do
57
+ instance_variable_defined? "@_lock_#{lock}_unlocked"
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ module InstanceMethods
65
+ # Unlock the field for updates inside the block
66
+ # yields, then restores it.
67
+ # Is class wide, meaning it Will temporarily open all instances of the class up for access in a threaded environment
68
+ def unlock_fields( *flds, &block )
69
+ attr_syms = flds.map(&:to_s)
70
+
71
+ self.class.attr_readonly.subtract( attr_syms )
72
+
73
+ yield
74
+
75
+ attr_syms.each do | fld |
76
+ self.class.attr_readonly.add( fld )
77
+ end
78
+
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,22 @@
1
+ module Skr
2
+ module Concerns
3
+
4
+ module PT
5
+
6
+ module Lines
7
+
8
+ def set_ship_qty
9
+ each{|l| l.qty_to_ship = l.qty }
10
+ end
11
+
12
+ def ea_picking_qty
13
+ if proxy_association.loaded?
14
+ inject(0){ | sum, ptl | sum+(ptl.qty*ptl.uom_size) unless ptl.is_complete? }
15
+ else
16
+ where( is_complete: false ).sum('qty*uom_size')
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,105 @@
1
+ module Skr::Concerns
2
+
3
+ module PendingEventListeners
4
+ # @private
5
+ @@listeners = Hash.new{ |hash, klass| hash[klass] = Hash.new{ |kh, event| kh[event]=Array.new } }
6
+ # @api private
7
+ def self.all
8
+ @@listeners
9
+ end
10
+ end
11
+
12
+
13
+ # Event subscription and publishing for Stockor Models
14
+ # Every model has certain built-in events (:save, :create, :update, :destroy)
15
+ # And may also implement custom events that reflect the models domain
16
+ # @example Send an email when a customer's name is updated
17
+ # Customer.observe(:update) do |customer|
18
+ # Mailer.notify_billing(customer).deliver if customer.name_changed?
19
+ # end
20
+ # @example Update some stats when a Sku's qty is changed
21
+ # Sku.observe(:qty_changed) do | sku, location, old_qty, new_qty |
22
+ # Stats.refresh( location )
23
+ # end
24
+ module PubSub
25
+ extend ActiveSupport::Concern
26
+
27
+ class InvalidEvent < RuntimeError
28
+ end
29
+
30
+ included do | base |
31
+
32
+ class_attribute :valid_event_names
33
+ class_attribute :_event_listeners
34
+ self.valid_event_names = [ :save, :create, :update, :destroy ]
35
+
36
+ after_save :fire_after_save_events
37
+ after_create :fire_after_create_events
38
+ after_update :fire_after_update_events
39
+ after_destroy :fire_after_destroy_events
40
+ end
41
+
42
+ module ClassMethods
43
+ def inherited(base)
44
+ super
45
+ klass = base.to_s.demodulize
46
+ if PendingEventListeners.all.has_key?( klass )
47
+ events = PendingEventListeners.all.delete(klass)
48
+ events.each{ | name, procs | base.event_listeners[name] += procs }
49
+ end
50
+ end
51
+
52
+ def event_listeners
53
+ self._event_listeners ||= Hash.new{ |hash, key| hash[key]=Array.new }
54
+ end
55
+
56
+ def _add_event_listener( name, proc )
57
+ self.event_listeners[name].push( proc ) unless self.event_listeners[name].include?(proc)
58
+ end
59
+
60
+ def observe( event, &block )
61
+ _ensure_validate_event( event )
62
+ _add_event_listener( event.to_sym, block )
63
+ end
64
+
65
+ def _ensure_validate_event(event)
66
+ unless self.valid_event_names.include?(event.to_sym)
67
+ raise InvalidEvent.new("#{event} is not a valid event for #{self}")
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ def has_additional_events( *names )
74
+ self.valid_event_names += names.map{ |name| name.to_sym }
75
+ end
76
+ end
77
+
78
+ protected
79
+ def fire_after_destroy_events
80
+ _fire_event(:update, self )
81
+ end
82
+ def fire_after_update_events
83
+ _fire_event(:update, self )
84
+ end
85
+ def fire_after_create_events
86
+ _fire_event(:create, self )
87
+ end
88
+ def fire_after_save_events
89
+ _fire_event( :save, self )
90
+ end
91
+
92
+ def fire_event( name, *arguments )
93
+ self.class._ensure_validate_event( name )
94
+ arguments.unshift( self )
95
+ _fire_event( name, *arguments )
96
+ end
97
+
98
+ private
99
+ def _fire_event( name, *arguments )
100
+ self.class.event_listeners[ name.to_sym ].each{ | block | block.call(*arguments) }
101
+ return true
102
+ end
103
+ end
104
+
105
+ end