Class: ShipmentReceipt
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ShipmentReceipt
- 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
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #effective_date ⇒ Object readonly
- #location ⇒ Object readonly
- #purchase_order_shipment_id ⇒ Object readonly
Belongs to collapse
Methods included from Models::Auditable
Has many collapse
- #landed_costs ⇒ ActiveRecord::Relation<LandedCost>
- #ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
- #purchase_orders ⇒ ActiveRecord::Relation<PurchaseOrder>
- #serial_numbers ⇒ ActiveRecord::Relation<SerialNumber>
- #shipment_receipt_items ⇒ ActiveRecord::Relation<ShipmentReceiptItem>
Instance Method Summary collapse
- #all_items_in_stock? ⇒ Boolean
- #all_serial_numbers_provided? ⇒ Boolean
-
#apply_estimated_landed_costs ⇒ Object
Backfill a carrier on the parent shipment if missing, then push the estimated landed cost onto each LandedCost row.
-
#currency_symbol ⇒ String
Currency symbol for display, falling back to the first item's currency when the receipt itself has none yet.
-
#enter_landed_costs(landed_cost = '', carrier_id = nil, estimated = false) ⇒ void
Allocate a freight/duty
landed_costacross each ShipmentReceiptItem pro-rata by line value (not weight, despite the in-line comments) and create the matching LandedCost rows. -
#enter_receipts(params) ⇒ Array<ShipmentReceiptItem>?
Build ShipmentReceiptItems from a
qty_received-keyed params hash (typically the receiving form). - #has_items_requiring_serial_number? ⇒ Boolean
-
#prorate_estimated_landed_cost ⇒ BigDecimal, Integer
Share of the parent shipment's estimated landed cost that belongs to this receipt, weighted by line value.
-
#prorate_landed_cost ⇒ BigDecimal, Integer
Share of the parent shipment's actual landed cost that belongs to this receipt, weighted by line value.
-
#serial_number_numbers ⇒ String
Comma-separated list of serial numbers received on this shipment, in number order.
-
#set_supplier_lead_time ⇒ Object
Recalculate every receipted PO's supplier lead time using this arrival as a fresh data point.
-
#void_estimated_landed_costs ⇒ Object
Reverse estimated LandedCost rows and clear
estimated_landed_costbefore applying actuals. -
#void_landed_costs ⇒ Object
Reverse actual (non-estimated) LandedCost rows and clear the
landed_costattribute, e.g. -
#void_shipment_receipt ⇒ Hash{String=>Boolean,String}
Void this receipt: reverse the GL transaction, reverse the per-item ledger entries, discontinue any auto-created serial numbers, void estimated landed costs, then run the
:voidstate event.
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 Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#effective_date ⇒ Object (readonly)
38 |
# File 'app/models/shipment_receipt.rb', line 38 validates :purchase_order_shipment_id, :effective_date, :location, presence: true |
#location ⇒ Object (readonly)
38 |
# File 'app/models/shipment_receipt.rb', line 38 validates :purchase_order_shipment_id, :effective_date, :location, presence: true |
#purchase_order_shipment_id ⇒ Object (readonly)
38 |
# File 'app/models/shipment_receipt.rb', line 38 validates :purchase_order_shipment_id, :effective_date, :location, presence: true |
Instance Method Details
#all_items_in_stock? ⇒ Boolean
242 243 244 |
# File 'app/models/shipment_receipt.rb', line 242 def all_items_in_stock? shipment_receipt_items.all?(&:has_stock?) end |
#all_serial_numbers_provided? ⇒ Boolean
91 92 93 94 95 96 97 98 99 |
# File 'app/models/shipment_receipt.rb', line 91 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.filter_map(&:presence).sum(&:qty) == sri.quantity end end |
#apply_estimated_landed_costs ⇒ Object
Backfill a carrier on the parent shipment if missing, then push the
estimated landed cost onto each LandedCost row. No-op when there
is no estimate or no carrier could be inferred.
314 315 316 317 318 319 320 |
# File 'app/models/shipment_receipt.rb', line 314 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_symbol ⇒ String
Currency symbol for display, falling back to the first item's
currency when the receipt itself has none yet.
237 238 239 240 |
# File 'app/models/shipment_receipt.rb', line 237 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) ⇒ void
This method returns an undefined value.
Allocate a freight/duty landed_cost across each
ShipmentReceiptItem pro-rata by line value (not weight, despite the
in-line comments) and create the matching LandedCost rows. When
estimated is false, also flips the receipted POs through
landed_costs_applied so they advance to the AP-ready state.
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 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'app/models/shipment_receipt.rb', line 148 def enter_landed_costs(landed_cost = '', carrier_id = nil, estimated = false) landed_cost = BigDecimal(landed_cost) return if 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'] # now we can calculate landed cost per pound # landed_cost_per_pound = landed_cost / total_weight # now we can create the landed costs 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 |
#enter_receipts(params) ⇒ Array<ShipmentReceiptItem>?
Build ShipmentReceiptItems from a qty_received-keyed params hash
(typically the receiving form). Iterates the corresponding
ShipmentItems, converts UoM-priced lines into per-each costs, and
appends unsaved children for the caller to persist.
109 110 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 |
# File 'app/models/shipment_receipt.rb', line 109 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
87 88 89 |
# File 'app/models/shipment_receipt.rb', line 87 def has_items_requiring_serial_number? shipment_receipt_items.any? { |sri| sri.purchase_order_item.item.require_reservation? } end |
#landed_costs ⇒ ActiveRecord::Relation<LandedCost>
33 |
# File 'app/models/shipment_receipt.rb', line 33 has_many :landed_costs |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
34 |
# File 'app/models/shipment_receipt.rb', line 34 has_many :ledger_transactions |
#prorate_estimated_landed_cost ⇒ BigDecimal, Integer
Share of the parent shipment's estimated landed cost that belongs to
this receipt, weighted by line value.
305 306 307 308 309 |
# File 'app/models/shipment_receipt.rb', line 305 def prorate_estimated_landed_cost poi_total_cost = purchase_order_shipment.purchase_order_items.sum(&:total_cost) sri_total_cost = shipment_receipt_items.sum(&:total_cost) poi_total_cost.zero? ? 0 : (purchase_order_shipment.estimated_landed_cost * (sri_total_cost / poi_total_cost)) end |
#prorate_landed_cost ⇒ BigDecimal, Integer
Share of the parent shipment's actual landed cost that belongs to
this receipt, weighted by line value.
295 296 297 298 299 |
# File 'app/models/shipment_receipt.rb', line 295 def prorate_landed_cost poi_total_cost = purchase_order_shipment.purchase_order_items.sum(&:total_cost) sri_total_cost = shipment_receipt_items.sum(&:total_cost) poi_total_cost.zero? ? 0 : (purchase_order_shipment.landed_cost * (sri_total_cost / poi_total_cost)) end |
#purchase_order_shipment ⇒ PurchaseOrderShipment
29 |
# File 'app/models/shipment_receipt.rb', line 29 belongs_to :purchase_order_shipment, optional: true |
#purchase_orders ⇒ ActiveRecord::Relation<PurchaseOrder>
32 |
# File 'app/models/shipment_receipt.rb', line 32 has_many :purchase_orders, -> { distinct }, through: :shipment_receipt_items |
#serial_number_numbers ⇒ String
Comma-separated list of serial numbers received on this shipment, in
number order. Used in PO receipt confirmations and audit views.
83 84 85 |
# File 'app/models/shipment_receipt.rb', line 83 def serial_number_numbers serial_numbers.order(:number).map(&:number).join(', ') end |
#serial_numbers ⇒ ActiveRecord::Relation<SerialNumber>
35 |
# File 'app/models/shipment_receipt.rb', line 35 has_many :serial_numbers, through: :shipment_receipt_items |
#set_supplier_lead_time ⇒ Object
Recalculate every receipted PO's supplier lead time using this
arrival as a fresh data point.
75 76 77 |
# File 'app/models/shipment_receipt.rb', line 75 def set_supplier_lead_time purchase_orders.map(&:supplier).uniq.each(&:calculate_lead_time) end |
#shipment_receipt_items ⇒ ActiveRecord::Relation<ShipmentReceiptItem>
31 |
# File 'app/models/shipment_receipt.rb', line 31 has_many :shipment_receipt_items, dependent: :destroy |
#void_estimated_landed_costs ⇒ Object
Reverse estimated LandedCost rows and clear estimated_landed_cost
before applying actuals.
226 227 228 229 230 231 |
# File 'app/models/shipment_receipt.rb', line 226 def void_estimated_landed_costs ShipmentReceipt.transaction do landed_costs.estimated.each(&:void!) update(estimated_landed_cost: nil) end end |
#void_landed_costs ⇒ Object
Reverse actual (non-estimated) LandedCost rows and clear the
landed_cost attribute, e.g. when the carrier voucher is corrected.
217 218 219 220 221 222 |
# File 'app/models/shipment_receipt.rb', line 217 def void_landed_costs ShipmentReceipt.transaction do landed_costs.actual.each(&:void!) update(landed_cost: nil) end end |
#void_shipment_receipt ⇒ Hash{String=>Boolean,String}
Void this receipt: reverse the GL transaction, reverse the per-item
ledger entries, discontinue any auto-created serial numbers, void
estimated landed costs, then run the :void state event. Refuses
when the receipt is already voided or any received unit has left
stock.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'app/models/shipment_receipt.rb', line 253 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 |