spree_cm_commissioner 2.5.13.pre.pre11 → 2.5.13.pre.patch1
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 +10 -0
- data/Gemfile.lock +1 -1
- data/app/controllers/concerns/spree/admin/service_calendars_concern.rb +93 -0
- data/app/controllers/spree/admin/classifications_controller.rb +11 -3
- data/app/controllers/spree/admin/import_existing_orders_controller.rb +3 -1
- data/app/controllers/spree/admin/import_new_orders_controller.rb +3 -1
- 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/product_service_calendars_controller.rb +48 -0
- data/app/controllers/spree/admin/stock_managements_controller.rb +5 -1
- data/app/controllers/spree/admin/taxons_controller_decorator.rb +6 -0
- data/app/controllers/spree/admin/vendor_service_calendars_controller.rb +7 -68
- data/app/controllers/spree/api/v2/operator/check_ins_controller.rb +15 -2
- data/app/controllers/spree/api/v2/operator/guest_json_gzips_controller.rb +7 -0
- data/app/controllers/spree/api/v2/operator/recalculate_tickets_controller.rb +4 -9
- 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/helpers/spree_cm_commissioner/admin/service_calendars_helper.rb +8 -1
- 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/import_order_job.rb +2 -2
- 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/export.rb +12 -1
- data/app/models/spree_cm_commissioner/exports/operator_guest_json_gzip.rb +1 -1
- 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/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/_product_tabs/service_calendars.html.erb.deface +8 -0
- data/app/overrides/spree/admin/shared/sub_menu/_stock/inventory_monitorings_tab.html.erb.deface +3 -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/api_caches/invalidate.rb +10 -4
- data/app/services/spree_cm_commissioner/check_ins/create_bulk.rb +13 -2
- data/app/services/spree_cm_commissioner/imports/orders/base.rb +67 -0
- data/app/services/spree_cm_commissioner/imports/orders/create.rb +135 -0
- data/app/services/spree_cm_commissioner/imports/orders/update.rb +77 -0
- data/app/services/spree_cm_commissioner/operator_guest_json_gzips/create.rb +12 -6
- data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +36 -0
- 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/homepage_background/index.html.erb +1 -0
- data/app/views/spree/admin/inventory_monitorings/index.html.erb +119 -0
- data/app/views/spree/admin/product_service_calendars/index.html.erb +70 -0
- data/app/views/spree/admin/product_service_calendars/new.html.erb +9 -0
- data/app/views/spree/admin/{vendor_service_calendars → shared/service_calendars}/_form.html.erb +1 -1
- data/app/views/spree/admin/stock_managements/index.html.erb +1 -1
- data/app/views/spree/admin/taxon_childrens/index.html.erb +4 -0
- data/app/views/spree/admin/vendor_service_calendars/new.html.erb +2 -2
- data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +15 -0
- data/config/locales/en.yml +7 -0
- data/config/routes.rb +11 -4
- data/db/migrate/20260202095500_create_cm_product_relations.rb +32 -0
- data/db/migrate/20260207100000_add_index_created_at_to_cm_imports.rb +5 -0
- data/db/migrate/20260217162827_add_index_to_cm_guests_on_event_id_and_bib_prefix_and_bib_number.rb +8 -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 +29 -17
- 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
- data/app/services/spree_cm_commissioner/imports/base_import_order_service.rb +0 -55
- data/app/services/spree_cm_commissioner/imports/create_order_service.rb +0 -99
- data/app/services/spree_cm_commissioner/imports/update_order_service.rb +0 -41
- /data/app/views/spree/admin/{vendor_service_calendars → shared/service_calendars}/_exception_rules.html.erb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ca95409fb32a7b43530a6b61ffdc869564893d7c9a8abcf68e72f9db8c9362d
|
|
4
|
+
data.tar.gz: 93e7de99a30c3f58f899426132837c134ca9a6a725e799a90443b9699b91213f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efd4bc5698835f6eaebfa42e71e213c6cb9ace7d5a6eb5219b3143e49993ed8679145da7c0732ee16176b8428509973f6bd1ead97dcbf0ede168af042e67ae0c
|
|
7
|
+
data.tar.gz: 2ea123009aa6a3ecb2208117d35e27a8ece4010e78142b619f8c71323a6741b453bb37e8f2148f23fca7320d933923e81be037ed20baf616b43b294c7be52739
|
data/.env.example
CHANGED
|
@@ -40,3 +40,13 @@ 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 app/jobs/spree_cm_commissioner/maintenance_tasks/orchestrate_job.rb
|
|
45
|
+
MAINTENANCE_TASKS_BATCH_SIZE=100
|
|
46
|
+
|
|
47
|
+
# Export related:
|
|
48
|
+
OPERATOR_GUEST_JSON_GZIP_CACHE_HOURS=24
|
|
49
|
+
EXPORT_PRESIGNED_URL_EXPIRATION_MINUTES=15
|
|
50
|
+
|
|
51
|
+
# import order batch size
|
|
52
|
+
IMPORT_ORDERS_BATCH_SIZE=50
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module ServiceCalendarsConcern
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
new_action.before :set_exception_rules
|
|
8
|
+
|
|
9
|
+
helper 'spree_cm_commissioner/admin/service_calendars'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def update_status
|
|
13
|
+
if @object.update(active: !@object.active)
|
|
14
|
+
flash[:success] = flash_message_for(@object, :successfully_updated)
|
|
15
|
+
else
|
|
16
|
+
flash[:error] = @object.errors.full_messages.to_sentence
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
redirect_to collection_url
|
|
20
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
21
|
+
flash[:error] = e.message
|
|
22
|
+
redirect_to collection_url
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def model_class
|
|
28
|
+
SpreeCmCommissioner::ServiceCalendar
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def object_name
|
|
32
|
+
'spree_cm_commissioner_service_calendars'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def permitted_resource_params
|
|
36
|
+
service_calendar_params = params.require(:spree_cm_commissioner_service_calendar)
|
|
37
|
+
.permit(:calendarable_id, :calendarable_type, :start_date, :end_date,
|
|
38
|
+
:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
|
|
39
|
+
)
|
|
40
|
+
service_calendar_params[:exception_rules] = build_exception_rules(params[:spree_cm_commissioner_service_calendar][:exception_rules])
|
|
41
|
+
service_calendar_params
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def build_exception_rules(exception_rules)
|
|
47
|
+
exception_rules.values.reject! { |rule| rule['from'].blank? || rule['to'].blank? || rule['type'].blank? } || exception_rules.values
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def collection
|
|
51
|
+
load_calendarable
|
|
52
|
+
|
|
53
|
+
@objects = model_class.where(
|
|
54
|
+
calendarable_type: calendarable_type,
|
|
55
|
+
calendarable_id: calendarable_id
|
|
56
|
+
).order(id: :desc)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def set_exception_rules
|
|
60
|
+
@exception_rules = [{ from: DateTime.now, to: DateTime.now, type: 'exclusion', reason: nil }]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_resource
|
|
64
|
+
today = Time.zone.today
|
|
65
|
+
model_class.new(start_date: today, end_date: today.next_year(3),
|
|
66
|
+
exception_rules: [{ from: nil, to: nil, type: 'inclusion' }]
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def set_calendarable
|
|
71
|
+
@object.calendarable_type = calendarable_type
|
|
72
|
+
@object.calendarable_id = calendarable_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# To be implemented by including controllers
|
|
76
|
+
def load_calendarable
|
|
77
|
+
raise NotImplementedError, 'must implement load_calendarable'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def calendarable
|
|
81
|
+
raise NotImplementedError, 'must implement calendarable'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def calendarable_id
|
|
85
|
+
calendarable.id
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def calendarable_type
|
|
89
|
+
calendarable.class.name
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -4,11 +4,19 @@ 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
|
+
) do |task|
|
|
12
|
+
task.manually_triggered = true
|
|
13
|
+
end.async_execute
|
|
14
|
+
|
|
15
|
+
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
|
16
|
+
else
|
|
17
|
+
flash[:error] = 'Conversion recalculation can only be performed for taxons under an event.' # rubocop:disable Rails/I18nLocaleTexts
|
|
9
18
|
end
|
|
10
19
|
|
|
11
|
-
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
|
12
20
|
redirect_to collection_url
|
|
13
21
|
end
|
|
14
22
|
|
|
@@ -3,7 +3,9 @@ module Spree
|
|
|
3
3
|
class ImportExistingOrdersController < BaseImportOrdersController
|
|
4
4
|
# override
|
|
5
5
|
def collection
|
|
6
|
-
@collection ||= model_class.existing_order
|
|
6
|
+
@collection ||= model_class.existing_order
|
|
7
|
+
.order(created_at: :desc)
|
|
8
|
+
.page(params[:page])
|
|
7
9
|
.per(params[:per_page])
|
|
8
10
|
end
|
|
9
11
|
|
|
@@ -3,7 +3,9 @@ module Spree
|
|
|
3
3
|
class ImportNewOrdersController < BaseImportOrdersController
|
|
4
4
|
# override
|
|
5
5
|
def collection
|
|
6
|
-
@collection ||= model_class.new_order
|
|
6
|
+
@collection ||= model_class.new_order
|
|
7
|
+
.order(created_at: :desc)
|
|
8
|
+
.page(params[:page])
|
|
7
9
|
.per(params[:per_page])
|
|
8
10
|
end
|
|
9
11
|
|
|
@@ -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
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class ProductServiceCalendarsController < Spree::Admin::ResourceController
|
|
4
|
+
include Spree::Admin::ServiceCalendarsConcern
|
|
5
|
+
|
|
6
|
+
before_action :load_product
|
|
7
|
+
before_action :ensure_product_type_supports_calendar
|
|
8
|
+
before_action :ensure_product_has_no_calendar, only: %i[new create]
|
|
9
|
+
|
|
10
|
+
create.before :set_calendarable
|
|
11
|
+
update.before :set_calendarable
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def collection_url(options = {})
|
|
16
|
+
admin_product_product_service_calendars_url(options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def load_product
|
|
22
|
+
@product ||= (Spree::Product.find_by(slug: params[:product_id]) || Spree::Product.find_by(id: params[:product_id]))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ensure_product_type_supports_calendar
|
|
26
|
+
return if @product.permanent_stock?
|
|
27
|
+
|
|
28
|
+
flash[:error] = I18n.t('vendor.service_calendars.not_available_for_product_type')
|
|
29
|
+
redirect_to edit_admin_product_url(@product)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ensure_product_has_no_calendar
|
|
33
|
+
return if @product.service_calendar.blank?
|
|
34
|
+
|
|
35
|
+
flash[:error] = I18n.t('vendor.service_calendars.already_has_calendar')
|
|
36
|
+
redirect_to admin_product_product_service_calendars_url(@product)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def load_calendarable
|
|
40
|
+
load_product
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def calendarable
|
|
44
|
+
@product
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
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
|
|
@@ -5,6 +5,12 @@ module Spree
|
|
|
5
5
|
base.before_action :build_assets, only: %i[create update]
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
# override
|
|
9
|
+
def new
|
|
10
|
+
@taxon.parent_id = params[:parent_id] if params[:parent_id].present?
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
8
14
|
def remove_category_icon
|
|
9
15
|
remove_asset(@taxon.category_icon)
|
|
10
16
|
end
|
|
@@ -1,92 +1,31 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Admin
|
|
3
3
|
class VendorServiceCalendarsController < Spree::Admin::ResourceController
|
|
4
|
-
|
|
5
|
-
new_action.before :set_exception_rules
|
|
6
|
-
|
|
7
|
-
create.before :set_vendor
|
|
8
|
-
update.before :set_vendor
|
|
9
|
-
|
|
10
|
-
helper 'spree_cm_commissioner/admin/service_calendars'
|
|
4
|
+
include Spree::Admin::ServiceCalendarsConcern
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
if @object.update(active: !@object.active)
|
|
14
|
-
flash[:success] = flash_message_for(@object, :successfully_updated)
|
|
15
|
-
else
|
|
16
|
-
flash[:error] = @object.errors.full_messages.to_sentence
|
|
17
|
-
end
|
|
6
|
+
before_action :load_vendor
|
|
18
7
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
flash[:error] = e.message
|
|
22
|
-
redirect_to admin_vendor_vendor_service_calendars_url
|
|
23
|
-
end
|
|
8
|
+
create.before :set_calendarable
|
|
9
|
+
update.before :set_calendarable
|
|
24
10
|
|
|
25
11
|
protected
|
|
26
12
|
|
|
27
|
-
def model_class
|
|
28
|
-
SpreeCmCommissioner::ServiceCalendar
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def object_name
|
|
32
|
-
'spree_cm_commissioner_service_calendars'
|
|
33
|
-
end
|
|
34
|
-
|
|
35
13
|
def collection_url(options = {})
|
|
36
14
|
admin_vendor_vendor_service_calendars_url(options)
|
|
37
15
|
end
|
|
38
16
|
|
|
39
|
-
def permitted_resource_params
|
|
40
|
-
service_calendar_params = params.require(:spree_cm_commissioner_service_calendar)
|
|
41
|
-
.permit(:calendarable_id, :calendarable_type, :start_date, :end_date,
|
|
42
|
-
:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
|
|
43
|
-
)
|
|
44
|
-
service_calendar_params[:exception_rules] = build_exception_rules(params[:spree_cm_commissioner_service_calendar][:exception_rules])
|
|
45
|
-
service_calendar_params
|
|
46
|
-
end
|
|
47
|
-
|
|
48
17
|
private
|
|
49
18
|
|
|
50
19
|
def load_vendor
|
|
51
20
|
@vendor ||= (Spree::Vendor.find_by(slug: params[:vendor_id]) || Spree::Vendor.find_by(id: params[:vendor_id]))
|
|
52
21
|
end
|
|
53
22
|
|
|
54
|
-
def
|
|
55
|
-
@object.calendarable_type = calendarable_type
|
|
56
|
-
@object.calendarable_id = calendarable_id
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def build_exception_rules(exception_rules)
|
|
60
|
-
exception_rules.values.reject! { |rule| rule['from'].blank? || rule['to'].blank? || rule['type'].blank? } || exception_rules.values
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def collection
|
|
23
|
+
def load_calendarable
|
|
64
24
|
load_vendor
|
|
65
|
-
|
|
66
|
-
@objects = model_class.where(
|
|
67
|
-
calendarable_type: calendarable_type,
|
|
68
|
-
calendarable_id: calendarable_id
|
|
69
|
-
).order(id: :desc)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def set_exception_rules
|
|
73
|
-
@exception_rules = [{ from: DateTime.now, to: DateTime.now, type: 'exclusion', reason: nil }]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# it load before :load_vendor
|
|
77
|
-
def build_resource
|
|
78
|
-
today = Time.zone.today
|
|
79
|
-
model_class.new(start_date: today, end_date: today.next_year(3),
|
|
80
|
-
exception_rules: [{ from: nil, to: nil, type: 'inclusion' }]
|
|
81
|
-
)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def calendarable_id
|
|
85
|
-
@vendor.id
|
|
86
25
|
end
|
|
87
26
|
|
|
88
|
-
def
|
|
89
|
-
@vendor
|
|
27
|
+
def calendarable
|
|
28
|
+
@vendor
|
|
90
29
|
end
|
|
91
30
|
end
|
|
92
31
|
end
|
|
@@ -5,10 +5,23 @@ module Spree
|
|
|
5
5
|
class CheckInsController < ::Spree::Api::V2::ResourceController
|
|
6
6
|
before_action :require_spree_current_user, only: %i[index create]
|
|
7
7
|
|
|
8
|
+
# Check-in history data requires fresh/recent data for operator dashboards
|
|
9
|
+
# Short cache duration ensures operators see near real-time check-in activity
|
|
10
|
+
CACHE_EXPIRES_IN = 1.minute
|
|
11
|
+
|
|
8
12
|
def collection
|
|
9
|
-
@collection
|
|
13
|
+
@collection ||= SpreeCmCommissioner::CheckIn
|
|
14
|
+
.where(checkinable_type: 'Spree::Taxon', checkinable_id: params[:taxon_id])
|
|
15
|
+
.page(params[:page])
|
|
16
|
+
.per(params[:per_page])
|
|
17
|
+
end
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
# override
|
|
20
|
+
def collection_cache_opts
|
|
21
|
+
{
|
|
22
|
+
namespace: Spree::Api::Config[:api_v2_collection_cache_namespace],
|
|
23
|
+
expires_in: CACHE_EXPIRES_IN
|
|
24
|
+
}
|
|
12
25
|
end
|
|
13
26
|
|
|
14
27
|
def create
|
|
@@ -3,7 +3,10 @@ module Spree
|
|
|
3
3
|
module V2
|
|
4
4
|
module Operator
|
|
5
5
|
class GuestJsonGzipsController < ::Spree::Api::V2::ResourceController
|
|
6
|
+
include ActiveStorage::SetCurrent
|
|
7
|
+
|
|
6
8
|
before_action :require_spree_current_user
|
|
9
|
+
before_action :require_event_user
|
|
7
10
|
|
|
8
11
|
# POST /api/v2/operator/guest_json_gzips
|
|
9
12
|
# - taxon_id=1
|
|
@@ -29,6 +32,10 @@ module Spree
|
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
|
|
35
|
+
def require_event_user
|
|
36
|
+
raise CanCan::AccessDenied unless spree_current_user.events.exists?(id: params[:taxon_id])
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
# override
|
|
33
40
|
def scope
|
|
34
41
|
SpreeCmCommissioner::Exports::OperatorGuestJsonGzip.where(
|
|
@@ -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
|
|
@@ -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
|
|
@@ -5,9 +5,16 @@ module SpreeCmCommissioner
|
|
|
5
5
|
label = resource.active ? 'Active' : 'Disabled'
|
|
6
6
|
btn_active_class = resource.active ? 'btn-primary' : 'btn-warning'
|
|
7
7
|
|
|
8
|
+
# Determine the correct URL based on the calendarable type
|
|
9
|
+
url = if resource.calendarable_type == 'Spree::Product'
|
|
10
|
+
update_status_admin_product_product_service_calendar_path(resource.calendarable, resource)
|
|
11
|
+
else
|
|
12
|
+
update_status_admin_vendor_vendor_service_calendar_url(resource.calendarable, resource)
|
|
13
|
+
end
|
|
14
|
+
|
|
8
15
|
button_to(
|
|
9
16
|
label,
|
|
10
|
-
|
|
17
|
+
url,
|
|
11
18
|
form: { data: { confirm: 'Are you sure?' }, class: "btn btn-sm btn-active #{btn_active_class}" },
|
|
12
19
|
method: :patch
|
|
13
20
|
)
|
|
@@ -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
|