spree_cm_commissioner 2.5.8 → 2.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/api/v2/operator/guest_json_gzips_controller.rb +53 -0
  4. data/app/controllers/spree_cm_commissioner/orders_controller.rb +1 -1
  5. data/app/jobs/spree_cm_commissioner/export_job.rb +8 -0
  6. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +5 -0
  7. data/app/models/spree_cm_commissioner/check_in_ability.rb +2 -0
  8. data/app/models/spree_cm_commissioner/export.rb +26 -10
  9. data/app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb +37 -0
  10. data/app/models/spree_cm_commissioner/order_decorator.rb +14 -0
  11. data/app/models/spree_cm_commissioner/pricing_action.rb +21 -14
  12. data/app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb +14 -0
  13. data/app/models/spree_cm_commissioner/pricing_actions/create_route_adjustments.rb +70 -0
  14. data/app/models/spree_cm_commissioner/pricing_model.rb +39 -0
  15. data/app/models/spree_cm_commissioner/pricing_model_handler/order.rb +72 -0
  16. data/app/models/spree_cm_commissioner/pricing_model_route.rb +16 -0
  17. data/app/models/spree_cm_commissioner/route.rb +2 -0
  18. data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -0
  19. data/app/queries/spree_cm_commissioner/check_in_sessions_metrics_query.rb +24 -0
  20. data/app/serializers/spree/v2/storefront/cart_serializer_decorator.rb +1 -0
  21. data/app/serializers/spree_cm_commissioner/v2/operator/export_serializer.rb +14 -0
  22. data/app/services/spree_cm_commissioner/cart/recalculate_decorator.rb +0 -4
  23. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +11 -1
  24. data/app/services/spree_cm_commissioner/operator_guest_json_gzips/create.rb +40 -0
  25. data/app/services/spree_cm_commissioner/orders/jwt_token/generate.rb +1 -1
  26. data/app/services/spree_cm_commissioner/transit/legs_builder_service.rb +2 -1
  27. data/app/services/spree_cm_commissioner/transit_order/create.rb +14 -8
  28. data/app/views/blazer/queries/_preset.html.erb +2 -4
  29. data/config/initializers/spree_permitted_attributes.rb +1 -0
  30. data/config/routes.rb +1 -0
  31. data/db/migrate/20260110073000_create_cm_pricing_model_routes.rb +37 -0
  32. data/db/migrate/20260128093305_add_exportable_to_cm_exports.rb +5 -0
  33. data/db/migrate/20260128093313_add_metadata_to_cm_exports.rb +11 -0
  34. data/db/migrate/20260129082338_remove_preferences_file_name_columns_from_cm_exports.rb +13 -0
  35. data/db/migrate/20260204092321_add_type_to_cm_exports.rb +11 -0
  36. data/db/migrate/20260204183545_add_term_accepted_at_to_spree_orders.rb +6 -0
  37. data/lib/spree_cm_commissioner/check_in_sessions_metric.rb +34 -0
  38. data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +4 -0
  39. data/lib/spree_cm_commissioner/test_helper/factories/pricing_model_route_factory.rb +8 -0
  40. data/lib/spree_cm_commissioner/transit/leg.rb +4 -1
  41. data/lib/spree_cm_commissioner/version.rb +1 -1
  42. data/lib/spree_cm_commissioner.rb +1 -0
  43. metadata +19 -4
  44. data/app/services/spree_cm_commissioner/line_items/apply_pricing_models.rb +0 -27
  45. data/app/services/spree_cm_commissioner/pricing_models/apply.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52ecb9b50cd59df5354348171034968d7c6c1b7a59ba4ad1eb1a6b134d3b3a8b
4
- data.tar.gz: bc18344da1e94917ceff4341cec0bfaa82d1160941ec54a5ea8dd0b2d13860b8
3
+ metadata.gz: 673c4b3d46c1121063b406d0a7228e1f640467906b8c0960d9c76bc4553c7a59
4
+ data.tar.gz: 45b5352a4f194674bae8fb259d766057d9404d498539ff5ebae46f202534ba6c
5
5
  SHA512:
6
- metadata.gz: 1e364c0093ef94862e7ab2d94f9e44b6ba2dee21b0951885c3c6e0feaded3e12ba572b856d33ea1fce84fc86c19ea87eb7bcf716c817ed4b5269c59b912976a4
7
- data.tar.gz: d6dc9e74e163f64d353513020ba7b8fdba7ab149774eb1dd3cf3153005ebdabea14de798968e043e6d9681aed59eed03334f36ea35d59707b20db277858bad59
6
+ metadata.gz: 2a0209ab5b1a8b0b381cf33211265acad61b368a0cc09eed582d0b4d6264b9151dd522fcf3d7609b3c501e1a957fb16779bab58f717a93664219a6ed2a9781b7
7
+ data.tar.gz: 88b21257aba4536f9ffbc86d2c94a631440b772af6f7ac724e80f943152ed33ac0c6207f15e12af32b96a3530ad6ef819fc4e216e6ce003c0fefecd796d4fcde
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.5.8)
37
+ spree_cm_commissioner (2.5.10)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -0,0 +1,53 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Operator
5
+ class GuestJsonGzipsController < ::Spree::Api::V2::ResourceController
6
+ before_action :require_spree_current_user
7
+
8
+ # POST /api/v2/operator/guest_json_gzips
9
+ # - taxon_id=1
10
+ # - resource_includes[]=[check_ins,check_ins.check_in_by]
11
+ # - force_create=true (optional, to bypass recent export check)
12
+ def create
13
+ spree_authorize! :create, SpreeCmCommissioner::Exports::OperatorGuestJsonGzip
14
+
15
+ exportable = Spree::Taxon.find(params[:taxon_id])
16
+
17
+ result = SpreeCmCommissioner::OperatorGuestJsonGzips::Create.call(
18
+ exportable: exportable,
19
+ resource_includes: params[:resource_includes] || [],
20
+ force_create: params[:force_create] == 'true'
21
+ )
22
+
23
+ if result.success?
24
+ status = result.value.created_at < 1.minute.ago ? 200 : 201
25
+ @export = result.value
26
+ render_serialized_payload(status) { serialize_resource(@export) }
27
+ else
28
+ render_error_payload(result.error)
29
+ end
30
+ end
31
+
32
+ # override
33
+ def scope
34
+ SpreeCmCommissioner::Exports::OperatorGuestJsonGzip.where(
35
+ exportable_type: 'Spree::Taxon',
36
+ exportable_id: params[:taxon_id]
37
+ ).order(created_at: :desc)
38
+ end
39
+
40
+ # override
41
+ def collection_serializer
42
+ SpreeCmCommissioner::V2::Operator::ExportSerializer
43
+ end
44
+
45
+ # override
46
+ def resource_serializer
47
+ SpreeCmCommissioner::V2::Operator::ExportSerializer
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -7,7 +7,7 @@ module SpreeCmCommissioner
7
7
  @order = if params[:id].match?(/^R\d{9,}-([A-Za-z0-9_\-]+)$/)
8
8
  Spree::Order.search_by_qr_data!(params[:id])
9
9
  else
10
- SpreeCmCommissioner::Orders::Tickets::VerifyToken.call(token: params[:id]).value[:order]
10
+ SpreeCmCommissioner::Orders::JwtToken::Verify.call(token: params[:t]).value[:order]
11
11
  end
12
12
 
13
13
  @product_type = @order.products.first&.product_type || 'accommodation'
@@ -0,0 +1,8 @@
1
+ module SpreeCmCommissioner
2
+ class ExportJob < ApplicationUniqueJob
3
+ def perform(options = {})
4
+ export_id = options[:export_id]
5
+ SpreeCmCommissioner::Export.find(export_id).export
6
+ end
7
+ end
8
+ end
@@ -53,6 +53,7 @@ module SpreeCmCommissioner
53
53
 
54
54
  def direction = private_metadata['direction']
55
55
  def trip_id = private_metadata['trip_id']&.to_i
56
+ def connected_trip_id = private_metadata['connected_trip_id']
56
57
  def boarding_trip_stop_id = private_metadata['boarding_trip_stop_id']&.to_i
57
58
  def drop_off_trip_stop_id = private_metadata['drop_off_trip_stop_id']&.to_i
58
59
 
@@ -99,6 +100,10 @@ module SpreeCmCommissioner
99
100
  set_private_metadata_value('trip_id', value)
100
101
  end
101
102
 
103
+ def connected_trip_id=(value)
104
+ set_private_metadata_value('connected_trip_id', value)
105
+ end
106
+
102
107
  def boarding_trip_stop_id=(value)
103
108
  set_private_metadata_value('boarding_trip_stop_id', value)
104
109
  end
@@ -5,9 +5,11 @@ module SpreeCmCommissioner
5
5
  def initialize(user)
6
6
  if user.has_spree_role?('operator')
7
7
  can :manage, CheckIn
8
+ can :create, Export
8
9
  elsif user.has_spree_role?('organizer')
9
10
  can :manage, CheckIn
10
11
  can :manage, Guest
12
+ can :create, Export
11
13
  else
12
14
  cannot :manage, CheckIn
13
15
  end
@@ -1,26 +1,42 @@
1
1
  module SpreeCmCommissioner
2
2
  class Export < Base
3
- before_create :set_uuid
3
+ self.inheritance_column = :export_type
4
+
5
+ include SpreeCmCommissioner::StoreMetadata
6
+ include ::Rails.application.routes.url_helpers
7
+
8
+ belongs_to :exportable, polymorphic: true
9
+
4
10
  has_one_attached :exported_file
5
11
 
12
+ before_validation :set_uuid
13
+
14
+ after_create :enqueue_export
15
+
6
16
  enum status: { :queue => 0, :progress => 1, :done => 2, :failed => 3 }
7
17
 
8
- def construct_header
9
- raise 'You must implement construct_header method in your sub class'
18
+ def set_uuid
19
+ self.uuid = SecureRandom.uuid
10
20
  end
11
21
 
12
- def construct_row(_resource)
13
- raise 'You must implement construct_row method in your sub class'
22
+ def exported_file_name
23
+ return nil if exported_file.blank?
24
+
25
+ exported_file.filename.to_s
14
26
  end
15
27
 
16
- def scope
17
- raise 'You must implement scope method in your sub class'
28
+ def exported_file_url
29
+ return nil if exported_file.blank?
30
+
31
+ cdn_image_url(exported_file)
18
32
  end
19
33
 
20
- def set_uuid
21
- self.uuid = SecureRandom.uuid
34
+ def enqueue_export
35
+ SpreeCmCommissioner::ExportJob.perform_later(export_id: id)
22
36
  end
23
37
 
24
- def export_csv; end
38
+ def export
39
+ raise NotImplementedError, 'Subclasses must implement the export method'
40
+ end
25
41
  end
26
42
  end
@@ -0,0 +1,37 @@
1
+ module SpreeCmCommissioner
2
+ module Exports
3
+ class OperatorGuestJsonGzip < ::SpreeCmCommissioner::Export
4
+ # eg. ['check_ins', 'check_ins.check_in_by', 'line_item', 'line_item.order']
5
+ store_public_metadata :resource_includes, :array
6
+ store_public_metadata :guests_count, :integer
7
+
8
+ # override
9
+ def export
10
+ update(status: :progress)
11
+
12
+ guests = SpreeCmCommissioner::GuestSearcherQuery.new(event_id: exportable_id).call
13
+ hash = SpreeCmCommissioner::V2::Operator::GuestSerializer.new(
14
+ guests,
15
+ { include: resource_includes }
16
+ ).serializable_hash
17
+
18
+ file_name = "exports_operator_guest_#{id}.json.gz"
19
+ json_gzip = ActiveSupport::Gzip.compress(JSON.generate(hash))
20
+
21
+ exported_file.attach(
22
+ io: StringIO.new(json_gzip),
23
+ filename: file_name,
24
+ content_type: 'application/gzip'
25
+ )
26
+
27
+ update(
28
+ guests_count: guests.size,
29
+ status: :done
30
+ )
31
+ rescue StandardError => e
32
+ CmAppLogger.error(label: "#{self.class.name}#export", data: { export_id: id, error: e.message, backtrace: e.backtrace })
33
+ update(status: :failed)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -264,6 +264,20 @@ module SpreeCmCommissioner
264
264
  tenant&.formatted_url.presence || ENV.fetch('DEFAULT_URL_HOST')
265
265
  end
266
266
 
267
+ # Returns arrays of connected line_item IDs grouped by connected_trip_id.
268
+ # Example: [[1,2,3], [4,5], [6]]
269
+ #
270
+ # Line items with the same connected_trip_id are grouped together.
271
+ # Line items without a group_id are returned as singletons.
272
+ def connected_line_item_ids(direction: nil)
273
+ scoped = line_items
274
+ scoped = scoped.select { |li| li.direction == direction } if direction.present?
275
+
276
+ scoped.group_by { |li| li.connected_trip_id || li.id }
277
+ .values
278
+ .map { |group| group.map(&:id).sort }
279
+ end
280
+
267
281
  private
268
282
 
269
283
  def unstock_inventory_in_redis!
@@ -1,27 +1,34 @@
1
1
  module SpreeCmCommissioner
2
2
  class PricingAction < Base
3
- belongs_to :pricing_rule_group, class_name: 'SpreeCmCommissioner::PricingRuleGroup'
3
+ include Spree::CalculatedAdjustments
4
+ include Spree::AdjustmentSource
4
5
 
5
- has_one :calculator, as: :calculable, class_name: 'Spree::Calculator', dependent: :destroy
6
- accepts_nested_attributes_for :calculator
6
+ has_many :adjustments, as: :source, class_name: 'Spree::Adjustment'
7
7
 
8
- def perform(line_item)
9
- return if pricing_rule_group.pricing_rules.blank?
8
+ belongs_to :pricing_rule_group, class_name: 'SpreeCmCommissioner::PricingRuleGroup'
10
9
 
11
- amount = calculator&.compute(line_item)
10
+ delegate :pricing_rules, to: :pricing_rule_group
12
11
 
13
- line_item.adjustments.create!(
14
- amount: amount,
15
- source: self,
16
- adjustable: line_item,
17
- order: line_item.order,
18
- label: pricing_rule_group.pricing_model.name,
19
- mandatory: true
20
- )
12
+ def perform(_options = {})
13
+ raise NotImplementedError, "#{self.class}#perform must be implemented"
14
+ end
15
+
16
+ def compute_amount(adjustable)
17
+ calculator&.compute(adjustable) || 0
21
18
  end
22
19
 
23
20
  def self.available_calculator_types
24
21
  SpreeCmCommissioner::Calculators.constants.map(&:to_s)
25
22
  end
23
+
24
+ protected
25
+
26
+ def label
27
+ pricing_rule_group.name
28
+ end
29
+
30
+ def order_currency(order)
31
+ order&.currency || Spree::Config[:currency]
32
+ end
26
33
  end
27
34
  end
@@ -1,6 +1,20 @@
1
1
  module SpreeCmCommissioner
2
2
  module PricingActions
3
3
  class CreateLineItemAdjustments < SpreeCmCommissioner::PricingAction
4
+ def perform(options = {})
5
+ order = options[:order]
6
+ line_items = options[:line_items] || []
7
+
8
+ return if line_items.blank?
9
+
10
+ line_items.each do |line_item|
11
+ create_unique_adjustment(order, line_item)
12
+ end
13
+ end
14
+
15
+ def compute_amount(line_item)
16
+ compute(line_item)
17
+ end
4
18
  end
5
19
  end
6
20
  end
@@ -0,0 +1,70 @@
1
+ module SpreeCmCommissioner
2
+ module PricingActions
3
+ class CreateRouteAdjustments < SpreeCmCommissioner::PricingAction
4
+ def perform(options = {})
5
+ order = options[:order]
6
+ line_items = options[:line_items] || []
7
+
8
+ return if pricing_rules.blank?
9
+ return if line_items.blank?
10
+
11
+ eligible_guests = collect_unique_eligible_guests(line_items)
12
+ return if eligible_guests.empty?
13
+
14
+ total_amount = compute_total_amount(order, eligible_guests)
15
+ return if total_amount.zero?
16
+
17
+ create_route_adjustment(order, total_amount)
18
+ end
19
+
20
+ private
21
+
22
+ def collect_unique_eligible_guests(line_items)
23
+ seen = {}
24
+
25
+ line_items.each_with_object([]) do |line_item, result|
26
+ line_item.guests.select { |guest| guest_eligible?(guest) }.each_with_index do |guest, index|
27
+ key = guest.saved_guest_id.present? ? [guest.saved_guest_id, index] : guest.id
28
+ next if seen[key]
29
+
30
+ seen[key] = true
31
+ result << guest
32
+ end
33
+ end
34
+ end
35
+
36
+ def guest_eligible?(guest)
37
+ pricing_rules.all? do |rule|
38
+ !rule.respond_to?(:guest_eligible?) || rule.guest_eligible?(guest)
39
+ end
40
+ end
41
+
42
+ def compute_total_amount(order, guests)
43
+ guests.sum { |guest| compute_per_guest_amount(order, guest) }
44
+ end
45
+
46
+ def compute_per_guest_amount(order, guest)
47
+ item = build_per_guest_item(order, guest)
48
+ compute_amount(item)
49
+ end
50
+
51
+ def build_per_guest_item(order, guest)
52
+ Struct.new(:amount, :currency, :quantity).new(
53
+ guest.line_item.amount_per_guest,
54
+ order_currency(order),
55
+ 1
56
+ )
57
+ end
58
+
59
+ def create_route_adjustment(order, amount)
60
+ adjustments.create!(
61
+ adjustable: order,
62
+ amount: amount,
63
+ order: order,
64
+ label: label,
65
+ mandatory: true
66
+ )
67
+ end
68
+ end
69
+ end
70
+ end
@@ -6,6 +6,8 @@ module SpreeCmCommissioner
6
6
 
7
7
  has_many :pricing_model_variants, class_name: 'SpreeCmCommissioner::PricingModelVariant', dependent: :destroy
8
8
  has_many :variants, through: :pricing_model_variants, class_name: 'Spree::Variant'
9
+ has_many :pricing_model_routes, class_name: 'SpreeCmCommissioner::PricingModelRoute', dependent: :destroy
10
+ has_many :routes, through: :pricing_model_routes, class_name: 'SpreeCmCommissioner::Route'
9
11
  has_many :pricing_rule_groups, class_name: 'SpreeCmCommissioner::PricingRuleGroup', dependent: :destroy
10
12
  has_many :pricing_rules, through: :pricing_rule_groups, class_name: 'SpreeCmCommissioner::PricingRule'
11
13
 
@@ -14,5 +16,42 @@ module SpreeCmCommissioner
14
16
  validates :name, presence: true, uniqueness: { scope: :vendor_id }
15
17
 
16
18
  enum status: { draft: 0, active: 1, archived: 2 }
19
+
20
+ # Activates pricing model for an order (similar to Spree::Promotion#activate)
21
+ def activate(order:, line_items:)
22
+ pricing_rule_groups.each do |group|
23
+ activate_group(group, order, line_items)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def activate_group(group, order, line_items)
30
+ eligible_rules = group.pricing_rules.select { |r| any_line_item_eligible?(r, line_items) }
31
+
32
+ return if eligible_rules.empty?
33
+
34
+ if group_eligible?(group, eligible_rules)
35
+
36
+ group.pricing_action&.perform(order: order, line_items: line_items)
37
+ else
38
+ Rails.logger.info("PricingRuleGroup #{group.id} not eligible for order #{order.id}")
39
+ end
40
+ end
41
+
42
+ def any_line_item_eligible?(rule, line_items)
43
+ line_items.any? { |li| rule.eligible?(li) }
44
+ end
45
+
46
+ def group_eligible?(group, eligible_rules)
47
+ case group.match_type
48
+ when 'all'
49
+ eligible_rules.size == group.pricing_rules.size
50
+ when 'any'
51
+ eligible_rules.any?
52
+ else
53
+ false
54
+ end
55
+ end
17
56
  end
18
57
  end
@@ -0,0 +1,72 @@
1
+ module SpreeCmCommissioner
2
+ module PricingModelHandler
3
+ # Handles pricing model activation for orders
4
+ class Order
5
+ attr_reader :order
6
+
7
+ def initialize(order)
8
+ @order = order
9
+ end
10
+
11
+ def activate
12
+ return unless order&.persisted?
13
+
14
+ order.adjustments.pricing_action.delete_all
15
+
16
+ order.connected_line_item_ids.each do |group_ids|
17
+ line_items_in_group = order.line_items.where(id: group_ids)
18
+ activate_for_line_items(line_items_in_group)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def activate_for_line_items(line_items)
25
+ return if line_items.blank?
26
+
27
+ pricing_models = find_pricing_models(line_items).compact
28
+ return if pricing_models.blank?
29
+
30
+ pricing_models.each do |pricing_model|
31
+ pricing_model.activate(order: order, line_items: line_items)
32
+ end
33
+ end
34
+
35
+ def find_pricing_models(line_items)
36
+ route_pricing_models(line_items) + variant_pricing_models(line_items)
37
+ end
38
+
39
+ def route_pricing_models(line_items)
40
+ transit_line_items = line_items.select { |li| li.trip_id.present? }
41
+ return [] if transit_line_items.blank?
42
+
43
+ origin_place_id, destination_place_id = determine_origin_destination(transit_line_items)
44
+
45
+ SpreeCmCommissioner::PricingModelRoute
46
+ .active
47
+ .for_origin_destination(origin_place_id, destination_place_id)
48
+ .includes(:pricing_model)
49
+ .map(&:pricing_model)
50
+ end
51
+
52
+ def variant_pricing_models(line_items)
53
+ line_items.flat_map { |li| li.variant&.pricing_models&.active&.to_a || [] }
54
+ end
55
+
56
+ def determine_origin_destination(line_items)
57
+ if line_items.size > 1
58
+ first_line_item = line_items.min_by(&:id)
59
+ last_line_item = line_items.max_by(&:id)
60
+
61
+ first_trip = SpreeCmCommissioner::Trip.find(first_line_item.trip_id)
62
+ last_trip = SpreeCmCommissioner::Trip.find(last_line_item.trip_id)
63
+
64
+ [first_trip.origin_place_id, last_trip.destination_place_id]
65
+ else
66
+ trip = SpreeCmCommissioner::Trip.find(line_items.first.trip_id)
67
+ [trip.origin_place_id, trip.destination_place_id]
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,16 @@
1
+ module SpreeCmCommissioner
2
+ class PricingModelRoute < Base
3
+ belongs_to :route, class_name: 'SpreeCmCommissioner::Route'
4
+ belongs_to :pricing_model, class_name: 'SpreeCmCommissioner::PricingModel'
5
+ belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place'
6
+ belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place'
7
+
8
+ scope :for_origin_destination, lambda { |origin_place_id, destination_place_id|
9
+ where(origin_place_id: origin_place_id, destination_place_id: destination_place_id)
10
+ }
11
+
12
+ scope :active, lambda {
13
+ joins(:pricing_model).where(pricing_model: { status: :active })
14
+ }
15
+ end
16
+ end
@@ -14,6 +14,8 @@ module SpreeCmCommissioner
14
14
  has_many :route_photos, class_name: 'SpreeCmCommissioner::RoutePhoto', as: :viewable, dependent: :destroy
15
15
 
16
16
  has_many :trips, inverse_of: :route
17
+ has_many :pricing_model_routes, class_name: 'SpreeCmCommissioner::PricingModelRoute', dependent: :destroy
18
+ has_many :pricing_models, through: :pricing_model_routes, class_name: 'SpreeCmCommissioner::PricingModel'
17
19
 
18
20
  validates :route_name, presence: true
19
21
 
@@ -41,6 +41,8 @@ module SpreeCmCommissioner
41
41
  base.has_many :visible_classifications, -> { where(visible: true).order(:position) }, class_name: 'Spree::Classification'
42
42
  base.has_many :visible_products, through: :visible_classifications, class_name: 'Spree::Product', source: :product
43
43
 
44
+ base.has_many :exports, -> { order(created_at: :desc) }, class_name: 'SpreeCmCommissioner::Export', as: :exportable
45
+
44
46
  base.belongs_to :vendor, class_name: 'Spree::Vendor'
45
47
 
46
48
  base.validates_associated :category_icon
@@ -0,0 +1,24 @@
1
+ module SpreeCmCommissioner
2
+ class CheckInSessionsMetricsQuery
3
+ def initialize(event:, session:)
4
+ @event = event
5
+ @session = session
6
+ end
7
+
8
+ def call
9
+ guests = @session.check_in_rules.any? ? eligible_guests_for_session : @event.guests
10
+ guests_count = guests.count
11
+ check_ins_count = guests.left_joins(:check_in).where.not(cm_check_ins: { id: nil })
12
+ .where(cm_check_ins: { check_in_session_id: [@session.id, nil] })
13
+ .distinct.count(:id)
14
+
15
+ CheckInSessionsMetric.new(check_ins_count: check_ins_count, guests_count: guests_count)
16
+ end
17
+
18
+ private
19
+
20
+ def eligible_guests_for_session
21
+ @event.guests.where('cm_guests.public_metadata @> ?::jsonb', { eligible_check_in_session_ids: [@session.id] }.to_json)
22
+ end
23
+ end
24
+ end
@@ -4,6 +4,7 @@ module Spree
4
4
  module CartSerializerDecorator
5
5
  def self.prepended(base)
6
6
  base.attributes :phone_number, :intel_phone_number, :country_code, :request_state,
7
+ :term_accepted_at,
7
8
  :channel, :hold_expires_at
8
9
 
9
10
  base.attribute :qr_data do |order|
@@ -0,0 +1,14 @@
1
+ module SpreeCmCommissioner
2
+ module V2
3
+ module Operator
4
+ class ExportSerializer < BaseSerializer
5
+ attributes :status,
6
+ :exported_file_name,
7
+ :exported_file_url,
8
+ :public_metadata,
9
+ :created_at,
10
+ :updated_at
11
+ end
12
+ end
13
+ end
14
+ end
@@ -24,10 +24,6 @@ module SpreeCmCommissioner
24
24
  else
25
25
  order.ensure_updated_shipments
26
26
  end
27
- # SPREE: Original Spree::Cart::Recalculate code ends here
28
-
29
- # CUSTOM: Apply pricing models (intercity taxi, line item pricing) to the line item
30
- SpreeCmCommissioner::LineItems::ApplyPricingModels.call(order: order, line_item: line_item)
31
27
 
32
28
  # SPREE: Original Spree::Cart::Recalculate code continues here
33
29
  ::Spree::PromotionHandler::Cart.new(order, line_item).activate
@@ -36,6 +36,8 @@ class SpreeCmCommissioner::Integrations::StadiumXV1
36
36
 
37
37
  match_mapping = Spree::Taxon.find_or_initialize_integration_mapping(integration_id: @integration.id, external_id: external_match._id)
38
38
  match_taxon = match_mapping.internal
39
+ match_name = "#{external_match.home_name} vs #{external_match.away_name}"
40
+ match_permalink = generate_match_permalink(match_name)
39
41
 
40
42
  # Track match sync and create event taxon directly under events/
41
43
  @sync_result.track(:match, match_taxon) do |tracker|
@@ -43,7 +45,8 @@ class SpreeCmCommissioner::Integrations::StadiumXV1
43
45
  vendor: @integration.vendor,
44
46
  parent: events_root_taxon,
45
47
  taxonomy: events_taxonomy,
46
- name: "#{external_match.home_name} vs #{external_match.away_name}",
48
+ name: match_name,
49
+ permalink: match_permalink,
47
50
  kind: :event,
48
51
  from_date: external_match.match_datetime,
49
52
  to_date: external_match.match_datetime + 120.minutes,
@@ -108,6 +111,13 @@ class SpreeCmCommissioner::Integrations::StadiumXV1
108
111
  def events_taxonomy
109
112
  @events_taxonomy ||= Spree::Taxonomy.events
110
113
  end
114
+
115
+ def generate_match_permalink(match_name)
116
+ loop do
117
+ base_permalink = "#{events_root_taxon.permalink}/#{match_name.to_s.parameterize}-#{SecureRandom.hex(4)}"
118
+ break base_permalink unless Spree::Taxon.exists?(permalink: base_permalink)
119
+ end
120
+ end
111
121
  end
112
122
  end
113
123
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreeCmCommissioner
4
+ module OperatorGuestJsonGzips
5
+ class Create
6
+ prepend ::Spree::ServiceModule::Base
7
+
8
+ def call(exportable:, resource_includes: [], force_create: false)
9
+ unless force_create
10
+ recent_export = find_recent_export(exportable)
11
+ return success(recent_export) if recent_export
12
+ end
13
+
14
+ export = build_export(exportable, resource_includes)
15
+ if export.save
16
+ success(export)
17
+ else
18
+ failure(export, export.errors.full_messages.to_sentence)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def find_recent_export(exportable)
25
+ SpreeCmCommissioner::Exports::OperatorGuestJsonGzip
26
+ .where(exportable: exportable)
27
+ .where('created_at > ?', 2.hours.ago)
28
+ .order(created_at: :desc)
29
+ .first
30
+ end
31
+
32
+ def build_export(exportable, resource_includes)
33
+ SpreeCmCommissioner::Exports::OperatorGuestJsonGzip.new(
34
+ exportable: exportable,
35
+ resource_includes: resource_includes
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -5,7 +5,7 @@ module SpreeCmCommissioner
5
5
 
6
6
  def call(order:)
7
7
  nonce = generate_nonce
8
- token = "#{order.number}?jwt_token=#{nonce}-#{generate_qr_jwt(order: order, nonce: nonce)}"
8
+ token = "#{order.number}?t=#{nonce}-#{generate_qr_jwt(order: order, nonce: nonce)}"
9
9
 
10
10
  success(token: token)
11
11
  end
@@ -13,6 +13,7 @@ module SpreeCmCommissioner
13
13
  SpreeCmCommissioner::Transit::Leg.new(
14
14
  direction: direction,
15
15
  trip_id: leg['trip_id'].to_i,
16
+ main_trip_id: leg['main_trip_id']&.to_i,
16
17
  boarding_trip_stop_id: leg['boarding_trip_stop_id'].to_i,
17
18
  drop_off_trip_stop_id: leg['drop_off_trip_stop_id'].to_i,
18
19
  seat_selections: Array(leg['seat_selections']).map do |seat|
@@ -35,7 +36,7 @@ module SpreeCmCommissioner
35
36
  else
36
37
  legs_params.map do |leg|
37
38
  leg.permit(
38
- :trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id,
39
+ :trip_id, :main_trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id,
39
40
  seat_selections: [:variant_id, :quantity, { block_ids: [] }]
40
41
  )
41
42
  end
@@ -53,8 +53,11 @@ module SpreeCmCommissioner
53
53
  all_line_items = []
54
54
  current_leg_date = initial_date
55
55
 
56
+ # Use main_trip_id for connected trips (when legs have multiple trips)
57
+ connected_trip_id = legs.size > 1 ? legs.first.main_trip_id&.to_s : nil
58
+
56
59
  legs.each do |leg|
57
- leg_line_items = build_line_items_for!(leg, order, current_leg_date)
60
+ leg_line_items = build_line_items_for!(leg, order, current_leg_date, connected_trip_id)
58
61
  leg_line_items = insert_saved_guests_per_line_items_leg(leg_line_items)
59
62
 
60
63
  all_line_items.concat(leg_line_items)
@@ -64,7 +67,7 @@ module SpreeCmCommissioner
64
67
  all_line_items
65
68
  end
66
69
 
67
- def build_line_items_for!(leg, order, date)
70
+ def build_line_items_for!(leg, order, date, connected_trip_id = nil)
68
71
  trip = SpreeCmCommissioner::Trip.find(leg.trip_id)
69
72
  trip_stops = trip.trip_stops.where(id: [leg.boarding_trip_stop_id, leg.drop_off_trip_stop_id]).index_by(&:id)
70
73
 
@@ -75,18 +78,21 @@ module SpreeCmCommissioner
75
78
  )
76
79
 
77
80
  leg.seat_selections.group_by(&:variant_id).map do |variant_id, seat_selections|
81
+ metadata = {
82
+ direction: leg.direction,
83
+ trip_id: leg.trip_id.to_s,
84
+ boarding_trip_stop_id: leg.boarding_trip_stop_id.to_s,
85
+ drop_off_trip_stop_id: leg.drop_off_trip_stop_id.to_s
86
+ }
87
+ metadata[:connected_trip_id] = connected_trip_id if connected_trip_id.present?
88
+
78
89
  line_item = order.line_items.new(
79
90
  product_type: :transit,
80
91
  from_date: from_date,
81
92
  to_date: to_date,
82
93
  variant_id: variant_id,
83
94
  quantity: seat_selections.sum(&:quantity),
84
- private_metadata: {
85
- direction: leg.direction,
86
- trip_id: leg.trip_id.to_s,
87
- boarding_trip_stop_id: leg.boarding_trip_stop_id.to_s,
88
- drop_off_trip_stop_id: leg.drop_off_trip_stop_id.to_s
89
- }
95
+ private_metadata: metadata
90
96
  )
91
97
 
92
98
  build_guests_for!(line_item, seat_selections)
@@ -4,8 +4,6 @@
4
4
  <%= f.label :preset, Spree.t(:preset) %>
5
5
  </div>
6
6
  <small style="margin-bottom: 6px; display: inline-block;">
7
- Mark as <strong>preset</strong> to auto-assign this report to new organizers or vendors.
7
+ Preset reports are generated automatically for all new organizers and vendors once created.
8
8
  </small>
9
- <% end %>
10
-
11
-
9
+ <% end %>
@@ -63,6 +63,7 @@ module Spree
63
63
  :channel,
64
64
  :phone_number,
65
65
  :country_code,
66
+ :term_accepted_at,
66
67
  {
67
68
  saved_guests_attributes: %i[
68
69
  id
data/config/routes.rb CHANGED
@@ -703,6 +703,7 @@ Spree::Core::Engine.add_routes do
703
703
  resources :event_qrs, only: [:show]
704
704
  resources :recalculate_tickets, only: [:create]
705
705
  resources :check_in_sessions, only: %i[index]
706
+ resources :guest_json_gzips, only: %i[index show create]
706
707
 
707
708
  resources :taxons, only: %i[show] do
708
709
  resource :event_ticket_aggregators, only: %i[show]
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration to create cm_route_pricing_models table
4
+ # This table links pricing models to routes with specific origin/destination pairs
5
+ #
6
+ # Example: A route PP → KP → KPC → SR can have different pricing models for each O-D pair:
7
+ # - PP → SR: $1 extra charge
8
+ # - PP → KP: $2 extra charge
9
+ # - KP → KPC: $3 extra charge
10
+ # - KPC → SR: $4 extra charge
11
+ #
12
+ # When a user books PP → SR, only the PP → SR pricing model ($1) applies,
13
+ # not the sum of all segment charges
14
+
15
+ class CreateCmPricingModelRoutes < ActiveRecord::Migration[7.0]
16
+ def change
17
+ create_table :cm_pricing_model_routes, if_not_exists: true do |t|
18
+ t.references :route, null: false, foreign_key: { to_table: :cm_routes }
19
+ t.references :pricing_model, null: false, foreign_key: { to_table: :cm_pricing_models }
20
+ t.references :origin_place, null: false, foreign_key: { to_table: :cm_places }
21
+ t.references :destination_place, null: false, foreign_key: { to_table: :cm_places }
22
+
23
+ t.timestamps
24
+ end
25
+
26
+ # Ensure unique combination of route + pricing_model + origin + destination
27
+ add_index :cm_pricing_model_routes,
28
+ %i[route_id pricing_model_id origin_place_id destination_place_id],
29
+ unique: true,
30
+ name: 'idx_pricing_model_routes_unique'
31
+
32
+ # Index for efficient lookup by route and origin/destination
33
+ add_index :cm_pricing_model_routes,
34
+ %i[route_id origin_place_id destination_place_id],
35
+ name: 'idx_pricing_model_routes_on_route_origin_dest'
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ class AddExportableToCmExports < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_reference :cm_exports, :exportable, polymorphic: true, index: true
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class AddMetadataToCmExports < ActiveRecord::Migration[7.0]
2
+ def up
3
+ add_column :cm_exports, :public_metadata, :jsonb, default: '{}', if_not_exists: true
4
+ add_column :cm_exports, :private_metadata, :jsonb, default: '{}', if_not_exists: true
5
+ end
6
+
7
+ def down
8
+ remove_column :cm_exports, :public_metadata, if_exists: true
9
+ remove_column :cm_exports, :private_metadata, if_exists: true
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class RemovePreferencesFileNameColumnsFromCmExports < ActiveRecord::Migration[7.0]
2
+ def up
3
+ remove_column :cm_exports, :preferences, if_exists: true
4
+ remove_column :cm_exports, :file_name, if_exists: true
5
+ remove_column :cm_exports, :file_path, if_exists: true
6
+ end
7
+
8
+ def down
9
+ add_column :cm_exports, :preferences, :text, if_not_exists: true
10
+ add_column :cm_exports, :file_name, :string, if_not_exists: true
11
+ add_column :cm_exports, :file_path, :string, if_not_exists: true
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class AddTypeToCmExports < ActiveRecord::Migration[7.0]
2
+ def up
3
+ add_column :cm_exports, :type, :string, if_not_exists: true
4
+ remove_column :cm_exports, :export_type, if_exists: true
5
+ end
6
+
7
+ def down
8
+ add_column :cm_exports, :export_type, :string, if_not_exists: true
9
+ remove_column :cm_exports, :type, if_exists: true
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ class AddTermAcceptedAtToSpreeOrders < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :spree_orders, :term_accepted_at, :datetime, null: true, if_not_exists: true
4
+ add_index :spree_orders, :term_accepted_at, if_not_exists: true
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ module SpreeCmCommissioner
2
+ class CheckInSessionsMetric
3
+ attr_reader :check_ins_count, :guests_count, :percent, :other_value
4
+
5
+ def initialize(check_ins_count:, guests_count:)
6
+ @check_ins_count = check_ins_count
7
+ @guests_count = guests_count
8
+ @percent = calculate_attendance_percent
9
+ @other_value = 100 - @percent
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ check_ins_count: check_ins_count,
15
+ guests_count: guests_count,
16
+ no_show: no_show,
17
+ percent: percent,
18
+ other_value: other_value
19
+ }
20
+ end
21
+
22
+ def no_show
23
+ guests_count - check_ins_count
24
+ end
25
+
26
+ private
27
+
28
+ def calculate_attendance_percent
29
+ return 0 if guests_count.zero?
30
+
31
+ ((check_ins_count.to_f / guests_count) * 100).round(2)
32
+ end
33
+ end
34
+ end
@@ -6,4 +6,8 @@ FactoryBot.define do
6
6
  factory :pricing_action_guest_adjustments, class: 'SpreeCmCommissioner::PricingActions::CreateGuestAdjustments' do
7
7
  association :pricing_rule_group, factory: :pricing_rule_group
8
8
  end
9
+
10
+ factory :pricing_action_route_adjustment, class: 'SpreeCmCommissioner::PricingActions::CreateRouteAdjustments' do
11
+ association :pricing_rule_group, factory: :pricing_rule_group
12
+ end
9
13
  end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :cm_pricing_model_route, class: 'SpreeCmCommissioner::PricingModelRoute' do
3
+ association :pricing_model, factory: :cm_pricing_model
4
+ association :route, factory: :cm_route
5
+ association :origin_place, factory: :cm_place
6
+ association :destination_place, factory: :cm_place
7
+ end
8
+ end
@@ -1,10 +1,11 @@
1
1
  module SpreeCmCommissioner::Transit
2
2
  class Leg
3
- attr_accessor :direction, :trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id, :seat_selections
3
+ attr_accessor :direction, :trip_id, :main_trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id, :seat_selections
4
4
 
5
5
  def initialize(options = {})
6
6
  @direction = options[:direction]
7
7
  @trip_id = options[:trip_id]
8
+ @main_trip_id = options[:main_trip_id]
8
9
  @boarding_trip_stop_id = options[:boarding_trip_stop_id]
9
10
  @drop_off_trip_stop_id = options[:drop_off_trip_stop_id]
10
11
  @seat_selections = options[:seat_selections] || []
@@ -14,6 +15,7 @@ module SpreeCmCommissioner::Transit
14
15
  new(
15
16
  direction: hash[:direction], # outbound / inbound
16
17
  trip_id: hash[:trip_id],
18
+ main_trip_id: hash[:main_trip_id],
17
19
  boarding_trip_stop_id: hash[:boarding_trip_stop_id],
18
20
  drop_off_trip_stop_id: hash[:drop_off_trip_stop_id],
19
21
  seat_selections: (hash[:seat_selections] || []).map { |seat_selection| SeatSelection.from_hash(seat_selection) }
@@ -28,6 +30,7 @@ module SpreeCmCommissioner::Transit
28
30
  {
29
31
  direction: @direction,
30
32
  trip_id: @trip_id,
33
+ main_trip_id: @main_trip_id,
31
34
  boarding_trip_stop_id: @boarding_trip_stop_id,
32
35
  drop_off_trip_stop_id: @drop_off_trip_stop_id,
33
36
  seat_selections: @seat_selections.map(&:to_h)
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.5.8'.freeze
2
+ VERSION = '2.5.10'.freeze
3
3
 
4
4
  module_function
5
5
 
@@ -30,6 +30,7 @@ require 'spree_cm_commissioner/transit/trip_stop_form'
30
30
  require 'spree_cm_commissioner/transit/service_calendar_form'
31
31
  require 'spree_cm_commissioner/intercity_taxi/map_place'
32
32
  require 'spree_cm_commissioner/distance'
33
+ require 'spree_cm_commissioner/check_in_sessions_metric'
33
34
 
34
35
  require 'activerecord_multi_tenant'
35
36
  require 'google/cloud/recaptcha_enterprise'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_cm_commissioner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.8
4
+ version: 2.5.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-03 00:00:00.000000000 Z
11
+ date: 2026-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -910,6 +910,7 @@ files:
910
910
  - app/controllers/spree/api/v2/operator/event_charts_controller.rb
911
911
  - app/controllers/spree/api/v2/operator/event_qrs_controller.rb
912
912
  - app/controllers/spree/api/v2/operator/event_ticket_aggregators_controller.rb
913
+ - app/controllers/spree/api/v2/operator/guest_json_gzips_controller.rb
913
914
  - app/controllers/spree/api/v2/operator/guests_controller.rb
914
915
  - app/controllers/spree/api/v2/operator/line_items_controller.rb
915
916
  - app/controllers/spree/api/v2/operator/pie_chart_event_aggregators_controller.rb
@@ -1319,6 +1320,7 @@ files:
1319
1320
  - app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb
1320
1321
  - app/jobs/spree_cm_commissioner/ensure_event_for_product_line_item_guests_job.rb
1321
1322
  - app/jobs/spree_cm_commissioner/ensure_event_id_for_guests_job.rb
1323
+ - app/jobs/spree_cm_commissioner/export_job.rb
1322
1324
  - app/jobs/spree_cm_commissioner/firebase_email_fetcher_job.rb
1323
1325
  - app/jobs/spree_cm_commissioner/guests/preload_check_in_session_ids_job.rb
1324
1326
  - app/jobs/spree_cm_commissioner/import_order_job.rb
@@ -1444,6 +1446,7 @@ files:
1444
1446
  - app/models/spree_cm_commissioner/dynamic_field_option.rb
1445
1447
  - app/models/spree_cm_commissioner/event_ticket_google_wallet.rb
1446
1448
  - app/models/spree_cm_commissioner/export.rb
1449
+ - app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb
1447
1450
  - app/models/spree_cm_commissioner/feature_image.rb
1448
1451
  - app/models/spree_cm_commissioner/front_image.rb
1449
1452
  - app/models/spree_cm_commissioner/google_wallet.rb
@@ -1515,7 +1518,10 @@ files:
1515
1518
  - app/models/spree_cm_commissioner/pricing_action.rb
1516
1519
  - app/models/spree_cm_commissioner/pricing_actions/create_guest_adjustments.rb
1517
1520
  - app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb
1521
+ - app/models/spree_cm_commissioner/pricing_actions/create_route_adjustments.rb
1518
1522
  - app/models/spree_cm_commissioner/pricing_model.rb
1523
+ - app/models/spree_cm_commissioner/pricing_model_handler/order.rb
1524
+ - app/models/spree_cm_commissioner/pricing_model_route.rb
1519
1525
  - app/models/spree_cm_commissioner/pricing_model_variant.rb
1520
1526
  - app/models/spree_cm_commissioner/pricing_rule.rb
1521
1527
  - app/models/spree_cm_commissioner/pricing_rule_group.rb
@@ -1776,6 +1782,7 @@ files:
1776
1782
  - app/overrides/spree/layouts/admin/map_head.html.erb.deface
1777
1783
  - app/presenters/spree/variants/visible_options_presenter.rb
1778
1784
  - app/queries/spree_cm_commissioner/accommodation_query.rb
1785
+ - app/queries/spree_cm_commissioner/check_in_sessions_metrics_query.rb
1779
1786
  - app/queries/spree_cm_commissioner/dashboard_crew_event_query.rb
1780
1787
  - app/queries/spree_cm_commissioner/event_chart_queries.rb
1781
1788
  - app/queries/spree_cm_commissioner/event_ticket_aggregator_query.rb
@@ -1912,6 +1919,7 @@ files:
1912
1919
  - app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb
1913
1920
  - app/serializers/spree_cm_commissioner/v2/operator/event_qr_serializer.rb
1914
1921
  - app/serializers/spree_cm_commissioner/v2/operator/event_ticket_aggregator_serializer.rb
1922
+ - app/serializers/spree_cm_commissioner/v2/operator/export_serializer.rb
1915
1923
  - app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb
1916
1924
  - app/serializers/spree_cm_commissioner/v2/operator/line_item_serializer.rb
1917
1925
  - app/serializers/spree_cm_commissioner/v2/operator/order_serializer.rb
@@ -2043,9 +2051,9 @@ files:
2043
2051
  - app/services/spree_cm_commissioner/intercity_taxi_order/calculate_distance.rb
2044
2052
  - app/services/spree_cm_commissioner/intercity_taxi_order/create.rb
2045
2053
  - app/services/spree_cm_commissioner/intercity_taxi_order/update.rb
2046
- - app/services/spree_cm_commissioner/line_items/apply_pricing_models.rb
2047
2054
  - app/services/spree_cm_commissioner/line_items/sync_event_date.rb
2048
2055
  - app/services/spree_cm_commissioner/metafields/product_metadata_service.rb
2056
+ - app/services/spree_cm_commissioner/operator_guest_json_gzips/create.rb
2049
2057
  - app/services/spree_cm_commissioner/order_params_checker.rb
2050
2058
  - app/services/spree_cm_commissioner/orders/bulk_archive_inactive_orders.rb
2051
2059
  - app/services/spree_cm_commissioner/orders/daily_archive_inactive_orders.rb
@@ -2056,7 +2064,6 @@ files:
2056
2064
  - app/services/spree_cm_commissioner/organizer/export_invite_guest_csv_service.rb
2057
2065
  - app/services/spree_cm_commissioner/payment_method_type_mapper.rb
2058
2066
  - app/services/spree_cm_commissioner/penalty_calculator.rb
2059
- - app/services/spree_cm_commissioner/pricing_models/apply.rb
2060
2067
  - app/services/spree_cm_commissioner/pricing_models/create_with_rule_groups.rb
2061
2068
  - app/services/spree_cm_commissioner/pricing_models/update_with_rule_groups.rb
2062
2069
  - app/services/spree_cm_commissioner/pricing_rules/build_params.rb
@@ -2998,9 +3005,15 @@ files:
2998
3005
  - db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb
2999
3006
  - db/migrate/20260106093359_add_contact_phone_to_cm_vendor_places.rb
3000
3007
  - db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb
3008
+ - db/migrate/20260110073000_create_cm_pricing_model_routes.rb
3001
3009
  - db/migrate/20260121024645_add_nationality_group_to_cm_guests.rb
3002
3010
  - db/migrate/20260126110528_seed_user_initial_usernames.rb
3003
3011
  - db/migrate/20260128043540_add_counter_cache_to_spree_users.rb
3012
+ - db/migrate/20260128093305_add_exportable_to_cm_exports.rb
3013
+ - db/migrate/20260128093313_add_metadata_to_cm_exports.rb
3014
+ - db/migrate/20260129082338_remove_preferences_file_name_columns_from_cm_exports.rb
3015
+ - db/migrate/20260204092321_add_type_to_cm_exports.rb
3016
+ - db/migrate/20260204183545_add_term_accepted_at_to_spree_orders.rb
3004
3017
  - docker-compose.yml
3005
3018
  - docs/api/scoped-access-token-endpoints.md
3006
3019
  - docs/option_types/attr_types.md
@@ -3025,6 +3038,7 @@ files:
3025
3038
  - lib/spree_cm_commissioner.rb
3026
3039
  - lib/spree_cm_commissioner/cached_inventory_item.rb
3027
3040
  - lib/spree_cm_commissioner/calendar_event.rb
3041
+ - lib/spree_cm_commissioner/check_in_sessions_metric.rb
3028
3042
  - lib/spree_cm_commissioner/distance.rb
3029
3043
  - lib/spree_cm_commissioner/engine.rb
3030
3044
  - lib/spree_cm_commissioner/factories.rb
@@ -3081,6 +3095,7 @@ files:
3081
3095
  - lib/spree_cm_commissioner/test_helper/factories/place_factory.rb
3082
3096
  - lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb
3083
3097
  - lib/spree_cm_commissioner/test_helper/factories/pricing_model_factory.rb
3098
+ - lib/spree_cm_commissioner/test_helper/factories/pricing_model_route_factory.rb
3084
3099
  - lib/spree_cm_commissioner/test_helper/factories/pricing_model_variant_factory.rb
3085
3100
  - lib/spree_cm_commissioner/test_helper/factories/pricing_rule_factory.rb
3086
3101
  - lib/spree_cm_commissioner/test_helper/factories/pricing_rule_group_factory.rb
@@ -1,27 +0,0 @@
1
- module SpreeCmCommissioner
2
- module LineItems
3
- class ApplyPricingModels
4
- prepend ::Spree::ServiceModule::Base
5
-
6
- def call(order:, line_item:)
7
- return success(nil) if line_item.blank?
8
-
9
- return success(line_item) unless order&.persisted?
10
- return success(line_item) if line_item.variant.blank?
11
-
12
- active_pricing_models = line_item.variant.pricing_models.active
13
- return success(line_item) unless active_pricing_models.exists?
14
-
15
- line_item.adjustments.pricing_action.delete_all
16
-
17
- active_pricing_models.each do |pricing_model|
18
- SpreeCmCommissioner::PricingModels::Apply.new(
19
- line_item: line_item,
20
- pricing_model: pricing_model
21
- ).call
22
- end
23
- success(line_item)
24
- end
25
- end
26
- end
27
- end
@@ -1,43 +0,0 @@
1
- module SpreeCmCommissioner
2
- module PricingModels
3
- class Apply
4
- def initialize(line_item:, pricing_model:)
5
- @line_item = line_item
6
- @pricing_model = pricing_model
7
- end
8
-
9
- def call
10
- pricing_model.pricing_rule_groups.each do |group|
11
- apply_group(group, line_item)
12
- end
13
- end
14
-
15
- private
16
-
17
- attr_reader :line_item, :pricing_model
18
-
19
- def apply_group(group, line_item)
20
- eligible_rules = group.pricing_rules.select { |r| r.eligible?(line_item) }
21
-
22
- return if eligible_rules.empty?
23
-
24
- if group_eligible?(group, eligible_rules)
25
- group.pricing_action&.perform(line_item)
26
- else
27
- Rails.logger.info("Group #{group.id} not eligible for line_item #{line_item.id}")
28
- end
29
- end
30
-
31
- def group_eligible?(group, eligible_rules)
32
- case group.match_type
33
- when 'all'
34
- eligible_rules.size == group.pricing_rules.size
35
- when 'any'
36
- eligible_rules.any?
37
- else
38
- false
39
- end
40
- end
41
- end
42
- end
43
- end