spree_cm_commissioner 2.3.0.pre.pre10 → 2.3.0.pre.pre12
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/api/chatrace/guests_controller.rb +3 -1
- data/app/controllers/spree/api/v2/storefront/line_items_controller.rb +24 -0
- data/app/controllers/spree/api/v2/tenant/line_items_controller.rb +10 -21
- data/app/errors/spree_cm_commissioner/seats/blocks_are_on_hold_by_other_guest_error.rb +12 -1
- data/app/errors/spree_cm_commissioner/seats/blocks_are_reserved_by_other_guest_error.rb +12 -1
- data/app/errors/spree_cm_commissioner/seats/unable_to_save_reserved_block_record_error.rb +12 -1
- data/app/jobs/spree_cm_commissioner/completion_steps/regenerate_for_line_items_job.rb +14 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +10 -0
- data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +20 -2
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +26 -14
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/product_completion_step.rb +9 -32
- data/app/models/spree_cm_commissioner/product_completion_steps/social_entry_url.rb +92 -5
- data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +2 -1
- data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +8 -3
- data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +8 -3
- data/app/serializers/spree/v2/storefront/order_serializer_decorator.rb +1 -1
- data/app/services/spree_cm_commissioner/completion_steps/mark_line_item_as_completed.rb +48 -0
- data/app/services/spree_cm_commissioner/completion_steps/regenerate_for_line_items.rb +53 -0
- data/config/locales/en.yml +3 -0
- data/config/locales/km.yml +3 -0
- data/config/routes.rb +6 -1
- data/lib/spree_cm_commissioner/test_helper/factories/product_completion_step_factory.rb +7 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 693c7855e8dc72dabdde0e8bf436e4dc9f4b7971bc1c09612ad8c454f49a6c79
|
|
4
|
+
data.tar.gz: d9adba89a30ccd967d3472ca8e7ebf2b11b4fe1a9b1f1a898c765c1b67d642e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4104850773bb75da44081c426992256bd439508c0144f7371d3bfebd890fa253996df33f6de1981b4e1ccb7db7050e43a2633b8ff0c763d5b364dc799ed28efd
|
|
7
|
+
data.tar.gz: 7a76e698a0c34336f0d0805cdefadc84eac6c7a39e1a66d7792ab39ca50b4d3e7d04e1e1c1890462cdf8ae781e585df11636de56dca30efe1194391b935f1797
|
data/Gemfile.lock
CHANGED
|
@@ -18,9 +18,11 @@ module Spree
|
|
|
18
18
|
guest.preferred_telegram_user_id = params[:telegram_user_id]
|
|
19
19
|
guest.preferred_telegram_user_verified_at = DateTime.current
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# save & generate_completion_steps which will mark status as completed.
|
|
22
|
+
if guest.save && guest.line_item.generate_completion_steps
|
|
22
23
|
render_serialized_payload { serialize_resource(guest) }
|
|
23
24
|
else
|
|
25
|
+
guest.errors.merge!(guest.line_item.errors)
|
|
24
26
|
render_error_payload(guest.errors)
|
|
25
27
|
end
|
|
26
28
|
end
|
|
@@ -16,6 +16,30 @@ module Spree
|
|
|
16
16
|
render_serialized_payload { serialize_resource(line_item) }
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# PATCH /api/v2/storefront/line_items/1/try_mark_as_completed?order_token=:order_token&position=:position
|
|
20
|
+
# Attempts to mark a line item completion step as completed based on provided position.
|
|
21
|
+
def try_mark_as_completed
|
|
22
|
+
scope = if spree_current_user.nil?
|
|
23
|
+
Spree::LineItem.joins(:order).where(spree_orders: { token: params[:order_token] })
|
|
24
|
+
else
|
|
25
|
+
spree_current_user.line_items
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
line_item = scope.find(params[:id])
|
|
29
|
+
step_position = params.fetch(:position, 1).to_i
|
|
30
|
+
|
|
31
|
+
result = SpreeCmCommissioner::CompletionSteps::MarkLineItemAsCompleted.call(
|
|
32
|
+
line_item: line_item,
|
|
33
|
+
position: step_position
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if result.success?
|
|
37
|
+
render_serialized_payload { serialize_resource(result.value) }
|
|
38
|
+
else
|
|
39
|
+
render_error_payload(result.error)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
19
43
|
def allowed_sort_attributes
|
|
20
44
|
super << :to_date
|
|
21
45
|
super << :from_date
|
|
@@ -18,31 +18,20 @@ module Spree
|
|
|
18
18
|
render_serialized_payload { serialize_resource(line_item) }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# TODO: refactor app to pass token here, it is not safe to pass id & update.
|
|
22
|
+
def mark_as_completed
|
|
22
23
|
line_item = Spree::LineItem.find(params[:id])
|
|
24
|
+
step_position = params.fetch(:position, 1).to_i
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
result = SpreeCmCommissioner::CompletionSteps::MarkLineItemAsCompleted.call(
|
|
27
|
+
line_item: line_item,
|
|
28
|
+
position: step_position
|
|
29
|
+
)
|
|
26
30
|
|
|
27
|
-
if
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if step
|
|
31
|
-
if step['action_url'].present?
|
|
32
|
-
step['completed'] = true
|
|
33
|
-
metadata['completion_steps'] = steps
|
|
34
|
-
line_item.public_metadata = metadata
|
|
35
|
-
line_item.save!
|
|
36
|
-
|
|
37
|
-
render_serialized_payload { serialize_resource(line_item) }
|
|
38
|
-
else
|
|
39
|
-
render_error_payload('Action url is nil, cannot update to completed')
|
|
40
|
-
end
|
|
41
|
-
else
|
|
42
|
-
render_error_payload('Step with position 1 not found')
|
|
43
|
-
end
|
|
31
|
+
if result.success?
|
|
32
|
+
render_serialized_payload { serialize_resource(result.value) }
|
|
44
33
|
else
|
|
45
|
-
render_error_payload(
|
|
34
|
+
render_error_payload(result.error)
|
|
46
35
|
end
|
|
47
36
|
end
|
|
48
37
|
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class Seats::BlocksAreOnHoldByOtherGuestError < StandardError
|
|
3
|
+
attr_reader :block_label
|
|
4
|
+
|
|
5
|
+
def initialize(block_label = nil)
|
|
6
|
+
@block_label = block_label
|
|
7
|
+
super()
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
# override
|
|
4
11
|
def message
|
|
5
|
-
|
|
12
|
+
if block_label.present?
|
|
13
|
+
I18n.t('line_item.validation.blocks_are_on_hold_by_other_guest_with_label', label: block_label)
|
|
14
|
+
else
|
|
15
|
+
I18n.t('line_item.validation.blocks_are_on_hold_by_other_guest')
|
|
16
|
+
end
|
|
6
17
|
end
|
|
7
18
|
end
|
|
8
19
|
end
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class Seats::BlocksAreReservedByOtherGuestError < StandardError
|
|
3
|
+
attr_reader :block_label
|
|
4
|
+
|
|
5
|
+
def initialize(block_label = nil)
|
|
6
|
+
@block_label = block_label
|
|
7
|
+
super()
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
# override
|
|
4
11
|
def message
|
|
5
|
-
|
|
12
|
+
if block_label.present?
|
|
13
|
+
I18n.t('line_item.validation.blocks_are_reserved_by_other_guest_with_label', label: block_label)
|
|
14
|
+
else
|
|
15
|
+
I18n.t('line_item.validation.blocks_are_reserved_by_other_guest')
|
|
16
|
+
end
|
|
6
17
|
end
|
|
7
18
|
end
|
|
8
19
|
end
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class Seats::UnableToSaveReservedBlockRecordError < StandardError
|
|
3
|
+
attr_reader :block_label
|
|
4
|
+
|
|
5
|
+
def initialize(block_label = nil)
|
|
6
|
+
@block_label = block_label
|
|
7
|
+
super()
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
# override
|
|
4
11
|
def message
|
|
5
|
-
|
|
12
|
+
if block_label.present?
|
|
13
|
+
I18n.t('line_item.validation.unable_to_save_reserved_block_record_with_label', label: block_label)
|
|
14
|
+
else
|
|
15
|
+
I18n.t('line_item.validation.unable_to_save_reserved_block_record')
|
|
16
|
+
end
|
|
6
17
|
end
|
|
7
18
|
end
|
|
8
19
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module CompletionSteps
|
|
3
|
+
class RegenerateForLineItemsJob < ApplicationJob
|
|
4
|
+
# Thin wrapper that regenerates completion steps for all line items
|
|
5
|
+
# of a product when a ProductCompletionStep is changed.
|
|
6
|
+
# ApplicationJob handles error logging via around_perform hook.
|
|
7
|
+
def perform(options = {})
|
|
8
|
+
product = Spree::Product.find(options[:product_id])
|
|
9
|
+
|
|
10
|
+
SpreeCmCommissioner::CompletionSteps::RegenerateForLineItems.call(product: product)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -10,6 +10,7 @@ module SpreeCmCommissioner
|
|
|
10
10
|
|
|
11
11
|
state_machine.before_transition to: :complete, do: :request, if: :need_confirmation?
|
|
12
12
|
state_machine.before_transition to: :complete, do: :generate_bib_number
|
|
13
|
+
state_machine.before_transition to: :complete, do: :generate_completion_steps!, if: :product_completion_steps_exist?
|
|
13
14
|
|
|
14
15
|
state_machine.after_transition to: :complete, do: :precalculate_conversion
|
|
15
16
|
state_machine.after_transition to: :complete, do: :notify_order_complete_app_notification_to_user, unless: :subscription?
|
|
@@ -71,6 +72,15 @@ module SpreeCmCommissioner
|
|
|
71
72
|
end
|
|
72
73
|
end
|
|
73
74
|
|
|
75
|
+
def product_completion_steps_exist?
|
|
76
|
+
product_completion_steps.any?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# This is called before complete, which is already wrapped by the transaction of unstock_inventory!
|
|
80
|
+
def generate_completion_steps!
|
|
81
|
+
line_items.each(&:generate_completion_steps!)
|
|
82
|
+
end
|
|
83
|
+
|
|
74
84
|
def precalculate_conversion
|
|
75
85
|
line_items.each do |item|
|
|
76
86
|
SpreeCmCommissioner::ConversionPreCalculatorJob.perform_later(item.product_id)
|
|
@@ -37,16 +37,34 @@ module SpreeCmCommissioner
|
|
|
37
37
|
module StoreMetadata
|
|
38
38
|
extend ActiveSupport::Concern
|
|
39
39
|
|
|
40
|
+
# Custom JSON coder that gracefully handles corrupted JSON data
|
|
41
|
+
class ResilientJSONCoder
|
|
42
|
+
def self.dump(obj)
|
|
43
|
+
JSON.dump(obj)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.load(json)
|
|
47
|
+
return {} if json.blank?
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
JSON.parse(json)
|
|
51
|
+
rescue JSON::ParserError, TypeError
|
|
52
|
+
# If JSON is corrupted, return empty hash instead of raising
|
|
53
|
+
{}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
40
58
|
class_methods do # rubocop:disable Metrics/BlockLength
|
|
41
59
|
def store_private_metadata(key, type, default: nil)
|
|
42
|
-
store :private_metadata, accessors: [key], coder:
|
|
60
|
+
store :private_metadata, accessors: [key], coder: ResilientJSONCoder
|
|
43
61
|
|
|
44
62
|
define_metadata_reader(:private_metadata, key, type, default)
|
|
45
63
|
define_metadata_validation(:private_metadata, key, type)
|
|
46
64
|
end
|
|
47
65
|
|
|
48
66
|
def store_public_metadata(key, type, default: nil)
|
|
49
|
-
store :public_metadata, accessors: [key], coder:
|
|
67
|
+
store :public_metadata, accessors: [key], coder: ResilientJSONCoder
|
|
50
68
|
|
|
51
69
|
define_metadata_reader(:public_metadata, key, type, default)
|
|
52
70
|
define_metadata_validation(:public_metadata, key, type)
|
|
@@ -26,8 +26,6 @@ module SpreeCmCommissioner
|
|
|
26
26
|
|
|
27
27
|
base.before_save :update_vendor_id
|
|
28
28
|
|
|
29
|
-
base.after_commit :persist_completion_steps_to_public_metadata, on: :create
|
|
30
|
-
|
|
31
29
|
base.before_create :add_due_date, if: :subscription?
|
|
32
30
|
|
|
33
31
|
base.validate :ensure_not_exceed_max_quantity_per_order, if: -> { variant&.max_quantity_per_order.present? }
|
|
@@ -39,6 +37,8 @@ module SpreeCmCommissioner
|
|
|
39
37
|
base.delegate :delivery_required?, :high_demand,
|
|
40
38
|
to: :variant
|
|
41
39
|
|
|
40
|
+
base.store_public_metadata :completion_steps, :array
|
|
41
|
+
|
|
42
42
|
base.accepts_nested_attributes_for :guests, allow_destroy: true
|
|
43
43
|
|
|
44
44
|
def base.json_api_columns
|
|
@@ -66,6 +66,7 @@ module SpreeCmCommissioner
|
|
|
66
66
|
|
|
67
67
|
def self.include_modules(base)
|
|
68
68
|
base.include Spree::Core::NumberGenerator.new(prefix: 'L')
|
|
69
|
+
base.include SpreeCmCommissioner::StoreMetadata
|
|
69
70
|
base.include SpreeCmCommissioner::LineItemDurationable
|
|
70
71
|
base.include SpreeCmCommissioner::LineItemsFilterScope
|
|
71
72
|
base.include SpreeCmCommissioner::LineItemGuestsConcern
|
|
@@ -197,8 +198,29 @@ module SpreeCmCommissioner
|
|
|
197
198
|
"#{order.number}-#{order.token}-L#{id}"
|
|
198
199
|
end
|
|
199
200
|
|
|
200
|
-
def
|
|
201
|
-
|
|
201
|
+
def generate_completion_steps
|
|
202
|
+
generate_completion_steps!
|
|
203
|
+
true
|
|
204
|
+
rescue StandardError => e
|
|
205
|
+
CmAppLogger.error(
|
|
206
|
+
label: "#{self.class.name}#generate_completion_steps",
|
|
207
|
+
data: {
|
|
208
|
+
line_item_id: id,
|
|
209
|
+
order_id: order_id,
|
|
210
|
+
error_class: e.class.name,
|
|
211
|
+
error_message: e.message,
|
|
212
|
+
backtrace: e.backtrace&.first(10)&.join("\n")
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
errors.add(:completion_steps, e.message)
|
|
216
|
+
false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def generate_completion_steps!
|
|
220
|
+
self.completion_steps = product_completion_steps.map do |completion_step|
|
|
221
|
+
completion_step.construct_hash(line_item: self)
|
|
222
|
+
end
|
|
223
|
+
save!
|
|
202
224
|
end
|
|
203
225
|
|
|
204
226
|
# override
|
|
@@ -217,16 +239,6 @@ module SpreeCmCommissioner
|
|
|
217
239
|
|
|
218
240
|
private
|
|
219
241
|
|
|
220
|
-
def persist_completion_steps_to_public_metadata
|
|
221
|
-
steps = product_completion_steps.order(:position).map do |pcs|
|
|
222
|
-
pcs.construct_hash(line_item: self).except(:completed).merge(
|
|
223
|
-
id: pcs.id,
|
|
224
|
-
completed: false
|
|
225
|
-
)
|
|
226
|
-
end
|
|
227
|
-
update_column(:public_metadata, (public_metadata || {}).merge(completion_steps: steps)) # rubocop:disable Rails/SkipsModelValidations
|
|
228
|
-
end
|
|
229
|
-
|
|
230
242
|
def ensure_not_exceed_max_quantity_per_order
|
|
231
243
|
errors.add(:quantity, I18n.t('line_item.validation.exceeded_max_quantity_per_order')) if quantity > variant.max_quantity_per_order
|
|
232
244
|
end
|
|
@@ -57,6 +57,7 @@ module SpreeCmCommissioner
|
|
|
57
57
|
base.has_many :blocks, through: :guests, class_name: 'SpreeCmCommissioner::Block', source: :block
|
|
58
58
|
base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
|
|
59
59
|
base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
|
|
60
|
+
base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', through: :line_items
|
|
60
61
|
|
|
61
62
|
base.delegate :customer, to: :user, allow_nil: true
|
|
62
63
|
|
|
@@ -4,9 +4,10 @@ module SpreeCmCommissioner
|
|
|
4
4
|
|
|
5
5
|
belongs_to :product, class_name: '::Spree::Product', optional: false
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# When a completion step is changed, regenerate completion steps for all line items
|
|
8
|
+
# of the product so they reflect the latest step configuration.
|
|
9
|
+
after_destroy :regenerate_line_items_completion_steps
|
|
10
|
+
after_save :regenerate_line_items_completion_steps
|
|
10
11
|
|
|
11
12
|
def action_url_for(_line_item)
|
|
12
13
|
nil
|
|
@@ -17,6 +18,8 @@ module SpreeCmCommissioner
|
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def construct_hash(line_item:)
|
|
21
|
+
existing_data = line_item.completion_steps&.find { |step| step['position'].to_i == position }
|
|
22
|
+
|
|
20
23
|
{
|
|
21
24
|
title: title,
|
|
22
25
|
type: type&.underscore,
|
|
@@ -24,41 +27,15 @@ module SpreeCmCommissioner
|
|
|
24
27
|
description: description,
|
|
25
28
|
action_label: action_label,
|
|
26
29
|
action_url: action_url_for(line_item),
|
|
30
|
+
completed_at: existing_data&.dig('completed_at'),
|
|
27
31
|
completed: completed?(line_item)
|
|
28
32
|
}
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
private
|
|
32
36
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
propagate_to_line_item(line_item)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def propagate_to_line_item(line_item)
|
|
40
|
-
current_metadata = line_item.reload.public_metadata || {}
|
|
41
|
-
stored_steps = Array(current_metadata[:completion_steps])
|
|
42
|
-
|
|
43
|
-
fresh_steps = product.product_completion_steps.order(:position).map do |pcs|
|
|
44
|
-
base_attrs = pcs.construct_hash(line_item: line_item).except(:completed)
|
|
45
|
-
|
|
46
|
-
previous =
|
|
47
|
-
stored_steps.find { |s| s[:id] == pcs.id } ||
|
|
48
|
-
stored_steps.find { |s| s[:type] == pcs.type&.underscore && s[:position] == pcs.position }
|
|
49
|
-
|
|
50
|
-
completed_flag = previous&.dig(:completed) ? true : false
|
|
51
|
-
|
|
52
|
-
base_attrs.merge(
|
|
53
|
-
id: pcs.id,
|
|
54
|
-
completed: completed_flag
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
line_item.update_column( # rubocop:disable Rails/SkipsModelValidations
|
|
59
|
-
:public_metadata,
|
|
60
|
-
current_metadata.merge(completion_steps: fresh_steps)
|
|
61
|
-
)
|
|
37
|
+
def regenerate_line_items_completion_steps
|
|
38
|
+
SpreeCmCommissioner::CompletionSteps::RegenerateForLineItemsJob.perform_later(product_id: product_id)
|
|
62
39
|
end
|
|
63
40
|
end
|
|
64
41
|
end
|
|
@@ -1,16 +1,103 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module ProductCompletionSteps
|
|
3
3
|
class SocialEntryUrl < ProductCompletionStep
|
|
4
|
-
#
|
|
4
|
+
# Template format examples:
|
|
5
|
+
# - https://t.me/ThePlatformKHBot?start={order.number}
|
|
6
|
+
# - https://example.com?guest_name={guests[0].first_name}&phone={guests[0].phone_number}
|
|
7
|
+
# - https://example.com?line_item_id={line_item.id}&quantity={line_item.quantity}
|
|
5
8
|
preference :entry_point_link, :string
|
|
6
9
|
|
|
10
|
+
# Allowed fields per object (excludes private/reference fields like *_id, *_by_id, metadata, etc.)
|
|
11
|
+
ORDER_ALLOWED_FIELDS = %w[
|
|
12
|
+
qr_data
|
|
13
|
+
number
|
|
14
|
+
state
|
|
15
|
+
email
|
|
16
|
+
total
|
|
17
|
+
item_total
|
|
18
|
+
created_at
|
|
19
|
+
completed_at
|
|
20
|
+
token
|
|
21
|
+
phone_number
|
|
22
|
+
country_code
|
|
23
|
+
currency
|
|
24
|
+
item_count
|
|
25
|
+
channel
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
LINE_ITEM_ALLOWED_FIELDS = %w[
|
|
29
|
+
qr_data
|
|
30
|
+
quantity
|
|
31
|
+
price
|
|
32
|
+
from_date
|
|
33
|
+
to_date
|
|
34
|
+
number
|
|
35
|
+
remark
|
|
36
|
+
created_at
|
|
37
|
+
updated_at
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
GUEST_ALLOWED_FIELDS = %w[
|
|
41
|
+
qr_data
|
|
42
|
+
first_name
|
|
43
|
+
last_name
|
|
44
|
+
full_name
|
|
45
|
+
dob
|
|
46
|
+
gender
|
|
47
|
+
age
|
|
48
|
+
email
|
|
49
|
+
phone_number
|
|
50
|
+
address
|
|
51
|
+
seat_number
|
|
52
|
+
bib_number
|
|
53
|
+
bib_prefix
|
|
54
|
+
bib_index
|
|
55
|
+
formatted_bib_number
|
|
56
|
+
token
|
|
57
|
+
social_contact
|
|
58
|
+
country_code
|
|
59
|
+
emergency_contact
|
|
60
|
+
expectation
|
|
61
|
+
other_occupation
|
|
62
|
+
other_organization
|
|
63
|
+
entry_type
|
|
64
|
+
upload_later
|
|
65
|
+
data_fill_stage_phase
|
|
66
|
+
created_at
|
|
67
|
+
updated_at
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
7
70
|
# override
|
|
8
|
-
def action_url_for(
|
|
9
|
-
preferred_entry_point_link.
|
|
71
|
+
def action_url_for(line_item)
|
|
72
|
+
return nil if preferred_entry_point_link.blank?
|
|
73
|
+
|
|
74
|
+
url = preferred_entry_point_link.dup
|
|
75
|
+
|
|
76
|
+
# Replace order placeholders: {order.field_name}
|
|
77
|
+
line_item.order.attributes.each do |field, value|
|
|
78
|
+
url.gsub!("{order.#{field}}", value.to_s) if ORDER_ALLOWED_FIELDS.include?(field)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Replace line_item placeholders: {line_item.field_name}
|
|
82
|
+
line_item.attributes.each do |field, value|
|
|
83
|
+
url.gsub!("{line_item.#{field}}", value.to_s) if LINE_ITEM_ALLOWED_FIELDS.include?(field)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Replace guest placeholders: {guests[index].field_name}
|
|
87
|
+
line_item.guests.each_with_index do |guest, index|
|
|
88
|
+
guest.attributes.each do |field, value|
|
|
89
|
+
url.gsub!("{guests[#{index}].#{field}}", value.to_s) if GUEST_ALLOWED_FIELDS.include?(field)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
url
|
|
10
94
|
end
|
|
11
95
|
|
|
12
|
-
def completed?(
|
|
13
|
-
false
|
|
96
|
+
def completed?(line_item)
|
|
97
|
+
return false if line_item.completion_steps.blank?
|
|
98
|
+
|
|
99
|
+
step_data = line_item.completion_steps.find { |step| step['position'].to_s == position.to_s }
|
|
100
|
+
step_data&.dig('completed_at').present?
|
|
14
101
|
end
|
|
15
102
|
end
|
|
16
103
|
end
|
|
@@ -16,12 +16,13 @@ module SpreeCmCommissioner
|
|
|
16
16
|
|
|
17
17
|
ActiveRecord::Base.transaction do
|
|
18
18
|
order.reserved_blocks.each do |reserved_block|
|
|
19
|
+
block_label = reserved_block.block.label
|
|
19
20
|
reserved_block.assign_attributes(
|
|
20
21
|
status: :canceled,
|
|
21
22
|
expired_at: nil,
|
|
22
23
|
updated_by: @cancel_by
|
|
23
24
|
)
|
|
24
|
-
raise SpreeCmCommissioner::Seats::UnableToSaveReservedBlockRecordError unless reserved_block.save
|
|
25
|
+
raise SpreeCmCommissioner::Seats::UnableToSaveReservedBlockRecordError, block_label unless reserved_block.save
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -39,10 +39,15 @@ module SpreeCmCommissioner
|
|
|
39
39
|
|
|
40
40
|
def hold_specific_block!(inventory_item, guest)
|
|
41
41
|
reserved_block = SpreeCmCommissioner::ReservedBlock.find_or_initialize_by(inventory_item: inventory_item, block: guest.block)
|
|
42
|
+
block_label = guest.block.label
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
if reserved_block.reserved? && reserved_block.guest_id != guest.id
|
|
45
|
+
raise SpreeCmCommissioner::Seats::BlocksAreReservedByOtherGuestError, block_label
|
|
46
|
+
end
|
|
44
47
|
raise SpreeCmCommissioner::Seats::BlocksAreReservedBySameGuestError if reserved_block.reserved? && reserved_block.guest_id == guest.id
|
|
45
|
-
|
|
48
|
+
if reserved_block.active_on_hold? && reserved_block.guest_id != guest.id
|
|
49
|
+
raise SpreeCmCommissioner::Seats::BlocksAreOnHoldByOtherGuestError, block_label
|
|
50
|
+
end
|
|
46
51
|
|
|
47
52
|
reserved_block.assign_attributes(
|
|
48
53
|
status: :on_hold,
|
|
@@ -52,7 +57,7 @@ module SpreeCmCommissioner
|
|
|
52
57
|
updated_by: @hold_by
|
|
53
58
|
)
|
|
54
59
|
|
|
55
|
-
raise SpreeCmCommissioner::Seats::UnableToSaveReservedBlockRecordError unless reserved_block.save
|
|
60
|
+
raise SpreeCmCommissioner::Seats::UnableToSaveReservedBlockRecordError, block_label unless reserved_block.save
|
|
56
61
|
|
|
57
62
|
reserved_block
|
|
58
63
|
end
|
|
@@ -30,10 +30,15 @@ module SpreeCmCommissioner
|
|
|
30
30
|
|
|
31
31
|
def reserve_specific_block!(inventory_item, guest)
|
|
32
32
|
reserved_block = SpreeCmCommissioner::ReservedBlock.find_or_initialize_by(inventory_item_id: inventory_item.id, block_id: guest.block_id)
|
|
33
|
+
block_label = guest.block.label
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
if reserved_block.reserved? && reserved_block.guest_id != guest.id
|
|
36
|
+
raise SpreeCmCommissioner::Seats::BlocksAreReservedByOtherGuestError, block_label
|
|
37
|
+
end
|
|
35
38
|
raise SpreeCmCommissioner::Seats::BlocksAreReservedBySameGuestError if reserved_block.reserved? && reserved_block.guest_id == guest.id
|
|
36
|
-
|
|
39
|
+
if reserved_block.active_on_hold? && reserved_block.guest_id != guest.id
|
|
40
|
+
raise SpreeCmCommissioner::Seats::BlocksAreOnHoldByOtherGuestError, block_label
|
|
41
|
+
end
|
|
37
42
|
|
|
38
43
|
# mark the block as reserved if not on_hold or lock by anyone but already expired
|
|
39
44
|
reserved_block.assign_attributes(
|
|
@@ -44,7 +49,7 @@ module SpreeCmCommissioner
|
|
|
44
49
|
updated_by: @reserve_by
|
|
45
50
|
)
|
|
46
51
|
|
|
47
|
-
raise SpreeCmCommissioner::Seats::UnableToSaveReservedBlockRecordError unless reserved_block.save
|
|
52
|
+
raise SpreeCmCommissioner::Seats::UnableToSaveReservedBlockRecordError, block_label unless reserved_block.save
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
end
|
|
@@ -4,7 +4,7 @@ module Spree
|
|
|
4
4
|
module OrderSerializerDecorator
|
|
5
5
|
def self.prepended(base)
|
|
6
6
|
base.attribute :has_incomplete_guest_info do |order|
|
|
7
|
-
order.user
|
|
7
|
+
order.user&.public_metadata&.[]('has_incomplete_guest_info') || false
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
base.attribute :guest_info_status do |order|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module CompletionSteps
|
|
3
|
+
class MarkLineItemAsCompleted
|
|
4
|
+
prepend ::Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
def call(line_item:, position: 1)
|
|
7
|
+
ApplicationRecord.transaction do
|
|
8
|
+
validate_completion_steps!(line_item)
|
|
9
|
+
validate_step_exists!(line_item, position)
|
|
10
|
+
validate_action_url!(line_item, position)
|
|
11
|
+
|
|
12
|
+
# this will set completed_at timestamp to the step in line item
|
|
13
|
+
mark_step_completed!(line_item, position)
|
|
14
|
+
|
|
15
|
+
# regenerate completion steps to check whether is consider completed.
|
|
16
|
+
# because some steps, just completed at is not enough.
|
|
17
|
+
line_item.generate_completion_steps!
|
|
18
|
+
|
|
19
|
+
success(line_item)
|
|
20
|
+
end
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
failure(nil, e.message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def validate_completion_steps!(line_item)
|
|
28
|
+
raise 'Completion steps not found for this line item' if line_item.completion_steps.blank?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate_step_exists!(line_item, position)
|
|
32
|
+
raise "Step at position #{position} not found" if step_data(line_item, position).blank?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate_action_url!(line_item, position)
|
|
36
|
+
raise 'Action URL is missing for this step' if step_data(line_item, position)['action_url'].blank?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def mark_step_completed!(line_item, position)
|
|
40
|
+
step_data(line_item, position)['completed_at'] = Time.current.iso8601
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def step_data(line_item, position)
|
|
44
|
+
line_item.completion_steps.find { |step| step['position'].to_s == position.to_s }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module CompletionSteps
|
|
3
|
+
class RegenerateForLineItems
|
|
4
|
+
prepend ::Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
# Regenerates completion steps for all completed line items of a product.
|
|
7
|
+
# Called when a ProductCompletionStep is created, updated, or destroyed.
|
|
8
|
+
def call(product:)
|
|
9
|
+
line_items = product.line_items.complete
|
|
10
|
+
|
|
11
|
+
CmAppLogger.log(
|
|
12
|
+
label: 'SpreeCmCommissioner::CompletionSteps::RegenerateForLineItems#call Starting',
|
|
13
|
+
data: {
|
|
14
|
+
product_id: product.id,
|
|
15
|
+
line_items_count: line_items.count
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
regenerated_count = 0
|
|
20
|
+
failed_count = 0
|
|
21
|
+
|
|
22
|
+
line_items.find_each do |line_item|
|
|
23
|
+
line_item.generate_completion_steps!
|
|
24
|
+
regenerated_count += 1
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
CmAppLogger.error(
|
|
27
|
+
label: 'SpreeCmCommissioner::CompletionSteps::RegenerateForLineItems#call Line item regeneration failed',
|
|
28
|
+
data: {
|
|
29
|
+
product_id: product.id,
|
|
30
|
+
line_item_id: line_item.id,
|
|
31
|
+
error_class: e.class.name,
|
|
32
|
+
error_message: e.message
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
failed_count += 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
CmAppLogger.log(
|
|
39
|
+
label: 'SpreeCmCommissioner::CompletionSteps::RegenerateForLineItems#call Completed',
|
|
40
|
+
data: {
|
|
41
|
+
product_id: product.id,
|
|
42
|
+
regenerated_count: regenerated_count,
|
|
43
|
+
failed_count: failed_count
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
success(regenerated_count: regenerated_count, failed_count: failed_count)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
failure(nil, e.message)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/config/locales/en.yml
CHANGED
|
@@ -124,9 +124,12 @@ en:
|
|
|
124
124
|
exceeded_max_quantity_per_order: "Exceeded maximum quantity per order"
|
|
125
125
|
seats_are_required: "Seats are required for all guests"
|
|
126
126
|
blocks_are_reserved_by_other_guest: "Seats were recently reserved by another guest"
|
|
127
|
+
blocks_are_reserved_by_other_guest_with_label: "Seat %{label} was recently reserved by another guest"
|
|
127
128
|
blocks_are_on_hold_by_other_guest: "Seats were recently put on hold by another guest"
|
|
129
|
+
blocks_are_on_hold_by_other_guest_with_label: "Seat %{label} was recently put on hold by another guest"
|
|
128
130
|
blocks_are_reserved_by_same_guest: "Seats were recently reserved by this guest"
|
|
129
131
|
unable_to_save_reserved_block_record: "Unable to save seat reservation"
|
|
132
|
+
unable_to_save_reserved_block_record_with_label: "Unable to save reservation for seat %{label}"
|
|
130
133
|
|
|
131
134
|
vectors:
|
|
132
135
|
icons:
|
data/config/locales/km.yml
CHANGED
|
@@ -118,9 +118,12 @@ km:
|
|
|
118
118
|
exceeded_max_quantity_per_order: "លើសពីបរិមាណអតិបរមាក្នុងមួយការបញ្ជាទិញ"
|
|
119
119
|
seats_are_required: "ត្រូវការលេខកៅអីសម្រាប់ភ្ញៀវទាំងអស់"
|
|
120
120
|
blocks_are_reserved_by_other_guest: "កៅអីត្រូវបានកក់ថ្មីៗដោយភ្ញៀវផ្សេងទៀត"
|
|
121
|
+
blocks_are_reserved_by_other_guest_with_label: "កៅអី %{label} ត្រូវបានកក់ថ្មីៗដោយភ្ញៀវផ្សេងទៀត"
|
|
121
122
|
blocks_are_on_hold_by_other_guest: "កៅអីត្រូវបានផ្អាកថ្មីៗដោយភ្ញៀវផ្សេងទៀត"
|
|
123
|
+
blocks_are_on_hold_by_other_guest_with_label: "កៅអី %{label} ត្រូវបានផ្អាកថ្មីៗដោយភ្ញៀវផ្សេងទៀត"
|
|
122
124
|
blocks_are_reserved_by_same_guest: "កៅអីត្រូវបានកក់ថ្មីៗដោយភ្ញៀវនេះ"
|
|
123
125
|
unable_to_save_reserved_block_record: "មិនអាចរក្សាទុកការកក់កៅអីបានទេ"
|
|
126
|
+
unable_to_save_reserved_block_record_with_label: "មិនអាចរក្សាទុកការកក់សម្រាប់កៅអី %{label} បានទេ"
|
|
124
127
|
|
|
125
128
|
subscription:
|
|
126
129
|
validation:
|
data/config/routes.rb
CHANGED
|
@@ -595,7 +595,12 @@ Spree::Core::Engine.add_routes do
|
|
|
595
595
|
resources :variants, only: %i[index show], module: :accommodations
|
|
596
596
|
end
|
|
597
597
|
|
|
598
|
-
resources :line_items, only: %i[index show]
|
|
598
|
+
resources :line_items, only: %i[index show] do
|
|
599
|
+
member do
|
|
600
|
+
patch :try_mark_as_completed
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
599
604
|
resources :account_checker
|
|
600
605
|
resource :account_recovers, only: [:update]
|
|
601
606
|
|
|
@@ -8,4 +8,11 @@ FactoryBot.define do
|
|
|
8
8
|
preferred_entry_point_link { 'https://t.me/ThePlatformKHBot?start=bookmeplus' }
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
factory :cm_social_entry_url_product_completion_step, class: SpreeCmCommissioner::ProductCompletionSteps::SocialEntryUrl do
|
|
13
|
+
title { 'Must complete this step' }
|
|
14
|
+
description { 'Click open now to complete!' }
|
|
15
|
+
action_label { 'Open' }
|
|
16
|
+
preferred_entry_point_link { 'https://www.mywebsite?order_number={order.number}&line_item_number={line_item.number}' }
|
|
17
|
+
end
|
|
11
18
|
end
|
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.3.0.pre.
|
|
4
|
+
version: 2.3.0.pre.pre12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|
|
@@ -1289,6 +1289,7 @@ files:
|
|
|
1289
1289
|
- app/jobs/spree_cm_commissioner/application_job_decorator.rb
|
|
1290
1290
|
- app/jobs/spree_cm_commissioner/application_unique_job.rb
|
|
1291
1291
|
- app/jobs/spree_cm_commissioner/chatrace_order_creator_job.rb
|
|
1292
|
+
- app/jobs/spree_cm_commissioner/completion_steps/regenerate_for_line_items_job.rb
|
|
1292
1293
|
- app/jobs/spree_cm_commissioner/conversion_pre_calculator_job.rb
|
|
1293
1294
|
- app/jobs/spree_cm_commissioner/customer_content_notification_creator_job.rb
|
|
1294
1295
|
- app/jobs/spree_cm_commissioner/customer_notification_cron_job.rb
|
|
@@ -1917,6 +1918,8 @@ files:
|
|
|
1917
1918
|
- app/services/spree_cm_commissioner/cart/remove_guest.rb
|
|
1918
1919
|
- app/services/spree_cm_commissioner/checkout/advance_decorator.rb
|
|
1919
1920
|
- app/services/spree_cm_commissioner/checkout/update_decorator.rb
|
|
1921
|
+
- app/services/spree_cm_commissioner/completion_steps/mark_line_item_as_completed.rb
|
|
1922
|
+
- app/services/spree_cm_commissioner/completion_steps/regenerate_for_line_items.rb
|
|
1920
1923
|
- app/services/spree_cm_commissioner/exports/export_guest_csv_service.rb
|
|
1921
1924
|
- app/services/spree_cm_commissioner/exports/export_order_csv_service.rb
|
|
1922
1925
|
- app/services/spree_cm_commissioner/feed.rb
|