spree_cm_commissioner 2.3.1 → 2.3.2.pre.pre4
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/admin/product_completion_steps_controller.rb +17 -1
- data/app/controllers/spree/admin/tenants_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/intercity_taxi/draft_orders_controller.rb +52 -11
- data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +63 -0
- data/app/controllers/spree/api/v2/storefront/route_places_controller.rb +68 -0
- data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +81 -0
- data/app/controllers/spree/api/v2/tenant/trip_places_controller.rb +42 -0
- data/app/controllers/spree/api/v2/tenant/trip_search_controller.rb +103 -0
- data/app/controllers/spree/api/v2/tenant/trips_controller.rb +32 -0
- data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +12 -0
- data/app/finders/spree_cm_commissioner/places/find_with_route.rb +43 -0
- data/app/finders/spree_cm_commissioner/routes/find_popular.rb +25 -0
- data/app/interactors/spree_cm_commissioner/create_ticket.rb +2 -0
- data/app/interactors/spree_cm_commissioner/host_matcher.rb +1 -1
- data/app/interactors/spree_cm_commissioner/vendor_creation_telegram_alert_sender.rb +1 -1
- data/app/mailers/spree/order_mailer_decorator.rb +12 -11
- data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +34 -89
- data/app/models/concerns/spree_cm_commissioner/service_recommendations.rb +2 -2
- data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
- data/app/models/spree_cm_commissioner/adjustment_decorator.rb +1 -1
- data/app/models/spree_cm_commissioner/calculators/extra_drop_off_distance.rb +15 -0
- data/app/models/spree_cm_commissioner/calculators/extra_pick_up_distance.rb +15 -0
- data/app/models/spree_cm_commissioner/place.rb +7 -0
- data/app/models/spree_cm_commissioner/pricing_action.rb +27 -0
- data/app/models/spree_cm_commissioner/pricing_model.rb +18 -0
- data/app/models/spree_cm_commissioner/pricing_model_variant.rb +6 -0
- data/app/models/spree_cm_commissioner/pricing_rule.rb +15 -0
- data/app/models/spree_cm_commissioner/pricing_rule_group.rb +20 -0
- data/app/models/spree_cm_commissioner/pricing_rules/extra_drop_off_distance.rb +10 -0
- data/app/models/spree_cm_commissioner/pricing_rules/extra_pick_up_distance.rb +12 -0
- data/app/models/spree_cm_commissioner/pricing_rules/nationality.rb +35 -0
- data/app/models/spree_cm_commissioner/product_completion_step.rb +4 -1
- data/app/models/spree_cm_commissioner/product_completion_step_banner.rb +12 -0
- data/app/models/spree_cm_commissioner/product_completion_steps/social_entry_url.rb +86 -19
- data/app/models/spree_cm_commissioner/tenant.rb +14 -1
- data/app/models/spree_cm_commissioner/variant_decorator.rb +3 -0
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/vendor_place.rb +10 -1
- data/app/overrides/spree/admin/stores/_form/store_preferences.html.erb.deface +9 -0
- data/app/queries/spree_cm_commissioner/trip_query.rb +23 -17
- data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_schema.rb +10 -0
- data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_update_schema.rb +29 -0
- data/app/request_schemas/spree_cm_commissioner/route_places_request_schema.rb +12 -0
- data/app/serializers/spree/v2/storefront/place_serializer.rb +3 -2
- data/app/serializers/spree/v2/tenant/cart_serializer.rb +1 -0
- data/app/serializers/spree/v2/tenant/intercity_taxi_cart_serializer.rb +12 -0
- data/app/serializers/spree/v2/tenant/line_item_serializer.rb +1 -0
- data/app/serializers/spree/v2/tenant/trip_place_serializer.rb +8 -0
- data/app/serializers/spree/v2/tenant/trip_query_result_serializer.rb +8 -0
- data/app/serializers/spree/v2/tenant/trip_serializer.rb +8 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_line_item_serializer.rb +17 -18
- data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +12 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +1 -1
- data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +20 -23
- data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +36 -72
- data/app/services/spree_cm_commissioner/pricing_models/apply.rb +51 -0
- data/app/services/spree_cm_commissioner/pricing_rules/build_params.rb +53 -0
- data/app/services/spree_cm_commissioner/pricing_rules/build_template.rb +27 -0
- data/app/services/spree_cm_commissioner/pricing_rules/create.rb +10 -0
- data/app/services/spree_cm_commissioner/pricing_rules/update.rb +11 -0
- data/app/services/spree_cm_commissioner/vendor_places/base.rb +24 -0
- data/app/services/spree_cm_commissioner/vendor_places/bulk_create.rb +70 -0
- data/app/services/spree_cm_commissioner/vendor_places/create_with_google_map_data.rb +85 -0
- data/app/services/spree_cm_commissioner/vendor_places/update.rb +56 -0
- data/app/views/shared/_asset_field.html.erb +13 -0
- data/app/views/spree/admin/product_completion_steps/_form.html.erb +28 -2
- data/app/views/spree/admin/product_completion_steps/_supported_fields.html.erb +33 -0
- data/app/views/spree/admin/tenants/_form.html.erb +1 -1
- data/app/views/spree/order_mailer/confirm_email.html.erb +1 -1
- data/app/views/spree_cm_commissioner/order_mailer/_mailer_stylesheets.html.erb +14 -13
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb +6 -4
- data/config/initializers/spree_permitted_attributes.rb +1 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/km.yml +65 -61
- data/config/routes.rb +15 -1
- data/db/migrate/20250915075830_create_spree_cm_commissioner_pricing_models.rb +17 -0
- data/db/migrate/20250915080733_create_spree_cm_commissioner_pricing_model_variants.rb +14 -0
- data/db/migrate/20251003082144_create_spree_cm_commissioner_pricing_rule_groups.rb +16 -0
- data/db/migrate/20251003093408_create_spree_cm_commissioner_pricing_rule.rb +19 -0
- data/db/migrate/20251003093643_create_spree_cm_commissioner_pricing_action.rb +14 -0
- data/db/migrate/20251119093000_add_tenant_id_to_cm_vendor_places.rb +8 -0
- data/lib/spree_cm_commissioner/intercity_taxi/distance.rb +38 -0
- data/lib/spree_cm_commissioner/intercity_taxi/map_place.rb +31 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +5 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_model_factory.rb +8 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_factory.rb +17 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_group_factory.rb +8 -0
- data/lib/spree_cm_commissioner/test_helper/factories/product_completion_step_banner_factory.rb +7 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +2 -0
- metadata +54 -4
|
@@ -12,14 +12,20 @@ module Spree
|
|
|
12
12
|
return false if @order.email.blank?
|
|
13
13
|
|
|
14
14
|
setup_tenant_and_store
|
|
15
|
+
if @tenant.present?
|
|
16
|
+
@name = @order.tenant.name
|
|
17
|
+
@logo = @order.tenant.active_vendor&.logo&.original_url
|
|
18
|
+
@brand_color = @order.tenant.preferred_brand_primary_color
|
|
19
|
+
end
|
|
15
20
|
@product_type = @order.products.first&.product_type || 'accommodation'
|
|
16
21
|
|
|
17
22
|
subject = build_subject(resend)
|
|
18
23
|
|
|
19
24
|
mail(to: @order.email,
|
|
20
|
-
from: from_email_address,
|
|
25
|
+
from: from_email_address,
|
|
26
|
+
subject: subject,
|
|
21
27
|
store_url: store_url
|
|
22
|
-
|
|
28
|
+
) do |format|
|
|
23
29
|
format.html { render layout: 'spree_cm_commissioner/layouts/order_mailer' }
|
|
24
30
|
format.text
|
|
25
31
|
end
|
|
@@ -37,22 +43,17 @@ module Spree
|
|
|
37
43
|
|
|
38
44
|
def setup_tenant_and_store
|
|
39
45
|
@tenant = @order.tenant
|
|
40
|
-
|
|
41
|
-
@brand_color = @tenant.preferences[:brand_primary_color]
|
|
42
|
-
@vendor_logo_url = @tenant.active_vendor&.logo&.original_url
|
|
43
|
-
@current_store = @tenant
|
|
44
|
-
else
|
|
45
|
-
@current_store = @order.store
|
|
46
|
-
end
|
|
46
|
+
@current_store = @order.store
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def build_subject(resend)
|
|
50
50
|
prefix = resend ? "[#{Spree.t(:resend).upcase}] " : ''
|
|
51
|
-
|
|
51
|
+
store_name = @tenant.present? ? @tenant.name : @current_store&.name
|
|
52
|
+
"#{prefix}#{store_name} Booking Confirmation ##{@order.number}"
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
def store_url
|
|
55
|
-
@tenant.present? ? @
|
|
56
|
+
@tenant.present? ? @tenant.url : @current_store.url
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
def ticket_email(guest, email)
|
|
@@ -27,11 +27,6 @@ module SpreeCmCommissioner
|
|
|
27
27
|
validates :boarding_trip_stop_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
|
28
28
|
validates :drop_off_trip_stop_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
|
29
29
|
|
|
30
|
-
validates :pickup_lat, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_nil: true
|
|
31
|
-
validates :pickup_lng, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_nil: true
|
|
32
|
-
validates :drop_off_lat, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_nil: true
|
|
33
|
-
validates :drop_off_lng, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_nil: true
|
|
34
|
-
|
|
35
30
|
validates :passenger_count, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
|
36
31
|
|
|
37
32
|
# Groups LineItems by trip fields. Some fields can be null,
|
|
@@ -61,121 +56,71 @@ module SpreeCmCommissioner
|
|
|
61
56
|
def boarding_trip_stop_id = private_metadata['boarding_trip_stop_id']&.to_i
|
|
62
57
|
def drop_off_trip_stop_id = private_metadata['drop_off_trip_stop_id']&.to_i
|
|
63
58
|
|
|
64
|
-
def pickup_place_name = public_metadata['pickup_place_name']
|
|
65
|
-
def drop_off_place_name = public_metadata['drop_off_place_name']
|
|
66
|
-
|
|
67
|
-
def pickup_lat = public_metadata['pickup_lat']&.to_f
|
|
68
|
-
def pickup_lng = public_metadata['pickup_lng']&.to_f
|
|
69
|
-
def drop_off_lat = public_metadata['drop_off_lat']&.to_f
|
|
70
|
-
def drop_off_lng = public_metadata['drop_off_lng']&.to_f
|
|
71
59
|
def passenger_count = public_metadata['passenger_count']&.to_i
|
|
72
|
-
def distance_km = public_metadata['distance_km']&.to_f
|
|
73
|
-
def ordered_points = public_metadata['ordered_points']
|
|
74
|
-
def base_km = public_metadata['base_km']&.to_f
|
|
75
|
-
def detour_pickup_km = public_metadata['detour_pickup_km']&.to_f
|
|
76
|
-
def detour_dropoff_km = public_metadata['detour_dropoff_km']&.to_f
|
|
77
|
-
def extra_pickup_km = public_metadata['extra_pickup_km']&.to_f
|
|
78
|
-
def extra_dropoff_km = public_metadata['extra_dropoff_km']&.to_f
|
|
79
|
-
def extra_pickup_charge_usd = public_metadata['extra_pickup_charge_usd']&.to_f
|
|
80
|
-
def extra_dropoff_charge_usd = public_metadata['extra_dropoff_charge_usd']&.to_f
|
|
81
|
-
def estimated_time_minutes = public_metadata['estimated_time_minutes']&.to_i
|
|
82
|
-
def pickup_oob_confirmed = private_metadata['pickup_oob_confirmed']
|
|
83
|
-
def drop_off_oob_confirmed = private_metadata['drop_off_oob_confirmed']
|
|
84
60
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
def pickup_map_place
|
|
62
|
+
pickup_hash = public_metadata&.dig('pickup_map_place')
|
|
63
|
+
return nil if pickup_hash.blank?
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
set_private_metadata_value('trip_id', value)
|
|
65
|
+
SpreeCmCommissioner::IntercityTaxi::MapPlace.from_hash(pickup_hash)
|
|
91
66
|
end
|
|
92
67
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
68
|
+
def dropoff_map_place
|
|
69
|
+
dropoff_hash = public_metadata&.dig('dropoff_map_place')
|
|
70
|
+
return nil if dropoff_hash.blank?
|
|
96
71
|
|
|
97
|
-
|
|
98
|
-
set_private_metadata_value('drop_off_trip_stop_id', value)
|
|
72
|
+
SpreeCmCommissioner::IntercityTaxi::MapPlace.from_hash(dropoff_hash)
|
|
99
73
|
end
|
|
100
74
|
|
|
101
|
-
def
|
|
102
|
-
|
|
75
|
+
def distance
|
|
76
|
+
distance_hash = public_metadata&.dig('distance')
|
|
77
|
+
return nil if distance_hash.blank?
|
|
78
|
+
|
|
79
|
+
SpreeCmCommissioner::IntercityTaxi::Distance.from_hash(distance_hash)
|
|
103
80
|
end
|
|
104
81
|
|
|
105
|
-
def
|
|
106
|
-
|
|
82
|
+
def pickup_map_place=(value)
|
|
83
|
+
set_public_metadata_object('pickup_map_place', value)
|
|
107
84
|
end
|
|
108
85
|
|
|
109
|
-
def
|
|
110
|
-
|
|
86
|
+
def dropoff_map_place=(value)
|
|
87
|
+
set_public_metadata_object('dropoff_map_place', value)
|
|
111
88
|
end
|
|
112
89
|
|
|
113
|
-
def
|
|
114
|
-
|
|
90
|
+
def distance=(value)
|
|
91
|
+
set_public_metadata_object('distance', value)
|
|
115
92
|
end
|
|
116
93
|
|
|
117
|
-
def
|
|
118
|
-
|
|
94
|
+
def direction=(value)
|
|
95
|
+
set_private_metadata_value('direction', value)
|
|
119
96
|
end
|
|
120
97
|
|
|
121
|
-
def
|
|
122
|
-
|
|
98
|
+
def trip_id=(value)
|
|
99
|
+
set_private_metadata_value('trip_id', value)
|
|
123
100
|
end
|
|
124
101
|
|
|
125
|
-
def
|
|
126
|
-
|
|
102
|
+
def boarding_trip_stop_id=(value)
|
|
103
|
+
set_private_metadata_value('boarding_trip_stop_id', value)
|
|
127
104
|
end
|
|
128
105
|
|
|
129
|
-
def
|
|
130
|
-
|
|
106
|
+
def drop_off_trip_stop_id=(value)
|
|
107
|
+
set_private_metadata_value('drop_off_trip_stop_id', value)
|
|
131
108
|
end
|
|
132
109
|
|
|
133
110
|
def passenger_count=(value)
|
|
134
111
|
set_public_metadata_value('passenger_count', value)
|
|
135
112
|
end
|
|
136
113
|
|
|
137
|
-
|
|
138
|
-
set_public_metadata_value('distance_km', value)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def ordered_points=(value)
|
|
142
|
-
set_public_metadata_value('ordered_points', value)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def base_km=(value)
|
|
146
|
-
set_public_metadata_value('base_km', value)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def detour_pickup_km=(value)
|
|
150
|
-
set_public_metadata_value('detour_pickup_km', value)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def detour_dropoff_km=(value)
|
|
154
|
-
set_public_metadata_value('detour_dropoff_km', value)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def extra_pickup_km=(value)
|
|
158
|
-
set_public_metadata_value('extra_pickup_km', value)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def extra_dropoff_km=(value)
|
|
162
|
-
set_public_metadata_value('extra_dropoff_km', value)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def extra_pickup_charge_usd=(value)
|
|
166
|
-
set_public_metadata_value('extra_pickup_charge_usd', value)
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def extra_dropoff_charge_usd=(value)
|
|
170
|
-
set_public_metadata_value('extra_dropoff_charge_usd', value)
|
|
171
|
-
end
|
|
114
|
+
private
|
|
172
115
|
|
|
173
|
-
def
|
|
174
|
-
|
|
116
|
+
def set_public_metadata_object(key, value)
|
|
117
|
+
if value.nil?
|
|
118
|
+
public_metadata.delete(key)
|
|
119
|
+
else
|
|
120
|
+
public_metadata[key] = value.to_h.stringify_keys
|
|
121
|
+
end
|
|
175
122
|
end
|
|
176
123
|
|
|
177
|
-
private
|
|
178
|
-
|
|
179
124
|
def set_public_metadata_value(key, value)
|
|
180
125
|
if value.nil?
|
|
181
126
|
public_metadata.delete(key)
|
|
@@ -50,9 +50,9 @@ module SpreeCmCommissioner
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
def tenant_link_or_default(tenant, default_link)
|
|
53
|
-
return default_link unless tenant.respond_to?(:
|
|
53
|
+
return default_link unless tenant.respond_to?(:formatted_url)
|
|
54
54
|
|
|
55
|
-
site = tenant.
|
|
55
|
+
site = tenant.formatted_url.to_s.strip
|
|
56
56
|
site.presence || default_link
|
|
57
57
|
rescue StandardError
|
|
58
58
|
default_link
|
|
@@ -6,6 +6,7 @@ module SpreeCmCommissioner
|
|
|
6
6
|
preference :sms_sender_id, :string
|
|
7
7
|
preference :telegram_order_alert_chat_id, :string
|
|
8
8
|
preference :telegram_order_request_alert_chat_id, :string
|
|
9
|
+
preference :telegram_new_vendor_alert_chat_id, :string
|
|
9
10
|
preference :assetlinks, :string, default: ''
|
|
10
11
|
preference :apple_app_site_association, :string, default: ''
|
|
11
12
|
end
|
|
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
|
|
|
2
2
|
module AdjustmentDecorator
|
|
3
3
|
def self.prepended(base)
|
|
4
4
|
base.belongs_to :payable, polymorphic: true, optional: true
|
|
5
|
-
|
|
5
|
+
base.scope :pricing_action, -> { where(source_type: 'SpreeCmCommissioner::PricingAction') }
|
|
6
6
|
base.whitelisted_ransackable_attributes |= %w[payable_id]
|
|
7
7
|
end
|
|
8
8
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Calculators
|
|
3
|
+
class ExtraDropOffDistance < ExtraPickUpDistance
|
|
4
|
+
preference :base_price, :decimal, default: 0
|
|
5
|
+
preference :amount, :decimal, default: 0
|
|
6
|
+
|
|
7
|
+
def compute(line_item)
|
|
8
|
+
distance_km = line_item.distance.extra_dropoff_km
|
|
9
|
+
return nil if distance_km.nil?
|
|
10
|
+
|
|
11
|
+
preferred_base_price + (distance_km * preferred_amount)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Calculators
|
|
3
|
+
class ExtraPickUpDistance < Spree::Calculator
|
|
4
|
+
preference :base_price, :decimal, default: 0
|
|
5
|
+
preference :amount, :decimal, default: 0
|
|
6
|
+
|
|
7
|
+
def compute(line_item)
|
|
8
|
+
distance_km = line_item.distance.extra_pickup_km
|
|
9
|
+
return nil if distance_km.nil?
|
|
10
|
+
|
|
11
|
+
preferred_base_price + (distance_km * preferred_amount)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -21,6 +21,13 @@ module SpreeCmCommissioner
|
|
|
21
21
|
has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
|
|
22
22
|
has_many :taxons, through: :taxon_places
|
|
23
23
|
|
|
24
|
+
has_many :routes_as_origin, class_name: 'SpreeCmCommissioner::Route', foreign_key: :origin_place_id
|
|
25
|
+
has_many :routes_as_destination, class_name: 'SpreeCmCommissioner::Route', foreign_key: :destination_place_id
|
|
26
|
+
|
|
27
|
+
# Scopes for route-based filtering
|
|
28
|
+
scope :with_routes_as_origin, -> { joins(:routes_as_origin).distinct }
|
|
29
|
+
scope :with_routes_as_destination, -> { joins(:routes_as_destination).distinct }
|
|
30
|
+
|
|
24
31
|
def self.ransackable_attributes(auth_object = nil)
|
|
25
32
|
super & %w[name code]
|
|
26
33
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class PricingAction < Base
|
|
3
|
+
belongs_to :pricing_rule_group, class_name: 'SpreeCmCommissioner::PricingRuleGroup'
|
|
4
|
+
|
|
5
|
+
has_one :calculator, as: :calculable, class_name: 'Spree::Calculator', dependent: :destroy
|
|
6
|
+
accepts_nested_attributes_for :calculator
|
|
7
|
+
|
|
8
|
+
def perform(line_item)
|
|
9
|
+
return if pricing_rule_group.pricing_rules.blank?
|
|
10
|
+
|
|
11
|
+
amount = calculator&.compute(line_item)
|
|
12
|
+
|
|
13
|
+
line_item.adjustments.create!(
|
|
14
|
+
amount: amount,
|
|
15
|
+
source: self,
|
|
16
|
+
adjustable: line_item,
|
|
17
|
+
order: line_item.order,
|
|
18
|
+
label: pricing_rule_group.pricing_model.name,
|
|
19
|
+
mandatory: true
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.available_calculator_types
|
|
24
|
+
SpreeCmCommissioner::Calculators.constants.map(&:to_s)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class PricingModel < Base
|
|
3
|
+
acts_as_list column: :position, scope: :vendor_id
|
|
4
|
+
|
|
5
|
+
belongs_to :vendor, class_name: 'Spree::Vendor'
|
|
6
|
+
|
|
7
|
+
has_many :pricing_model_variants, class_name: 'SpreeCmCommissioner::PricingModelVariant', dependent: :destroy
|
|
8
|
+
has_many :variants, through: :pricing_model_variants, class_name: 'Spree::Variant'
|
|
9
|
+
has_many :pricing_rule_groups, class_name: 'SpreeCmCommissioner::PricingRuleGroup', dependent: :destroy
|
|
10
|
+
has_many :pricing_rules, through: :pricing_rule_groups, class_name: 'SpreeCmCommissioner::PricingRule'
|
|
11
|
+
|
|
12
|
+
accepts_nested_attributes_for :pricing_rule_groups, allow_destroy: true
|
|
13
|
+
|
|
14
|
+
validates :name, presence: true, uniqueness: { scope: :vendor_id }
|
|
15
|
+
|
|
16
|
+
enum status: { draft: 0, active: 1, archived: 2 }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class PricingRule < Base
|
|
3
|
+
acts_as_list column: :position, scope: :pricing_rule_group_id
|
|
4
|
+
|
|
5
|
+
belongs_to :pricing_rule_group, class_name: 'SpreeCmCommissioner::PricingRuleGroup'
|
|
6
|
+
|
|
7
|
+
def eligible?(_line_item)
|
|
8
|
+
raise 'eligible? should be implemented in a sub-class of SpreeCmCommissioner::PricingRule'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.available_rule_types
|
|
12
|
+
SpreeCmCommissioner::PricingRules.constants.map(&:to_s)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class PricingRuleGroup < Base
|
|
3
|
+
MATCH_TYPES = %w[all any none].freeze
|
|
4
|
+
|
|
5
|
+
acts_as_list column: :position, scope: :pricing_model_id
|
|
6
|
+
store_accessor :private_metadata, :template_key
|
|
7
|
+
store_accessor :private_metadata, :match_type
|
|
8
|
+
|
|
9
|
+
belongs_to :pricing_model, class_name: 'SpreeCmCommissioner::PricingModel'
|
|
10
|
+
has_many :pricing_rules, class_name: 'SpreeCmCommissioner::PricingRule', dependent: :destroy
|
|
11
|
+
has_one :pricing_action, class_name: 'SpreeCmCommissioner::PricingAction', dependent: :destroy
|
|
12
|
+
|
|
13
|
+
accepts_nested_attributes_for :pricing_rules, allow_destroy: true
|
|
14
|
+
accepts_nested_attributes_for :pricing_action, allow_destroy: true
|
|
15
|
+
|
|
16
|
+
validates :name, presence: true
|
|
17
|
+
|
|
18
|
+
enum status: { draft: 0, active: 1, archived: 2 }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module PricingRules
|
|
3
|
+
class ExtraPickUpDistance < SpreeCmCommissioner::PricingRule
|
|
4
|
+
store_accessor :private_metadata, :min_distance
|
|
5
|
+
|
|
6
|
+
def eligible?(line_item)
|
|
7
|
+
distance_km = line_item.distance&.extra_pickup_km || 0
|
|
8
|
+
distance_km > min_distance.to_f
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module PricingRules
|
|
3
|
+
class Nationality < SpreeCmCommissioner::PricingRule
|
|
4
|
+
RULE_TYPES = %w[all any none].freeze
|
|
5
|
+
|
|
6
|
+
store_accessor :private_metadata, :rule_type, :nationalities
|
|
7
|
+
|
|
8
|
+
def eligible?(line_item) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
9
|
+
return false if nationalities.blank? || line_item.guests.blank?
|
|
10
|
+
|
|
11
|
+
targets = Array.wrap(nationalities).map(&:downcase)
|
|
12
|
+
guest_nationalities = line_item.guests.map { |g| guest_nationality(g) }.compact
|
|
13
|
+
|
|
14
|
+
return false if guest_nationalities.empty?
|
|
15
|
+
|
|
16
|
+
case rule_type
|
|
17
|
+
when 'all'
|
|
18
|
+
guest_nationalities.all? { |n| targets.include?(n) }
|
|
19
|
+
when 'any'
|
|
20
|
+
guest_nationalities.any? { |n| targets.include?(n) }
|
|
21
|
+
when 'none'
|
|
22
|
+
guest_nationalities.none? { |n| targets.include?(n) }
|
|
23
|
+
else
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def guest_nationality(guest)
|
|
31
|
+
guest.nationality&.name.to_s.downcase
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -4,6 +4,8 @@ module SpreeCmCommissioner
|
|
|
4
4
|
|
|
5
5
|
belongs_to :product, class_name: '::Spree::Product', optional: false
|
|
6
6
|
|
|
7
|
+
has_one :banner, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::ProductCompletionStepBanner'
|
|
8
|
+
|
|
7
9
|
# When a completion step is changed, regenerate completion steps for all line items
|
|
8
10
|
# of the product so they reflect the latest step configuration.
|
|
9
11
|
after_destroy :regenerate_line_items_completion_steps
|
|
@@ -28,7 +30,8 @@ module SpreeCmCommissioner
|
|
|
28
30
|
action_label: action_label,
|
|
29
31
|
action_url: action_url_for(line_item),
|
|
30
32
|
completed_at: existing_data&.dig('completed_at'),
|
|
31
|
-
completed: completed?(line_item)
|
|
33
|
+
completed: completed?(line_item),
|
|
34
|
+
banner_url: banner.present? ? banner.original_url : nil
|
|
32
35
|
}
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -5,6 +5,7 @@ module SpreeCmCommissioner
|
|
|
5
5
|
# - https://t.me/ThePlatformKHBot?start={order.number}
|
|
6
6
|
# - https://example.com?guest_name={guests[0].first_name}&phone={guests[0].phone_number}
|
|
7
7
|
# - https://example.com?line_item_id={line_item.id}&quantity={line_item.quantity}
|
|
8
|
+
# - https://example.com?bill_address.first_name={bill_address.first_name}&bill_address.lastname={bill_address.lastname}
|
|
8
9
|
preference :entry_point_link, :string
|
|
9
10
|
|
|
10
11
|
# Allowed fields per object (excludes private/reference fields like *_id, *_by_id, metadata, etc.)
|
|
@@ -14,7 +15,11 @@ module SpreeCmCommissioner
|
|
|
14
15
|
state
|
|
15
16
|
email
|
|
16
17
|
total
|
|
18
|
+
display_total
|
|
17
19
|
item_total
|
|
20
|
+
display_item_total
|
|
21
|
+
adjustment_total
|
|
22
|
+
display_adjustment_total
|
|
18
23
|
created_at
|
|
19
24
|
completed_at
|
|
20
25
|
token
|
|
@@ -23,12 +28,31 @@ module SpreeCmCommissioner
|
|
|
23
28
|
currency
|
|
24
29
|
item_count
|
|
25
30
|
channel
|
|
31
|
+
special_instructions
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
BILL_ADDRESS_ALLOWED_FIELDS = %w[
|
|
35
|
+
first_name
|
|
36
|
+
last_name
|
|
37
|
+
full_name
|
|
38
|
+
address1
|
|
39
|
+
address2
|
|
40
|
+
city
|
|
41
|
+
zipcode
|
|
42
|
+
phone
|
|
43
|
+
state_name
|
|
44
|
+
alternative_phone
|
|
45
|
+
company
|
|
46
|
+
label
|
|
47
|
+
age
|
|
48
|
+
gender
|
|
26
49
|
].freeze
|
|
27
50
|
|
|
28
51
|
LINE_ITEM_ALLOWED_FIELDS = %w[
|
|
29
52
|
qr_data
|
|
30
53
|
quantity
|
|
31
54
|
price
|
|
55
|
+
display_price
|
|
32
56
|
from_date
|
|
33
57
|
to_date
|
|
34
58
|
number
|
|
@@ -45,7 +69,6 @@ module SpreeCmCommissioner
|
|
|
45
69
|
dob
|
|
46
70
|
gender
|
|
47
71
|
age
|
|
48
|
-
email
|
|
49
72
|
phone_number
|
|
50
73
|
address
|
|
51
74
|
seat_number
|
|
@@ -60,9 +83,6 @@ module SpreeCmCommissioner
|
|
|
60
83
|
expectation
|
|
61
84
|
other_occupation
|
|
62
85
|
other_organization
|
|
63
|
-
entry_type
|
|
64
|
-
upload_later
|
|
65
|
-
data_fill_stage_phase
|
|
66
86
|
created_at
|
|
67
87
|
updated_at
|
|
68
88
|
].freeze
|
|
@@ -72,32 +92,79 @@ module SpreeCmCommissioner
|
|
|
72
92
|
return nil if preferred_entry_point_link.blank?
|
|
73
93
|
|
|
74
94
|
url = preferred_entry_point_link.dup
|
|
95
|
+
url = replace_order_placeholders(url, line_item)
|
|
96
|
+
url = replace_bill_address_placeholders(url, line_item)
|
|
97
|
+
url = replace_line_item_placeholders(url, line_item)
|
|
98
|
+
|
|
99
|
+
replace_guest_placeholders(url, line_item)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def completed?(line_item)
|
|
103
|
+
return false if line_item.completion_steps.blank?
|
|
104
|
+
|
|
105
|
+
step_data = line_item.completion_steps.find { |step| step['position'].to_s == position.to_s }
|
|
106
|
+
step_data&.dig('completed_at').present?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns a hash of all supported placeholder fields grouped by object type.
|
|
110
|
+
# Used by the admin UI to display available placeholders when configuring the entry_point_link template.
|
|
111
|
+
# Example usage in template: {order.number}, {bill_address.first_name}, {line_item.quantity}, {guests[0].email}
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash] Hash with keys :order, :bill_address, :line_item, :guests
|
|
114
|
+
# Each key maps to an array of allowed field names for that object type
|
|
115
|
+
def supported_fields
|
|
116
|
+
{
|
|
117
|
+
order: ORDER_ALLOWED_FIELDS,
|
|
118
|
+
bill_address: BILL_ADDRESS_ALLOWED_FIELDS,
|
|
119
|
+
line_item: LINE_ITEM_ALLOWED_FIELDS,
|
|
120
|
+
guests: GUEST_ALLOWED_FIELDS
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
75
125
|
|
|
76
|
-
|
|
77
|
-
line_item.order.
|
|
78
|
-
|
|
126
|
+
def replace_order_placeholders(url, line_item)
|
|
127
|
+
return url if line_item.order.blank?
|
|
128
|
+
|
|
129
|
+
ORDER_ALLOWED_FIELDS.each do |field|
|
|
130
|
+
value = line_item.order.public_send(field)
|
|
131
|
+
url.gsub!("{order.#{field}}", value.to_s) if value.present?
|
|
79
132
|
end
|
|
80
133
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
url
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def replace_bill_address_placeholders(url, line_item)
|
|
138
|
+
return url if line_item.order&.bill_address.blank?
|
|
139
|
+
|
|
140
|
+
BILL_ADDRESS_ALLOWED_FIELDS.each do |field|
|
|
141
|
+
value = line_item.order.bill_address.public_send(field)
|
|
142
|
+
url.gsub!("{bill_address.#{field}}", value.to_s) if value.present?
|
|
84
143
|
end
|
|
85
144
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
145
|
+
url
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def replace_line_item_placeholders(url, line_item)
|
|
149
|
+
LINE_ITEM_ALLOWED_FIELDS.each do |field|
|
|
150
|
+
value = line_item.public_send(field)
|
|
151
|
+
url.gsub!("{line_item.#{field}}", value.to_s) if value.present?
|
|
91
152
|
end
|
|
92
153
|
|
|
93
154
|
url
|
|
94
155
|
end
|
|
95
156
|
|
|
96
|
-
def
|
|
97
|
-
return
|
|
157
|
+
def replace_guest_placeholders(url, line_item)
|
|
158
|
+
return url if line_item.guests.blank?
|
|
98
159
|
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
line_item.guests.each_with_index do |guest, index|
|
|
161
|
+
GUEST_ALLOWED_FIELDS.each do |field|
|
|
162
|
+
value = guest.public_send(field)
|
|
163
|
+
url.gsub!("{guests[#{index}].#{field}}", value.to_s) if value.present?
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
url
|
|
101
168
|
end
|
|
102
169
|
end
|
|
103
170
|
end
|