spree_cm_commissioner 2.3.2 → 2.4.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/api/v2/storefront/intercity_taxi/draft_orders_controller.rb +52 -11
  4. data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +63 -0
  5. data/app/controllers/spree/api/v2/storefront/route_places_controller.rb +68 -0
  6. data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +81 -0
  7. data/app/controllers/spree/api/v2/tenant/trip_places_controller.rb +42 -0
  8. data/app/controllers/spree/api/v2/tenant/trip_search_controller.rb +103 -0
  9. data/app/controllers/spree/api/v2/tenant/trips_controller.rb +32 -0
  10. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +12 -0
  11. data/app/finders/spree_cm_commissioner/places/find_with_route.rb +43 -0
  12. data/app/finders/spree_cm_commissioner/routes/find_popular.rb +25 -0
  13. data/app/mailers/spree/order_mailer_decorator.rb +8 -6
  14. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +34 -89
  15. data/app/models/concerns/spree_cm_commissioner/service_recommendations.rb +2 -2
  16. data/app/models/spree_cm_commissioner/adjustment_decorator.rb +1 -1
  17. data/app/models/spree_cm_commissioner/calculators/extra_drop_off_distance.rb +15 -0
  18. data/app/models/spree_cm_commissioner/calculators/extra_pick_up_distance.rb +15 -0
  19. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -1
  20. data/app/models/spree_cm_commissioner/place.rb +7 -0
  21. data/app/models/spree_cm_commissioner/pricing_action.rb +27 -0
  22. data/app/models/spree_cm_commissioner/pricing_model.rb +18 -0
  23. data/app/models/spree_cm_commissioner/pricing_model_variant.rb +6 -0
  24. data/app/models/spree_cm_commissioner/pricing_rule.rb +15 -0
  25. data/app/models/spree_cm_commissioner/pricing_rule_group.rb +20 -0
  26. data/app/models/spree_cm_commissioner/pricing_rules/extra_drop_off_distance.rb +10 -0
  27. data/app/models/spree_cm_commissioner/pricing_rules/extra_pick_up_distance.rb +12 -0
  28. data/app/models/spree_cm_commissioner/pricing_rules/nationality.rb +35 -0
  29. data/app/models/spree_cm_commissioner/tenant.rb +4 -0
  30. data/app/models/spree_cm_commissioner/variant_decorator.rb +3 -0
  31. data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -0
  32. data/app/models/spree_cm_commissioner/vendor_place.rb +9 -0
  33. data/app/queries/spree_cm_commissioner/trip_query.rb +23 -17
  34. data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_schema.rb +10 -0
  35. data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_update_schema.rb +29 -0
  36. data/app/request_schemas/spree_cm_commissioner/route_places_request_schema.rb +12 -0
  37. data/app/serializers/spree/v2/storefront/place_serializer.rb +3 -2
  38. data/app/serializers/spree/v2/tenant/cart_serializer.rb +1 -0
  39. data/app/serializers/spree/v2/tenant/intercity_taxi_cart_serializer.rb +12 -0
  40. data/app/serializers/spree/v2/tenant/line_item_serializer.rb +1 -0
  41. data/app/serializers/spree/v2/tenant/trip_place_serializer.rb +8 -0
  42. data/app/serializers/spree/v2/tenant/trip_query_result_serializer.rb +8 -0
  43. data/app/serializers/spree/v2/tenant/trip_serializer.rb +8 -0
  44. data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_line_item_serializer.rb +17 -18
  45. data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +12 -0
  46. data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +20 -23
  47. data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +36 -72
  48. data/app/services/spree_cm_commissioner/pricing_models/apply.rb +51 -0
  49. data/app/services/spree_cm_commissioner/pricing_rules/build_params.rb +53 -0
  50. data/app/services/spree_cm_commissioner/pricing_rules/build_template.rb +27 -0
  51. data/app/services/spree_cm_commissioner/pricing_rules/create.rb +10 -0
  52. data/app/services/spree_cm_commissioner/pricing_rules/update.rb +11 -0
  53. data/app/services/spree_cm_commissioner/vendor_places/base.rb +24 -0
  54. data/app/services/spree_cm_commissioner/vendor_places/bulk_create.rb +70 -0
  55. data/app/services/spree_cm_commissioner/vendor_places/create_with_google_map_data.rb +85 -0
  56. data/app/services/spree_cm_commissioner/vendor_places/update.rb +56 -0
  57. data/app/views/spree/order_mailer/confirm_email.html.erb +1 -1
  58. data/app/views/spree_cm_commissioner/order_mailer/_mailer_stylesheets.html.erb +14 -13
  59. data/app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb +6 -4
  60. data/config/routes.rb +11 -1
  61. data/db/migrate/20250915075830_create_spree_cm_commissioner_pricing_models.rb +17 -0
  62. data/db/migrate/20250915080733_create_spree_cm_commissioner_pricing_model_variants.rb +14 -0
  63. data/db/migrate/20251003082144_create_spree_cm_commissioner_pricing_rule_groups.rb +16 -0
  64. data/db/migrate/20251003093408_create_spree_cm_commissioner_pricing_rule.rb +19 -0
  65. data/db/migrate/20251003093643_create_spree_cm_commissioner_pricing_action.rb +14 -0
  66. data/db/migrate/20251119093000_add_tenant_id_to_cm_vendor_places.rb +8 -0
  67. data/lib/spree_cm_commissioner/intercity_taxi/distance.rb +38 -0
  68. data/lib/spree_cm_commissioner/intercity_taxi/map_place.rb +31 -0
  69. data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +5 -0
  70. data/lib/spree_cm_commissioner/test_helper/factories/pricing_model_factory.rb +8 -0
  71. data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_factory.rb +17 -0
  72. data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_group_factory.rb +8 -0
  73. data/lib/spree_cm_commissioner/version.rb +1 -1
  74. data/lib/spree_cm_commissioner.rb +2 -0
  75. metadata +49 -2
@@ -0,0 +1,8 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class TripSerializer < SpreeCmCommissioner::V2::Storefront::TripSerializer
5
+ end
6
+ end
7
+ end
8
+ end
@@ -2,29 +2,28 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class IntercityTaxiLineItemSerializer < BaseSerializer
5
- attributes :pickup_place_name,
6
- :drop_off_place_name,
7
- :pickup_lat,
8
- :pickup_lng,
9
- :drop_off_lat,
10
- :drop_off_lng,
11
- :passenger_count,
12
- :distance_km,
13
- :ordered_points,
14
- :base_km,
15
- :detour_pickup_km,
16
- :detour_dropoff_km,
17
- :extra_pickup_km,
18
- :extra_dropoff_km,
19
- :extra_pickup_charge_usd,
20
- :extra_dropoff_charge_usd,
21
- :estimated_time_minutes,
5
+ # Nested-only API shape
6
+ attributes :pickup_map_place,
7
+ :dropoff_map_place,
8
+ :distance,
22
9
  :from_date,
23
10
  :to_date,
24
- :direction,
11
+ :remark,
25
12
  :trip_id
26
13
 
27
14
  cache_options store: nil
15
+
16
+ def pickup_map_place
17
+ object.pickup_map_place&.to_h
18
+ end
19
+
20
+ def dropoff_map_place
21
+ object.dropoff_map_place&.to_h
22
+ end
23
+
24
+ def distance
25
+ object.distance&.to_h
26
+ end
28
27
  end
29
28
  end
30
29
  end
@@ -0,0 +1,12 @@
1
+ module SpreeCmCommissioner
2
+ module V2
3
+ module Storefront
4
+ class RouteSerializer < BaseSerializer
5
+ attributes :route_name
6
+
7
+ has_one :origin_place, serializer: ::Spree::V2::Storefront::PlaceSerializer
8
+ has_one :destination_place, serializer: ::Spree::V2::Storefront::PlaceSerializer
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,35 +1,19 @@
1
1
  module SpreeCmCommissioner
2
2
  module IntercityTaxiOrder
3
3
  class Create
4
- attr_reader :trip_id, :from_date, :to_date, :user_id, :quantity
4
+ prepend ::Spree::ServiceModule::Base
5
5
 
6
- def self.call(trip_id:, from_date:, to_date:, user_id: nil, quantity: 1)
7
- new(
8
- trip_id: trip_id,
9
- from_date: from_date,
10
- to_date: to_date,
11
- user_id: user_id,
12
- quantity: quantity
13
- ).call
14
- end
15
-
16
- def initialize(trip_id:, from_date:, to_date:, user_id:, quantity:)
17
- @trip_id = trip_id
18
- @from_date = from_date
19
- @to_date = to_date
20
- @user_id = user_id
21
- @quantity = quantity.to_i <= 0 ? 1 : quantity.to_i
22
- end
6
+ def call(trip_id:, from_date:, to_date:, user: nil, quantity: 1)
7
+ validate!(trip_id: trip_id, from_date: from_date, to_date: to_date, quantity: quantity)
23
8
 
24
- def call
25
- validate!
9
+ quantity = quantity.to_i
26
10
 
27
- ActiveRecord::Base.transaction do
11
+ order = ActiveRecord::Base.transaction do
28
12
  trip = SpreeCmCommissioner::Trip.find(trip_id)
29
13
  variant = trip.product.variants.first
30
14
  raise ArgumentError, 'No variant found for trip' if variant.blank?
31
15
 
32
- order = Spree::Order.new(state: 'cart', use_billing: true, user_id: user_id)
16
+ order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
33
17
 
34
18
  line_item = order.line_items.new(
35
19
  variant_id: variant.id,
@@ -42,26 +26,39 @@ module SpreeCmCommissioner
42
26
  )
43
27
 
44
28
  build_guests_for!(line_item, quantity)
29
+ assign_saved_guests_to_line_item(line_item)
45
30
 
46
31
  order.save!
47
32
  order.update_with_updater!
48
33
 
49
34
  order
50
35
  end
36
+
37
+ success(order)
38
+ rescue StandardError => e
39
+ failure(nil, e.message)
51
40
  end
52
41
 
53
42
  private
54
43
 
55
- def validate!
44
+ def validate!(trip_id:, from_date:, to_date:, quantity:)
56
45
  raise ArgumentError, 'trip_id is required' if trip_id.blank?
57
46
  raise ArgumentError, 'from_date is required' if from_date.blank?
58
47
  raise ArgumentError, 'to_date is required' if to_date.blank?
59
48
  raise ArgumentError, 'to_date must be after from_date' if from_date.present? && to_date.present? && to_date < from_date
49
+ raise ArgumentError, 'quantity must be 1' if quantity.to_i != 1
60
50
  end
61
51
 
62
52
  def build_guests_for!(line_item, qty)
63
53
  Array.new(qty) { line_item.guests.new }
64
54
  end
55
+
56
+ def assign_saved_guests_to_line_item(line_item)
57
+ line_item.guests.each do |guest|
58
+ saved_guest = SpreeCmCommissioner::SavedGuest.new
59
+ guest.saved_guest = saved_guest
60
+ end
61
+ end
65
62
  end
66
63
  end
67
64
  end
@@ -1,78 +1,42 @@
1
1
  module SpreeCmCommissioner
2
2
  module IntercityTaxiOrder
3
3
  class Update
4
- attr_reader :order_number, :params
5
-
6
- def self.call(order_number:, params:)
7
- new(order_number: order_number, params: params).call
8
- end
9
-
10
- def initialize(order_number:, params:)
11
- @order_number = order_number
12
- @params = ensure_parameters(params)
13
- end
14
-
15
- def call
16
- order = Spree::Order.find_by!(number: order_number)
17
- order.update!(order_params)
18
-
19
- create_billing_address_if_missing(order)
20
-
21
- order
22
- end
23
-
24
- private
25
-
26
- def ensure_parameters(params)
27
- return params if params.is_a?(::ActionController::Parameters)
28
-
29
- ::ActionController::Parameters.new(params)
30
- end
31
-
32
- def create_billing_address_if_missing(order)
33
- return if order.bill_address.present?
34
-
35
- SpreeCmCommissioner::BillingAddressCreator.call(order: order)
36
- end
37
-
38
- def order_params
39
- params.require(:order).permit(
40
- :intel_phone_number,
41
- :email,
42
- line_items_attributes: [
43
- :id,
44
- :pickup_place_name,
45
- :drop_off_place_name,
46
- :from_date,
47
- :pickup_lat,
48
- :pickup_lng,
49
- :pickup_oob_confirmed,
50
- :drop_off_lat,
51
- :drop_off_lng,
52
- :drop_off_oob_confirmed,
53
- :distance_km,
54
- :ordered_points,
55
- :base_km,
56
- :detour_pickup_km,
57
- :detour_dropoff_km,
58
- :extra_pickup_km,
59
- :extra_dropoff_km,
60
- :extra_pickup_charge_usd,
61
- :extra_dropoff_charge_usd,
62
- :estimated_time_minutes,
63
- :remark,
64
- :passenger_count,
65
- { guests_attributes: %i[
66
- id
67
- first_name
68
- last_name
69
- gender
70
- nationality_id
71
- age
72
- ]
73
- }
74
- ]
75
- )
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(
7
+ order:,
8
+ remark: nil,
9
+ pickup_map_place_attributes: nil,
10
+ dropoff_map_place_attributes: nil,
11
+ distance_attributes: nil
12
+ )
13
+ line_item = order.line_items.first
14
+
15
+ raise ArgumentError, 'Order has no line items' if line_item.blank?
16
+
17
+ if pickup_map_place_attributes.present?
18
+ line_item.pickup_map_place = SpreeCmCommissioner::IntercityTaxi::MapPlace.new(
19
+ pickup_map_place_attributes
20
+ )
21
+ end
22
+
23
+ if dropoff_map_place_attributes.present?
24
+ line_item.dropoff_map_place = SpreeCmCommissioner::IntercityTaxi::MapPlace.new(
25
+ dropoff_map_place_attributes
26
+ )
27
+ end
28
+
29
+ if distance_attributes.present?
30
+ line_item.distance = SpreeCmCommissioner::IntercityTaxi::Distance.new(
31
+ distance_attributes
32
+ )
33
+ end
34
+
35
+ line_item.update!(remark: remark)
36
+
37
+ success(order.reload)
38
+ rescue StandardError => e
39
+ failure(nil, e.message)
76
40
  end
77
41
  end
78
42
  end
@@ -0,0 +1,51 @@
1
+ module SpreeCmCommissioner
2
+ module PricingModels
3
+ class Apply
4
+ def initialize(line_item_id:, pricing_model_id:)
5
+ @line_item_id = line_item_id
6
+ @pricing_model_id = pricing_model_id
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_id, :pricing_model_id
18
+
19
+ def line_item
20
+ @line_item ||= Spree::LineItem.find(line_item_id)
21
+ end
22
+
23
+ def pricing_model
24
+ @pricing_model ||= SpreeCmCommissioner::PricingModel.find(pricing_model_id)
25
+ end
26
+
27
+ def apply_group(group, line_item)
28
+ eligible_rules = group.pricing_rules.select { |r| r.eligible?(line_item) }
29
+
30
+ return if eligible_rules.empty?
31
+
32
+ if group_eligible?(group, eligible_rules)
33
+ group.pricing_action&.perform(line_item)
34
+ else
35
+ Rails.logger.info("Group #{group.id} not eligible for line_item #{line_item.id}")
36
+ end
37
+ end
38
+
39
+ def group_eligible?(group, eligible_rules)
40
+ case group.match_type
41
+ when 'all'
42
+ eligible_rules.size == group.pricing_rules.size
43
+ when 'any'
44
+ eligible_rules.any?
45
+ else
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ module SpreeCmCommissioner
2
+ module PricingRules
3
+ module BuildParams
4
+ module_function
5
+
6
+ def permitted_group_params(params)
7
+ group_params = params[:spree_cm_commissioner_pricing_rule_group] || params
8
+
9
+ {
10
+ name: group_params[:name],
11
+ status: group_params[:status],
12
+ template_key: group_params[:template_key],
13
+ match_type: group_params[:match_type],
14
+ pricing_rules_attributes: build_rules_attributes(params[:pricing_rules]),
15
+ pricing_action_attributes: build_action_attributes(params[:pricing_action])
16
+ }
17
+ end
18
+
19
+ def build_rules_attributes(rules_array)
20
+ return [] unless rules_array
21
+
22
+ rules_array.map do |r|
23
+ {
24
+ id: r[:id],
25
+ type: r[:type],
26
+ private_metadata: r[:private_metadata],
27
+ _destroy: r[:_destroy]
28
+ }
29
+ end
30
+ end
31
+
32
+ def build_action_attributes(action_data)
33
+ return nil unless action_data&.dig(:calculator)
34
+
35
+ calc_data = action_data[:calculator]
36
+ calculator = calc_data[:type].safe_constantize&.new
37
+ set_preferences(calculator, calc_data[:preferences]) if calculator
38
+
39
+ { id: action_data[:id], calculator: calculator }
40
+ end
41
+
42
+ def set_preferences(calculator, preferences)
43
+ return unless preferences && calculator
44
+
45
+ prefs = preferences.respond_to?(:to_unsafe_h) ? preferences.to_unsafe_h : preferences
46
+ prefs.each do |key, value|
47
+ setter = "preferred_#{key}="
48
+ calculator.public_send(setter, value) if calculator.respond_to?(setter)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+ module SpreeCmCommissioner
2
+ module PricingRules
3
+ class BuildTemplate
4
+ def self.call(pricing_model:, template_key:)
5
+ template = PRICING_RULE_GROUP_TEMPLATES[template_key.to_sym]
6
+ return unless template
7
+
8
+ group = pricing_model.pricing_rule_groups.build
9
+
10
+ template[:rules]&.each do |rule_data|
11
+ group.pricing_rules.where(type: rule_data[:type]).build(
12
+ type: rule_data[:type],
13
+ private_metadata: rule_data[:private_metadata] || {}
14
+ )
15
+ end
16
+
17
+ if template[:action]
18
+ calculator = template[:action][:calculator_type].constantize.new
19
+ BuildParams.set_preferences(calculator, template[:action][:preferences])
20
+ group.build_pricing_action(calculator: calculator)
21
+ end
22
+
23
+ group
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module PricingRules
3
+ class Create
4
+ def self.call(pricing_model:, params:)
5
+ attrs = BuildParams.permitted_group_params(params)
6
+ pricing_model.pricing_rule_groups.create(attrs)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module PricingRules
3
+ class Update
4
+ def self.call(pricing_rule_group:, params:)
5
+ attrs = BuildParams.permitted_group_params(params)
6
+ pricing_rule_group.update(attrs)
7
+ pricing_rule_group
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module SpreeCmCommissioner
2
+ module VendorPlaces
3
+ class Base
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ ALLOWED_PLACE_TYPES = %w[venue stop branch location].freeze
7
+
8
+ private
9
+
10
+ def validate_place_type!(place_type)
11
+ return I18n.t('controller.vendor_places.place_type_required', default: 'Place type is required') if place_type.nil?
12
+
13
+ unless ALLOWED_PLACE_TYPES.include?(place_type.to_s)
14
+ return I18n.t(
15
+ 'controller.vendor_places.invalid_place_type',
16
+ default: "Place type must be one of: #{ALLOWED_PLACE_TYPES.join(', ')}"
17
+ )
18
+ end
19
+
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,70 @@
1
+ # Bulk creates vendor places of any valid type (e.g., :stop, :branch, :location) with validation and transaction support.
2
+ #
3
+ # Validates place_ids count and creates vendor places atomically.
4
+ # Rolls back all changes if any creation fails.
5
+ #
6
+ # Usage:
7
+ # result = SpreeCmCommissioner::VendorPlaces::BulkCreate.call(
8
+ # vendor: current_vendor,
9
+ # place_ids: [1, 2, 3],
10
+ # place_type: :location, # or :stop, :branch, etc.
11
+ # description: "Sample description",
12
+ # max_count: 10,
13
+ # location: parent_location # required for :stop and :branch
14
+ # )
15
+ #
16
+ # if result.success?
17
+ # created_vendor_places = result.value
18
+ # else
19
+ # error_message = result.error
20
+
21
+ module SpreeCmCommissioner
22
+ module VendorPlaces
23
+ class BulkCreate < Base
24
+ def call(vendor:, place_ids:, place_type:, description: nil, max_count: 10, location: nil) # rubocop:disable Metrics/ParameterLists
25
+ error_message = validate_inputs!(place_ids: place_ids, place_type: place_type, max_count: max_count, location: location)
26
+ return failure(nil, error_message) if error_message
27
+
28
+ created_vendor_places = []
29
+
30
+ ActiveRecord::Base.transaction do
31
+ place_ids.each do |place_id|
32
+ vendor_place = build_vendor_place(
33
+ vendor: vendor,
34
+ place_id: place_id,
35
+ place_type: place_type,
36
+ description: description,
37
+ location: location
38
+ )
39
+
40
+ vendor_place.save!
41
+ created_vendor_places << vendor_place
42
+ end
43
+ end
44
+
45
+ success(created_vendor_places)
46
+ rescue ActiveRecord::RecordInvalid => e
47
+ failure(nil, e.record.errors.full_messages.to_sentence)
48
+ end
49
+
50
+ private
51
+
52
+ def validate_inputs!(place_ids:, place_type:, max_count:, location: nil)
53
+ validate_place_type!(place_type) ||
54
+ (place_ids.blank? && I18n.t('controller.vendor_places.place_ids_blank')) ||
55
+ (place_ids.size > max_count && I18n.t('controller.vendor_places.selection_limit', count: max_count)) ||
56
+ (requires_location?(place_type: place_type) && location.blank? && I18n.t('controller.vendor_places.location_required'))
57
+ end
58
+
59
+ def build_vendor_place(vendor:, place_id:, place_type:, description:, location: nil)
60
+ attrs = { vendor_id: vendor.id, place_type: place_type.to_sym, place_id: place_id, description: description }
61
+ attrs[:location_id] = location&.id if requires_location?(place_type: place_type)
62
+ SpreeCmCommissioner::VendorPlace.new(attrs)
63
+ end
64
+
65
+ def requires_location?(place_type:)
66
+ %i[stop branch].include?(place_type.to_sym)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,85 @@
1
+ # Creates a vendor place from Google Map base64-encoded place data.
2
+ # Processes place data and creates both Place and VendorPlace records atomically.
3
+ #
4
+ # Usage:
5
+ # result = SpreeCmCommissioner::VendorPlaces::CreateWithGoogleMapData.call(
6
+ # vendor: current_vendor,
7
+ # place_type: 'stop',
8
+ # google_place_base_64_content: google_place_base_64_content,
9
+ # description: 'Main Warehouse',
10
+ # location: parent_vendor_location
11
+ # )
12
+ #
13
+ # if result.success?
14
+ # vendor_place = result.value
15
+ # else
16
+ # error_message = result.error
17
+ # end
18
+
19
+ module SpreeCmCommissioner
20
+ module VendorPlaces
21
+ class CreateWithGoogleMapData < Base
22
+ def call(vendor:, place_type:, google_place_base_64_content:, description: nil, location: nil)
23
+ error_message = validate_place_type!(place_type)
24
+ return failure(nil, error_message) if error_message.present?
25
+
26
+ # Only require location for stop and branch types
27
+ return failure(nil, I18n.t('controller.vendor_places.location_required')) if %w[stop branch].include?(place_type) && location.nil?
28
+
29
+ place = process_place(google_place_base_64_content: google_place_base_64_content)
30
+ return failure(nil, place) unless place.is_a?(SpreeCmCommissioner::Place)
31
+
32
+ vendor_place = create_vendor_place(vendor: vendor, place: place, place_type: place_type, description: description, location: location)
33
+ return failure(nil, vendor_place) if vendor_place.nil? || vendor_place.is_a?(String)
34
+
35
+ success(vendor_place)
36
+ end
37
+
38
+ private
39
+
40
+ def process_place(google_place_base_64_content:)
41
+ place = SpreeCmCommissioner::PlaceDecoder.process_place(google_place_base_64_content)
42
+ return I18n.t('controller.vendor_places.process_place_failed', default: 'Failed to process place') if place.nil? || !place.save
43
+
44
+ place
45
+ rescue StandardError => e
46
+ e.message
47
+ end
48
+
49
+ def create_vendor_place(vendor:, place:, place_type:, description:, location:)
50
+ error_message = nil
51
+ result = ActiveRecord::Base.transaction do
52
+ vendor_place = build_vendor_place(vendor: vendor, place: place, type: place_type, description: description, location: location)
53
+ unless vendor_place.save
54
+ error_message = vendor_place.errors.full_messages.to_sentence
55
+ raise ActiveRecord::Rollback
56
+ end
57
+ vendor_place
58
+ end
59
+ error_message || result
60
+ end
61
+
62
+ def build_vendor_place(vendor:, place:, type:, description: nil, location: nil)
63
+ case type
64
+ when 'stop'
65
+ vendor.stops.new(
66
+ place_id: place.id,
67
+ description: description,
68
+ location_id: location&.id
69
+ )
70
+ when 'branch'
71
+ vendor.branches.new(
72
+ place_id: place.id,
73
+ description: description,
74
+ location_id: location&.id
75
+ )
76
+ when 'location'
77
+ vendor.locations.new(
78
+ place_id: place.id,
79
+ description: description
80
+ )
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,56 @@
1
+ # Updates all vendor places matching the same vendor, place_id, and location_id.
2
+ # Only updates description and location_id. Rolls back if any update fails.
3
+ #
4
+ # Usage:
5
+ # result = SpreeCmCommissioner::VendorPlaces::Update.call(
6
+ # vendor: current_vendor,
7
+ # vendor_place: existing_vendor_place,
8
+ # location_id: new_location_id,
9
+ # description: 'Updated description'
10
+ # )
11
+ #
12
+ # if result.success?
13
+ # updated_place = result.value
14
+ # else
15
+ # error_message = result.error
16
+ # end
17
+ module SpreeCmCommissioner
18
+ module VendorPlaces
19
+ class Update < Base
20
+ def call(vendor:, vendor_place:, description: nil, location_id: nil)
21
+ if vendor_place.blank?
22
+ return failure(
23
+ nil,
24
+ I18n.t('controller.vendor_places.vendor_place_not_found', default: 'Vendor place not found')
25
+ )
26
+ end
27
+
28
+ ActiveRecord::Base.transaction do
29
+ related_places = SpreeCmCommissioner::VendorPlace.where(
30
+ vendor_id: vendor.id,
31
+ place_id: vendor_place.place_id,
32
+ location_id: vendor_place.location_id
33
+ )
34
+
35
+ update_attrs = {}
36
+ update_attrs[:description] = description unless description.nil?
37
+ update_attrs[:location_id] = location_id unless location_id.nil?
38
+
39
+ related_places.each do |vp|
40
+ unless vp.update(update_attrs)
41
+ error_message = vp.errors.full_messages.to_sentence
42
+ raise ActiveRecord::Rollback, error_message
43
+ end
44
+ end
45
+ end
46
+
47
+ vendor_place.reload
48
+ success(vendor_place)
49
+ rescue ActiveRecord::RecordInvalid => e
50
+ failure(nil, e.record.errors.full_messages.to_sentence)
51
+ rescue ActiveRecord::Rollback => e
52
+ failure(nil, e.message)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -16,7 +16,7 @@
16
16
  </div>
17
17
  <% else %>
18
18
  <div class="f-fallback">
19
- <%= render 'spree_cm_commissioner/order_mailer/tenant/greeting', order: @order %>
19
+ <%= render 'spree_cm_commissioner/order_mailer/tenant/greeting', order: @order, logo: @logo, name: @name%>
20
20
  <div class="content">
21
21
  <%= render 'spree_cm_commissioner/order_mailer/order_total', order: @order %>
22
22
  <%= render 'spree_cm_commissioner/order_mailer/your_booking', order: @order, show_booking_header: true %>