Class: PurchaseOrder

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable, Models::LiquidMethods
Defined in:
app/models/purchase_order.rb

Overview

== Schema Information

Table name: purchase_orders
Database name: primary

id :integer not null, primary key
cancel_date :date
currency :string(255)
description :string(255)
drop_ship :boolean
exchange_rate :float
order_date :date
po_type :string(255)
reference_number :string(20) not null
request_date :date
state :string(255)
terms :string(255)
total_cost :decimal(8, 2)
total_weight :float
uploads_count :integer
created_at :datetime
updated_at :datetime
carrier_id :integer
company_id :integer
creator_id :integer
drop_ship_delivery_id :integer
drop_ship_order_id :integer
rma_id :integer
store_id :integer
supplier_id :integer
updater_id :integer

Indexes

index_purchase_orders_on_drop_ship_delivery_id (drop_ship_delivery_id)
index_purchase_orders_on_reference_number (reference_number) UNIQUE
index_purchase_orders_on_state (state)
store_id_state (store_id,state)
supplier_id_state (supplier_id,state)

Defined Under Namespace

Classes: PdfGenerator

Constant Summary collapse

REFERENCE_NUMBER_PATTERN =

Regex pattern matching reference number.

/^PO\d+/i
CARRIERS =

Carriers.

{
  'US' => { 'UPS' => 181_590, 'FedEx' => 181_593, 'UPSFreight' => 181_595, 'Freightquote' => 7_269_908, 'override' => 295_414 },
  'CA' => { 'UPS' => 294_788, 'UPSFreight' => 181_595, 'FedEx' => 1_860_541, 'Purolator' => 294_759, 'Canadapost' => 294_677,
            'PurolatorFreight' => 294_760, 'Freightquote' => 7_269_908, 'override' => 295_414 }
}.freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has one collapse

Has many collapse

Class Method Summary 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 Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#carrier_idObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#company_idObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#currencyObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#drop_ship_delivery_idObject



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

validates :drop_ship_delivery_id, presence: { if: :drop_ship? }

#exchange_rateObject (readonly)



81
# File 'app/models/purchase_order.rb', line 81

validates :exchange_rate, presence: { if: :currencies_not_matching? }

#order_dateObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#po_typeObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#request_dateObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#store_idObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

#supplier_idObject (readonly)



79
# File 'app/models/purchase_order.rb', line 79

validates :company_id, :store_id, :supplier_id, :carrier_id, :order_date, :request_date, :currency, :po_type, presence: true

Class Method Details

.carrier_options_for_selectObject



194
195
196
# File 'app/models/purchase_order.rb', line 194

def self.carrier_options_for_select
  CARRIERS.map { |c, cv| cv.map { |k, v| ["#{c} - #{k}", v] } }.flatten
end

.get_next_reference_numberObject



442
443
444
445
# File 'app/models/purchase_order.rb', line 442

def self.get_next_reference_number
  seq = PurchaseOrder.find_by_sql("SELECT nextval('purchase_order_reference_numbers_seq') AS reference_number")
  "PO#{seq[0].reference_number}"
end

.new_or_update_st_po_from_delivery(delivery) ⇒ Object



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
# File 'app/models/purchase_order.rb', line 198

def self.new_or_update_st_po_from_delivery(delivery)
  order = delivery.order
  order.from_store.company
  to_company = order.to_store.company
  shipment = delivery.shipments.completed.first
  supplier = order.from_store.supplier
  store = order.from_store
  new_store = order.to_store
  carrier_id = if shipment
                 CARRIERS.dig(store.country.iso, shipment.carrier) || CARRIERS.dig(store.country.iso, 'override')
               else
                 supplier.id
               end
  # carrier_id ||= 7269908 # set it to Freightquote if we couldn't find a match
  po = order.purchase_order || PurchaseOrder.new(po_type: 'store_transfer',
                                                 company: to_company,
                                                 store: new_store,
                                                 supplier:,
                                                 terms: supplier.terms,
                                                 state: 'pending_fulfillment',
                                                 carrier_id:,
                                                 order_date: Date.current,
                                                 request_date: delivery.order.created_at,
                                                 currency: store.currency,
                                                 exchange_rate: store.currency == new_store.currency ? nil : ExchangeRate.get_exchange_rate(store.currency, new_store.currency, Date.current)) # if the currencies are different, need to record the exchange rate
  po.purchase_order_items.removable.destroy_all
  # Create the PO only with the kit component items or simple items
  delivery.line_items.without_children.non_shipping.each do |li|
    unit_cost = li.parent.present? ? li.store_item.unit_cogs : li.discounted_price
    total_cost = unit_cost * li.quantity
    po.purchase_order_items << PurchaseOrderItem.new(item: li.item,
                                                     sku: li.item.sku,
                                                     description: li.item.name,
                                                     quantity: li.quantity,
                                                     unit_weight: li.item.base_weight,
                                                     total_weight: li.item.base_weight * li.quantity,
                                                     unit_cost:,
                                                     total_cost:,
                                                     uom: 'EA',
                                                     unit_quantity: li.quantity,
                                                     line_item: li)
  end
  po.save!
  order.update(purchase_order: po)
  po
end

.open_poActiveRecord::Relation<PurchaseOrder>

A relation of PurchaseOrders that are open po. Active Record Scope

Returns:

See Also:



102
# File 'app/models/purchase_order.rb', line 102

scope :open_po, -> { where(state: %w[awaiting_transmission pending_fulfillment shipped partially_receipted]) }

.pending_service_fulfillmentActiveRecord::Relation<PurchaseOrder>

A relation of PurchaseOrders that are pending service fulfillment. Active Record Scope

Returns:

See Also:



100
# File 'app/models/purchase_order.rb', line 100

scope :pending_service_fulfillment, -> { where(state: 'pending_service_fulfillment') }

.ship_to_attributes(order) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'app/models/purchase_order.rb', line 245

def self.ship_to_attributes(order)
  phone = order.shipping_phone || order.customer.phone || order.customer.cell_phone || (begin
    order.customer.sales_rep.direct_phone
  rescue StandardError
    nil
  end) || SHIPPING_SHIPPER_CONFIGURATION[order.country.iso3.to_sym][:shipper_phone]
  email = order.first_tracking_email || (begin
    order.customer.sales_rep.email
  rescue StandardError
    nil
  end) || SHIPPING_SHIPPER_CONFIGURATION[order.country.iso3.to_sym][:shipper_email]
  { address_type: true, phone:, email: }
end

.states_for_selectObject



190
191
192
# File 'app/models/purchase_order.rb', line 190

def self.states_for_select
  state_machines[:state].states.sort_by(&:human_name).map { |s| [s.human_name, s.value] }
end

Instance Method Details

#activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



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

has_many :activities, as: :resource, dependent: :nullify

#all_items_cancelled?Boolean

Returns:

  • (Boolean)


370
371
372
# File 'app/models/purchase_order.rb', line 370

def all_items_cancelled?
  purchase_order_items.all?(&:cancelled?)
end

#all_items_receipted?Boolean

Returns:

  • (Boolean)


358
359
360
# File 'app/models/purchase_order.rb', line 358

def all_items_receipted?
  purchase_order_items.all?(&:fully_receipted?)
end

#all_landed_costs_applied?Boolean

Returns:

  • (Boolean)


366
367
368
# File 'app/models/purchase_order.rb', line 366

def all_landed_costs_applied?
  purchase_order_items.all?(&:fully_applied?)
end

#build_activityObject



259
260
261
# File 'app/models/purchase_order.rb', line 259

def build_activity
  activities.new(resource: self, party: supplier)
end

#can_be_cancelled?Boolean

Returns:

  • (Boolean)


342
343
344
# File 'app/models/purchase_order.rb', line 342

def can_be_cancelled?
  %w[awaiting_transmission pending_fulfillment pending_service_fulfillment pending_acknowledgement shipped].include?(state)
end

#can_be_edited?Boolean

Returns:

  • (Boolean)


338
339
340
# File 'app/models/purchase_order.rb', line 338

def can_be_edited?
  %w[fully_receipted landed_costs cancelled].exclude?(state)
end

#cancel_items_and_selfObject



329
330
331
332
# File 'app/models/purchase_order.rb', line 329

def cancel_items_and_self
  purchase_order_items.each(&:cancel)
  cancel
end

#carrierParty

Returns:

See Also:



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

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

#communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



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

has_many :communications, as: :resource, dependent: :nullify

#companyCompany

Returns:

See Also:



58
# File 'app/models/purchase_order.rb', line 58

belongs_to :company, optional: true

#currencies_not_matching?Boolean

Returns:

  • (Boolean)


354
355
356
# File 'app/models/purchase_order.rb', line 354

def currencies_not_matching?
  currency and company and currency != company.currency
end

#currency_symbolObject



378
379
380
# File 'app/models/purchase_order.rb', line 378

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

#drop_ship_deliveryDelivery

Returns:

See Also:



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

belongs_to :drop_ship_delivery, class_name: 'Delivery', optional: true

#drop_ship_order_refObject



397
398
399
# File 'app/models/purchase_order.rb', line 397

def drop_ship_order_ref
  drop_ship_delivery.order.try(:reference_number) if drop_ship_delivery.present?
end

#drop_ship_order_ref=(ref) ⇒ Object



401
402
403
404
405
406
407
408
409
# File 'app/models/purchase_order.rb', line 401

def drop_ship_order_ref=(ref)
  return if ref.blank?

  ord = Order.find_by(reference_number: ref)
  return if ord.nil?

  self.drop_ship_delivery ||= ord.deliveries.where.not(deliveries: { origin_address_id: ord.origin_address.id }).first
  # only set to first likely dropship delivery on order, if not already set
end

#file_nameObject



277
278
279
# File 'app/models/purchase_order.rb', line 277

def file_name
  "purchase-order-#{reference_number}.pdf"
end

#generate_pdfObject



382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'app/models/purchase_order.rb', line 382

def generate_pdf
  store
  pdf = PurchaseOrder::PdfGenerator.new(self).generate

  path = Rails.application.config.x.temp_storage_path.join(file_name)
  File.open(path, 'wb') do |file|
    file.write pdf
    file.flush
    file.fsync
  end
  upload = Upload.uploadify(path, 'purchase_order', self, file_name)
  uploads << upload
  upload
end

#get_or_regen_pdfObject



281
282
283
# File 'app/models/purchase_order.rb', line 281

def get_or_regen_pdf
  uploads.where(category: 'purchase_order').order('uploads.id desc').find { |u| u.attachment.present? } || generate_pdf
end

#has_service_items?Boolean

Returns:

  • (Boolean)


325
326
327
# File 'app/models/purchase_order.rb', line 325

def has_service_items?
  purchase_order_items.any? { |poi| poi.item.present? and poi.item.tax_class == 'svc' }
end

#landed_costsActiveRecord::Relation<LandedCost>

Returns:

See Also:



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

has_many :landed_costs

#mark_st_po_as_shipped_from_delivery(delivery) ⇒ Object



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'app/models/purchase_order.rb', line 411

def mark_st_po_as_shipped_from_delivery(delivery)
  return unless po_type == 'store_transfer'

  shipment = delivery.shipments.completed.first
  carrier_id = CARRIERS.dig(store.country.iso, shipment.carrier) || CARRIERS.dig(store.country.iso, 'override') if shipment
  save
  pos = PurchaseOrderShipment.new(date_shipped: delivery.shipped_date,
                                  promised_delivery_date: delivery.shipped_date + 5.working.days,
                                  currency: delivery.order.purchase_order.currency,
                                  carrier_id:,
                                  tracking_number: shipment.try(:tracking_number),
                                  weight: shipment.try(:weight),
                                  length: shipment.try(:length),
                                  width: shipment.try(:width),
                                  height: shipment.try(:height),
                                  actual_shipping_cost: shipment.try(:actual_total_charges))
  purchase_order_items.each do |poi|
    pos.shipment_items.build(purchase_order: self,
                             purchase_order_item: poi,
                             quantity: poi.quantity,
                             uom: 'EA',
                             ea_quantity: poi.quantity)
  end
  pos.save!
  pos.set_estimated_landed_cost if pos.estimated_landed_cost.nil?
  pos.ship!
  self
end

#move_service_gl_fundsObject



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
# File 'app/models/purchase_order.rb', line 285

def move_service_gl_funds
  transaction do
     = LedgerCompanyAccount.(company_id, RECEIPTS_NOT_VOUCHERED_ACCOUNT)
     = LedgerCompanyAccount.(company_id, SERVICES_PENDING_FULFILLMENT_CREDIT_ACCOUNT)

    raise 'Unable to find Services-Pending Fulfillment Credit company account' if .nil?
    raise 'Unable to find Receipts Not Vouchered company account' if .nil?

    grouped_items = shipment_receipt_items.group_by(&:purchase_order)

    grouped_items.each do |po, sr_items|
      next if po != self # don't process the ones which are not for this po

      service_items = sr_items.select { |sri| sri.purchase_order_item.item.present? and sri.purchase_order_item.item.tax_class == 'svc' }

      # collect the total value of the service receipted items
      total_service_value = service_items.sum(&:total_cost)

      # need to take money out of 4122 and put into 4120
      # + Services-Pending Fulfillment Credit (4122)
      # - Receipts Not Vouchered (4120)
      transaction = LedgerTransaction.new(transaction_type: 'PO_SERVICE_FULFILLMENT',
                                          transaction_date: Date.current,
                                          description:,
                                          currency:,
                                          company:,
                                          shipment_receipt: sr_items.first.shipment_receipt)
      transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: .id, currency:,
                                                    amount: total_service_value, description:)
      transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: .id, currency:,
                                                    amount: -total_service_value, description:)
      transaction.save!
    end
  end
end

#nameObject



374
375
376
# File 'app/models/purchase_order.rb', line 374

def name
  reference_number.to_s
end

#orderOrder

Returns:

See Also:



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

has_one :order

#post_communication_queued_hook(_params = {}) ⇒ Object



263
264
265
266
# File 'app/models/purchase_order.rb', line 263

def post_communication_queued_hook(_params = {})
  # Handles post transmission
  transmit if can_transmit?
end

#post_communication_sent_hook(_params = {}) ⇒ Object



268
269
270
271
# File 'app/models/purchase_order.rb', line 268

def post_communication_sent_hook(_params = {})
  # Handles post transmission
  transmit if can_transmit?
end

#promised_delivery_dateObject



273
274
275
# File 'app/models/purchase_order.rb', line 273

def promised_delivery_date
  purchase_order_shipments.open_shipments.minimum(:promised_delivery_date)
end

#purchase_order_itemsActiveRecord::Relation<PurchaseOrderItem>

Returns:

See Also:



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

has_many :purchase_order_items, dependent: :destroy, inverse_of: :purchase_order

#purchase_order_shipmentsActiveRecord::Relation<PurchaseOrderShipment>

Returns:

See Also:



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

has_many :purchase_order_shipments, -> { distinct }, through: :shipment_items

#rmaRma

Returns:

See Also:



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

belongs_to :rma, optional: true

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns:

See Also:



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

has_many :serial_numbers, through: :shipment_receipt_items

#shipment_itemsActiveRecord::Relation<ShipmentItem>

Returns:

See Also:



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

has_many :shipment_items, dependent: :destroy

#shipment_receipt_itemsActiveRecord::Relation<ShipmentReceiptItem>

Returns:

See Also:



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

has_many :shipment_receipt_items

#shipment_receiptsActiveRecord::Relation<ShipmentReceipt>

Returns:

See Also:



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

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

#should_lock_linked_order?Boolean

Returns:

  • (Boolean)


334
335
336
# File 'app/models/purchase_order.rb', line 334

def should_lock_linked_order?
  %w[shipped partially_receipted fully_receipted landed_costs].include?(state)
end

#some_items_receipted?Boolean

Returns:

  • (Boolean)


362
363
364
# File 'app/models/purchase_order.rb', line 362

def some_items_receipted?
  purchase_order_items.any? { |poi| poi.partially_receipted? or poi.fully_receipted? }
end

#storeStore

Returns:

See Also:



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

belongs_to :store, optional: true

#supplierParty

Returns:

See Also:



59
# File 'app/models/purchase_order.rb', line 59

belongs_to :supplier, class_name: 'Party', inverse_of: :purchase_orders, optional: true

#to_sObject



321
322
323
# File 'app/models/purchase_order.rb', line 321

def to_s
  "PO # #{reference_number}"
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



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

has_many :uploads, as: :resource, dependent: :destroy