Class: ItemLedgerEntry

Inherits:
ApplicationRecord show all
Defined in:
app/models/item_ledger_entry.rb

Overview

== Schema Information

Table name: item_ledger_entries
Database name: primary

id :integer not null, primary key
category :string(255)
currency :string(255)
description :string(255)
gl_date :date
location :string(255)
new_qty_on_hand :integer
new_unit_cogs :decimal(10, 4)
old_qty_on_hand :integer
old_unit_cogs :decimal(10, 4)
quantity :integer
quantity_eval :integer
total_cost :decimal(10, 4)
unit_cost :decimal(10, 4)
created_at :datetime
updated_at :datetime
business_unit_id :integer
creator_id :integer
cycle_count_item_id :integer
delivery_id :integer
invoice_id :integer
item_id :integer
item_kit_id :integer
landed_cost_id :integer
ledger_company_account_id :integer
ledger_detail_project_id :integer
ledger_transaction_id :integer
reversed_entry_id :integer
rma_item_id :integer
shipment_receipt_item_id :integer
store_id :integer
updater_id :integer

Indexes

idx_gl_date (gl_date)
idx_store_id_gl_date (store_id,gl_date)
idx_store_id_item_id_invoice_id (store_id,item_id,invoice_id)
idx_store_id_location (store_id,location)
index_item_ledger_entries_on_cycle_count_item_id (cycle_count_item_id)
index_item_ledger_entries_on_delivery_id (delivery_id)
index_item_ledger_entries_on_invoice_id (invoice_id)
index_item_ledger_entries_on_item_id (item_id)
index_item_ledger_entries_on_landed_cost_id (landed_cost_id)
index_item_ledger_entries_on_ledger_transaction_id (ledger_transaction_id)
index_item_ledger_entries_on_reversed_entry_id (reversed_entry_id)
index_item_ledger_entries_on_rma_item_id (rma_item_id)
index_on_rma_item_id_where_is_null (rma_item_id) WHERE (reversed_entry_id IS NULL)
shipment_receipt_item_id_category (shipment_receipt_item_id,category)

Defined Under Namespace

Classes: ItemLedgerEntryError

Constant Summary collapse

TYPE_ABBREVIATIONS =
{ 'PO_RECEIPT' => 'POR', 'PO_RECEIPT_VOID' => 'PORV', 'PO_LANDED_COST' => 'POLC', 'INVOICE' => 'INV', 'INVOICE_KIT' => 'INV_KIT', 'ISSUE_TO_ACCOUNT' => 'ITA', 'INVENTORY_ADJUSTMENT' => 'IA', 'LOCATION_TRANSFER' => 'LT',
'ITEM_RECLASSIFICATION' => 'IR', 'RMA_RECEIPT' => 'RMA', 'RMA_RECEIPT_KIT' => 'RMA_KIT', 'COGS_ADJUSTMENT' => 'COGS', 'TOTAL_COST_ADJUSTMENT' => 'TCA', 'CYCLE_COUNT' => 'CC', 'REVERSAL' => 'REV', 'STORE_TRANSFER' => 'ST', 'STORE_TRANSFER_KIT' => 'ST_KIT' }.freeze

Instance Attribute Summary collapse

Belongs to collapse

Has one collapse

Has and belongs to many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#allow_kitsObject

Returns the value of attribute allow_kits.



92
93
94
# File 'app/models/item_ledger_entry.rb', line 92

def allow_kits
  @allow_kits
end

#categoryObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#currencyObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#gl_dateObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#item_idObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#locationObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#previous_quantityObject

Returns the value of attribute previous_quantity.



92
93
94
# File 'app/models/item_ledger_entry.rb', line 92

def previous_quantity
  @previous_quantity
end

#quantityObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#quantity_evalObject (readonly)



82
# File 'app/models/item_ledger_entry.rb', line 82

validates :quantity_eval, presence: { if: proc { |ile| %w[PO_LANDED_COST COGS_ADJUSTMENT].include?(ile.category) } }

#reversed_entry_idObject (readonly)



83
# File 'app/models/item_ledger_entry.rb', line 83

validates :reversed_entry_id, presence: { if: proc { |ile| ile.category == 'REVERSAL' } }

#skip_consolidate_kit_parentsObject

Returns the value of attribute skip_consolidate_kit_parents.



92
93
94
# File 'app/models/item_ledger_entry.rb', line 92

def skip_consolidate_kit_parents
  @skip_consolidate_kit_parents
end

#store_idObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#total_costObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

#unit_costObject (readonly)



80
# File 'app/models/item_ledger_entry.rb', line 80

validates :category, :store_id, :item_id, :quantity, :gl_date, :location, :unit_cost, :total_cost, :currency, presence: true

Class Method Details

.category_type_abbreviation_for_selectObject



107
108
109
# File 'app/models/item_ledger_entry.rb', line 107

def self.category_type_abbreviation_for_select
  TYPE_ABBREVIATIONS.map { |doc, abbr| ["#{doc.titleize} [#{abbr}]", doc] }
end

.cogs_adjustment(store, line_items, gl_date, description = nil) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'app/models/item_ledger_entry.rb', line 363

def self.cogs_adjustment(store, line_items, gl_date, description = nil)
  ItemLedgerEntry.transaction do
    line_items.each do |_index, attrs|
      store_item = StoreItem.find(attrs[:store_item_id])
      item = store_item.item
      new_cogs = BigDecimal(attrs[:new_cogs])
      if store_item.qty_on_hand == 0
        quantity = 0
        unit_cost = 0
        total_cost = 0
      else
        quantity = store_item.qty_on_hand

        existing_value = quantity * store_item.unit_cogs
        new_value = quantity * new_cogs

        total_cost = new_value - existing_value
        unit_cost = total_cost / quantity
      end

      ile = ItemLedgerEntry.new(store_id: store.id,
                                item_id: item.id,
                                category: 'COGS_ADJUSTMENT',
                                gl_date:,
                                quantity: 0,
                                quantity_eval: quantity,
                                location: store_item.location,
                                currency: store.company.currency,
                                unit_cost:,
                                total_cost:,
                                description:,
                                new_unit_cogs: new_cogs)
      ile.save!
    end
  end
end

.consolidate_kits(kit_item:, store_id:, async: true, async_delay: 5) ⇒ Object



594
595
596
597
598
599
600
601
602
603
604
605
606
# File 'app/models/item_ledger_entry.rb', line 594

def self.consolidate_kits(kit_item:, store_id:, async: true, async_delay: 5)
  # add a delay here to allow the db commit to finish
  if async
    if async_delay.positive?
      KitConsolidationWorker.perform_in(async_delay.seconds, kit_item.id, store_id)
    else
      KitConsolidationWorker.perform_async(kit_item.id, store_id)
    end
  else
    # populate_components removed, now is not the time in item ledger to do this
    Item::KitConsolidator.new(kit_item, store_id:).consolidate_qty.consolidate_unit_cogs.commit
  end
end

.inventory_adjustment(store, line_items, gl_date, description = nil, allow_kits = false) ⇒ Object



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'app/models/item_ledger_entry.rb', line 271

def self.inventory_adjustment(store, line_items, gl_date, description = nil, allow_kits = false)
  ItemLedgerEntry.transaction do
    adjustments = []
    line_items.each do |_index, attrs|
      master_store_item = StoreItem.find(attrs[:store_item_id])
      item = master_store_item.item
      master_qty_adjustment = attrs[:qty].to_i
      serial_number_ids = [attrs[:serial_number_id].presence].compact.presence

      # User wants to adjust a kit, so we explode the kit
      if item.kit_target_item_relations.present?
        item.kit_target_item_relations.each_with_object({}) do |tir, _hsh|
          target_item = tir.target_item
          store_item = target_item.store_items.where(store_id: master_store_item.store_id, location: master_store_item.location).first
          unless store_item
            raise ItemLedgerEntryError,
                  "Could not find kit component #{target_item.sku} from #{item.sku} in target store id #{master_store_item.store_id} and location #{master_store_item.location}"
          end
          if store_item.requires_serial_number
            raise ItemLedgerEntryError,
                  "At least one kit component #{target_item.sku} requires serial number, adjustments must be made at the individual component level"
          end

          adjustments << {
            store_item:,
            quantity: master_qty_adjustment * tir.quantity,
            allow_kits: false,
            from_kit: true,
            serial_number_ids:
          }
        end
      else
        adjustments << {
          store_item: master_store_item,
          quantity: master_qty_adjustment,
          allow_kits: false,
          from_kit: false,
          serial_number_ids:
        }
      end
    end

    # Prevent potential issues with adjusting kit and components at the same time
    if adjustments.any? { |a| a[:from_kit] } && !adjustments.all? { |a| a[:from_kit] }
      raise ItemLedgerEntryError,
            'Mixed kit and non kit components in inventory adjustment is not allowed in a single adjustment transaction to ensure integrity'
    end

    kit_parents = []
    # Now proceed to record our adjustment
    adjustments.each do |adjustment|
      store_item = adjustment[:store_item]
      quantity = adjustment[:quantity]
      item = store_item.item
      # We will find the topmost kit parents and consolidate those which
      # will trickle down
      item.kit_parents.each do |kpi|
        if kpi.is_kit?
          kit_parents += kpi.kit_parents.to_a
        else
          kit_parents << kpi
        end
      end
      store = store_item.store
      allow_kits = adjustment[:allow_kits]
      serial_number_ids = adjustment[:serial_number_ids]
      ItemLedgerEntry.inventory_adjustment_individual(store_id: store_item.store_id,
                                                      item_id: item.id,
                                                      gl_date:,
                                                      quantity:,
                                                      location: store_item.location,
                                                      currency: store.company.currency,
                                                      unit_cost: store_item.unit_cogs,
                                                      total_cost: store_item.unit_cogs * quantity,
                                                      description:,
                                                      allow_kits:,
                                                      serial_number_ids:,
                                                      skip_consolidate_kit_parents: true)
    end
    # Consolidate our kit parents in one shot
    kit_parents.uniq.each do |kit_item|
      consolidate_kits(kit_item:, store_id: store.id, async: false, async_delay: 0)
    end
  end
end

.inventory_adjustment_individual(attributes = {}) ⇒ Object



357
358
359
360
361
# File 'app/models/item_ledger_entry.rb', line 357

def self.inventory_adjustment_individual(attributes = {})
  attributes[:category] ||= 'INVENTORY_ADJUSTMENT'
  attributes[:total_cost] ||= attributes[:unit_cost] * attributes[:quantity]
  ItemLedgerEntry.create!(attributes)
end

.issue_to_account(store, line_items, company_account, business_unit = nil, gl_date = nil, project = nil, description = nil) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'app/models/item_ledger_entry.rb', line 247

def self.(store, line_items, , business_unit = nil, gl_date = nil, project = nil, description = nil)
  ItemLedgerEntry.transaction do
    line_items.each do |_index, attrs|
      store_item = StoreItem.find(attrs[:store_item_id])
      item = store_item.item
      quantity = attrs[:qty].to_i
      ItemLedgerEntry.create!(store_id: store.id,
                              item_id: item.id,
                              category: 'ISSUE_TO_ACCOUNT',
                              gl_date:,
                              quantity: quantity * -1,
                              location: store_item.location,
                              currency: store.company.currency,
                              unit_cost: store_item.unit_cogs,
                              total_cost: store_item.unit_cogs * (quantity * -1),
                              description:,
                              ledger_company_account: ,
                              ledger_detail_project: project,
                              business_unit:,
                              serial_number_ids: attrs[:serial_number_id].blank? ? nil : [attrs[:serial_number_id]])
    end
  end
end

.item_reclassification(store, from_line_items, to_line_items, gl_date, description = nil, allow_kits = false) ⇒ Object



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'app/models/item_ledger_entry.rb', line 421

def self.item_reclassification(store, from_line_items, to_line_items, gl_date, description = nil, allow_kits = false)
  ItemLedgerEntry.transaction do
     = LedgerCompanyAccount.(store.company_id, store.)
     = LedgerCompanyAccount.(store.company_id, store.)
    raise ItemLedgerEntryError, 'Unable to find Purchased Finished Goods company account' if .nil?
    raise ItemLedgerEntryError, 'Unable to find Services-Pending Fulfillment Debit company account' if .nil?

     = nil

    # record what the total value is of all the items we are reclassifying
    total_value = BigDecimal(0)
    currency = store.company.currency
    entries = []
    from_line_items_qty = 0

    from_line_items.each do |_index, attrs|
      store_item = StoreItem.find(attrs[:store_item_id])
      quantity = attrs[:qty].to_i
      store_item_value = store_item.unit_cogs * quantity
      raise ItemLedgerEntryError, "From store item #{store_item.id} must have a cogs value > 0" if store_item_value == 0

      total_value += store_item_value
      from_line_items_qty += quantity
      entries << ItemLedgerEntry.create!(store_id: store.id,
                                         item_id: store_item.item_id,
                                         category: 'ITEM_RECLASSIFICATION',
                                         gl_date:,
                                         quantity: quantity * -1,
                                         location: store_item.location,
                                         currency:,
                                         unit_cost: store_item.unit_cogs,
                                         total_cost: store_item_value * -1,
                                         description:,
                                         allow_kits:,
                                         serial_number_ids: attrs[:serial_number_id].blank? ? nil : [attrs[:serial_number_id]])
      if .nil? && (store_item.item.tax_class == 'svc')
         = 
      elsif .nil?
         = 
      end
    end

    # record how many items in total we are reclassifying TO
    # we will use this later to work out what the new cogs should be
    to_total_cost = BigDecimal(0)
    to_line_items.each do |_index, attrs|
      store_item = StoreItem.where(store_id: store.id, item_id: attrs[:item_id], location: attrs[:location]).first_or_create
      raise ItemLedgerEntryError, 'Store item must be present in target store and location.' if store_item.nil?
      raise ItemLedgerEntryError, "To store item #{store_item.id} must have a cogs value.positive? if stock is present" if (store_item.unit_cogs == 0) && (store_item.qty_on_hand >= 1)

      attrs[:store_item] = store_item
      cogs_to_use = store_item.unit_cogs.zero? ? (total_value / from_line_items_qty) : store_item.unit_cogs
      to_total_cost += cogs_to_use * attrs[:qty].to_i
    end

    total_assigned_value = BigDecimal(0)

    to_line_items.each do |_index, attrs|
      item = Item.find(attrs[:item_id])
      quantity = attrs[:qty].to_i
      location = attrs[:location]
      store_item = attrs[:store_item]
      cogs_to_use = store_item.unit_cogs.zero? ? (total_value / from_line_items_qty) : store_item.unit_cogs
      cogs_percentage = ((cogs_to_use * attrs[:qty].to_i) / to_total_cost) * 100
      unit_cogs = ((total_value / 100) * cogs_percentage) / quantity
      entries << ItemLedgerEntry.create!(store_id: store.id,
                                         item_id: item.id,
                                         category: 'ITEM_RECLASSIFICATION',
                                         gl_date:,
                                         quantity:,
                                         location:,
                                         currency:,
                                         unit_cost: unit_cogs,
                                         total_cost: unit_cogs * quantity,
                                         description:,
                                         allow_kits:)
      total_assigned_value += (unit_cogs * quantity)
    end

    transaction = LedgerTransaction.new(transaction_type: 'ITEM_RECLASSIFICATION',
                                        transaction_date: gl_date,
                                        description:,
                                        currency:,
                                        company: store.company)

    # withdraw funds from the purchased finished goods account
    transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: .id, currency:,
                                                  amount: total_assigned_value * -1, description:)

    # and add them back
    transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: .id, currency:, amount: total_assigned_value,
                                                  description:)

    transaction.save!

    entries.each { |e| e.update!(ledger_transaction: transaction) }
  end
end

.location_transfer(store, line_items, gl_date, description = nil) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'app/models/item_ledger_entry.rb', line 111

def self.location_transfer(store, line_items, gl_date, description = nil)
  ItemLedgerEntry.transaction do
    company = store.company
    currency = company.currency
     = LedgerCompanyAccount.(company.id, store.)
     = LedgerCompanyAccount.(company.id, store.)
    raise ItemLedgerEntryError, 'Unable to find Purchased Finished Goods company account' if .nil?
    raise ItemLedgerEntryError, 'Unable to find Services-Pending Fulfillment Debit company account' if .nil?

    line_items.each do |_index, attrs|
      store_item = StoreItem.find(attrs[:store_item_id])
      item = store_item.item
      quantity = attrs[:qty].to_i
      entry1 = ItemLedgerEntry.create!(store_id: store.id,
                                       item_id: item.id,
                                       category: 'LOCATION_TRANSFER',
                                       gl_date:,
                                       quantity: quantity * -1,
                                       location: store_item.location,
                                       currency:,
                                       unit_cost: store_item.unit_cogs,
                                       total_cost: store_item.unit_cogs * (quantity * -1),
                                       description:,
                                       serial_number_ids: attrs[:serial_number_id].blank? ? nil : [attrs[:serial_number_id]])
      entry2 = ItemLedgerEntry.create!(store_id: store.id,
                                       item_id: item.id,
                                       category: 'LOCATION_TRANSFER',
                                       gl_date:,
                                       quantity: quantity.to_i,
                                       location: attrs[:new_location],
                                       currency:,
                                       unit_cost: store_item.unit_cogs,
                                       total_cost: store_item.unit_cogs * quantity.to_i,
                                       description:,
                                       serial_number_ids: attrs[:serial_number_id].blank? ? nil : [attrs[:serial_number_id]])

      # now post to the account ledger to remove/add funds from purchased finished goods
      transaction = LedgerTransaction.new(transaction_type: 'LOCATION_TRANSFER',
                                          transaction_date: gl_date,
                                          description:,
                                          currency:,
                                          company:)

       = if item.tax_class == 'svc'
                         
                       else
                         
                       end

      # withdraw funds from the purchased finished goods account
      transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: .id, currency:,
                                                    amount: store_item.unit_cogs * (quantity.to_i * -1), description:)

      # and add them back
      transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: .id, currency:,
                                                    amount: store_item.unit_cogs * quantity.to_i, description:)

      transaction.save!

      entry1.update!(ledger_transaction: transaction)
      entry2.update!(ledger_transaction: transaction)
    end
  end
end

.not_reversed_or_reversalActiveRecord::Relation<ItemLedgerEntry>

A relation of ItemLedgerEntries that are not reversed or reversal. Active Record Scope

Returns:

See Also:



88
89
90
# File 'app/models/item_ledger_entry.rb', line 88

scope :not_reversed_or_reversal, -> {
  joins('LEFT OUTER JOIN item_ledger_entries reversals ON reversals.reversed_entry_id = item_ledger_entries.id').where('item_ledger_entries.reversed_entry_id is null and reversals.id is null')
}

.process_intracompany_st_delivery(delivery) ⇒ Object



545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'app/models/item_ledger_entry.rb', line 545

def self.process_intracompany_st_delivery(delivery)
  ItemLedgerEntry.transaction do
    # release/uncommit catalog items, but skip kit consolidation as this will be done in the consolidate_kits after_create callback anyway
    delivery.uncommit_catalog_items
    delivery.line_items.non_shipping.eager_load(catalog_item: :store_item).where(store_items: { permanent_qty_available: nil }).each do |li|
      if li.item.is_kit?
        # li.item.kit_target_item_relations.each do |ki|
        #   store_item = ki.target_item.store_item_for(delivery.order.from_store_id, 'AVAILABLE')
        #   ItemLedgerEntry.create!(store: delivery.order.from_store, item: ki.target_item, item_kit: li.item, category: 'STORE_TRANSFER', gl_date: delivery.shipped_date, quantity: -(ki.quantity * li.quantity), currency: delivery.order.currency, unit_cost: store_item.unit_cogs, total_cost: -(store_item.unit_cogs * ki.quantity * li.quantity), location: 'AVAILABLE', delivery: delivery, ledger_transaction: delivery.ledger_transactions.first, serial_number_ids: (ki.target_item.require_reservation? ? li.serial_number_ids : nil))
        # end
        ItemLedgerEntry.create!(store: delivery.order.from_store, item: li.item, category: 'STORE_TRANSFER_KIT', gl_date: delivery.shipped_date,
                                quantity: 0, quantity_eval: -li.quantity, currency: delivery.order.currency, unit_cost: 0, total_cost: 0, location: 'AVAILABLE', delivery:, ledger_transaction: delivery.ledger_transactions.first)
      else
        unit_cost = li.parent.present? ? StoreItem.where(store_id: delivery.order.from_store_id, item_id: li.item_id).first.unit_cogs : li.unit_cogs
        total_cost = unit_cost * li.quantity
        ItemLedgerEntry.create!(store: delivery.order.from_store, item: li.item, item_kit: li.parent&.item, category: 'STORE_TRANSFER',
                                gl_date: delivery.shipped_date, quantity: li.quantity * -1, currency: delivery.order.currency, unit_cost:, total_cost: total_cost * -1, location: 'AVAILABLE', delivery:, ledger_transaction: delivery.ledger_transactions.first, serial_number_ids: li.serial_number_ids)
      end
    end
  end
end

.process_invoice(invoice) ⇒ Object



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'app/models/item_ledger_entry.rb', line 520

def self.process_invoice(invoice)
  ItemLedgerEntry.transaction do
    store = if invoice.invoice_type == Invoice::MI
              invoice.store
            else
              invoice.order_id.present? ? invoice.order.store : invoice.line_items.parents_only.eager_load(catalog_item: :store_item).where(cm_category: 'Item').first.store_item.store
            end
    invoice.line_items.parents_only.eager_load(catalog_item: :store_item).where(cm_category: 'Item').each do |li|
      if li.has_children?
        li.children.eager_load(catalog_item: :store_item).where(store_items: { permanent_qty_available: nil }).each do |child|
          store_id = invoice.order_id.present? ? invoice.order.store_id : li.store_item.store_id
          store_item = child.store_item_id.nil? ? child.item.store_items.active.available.where(store_id:).first : child.store_item
          ItemLedgerEntry.create!(store:, item: child.item, item_kit: li.item, category: 'INVOICE', gl_date: invoice.gl_date,
                                  quantity: -child.quantity, currency: invoice.currency, unit_cost: store_item.unit_cogs, total_cost: -(store_item.unit_cogs * child.quantity), location: store_item.location, invoice:, ledger_transaction: invoice.ledger_transactions.first, serial_number_ids: child.serial_number_ids)
        end
        ItemLedgerEntry.create!(store:, item: li.item, category: 'INVOICE_KIT', gl_date: invoice.gl_date, quantity: 0,
                                quantity_eval: -li.quantity, currency: invoice.currency, unit_cost: 0, total_cost: 0, location: li.store_item.location, invoice:, ledger_transaction: invoice.ledger_transactions.first, serial_number_ids: li.serial_number_ids)
      else
        ItemLedgerEntry.create!(store:, item: li.item, category: 'INVOICE', gl_date: invoice.gl_date, quantity: li.quantity * -1,
                                currency: invoice.currency, unit_cost: li.unit_cogs, total_cost: -(li.unit_cogs * li.quantity), location: li.store_item.location, invoice:, ledger_transaction: invoice.ledger_transactions.first, serial_number_ids: li.serial_number_ids)
      end
    end
  end
end

.store_transfer(store:, line_items:, new_store:, shipping_option_id:, gl_date:, address_id:, description: nil, fulfillment_order_reference: nil) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'app/models/item_ledger_entry.rb', line 176

def self.store_transfer(store:, line_items:, new_store:, shipping_option_id:, gl_date:, address_id:, description: nil, fulfillment_order_reference: nil)
  ItemLedgerEntry.transaction do
    from_company = store.company
    new_store.company
    preferred_shipping_option = shipping_option_id.blank? ? nil : ShippingOption.find(shipping_option_id)

    fulfillment_order_id = nil
    if fulfillment_order_reference.present?
      fulfillment_order = Order.where(reference_number: fulfillment_order_reference).first
      fulfillment_order_id = fulfillment_order.id if fulfillment_order.present?
    end
    new_store_sold_to_customer = StoreTransfer.where(from_store_id: store.id).where(to_store_id: new_store.id).first.sold_to_customer
    # create an ST for the current store, selling to the new store and move it to the at_warehouse state
    so = Order.new(customer: new_store_sold_to_customer, # Store's consignee party is the customer for this store transfer
                   shipping_address_id: address_id,
                   currency: from_company.currency,
                   disable_auto_coupon: true,
                   shipping_method: preferred_shipping_option.try(:name) || new_store.preferred_inbound_shipping_option.try(:name) || store.preferred_outbound_shipping_option.try(:name) || new_store.preferred_outbound_shipping_option.try(:name),
                   order_type: Invoice::ST,
                   from_store: store,
                   to_store: new_store,
                   parent_id: fulfillment_order_id,
                   shipment_reference_number: fulfillment_order_reference)
    so.save!
    fulfillment_order.presence&.quick_note(so.reference_number)
    line_items.each do |_index, attrs|
      store_item = StoreItem.find(attrs[:store_item_id])
      item = store_item.item

      quantity = attrs[:qty].to_i
      cat_item = store.primary_catalog.catalog_items.joins(:store_item).find_by(store_items: { item_id: item.id })
      if cat_item.nil?
        raise ItemLedgerEntryError,
              "Unable to find given item SKU: #{item.sku} / ID: #{item.id} in origin store's primary catalog NAME: #{store.primary_catalog.name} / ID: #{store.primary_catalog_id}."
      end

      dest_cat_item = new_store.primary_catalog.catalog_items.joins(:store_item).find_by(store_items: { item_id: item.id })
      if dest_cat_item.nil?
        raise ItemLedgerEntryError,
              "Unable to find given item SKU: #{item.sku} / ID: #{item.id} in destination store's primary catalog NAME: #{new_store.primary_catalog.name} / ID: #{new_store.primary_catalog_id}."
      end

      so.line_items.build(catalog_item: cat_item,
                          quantity:,
                          price: store_item.unit_cogs.round(2),
                          discounted_price: store_item.unit_cogs.round(2))
    end
    so.save!
    # Retrieve shipping costs to create deliveries for the store transfer
    so.retrieve_shipping_costs
    d = so.reload.deliveries.first

    # Safety check: ensure delivery was created
    if d.nil?
      raise ItemLedgerEntryError,
            "Failed to create delivery for store transfer #{so.reference_number}. Please check shipping address and line items."
    end

    PurchaseOrder.new_or_update_st_po_from_delivery(d)
    so.payment_complete!
    # Ramie Blatt 5/19/22, I do not agree with the below, we need to enter the FBA ID and PRO numbers for the record and that must be done manually
    # if store.owner == 'amazon'
    #   # push the order through as no shipping info needs to be put in
    #   d.update(state: 'pending_ship_confirm')
    #   d.shipped
    #   d.trigger_invoiced
    # end
    so
  end
end

.total_cost_adjustment(store, line_items, gl_date, description = nil, allow_kits = false) ⇒ Object



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'app/models/item_ledger_entry.rb', line 400

def self.total_cost_adjustment(store, line_items, gl_date, description = nil, allow_kits = false)
  ItemLedgerEntry.transaction do
    line_items.each do |_index, attrs|
      store_item = StoreItem.find(attrs[:store_item_id])
      item = store_item.item
      total_cost = BigDecimal(attrs[:total_cost])
      ItemLedgerEntry.create!(store_id: store.id,
                              item_id: item.id,
                              category: 'TOTAL_COST_ADJUSTMENT',
                              gl_date:,
                              quantity: 0,
                              location: store_item.location,
                              currency: store.company.currency,
                              unit_cost: 0,
                              total_cost:,
                              description:,
                              allow_kits:)
    end
  end
end

Instance Method Details

#business_unitBusinessUnit



71
# File 'app/models/item_ledger_entry.rb', line 71

belongs_to :business_unit, optional: true

#consolidate_kits(async: !Rails.env.development?)) ⇒ Object



586
587
588
589
590
591
592
# File 'app/models/item_ledger_entry.rb', line 586

def consolidate_kits(async: !Rails.env.development?)
  return if skip_consolidate_kit_parents.to_b

  item.kit_parents.each do |kp|
    self.class.consolidate_kits(kit_item: kp, store_id:, async:)
  end
end

#creatorParty

Returns:

See Also:



73
# File 'app/models/item_ledger_entry.rb', line 73

belongs_to :creator, class_name: 'Party', optional: true

#currency_symbolObject



103
104
105
# File 'app/models/item_ledger_entry.rb', line 103

def currency_symbol
  Money::Currency.new(currency).symbol
end

#cycle_count_itemCycleCountItem



72
# File 'app/models/item_ledger_entry.rb', line 72

belongs_to :cycle_count_item, optional: true

#deliveryDelivery

Returns:

See Also:



69
# File 'app/models/item_ledger_entry.rb', line 69

belongs_to :delivery, optional: true

#entry_reversalItemLedgerEntry



76
# File 'app/models/item_ledger_entry.rb', line 76

has_one :entry_reversal, class_name: 'ItemLedgerEntry', foreign_key: 'reversed_entry_id'

#invoiceInvoice

Returns:

See Also:



68
# File 'app/models/item_ledger_entry.rb', line 68

belongs_to :invoice, optional: true

#itemItem

Returns:

See Also:



60
# File 'app/models/item_ledger_entry.rb', line 60

belongs_to :item

#item_kitItem

Returns:

See Also:



61
# File 'app/models/item_ledger_entry.rb', line 61

belongs_to :item_kit, class_name: 'Item', optional: true

#landed_costLandedCost

Returns:

See Also:



64
# File 'app/models/item_ledger_entry.rb', line 64

belongs_to :landed_cost, optional: true

#ledger_company_accountLedgerCompanyAccount



65
# File 'app/models/item_ledger_entry.rb', line 65

belongs_to :ledger_company_account, optional: true

#ledger_detail_projectLedgerDetailProject



67
# File 'app/models/item_ledger_entry.rb', line 67

belongs_to :ledger_detail_project, optional: true

#ledger_transactionLedgerTransaction



66
# File 'app/models/item_ledger_entry.rb', line 66

belongs_to :ledger_transaction, optional: true

#reverse(reason = nil) ⇒ Object



567
568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'app/models/item_ledger_entry.rb', line 567

def reverse(reason = nil)
  rev = dup
  rev.category = item.is_kit? ? 'REVERSAL_KIT' : 'REVERSAL'
  rev.quantity = -quantity
  rev.quantity_eval = -quantity_eval if quantity_eval
  rev.total_cost = -total_cost
  rev.description = reason
  rev.creator_id = rev.updater_id = rev.created_at = rev.updated_at = nil
  rev.old_qty_on_hand = rev.new_qty_on_hand = nil
  rev.old_unit_cogs = rev.new_unit_cogs = nil
  rev.ledger_transaction_id = nil
  rev.reversed_entry_id = id
  rev.save!
end

#reversed_entryItemLedgerEntry



75
# File 'app/models/item_ledger_entry.rb', line 75

belongs_to :reversed_entry, class_name: 'ItemLedgerEntry', optional: true

#rma_itemRmaItem

Returns:

See Also:



70
# File 'app/models/item_ledger_entry.rb', line 70

belongs_to :rma_item, optional: true

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns:

See Also:



78
# File 'app/models/item_ledger_entry.rb', line 78

has_and_belongs_to_many :serial_numbers

#shipment_receipt_itemShipmentReceiptItem



63
# File 'app/models/item_ledger_entry.rb', line 63

belongs_to :shipment_receipt_item, optional: true

#storeStore

Returns:

See Also:



62
# File 'app/models/item_ledger_entry.rb', line 62

belongs_to :store

#store_itemObject



582
583
584
# File 'app/models/item_ledger_entry.rb', line 582

def store_item
  @store_item ||= StoreItem.where(store_id:, item_id:, location:).first
end

#store_item_auditStoreItemAudit



77
# File 'app/models/item_ledger_entry.rb', line 77

has_one :store_item_audit

#updaterParty

Returns:

See Also:



74
# File 'app/models/item_ledger_entry.rb', line 74

belongs_to :updater, class_name: 'Party', optional: true