spree_cm_commissioner 2.5.1.pre.pre1 → 2.5.1.pre.pre2

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/admin/guests_controller.rb +5 -5
  4. data/app/controllers/spree/api/chatrace/check_ins_controller.rb +6 -4
  5. data/app/controllers/spree/api/chatrace/guests_controller.rb +5 -18
  6. data/app/controllers/spree/api/v2/operator/check_in_bulks_controller.rb +6 -5
  7. data/app/controllers/spree/api/v2/operator/check_in_sessions_controller.rb +32 -0
  8. data/app/controllers/spree/api/v2/operator/check_ins_controller.rb +5 -5
  9. data/app/controllers/spree/api/v2/operator/event_qrs_controller.rb +2 -0
  10. data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +1 -1
  11. data/app/controllers/spree/api/v2/storefront/self_check_in_controller.rb +7 -7
  12. data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +4 -4
  13. data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +60 -0
  14. data/app/controllers/spree/api/v2/tenant/routes_controller.rb +50 -0
  15. data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +46 -0
  16. data/app/controllers/spree/events/guests_controller.rb +9 -10
  17. data/app/controllers/spree/transit/trips_controller.rb +3 -3
  18. data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
  19. data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +44 -0
  20. data/app/finders/spree_cm_commissioner/routes/find.rb +94 -0
  21. data/app/finders/spree_cm_commissioner/routes/find_popular.rb +20 -35
  22. data/app/interactors/spree_cm_commissioner/event_qr_generator.rb +2 -1
  23. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +11 -4
  24. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +10 -1
  25. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +4 -3
  26. data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
  27. data/app/jobs/spree_cm_commissioner/guests/preload_check_in_session_ids_job.rb +10 -0
  28. data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +10 -0
  29. data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +10 -0
  30. data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +10 -0
  31. data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +10 -0
  32. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
  33. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
  34. data/app/models/spree_cm_commissioner/check_in.rb +34 -7
  35. data/app/models/spree_cm_commissioner/check_in_session.rb +8 -3
  36. data/app/models/spree_cm_commissioner/guest.rb +60 -28
  37. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +2 -2
  38. data/app/models/spree_cm_commissioner/place.rb +5 -8
  39. data/app/models/spree_cm_commissioner/pricing_actions/create_guest_adjustments.rb +52 -0
  40. data/app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb +6 -0
  41. data/app/models/spree_cm_commissioner/pricing_rule.rb +4 -0
  42. data/app/models/spree_cm_commissioner/pricing_rules/age_group.rb +45 -0
  43. data/app/models/spree_cm_commissioner/pricing_rules/nationality.rb +21 -2
  44. data/app/models/spree_cm_commissioner/pricing_rules/nationality_group.rb +41 -0
  45. data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
  46. data/app/models/spree_cm_commissioner/route.rb +46 -5
  47. data/app/models/spree_cm_commissioner/route_metric.rb +21 -0
  48. data/app/models/spree_cm_commissioner/route_photo.rb +12 -0
  49. data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
  50. data/app/models/spree_cm_commissioner/trip.rb +8 -33
  51. data/app/models/spree_cm_commissioner/trip_stop.rb +16 -2
  52. data/app/models/spree_cm_commissioner/vendor_decorator.rb +3 -1
  53. data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
  54. data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +11 -0
  55. data/app/serializers/spree_cm_commissioner/v2/operator/check_in_serializer.rb +1 -2
  56. data/app/serializers/spree_cm_commissioner/v2/operator/check_in_session_serializer.rb +9 -0
  57. data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -1
  58. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +2 -1
  59. data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +3 -1
  60. data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +17 -0
  61. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
  62. data/app/services/spree_cm_commissioner/check_ins/create_bulk.rb +65 -0
  63. data/app/services/spree_cm_commissioner/check_ins/destroy_bulk.rb +54 -0
  64. data/app/services/spree_cm_commissioner/guests/preload_check_in_session_ids.rb +22 -0
  65. data/app/services/spree_cm_commissioner/pricing_models/create_with_rule_groups.rb +56 -0
  66. data/app/services/spree_cm_commissioner/pricing_models/update_with_rule_groups.rb +69 -0
  67. data/app/services/spree_cm_commissioner/pricing_rules/build_params.rb +16 -7
  68. data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +31 -0
  69. data/app/services/spree_cm_commissioner/{routes/increment_fulfilled_order_count.rb → route_metrics/increase_fulfilled_order_count.rb} +3 -3
  70. data/app/services/spree_cm_commissioner/{routes/increment_order_count.rb → route_metrics/increase_order_count.rb} +3 -3
  71. data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +31 -0
  72. data/app/services/spree_cm_commissioner/{routes/base_update_order_metrics.rb → route_metrics/update_route_metrics.rb} +11 -16
  73. data/app/services/spree_cm_commissioner/routes/create.rb +51 -0
  74. data/app/services/spree_cm_commissioner/routes/update.rb +25 -0
  75. data/app/{interactors/spree_cm_commissioner/transit/draft_order_creator.rb → services/spree_cm_commissioner/transit_order/create.rb} +13 -16
  76. data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +123 -0
  77. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +54 -0
  78. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +88 -0
  79. data/app/services/spree_cm_commissioner/trips/variants/create.rb +103 -0
  80. data/app/views/spree/transit/trip_stops/index.html.erb +4 -2
  81. data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +0 -1
  82. data/config/initializers/spree_permitted_attributes.rb +11 -0
  83. data/config/locales/en.yml +2 -0
  84. data/config/locales/km.yml +2 -0
  85. data/config/routes.rb +8 -0
  86. data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +17 -0
  87. data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +30 -0
  88. data/db/migrate/20251225100000_add_age_group_to_cm_guests.rb +6 -0
  89. data/db/migrate/20260105024742_add_type_to_cm_pricing_actions.rb +11 -0
  90. data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +12 -0
  91. data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +5 -0
  92. data/db/migrate/20260121024645_add_nationality_group_to_cm_guests.rb +5 -0
  93. data/docs/pricing_model/age_group.md +40 -0
  94. data/docs/pricing_model/nationality_group.md +35 -0
  95. data/lib/spree_cm_commissioner/test_helper/factories/check_in_factory.rb +3 -1
  96. data/lib/spree_cm_commissioner/test_helper/factories/check_in_session_factory.rb +26 -0
  97. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +4 -0
  98. data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +4 -0
  99. data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_factory.rb +45 -1
  100. data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +7 -6
  101. data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +12 -0
  102. data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +5 -0
  103. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +4 -1
  104. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +3 -1
  105. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +2 -0
  106. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +22 -0
  107. data/lib/spree_cm_commissioner/transit/route_stop.rb +61 -0
  108. data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +175 -0
  109. data/lib/spree_cm_commissioner/transit/trip_form.rb +81 -0
  110. data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +61 -0
  111. data/lib/spree_cm_commissioner/version.rb +1 -1
  112. data/lib/spree_cm_commissioner.rb +4 -0
  113. metadata +54 -20
  114. data/app/interactors/spree_cm_commissioner/check_in_bulk_creator.rb +0 -71
  115. data/app/interactors/spree_cm_commissioner/check_in_destroyer.rb +0 -43
  116. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +0 -10
  117. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +0 -10
  118. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +0 -13
  119. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +0 -10
  120. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +0 -10
  121. data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +0 -48
  122. data/app/models/spree_cm_commissioner/trip_connection.rb +0 -36
  123. data/app/models/spree_cm_commissioner/vendor_route.rb +0 -9
  124. data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +0 -30
  125. data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +0 -33
  126. data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +0 -33
  127. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +0 -6
@@ -0,0 +1,56 @@
1
+ module SpreeCmCommissioner
2
+ module PricingModels
3
+ class CreateWithRuleGroups
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(vendor:, params:, route: nil, origin_place_id: nil, destination_place_id: nil)
7
+ pricing_model = vendor.pricing_models.new(model_params(params))
8
+
9
+ ApplicationRecord.transaction do
10
+ pricing_model.save!
11
+ build_rule_groups(pricing_model, params[:rule_groups])
12
+ create_route_assignment(pricing_model, route, origin_place_id, destination_place_id) if route.present?
13
+ end
14
+
15
+ success(pricing_model)
16
+ rescue ActiveRecord::RecordInvalid => e
17
+ failure(pricing_model, e.message)
18
+ end
19
+
20
+ private
21
+
22
+ def model_params(params)
23
+ {
24
+ name: params[:name],
25
+ description: params[:description],
26
+ status: params[:status] || 'draft',
27
+ variant_ids: params[:variant_ids] || []
28
+ }
29
+ end
30
+
31
+ def build_rule_groups(pricing_model, rule_groups_params)
32
+ return if rule_groups_params.blank?
33
+
34
+ rule_groups_params.each do |template_key, group_params|
35
+ next if group_params.blank?
36
+
37
+ PricingRules::Create.call(
38
+ pricing_model: pricing_model,
39
+ params: group_params.merge(template_key: template_key)
40
+ )
41
+ end
42
+ end
43
+
44
+ def create_route_assignment(pricing_model, route, origin_place_id, destination_place_id)
45
+ return unless origin_place_id.present? && destination_place_id.present?
46
+
47
+ PricingModelRoute.create!(
48
+ route: route,
49
+ pricing_model: pricing_model,
50
+ origin_place_id: origin_place_id,
51
+ destination_place_id: destination_place_id
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,69 @@
1
+ module SpreeCmCommissioner
2
+ module PricingModels
3
+ class UpdateWithRuleGroups
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(pricing_model:, params:, route: nil, origin_place_id: nil, destination_place_id: nil)
7
+ ApplicationRecord.transaction do
8
+ pricing_model.update!(model_params(params))
9
+ sync_rule_groups(pricing_model, params[:rule_groups])
10
+ update_route_assignment(pricing_model, route, origin_place_id, destination_place_id) if route.present?
11
+ end
12
+
13
+ success(pricing_model)
14
+ rescue ActiveRecord::RecordInvalid => e
15
+ failure(pricing_model, e.message)
16
+ end
17
+
18
+ private
19
+
20
+ def model_params(params)
21
+ {
22
+ name: params[:name],
23
+ description: params[:description],
24
+ status: params[:status],
25
+ variant_ids: params[:variant_ids] || []
26
+ }.compact
27
+ end
28
+
29
+ def sync_rule_groups(pricing_model, rule_groups_params)
30
+ return if rule_groups_params.nil?
31
+
32
+ keep_ids = []
33
+
34
+ rule_groups_params.each do |template_key, group_params|
35
+ next if group_params.blank?
36
+
37
+ if group_params[:id].present?
38
+ group = pricing_model.pricing_rule_groups.find(group_params[:id])
39
+ PricingRules::Update.call(
40
+ pricing_rule_group: group,
41
+ params: group_params.merge(template_key: template_key)
42
+ )
43
+ keep_ids << group.id
44
+ else
45
+ new_group = PricingRules::Create.call(
46
+ pricing_model: pricing_model,
47
+ params: group_params.merge(template_key: template_key)
48
+ )
49
+ keep_ids << new_group.id if new_group.persisted?
50
+ end
51
+ end
52
+ pricing_model.pricing_rule_groups.where.not(id: keep_ids).destroy_all
53
+ end
54
+
55
+ def update_route_assignment(pricing_model, route, origin_place_id, destination_place_id)
56
+ return unless origin_place_id.present? && destination_place_id.present?
57
+
58
+ route_assignment = PricingModelRoute.find_or_initialize_by(
59
+ route: route,
60
+ pricing_model: pricing_model
61
+ )
62
+ route_assignment.update!(
63
+ origin_place_id: origin_place_id,
64
+ destination_place_id: destination_place_id
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
@@ -5,14 +5,16 @@ module SpreeCmCommissioner
5
5
 
6
6
  def permitted_group_params(params)
7
7
  group_params = params[:spree_cm_commissioner_pricing_rule_group] || params
8
+ template_key = group_params[:template_key] || params[:template_key]
9
+ template = PRICING_RULE_GROUP_TEMPLATES[template_key.to_sym] if template_key
8
10
 
9
11
  {
10
- name: group_params[:name],
11
- status: group_params[:status],
12
- template_key: group_params[:template_key],
13
- match_type: group_params[:match_type],
12
+ name: template&.dig(:name) || group_params[:name],
13
+ status: group_params[:status] || 'active',
14
+ template_key: template_key,
15
+ match_type: group_params[:match_type] || 'any',
14
16
  pricing_rules_attributes: build_rules_attributes(params[:pricing_rules]),
15
- pricing_action_attributes: build_action_attributes(params[:pricing_action])
17
+ pricing_action_attributes: build_action_attributes(params[:pricing_action], template)
16
18
  }
17
19
  end
18
20
 
@@ -29,14 +31,21 @@ module SpreeCmCommissioner
29
31
  end
30
32
  end
31
33
 
32
- def build_action_attributes(action_data)
34
+ def build_action_attributes(action_data, template = nil)
33
35
  return nil unless action_data&.dig(:calculator)
34
36
 
35
37
  calc_data = action_data[:calculator]
36
38
  calculator = calc_data[:type].safe_constantize&.new
39
+
37
40
  set_preferences(calculator, calc_data[:preferences]) if calculator
38
41
 
39
- { id: action_data[:id], calculator: calculator }
42
+ action_type = action_data[:type] || template&.dig(:action, :type)
43
+
44
+ {
45
+ id: action_data[:id],
46
+ type: action_type,
47
+ calculator: calculator
48
+ }
40
49
  end
41
50
 
42
51
  def set_preferences(calculator, preferences)
@@ -0,0 +1,31 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class DecreaseTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(trip:)
7
+ return failure(nil, 'Trip not found') unless trip
8
+
9
+ route_metric = find_or_create_route_metric(trip)
10
+
11
+ route_metric.with_lock do
12
+ route_metric.update!(trip_count: [route_metric.trip_count - 1, 0].max)
13
+ end
14
+
15
+ success(trip: trip)
16
+ rescue StandardError => e
17
+ failure(nil, e.message)
18
+ end
19
+
20
+ private
21
+
22
+ def find_or_create_route_metric(trip)
23
+ SpreeCmCommissioner::RouteMetric.find_or_create_by!(
24
+ origin_place_id: trip.origin_place_id,
25
+ destination_place_id: trip.destination_place_id,
26
+ route_type: trip.route_type
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,12 +1,12 @@
1
1
  module SpreeCmCommissioner
2
- module Routes
3
- class IncrementFulfilledOrderCount
2
+ module RouteMetrics
3
+ class IncreaseFulfilledOrderCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:)
7
7
  return failure(nil, 'Order not found') unless order
8
8
 
9
- SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
9
+ SpreeCmCommissioner::RouteMetrics::UpdateRouteMetrics
10
10
  .call(order: order, attribute: :fulfilled_order_count)
11
11
 
12
12
  success(order: order)
@@ -1,12 +1,12 @@
1
1
  module SpreeCmCommissioner
2
- module Routes
3
- class IncrementOrderCount
2
+ module RouteMetrics
3
+ class IncreaseOrderCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:)
7
7
  return failure(nil, 'Order not found') unless order
8
8
 
9
- SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
9
+ SpreeCmCommissioner::RouteMetrics::UpdateRouteMetrics
10
10
  .new(order: order, attribute: :order_count)
11
11
  .call
12
12
 
@@ -0,0 +1,31 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class IncreaseTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(trip:)
7
+ return failure(nil, 'Trip not found') unless trip
8
+
9
+ route_metric = find_or_create_route_metric(trip)
10
+
11
+ route_metric.with_lock do
12
+ route_metric.update!(trip_count: route_metric.trip_count + 1)
13
+ end
14
+
15
+ success(trip: trip)
16
+ rescue StandardError => e
17
+ failure(nil, e.message)
18
+ end
19
+
20
+ private
21
+
22
+ def find_or_create_route_metric(trip)
23
+ SpreeCmCommissioner::RouteMetric.find_or_create_by!(
24
+ origin_place_id: trip.origin_place_id,
25
+ destination_place_id: trip.destination_place_id,
26
+ route_type: trip.route_type
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module Routes
3
- class BaseUpdateOrderMetrics
2
+ module RouteMetrics
3
+ class UpdateRouteMetrics
4
4
  attr_reader :order, :attribute
5
5
 
6
6
  SUPPORTED_ATTRIBUTES = %i[order_count fulfilled_order_count].freeze
@@ -31,26 +31,21 @@ module SpreeCmCommissioner
31
31
  trip = product&.trip
32
32
  return unless trip
33
33
 
34
- route = find_or_create_route_for_trip(trip)
35
- ensure_trip_route(trip, route)
36
- increment_route_by(route, line_item.quantity)
34
+ route_metric = find_or_create_route_metric_for_trip(trip)
35
+ increment_route_by(route_metric, line_item.quantity)
36
+
37
+ route = trip.route
38
+ increment_route_by(route, line_item.quantity) if route
37
39
  end
38
40
 
39
- def find_or_create_route_for_trip(trip)
40
- SpreeCmCommissioner::Route.find_or_create_by(
41
+ def find_or_create_route_metric_for_trip(trip)
42
+ SpreeCmCommissioner::RouteMetric.find_or_create_by(
41
43
  origin_place_id: trip.origin_place_id,
42
- destination_place_id: trip.destination_place_id
44
+ destination_place_id: trip.destination_place_id,
45
+ route_type: trip.route_type
43
46
  )
44
47
  end
45
48
 
46
- def ensure_trip_route(trip, route)
47
- trip_route_id = trip.respond_to?(:route_id) ? trip.route_id : nil
48
-
49
- return unless trip.respond_to?(:update_column) && trip_route_id != route.id
50
-
51
- trip.update_column(:route_id, route.id) # rubocop:disable Rails/SkipsModelValidations
52
- end
53
-
54
49
  def increment_route_by(route, qty)
55
50
  route.with_lock do
56
51
  current_value = route.public_send(attribute) || 0
@@ -0,0 +1,51 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class Create
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ # @param vendor [Spree::Vendor]
7
+ # @param route_params [Hash]
8
+ # @param route_stops [SpreeCmCommissioner::Transit::RouteStopCollection]
9
+ def call(vendor:, route_params:, route_stops:)
10
+ # vendor_id, tenant_id, origin_place_id, destination_place_id are excluded and set automatically.
11
+ attrs = ::Spree::PermittedAttributes.route_attributes
12
+ .index_with { |attr_key| route_params[attr_key] }
13
+ .except(:vendor_id, :tenant_id, :route_stops, :origin_place_id, :destination_place_id)
14
+ .compact
15
+
16
+ return failure(nil, 'Route stops are required') if route_stops.length < 2
17
+
18
+ origin_place = find_place_for(route_stops, route_stops.first)
19
+ destination_place = find_place_for(route_stops, route_stops.last)
20
+
21
+ return failure(nil, 'Origin or destination place could not be determined') if origin_place.nil? || destination_place.nil?
22
+ return failure(nil, 'Route with origin & destination already exists') if vendor.routes.exists?(
23
+ origin_place_id: origin_place.id,
24
+ destination_place_id: destination_place.id
25
+ )
26
+
27
+ attrs = attrs.merge(
28
+ tenant_id: vendor.tenant_id,
29
+ route_name: attrs[:route_name] || "#{origin_place.name} -> #{destination_place.name}",
30
+ origin_place_id: origin_place.id,
31
+ destination_place_id: destination_place.id,
32
+ route_stops: route_stops
33
+ )
34
+
35
+ route = vendor.routes.new(attrs)
36
+ if route.save
37
+ success(route: route)
38
+ else
39
+ failure(route.errors)
40
+ end
41
+ end
42
+
43
+ def find_place_for(route_stops, route_stop)
44
+ return nil if route_stop.nil?
45
+
46
+ @locations ||= SpreeCmCommissioner::VendorPlace.where(id: route_stops.map(&:location_id).uniq).index_by(&:id)
47
+ @locations[route_stop.location_id]&.place
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class Update
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(vendor:, route:, route_params:)
7
+ # vendor_id, tenant_id, origin_place_id, destination_place_id are excluded and set automatically.
8
+ # route_stops is not updateable.
9
+ attrs = ::Spree::PermittedAttributes.route_attributes
10
+ .index_with { |attr_key| route_params[attr_key] }
11
+ .except(:vendor_id, :tenant_id, :route_stops, :origin_place_id, :destination_place_id)
12
+ .compact
13
+
14
+ route = vendor.routes.find_by(id: route.id)
15
+ return failure(nil, :route_not_found) if route.nil?
16
+
17
+ if route.update(attrs)
18
+ success(route: route)
19
+ else
20
+ failure(route.errors)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,4 @@
1
- # DraftOrderCreator creates a new order for a entire journey, including outbound and (if present) inbound directions.
1
+ # SpreeCmCommissioner::TransitOrder::Create creates a new order for a entire journey, including outbound and (if present) inbound directions.
2
2
  #
3
3
  # Attributes:
4
4
  # - outbound_date: [Date] The date of outbound transit
@@ -12,25 +12,22 @@
12
12
  #
13
13
  # Note: inbound_legs and inbound_date are optional; the order may represent a one-way (outbound only) journey.
14
14
  module SpreeCmCommissioner
15
- module Transit
16
- class DraftOrderCreator < BaseInteractor
17
- delegate :outbound_date,
18
- :inbound_date,
19
- :outbound_legs,
20
- :inbound_legs,
21
- :user, to: :context
15
+ module TransitOrder
16
+ class Create
17
+ prepend ::Spree::ServiceModule::Base
22
18
 
23
- def call
24
- return context.fail!(message: 'Outbound legs are missing') if outbound_legs.blank?
19
+ def call(outbound_date:, inbound_date:, outbound_legs:, inbound_legs: [], user: nil)
20
+ return failure(nil, 'Outbound legs are missing') if outbound_legs.blank?
25
21
 
26
22
  begin
27
- context.order = create_order!
23
+ order = create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user)
24
+ success(order: order)
28
25
  rescue StandardError => e
29
- context.fail!(message: e.message)
26
+ failure(nil, e.message)
30
27
  end
31
28
  end
32
29
 
33
- def create_order!
30
+ def create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user)
34
31
  order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
35
32
 
36
33
  outbound_line_items = build_line_items_for_legs!(order: order, legs: outbound_legs, initial_date: outbound_date)
@@ -112,10 +109,10 @@ module SpreeCmCommissioner
112
109
 
113
110
  def insert_saved_guests_per_line_items_leg(line_items)
114
111
  line_items.flat_map(&:guests).each_with_index do |guest, index|
115
- context.saved_guests ||= []
116
- context.saved_guests << SpreeCmCommissioner::SavedGuest.new if context.saved_guests[index].blank?
112
+ @saved_guests ||= []
113
+ @saved_guests << SpreeCmCommissioner::SavedGuest.new if @saved_guests[index].blank?
117
114
 
118
- guest.saved_guest = context.saved_guests[index]
115
+ guest.saved_guest = @saved_guests[index]
119
116
  end
120
117
 
121
118
  line_items
@@ -0,0 +1,123 @@
1
+ module SpreeCmCommissioner
2
+ module Trips
3
+ # Service class responsible for creating a single-leg trip in the booking system.
4
+ # Orchestrates the creation of product variant, trip record, stops, calendar, and inventory.
5
+ class CreateSingleLeg
6
+ prepend Spree::ServiceModule::Base
7
+
8
+ # Main service method that validates the trip form and orchestrates creation.
9
+ # Creates variant, trip, calendar, and inventory within a database transaction.
10
+ # Returns success with created objects or failure with error message on exceptions.
11
+ def call(vendor:, trip_form:)
12
+ return failure(nil, trip_form.errors.full_messages.to_sentence) unless trip_form.valid_data?
13
+
14
+ ApplicationRecord.transaction do
15
+ variant = create_variant!(vendor, trip_form)
16
+ trip = create_trip!(vendor, trip_form, variant)
17
+ calendar = setup_calendar!(variant.product, trip_form.service_calendar)
18
+ generate_inventory!(variant)
19
+
20
+ success(trip: trip, variant: variant, calendar: calendar)
21
+ rescue StandardError => e
22
+ CmAppLogger.error(
23
+ label: 'SpreeCmCommissioner::Trips::CreateSingleLeg#call',
24
+ data: {
25
+ error_class: e.class.name,
26
+ error_message: e.message
27
+ }
28
+ )
29
+ failure(nil, e.message)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # Creates a product variant for the trip using the vehicle type's seat capacity.
36
+ def create_variant!(vendor, trip_form)
37
+ first_stop = trip_form.trip_stops.first
38
+ vehicle_type = SpreeCmCommissioner::VehicleType.find(first_stop.vehicle_type_id)
39
+ capacity = vehicle_type.number_of_seats
40
+
41
+ result = SpreeCmCommissioner::Trips::Variants::Create.call(
42
+ vendor: vendor,
43
+ trip_form: trip_form,
44
+ price: trip_form.price || 0.0,
45
+ capacity: capacity
46
+ )
47
+
48
+ raise StandardError, result.error unless result.success?
49
+
50
+ result.value[:variant]
51
+ end
52
+
53
+ # Creates the main trip record with details extracted from the trip form.
54
+ # Associates the trip with vendor, product, vehicle, route, and time.
55
+ def create_trip!(vendor, trip_form, variant)
56
+ first_stop = trip_form.trip_stops.first
57
+ last_stop = trip_form.trip_stops.last
58
+
59
+ locations = vendor.locations.where(id: trip_form.trip_stops.map(&:location_id)).index_by(&:id)
60
+ stops = vendor.stops.where(id: trip_form.trip_stops.map(&:stop_id)).index_by(&:id)
61
+
62
+ trip = vendor.trips.create!(
63
+ product: variant.product,
64
+ vehicle_type: SpreeCmCommissioner::VehicleType.find_by(id: first_stop.vehicle_type_id),
65
+ vehicle: SpreeCmCommissioner::Vehicle.find_by(id: first_stop.vehicle_id),
66
+ origin_place: locations[first_stop.location_id].place,
67
+ destination_place: locations[last_stop.location_id].place,
68
+ route: vendor.routes.find(trip_form.route_id),
69
+ departure_time: first_stop.departure_time,
70
+ duration: trip_form.total_duration_seconds,
71
+ allow_booking: trip_form.allow_booking,
72
+ allow_seat_selection: trip_form.allow_seat_selection,
73
+ route_type: trip_form.route_type || vendor.routes.find(trip_form.route_id)&.route_type
74
+ )
75
+
76
+ create_trip_stops(trip, trip_form, locations, stops)
77
+ trip
78
+ end
79
+
80
+ # Creates individual trip stops based on the trip form stops.
81
+ # Each stop includes arrival/departure times and boarding/drop-off permissions.
82
+ def create_trip_stops(trip, trip_form, locations, stops)
83
+ previous_departure = nil
84
+
85
+ trip_form.trip_stops.each do |ts|
86
+ arrival_time = previous_departure || ts.departure_time
87
+
88
+ trip.trip_stops.create!(
89
+ stop_place: stops[ts.stop_id].place,
90
+ location_place: locations[ts.location_id].place,
91
+ allow_boarding: ts.allow_boarding?,
92
+ allow_drop_off: ts.allow_drop_off,
93
+ arrival_time: arrival_time,
94
+ departure_time: ts.departure_time
95
+ )
96
+
97
+ previous_departure = ts.departure_time
98
+ end
99
+ end
100
+
101
+ # Sets up the service calendar for the trip, defining operational dates, weekdays, and exceptions.
102
+ def setup_calendar!(product, calendar_form)
103
+ res = SpreeCmCommissioner::Trips::ServiceCalendars::CreateOrUpdate.call(
104
+ calendarable: product,
105
+ name: calendar_form.name,
106
+ start_date: calendar_form.start_date,
107
+ end_date: calendar_form.end_date,
108
+ weekdays: calendar_form.weekdays,
109
+ exception_rules: calendar_form.exception_rules || []
110
+ )
111
+
112
+ raise StandardError, res.error unless res.success?
113
+
114
+ res.value[:calendar]
115
+ end
116
+
117
+ # Triggers asynchronous generation of permanent inventory items for the variant.
118
+ def generate_inventory!(variant)
119
+ SpreeCmCommissioner::Stock::PermanentInventoryItemsGeneratorJob.perform_later(variant_ids: [variant[:id]])
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,54 @@
1
+ module SpreeCmCommissioner
2
+ module Trips
3
+ module ServiceCalendars
4
+ # Service to create or update a service calendar for a calendarable entity.
5
+ class CreateOrUpdate
6
+ prepend ::Spree::ServiceModule::Base
7
+
8
+ def call(calendarable:, name:, start_date:, end_date:, weekdays:, exception_rules: []) # rubocop:disable Metrics/ParameterLists
9
+ return failure(nil, 'calendarable must be present') if calendarable.blank?
10
+ return failure(nil, 'name must be present') if name.blank?
11
+ return failure(nil, 'start_date must be present') if start_date.blank?
12
+ return failure(nil, 'end_date must be present') if end_date.blank?
13
+ return failure(nil, 'weekdays must be present') if weekdays.blank?
14
+
15
+ ApplicationRecord.transaction do
16
+ calendar = create_or_update_calendar(calendarable, name, start_date, end_date, weekdays, exception_rules)
17
+ success(calendar: calendar)
18
+ end
19
+ rescue StandardError => e
20
+ CmAppLogger.error(
21
+ label: 'SpreeCmCommissioner::Trips::ServiceCalendars::CreateOrUpdate#call',
22
+ data: {
23
+ error_class: e.class.name,
24
+ error_message: e.message
25
+ }
26
+ )
27
+ failure(nil, 'Failed to create service calendar')
28
+ end
29
+
30
+ private
31
+
32
+ # Create or update calendar
33
+ def create_or_update_calendar(calendarable, name, start_date, end_date, weekdays, exception_rules) # rubocop:disable Metrics/ParameterLists
34
+ calendar = calendarable.service_calendar || calendarable.build_service_calendar
35
+
36
+ calendar.assign_attributes(
37
+ name: name,
38
+ start_date: start_date,
39
+ end_date: end_date,
40
+ exception_rules: exception_rules
41
+ )
42
+
43
+ # Set weekday attributes
44
+ weekdays.each do |day, enabled|
45
+ calendar.send("#{day}=", enabled)
46
+ end
47
+
48
+ calendar.save!
49
+ calendar
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end