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,31 @@
1
+ module Skr
2
+
3
+ # Records a manual entry into the General Leger.
4
+ # Most GL entries are triggered by system events such as an Inventory Receipt, or Invoice payment.
5
+ # A manual entry differs in that it's performed, well *Manually*.
6
+ # It's usually used to balance ledger accounts or as part of closing an accounting period.
7
+ class GlManualEntry < Skr::Model
8
+
9
+ has_visible_id
10
+
11
+ is_immutable
12
+
13
+ has_one :gl_transaction, :as=>:source,
14
+ inverse_of: :source, export: { writable: true }
15
+
16
+ validates :gl_transaction, presence: true
17
+
18
+ before_create :copy_notes_to_transaction
19
+
20
+ def gl_transaction=( transaction )
21
+ super
22
+ copy_notes_to_transaction
23
+ end
24
+ private
25
+
26
+ def copy_notes_to_transaction
27
+ self.gl_transaction.description = self.notes[0..100]
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module Skr
2
+
3
+ class GlPeriod < Skr::Model
4
+
5
+ is_immutable
6
+
7
+ def self.current
8
+ attr = { year: Time.now.year, period: Time.now.month }
9
+ GlPeriod.where( attr ).first || GlPeriod.create( attr )
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,54 @@
1
+ module Skr
2
+ class GlPosting < Skr::Model
3
+
4
+ is_immutable
5
+
6
+ belongs_to :gl_transaction
7
+
8
+ before_validation :cache_related_attributes
9
+
10
+ validates :gl_transaction, set: true
11
+ validates :account_number, numericality: true, length: { :is=>6 }
12
+ validates :amount, numericality: true, presence: true
13
+ validate :ensure_accounting_validity, on: :create
14
+
15
+ scope :applying_to_period, ->(period){ where( '(period <= :period and year = :year) or (year < :year)',
16
+ { period: period.period, year: period.year } ) }
17
+ scope :matching, ->(period, account_mask){
18
+ applying_to_period( period ).where('account_number like ?', account_mask )
19
+ }
20
+
21
+ def account=(acct)
22
+ @account = acct
23
+ assign_account_number
24
+ end
25
+
26
+ def location=(location)
27
+ @location = location
28
+ assign_account_number
29
+ end
30
+
31
+ private
32
+
33
+ def assign_account_number
34
+ self.account_number = @account.number_for_location(@location) if @account && @location
35
+ end
36
+
37
+ def ensure_accounting_validity
38
+ unless self.gl_transaction.new_record? #postings_create_ok?
39
+ self.errors.add( :gl_transaction, "does not accept new postings" )
40
+ end
41
+ if @account && ! @account.is_active?
42
+ self.errors.add(:account, "is not active")
43
+ end
44
+
45
+ end
46
+
47
+ def cache_related_attributes
48
+ assign_account_number
49
+ self.year = gl_transaction.period.year
50
+ self.period = gl_transaction.period.period
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,174 @@
1
+ module Skr
2
+
3
+ # A transaction is a record of a business event that has financial consequences.
4
+ # It consists of an at least one credit and at least one debit
5
+ # Transactions can be nested, with each level compacting all the entries that were made on it
6
+ #
7
+ # require 'skr/core'
8
+ # customer = Customer.find_by_code "MONEYBAGS"
9
+ # GlTransaction.record( source: invoice, description: "Invoice Example" ) do | transaction |
10
+ # transaction.location = Location.default # <- could also specify in record's options
11
+ # Sku.where( code: ['HAT','STRING'] ).each do | sku |
12
+ # transaction.add_posting( amount: sku.default_price,
13
+ # debit: sku.gl_asset_account,
14
+ # credit: customer.gl_receivables_account )
15
+ # end
16
+ # end
17
+ #
18
+ class GlTransaction < Skr::Model
19
+
20
+ is_immutable
21
+
22
+ # A Transaction must refer to another record, such as Invoice, Inventory Adjustment, or a Manual Posting.
23
+ belongs_to :source, :polymorphic=>true
24
+
25
+ # Each transaction belongs to an accounting period
26
+ belongs_to :period, :class_name=>'Skr::GlPeriod', export: true
27
+
28
+ has_many :credits, ->{ where({ is_debit: false }) }, class_name: 'Skr::GlPosting',
29
+ extend: Concerns::GlTran::Postings,
30
+ inverse_of: :gl_transaction, export: { writable: true }
31
+
32
+ # Must equal credits, checked by the {#ensure_postings_correct} validation
33
+ has_many :debits, ->{ where({ is_debit: true }) }, class_name: 'Skr::GlPosting',
34
+ extend: Concerns::GlTran::Postings,
35
+ inverse_of: :gl_transaction, export: { writable: true }
36
+
37
+ before_validation :set_defaults
38
+ validate :ensure_postings_correct
39
+ validates :source, :period, :set=>true
40
+ validates :description, :presence=>true
41
+
42
+
43
+ # Add a debit/credit pair to the transaction with amount
44
+ # @param amount [BigDecimal] the amount to apply to each posting
45
+ # @param debit [GlAccount]
46
+ # @param credit [GlAccount]
47
+ def add_posting( amount: nil, debit: nil, credit: nil )
48
+ Skr::Core.logger.debug "GlTransaction add_posting #{debit} : #{credit}"
49
+ self.credits.build( location: @location, is_debit: false,
50
+ account: credit, amount: amount )
51
+ self.debits.build( location: @location, is_debit: true,
52
+ account: debit, amount: amount * -1 )
53
+ end
54
+
55
+ # Passes the location onto the postings.
56
+ # @param location [Location]
57
+ def location=(location)
58
+ @location = location
59
+ each_posting do | posting |
60
+ posting.location = location
61
+ end
62
+ end
63
+
64
+ # @yield [GlPosting] each posting associated with the Transaction
65
+ def each_posting
66
+ self.credits.each{ |posting| yield posting }
67
+ self.debits.each{ |posting| yield posting }
68
+ end
69
+
70
+ # @return [GlTransaction] the current transaction that's in progress
71
+ def self.current
72
+ glt = Thread.current[:gl_transaction]
73
+ glt ? glt.last : nil
74
+ end
75
+
76
+ # Start a new nested GlTransaction
77
+ # When a transaction is created, it can have
78
+ # @return [GlTransaction] new transaction
79
+ # @yield [GlTransaction] new transaction
80
+ def self.record( attributes={} )
81
+ Thread.current[:gl_transaction] ||= []
82
+ glt = GlTransaction.new( attributes )
83
+ Thread.current[:gl_transaction].push( glt )
84
+ Skr::Core.logger.debug "B4 GlTransaction"
85
+ results = yield glt
86
+ Thread.current[:gl_transaction].pop
87
+ if results
88
+ if results.is_a?(Hash) && results.has_key?(:attributes)
89
+ glt.assign_attributes( results[:attributes] )
90
+ end
91
+ glt._save_recorded
92
+ Skr::Core.logger.debug "AF GlTransaction new=#{glt.new_record?} #{glt.errors.full_messages}"
93
+ end
94
+ return glt
95
+ end
96
+
97
+ # @param owner [Skr::Model]
98
+ # @param amount [BigDecimal]
99
+ # @param debit [GlAccount]
100
+ # @param credit [GlAccount]
101
+ # @param options [Hash] options to pass to the [GlTransaction] if one is created
102
+ def self.push_or_save( owner: nil, amount: nil, debit:nil, credit:nil, options:{} )
103
+ if glt = self.current # we push
104
+ glt.add_posting( amount: amount, debit: debit, credit: credit )
105
+ else
106
+ options.merge!({
107
+ source: owner,
108
+ location: options[:location] || owner.location
109
+ })
110
+ glt = GlTransaction.new( options )
111
+ glt.add_posting( amount: amount, debit: debit, credit: credit )
112
+ glt.save
113
+ end
114
+ glt
115
+ end
116
+
117
+ # @private
118
+ def _save_recorded
119
+ compact( 'debits' )
120
+ compact( 'credits' )
121
+ self.save if self.credits.any? || self.debits.any?
122
+ self
123
+ end
124
+
125
+ private
126
+
127
+ def compact( assoc_name )
128
+ accounts = self.send( assoc_name ).to_a
129
+ self.send( assoc_name + "=", [] )
130
+ account_numbers = accounts.group_by{ |posting| posting.account_number }
131
+ account_numbers.each do | number, matching |
132
+ amount = matching.sum(&:amount)
133
+ self.send( assoc_name ).build({
134
+ account_number: number,
135
+ is_debit: ( assoc_name == "debits" ),
136
+ amount: amount,
137
+ })
138
+ end
139
+ end
140
+
141
+ def ensure_postings_correct
142
+ if debits.total != ( -1 * credits.total )
143
+ self.errors.add(:credits, "must equal debits")
144
+ self.errors.add(:debits, "must equal credits")
145
+ return false
146
+ end
147
+ true
148
+ end
149
+
150
+ def set_defaults
151
+ self.period ||= GlPeriod.current
152
+ end
153
+
154
+ # def ensure_postings_exist
155
+ # if self.new_record? and self.postings.empty?
156
+ # 2.times { self.postings.build }
157
+ # end
158
+ # end
159
+
160
+ end
161
+ end
162
+
163
+ __END__
164
+
165
+ # export_join_tables :details
166
+ # export_scope :with_details_for, lambda { | acct |
167
+ # acct = acct.gsub(/\D/,'') + '%'
168
+ # cs = "case when details.debit_account_number like '#{acct}' then details.debit_amount " +
169
+ # "when details.credit_account_number like '#{acct}' then details.credit_amount else 0 end"
170
+ # window = "#{cs} as amount, sum(#{cs}) over (order by created_at) as balance"
171
+ # joins('join gl_transaction_details as details on details.gl_transaction_id = gl_transactions.id')
172
+ # .select("gl_transactions.*, details.*, #{window}")
173
+ # .where("debit_account_number like :acct or credit_account_number like :acct", acct: acct)
174
+ # }
@@ -0,0 +1,129 @@
1
+ module Skr
2
+
3
+ # An Inventory Adjustment Line.
4
+ # Each model contains the {SkuLoc}, cost, UOM, and qty for a {Sku} to adjust
5
+ #
6
+ # An adjustment starts out in the "pending" state, then when it moves to the "applied"
7
+ # state, each line creates an {SkuTran} record that adjusts the inventory in or out.
8
+ class IaLine < Skr::Model
9
+
10
+ acts_as_uom
11
+
12
+ belongs_to :inventory_adjustment
13
+ belongs_to :sku_loc, export: true
14
+ has_one :sku, :through => :sku_loc, export: true
15
+
16
+ has_one :sku_tran, :as=>:origin
17
+ scope :unapplied, lambda { includes(:sku_tran).references(:sku_tran).where( SkuTran.table_name+'.id is null' ) }
18
+
19
+ export_methods :sku_loc_qty, :sku_loc_mac, :optional=>false
20
+
21
+ validates :sku_loc, set: true
22
+ validates :qty, numericality: true
23
+ validates :uom_code, :uom_size, presence: true
24
+ validate :ensure_cost_set_properly
25
+
26
+ before_save :set_cost_from_sku_loc
27
+ before_destroy :ensure_adjustment_isnt_applied
28
+ before_update :ensure_adjustment_isnt_applied
29
+ delegate_and_export :sku_code, :sku_description
30
+
31
+ has_locks :adjusting
32
+
33
+ # @return [Fixnum] The qty available on the location, expressed in terms of the UOM
34
+ def sku_loc_qty
35
+ ( self.uom_size && self.sku_loc ) ? BigDecimal.new( self.sku_loc.qty ) / self.uom_size : 0
36
+ end
37
+
38
+ # @return [BigDecimal] the current moving average cost (mac) on the location, expressed in terms of the UOM
39
+ def sku_loc_mac
40
+ self.sku_loc ? ( self.sku_loc.mac * self.uom_size ) : 0
41
+ end
42
+
43
+ # @return [Boolean] has the line been applied
44
+ def is_applied?
45
+ sku_tran.present?
46
+ end
47
+
48
+ # @return [Boolean] is the qty negative?
49
+ def is_removing_qty?
50
+ qty && qty <=0
51
+ end
52
+
53
+ # The qty for the line expressed in terms of the single UOM
54
+ def ea_qty
55
+ self.uom_size * self.qty
56
+ end
57
+
58
+ # @return [BigDecimal] the total value of the line
59
+ def total
60
+ self.ledger_cost * self.qty
61
+ end
62
+ #alias_method :ledger_value, :total
63
+
64
+ # @return [BigDecimal] either the current MAC for the sku's location or the cost that was manually set
65
+ def ledger_cost
66
+ if cost_was_set? || is_applied?
67
+ self.cost
68
+ else
69
+ self.sku_loc_mac
70
+ end
71
+ end
72
+
73
+ # copies the cost from the sku_loc to the {#cost} field
74
+ def set_cost_from_sku_loc
75
+ if ! cost_was_set?
76
+ self.cost = self.sku_loc_mac
77
+ end
78
+ true
79
+ end
80
+
81
+ # Perform the adjustment. Requires adjusting to be unlocked and {#is_applied?} must be false
82
+ #
83
+ # It creates a {SkuTran} to adjust the inventory, and allocates available qty to the {SoLine}
84
+ def adjust_qty!
85
+ if ! is_adjusting_unlocked? || is_applied?
86
+ raise "Unable to apply line, either not approved or previously applied"
87
+ end
88
+ set_cost_from_sku_loc
89
+ Core.logger.debug( "Adjusting #{self.qty} #{combined_uom} of #{sku_code} into stock")
90
+ self.build_sku_tran({
91
+ :origin=>self, :qty => self.qty, :sku_loc=>self.sku_loc,
92
+ origin_description: "IA #{self.inventory_adjustment.visible_id}:#{self.sku.code}",
93
+ cost: total, uom: self.uom,
94
+ allocate_after_save: true,
95
+ debit_gl_account: self.inventory_adjustment.reason.gl_account,
96
+ credit_gl_account: self.sku.gl_asset_account
97
+ })
98
+ self.sku_tran.save unless self.new_record?
99
+ true
100
+ end
101
+
102
+ private
103
+
104
+
105
+ # Cost cannot be set if the qty is negative
106
+ def ensure_cost_set_properly
107
+ if cost_was_set? && is_removing_qty?
108
+ errors.add(:cost,'cannot be set if removing qty')
109
+ return false
110
+ end
111
+ true
112
+ end
113
+
114
+ def ensure_adjustment_isnt_applied
115
+ if inventory_adjustment.applied?
116
+ errors.add(:base,'cannot be modified after adjustment is approved and applied')
117
+ return false
118
+ else
119
+ return true
120
+ end
121
+ end
122
+
123
+
124
+
125
+
126
+ end
127
+
128
+
129
+ end # Skr module
@@ -0,0 +1,16 @@
1
+ module Skr
2
+
3
+ class IaReason < Skr::Model
4
+
5
+ has_code_identifier
6
+
7
+ belongs_to :gl_account, export: true
8
+
9
+ delegate_and_export :gl_account_number, :gl_account_name
10
+
11
+ validates :gl_account, set: true
12
+
13
+ end
14
+
15
+
16
+ end # Skr module
@@ -0,0 +1,90 @@
1
+ module Skr
2
+
3
+ class InvLine < Skr::Model
4
+
5
+ acts_as_uom
6
+ is_immutable
7
+ is_sku_loc_line parent: "invoice"
8
+
9
+ locked_fields :qty, :sku_loc_id, :so_line
10
+ belongs_to :invoice
11
+
12
+ belongs_to :so_line, export: true
13
+ belongs_to :pt_line, :inverse_of=>:inv_line, export: true
14
+ belongs_to :sku_loc, export: true
15
+
16
+ has_one :sku, :through => :sku_loc, export: true
17
+ has_one :location, :through => :sku_loc
18
+ has_one :sku_tran, :as=>:origin
19
+
20
+ validates :sku_loc, set: true
21
+
22
+ validates :price, :qty, :numericality=>true
23
+ validates :uom_code, :sku_code, :description, :uom_size, :presence=>true
24
+
25
+ before_validation :set_defaults
26
+ before_save :adjust_inventory
27
+
28
+ scope :with_details, lambda { |should_use=true |
29
+ compose_query_using_detail_view( view: 'inv_details', join_to: 'inv_line_id' ) if should_use
30
+ }, export: true
31
+
32
+ scope :summarize_by, lambda { | values |
33
+ select( "sum(inv_lines.qty) as qty, count(*) as num_sales, sum(inv_lines.price) as price, avg(inv_lines.price) as avg_price, s.code as sku_code, s.description, s.id as sku_id, 1 as uom_size, 'EA' as uom_code")
34
+ .joins("join sku_locs sl on sl.id = inv_lines.sku_loc_id join skus s on s.id = sl.sku_id")
35
+ .where(["inv_lines.created_at between ? and ?",values['from'], values['to'] ])
36
+ .group('s.code,s.description, s.id')
37
+ }, export: true
38
+
39
+
40
+ private
41
+
42
+ def set_defaults
43
+ # puts "set_defaults #{self}"
44
+ line = [ self.pt_line, self.so_line ].detect{ |l| ! l.blank? }
45
+ if line
46
+ self.uom = line.uom if self.uom.blank?
47
+ self.sku_code ||= line.sku_code
48
+ self.description ||= line.description
49
+ self.sku_loc ||= line.sku_loc
50
+ self.qty ||= line.qty
51
+ self.so_line ||= line.so_line
52
+ elsif sku
53
+ self.uom = sku.uoms.default if self.uom_code.blank?
54
+ self.sku_code ||= sku.code
55
+ self.description ||= sku.description
56
+ self.sku_loc_id ||= sku.sku_locs.for_location( self.invoice.location ).id if self.invoice && self.invoice.location
57
+ end
58
+ if !price && invoice && invoice.customer && sku_loc && uom.present?
59
+ self.price = Core.config.pricing_provider.price(
60
+ sku_loc:sku_loc, customer:invoice.customer,
61
+ uom:uom, qty: qty )
62
+ end
63
+ true
64
+ end
65
+
66
+ def adjust_inventory
67
+ debit = self.sku.gl_asset_account
68
+ credit = self.invoice.customer.gl_receivables_account
69
+ if self.sku.does_track_inventory?
70
+ self.build_sku_tran({
71
+ origin: self, qty: self.qty*-1, sku_loc: self.sku_loc, uom: self.uom,
72
+ mac: 0, cost: self.extended_price,
73
+ origin_description: "INV #{self.invoice.visible_id}:#{self.sku.code}",
74
+ debit_gl_account: debit, credit_gl_account: credit
75
+ })
76
+ else
77
+ GlTransaction.push_or_save(
78
+ owner: self, amount: self.total,
79
+ debit: debit, credit: credit
80
+ )
81
+ end
82
+ true
83
+ end
84
+
85
+
86
+
87
+ end
88
+
89
+
90
+ end # Skr module