Module: SpreeCmCommissioner::ProductDecorator

Defined in:
app/models/spree_cm_commissioner/product_decorator.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.prepended(base) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 4

def self.prepended(base)
  base.include SpreeCmCommissioner::ProductType
  base.include SpreeCmCommissioner::KycBitwise
  base.include SpreeCmCommissioner::Metafield
  base.include SpreeCmCommissioner::TenantUpdatable
  base.include SpreeCmCommissioner::ServiceType
  base.include SpreeCmCommissioner::ServiceRecommendations
  base.include SpreeCmCommissioner::Integrations::IntegrationMappable
  base.include SpreeCmCommissioner::StoreMetadata

  base.delegate :is_open_dated, :is_open_dated?, to: :trip, allow_nil: true

  base.has_many :voting_sessions, class_name: 'SpreeCmCommissioner::VotingSession', foreign_key: :episode_id, dependent: :destroy

  base.has_many :variant_kind_option_types, -> { where(kind: :variant).order(:position) },
                through: :product_option_types, source: :option_type

  base.has_many :product_kind_option_types, -> { where(kind: :product).order(:position) },
                through: :product_option_types, source: :option_type

  base.has_many :promoted_option_types, -> { where(promoted: true).order(:position) },
                through: :product_option_types, source: :option_type

  base.has_many :option_values, through: :option_types
  base.has_many :prices_including_master, lambda {
                                            order('spree_variants.position, spree_variants.id, currency')
                                          }, source: :prices, through: :variants_including_master

  base.has_many :homepage_section_relatables,
                class_name: 'SpreeCmCommissioner::HomepageSectionRelatable',
                dependent: :destroy, as: :relatable

  # after finish purchase an order, user must complete these steps
  base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', dependent: :destroy

  base.has_one :default_state, through: :vendor
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', dependent: :destroy

  base.has_many :complete_line_items, through: :classifications, source: :line_items
  base.has_many :inventory_items, through: :variants
  base.has_many :guests, through: :line_items

  base.has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
  base.has_many :places, through: :product_places
  base.has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
  base.has_many :dynamic_fields, through: :product_dynamic_fields, class_name: 'SpreeCmCommissioner::DynamicField'

  base.has_one :venue, -> { where(type: :venue) }, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy

  base.accepts_nested_attributes_for :product_places, allow_destroy: true

  base.has_one :trip, class_name: 'SpreeCmCommissioner::Trip', dependent: :destroy
  base.has_one :service_calendar, as: :calendarable, class_name: 'SpreeCmCommissioner::ServiceCalendar', dependent: :destroy

  base.has_many :product_relations, class_name: 'SpreeCmCommissioner::ProductRelation', dependent: :destroy
  base.has_many :related_products, through: :product_relations

  base.belongs_to :event, class_name: 'Spree::Taxon', optional: true

  base.has_many :preview_roles, class_name: 'SpreeCmCommissioner::PreviewRole', as: :previewable
  base.has_many :industry_classifications, -> { joins(:taxon).where(spree_taxons: { kind: :industry }) }, class_name: 'Spree::Classification'
  base.has_many :industry_taxons, through: :industry_classifications, source: :taxon
  base.scope :visible_to, lambda { |user|
    publicly_available = where(status: :active, preview: false)

    if user
      # Resolved through associations — avoids hardcoding class names.
      taxon_type = reflect_on_association(:taxons).klass.polymorphic_name

      # IDs of products that have been explicitly assigned their own PreviewRole.
      products_with_own_roles = SpreeCmCommissioner::PreviewRole
                                .where(previewable_type: polymorphic_name)
                                .select(:previewable_id)

      # Path A: product has its own PreviewRole → user must hold that role directly.
      # Taxon membership is irrelevant for these products.
      via_product_role = where(status: :active, preview: true)
                         .where(id: products_with_own_roles)
                         .where(id: user.preview_roles.where(previewable_type: polymorphic_name).select(:previewable_id))

      # Path B: product has no own PreviewRole → inherit access from its event or taxons.
      # Accessible if the user holds a preview role for the product's event_id or any
      # taxon the product belongs to via the classifications join table.
      allowed_taxon_ids = user.preview_roles.where(previewable_type: taxon_type).select(:previewable_id)
      preview_no_own = where(status: :active, preview: true).where.not(id: products_with_own_roles)

      via_event_id = preview_no_own.where(event_id: allowed_taxon_ids)
      via_taxon_join = preview_no_own.joins(:taxons).where(spree_taxons: { id: allowed_taxon_ids })

      publicly_available
        .or(via_product_role)
        .or(where(id: via_event_id.select(:id)))
        .or(where(id: via_taxon_join.select(:id)))
    else
      publicly_available
    end
  }

  base.scope :min_price, lambda { |vendor|
    joins(:prices_including_master)
      .where(vendor_id: vendor.id, product_type: vendor.primary_product_type)
      .minimum('spree_prices.price').to_f
  }

  base.scope :max_price, lambda { |vendor|
    joins(:prices_including_master)
      .where(vendor_id: vendor.id, product_type: vendor.primary_product_type)
      .maximum('spree_prices.price').to_f
  }
  base.scope :subscribable, -> { where(subscribable: 1) }

  base.before_validation :set_event_id

  base.validate :validate_event_taxons, if: -> { taxons.event.present? }
  base.validate :validate_product_date, if: -> { available_on.present? && discontinue_on.present? }
  base.validate :product_type_unchanged, on: :update
  base.validates :commission_rate, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true

  base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name]

  base. :open_dated_validity_days, :integer, default: 90

  base.after_update :update_variants_vendor_id, if: :saved_change_to_vendor_id?
  base.after_update :sync_event_id_to_children, if: :saved_change_to_event_id?

  base.enum purchasable_on: { both: 0, web: 1, app: 2 }, _prefix: true
  base.enum call_to_action: { buy_now: 0,
                              book_now: 1,
                              reserve: 2,
                              register: 3,
                              apply: 4,
                              get_ticket: 5,
                              secure_your_seat: 6,
                              join_the_event: 7,
                              request_to_book: 8
                              }

  base.belongs_to :tenant, class_name: 'SpreeCmCommissioner::Tenant'
  base.before_save :set_tenant
end

Instance Method Details

#action_buttonObject



149
150
151
152
153
154
155
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 149

def action_button
  return :request_to_book if need_confirmation?
  return :reserve if free?
  return call_to_action.to_sym if call_to_action.present?

  :buy_now
end

#dynamic_fields_by_collection_phaseObject



165
166
167
168
169
170
171
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 165

def dynamic_fields_by_collection_phase
  {
    pre_registration: dynamic_fields.pre_registration.includes(:dynamic_field_options).order(:position),
    post_registration: dynamic_fields.post_registration.includes(:dynamic_field_options).order(:position),
    during_check_in: dynamic_fields.during_check_in.includes(:dynamic_field_options).order(:position)
  }
end

#free?Boolean

Returns:

  • (Boolean)


157
158
159
160
161
162
163
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 157

def free?
  return false if prices_including_master.empty?

  prices_including_master.all? do |master_price|
    master_price.price.zero? && (master_price.compare_at_price.nil? || master_price.compare_at_price.zero?)
  end
end

#required_dynamic_fields_completed?(guest, phase) ⇒ Boolean

Returns:

  • (Boolean)


173
174
175
176
177
178
179
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 173

def required_dynamic_fields_completed?(guest, phase)
  required_fields = dynamic_fields.where(data_fill_stage: phase)
  return true if required_fields.empty?

  filled_ids = guest.guest_dynamic_fields.where(dynamic_field_id: required_fields.select(:id)).pluck(:dynamic_field_id)
  (required_fields.pluck(:id) - filled_ids).empty?
end

#required_fields_for_guest(guest) ⇒ Object



181
182
183
184
185
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 181

def required_fields_for_guest(guest)
  dynamic_fields_by_collection_phase.transform_values do |fields|
    fields.select { |field| guest.guest_dynamic_fields.where(dynamic_field: field).empty? }
  end
end

#ticket_urlObject



145
146
147
# File 'app/models/spree_cm_commissioner/product_decorator.rb', line 145

def ticket_url
  "#{Spree::Store.default.formatted_url}/tickets/#{slug}"
end