spree_cm_commissioner 2.5.13.pre.pre.pre.3 → 2.5.13
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/.env.example +3 -0
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/classifications_controller.rb +10 -3
- data/app/controllers/spree/admin/inventory_items_controller.rb +3 -3
- data/app/controllers/spree/admin/inventory_monitorings_controller.rb +39 -0
- data/app/controllers/spree/admin/stock_managements_controller.rb +5 -1
- data/app/controllers/spree/api/v2/operator/recalculate_tickets_controller.rb +4 -9
- data/app/controllers/spree/api/v2/tenant/guests_controller.rb +3 -6
- data/app/controllers/spree_cm_commissioner/orders_controller.rb +14 -2
- data/app/finders/spree_cm_commissioner/inventory_items/recently_changed_finder.rb +88 -0
- data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +11 -1
- data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +7 -3
- data/app/jobs/spree_cm_commissioner/maintenance_tasks/orchestrate_job.rb +28 -0
- data/app/jobs/spree_cm_commissioner/maintenance_tasks/process_job.rb +18 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +16 -11
- data/app/models/concerns/spree_cm_commissioner/product_relation_type.rb +29 -0
- data/app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb +123 -37
- data/app/models/spree_cm_commissioner/inventory_item.rb +5 -3
- data/app/models/spree_cm_commissioner/maintenance_task.rb +62 -0
- data/app/models/spree_cm_commissioner/maintenance_tasks/event.rb +61 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +8 -0
- data/app/models/spree_cm_commissioner/pricing_model.rb +1 -1
- data/app/models/spree_cm_commissioner/product_decorator.rb +5 -2
- data/app/models/spree_cm_commissioner/product_relation.rb +23 -0
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +1 -0
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +20 -6
- data/app/overrides/spree/admin/shared/sub_menu/_stock/inventory_monitorings_tab.html.erb.deface +3 -0
- data/app/serializers/spree/api/v2/platform/create_guest_adjustment_serializer.rb +10 -0
- data/app/serializers/spree/api/v2/platform/create_line_item_adjustment_serializer.rb +10 -0
- data/app/serializers/spree/api/v2/platform/create_route_adjustment_serializer.rb +10 -0
- data/app/serializers/spree/api/v2/platform/pricing_action_serializer.rb +15 -0
- data/app/serializers/spree/v2/tenant/cart_serializer.rb +4 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +39 -0
- data/app/services/spree_cm_commissioner/pricing_models/update_with_rule_groups.rb +1 -6
- data/app/services/spree_cm_commissioner/trips/clone.rb +195 -0
- data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +2 -0
- data/app/views/spree/admin/inventory_monitorings/index.html.erb +119 -0
- data/app/views/spree/admin/stock_managements/index.html.erb +1 -1
- data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +15 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +5 -4
- data/db/migrate/20260202095500_create_cm_product_relations.rb +32 -0
- data/db/migrate/20260218100000_create_cm_maintenance_tasks.rb +23 -0
- data/lib/spree_cm_commissioner/cached_inventory_item.rb +8 -7
- data/lib/spree_cm_commissioner/test_helper/factories/product_relation_factory.rb +9 -0
- data/lib/spree_cm_commissioner/transit/trip_form.rb +1 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +23 -14
- data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +0 -63
- data/app/interactors/spree_cm_commissioner/conversion_pre_calculator.rb +0 -64
- data/app/interactors/spree_cm_commissioner/enqueue_cart/add_item.rb +0 -43
- data/app/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker.rb +0 -66
- data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +0 -138
- data/app/jobs/spree_cm_commissioner/conversion_pre_calculator_job.rb +0 -12
- data/app/jobs/spree_cm_commissioner/enqueue_cart/add_item_job.rb +0 -17
- data/app/serializables/spree_cm_commissioner/queue_item.rb +0 -13
- data/app/serializers/spree/v2/storefront/cart_serializer_decorator.rb +0 -23
- data/app/serializers/spree/v2/storefront/firestore_queue_serializer.rb +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e4173db9eaef31ba44153b210477bd83e359559c6b007f644723d320e9e8f30
|
|
4
|
+
data.tar.gz: 38aba375ecb1177922443efbe00c09e923c75f3f2547c3df5cb383378e45a8b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 698f5ca602839ec3b8293cd2aecf31a1cc89d6125c16b831a8500ec0aa8c01d705bf15bc215dde6cd50de568c3d505803cf97ad4c4c6a4493f7018f7b535dde1
|
|
7
|
+
data.tar.gz: ef9ffdee2d499c102c1f8e2c7166d3ed0d3ccea24f9f6259d74555f4cdc8f7646b9e97ae373710f1412cf256f510e0296259e44fa5d693a19d927b9eecf51396
|
data/.env.example
CHANGED
|
@@ -40,3 +40,6 @@ CACHE_SEMI_STATIC_MAX_AGE=3600 # Semi-static content (menus, homepage background
|
|
|
40
40
|
CACHE_MODERATE_MAX_AGE=1800 # Moderate freshness (products, events, vendors) - 30 minutes
|
|
41
41
|
CACHE_REALTIME_MAX_AGE=300 # High freshness (trips, trip search) - 5 minutes
|
|
42
42
|
CACHE_DEFAULT_MAX_AGE=300 # Default for unlisted controllers - 5 minutes
|
|
43
|
+
|
|
44
|
+
# Batch size for processing maintenance tasks in SpreeCmCommissioner::MaintenanceTasks::OrchestrateJob
|
|
45
|
+
MAINTENANCE_TASKS_BATCH_SIZE=100
|
data/Gemfile.lock
CHANGED
|
@@ -4,11 +4,18 @@ module Spree
|
|
|
4
4
|
before_action :load_taxon_and_products
|
|
5
5
|
|
|
6
6
|
def recalculate_conversions
|
|
7
|
-
@taxon.
|
|
8
|
-
SpreeCmCommissioner::
|
|
7
|
+
if @taxon.parent.event?
|
|
8
|
+
SpreeCmCommissioner::MaintenanceTasks::Event.pending.find_or_create_by(
|
|
9
|
+
maintainable_type: 'Spree::Taxon',
|
|
10
|
+
maintainable_id: @taxon.parent.id,
|
|
11
|
+
manually_triggered: true
|
|
12
|
+
).async_execute
|
|
13
|
+
|
|
14
|
+
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
|
15
|
+
else
|
|
16
|
+
flash[:error] = 'Conversion recalculation can only be performed for taxons under an event.' # rubocop:disable Rails/I18nLocaleTexts
|
|
9
17
|
end
|
|
10
18
|
|
|
11
|
-
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
|
12
19
|
redirect_to collection_url
|
|
13
20
|
end
|
|
14
21
|
|
|
@@ -42,8 +42,8 @@ module Spree
|
|
|
42
42
|
inventory_items = @product.inventory_items.where(id: inventory_item_ids)
|
|
43
43
|
target_quantity = params[:quantity].to_i
|
|
44
44
|
|
|
45
|
-
if target_quantity.
|
|
46
|
-
flash[:error] = "Target quantity (#{target_quantity}) must be
|
|
45
|
+
if target_quantity.negative?
|
|
46
|
+
flash[:error] = "Target quantity (#{target_quantity}) must not be negative"
|
|
47
47
|
return redirect_back(fallback_location: admin_product_stock_managements_path(@product))
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -52,7 +52,7 @@ module Spree
|
|
|
52
52
|
quantity_change = target_quantity - inventory_item.quantity_available
|
|
53
53
|
|
|
54
54
|
# Use adjust_quantity! to update max_capacity, quantity_available, and sync to Redis
|
|
55
|
-
inventory_item.adjust_quantity!(quantity_change)
|
|
55
|
+
inventory_item.adjust_quantity!(quantity_change)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
flash[:success] = "Successfully updated stocks for #{inventory_items.count} items"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class InventoryMonitoringsController < BaseController
|
|
4
|
+
def index
|
|
5
|
+
authorize! :manage, SpreeCmCommissioner::InventoryItem
|
|
6
|
+
|
|
7
|
+
@time_range = params[:time_range] || 7
|
|
8
|
+
@filter_type = params[:filter_type] || 'all'
|
|
9
|
+
@vendor_id = params[:vendor_id]
|
|
10
|
+
|
|
11
|
+
finder = SpreeCmCommissioner::InventoryItems::RecentlyChangedFinder.new(
|
|
12
|
+
time_range: @time_range.to_i.days.ago,
|
|
13
|
+
limit: 1000,
|
|
14
|
+
vendor_id: @vendor_id,
|
|
15
|
+
filter_type: @filter_type
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
@inventory_items = finder.execute
|
|
19
|
+
@total_count = @inventory_items.size
|
|
20
|
+
@out_of_sync_count = @inventory_items.count { |item| item[:out_of_sync] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset
|
|
24
|
+
authorize! :manage, SpreeCmCommissioner::InventoryItem
|
|
25
|
+
|
|
26
|
+
inventory_item = SpreeCmCommissioner::InventoryItem.find(params[:id])
|
|
27
|
+
result = SpreeCmCommissioner::Stock::InventoryItemResetter.call(inventory_item: inventory_item)
|
|
28
|
+
|
|
29
|
+
if result.success?
|
|
30
|
+
flash[:success] = "Successfully reset inventory for #{inventory_item.variant.product.name}"
|
|
31
|
+
else
|
|
32
|
+
flash[:error] = "Failed to reset inventory: #{result.message}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
redirect_to action: :index, time_range: params[:time_range], filter_type: params[:filter_type]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -14,7 +14,11 @@ module Spree
|
|
|
14
14
|
def index
|
|
15
15
|
@variants = @product.variants.includes(:images, :default_price, stock_items: :stock_location, option_values: :option_type)
|
|
16
16
|
@variants = [@product.master] if @variants.empty?
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
vendor_stock_locations = @product.vendor&.stock_locations || []
|
|
19
|
+
variant_stock_locations = @variants.flat_map(&:stock_locations).uniq
|
|
20
|
+
|
|
21
|
+
@stock_locations = (variant_stock_locations + vendor_stock_locations).uniq
|
|
18
22
|
|
|
19
23
|
load_inventories unless @product.permanent_stock?
|
|
20
24
|
end
|
|
@@ -7,13 +7,9 @@ module Spree
|
|
|
7
7
|
before_action :load_taxon, only: :create
|
|
8
8
|
|
|
9
9
|
def create
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
classification_ids.each do |classification_id|
|
|
15
|
-
SpreeCmCommissioner::ConversionPreCalculator.call(product_taxon: Spree::Classification.find(classification_id))
|
|
16
|
-
end
|
|
10
|
+
SpreeCmCommissioner::MaintenanceTasks::Event.pending.find_or_create_by(
|
|
11
|
+
maintainable: @taxon
|
|
12
|
+
).async_execute
|
|
17
13
|
|
|
18
14
|
render json: { message: 'Conversions recalculated successfully' }, status: :ok
|
|
19
15
|
end
|
|
@@ -21,8 +17,7 @@ module Spree
|
|
|
21
17
|
private
|
|
22
18
|
|
|
23
19
|
def load_taxon
|
|
24
|
-
|
|
25
|
-
@taxons = parent_taxon.children
|
|
20
|
+
@taxon = Spree::Taxon.find(params[:taxon_id])
|
|
26
21
|
end
|
|
27
22
|
end
|
|
28
23
|
end
|
|
@@ -34,9 +34,9 @@ module Spree
|
|
|
34
34
|
|
|
35
35
|
ActiveRecord::Base.transaction do
|
|
36
36
|
resource.save!
|
|
37
|
+
recalculate_cart(resource.line_item)
|
|
37
38
|
run_dynamic_fields(resource)
|
|
38
39
|
resource.save_and_move_to_next_stage
|
|
39
|
-
recalculate_cart(resource.line_item)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
render_serialized_payload(201) { serialize_resource(resource) }
|
|
@@ -49,8 +49,8 @@ module Spree
|
|
|
49
49
|
run_dynamic_fields(resource) if guest_params[:guest_dynamic_fields_attributes]
|
|
50
50
|
resource.assign_attributes(guest_params.except(:guest_dynamic_fields_attributes))
|
|
51
51
|
resource.save!
|
|
52
|
-
resource.save_and_move_to_next_stage
|
|
53
52
|
recalculate_cart(resource.line_item)
|
|
53
|
+
resource.save_and_move_to_next_stage
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
render_serialized_payload { serialize_resource(resource) }
|
|
@@ -117,10 +117,7 @@ module Spree
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def recalculate_cart(line_item)
|
|
120
|
-
::Spree::Dependencies.cart_recalculate_service.constantize.call(
|
|
121
|
-
line_item: line_item,
|
|
122
|
-
order: spree_current_order
|
|
123
|
-
)
|
|
120
|
+
::Spree::Dependencies.cart_recalculate_service.constantize.call(line_item: line_item, order: spree_current_order)
|
|
124
121
|
end
|
|
125
122
|
end
|
|
126
123
|
end
|
|
@@ -3,14 +3,26 @@ module SpreeCmCommissioner
|
|
|
3
3
|
layout 'spree_cm_commissioner/layouts/order_mailer'
|
|
4
4
|
helper 'spree/mail'
|
|
5
5
|
|
|
6
|
-
def show
|
|
6
|
+
def show # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
7
7
|
@order = if params[:id].match?(/^R\d{9,}-([A-Za-z0-9_\-]+)$/)
|
|
8
8
|
Spree::Order.search_by_qr_data!(params[:id])
|
|
9
9
|
else
|
|
10
|
-
SpreeCmCommissioner::Orders::JwtToken::Verify.call(token: params[:t])
|
|
10
|
+
result = SpreeCmCommissioner::Orders::JwtToken::Verify.call(token: params[:t])
|
|
11
|
+
unless result.success?
|
|
12
|
+
@title = I18n.t('views.sign_in.errors.request_invalid')
|
|
13
|
+
@message = I18n.t('views.sign_in.errors.request_invalid_message')
|
|
14
|
+
|
|
15
|
+
return render(
|
|
16
|
+
template: 'shared_console/errors/request_invalid',
|
|
17
|
+
layout: 'error'
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
result.value[:order]
|
|
11
22
|
end
|
|
12
23
|
|
|
13
24
|
@product_type = @order.products.first&.product_type || 'accommodation'
|
|
25
|
+
@expired_at = Time.zone.at(result&.value&.dig(:payload, 'exp')) if result&.value&.dig(:payload, 'exp')
|
|
14
26
|
|
|
15
27
|
if @order.tenant.present?
|
|
16
28
|
@name = @order.tenant.name
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module InventoryItems
|
|
3
|
+
class RecentlyChangedFinder
|
|
4
|
+
def initialize(time_range: 7.days.ago, limit: 1000, vendor_id: nil, filter_type: 'all')
|
|
5
|
+
@time_range = time_range
|
|
6
|
+
@limit = limit
|
|
7
|
+
@vendor_id = vendor_id
|
|
8
|
+
@filter_type = filter_type
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Finds recently changed items with Redis comparison
|
|
12
|
+
# Returns array of hashes sorted by biggest discrepancy first
|
|
13
|
+
def execute
|
|
14
|
+
items = fetch_recent_items
|
|
15
|
+
items_with_comparison = fetch_quantity_in_redis_batch(items)
|
|
16
|
+
|
|
17
|
+
# Sort by biggest discrepancy first
|
|
18
|
+
items_with_comparison.sort_by { |item| (item[:quantity_in_redis].to_i - item[:quantity_available]).abs }.reverse
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def fetch_recent_items
|
|
24
|
+
query = SpreeCmCommissioner::InventoryItem.active
|
|
25
|
+
.where('cm_inventory_items.updated_at > ?', @time_range)
|
|
26
|
+
.joins(variant: :product)
|
|
27
|
+
.select(
|
|
28
|
+
'cm_inventory_items.id',
|
|
29
|
+
'cm_inventory_items.variant_id',
|
|
30
|
+
'cm_inventory_items.inventory_date',
|
|
31
|
+
'cm_inventory_items.quantity_available',
|
|
32
|
+
'cm_inventory_items.updated_at',
|
|
33
|
+
'cm_inventory_items.product_type',
|
|
34
|
+
'spree_variants.sku',
|
|
35
|
+
'spree_products.name as product_name',
|
|
36
|
+
'spree_products.slug as product_slug'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Apply filter_type at query level
|
|
40
|
+
query = case @filter_type
|
|
41
|
+
when 'permanent'
|
|
42
|
+
query.where(cm_inventory_items: { inventory_date: nil })
|
|
43
|
+
when 'non_permanent'
|
|
44
|
+
query.where.not(cm_inventory_items: { inventory_date: nil })
|
|
45
|
+
else
|
|
46
|
+
query
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
query = query.where(spree_variants: { vendor_id: @vendor_id }) if @vendor_id.present?
|
|
50
|
+
|
|
51
|
+
query.limit(@limit).order('cm_inventory_items.updated_at DESC')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Batch fetch from Redis to avoid N+1 queries
|
|
55
|
+
def fetch_quantity_in_redis_batch(items)
|
|
56
|
+
return [] if items.empty?
|
|
57
|
+
|
|
58
|
+
keys = items.map { |item| "inventory:#{item.id}" }
|
|
59
|
+
|
|
60
|
+
# Use mget for efficient batch Redis fetch
|
|
61
|
+
quantity_in_redis_array = SpreeCmCommissioner.inventory_redis_pool.with do |redis|
|
|
62
|
+
redis.mget(*keys)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Combine with DB data
|
|
66
|
+
items.each_with_index.map do |item, index|
|
|
67
|
+
quantity_in_redis = quantity_in_redis_array[index]&.to_i
|
|
68
|
+
quantity_available = item.quantity_available
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
inventory_item_id: item.id,
|
|
72
|
+
variant_id: item.variant_id,
|
|
73
|
+
product_name: item.product_name,
|
|
74
|
+
product_slug: item.product_slug,
|
|
75
|
+
sku: item.sku,
|
|
76
|
+
inventory_date: item.inventory_date,
|
|
77
|
+
quantity_available: quantity_available,
|
|
78
|
+
quantity_in_redis: quantity_in_redis,
|
|
79
|
+
difference: quantity_in_redis ? (quantity_in_redis - quantity_available) : nil,
|
|
80
|
+
out_of_sync: quantity_in_redis.nil? || quantity_in_redis != quantity_available,
|
|
81
|
+
last_updated: item.updated_at,
|
|
82
|
+
product_type: item.product_type
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -29,7 +29,17 @@ module SpreeCmCommissioner
|
|
|
29
29
|
# ❌ DO NOT use increment! because it skips model validations.
|
|
30
30
|
# We need validations to run so negative quantities are caught and surfaced
|
|
31
31
|
# as errors, not silently allowed by the database.
|
|
32
|
-
|
|
32
|
+
#
|
|
33
|
+
# LOCKING STRATEGY: Dual Layer Protection
|
|
34
|
+
# 1. with_lock (Pessimistic): SELECT ... FOR UPDATE prevents concurrent writes
|
|
35
|
+
# 2. lock_version (Optimistic): Catches race conditions if row is modified between
|
|
36
|
+
# SELECT and UPDATE (e.g., admin adjusts stock while job runs)
|
|
37
|
+
#
|
|
38
|
+
# Why both? Pessimistic lock protects against concurrent job execution.
|
|
39
|
+
# Optimistic lock catches stale reads from outside transactions.
|
|
40
|
+
inventory_item.with_lock do
|
|
41
|
+
inventory_item.update!(quantity_available: inventory_item.quantity_available + quantity)
|
|
42
|
+
end
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def inventory_items
|
|
@@ -11,7 +11,7 @@ module SpreeCmCommissioner
|
|
|
11
11
|
updated = inventory_item.update(max_capacity: max_capacity, quantity_available: quantity_available)
|
|
12
12
|
return context.fail!(message: 'Failed to update inventory item', errors: inventory_item.errors.full_messages) unless updated
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
reset_redis_inventory
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def variant_total_inventory
|
|
@@ -34,9 +34,13 @@ module SpreeCmCommissioner
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def
|
|
37
|
+
def reset_redis_inventory
|
|
38
38
|
SpreeCmCommissioner.inventory_redis_pool.with do |redis|
|
|
39
|
-
redis.
|
|
39
|
+
redis.setex(
|
|
40
|
+
inventory_item.redis_key,
|
|
41
|
+
inventory_item.redis_expired_in,
|
|
42
|
+
inventory_item.quantity_available
|
|
43
|
+
)
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Usage inside config/schedule.yml:
|
|
2
|
+
#
|
|
3
|
+
# maintenance_tasks_orchestrate:
|
|
4
|
+
# cron: "*/5 * * * *" # Every 5 minutes
|
|
5
|
+
# class: InvokeJob
|
|
6
|
+
# args: { class: "SpreeCmCommissioner::MaintenanceTasks::OrchestrateJob" }
|
|
7
|
+
#
|
|
8
|
+
# Or if you prefer less frequent:
|
|
9
|
+
# cron: "*/10 * * * *"
|
|
10
|
+
module SpreeCmCommissioner
|
|
11
|
+
module MaintenanceTasks
|
|
12
|
+
class OrchestrateJob < ApplicationJob
|
|
13
|
+
queue_as :maintenance
|
|
14
|
+
|
|
15
|
+
BATCH_SIZE = ENV.fetch('MAINTENANCE_TASKS_BATCH_SIZE', '100').to_i
|
|
16
|
+
|
|
17
|
+
# Scheduled job that orchestrates maintenance tasks by enqueuing
|
|
18
|
+
# individual processor jobs. Thin wrapper for Orchestrate service.
|
|
19
|
+
# ApplicationJob handles error logging via around_perform hook.
|
|
20
|
+
def perform
|
|
21
|
+
tasks = SpreeCmCommissioner::MaintenanceTask.due_for_retry.order(created_at: :asc).limit(BATCH_SIZE)
|
|
22
|
+
return if tasks.empty?
|
|
23
|
+
|
|
24
|
+
tasks.each(&:async_execute)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module MaintenanceTasks
|
|
3
|
+
class ProcessJob < ApplicationUniqueJob
|
|
4
|
+
queue_as :maintenance
|
|
5
|
+
|
|
6
|
+
# Process a single maintenance task by calling task.execute directly.
|
|
7
|
+
# ApplicationJob handles error logging via around_perform hook.
|
|
8
|
+
def perform(options = {})
|
|
9
|
+
task_id = options[:task_id]
|
|
10
|
+
task = SpreeCmCommissioner::MaintenanceTask.find_by(id: task_id)
|
|
11
|
+
|
|
12
|
+
return if task.nil? || task.completed_at.present?
|
|
13
|
+
|
|
14
|
+
task.execute
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -53,9 +53,7 @@ module SpreeCmCommissioner
|
|
|
53
53
|
after_transition from: :requested, to: :accepted, do: :decrement_user_request_orders_count
|
|
54
54
|
after_transition to: :accepted, do: :send_order_accepted_app_notification_to_user
|
|
55
55
|
after_transition to: :accepted, do: :send_order_accepted_telegram_alert_to_store
|
|
56
|
-
|
|
57
|
-
# call confirmation_delivered column directly since confirmation_delivered? is overrided
|
|
58
|
-
after_transition to: :accepted, if: :confirmation_delivered, do: :deliver_order_confirmation_email
|
|
56
|
+
after_transition to: :accepted, if: :confirmation_delivered?, do: :deliver_order_confirmation_email
|
|
59
57
|
|
|
60
58
|
event :reject do
|
|
61
59
|
transition from: :requested, to: :rejected
|
|
@@ -76,6 +74,14 @@ module SpreeCmCommissioner
|
|
|
76
74
|
end
|
|
77
75
|
end
|
|
78
76
|
|
|
77
|
+
# override
|
|
78
|
+
def deliver_order_confirmation_email
|
|
79
|
+
return if requested?
|
|
80
|
+
return if any_app_only_products?
|
|
81
|
+
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
|
|
79
85
|
def increment_user_request_orders_count
|
|
80
86
|
return if user.nil?
|
|
81
87
|
|
|
@@ -98,8 +104,13 @@ module SpreeCmCommissioner
|
|
|
98
104
|
end
|
|
99
105
|
|
|
100
106
|
def precalculate_conversion
|
|
101
|
-
line_items.
|
|
102
|
-
|
|
107
|
+
event_ids = line_items.pluck(:event_id).compact.uniq
|
|
108
|
+
event_ids.each do |event_id|
|
|
109
|
+
# Just create task, CRON job will pick it up and execute.
|
|
110
|
+
SpreeCmCommissioner::MaintenanceTasks::Event.pending.find_or_create_by(
|
|
111
|
+
maintainable_type: 'Spree::Taxon',
|
|
112
|
+
maintainable_id: event_id
|
|
113
|
+
)
|
|
103
114
|
end
|
|
104
115
|
end
|
|
105
116
|
|
|
@@ -126,12 +137,6 @@ module SpreeCmCommissioner
|
|
|
126
137
|
end
|
|
127
138
|
end
|
|
128
139
|
|
|
129
|
-
# overrided not to send email yet to user if order needs confirmation
|
|
130
|
-
# it will send after vendors accepted.
|
|
131
|
-
def confirmation_delivered?
|
|
132
|
-
confirmation_delivered || need_confirmation?
|
|
133
|
-
end
|
|
134
|
-
|
|
135
140
|
# allow authorized user to accept all requested line items
|
|
136
141
|
def accepted_by(user)
|
|
137
142
|
transaction do
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreeCmCommissioner
|
|
4
|
+
module ProductRelationType
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# Relation types for linking products together
|
|
8
|
+
# - open_dated_pair: Links fixed-date trip to open-dated trip (for flexible ticket redemption)
|
|
9
|
+
# - add_on: Links main product to optional extras
|
|
10
|
+
PRODUCT_RELATION_TYPES = %i[
|
|
11
|
+
open_dated_pair
|
|
12
|
+
add_on
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
enum relation_type: PRODUCT_RELATION_TYPES.each_with_index.to_h { |type, index| [type, index] }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.options
|
|
20
|
+
PRODUCT_RELATION_TYPES.map { |k| [I18n.t("product_relation_type.#{k}"), k] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def display_relation_type
|
|
24
|
+
return nil if relation_type.blank?
|
|
25
|
+
|
|
26
|
+
I18n.t("product_relation_type.#{relation_type}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|