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,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