spree_cm_commissioner 2.5.0.pre.pre2 → 2.5.0.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/.github/workflows/test_and_build_gem.yml +1 -1
- data/.tool-versions +1 -1
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/integration_mappings_controller.rb +21 -0
- data/app/controllers/spree/admin/integration_sessions_controller.rb +21 -0
- data/app/controllers/spree/admin/integrations_controller.rb +83 -0
- data/app/controllers/spree/api/v2/storefront/event_matches_controller.rb +15 -0
- data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +1 -7
- data/app/controllers/spree/api/v2/storefront/route_places_controller.rb +9 -9
- data/app/errors/spree_cm_commissioner/integrations/external_client_error.rb +10 -0
- data/app/errors/spree_cm_commissioner/integrations/sync_error.rb +4 -0
- data/app/finders/spree_cm_commissioner/events/find_matches.rb +15 -0
- data/app/finders/spree_cm_commissioner/places/find_with_route.rb +10 -10
- data/app/finders/spree_cm_commissioner/routes/find_popular.rb +14 -10
- data/app/helpers/spree_cm_commissioner/external_integrations_helper.rb +58 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +3 -2
- data/app/jobs/spree_cm_commissioner/integrations/base_job.rb +39 -0
- data/app/jobs/spree_cm_commissioner/integrations/polling_job.rb +53 -0
- data/app/jobs/spree_cm_commissioner/integrations/polling_scheduler_job.rb +30 -0
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +6 -3
- data/app/jobs/spree_cm_commissioner/telegram_alerts/integration_sync_failure_job.rb +17 -0
- data/app/models/concerns/spree_cm_commissioner/integrations/integration_mappable.rb +61 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_integration.rb +36 -0
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +15 -3
- data/app/models/concerns/spree_cm_commissioner/order_integration.rb +33 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +2 -0
- data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +14 -2
- data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -3
- data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -7
- data/app/models/spree_cm_commissioner/guest.rb +13 -0
- data/app/models/spree_cm_commissioner/integration.rb +29 -0
- data/app/models/spree_cm_commissioner/integration_mapping.rb +41 -0
- data/app/models/spree_cm_commissioner/integration_sync_session.rb +15 -0
- data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +37 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +5 -1
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +13 -1
- data/app/models/spree_cm_commissioner/option_type_decorator.rb +8 -0
- data/app/models/spree_cm_commissioner/option_value_decorator.rb +34 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +3 -2
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -2
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +2 -2
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +13 -1
- data/app/models/spree_cm_commissioner/variant_decorator.rb +7 -4
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +4 -0
- data/app/overrides/spree/admin/shared/sub_menu/_integrations/external_integrations.html.erb.deface +6 -0
- data/app/presenters/spree/variants/{visable_options_presenter.rb → visible_options_presenter.rb} +2 -4
- data/app/request_schemas/spree_cm_commissioner/route_places_request_schema.rb +0 -5
- data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +69 -0
- data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +183 -0
- data/app/services/spree_cm_commissioner/integrations/polling.rb +70 -0
- data/app/services/spree_cm_commissioner/integrations/polling_scheduler.rb +79 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +152 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/inventory/unstock_inventory.rb +83 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +113 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +215 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/base.rb +20 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/league.rb +19 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/match.rb +95 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/ticket.rb +81 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/ticket_image.rb +19 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/zone.rb +90 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +35 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +38 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +44 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +16 -0
- data/app/services/spree_cm_commissioner/telegram_alerts/integration_sync_failure.rb +49 -0
- data/app/views/spree/admin/integration_mappings/_integration_mappings.html.erb +107 -0
- data/app/views/spree/admin/integration_mappings/index.html.erb +33 -0
- data/app/views/spree/admin/integration_sessions/_integration_sync_sessions.html.erb +116 -0
- data/app/views/spree/admin/integration_sessions/index.html.erb +42 -0
- data/app/views/spree/admin/integrations/_form.html.erb +104 -0
- data/app/views/spree/admin/integrations/_stadium_x_v1_fields.html.erb +29 -0
- data/app/views/spree/admin/integrations/edit.html.erb +45 -0
- data/app/views/spree/admin/integrations/index.html.erb +75 -0
- data/app/views/spree/admin/integrations/new.html.erb +25 -0
- data/app/views/spree/admin/tenants/_form.html.erb +0 -18
- data/app/views/spree/order_mailer/confirm_email.html.erb +1 -1
- data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +1 -1
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb +6 -13
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb +24 -23
- data/config/locales/en.yml +8 -0
- data/config/locales/km.yml +8 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20251017094845_create_cm_integrations.rb +22 -0
- data/db/migrate/20251017101555_create_cm_integration_sync_sessions.rb +68 -0
- data/db/migrate/20251017101605_create_cm_integration_mappings.rb +52 -0
- data/lib/cm_app_logger.rb +1 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +25 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_mapping_factory.rb +14 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_sync_session_factory.rb +7 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +8 -7
- metadata +56 -6
- data/app/request_schemas/spree_cm_commissioner/popular_route_places_request_schema.rb +0 -12
- data/app/views/spree/admin/tenants/form/_footer.html.erb +0 -31
- data/app/views/spree/admin/tenants/form/_social.html.erb +0 -31
- data/db/migrate/20251209022924_add_contact_fields_to_cm_tenants.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: ba91dacd894816a0b4105c032a0550371aeab884b3f344ebe554758c3556a0a0
|
|
4
|
+
data.tar.gz: 077d13272adb0ced0f246dc9d48d5d3b9ae56a95beaa74a6d2cd6fc7f134f7e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a990d42d6cf09c9304971a41efb74525761de29ee6cefe04e1a7b0dbe2831e9ad40f52f229ca463ee905ed1e039b1562daa2eace5307c4dd3eb6f725488e671
|
|
7
|
+
data.tar.gz: 309280aeb9987456b2788be11cdad59095da58b2b258e294477cfb5edb63323b4ee6d3fd8ae03f845fd79db0d0186ff54d64f6cb406567c2a284dc26454446f8
|
data/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby 3.
|
|
1
|
+
ruby 3.2.0
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class IntegrationMappingsController < Spree::Admin::BaseController
|
|
4
|
+
helper SpreeCmCommissioner::ExternalIntegrationsHelper
|
|
5
|
+
before_action :load_integration
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@mappings = @integration.integration_mappings
|
|
9
|
+
.order(updated_at: :desc)
|
|
10
|
+
.page(params[:page])
|
|
11
|
+
.per(params[:per_page] || 25)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def load_integration
|
|
17
|
+
@integration ||= SpreeCmCommissioner::Integration.find(params[:integration_id])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class IntegrationSessionsController < Spree::Admin::BaseController
|
|
4
|
+
helper SpreeCmCommissioner::ExternalIntegrationsHelper
|
|
5
|
+
before_action :load_integration
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@sync_sessions = @integration.integration_sync_sessions
|
|
9
|
+
.order(created_at: :desc)
|
|
10
|
+
.page(params[:page])
|
|
11
|
+
.per(params[:per_page] || 25)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def load_integration
|
|
17
|
+
@integration ||= SpreeCmCommissioner::Integration.find(params[:integration_id])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class IntegrationsController < Spree::Admin::ResourceController
|
|
4
|
+
helper SpreeCmCommissioner::ExternalIntegrationsHelper
|
|
5
|
+
before_action :load_form_collections, only: %i[new create edit update]
|
|
6
|
+
before_action :load_integration, only: %i[enqueue_polling]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@integrations = model_class.all
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def enqueue_polling
|
|
13
|
+
sync_type = params[:sync_type].presence || :full
|
|
14
|
+
|
|
15
|
+
if @integration.active?
|
|
16
|
+
SpreeCmCommissioner::Integrations::PollingJob.perform_later(
|
|
17
|
+
integration_id: @integration.id,
|
|
18
|
+
sync_type: sync_type
|
|
19
|
+
)
|
|
20
|
+
flash[:success] = Spree.t('admin.integration_sync_enqueued')
|
|
21
|
+
else
|
|
22
|
+
flash[:error] = Spree.t('admin.integration_inactive')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
redirect_to admin_integration_sessions_path(@integration)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# override
|
|
29
|
+
def model_class
|
|
30
|
+
SpreeCmCommissioner::Integration
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# override
|
|
34
|
+
def collection_url(options = {})
|
|
35
|
+
admin_integrations_url(options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def permitted_resource_params
|
|
41
|
+
params.require(:spree_cm_commissioner_integration).permit(
|
|
42
|
+
:name,
|
|
43
|
+
:type,
|
|
44
|
+
:status,
|
|
45
|
+
:conflict_strategy,
|
|
46
|
+
:incremental_sync_interval_seconds,
|
|
47
|
+
:full_sync_interval_hours,
|
|
48
|
+
:tenant_id,
|
|
49
|
+
:vendor_id,
|
|
50
|
+
public_metadata: {},
|
|
51
|
+
private_metadata: {}
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_integration
|
|
56
|
+
@integration ||= SpreeCmCommissioner::Integration.find(params[:id])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def load_form_collections
|
|
60
|
+
@integration_type_options ||= integration_type_options
|
|
61
|
+
@status_options ||= enum_to_options(SpreeCmCommissioner::Integration.statuses)
|
|
62
|
+
@conflict_strategy_options ||= enum_to_options(SpreeCmCommissioner::Integration.conflict_strategies)
|
|
63
|
+
@tenants ||= SpreeCmCommissioner::Tenant.select(:id, :name).order(:name)
|
|
64
|
+
@vendors ||= Spree::Vendor.select(:id, :name).order(:name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def enum_to_options(enum_hash)
|
|
68
|
+
enum_hash.keys.map { |key| [key.humanize, key] }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def integration_type_options
|
|
72
|
+
files = Dir[SpreeCmCommissioner::Engine.root.join('app/models/spree_cm_commissioner/integrations/*.rb')]
|
|
73
|
+
|
|
74
|
+
return [[Spree.t(:type), 'SpreeCmCommissioner::Integration']] if files.blank?
|
|
75
|
+
|
|
76
|
+
files.map do |path|
|
|
77
|
+
class_name = "SpreeCmCommissioner::Integrations::#{File.basename(path, '.rb').camelize}"
|
|
78
|
+
[class_name, class_name]
|
|
79
|
+
end.sort_by(&:first)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -19,8 +19,7 @@ module Spree
|
|
|
19
19
|
# override
|
|
20
20
|
def collection
|
|
21
21
|
@collection ||= collection_finder.new.execute(
|
|
22
|
-
route_type: params[:route_type]
|
|
23
|
-
query: params[:query]
|
|
22
|
+
route_type: params[:route_type]
|
|
24
23
|
)
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -52,11 +51,6 @@ module Spree
|
|
|
52
51
|
)
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
# override
|
|
56
|
-
def required_schema
|
|
57
|
-
SpreeCmCommissioner::PopularRoutePlacesRequestSchema
|
|
58
|
-
end
|
|
59
|
-
|
|
60
54
|
def include_vendors?
|
|
61
55
|
resource_includes.include?(:vendors) || false
|
|
62
56
|
end
|
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
# GET /api/v2/storefront/route_places
|
|
4
4
|
#
|
|
5
5
|
# Finds places (origins or destinations) that are connected to a given place through existing routes.
|
|
6
|
-
# Optionally filters results by
|
|
6
|
+
# Optionally filters results by keyword or route_type. Supports two modes:
|
|
7
7
|
#
|
|
8
8
|
# Usage 1: Connected places (requires place_id)
|
|
9
9
|
# - Finds places connected to a specific place via routes
|
|
10
10
|
# - Returns origins/destinations that have routes with the specified place
|
|
11
11
|
#
|
|
12
|
-
# Usage 2:
|
|
13
|
-
# - Searches all route places by
|
|
14
|
-
# - Returns all origins/destinations matching the
|
|
12
|
+
# Usage 2: Keyword search (requires query, place_id optional)
|
|
13
|
+
# - Searches all route places by keyword
|
|
14
|
+
# - Returns all origins/destinations matching the keyword
|
|
15
15
|
#
|
|
16
16
|
# Query Parameters:
|
|
17
17
|
# - place_type: [String] Required. Type of route place: 'origin' or 'destination'
|
|
18
18
|
# * 'origin': returns origins that have routes TO the specified place (if place_id provided)
|
|
19
19
|
# * 'destination': returns destinations that have routes FROM the specified place (if place_id provided)
|
|
20
20
|
# - place_id: [Integer] Optional. The place ID to find connected places for
|
|
21
|
-
# - query: [String] Optional.
|
|
21
|
+
# - query: [String] Optional. Keyword to filter place names (case-insensitive)
|
|
22
22
|
# - include: Optional comma-separated list (e.g., 'vendors,nearby_places')
|
|
23
23
|
#
|
|
24
24
|
# Response: Collection of places serialized with PlaceSerializer
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
# - Returns empty collection if place_type is invalid
|
|
28
28
|
# - If place_id provided: returns places connected to that place
|
|
29
29
|
# - If place_id blank: returns all origins/destinations
|
|
30
|
-
# - Filters results by
|
|
30
|
+
# - Filters results by keyword or route_type if provided
|
|
31
31
|
#
|
|
32
32
|
# @example Mode 1: Find destinations connected to origin place ID 123
|
|
33
33
|
# GET /api/v2/storefront/route_places?place_id=123&place_type=destination
|
|
34
34
|
#
|
|
35
|
-
# @example Mode 2: Search all destination places by
|
|
35
|
+
# @example Mode 2: Search all destination places by keyword
|
|
36
36
|
# GET /api/v2/storefront/route_places?place_type=destination&query=Phnom
|
|
37
37
|
#
|
|
38
|
-
# @example Combined: Find origins connected to place 456, filtered by
|
|
38
|
+
# @example Combined: Find origins connected to place 456, filtered by keyword
|
|
39
39
|
# GET /api/v2/storefront/route_places?place_id=456&place_type=origin&query=Siem
|
|
40
40
|
module Spree
|
|
41
41
|
module Api
|
|
@@ -49,7 +49,7 @@ module Spree
|
|
|
49
49
|
@collection ||= collection_finder.new(
|
|
50
50
|
place_type: params[:place_type],
|
|
51
51
|
place_id: params[:place_id],
|
|
52
|
-
|
|
52
|
+
keyword: params[:query],
|
|
53
53
|
route_type: params[:route_type]
|
|
54
54
|
).execute
|
|
55
55
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Events
|
|
3
|
+
class FindMatches < ::Spree::BaseFinder
|
|
4
|
+
# currently, there is no way to determine if an event is a match yet.
|
|
5
|
+
# this finder returns all events that have active integration mappings (synced from external systems like StadiumX)
|
|
6
|
+
# as a temporary workaround to filter out non-match events.
|
|
7
|
+
def execute
|
|
8
|
+
scope.joins(:integration_mappings).where(
|
|
9
|
+
kind: :event,
|
|
10
|
+
integration_mappings: { status: :active }
|
|
11
|
+
).distinct
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
#
|
|
3
3
|
# @param place_type [String] Required. 'origin' or 'destination'
|
|
4
4
|
# @param place_id [Integer] Optional. Filter by connected place ID
|
|
5
|
-
# @param
|
|
5
|
+
# @param keyword [String] Optional. Filter by place name (case-insensitive)
|
|
6
6
|
# @param route_type [Symbol, String] Optional. Filter by route type from associated trips (e.g., :ferry, :bus)
|
|
7
7
|
#
|
|
8
8
|
# @return [ActiveRecord::Relation<SpreeCmCommissioner::Place>]
|
|
9
9
|
#
|
|
10
10
|
# Modes:
|
|
11
11
|
# - place_id only: returns connected places (origins TO or destinations FROM given place)
|
|
12
|
-
# -
|
|
12
|
+
# - keyword only: filters by name
|
|
13
13
|
# - route_type only: filters by trip route types on those routes
|
|
14
14
|
# - combinations: connected places filtered by name and/or route type
|
|
15
15
|
# - neither: all origins/destinations in routes
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
# @example Origins with ferry trips to place 123
|
|
18
18
|
# FindWithRoute.new(place_type: 'origin', place_id: 123, route_type: :ferry).execute
|
|
19
19
|
#
|
|
20
|
-
# @example Destinations filtered by
|
|
21
|
-
# FindWithRoute.new(place_type: 'destination',
|
|
20
|
+
# @example Destinations filtered by keyword and route type
|
|
21
|
+
# FindWithRoute.new(place_type: 'destination', keyword: 'Phnom', route_type: :bus).execute
|
|
22
22
|
module SpreeCmCommissioner
|
|
23
23
|
module Places
|
|
24
24
|
class FindWithRoute
|
|
25
|
-
attr_reader :place_type, :place_id, :
|
|
25
|
+
attr_reader :place_type, :place_id, :keyword, :route_type
|
|
26
26
|
|
|
27
|
-
def initialize(place_type:, place_id: nil,
|
|
27
|
+
def initialize(place_type:, place_id: nil, keyword: nil, route_type: nil)
|
|
28
28
|
@place_type = place_type
|
|
29
29
|
@place_id = place_id
|
|
30
|
-
@
|
|
30
|
+
@keyword = keyword
|
|
31
31
|
@route_type = route_type
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -36,7 +36,7 @@ module SpreeCmCommissioner
|
|
|
36
36
|
return SpreeCmCommissioner::Place.none if place_id.present? && !SpreeCmCommissioner::Place.exists?(place_id)
|
|
37
37
|
|
|
38
38
|
result = scope
|
|
39
|
-
result =
|
|
39
|
+
result = apply_keyword_filter(result) if keyword.present?
|
|
40
40
|
result = apply_route_type_filter(result) if route_type.present?
|
|
41
41
|
result
|
|
42
42
|
end
|
|
@@ -57,8 +57,8 @@ module SpreeCmCommissioner
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def
|
|
61
|
-
result.where('cm_places.name ILIKE ?', "%#{
|
|
60
|
+
def apply_keyword_filter(result)
|
|
61
|
+
result.where('cm_places.name ILIKE ?', "%#{keyword}%")
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def apply_route_type_filter(result)
|
|
@@ -24,22 +24,26 @@
|
|
|
24
24
|
module SpreeCmCommissioner
|
|
25
25
|
module Routes
|
|
26
26
|
class FindPopular
|
|
27
|
-
def execute(route_type: nil
|
|
28
|
-
scope(route_type: route_type
|
|
27
|
+
def execute(route_type: nil)
|
|
28
|
+
scope(route_type: route_type)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
-
def scope(route_type
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
result = SpreeCmCommissioner::Route.includes(*includes_associations)
|
|
33
|
+
def scope(route_type: nil)
|
|
34
|
+
result = SpreeCmCommissioner::Route
|
|
35
|
+
.includes(:origin_place, :destination_place)
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
if route_type.present?
|
|
38
|
+
# Filter routes that have trips with the specified route_type
|
|
39
|
+
route_scope = SpreeCmCommissioner::Route
|
|
40
|
+
.joins(:trips)
|
|
41
|
+
.where(cm_trips: { route_type: route_type.to_sym })
|
|
42
|
+
result = result.where(id: route_scope.select(:id))
|
|
43
|
+
end
|
|
39
44
|
|
|
40
|
-
result
|
|
41
|
-
|
|
42
|
-
result.distinct.order(fulfilled_order_count: :desc, order_count: :desc)
|
|
45
|
+
result.order(Arel.sql('COALESCE(fulfilled_order_count, 0) DESC'))
|
|
46
|
+
.order(Arel.sql('COALESCE(order_count, 0) DESC'))
|
|
43
47
|
end
|
|
44
48
|
end
|
|
45
49
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module ExternalIntegrationsHelper
|
|
3
|
+
def integration_status_badge(status)
|
|
4
|
+
badge_class = case status.to_s
|
|
5
|
+
when 'active' then 'badge-success'
|
|
6
|
+
when 'paused' then 'badge-warning'
|
|
7
|
+
else 'badge-secondary'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
content_tag(:span, status.to_s.humanize, class: "badge badge-pill #{badge_class}")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def integration_conflict_strategy_badge(strategy)
|
|
14
|
+
content_tag(:span, strategy.to_s.humanize, class: 'badge badge-pill badge-secondary')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def integration_interval_badge(value, unit)
|
|
18
|
+
content_tag(:span, "#{value}#{unit}", class: 'badge badge-pill badge-info')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def integration_base_url_link(url)
|
|
22
|
+
if url.present?
|
|
23
|
+
link_to url, url, target: '_blank', rel: 'noopener', class: 'text-decoration-underline'
|
|
24
|
+
else
|
|
25
|
+
content_tag(:span, '—', class: 'text-muted')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def integration_mapping_internal_link(mapping)
|
|
30
|
+
return mapping.internal_id if mapping.internal.blank?
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
case mapping.internal_type
|
|
34
|
+
when 'Spree::Taxon'
|
|
35
|
+
taxon = mapping.internal
|
|
36
|
+
taxonomy_id = taxon.taxonomy_id || taxon.taxonomy&.id
|
|
37
|
+
if taxonomy_id
|
|
38
|
+
link_to mapping.internal_id,
|
|
39
|
+
edit_admin_taxonomy_taxon_path(taxonomy_id, taxon.id),
|
|
40
|
+
class: 'text-primary'
|
|
41
|
+
end
|
|
42
|
+
when 'Spree::Product'
|
|
43
|
+
link_to mapping.internal_id, edit_admin_product_path(mapping.internal), class: 'text-primary'
|
|
44
|
+
when 'Spree::Variant'
|
|
45
|
+
link_to mapping.internal_id, edit_admin_product_variant_path(mapping.internal.product_id, mapping.internal), class: 'text-primary'
|
|
46
|
+
when 'Spree::Vendor'
|
|
47
|
+
link_to mapping.internal_id, edit_admin_vendor_path(mapping.internal), class: 'text-primary'
|
|
48
|
+
else
|
|
49
|
+
# Try polymorphic route as fallback
|
|
50
|
+
link_to mapping.internal_id, [:edit, :admin, mapping.internal], class: 'text-primary'
|
|
51
|
+
end
|
|
52
|
+
rescue StandardError => e
|
|
53
|
+
Rails.logger.warn("Failed to generate link for mapping #{mapping.id}: #{e.message}")
|
|
54
|
+
mapping.internal_id
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -23,8 +23,9 @@ module SpreeCmCommissioner
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def adjust_inventory_items_async(variant_id, quantity)
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
args = { variant_id: variant_id, quantity: quantity }
|
|
27
|
+
CmAppLogger.log(label: "#{self.class.name}#adjust_inventory_items_async", data: args) do
|
|
28
|
+
SpreeCmCommissioner::Stock::InventoryItemsAdjusterJob.perform_later(**args)
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Integrations
|
|
3
|
+
# Base class for integration jobs
|
|
4
|
+
#
|
|
5
|
+
# Provides common error handling and alerting functionality for all integration jobs.
|
|
6
|
+
# Subclasses should implement their own perform logic and call the appropriate
|
|
7
|
+
# error handler methods.
|
|
8
|
+
class BaseJob < ApplicationJob
|
|
9
|
+
queue_as :integration
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
# Handle error by logging and sending alert
|
|
14
|
+
# @param label [String] The error label for logging
|
|
15
|
+
# @param error [StandardError] The error that occurred
|
|
16
|
+
# @param data [Hash] Additional context data (e.g., sync_type, match_id, event_type)
|
|
17
|
+
def handle_error(label:, error:, data: {})
|
|
18
|
+
error_message = "#{error.class.name}: #{error.message}"
|
|
19
|
+
|
|
20
|
+
# Log the error with structured data
|
|
21
|
+
CmAppLogger.error(
|
|
22
|
+
label: label,
|
|
23
|
+
data: data.merge(
|
|
24
|
+
message: error_message,
|
|
25
|
+
backtrace: error.backtrace
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Send alert via Telegram
|
|
30
|
+
TelegramAlerts::IntegrationSyncFailureJob.perform_later(
|
|
31
|
+
error_message: error_message,
|
|
32
|
+
data: data
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
error_message
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Integrations
|
|
3
|
+
# Job that performs the actual polling (syncing) for a specific integration
|
|
4
|
+
#
|
|
5
|
+
# This job is enqueued by PollingSchedulerJob and handles:
|
|
6
|
+
# - Full syncs: Complete refresh of all data
|
|
7
|
+
# - Incremental syncs: Fetch only recent changes
|
|
8
|
+
# - Webhook syncs: Real-time event processing (triggered externally)
|
|
9
|
+
#
|
|
10
|
+
# Each integration type (StadiumXV1, LarrytaV1, etc.) must implement
|
|
11
|
+
# a sync_manager that responds to sync_full!, sync_incremental!, and sync_webhook!
|
|
12
|
+
class PollingJob < BaseJob
|
|
13
|
+
# Perform the integration pull
|
|
14
|
+
# @param integration_id [Integer] The integration ID
|
|
15
|
+
# @param sync_type [String] The sync type: 'full', 'incremental', or 'webhook_triggered'
|
|
16
|
+
# @param event_type [String] Optional event type for webhook syncs
|
|
17
|
+
# @param event_data [Hash] Optional event data for webhook syncs
|
|
18
|
+
def perform(options)
|
|
19
|
+
integration_id = options[:integration_id]
|
|
20
|
+
sync_type = options[:sync_type]
|
|
21
|
+
event_type = options[:event_type]
|
|
22
|
+
event_data = options[:event_data]
|
|
23
|
+
integration = SpreeCmCommissioner::Integration.find(integration_id)
|
|
24
|
+
|
|
25
|
+
# integration is no longer active, skip processing.
|
|
26
|
+
return unless integration.active?
|
|
27
|
+
|
|
28
|
+
SpreeCmCommissioner::Integrations::Polling.new.call(
|
|
29
|
+
integration: integration,
|
|
30
|
+
sync_type: sync_type,
|
|
31
|
+
event_type: event_type,
|
|
32
|
+
event_data: event_data
|
|
33
|
+
)
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
handle_sync_error(e, options)
|
|
36
|
+
raise
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Handle sync errors and alert
|
|
42
|
+
# @param error [StandardError] The error that occurred
|
|
43
|
+
# @param options [Hash] The options passed to perform
|
|
44
|
+
def handle_sync_error(error, options)
|
|
45
|
+
handle_error(
|
|
46
|
+
label: 'Integrations::IntegrationPullJob#perform Sync failed',
|
|
47
|
+
error: error,
|
|
48
|
+
data: options
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Integrations
|
|
3
|
+
# Scheduler job that runs periodically to enqueue integration polling jobs
|
|
4
|
+
#
|
|
5
|
+
# This job is responsible for:
|
|
6
|
+
# - Running full syncs every 24 hours
|
|
7
|
+
# - Running incremental syncs every 10 seconds
|
|
8
|
+
# - Webhook syncs are triggered externally (not scheduled here)
|
|
9
|
+
#
|
|
10
|
+
# The job queries all active integrations and enqueues appropriate pull jobs
|
|
11
|
+
# based on their last sync time and sync type configuration.
|
|
12
|
+
class PollingSchedulerJob < BaseJob
|
|
13
|
+
def perform
|
|
14
|
+
SpreeCmCommissioner::Integrations::PollingScheduler.new.call
|
|
15
|
+
rescue StandardError => e
|
|
16
|
+
handle_scheduler_error(e)
|
|
17
|
+
raise
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def handle_scheduler_error(error)
|
|
23
|
+
handle_error(
|
|
24
|
+
label: 'Integrations::PollingSchedulerJob#perform Error scheduling integration pulls',
|
|
25
|
+
error: error
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module Stock
|
|
3
3
|
class InventoryItemsAdjusterJob < ApplicationUniqueJob
|
|
4
|
-
def perform(
|
|
5
|
-
variant = Spree::Variant.
|
|
4
|
+
def perform(options = {})
|
|
5
|
+
variant = Spree::Variant.find_by(id: options[:variant_id])
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# potentially the variant was deleted during the job wait time, we just skip in that case.
|
|
8
|
+
return if variant.blank?
|
|
9
|
+
|
|
10
|
+
SpreeCmCommissioner::Stock::InventoryItemsAdjuster.call(variant: variant, quantity: options[:quantity])
|
|
8
11
|
end
|
|
9
12
|
end
|
|
10
13
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module TelegramAlerts
|
|
3
|
+
class IntegrationSyncFailureJob < ApplicationJob
|
|
4
|
+
queue_as :telegram_bot
|
|
5
|
+
|
|
6
|
+
# Handle error by logging and sending alert
|
|
7
|
+
# @param error_message [String] The error message
|
|
8
|
+
# @param data [Hash] Additional context data (e.g., sync_type, match_id, event_type)
|
|
9
|
+
def perform(options)
|
|
10
|
+
SpreeCmCommissioner::TelegramAlerts::IntegrationSyncFailure.call(
|
|
11
|
+
error_message: options[:error_message],
|
|
12
|
+
data: options[:data] || {}
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|