Class: WarehousePackage

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

Overview

== Schema Information

Table name: warehouse_packages
Database name: primary

id :integer not null, primary key
box_type :string(255)
description :string(255)
fake_stopgap :boolean
flat_rate_package_type :string
height :float
is_oversize :boolean
length :float
packagings_count :integer default(0), not null
sort_order :integer
supplier_name :string
supplier_part_number :string
volume :float
width :float
created_at :datetime
updated_at :datetime
creator_id :integer
store_id :integer
updater_id :integer

Indexes

idx_height_store_id (height,store_id)
index_warehouse_packages_on_flat_rate_package_type (flat_rate_package_type)
index_warehouse_packages_on_is_oversize (is_oversize)
index_warehouse_packages_on_store_id (store_id)

Foreign Keys

warehouse_packages_store_id_fk (store_id => stores.id) ON DELETE => cascade

Constant Summary collapse

BOXTYPES =

Boxtypes.

%w[box pallet cylinder crate].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 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

#box_typeObject (readonly)



48
# File 'app/models/warehouse_package.rb', line 48

validates :description, :store, :length, :width, :height, :volume, :box_type, presence: true

#descriptionObject (readonly)



48
# File 'app/models/warehouse_package.rb', line 48

validates :description, :store, :length, :width, :height, :volume, :box_type, presence: true

#heightObject (readonly)



48
# File 'app/models/warehouse_package.rb', line 48

validates :description, :store, :length, :width, :height, :volume, :box_type, presence: true

#lengthObject (readonly)



48
# File 'app/models/warehouse_package.rb', line 48

validates :description, :store, :length, :width, :height, :volume, :box_type, presence: true

#volumeObject (readonly)



48
# File 'app/models/warehouse_package.rb', line 48

validates :description, :store, :length, :width, :height, :volume, :box_type, presence: true

#widthObject (readonly)



48
# File 'app/models/warehouse_package.rb', line 48

validates :description, :store, :length, :width, :height, :volume, :box_type, presence: true

Class Method Details

.activeActiveRecord::Relation<WarehousePackage>

A relation of WarehousePackages that are active. Active Record Scope

Returns:

See Also:



45
# File 'app/models/warehouse_package.rb', line 45

scope :active, -> { where("warehouse_packages.fake_stopgap IS NOT TRUE") }

.box_type_options_for_selectObject



136
137
138
# File 'app/models/warehouse_package.rb', line 136

def self.box_type_options_for_select
  BOXTYPES.map { |p| [p.humanize, p] }
end

.by_store_idActiveRecord::Relation<WarehousePackage>

A relation of WarehousePackages that are by store id. Active Record Scope

Returns:

See Also:



44
# File 'app/models/warehouse_package.rb', line 44

scope :by_store_id, ->(store_id) { where(store_id: store_id).order(:sort_order, :volume) }

.check_if_dimensions_oversize(dimensions, container_type = Shipment.container_types.keys.first, carrier: nil) ⇒ Object

Check if dimensions would be oversize for a specific carrier

Parameters:

  • dimensions (Array<Numeric>)

    Array of [length, width, height]

  • container_type (String) (defaults to: Shipment.container_types.keys.first)

    Container type

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name



93
94
95
96
97
# File 'app/models/warehouse_package.rb', line 93

def self.check_if_dimensions_oversize(dimensions, container_type = Shipment.container_types.keys.first, carrier: nil)
  dim_length, dim_width, dim_height = dimensions.sort.reverse
  pkg = Shipping::Container.new(length: dim_length, width: dim_width, height: dim_height, container_type:)
  check_oversize?(pkg, carrier:)
end

.check_oversize?(pkg, carrier: nil) ⇒ Boolean

Check if a package is oversize for a specific carrier (or all carriers if nil)

Parameters:

  • pkg (Shipping::Container)

    The package to check

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name (e.g., 'UPS', 'FedEx', 'Purolator', 'Canpar')

Returns:

  • (Boolean)


82
83
84
85
86
87
# File 'app/models/warehouse_package.rb', line 82

def self.check_oversize?(pkg, carrier: nil)
  r = Shipping::PackageAuditor.new
  carriers = carrier.present? ? [carrier] : nil
  r.process(pkg, carriers:)
  r.oversize_conditions?
end

.default_sortActiveRecord::Relation<WarehousePackage>

A relation of WarehousePackages that are default sort. Active Record Scope

Returns:

See Also:



46
# File 'app/models/warehouse_package.rb', line 46

scope :default_sort, -> { order(:sort_order, :volume) }

.find_or_create_closest_package_within_tolerance(store_id, package_dims, tolerance = 0.1, short_desc = "", fake_stopgap = nil) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'app/models/warehouse_package.rb', line 148

def self.find_or_create_closest_package_within_tolerance(store_id, package_dims, tolerance = 0.1, short_desc = "", fake_stopgap = nil)
  sorted_dims = package_dims.sort_by { |d| d.to_f.ceil }
  # find matching warehouse package in this store (assume length is longest dimension then width, then height)
  warehouse_package = WarehousePackage.by_store_id(store_id).where(length: sorted_dims[2].to_f, width: sorted_dims[1].to_f, height: sorted_dims[0].to_f).first
  unless warehouse_package
    # if no exact match, find closest warehouse package by volume and see if its dimensions are within tolerance of 10%
    target_vol = sorted_dims[2] * sorted_dims[1] * sorted_dims[0].to_f
    WarehousePackage.by_store_id(store_id).sort_by { |whp| whp.volume.to_f }.each_with_index do |whp, _i|
      next unless ((whp.volume.to_f / target_vol) - 1.0).abs < tolerance

      # found one close
      if (((whp.length / sorted_dims[2]) - 1.0).abs < tolerance) && (((whp.width / sorted_dims[1]) - 1.0).abs < tolerance) && (((whp.height / sorted_dims[0]) - 1.0).abs < tolerance)
        warehouse_package = whp
        break
      end
    end
  end
  warehouse_package ||= WarehousePackage.create({ description: "#{sorted_dims[2].to_i}x#{sorted_dims[1].to_i}x#{sorted_dims[0].to_i} #{short_desc}", store_id: store_id, length: sorted_dims[2].to_f, width: sorted_dims[1].to_f,
height: sorted_dims[0].to_f, fake_stopgap: fake_stopgap })
  warehouse_package
end

.flat_rate_package_types_for_selectObject



61
62
63
# File 'app/models/warehouse_package.rb', line 61

def self.flat_rate_package_types_for_select
  %w[FlatRatePaddedEnvelope FlatRateEnvelope MediumFlatRateBox RegionalRateBoxA LargeFlatRateBox SmallFlatRateEnvelope]
end

.options_for_select(store_id) ⇒ Object



57
58
59
# File 'app/models/warehouse_package.rb', line 57

def self.options_for_select(store_id)
  WarehousePackage.active.by_store_id(store_id).sort_by { |p| [(p.flat_rate_package_type.present? ? 0 : 1), p.volume, p.description] }.map { |p| ["#{p.description}#{', oversize' if p.is_oversize}", p.id] }
end

.sort_order_options_for_selectObject



140
141
142
143
144
145
146
# File 'app/models/warehouse_package.rb', line 140

def self.sort_order_options_for_select
  num_arr = []
  WarehousePackage.count.times do |i|
    num_arr << i
  end
  [["Autosort By Volume", nil]].concat(num_arr.map { |n| [n.to_s, n] })
end

Instance Method Details

#can_contain?(warehouse_package, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


105
106
107
# File 'app/models/warehouse_package.rb', line 105

def can_contain?(warehouse_package, tolerance = 0.0)
  can_contain_dimensions?([warehouse_package.length, warehouse_package.width, warehouse_package.height], tolerance)
end

#can_contain_dimensions?(dimensions, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


116
117
118
119
# File 'app/models/warehouse_package.rb', line 116

def can_contain_dimensions?(dimensions, tolerance = 0.0)
  dim_length, dim_width, dim_height = dimensions.sort_by(&:to_f).reverse
  (dim_length * (1.0 - tolerance) <= length) && (dim_width * (1.0 - tolerance) <= width) && (dim_height * (1.0 - tolerance) <= height)
end

#check_oversize?(carrier: nil) ⇒ Boolean

Check if this package is oversize for a specific carrier (or all carriers if nil)

Parameters:

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name (e.g., 'UPS', 'FedEx', 'Purolator', 'Canpar')

Returns:

  • (Boolean)


75
76
77
# File 'app/models/warehouse_package.rb', line 75

def check_oversize?(carrier: nil)
  self.class.check_oversize?(shipping_dimensions_to_package, carrier:)
end

#container_typeObject



184
185
186
187
188
189
190
191
192
193
# File 'app/models/warehouse_package.rb', line 184

def container_type
  # This method basically maps to Shipment container types
  if box_type == 'pallet'
    Shipment.container_types.keys[1] # 'pallet'
  elsif box_type == 'crate'
    Shipment.container_types.keys[2] # 'crate'
  else
    Shipment.container_types.keys.first # 'carton'
  end
end

#fits_inside?(warehouse_package, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


100
101
102
# File 'app/models/warehouse_package.rb', line 100

def fits_inside?(warehouse_package, tolerance = 0.0)
  fits_inside_dimensions?([warehouse_package.length, warehouse_package.width, warehouse_package.height], tolerance)
end

#fits_inside_dimensions?(dimensions, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


110
111
112
113
# File 'app/models/warehouse_package.rb', line 110

def fits_inside_dimensions?(dimensions, tolerance = 0.0)
  dim_length, dim_width, dim_height = dimensions.sort.reverse
  (dim_length.to_f >= length * (1.0 - tolerance)) && (dim_width.to_f >= width * (1.0 - tolerance)) && (dim_height.to_f >= height * (1.0 - tolerance))
end

#girthObject



176
177
178
# File 'app/models/warehouse_package.rb', line 176

def girth
  2.0 * (width + height)
end

#length_plus_girthObject



180
181
182
# File 'app/models/warehouse_package.rb', line 180

def length_plus_girth
  (length + girth).ceil
end

#ok_to_destroy?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'app/models/warehouse_package.rb', line 65

def ok_to_destroy?
  packagings.blank?
end

#packagingsActiveRecord::Relation<Packaging>

Returns:

See Also:



42
# File 'app/models/warehouse_package.rb', line 42

has_many :packagings, dependent: :destroy

#set_dimensions_volume_and_box_typeObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/models/warehouse_package.rb', line 121

def set_dimensions_volume_and_box_type
  # always set largest dimension to length, then width, then height
  dims = [length, width, height].sort.map(&:presence).reverse
  if dims.size == 3 # we have complete dimensions
    self.length, self.width, self.height = dims
    self.volume = length * width * height
    desc_prepend = "#{length.round}x#{width.round}x#{height.round}"
    self.description = "#{desc_prepend}, #{description}" unless description.to_s.index(desc_prepend)
    self.is_oversize = check_oversize?
  end
  # just assume box geometry for now
  self.box_type ||= "box"
  true
end

#shipping_dimensions_to_packageObject



69
70
71
# File 'app/models/warehouse_package.rb', line 69

def shipping_dimensions_to_package
  Shipping::Container.new(length: length, width: width, height: height, container_type: container_type)
end

#short_descriptionObject



170
171
172
173
174
# File 'app/models/warehouse_package.rb', line 170

def short_description
  d = [length, width, height].compact.map { |u| "#{u}\"" }.join(' x ')
  d << " [Oversize]" if is_oversize
  d
end

#storeStore

Returns:

See Also:

Validations:



41
# File 'app/models/warehouse_package.rb', line 41

belongs_to :store, optional: true