acts_as_fifo_lifo 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 82b246066063db6e8bba14109b13951be0b077e767661e60d580143a1083e12e
4
+ data.tar.gz: da151bdb155f6b43125aae53f3ad31d6834d0c63e08487db8aaa6ad549321178
5
+ SHA512:
6
+ metadata.gz: ea09a13a508720420749418a33c1d6bf925f966588fd02fc9a9392fe97858a99395ce0d34eee823d02f29f7d33c83039ab302193058f509ebed693abccf1b11d
7
+ data.tar.gz: 3306b8b63055604ffeb81852a686d1e4db673a7f6a43c64bfd5c59c8095199dbf2e660fc68be1a061ac255f4ad08e1362bb201169b93a00fbabf92080ca3ce2b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Rem
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # ActsAsFifoLifo
2
+
3
+ A Rails gem providing FIFO (First In, First Out) and LIFO (Last In, First Out) inventory calculation methods for ActiveRecord models.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem "acts_as_fifo_lifo"
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ $ bundle
16
+ ```
17
+
18
+ Or install it yourself as:
19
+ ```bash
20
+ $ gem install acts_as_fifo_lifo
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ Include the module in your ActiveRecord model and configure the field mappings:
26
+
27
+ ```ruby
28
+ class StockTransaction < ApplicationRecord
29
+ acts_as_fifo_lifo item_field: :item_id,
30
+ qty_field: :quantity,
31
+ cost_field: :unit_cost,
32
+ time_field: :created_at,
33
+ batch_field: :batch_number,
34
+ storage_field: :storage_id,
35
+ operation_field: :operation_id,
36
+ operation_type_field: :operation_type
37
+
38
+ end
39
+ ```
40
+
41
+ ## Demo application:
42
+
43
+ [FIFO LIFO Warehouse Application](https://github.com/fatshinobi/fifo_lifo_warehouse)
44
+
45
+ ### Available Methods
46
+
47
+ #### `get_batches_for(item_id, store_id, qty, time_at, method: "fifo")`
48
+
49
+ Returns an ordered list of batches needed to satisfy a quantity request.
50
+
51
+ ```ruby
52
+ StockTransaction.get_batches_for(1, 1, 100, Time.current, method: "fifo")
53
+ # => [{ batch_number: "B001", qty: 50, cost: 10.5, batch_time: 2024-01-01 10:00:00 }, ...]
54
+ ```
55
+
56
+ #### `stock_balance_by_batches_calculation(storage_id: nil, item_id: nil, to_time: nil, fields_info: {})`
57
+
58
+ Returns stock balance grouped by storage, item, and batch in a nested structure.
59
+
60
+ #### `stock_balance_by_items_calculation(storage_id: nil, item_id: nil, to_time: nil, fields_info: {})`
61
+
62
+ Returns stock balance grouped by storage and item with mean cost calculation.
63
+
64
+ #### `stock_movement_calculation(storage_id: nil, item_id: nil, start_time: nil, end_time: nil, fields_info: {})`
65
+
66
+ Returns stock movement with running balance, grouped by storage, item, and transaction.
67
+
68
+ #### `stock_balance_for_items(item_id: nil, to_time: nil, limit: nil, fields_info: {})`
69
+
70
+ Returns an ActiveRecord::Relation with aggregate stock data per item (total_qty and mean_cost).
71
+
72
+ #### `stock_balance_for_items_calculation(item_id: nil, to_time: nil, fields_info: {})`
73
+
74
+ Transforms item stock balance records into a structured array format.
75
+
76
+ ## Contributing
77
+
78
+ Contribution directions go here.
79
+
80
+ ## License
81
+
82
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,4 @@
1
+ module ActsAsFifoLifo
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsFifoLifo
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,387 @@
1
+ require "acts_as_fifo_lifo/version"
2
+ require "acts_as_fifo_lifo/railtie"
3
+
4
+ module ActsAsFifoLifo
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ # Configures FIFO/LIFO behavior field mappings for the model.
9
+ #
10
+ # Stores field name mappings as class-level instance variables for use in
11
+ # FIFO/LIFO calculation methods. All parameters are required keyword arguments.
12
+ #
13
+ # @param item_field [Symbol,String] Field name for the item identifier
14
+ # @param qty_field [Symbol,String] Field name for the quantity
15
+ # @param cost_field [Symbol,String] Field name for the cost
16
+ # @param time_field [Symbol,String] Field name for the timestamp
17
+ # @param batch_field [Symbol,String] Field name for the batch identifier
18
+ # @param storage_field [Symbol,String] Field name for the storage identifier
19
+ # @param operation_field [Symbol,String] Field name for the operation identifier
20
+ # @param operation_type_field [Symbol,String] Field name for the operation type
21
+ def acts_as_fifo_lifo(item_field:, qty_field:, cost_field:, time_field:, batch_field:, storage_field:, operation_field:, operation_type_field:)
22
+ @fifo_item_field = item_field
23
+ @fifo_qty_field = qty_field
24
+ @fifo_cost_field = cost_field
25
+ @fifo_time_field = time_field
26
+ @fifo_batch_field = batch_field
27
+ @fifo_storage_field = storage_field
28
+ @fifo_operation_field = operation_field
29
+ @fifo_operation_type_field = operation_type_field
30
+ end
31
+
32
+ # Returns an ordered list of batches needed to satisfy a quantity request.
33
+ # Each element is a hash with :batch_number, :qty, :cost, and :batch_time keys.
34
+ #
35
+ # @param item_id [Integer] the identifier of the item
36
+ # @param store_id [Integer] the identifier of the storage location
37
+ # @param qty [Integer] the required quantity
38
+ # @param time_at [Time,DateTime,String] the reference timestamp for filtering transactions
39
+ # @param method [String] "fifo" for ascending order or "lifo" for descending order
40
+ # @return [Array<Hash{batch_number: String, qty: Integer, cost: Float, batch_time: Time}>]
41
+ def get_batches_for(item_id, store_id, qty, time_at, method: "fifo")
42
+ base_scope = where(
43
+ @fifo_item_field => item_id,
44
+ @fifo_storage_field => store_id
45
+ )
46
+
47
+ order_direction = method == "fifo" ? "ASC" : "DESC"
48
+
49
+ batch_records = base_scope
50
+ .group(@fifo_batch_field)
51
+ .select(
52
+ @fifo_batch_field,
53
+ "SUM(#{@fifo_qty_field}) AS total_qty",
54
+ "MIN(#{@fifo_cost_field}) AS total_cost",
55
+ "MIN(#{@fifo_time_field}) AS first_time"
56
+ )
57
+ .where("#{@fifo_time_field} <= ?", time_at)
58
+ .having("SUM(#{@fifo_qty_field}) > 0")
59
+ .order("first_time #{order_direction}")
60
+
61
+ result = []
62
+ remaining = qty
63
+
64
+ batch_records.each do |rec|
65
+ batch_number = rec.send(@fifo_batch_field)
66
+ batch_qty = rec.total_qty.to_i
67
+ batch_cost = rec.total_cost.to_f
68
+ batch_time = rec.first_time
69
+
70
+ take = [ batch_qty, remaining ].min
71
+ result << { batch_number: batch_number, qty: take, cost: batch_cost.round(2), batch_time: batch_time }
72
+ remaining -= take
73
+ break if remaining <= 0
74
+ end
75
+
76
+ result
77
+ end
78
+
79
+ # Calculates stock balance grouped by storage, item and batch.
80
+ # Returns a nested array of hashes with :details and :children keys.
81
+ # Each element represents a storage location (Level 1), containing items (Level 2),
82
+ # which in turn contain batches (Level 3) with their qty and cost.
83
+ #
84
+ # @param storage_id [Integer, nil] optional storage location filter
85
+ # @param item_id [Integer, nil] optional item filter
86
+ # @param to_time [Time, nil] optional upper bound timestamp for transactions
87
+ # @param fields_info [Hash] association field configuration for :storages and :items
88
+ # @return [Array<Hash{details: Hash, children: Array>]: nested structure with storage->items->batches
89
+ def stock_balance_by_batches_calculation(storage_id: nil, item_id: nil, to_time: nil, fields_info: {})
90
+ storage_include = fields_info.dig(:storages, :include) || :storage
91
+ item_include = fields_info.dig(:items, :include) || :item
92
+ storage_field = fields_info.dig(:storages, :field) || :name
93
+ item_field = fields_info.dig(:items, :field) || :name
94
+
95
+ base_scope = all
96
+
97
+ base_scope = base_scope.where(@fifo_storage_field => storage_id) if storage_id.present?
98
+ base_scope = base_scope.where(@fifo_item_field => item_id) if item_id.present?
99
+ base_scope = base_scope.where(@fifo_time_field => ...to_time) if to_time.present?
100
+
101
+ records = base_scope.group(@fifo_storage_field, @fifo_item_field, @fifo_batch_field)
102
+ .select(
103
+ @fifo_storage_field,
104
+ @fifo_item_field,
105
+ @fifo_batch_field,
106
+ "SUM(#{@fifo_qty_field}) AS total_qty",
107
+ "MIN(#{@fifo_cost_field}) AS batch_cost"
108
+ )
109
+
110
+ records = records.includes(storage_include, item_include)
111
+
112
+ nested_records = records.group_by(&@fifo_storage_field.to_sym).transform_values do |storage_group|
113
+ storage_group.group_by(&@fifo_item_field.to_sym)
114
+ end
115
+
116
+ results = []
117
+ nested_records.each do |storage_id, items_hash|
118
+ storage_name = items_hash.first.dig(1, 0)&.send(storage_include)&.send(storage_field) || "Storage #{storage_id}"
119
+ # Level 1: Storage level
120
+ storage_hash = { details: { item: storage_name, qty: 0, batch_cost: "", cost: 0 }, children: [] }
121
+
122
+ items_hash.each do |item_id, records|
123
+ item_name = records.first&.send(item_include)&.send(item_field) || "Item #{item_id}"
124
+
125
+ # Level 2: Item level
126
+ item_hash = { details: { item: item_name, qty: 0, batch_cost: "", cost: 0 }, children: [] }
127
+ item_hash[:details][:cost] = item_hash[:details][:cost].round(2)
128
+ item_hash[:details][:mean_cost] = item_hash[:details][:mean_cost].round(2) if item_hash[:details][:mean_cost].is_a?(Numeric)
129
+ storage_hash[:children] << item_hash
130
+
131
+ # Level 3: Batch records level (each record contains your select aliases)
132
+ records.each do |record|
133
+ batch_cost = record.batch_cost.to_f.round(2)
134
+ item_hash[:children] << { details: { item: record.batch_number, qty: record.total_qty.to_i, batch_cost: batch_cost, cost: (batch_cost * record.total_qty.to_i).round(2) }, children: [] }
135
+ item_hash[:details][:qty] += record.total_qty.to_i
136
+
137
+ batch_cost = record.batch_cost.to_f.round(2)
138
+ total_batch_cost = (batch_cost * record.total_qty.to_i).round(2)
139
+ item_hash[:details][:cost] = (item_hash[:details][:cost] + total_batch_cost).round(2)
140
+ storage_hash[:details][:qty] += record.total_qty.to_i
141
+ storage_hash[:details][:cost] = (storage_hash[:details][:cost] + total_batch_cost).round(2)
142
+ end
143
+ end
144
+ storage_hash[:details][:cost] = storage_hash[:details][:cost].round(2)
145
+ storage_hash[:details][:mean_cost] = storage_hash[:details][:mean_cost].round(2) if storage_hash[:details][:mean_cost].is_a?(Numeric)
146
+ results << storage_hash
147
+ end
148
+ results
149
+ end
150
+
151
+ # Calculates stock balance grouped by storage and item.
152
+ # Returns a nested array of hashes with :details and :children keys.
153
+ # Each element represents a storage location containing items with their mean cost.
154
+ #
155
+ # @param storage_id [Integer, nil] optional storage location filter
156
+ # @param item_id [Integer, nil] optional item filter
157
+ # @param to_time [Time, nil] optional upper bound timestamp for transactions
158
+ # @param fields_info [Hash] association field configuration for :storages and :items
159
+ # @return [Array<Hash{details: Hash, children: Array>]: nested structure with storage->items
160
+ def stock_balance_by_items_calculation(storage_id: nil, item_id: nil, to_time: nil, fields_info: {})
161
+ storage_include = fields_info.dig(:storages, :include) || :storage
162
+ item_include = fields_info.dig(:items, :include) || :item
163
+ storage_field = fields_info.dig(:storages, :field) || :name
164
+ item_field = fields_info.dig(:items, :field) || :name
165
+
166
+ base_scope = all
167
+
168
+ base_scope = base_scope.where(@fifo_storage_field => storage_id) if storage_id.present?
169
+ base_scope = base_scope.where(@fifo_item_field => item_id) if item_id.present?
170
+ base_scope = base_scope.where(@fifo_time_field => ...to_time) if to_time.present?
171
+
172
+ records = base_scope.group(@fifo_storage_field, @fifo_item_field)
173
+ .select(
174
+ @fifo_storage_field,
175
+ @fifo_item_field,
176
+ "SUM(#{@fifo_qty_field}) AS total_qty",
177
+ "SUM(#{@fifo_cost_field} * #{@fifo_qty_field}) / SUM(#{@fifo_qty_field}) AS item_cost"
178
+ )
179
+
180
+ records = records.includes(storage_include, item_include)
181
+ results = []
182
+
183
+ nested = records.group_by(&@fifo_storage_field.to_sym).transform_values do |storage_group|
184
+ storage_group.group_by(&@fifo_item_field.to_sym)
185
+ end
186
+
187
+ nested.each do |storage_id, items_hash|
188
+ first_record = items_hash.values.first.first
189
+ storage_name = first_record&.send(storage_include)&.send(storage_field) || "Storage #{storage_id}"
190
+
191
+ storage_hash = { details: { item: storage_name, qty: 0, mean_cost: "", cost: 0.0 }, children: [] }
192
+
193
+ items_hash.each do |item_id, recs|
194
+ first_item = recs.first
195
+ item_name = first_item&.send(item_include)&.send(item_field) || "Item #{item_id}"
196
+ item_hash = { details: { item: item_name, qty: 0, mean_cost: 0.0, cost: 0.0 }, children: [] }
197
+
198
+ recs.each do |record|
199
+ qty = record.total_qty.to_i
200
+ cost_per = record.item_cost.to_f
201
+ total_cost = (qty * cost_per).round(2)
202
+ item_hash[:details][:qty] += qty
203
+ item_hash[:details][:cost] += total_cost
204
+ if item_hash[:details][:qty] > 0
205
+ mean = item_hash[:details][:cost] / item_hash[:details][:qty]
206
+ item_hash[:details][:mean_cost] = mean.round(2)
207
+ end
208
+ storage_hash[:details][:qty] += qty
209
+ storage_hash[:details][:cost] += total_cost
210
+ end
211
+
212
+ storage_hash[:children] << item_hash
213
+ end
214
+
215
+ results << storage_hash
216
+ end
217
+
218
+ results
219
+ end
220
+
221
+ # Calculates stock movement returning a three-level nested structure.
222
+ # Groups by storage (Level 1), then by item (Level 2), then by batch/transaction (Level 3).
223
+ # Each transaction record includes running balance computed from the initial balance plus
224
+ # all preceding transactions in chronological order.
225
+ #
226
+ # @param storage_id [Integer, nil] optional storage location filter
227
+ # @param item_id [Integer, nil] optional item filter
228
+ # @param start_time [Time, nil] lower bound timestamp for transactions
229
+ # @param end_time [Time, nil] upper bound timestamp for transactions
230
+ # @param fields_info [Hash] association field configuration for :storages and :items
231
+ # @return [Array<Hash{details: Hash, children: Array>]: nested structure with storage->items->transactions
232
+ def stock_movement_calculation(storage_id: nil, item_id: nil, start_time: nil, end_time: nil, fields_info: {})
233
+ storage_include = fields_info.dig(:storages, :include) || :storage
234
+ item_include = fields_info.dig(:items, :include) || :item
235
+ storage_field = fields_info.dig(:storages, :field) || :name
236
+ item_field = fields_info.dig(:items, :field) || :name
237
+
238
+ balances = balance_for(start_time, item_id = item_id, store_id = storage_id)
239
+
240
+ base_scope = all
241
+ base_scope = base_scope.where(@fifo_storage_field => storage_id) if storage_id.present?
242
+ base_scope = base_scope.where(@fifo_item_field => item_id) if item_id.present?
243
+ base_scope = base_scope.where(@fifo_time_field => start_time..end_time) if start_time.present? && end_time.present?
244
+
245
+ records = base_scope
246
+ .select(
247
+ @fifo_storage_field,
248
+ @fifo_item_field,
249
+ @fifo_batch_field,
250
+ @fifo_time_field,
251
+ @fifo_qty_field,
252
+ @fifo_cost_field,
253
+ @fifo_operation_field,
254
+ @fifo_operation_type_field
255
+ )
256
+ .order(@fifo_time_field => :asc)
257
+
258
+ records = records.includes(storage_include, item_include)
259
+
260
+ nested = records.group_by(&@fifo_storage_field.to_sym).transform_values do |storage_group|
261
+ storage_group.group_by(&@fifo_item_field.to_sym)
262
+ end
263
+
264
+ results = []
265
+ nested.each do |storage_id_key, items_hash|
266
+ first_record = items_hash.values.first.first
267
+ storage_name = first_record&.send(storage_include)&.send(storage_field) || "Storage #{storage_id_key}"
268
+ storage_hash = { details: { item: storage_name, time: "", operation: "", qty: 0, cost: "", balance: 0 }, children: [] }
269
+
270
+ items_hash.each do |item_id_key, recs|
271
+ first_item = recs.first
272
+ item_name = first_item&.send(item_include)&.send(item_field) || "Item #{item_id_key}"
273
+ item_balance = balances[[ item_id_key, storage_id_key ]] || 0
274
+ item_hash = { details: { item: item_name, time: "", operation: "", qty: 0, cost: "", balance: item_balance }, children: [] }
275
+
276
+ running_balance = item_balance
277
+ recs.each do |record|
278
+ qty = record.send(@fifo_qty_field).to_i
279
+ cost = record.send(@fifo_cost_field).to_f
280
+ running_balance += qty
281
+
282
+ item_hash[:children] << {
283
+ details: {
284
+ item: record.send(@fifo_batch_field),
285
+ time: record.send(@fifo_time_field).strftime("%F %T"),
286
+ operation: "#{record.send(@fifo_operation_type_field)} ##{record.send(@fifo_operation_field)}",
287
+ qty: qty,
288
+ cost: cost.round(2),
289
+ balance: running_balance
290
+ },
291
+ children: []
292
+ }
293
+
294
+ item_hash[:details][:qty] += qty
295
+ end
296
+
297
+ storage_hash[:children] << item_hash
298
+
299
+ storage_hash[:details][:qty] += item_hash[:details][:qty]
300
+ storage_hash[:details][:balance] += item_hash[:details][:balance]
301
+ end
302
+
303
+ results << storage_hash
304
+ end
305
+ results
306
+ end
307
+
308
+ # Computes initial stock balances for items at a given point in time.
309
+ # Returns a hash mapping [item_id, storage_id] pairs to their balance quantities.
310
+ #
311
+ # @param to_time [Time] the reference timestamp for the balance calculation
312
+ # @param item_id [Integer, nil] optional item filter
313
+ # @param store_id [Integer, nil] optional storage location filter
314
+ # @return [Hash{Array<item_id, storage_id> => Integer] mapping of item-storage pairs to quantities
315
+ def balance_for(to_time, item_id = nil, store_id = nil)
316
+ item_scope = item_id.present? ? where(@fifo_item_field => item_id) : all
317
+ store_scope = store_id.present? ? item_scope.where(@fifo_storage_field => store_id) : item_scope
318
+
319
+ result = store_scope
320
+ .where("#{@fifo_time_field} <= ?", to_time)
321
+ .group(@fifo_storage_field, @fifo_item_field)
322
+ .select(
323
+ @fifo_storage_field,
324
+ @fifo_item_field,
325
+ "SUM(#{@fifo_qty_field}) AS total_qty"
326
+ )
327
+
328
+ result.each_with_object({}) do |record, hash|
329
+ item_id_key = record.send(@fifo_item_field)
330
+ store_id_key = record.send(@fifo_storage_field)
331
+ hash[[ item_id_key, store_id_key ]] = record.total_qty.to_i
332
+ end
333
+ end
334
+
335
+ # Queries aggregate stock data for items using mean cost calculation.
336
+ # Returns an ActiveRecord::Relation with total_qty and mean_cost selected per item.
337
+ #
338
+ # @param item_id [Integer, nil] optional item filter
339
+ # @param to_time [Time, nil] optional upper bound timestamp for transactions
340
+ # @param limit [Integer, nil] optional limit on number of results
341
+ # @param fields_info [Hash] association field configuration for :items
342
+ # @return [ActiveRecord::Relation] query scope for further chaining or execution
343
+ def stock_balance_for_items(item_id: nil, to_time: nil, limit: nil, fields_info: {})
344
+ scope = all
345
+ scope = scope.where(@fifo_item_field => item_id) if item_id.present?
346
+ scope = scope.where("#{@fifo_time_field} <= ?", to_time) if to_time.present?
347
+ scope = scope.group(@fifo_item_field)
348
+ .select(
349
+ @fifo_item_field,
350
+ "SUM(#{@fifo_qty_field}) AS total_qty",
351
+ "SUM(#{@fifo_cost_field} * #{@fifo_qty_field}) / SUM(#{@fifo_qty_field}) AS mean_cost"
352
+ )
353
+ scope = scope.order("total_qty DESC")
354
+ item_include = fields_info.dig(:items, :include) || :item
355
+
356
+ scope = scope.includes(item_include)
357
+ scope = scope.limit(limit) if limit.present?
358
+ scope
359
+ end
360
+
361
+ # Transforms item stock balance records into a structured format.
362
+ # Wraps each item's data in a :details/:children hash structure.
363
+ #
364
+ # @param item_id [Integer, nil] optional item filter
365
+ # @param to_time [Time, nil] optional upper bound timestamp for transactions
366
+ # @param fields_info [Hash] association field configuration for :items
367
+ # @return [Array<Hash{details: Hash, children: Array>]: array of item summaries with qty and mean_cost
368
+ def stock_balance_for_items_calculation(item_id: nil, to_time: nil, fields_info: {})
369
+ records = stock_balance_for_items(item_id: item_id, to_time: to_time, fields_info: fields_info)
370
+ item_include = fields_info.dig(:items, :include) || :item
371
+ item_field = fields_info.dig(:items, :field) || :name
372
+
373
+ records.map do |record|
374
+ {
375
+ details: {
376
+ item: record.send(item_include)&.send(item_field),
377
+ qty: record.total_qty,
378
+ cost: record.mean_cost.round(2)
379
+ },
380
+ children: []
381
+ }
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ ActiveRecord::Base.send :include, ActsAsFifoLifo
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_fifo_lifo do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_fifo_lifo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rem
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-05-31 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.1.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 8.1.3
26
+ description: A Rails gem for handling FIFO (First-In, First-Out) and LIFO (Last-In,
27
+ First-Out) inventory management operations.
28
+ email:
29
+ - r3mnik@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/acts_as_fifo_lifo.rb
38
+ - lib/acts_as_fifo_lifo/railtie.rb
39
+ - lib/acts_as_fifo_lifo/version.rb
40
+ - lib/tasks/acts_as_fifo_lifo_tasks.rake
41
+ homepage: https://github.com/fatshinobi/acts_as_fifo_lifo/tree/main
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ homepage_uri: https://github.com/fatshinobi/acts_as_fifo_lifo/tree/main
46
+ source_code_uri: https://github.com/fatshinobi/acts_as_fifo_lifo/tree/main
47
+ changelog_uri: https://github.com/fatshinobi/acts_as_fifo_lifo/blob/main/CHANGELOG.md
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.6.2
63
+ specification_version: 4
64
+ summary: Gem to process FIFO & LIFO operations in inventory management.
65
+ test_files: []