spree_cm_commissioner 2.5.8 → 2.5.9
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/spree_cm_commissioner/orders_controller.rb +1 -1
- data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +5 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +14 -0
- data/app/models/spree_cm_commissioner/pricing_action.rb +21 -14
- data/app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb +14 -0
- data/app/models/spree_cm_commissioner/pricing_actions/create_route_adjustments.rb +70 -0
- data/app/models/spree_cm_commissioner/pricing_model.rb +39 -0
- data/app/models/spree_cm_commissioner/pricing_model_handler/order.rb +72 -0
- data/app/models/spree_cm_commissioner/pricing_model_route.rb +16 -0
- data/app/models/spree_cm_commissioner/route.rb +2 -0
- data/app/queries/spree_cm_commissioner/check_in_sessions_metrics_query.rb +24 -0
- data/app/serializers/spree/v2/storefront/cart_serializer_decorator.rb +1 -0
- data/app/services/spree_cm_commissioner/cart/recalculate_decorator.rb +0 -4
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +11 -1
- data/app/services/spree_cm_commissioner/orders/jwt_token/generate.rb +1 -1
- data/app/services/spree_cm_commissioner/transit/legs_builder_service.rb +2 -1
- data/app/services/spree_cm_commissioner/transit_order/create.rb +14 -8
- data/app/views/blazer/queries/_preset.html.erb +2 -4
- data/config/initializers/spree_permitted_attributes.rb +1 -0
- data/db/migrate/20260110073000_create_cm_pricing_model_routes.rb +37 -0
- data/db/migrate/20260204183545_add_term_accepted_at_to_spree_orders.rb +6 -0
- data/lib/spree_cm_commissioner/check_in_sessions_metric.rb +34 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +4 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_model_route_factory.rb +8 -0
- data/lib/spree_cm_commissioner/transit/leg.rb +4 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +1 -0
- metadata +10 -4
- data/app/services/spree_cm_commissioner/line_items/apply_pricing_models.rb +0 -27
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1e130ccfe9b9b83a2152d4a021b2505e3a4e8544144df805541e9655fe024f40
|
|
4
|
+
data.tar.gz: 715e29f413240f94d3eb6a6a7bc6810fb7a443da0acac4d654dae65e1fc4070b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15b4365a24963a3f2c427169331bf098728dcc12166d42878369a3060be530b880c8bf6678a35e90dfb2a81926615da425e6c1c334711bfe87906cd8216afe12
|
|
7
|
+
data.tar.gz: 374b79ae73eefe142f4358472e4800d069864c6efd83592a903203796bf6447ce1f0ac9ef369f4a78c3289e36cec47e49db111ee09335d61b011434e1ff6f7fb
|
data/Gemfile.lock
CHANGED
|
@@ -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::
|
|
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'
|
|
@@ -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
|
|
@@ -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
|
-
|
|
3
|
+
include Spree::CalculatedAdjustments
|
|
4
|
+
include Spree::AdjustmentSource
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
accepts_nested_attributes_for :calculator
|
|
6
|
+
has_many :adjustments, as: :source, class_name: 'Spree::Adjustment'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
return if pricing_rule_group.pricing_rules.blank?
|
|
8
|
+
belongs_to :pricing_rule_group, class_name: 'SpreeCmCommissioner::PricingRuleGroup'
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
delegate :pricing_rules, to: :pricing_rule_group
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
|
@@ -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
|
|
@@ -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:
|
|
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
|
|
@@ -5,7 +5,7 @@ module SpreeCmCommissioner
|
|
|
5
5
|
|
|
6
6
|
def call(order:)
|
|
7
7
|
nonce = generate_nonce
|
|
8
|
-
token = "#{order.number}?
|
|
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
|
-
|
|
7
|
+
Preset reports are generated automatically for all new organizers and vendors once created.
|
|
8
8
|
</small>
|
|
9
|
-
<% end %>
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<% end %>
|
|
@@ -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,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)
|
|
@@ -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.
|
|
4
|
+
version: 2.5.9
|
|
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-
|
|
11
|
+
date: 2026-02-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|
|
@@ -1515,7 +1515,10 @@ files:
|
|
|
1515
1515
|
- app/models/spree_cm_commissioner/pricing_action.rb
|
|
1516
1516
|
- app/models/spree_cm_commissioner/pricing_actions/create_guest_adjustments.rb
|
|
1517
1517
|
- app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb
|
|
1518
|
+
- app/models/spree_cm_commissioner/pricing_actions/create_route_adjustments.rb
|
|
1518
1519
|
- app/models/spree_cm_commissioner/pricing_model.rb
|
|
1520
|
+
- app/models/spree_cm_commissioner/pricing_model_handler/order.rb
|
|
1521
|
+
- app/models/spree_cm_commissioner/pricing_model_route.rb
|
|
1519
1522
|
- app/models/spree_cm_commissioner/pricing_model_variant.rb
|
|
1520
1523
|
- app/models/spree_cm_commissioner/pricing_rule.rb
|
|
1521
1524
|
- app/models/spree_cm_commissioner/pricing_rule_group.rb
|
|
@@ -1776,6 +1779,7 @@ files:
|
|
|
1776
1779
|
- app/overrides/spree/layouts/admin/map_head.html.erb.deface
|
|
1777
1780
|
- app/presenters/spree/variants/visible_options_presenter.rb
|
|
1778
1781
|
- app/queries/spree_cm_commissioner/accommodation_query.rb
|
|
1782
|
+
- app/queries/spree_cm_commissioner/check_in_sessions_metrics_query.rb
|
|
1779
1783
|
- app/queries/spree_cm_commissioner/dashboard_crew_event_query.rb
|
|
1780
1784
|
- app/queries/spree_cm_commissioner/event_chart_queries.rb
|
|
1781
1785
|
- app/queries/spree_cm_commissioner/event_ticket_aggregator_query.rb
|
|
@@ -2043,7 +2047,6 @@ files:
|
|
|
2043
2047
|
- app/services/spree_cm_commissioner/intercity_taxi_order/calculate_distance.rb
|
|
2044
2048
|
- app/services/spree_cm_commissioner/intercity_taxi_order/create.rb
|
|
2045
2049
|
- app/services/spree_cm_commissioner/intercity_taxi_order/update.rb
|
|
2046
|
-
- app/services/spree_cm_commissioner/line_items/apply_pricing_models.rb
|
|
2047
2050
|
- app/services/spree_cm_commissioner/line_items/sync_event_date.rb
|
|
2048
2051
|
- app/services/spree_cm_commissioner/metafields/product_metadata_service.rb
|
|
2049
2052
|
- app/services/spree_cm_commissioner/order_params_checker.rb
|
|
@@ -2056,7 +2059,6 @@ files:
|
|
|
2056
2059
|
- app/services/spree_cm_commissioner/organizer/export_invite_guest_csv_service.rb
|
|
2057
2060
|
- app/services/spree_cm_commissioner/payment_method_type_mapper.rb
|
|
2058
2061
|
- app/services/spree_cm_commissioner/penalty_calculator.rb
|
|
2059
|
-
- app/services/spree_cm_commissioner/pricing_models/apply.rb
|
|
2060
2062
|
- app/services/spree_cm_commissioner/pricing_models/create_with_rule_groups.rb
|
|
2061
2063
|
- app/services/spree_cm_commissioner/pricing_models/update_with_rule_groups.rb
|
|
2062
2064
|
- app/services/spree_cm_commissioner/pricing_rules/build_params.rb
|
|
@@ -2998,9 +3000,11 @@ files:
|
|
|
2998
3000
|
- db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb
|
|
2999
3001
|
- db/migrate/20260106093359_add_contact_phone_to_cm_vendor_places.rb
|
|
3000
3002
|
- db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb
|
|
3003
|
+
- db/migrate/20260110073000_create_cm_pricing_model_routes.rb
|
|
3001
3004
|
- db/migrate/20260121024645_add_nationality_group_to_cm_guests.rb
|
|
3002
3005
|
- db/migrate/20260126110528_seed_user_initial_usernames.rb
|
|
3003
3006
|
- db/migrate/20260128043540_add_counter_cache_to_spree_users.rb
|
|
3007
|
+
- db/migrate/20260204183545_add_term_accepted_at_to_spree_orders.rb
|
|
3004
3008
|
- docker-compose.yml
|
|
3005
3009
|
- docs/api/scoped-access-token-endpoints.md
|
|
3006
3010
|
- docs/option_types/attr_types.md
|
|
@@ -3025,6 +3029,7 @@ files:
|
|
|
3025
3029
|
- lib/spree_cm_commissioner.rb
|
|
3026
3030
|
- lib/spree_cm_commissioner/cached_inventory_item.rb
|
|
3027
3031
|
- lib/spree_cm_commissioner/calendar_event.rb
|
|
3032
|
+
- lib/spree_cm_commissioner/check_in_sessions_metric.rb
|
|
3028
3033
|
- lib/spree_cm_commissioner/distance.rb
|
|
3029
3034
|
- lib/spree_cm_commissioner/engine.rb
|
|
3030
3035
|
- lib/spree_cm_commissioner/factories.rb
|
|
@@ -3081,6 +3086,7 @@ files:
|
|
|
3081
3086
|
- lib/spree_cm_commissioner/test_helper/factories/place_factory.rb
|
|
3082
3087
|
- lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb
|
|
3083
3088
|
- lib/spree_cm_commissioner/test_helper/factories/pricing_model_factory.rb
|
|
3089
|
+
- lib/spree_cm_commissioner/test_helper/factories/pricing_model_route_factory.rb
|
|
3084
3090
|
- lib/spree_cm_commissioner/test_helper/factories/pricing_model_variant_factory.rb
|
|
3085
3091
|
- lib/spree_cm_commissioner/test_helper/factories/pricing_rule_factory.rb
|
|
3086
3092
|
- 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
|