Class: ShipmentReceipt

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/shipment_receipt.rb

Overview

== Schema Information

Table name: shipment_receipts
Database name: primary

id :integer not null, primary key
currency :string(255)
effective_date :date
estimated_landed_cost :decimal(8, 2)
landed_cost :decimal(8, 2)
location :string(255)
serial_number_state :string
state :string(255)
created_at :datetime
updated_at :datetime
creator_id :integer
purchase_order_shipment_id :integer
updater_id :integer

Indexes

index_on_pos_id (purchase_order_shipment_id)

Constant Summary

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#effective_dateObject (readonly)



37
# File 'app/models/shipment_receipt.rb', line 37

validates :purchase_order_shipment_id, :effective_date, :location, presence: true

#locationObject (readonly)



37
# File 'app/models/shipment_receipt.rb', line 37

validates :purchase_order_shipment_id, :effective_date, :location, presence: true

#purchase_order_shipment_idObject (readonly)



37
# File 'app/models/shipment_receipt.rb', line 37

validates :purchase_order_shipment_id, :effective_date, :location, presence: true

Instance Method Details

#all_items_in_stock?Boolean

Returns:

  • (Boolean)


212
213
214
# File 'app/models/shipment_receipt.rb', line 212

def all_items_in_stock?
  shipment_receipt_items.all?(&:has_stock?)
end

#all_serial_numbers_provided?Boolean

Returns:

  • (Boolean)


84
85
86
87
88
89
90
91
92
# File 'app/models/shipment_receipt.rb', line 84

def all_serial_numbers_provided?
  # using .to_a so it inspects the unsaved serial numbers
  sri_with_reservations = shipment_receipt_items.select do |sri|
    sri.purchase_order_item.item.require_reservation?
  end
  sri_with_reservations.all? do |sri|
    sri.serial_numbers.to_a.map(&:presence).compact.sum(&:qty) == sri.quantity
  end
end

#apply_estimated_landed_costsObject



266
267
268
269
270
271
272
# File 'app/models/shipment_receipt.rb', line 266

def apply_estimated_landed_costs
  purchase_order_shipment.update(carrier_id: purchase_order_shipment.purchase_orders.first.carrier_id) if purchase_order_shipment.carrier_id.nil?
  ShipmentReceipt.transaction do
    # now apply the estimated landed costs if we have one
    enter_landed_costs(estimated_landed_cost.to_s, purchase_order_shipment.carrier_id, true) unless estimated_landed_cost.nil? || purchase_order_shipment.carrier_id.nil?
  end
end

#currency_symbolObject



207
208
209
210
# File 'app/models/shipment_receipt.rb', line 207

def currency_symbol
  cur_index = currency || shipment_receipt_items.pick(:currency)
  Money::Currency.new(cur_index).symbol
end

#enter_landed_costs(landed_cost = '', carrier_id = nil, estimated = false) ⇒ Object



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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/shipment_receipt.rb', line 123

def enter_landed_costs(landed_cost = '', carrier_id = nil, estimated = false)
  landed_cost = BigDecimal(landed_cost)
  unless carrier_id.nil?
    # do this as a transaction so if any part fails it will roll back
    ShipmentReceipt.transaction do
      shipment_total_cost = shipment_receipt_items.sum { |si| si.shipment_item.total_cost } || BigDecimal('0')
      item_details = []
      shipment_receipt_items.each do |si|
        # find the matching purchase order item

        poi = si.purchase_order_item
        uom_to_ea_weight = poi.total_weight / poi.unit_quantity
        item_detail = {}
        # need to store the weight per item so that we can work out landed cost per pound
        item_detail['sr_item'] = si
        item_detail['po_item'] = poi
        item_detail['total_weight'] = si.quantity * uom_to_ea_weight
        item_details << item_detail
      end

      # sum up all item weights to find total weight
      total_weight = BigDecimal('0')

      item_details.each do |item|
        total_weight += item['total_weight']
      end

      # now we can calculate landed cost per pound
      # landed_cost_per_pound = landed_cost / total_weight

      # now we can create the landed costs
      item_details.each do |item|
        # find the matching purchase order item
        poi = item['po_item']
        si = item['sr_item']

        uom_to_ea_weight = (poi.total_weight / poi.unit_quantity)

        # unit_landed_cost = (landed_cost_per_pound * uom_to_ea_weight)
        # total_landed_cost = (unit_landed_cost * si.quantity)

        total_landed_cost = shipment_total_cost.zero? ? BigDecimal('0') : ((si.shipment_item.total_cost / shipment_total_cost) * landed_cost)
        unit_landed_cost = total_landed_cost / si.quantity

        LandedCost.create!(purchase_order: poi.purchase_order,
                           purchase_order_item: poi,
                           shipment_receipt_item: si,
                           shipment_receipt: si.shipment_receipt,
                           quantity: si.quantity,
                           currency: poi.currency,
                           unit_weight: uom_to_ea_weight,
                           total_weight: item['total_weight'],
                           unit_landed_cost: unit_landed_cost,
                           total_landed_cost: total_landed_cost,
                           carrier_id: carrier_id,
                           estimated: estimated)
      end

      if estimated.to_bool == true
        update(estimated_landed_cost: landed_cost,
               currency: purchase_order_shipment.currency)
      else
        update(landed_cost: landed_cost,
               currency: purchase_order_shipment.currency)
        purchase_orders.each(&:landed_costs_applied)
      end
    end
  end
end

#enter_receipts(params) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'app/models/shipment_receipt.rb', line 94

def enter_receipts(params)
  # do this as a transaction so if any part fails it will roll back
  return if params.blank?

  # Load up all shipment items
  shipment_items_attributes = (params.to_h || {}).select do |_k, attrs|
    attrs[:qty_received].present?
  end.transform_keys(&:to_i)
  shipment_item_ids = shipment_items_attributes.keys
  shipment_items = ShipmentItem.joins(:purchase_order_item).includes(purchase_order_item: :item).where(id: shipment_item_ids)
  # Loop through
  shipment_items.each do |si|
    qty_received = shipment_items_attributes[si.id][:qty_received].to_i
    next unless qty_received.positive?

    poi = si.purchase_order_item
    uom_to_ea_qty = poi.unit_quantity / poi.quantity
    shipment_receipt_items.build(purchase_order: poi.purchase_order,
                                 purchase_order_item: poi,
                                 quantity: qty_received * uom_to_ea_qty,
                                 currency: poi.currency,
                                 unit_cost: poi.unit_cost / uom_to_ea_qty,
                                 total_cost: qty_received * poi.unit_cost,
                                 effective_date: effective_date,
                                 shipment_item: si)
  end
  shipment_receipt_items
end

#has_items_requiring_serial_number?Boolean

Returns:

  • (Boolean)


80
81
82
# File 'app/models/shipment_receipt.rb', line 80

def has_items_requiring_serial_number?
  shipment_receipt_items.any? { |sri| sri.purchase_order_item.item.require_reservation? }
end

#landed_costsActiveRecord::Relation<LandedCost>

Returns:

See Also:



32
# File 'app/models/shipment_receipt.rb', line 32

has_many :landed_costs

#ledger_transactionsActiveRecord::Relation<LedgerTransaction>

Returns:

See Also:



33
# File 'app/models/shipment_receipt.rb', line 33

has_many :ledger_transactions

#prorate_estimated_landed_costObject



260
261
262
263
264
# File 'app/models/shipment_receipt.rb', line 260

def prorate_estimated_landed_cost
  poi_total_cost = purchase_order_shipment.purchase_order_items.map(&:total_cost).sum
  sri_total_cost = shipment_receipt_items.map(&:total_cost).sum
  estimated_landed_cost_calc = poi_total_cost.zero? ? 0 : (purchase_order_shipment.estimated_landed_cost * (sri_total_cost / poi_total_cost))
end

#prorate_landed_costObject



254
255
256
257
258
# File 'app/models/shipment_receipt.rb', line 254

def prorate_landed_cost
  poi_total_cost = purchase_order_shipment.purchase_order_items.map(&:total_cost).sum
  sri_total_cost = shipment_receipt_items.map(&:total_cost).sum
  landed_cost_calc = poi_total_cost.zero? ? 0 : (purchase_order_shipment.landed_cost * (sri_total_cost / poi_total_cost))
end

#purchase_order_shipmentPurchaseOrderShipment



28
# File 'app/models/shipment_receipt.rb', line 28

belongs_to :purchase_order_shipment, optional: true

#purchase_ordersActiveRecord::Relation<PurchaseOrder>

Returns:

See Also:



31
# File 'app/models/shipment_receipt.rb', line 31

has_many :purchase_orders, -> { distinct }, through: :shipment_receipt_items

#serial_number_numbersObject



76
77
78
# File 'app/models/shipment_receipt.rb', line 76

def serial_number_numbers
  serial_numbers.order(:number).collect(&:number).join(', ')
end

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns:

See Also:



34
# File 'app/models/shipment_receipt.rb', line 34

has_many :serial_numbers, through: :shipment_receipt_items

#set_supplier_lead_timeObject



72
73
74
# File 'app/models/shipment_receipt.rb', line 72

def set_supplier_lead_time
  purchase_orders.collect(&:supplier).uniq.each(&:calculate_lead_time)
end

#shipment_receipt_itemsActiveRecord::Relation<ShipmentReceiptItem>

Returns:

See Also:



30
# File 'app/models/shipment_receipt.rb', line 30

has_many :shipment_receipt_items, dependent: :destroy

#void_estimated_landed_costsObject



200
201
202
203
204
205
# File 'app/models/shipment_receipt.rb', line 200

def void_estimated_landed_costs
  ShipmentReceipt.transaction do
    landed_costs.estimated.each(&:void!)
    update(estimated_landed_cost: nil)
  end
end

#void_landed_costsObject



193
194
195
196
197
198
# File 'app/models/shipment_receipt.rb', line 193

def void_landed_costs
  ShipmentReceipt.transaction do
    landed_costs.actual.each(&:void!)
    update(landed_cost: nil)
  end
end

#void_shipment_receiptObject



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
246
247
248
249
250
251
252
# File 'app/models/shipment_receipt.rb', line 216

def void_shipment_receipt
  res = { 'success' => nil, 'message' => nil }
  if voided?
    # check if it's already been voided
    res['success'] = false
    res['message'] = 'Shipment receipt has already been voided'
    res
  elsif !all_items_in_stock?
    # check all the items are in stock
    res['success'] = false
    res['message'] = 'All items must be in stock before you can void the shipment receipt'
    res
  else
    lt = ledger_transactions.first
    raise "Unable to find ledger transaction for shipment receipt #{id}" if lt.nil?

    # do this as a transaction so if any part fails it will roll back
    ShipmentReceipt.transaction do
      # reverse the ledger transaction
      rev = lt.reverse(Date.current)
      # loop through each shipment receipt item and reverse the item ledger entry
      shipment_receipt_items.each do |sri|
        sri.reverse_item_ledger(rev)
      end
      # Discontinue any serial numbers created
      serial_numbers.each(&:discontinue)
      # now void the estimated landed cost
      void_estimated_landed_costs
      # now void the shipment receipt
      void!
    end

    res['success'] = true
    res['message'] = 'Shipment receipt has been voided'
    res
  end
end