Class: MaterialAlert

Inherits:
ApplicationRecord show all
Defined in:
app/models/material_alert.rb

Overview

== Schema Information

Table name: material_alerts
Database name: primary

id :bigint not null, primary key
actual_qty :integer
group_name :string
group_type :string
name :string
recommended_qty :integer
signature :string not null
unmaskable :boolean default(FALSE), not null
created_at :datetime not null
updated_at :datetime not null
order_id :bigint
quote_id :bigint
room_configuration_id :bigint

Indexes

idx_material_alerts_order_sig (order_id,signature)
idx_material_alerts_quote_sig (quote_id,signature)
idx_material_alerts_rc_sig (room_configuration_id,signature)

Foreign Keys

fk_rails_... (order_id => orders.id) ON DELETE => cascade
fk_rails_... (quote_id => quotes.id) ON DELETE => cascade
fk_rails_... (room_configuration_id => room_configurations.id) ON DELETE => cascade

Constant Summary collapse

ITEM_INCLUDES =

Scopes for each resource type with eager loading
We include both :items (through association) and material_alert_items: :item
to ensure items are preloaded regardless of how they're accessed in views.
Additional includes on items:

  • :primary_image for image_url2
  • :orderable_view_product_catalogs for is_available_to_public?
%i[primary_image orderable_view_product_catalogs].freeze

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#signatureObject (readonly)

Validations

Validations:



50
# File 'app/models/material_alert.rb', line 50

validates :signature, presence: true

Class Method Details

.create_from_check_result(resource, result, signature) ⇒ MaterialAlert?

Create a MaterialAlert from a check result hash

Parameters:

  • resource (Order, Quote, RoomConfiguration)

    the resource

  • result (Hash)

    alert data from check

  • signature (String)

    the line_items signature

Returns:



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

def create_from_check_result(resource, result, signature)
  attrs = {
    signature: signature,
    name: result[:name],
    recommended_qty: result[:recommended_qty],
    actual_qty: result[:actual_qty],
    group_name: result[:group_name],
    group_type: result[:group_type],
    unmaskable: result[:unmaskable] || false
  }

  # Set the appropriate FK based on resource type
  case resource
  when Order then attrs[:order] = resource
  when Quote then attrs[:quote] = resource
  when RoomConfiguration then attrs[:room_configuration] = resource
  end

  alert = create!(attrs)

  # Add associated items, deduplicating by id to avoid unique constraint violations
  # when check results return the same item more than once (incident #3635).
  if result[:items].present?
    result[:items].compact.uniq(&:id).each_with_index do |item, position|
      alert.material_alert_items.create!(item: item, position: position)
    end
  end

  alert
rescue ActiveRecord::RecordInvalid => e
  Rails.logger.warn("Failed to create MaterialAlert: #{e.message}")
  nil
end

.for_orderActiveRecord::Relation<MaterialAlert>

A relation of MaterialAlerts that are for order. Active Record Scope

Returns:

See Also:



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

scope :for_order, ->(order, signature:) {
  where(order: order, signature: signature)
    .includes(items: ITEM_INCLUDES, material_alert_items: { item: ITEM_INCLUDES })
}

.for_quoteActiveRecord::Relation<MaterialAlert>

A relation of MaterialAlerts that are for quote. Active Record Scope

Returns:

See Also:



66
67
68
69
# File 'app/models/material_alert.rb', line 66

scope :for_quote, ->(quote, signature:) {
  where(quote: quote, signature: signature)
    .includes(items: ITEM_INCLUDES, material_alert_items: { item: ITEM_INCLUDES })
}

.for_resource(resource, signature:) ⇒ ActiveRecord::Relation

Find alerts for any resource type

Parameters:

Returns:

  • (ActiveRecord::Relation)

    alerts with items eager-loaded



91
92
93
94
95
96
97
98
99
# File 'app/models/material_alert.rb', line 91

def for_resource(resource, signature:)
  case resource
  when Order then for_order(resource, signature: signature)
  when Quote then for_quote(resource, signature: signature)
  when RoomConfiguration then for_room_configuration(resource, signature: signature)
  else
    raise ArgumentError, "Unknown resource type: #{resource.class}"
  end
end

.for_room_configurationActiveRecord::Relation<MaterialAlert>

A relation of MaterialAlerts that are for room configuration. Active Record Scope

Returns:

See Also:



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

scope :for_room_configuration, ->(rc, signature:) {
  where(room_configuration: rc, signature: signature)
    .includes(items: ITEM_INCLUDES, material_alert_items: { item: ITEM_INCLUDES })
}

.invalidate_for(resource) ⇒ Integer

Delete all alerts for a resource (used for invalidation)

Parameters:

Returns:

  • (Integer)

    number of deleted alerts



104
105
106
107
108
109
110
111
112
# File 'app/models/material_alert.rb', line 104

def invalidate_for(resource)
  case resource
  when Order then where(order: resource).delete_all
  when Quote then where(quote: resource).delete_all
  when RoomConfiguration then where(room_configuration: resource).delete_all
  else
    0
  end
end

.regenerate_for(resource, check_results:, signature:) ⇒ Array<MaterialAlert>

Regenerate alerts for a resource from check results

Parameters:

  • resource (Order, Quote, RoomConfiguration)

    the resource

  • check_results (Array<Hash>)

    results from Item::Materials::Check

  • signature (String)

    the line_items signature

Returns:



119
120
121
122
123
124
125
126
127
128
# File 'app/models/material_alert.rb', line 119

def regenerate_for(resource, check_results:, signature:)
  return [] if check_results.blank?

  transaction do
    invalidate_for(resource)
    check_results.filter_map do |result|
      create_from_check_result(resource, result, signature)
    end
  end
end

Instance Method Details

#itemsActiveRecord::Relation<Item>

Items recommended by this alert.

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



47
# File 'app/models/material_alert.rb', line 47

has_many :items, through: :material_alert_items

#material_alert_itemsActiveRecord::Relation<MaterialAlertItem>

Join table for items recommended in this alert

Returns:

See Also:



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

has_many :material_alert_items, dependent: :destroy

#orderOrder

Associations - exactly one of these will be set

Returns:

See Also:



38
# File 'app/models/material_alert.rb', line 38

belongs_to :order, optional: true

#quoteQuote

Quote the alert was raised for.

Returns:

See Also:



40
# File 'app/models/material_alert.rb', line 40

belongs_to :quote, optional: true

#resourceObject

Returns the associated resource (whichever is set)



82
83
84
# File 'app/models/material_alert.rb', line 82

def resource
  order || quote || room_configuration
end

#roomObject

Convenience method to match Virtus interface (views use material_alert.room)



77
78
79
# File 'app/models/material_alert.rb', line 77

def room
  room_configuration
end

#room_configurationRoomConfiguration

Room configuration the alert was raised for.



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

belongs_to :room_configuration, optional: true