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?
[:primary_image, :orderable_view_product_catalogs].freeze

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

#publish_event

Instance Attribute Details

#signatureObject (readonly)

Validations

Validations:



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

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:



131
132
133
134
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
# File 'app/models/material_alert.rb', line 131

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:



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

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:



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

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:

  • resource (Order, Quote, RoomConfiguration)

    the resource

  • signature (String)

    the line_items signature to match

Returns:

  • (ActiveRecord::Relation)

    alerts with items eager-loaded



87
88
89
90
91
92
93
94
95
# File 'app/models/material_alert.rb', line 87

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:



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

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:

  • resource (Order, Quote, RoomConfiguration)

    the resource

Returns:

  • (Integer)

    number of deleted alerts



100
101
102
103
104
105
106
107
108
# File 'app/models/material_alert.rb', line 100

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:



115
116
117
118
119
120
121
122
123
124
# File 'app/models/material_alert.rb', line 115

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>

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

has_many :items, through: :material_alert_items

#material_alert_itemsActiveRecord::Relation<MaterialAlertItem>

Join table for items recommended in this alert

Returns:

See Also:



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

has_many :material_alert_items, dependent: :destroy

#orderOrder

Associations - exactly one of these will be set

Returns:

See Also:



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

belongs_to :order, optional: true

#quoteQuote

Returns:

See Also:



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

belongs_to :quote, optional: true

#resourceObject

Returns the associated resource (whichever is set)



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

def resource
  order || quote || room_configuration
end

#roomObject

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



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

def room
  room_configuration
end

#room_configurationRoomConfiguration

Returns:

  • (RoomConfiguration)

See Also:



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

belongs_to :room_configuration, optional: true