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 =
/^PO\d+/i
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

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 Models::EventPublishable

#publish_event

Instance Attribute Details

#carrier_idObject (readonly)



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

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

#company_idObject (readonly)



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

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

#currencyObject (readonly)



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

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

#drop_ship_delivery_idObject



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

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

#exchange_rateObject (readonly)



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

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

#order_dateObject (readonly)



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

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

#po_typeObject (readonly)



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

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

#request_dateObject (readonly)



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

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

#store_idObject (readonly)



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

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

#supplier_idObject (readonly)



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

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



192
193
194
# File 'app/models/purchase_order.rb', line 192

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

.get_next_reference_numberObject



440
441
442
443
# File 'app/models/purchase_order.rb', line 440

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



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

def self.new_or_update_st_po_from_delivery(delivery)
  order = delivery.order
  from_company = 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:



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

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:



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

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

.ship_to_attributes(order) ⇒ Object



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

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



188
189
190
# File 'app/models/purchase_order.rb', line 188

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:



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

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

#all_items_cancelled?Boolean

Returns:

  • (Boolean)


368
369
370
# File 'app/models/purchase_order.rb', line 368

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

#all_items_receipted?Boolean

Returns:

  • (Boolean)


356
357
358
# File 'app/models/purchase_order.rb', line 356

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

#all_landed_costs_applied?Boolean

Returns:

  • (Boolean)


364
365
366
# File 'app/models/purchase_order.rb', line 364

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

#build_activityObject



257
258
259
# File 'app/models/purchase_order.rb', line 257

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

#can_be_cancelled?Boolean

Returns:

  • (Boolean)


340
341
342
# File 'app/models/purchase_order.rb', line 340

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

#can_be_edited?Boolean

Returns:

  • (Boolean)


336
337
338
# File 'app/models/purchase_order.rb', line 336

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

#cancel_items_and_selfObject



327
328
329
330
# File 'app/models/purchase_order.rb', line 327

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

#carrierParty

Returns:

See Also:



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

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

#communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



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

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

#companyCompany

Returns:

See Also:



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

belongs_to :company, optional: true

#currencies_not_matching?Boolean

Returns:

  • (Boolean)


352
353
354
# File 'app/models/purchase_order.rb', line 352

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

#currency_symbolObject



376
377
378
# File 'app/models/purchase_order.rb', line 376

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

#drop_ship_deliveryDelivery

Returns:

See Also:



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

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

#drop_ship_order_refObject



395
396
397
# File 'app/models/purchase_order.rb', line 395

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

#drop_ship_order_ref=(ref) ⇒ Object



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

def drop_ship_order_ref=(ref)
  return unless ref.present?

  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



275
276
277
# File 'app/models/purchase_order.rb', line 275

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

#generate_pdfObject



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

def generate_pdf
  store = self.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



279
280
281
# File 'app/models/purchase_order.rb', line 279

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

#has_service_items?Boolean

Returns:

  • (Boolean)


323
324
325
# File 'app/models/purchase_order.rb', line 323

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:



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

has_many :landed_costs

#mark_st_po_as_shipped_from_delivery(delivery) ⇒ Object



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

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



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

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



372
373
374
# File 'app/models/purchase_order.rb', line 372

def name
  reference_number.to_s
end

#orderOrder

Returns:

See Also:



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

has_one :order

#post_communication_queued_hook(_params = {}) ⇒ Object



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

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

#post_communication_sent_hook(_params = {}) ⇒ Object



266
267
268
269
# File 'app/models/purchase_order.rb', line 266

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

#promised_delivery_dateObject



271
272
273
# File 'app/models/purchase_order.rb', line 271

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

#purchase_order_itemsActiveRecord::Relation<PurchaseOrderItem>

Returns:

See Also:



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

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

#purchase_order_shipmentsActiveRecord::Relation<PurchaseOrderShipment>

Returns:

See Also:



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

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

#rmaRma

Returns:

See Also:



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

belongs_to :rma, optional: true

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns:

See Also:



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

has_many :serial_numbers, through: :shipment_receipt_items

#shipment_itemsActiveRecord::Relation<ShipmentItem>

Returns:

See Also:



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

has_many :shipment_items, dependent: :destroy

#shipment_receipt_itemsActiveRecord::Relation<ShipmentReceiptItem>

Returns:

See Also:



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

has_many :shipment_receipt_items

#shipment_receiptsActiveRecord::Relation<ShipmentReceipt>

Returns:

See Also:



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

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

#should_lock_linked_order?Boolean

Returns:

  • (Boolean)


332
333
334
# File 'app/models/purchase_order.rb', line 332

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

#some_items_receipted?Boolean

Returns:

  • (Boolean)


360
361
362
# File 'app/models/purchase_order.rb', line 360

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

#storeStore

Returns:

See Also:



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

belongs_to :store, optional: true

#supplierParty

Returns:

See Also:



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

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

#to_sObject



319
320
321
# File 'app/models/purchase_order.rb', line 319

def to_s
  "PO # #{reference_number}"
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



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

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