spree_cm_commissioner 2.4.2 → 2.5.0.pre.pre1

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +1 -1
  3. data/.tool-versions +1 -1
  4. data/Gemfile.lock +1 -1
  5. data/README.md +32 -0
  6. data/app/controllers/spree/admin/integration_mappings_controller.rb +21 -0
  7. data/app/controllers/spree/admin/integration_sessions_controller.rb +21 -0
  8. data/app/controllers/spree/admin/integrations_controller.rb +83 -0
  9. data/app/controllers/spree/api/v2/storefront/event_matches_controller.rb +15 -0
  10. data/app/controllers/spree/api/v2/storefront/tenants_controller.rb +30 -0
  11. data/app/errors/spree_cm_commissioner/integrations/external_client_error.rb +10 -0
  12. data/app/errors/spree_cm_commissioner/integrations/sync_error.rb +4 -0
  13. data/app/finders/spree_cm_commissioner/events/find_matches.rb +15 -0
  14. data/app/helpers/spree_cm_commissioner/external_integrations_helper.rb +58 -0
  15. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
  16. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +4 -2
  17. data/app/jobs/spree_cm_commissioner/integrations/base_job.rb +39 -0
  18. data/app/jobs/spree_cm_commissioner/integrations/polling_job.rb +53 -0
  19. data/app/jobs/spree_cm_commissioner/integrations/polling_scheduler_job.rb +30 -0
  20. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +5 -4
  21. data/app/jobs/spree_cm_commissioner/telegram_alerts/integration_sync_failure_job.rb +17 -0
  22. data/app/models/concerns/spree_cm_commissioner/integrations/integration_mappable.rb +58 -0
  23. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +15 -3
  24. data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +14 -2
  25. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +2 -0
  26. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -7
  27. data/app/models/spree_cm_commissioner/integration.rb +21 -0
  28. data/app/models/spree_cm_commissioner/integration_mapping.rb +37 -0
  29. data/app/models/spree_cm_commissioner/integration_sync_session.rb +15 -0
  30. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +21 -0
  31. data/app/models/spree_cm_commissioner/inventory_item.rb +5 -1
  32. data/app/models/spree_cm_commissioner/option_type_decorator.rb +8 -0
  33. data/app/models/spree_cm_commissioner/option_value_decorator.rb +34 -0
  34. data/app/models/spree_cm_commissioner/product_decorator.rb +3 -2
  35. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -2
  36. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +2 -2
  37. data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
  38. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +13 -1
  39. data/app/models/spree_cm_commissioner/variant_decorator.rb +7 -4
  40. data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -0
  41. data/app/overrides/spree/admin/shared/sub_menu/_integrations/external_integrations.html.erb.deface +6 -0
  42. data/app/presenters/spree/variants/{visable_options_presenter.rb → visible_options_presenter.rb} +2 -4
  43. data/app/serializers/spree/v2/storefront/tenant_serializer.rb +14 -0
  44. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +69 -0
  45. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +183 -0
  46. data/app/services/spree_cm_commissioner/integrations/polling.rb +70 -0
  47. data/app/services/spree_cm_commissioner/integrations/polling_scheduler.rb +79 -0
  48. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +152 -0
  49. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +113 -0
  50. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +215 -0
  51. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/base.rb +20 -0
  52. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/league.rb +19 -0
  53. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/match.rb +95 -0
  54. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/ticket.rb +81 -0
  55. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/ticket_image.rb +19 -0
  56. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/zone.rb +90 -0
  57. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +35 -0
  58. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +38 -0
  59. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +44 -0
  60. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +16 -0
  61. data/app/services/spree_cm_commissioner/telegram_alerts/integration_sync_failure.rb +49 -0
  62. data/app/views/spree/admin/integration_mappings/_integration_mappings.html.erb +107 -0
  63. data/app/views/spree/admin/integration_mappings/index.html.erb +33 -0
  64. data/app/views/spree/admin/integration_sessions/_integration_sync_sessions.html.erb +116 -0
  65. data/app/views/spree/admin/integration_sessions/index.html.erb +42 -0
  66. data/app/views/spree/admin/integrations/_form.html.erb +104 -0
  67. data/app/views/spree/admin/integrations/_stadium_x_v1_fields.html.erb +29 -0
  68. data/app/views/spree/admin/integrations/edit.html.erb +45 -0
  69. data/app/views/spree/admin/integrations/index.html.erb +75 -0
  70. data/app/views/spree/admin/integrations/new.html.erb +25 -0
  71. data/app/views/spree/admin/tenants/_form.html.erb +79 -36
  72. data/config/locales/en.yml +8 -0
  73. data/config/locales/km.yml +8 -0
  74. data/config/routes.rb +9 -1
  75. data/db/migrate/20251017094845_create_cm_integrations.rb +22 -0
  76. data/db/migrate/20251017101555_create_cm_integration_sync_sessions.rb +68 -0
  77. data/db/migrate/20251017101605_create_cm_integration_mappings.rb +52 -0
  78. data/lib/cm_app_logger.rb +1 -0
  79. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +25 -0
  80. data/lib/spree_cm_commissioner/test_helper/factories/integration_mapping_factory.rb +14 -0
  81. data/lib/spree_cm_commissioner/test_helper/factories/integration_sync_session_factory.rb +7 -0
  82. data/lib/spree_cm_commissioner/version.rb +1 -1
  83. data/lib/spree_cm_commissioner.rb +8 -7
  84. metadata +58 -5
@@ -0,0 +1,49 @@
1
+ module SpreeCmCommissioner
2
+ module TelegramAlerts
3
+ class IntegrationSyncFailure
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ # Send Telegram alert for integration sync failure
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 call(error_message:, data: {})
10
+ chat_id = ENV.fetch('EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID', nil)
11
+ return failure(nil, 'Telegram chat ID not configured') if chat_id.blank?
12
+
13
+ telegram_client = ::Telegram.bots[:exception_notifier]
14
+ return failure(nil, 'Telegram bot not configured') if telegram_client.blank?
15
+
16
+ telegram_client.send_message(
17
+ chat_id: chat_id,
18
+ parse_mode: 'HTML',
19
+ text: format_message(error_message, data)
20
+ )
21
+
22
+ success(message: 'Alert sent successfully')
23
+ rescue StandardError => e
24
+ failure(nil, "Failed to send Telegram alert: #{e.message}")
25
+ end
26
+
27
+ private
28
+
29
+ def format_message(error_message, data)
30
+ text = []
31
+
32
+ text << '🚨 <b>Integration Sync Failed</b>'
33
+
34
+ # Render all data fields dynamically
35
+ data.each do |key, value|
36
+ next if value.blank?
37
+
38
+ formatted_key = key.to_s.humanize
39
+ text << "<b>#{formatted_key}:</b> <code>#{value}</code>"
40
+ end
41
+
42
+ text << "<b>Error:</b> <code>#{error_message}</code>"
43
+ text << "<b>Time:</b> <code>#{Time.current.iso8601}</code>"
44
+
45
+ text.compact.join("\n")
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,107 @@
1
+ <div class="table-responsive">
2
+ <table class="table table-sm" id="listing_integration_mappings" data-hook>
3
+ <thead class="text-muted">
4
+ <tr>
5
+ <th><%= Spree.t(:id) %></th>
6
+ <th><%= Spree.t(:external_id) %></th>
7
+ <th><%= Spree.t(:internal_type) %></th>
8
+ <th><%= Spree.t(:internal_id) %></th>
9
+ <th><%= Spree.t(:status) %></th>
10
+ <th><%= Spree.t(:last_synced_at) %></th>
11
+ <th><%= Spree.t(:created_at) %></th>
12
+ <th><%= Spree.t(:updated_at) %></th>
13
+ <th></th>
14
+ </tr>
15
+ </thead>
16
+
17
+ <tbody>
18
+ <% mappings = defined?(@mappings) ? @mappings : integration.integration_mappings.order(updated_at: :desc).limit(25) %>
19
+ <% mappings.each do |mapping| %>
20
+ <tr id="integration_mapping_<%= mapping.id %>">
21
+ <td><%= mapping.id %></td>
22
+ <td><%= mapping.external_id %></td>
23
+ <td><%= mapping.internal_type %></td>
24
+ <td>
25
+ <%= integration_mapping_internal_link(mapping) %>
26
+ </td>
27
+ <td><%= mapping.status %></td>
28
+ <td><%= mapping.last_synced_at %></td>
29
+ <td><%= mapping.created_at %></td>
30
+ <td><%= mapping.updated_at %></td>
31
+
32
+ <td class="actions">
33
+ <% if mapping.external_payload.present? %>
34
+ <%= link_to_with_icon 'activity.svg', Spree.t("admin.external_integrations.view_result"), "#mapping_payload_modal_#{mapping.id}",
35
+ class: 'btn btn-light btn-sm',
36
+ no_text: true,
37
+ data: { toggle: 'modal', target: "#mapping_payload_modal_#{mapping.id}" },
38
+ title: Spree.t("admin.external_integrations.external_payload") %>
39
+ <% end %>
40
+ </td>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
45
+
46
+ <% if mappings.empty? %>
47
+ <small class="form-text text-muted"><%= Spree.t("admin.external_integrations.no_results") %></small>
48
+ <% end %>
49
+
50
+ <% if defined?(@mappings) && @mappings.respond_to?(:total_pages) %>
51
+ <div class="mt-3">
52
+ <%= paginate @mappings, theme: 'admin-twitter-bootstrap-4' %>
53
+ </div>
54
+ <% end %>
55
+ </div>
56
+
57
+ <% mappings.each do |mapping| %>
58
+ <% if mapping.external_payload.present? %>
59
+ <div
60
+ class="modal fade"
61
+ id="mapping_payload_modal_<%= mapping.id %>"
62
+ tabindex="-1"
63
+ role="dialog"
64
+ aria-labelledby="mapping_payload_modal_label_<%= mapping.id %>"
65
+ aria-hidden="true"
66
+ >
67
+ <div class="modal-dialog modal-lg" role="document">
68
+ <div class="modal-content">
69
+ <div class="modal-header">
70
+ <h5
71
+ class="modal-title"
72
+ id="mapping_payload_modal_label_<%= mapping.id %>"
73
+ >
74
+ <%= Spree.t("admin.external_integrations.external_payload") %> - Mapping #<%= mapping.id %>
75
+ </h5>
76
+
77
+ <button
78
+ type="button"
79
+ class="close"
80
+ data-dismiss="modal"
81
+ aria-label="Close"
82
+ >
83
+ <span aria-hidden="true">&times;</span>
84
+ </button>
85
+ </div>
86
+
87
+ <div class="modal-body">
88
+ <pre
89
+ class="small mb-0 p-2 bg-light border rounded"
90
+ style="max-height: 500px; overflow-y: auto;"
91
+ ><code><%= JSON.pretty_generate(mapping.external_payload) %></code></pre>
92
+ </div>
93
+
94
+ <div class="modal-footer">
95
+ <button
96
+ type="button"
97
+ class="btn btn-secondary"
98
+ data-dismiss="modal"
99
+ >
100
+ <%= Spree.t(:close) %>
101
+ </button>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ <% end %>
107
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= @integration.name %>
3
+ <% end %>
4
+
5
+ <% content_for :page_tabs do %>
6
+ <li class="nav-item">
7
+ <%= link_to Spree.t(:edit),
8
+ edit_admin_integration_path(@integration),
9
+ class: "nav-link" %>
10
+ </li>
11
+
12
+ <li class="nav-item">
13
+ <%= link_to Spree.t(:integration_sync_sessions),
14
+ admin_integration_sessions_path(@integration),
15
+ class: "nav-link" %>
16
+ </li>
17
+
18
+ <li class="nav-item">
19
+ <%= link_to Spree.t(:integration_mappings),
20
+ admin_integration_mappings_path(@integration),
21
+ class: "nav-link active" %>
22
+ </li>
23
+ <% end %>
24
+
25
+ <div data-hook="admin_integrations_mappings" class="card mb-3">
26
+ <div class="card-header">
27
+ <h5 class="card-title mb-0 h6"><%= Spree.t(:integration_mappings) %></h5>
28
+ </div>
29
+
30
+ <div class="card-body">
31
+ <%= render partial: 'integration_mappings', locals: { integration: @integration } %>
32
+ </div>
33
+ </div>
@@ -0,0 +1,116 @@
1
+ <div class="table-responsive">
2
+ <table
3
+ class="table table-sm"
4
+ id="listing_integration_sync_sessions"
5
+ data-hook
6
+ >
7
+ <thead class="text-muted">
8
+ <tr>
9
+ <th><%= Spree.t(:id) %></th>
10
+ <th><%= Spree.t(:sync_type) %></th>
11
+ <th><%= Spree.t(:status) %></th>
12
+ <th><%= Spree.t(:started_at) %></th>
13
+ <th><%= Spree.t(:ended_at) %></th>
14
+ <th><%= Spree.t(:duration) %></th>
15
+ <th></th>
16
+ </tr>
17
+ </thead>
18
+
19
+ <tbody>
20
+ <% sync_sessions = defined?(@sync_sessions) ? @sync_sessions : integration.integration_sync_sessions.order(created_at: :desc).limit(25) %>
21
+ <% sync_sessions.each do |sess| %>
22
+ <tr id="integration_sync_session_<%= sess.id %>">
23
+ <td><%= sess.id %></td>
24
+ <td><%= sess.sync_type %></td>
25
+ <td><%= sess.status %></td>
26
+ <td><%= sess.started_at %></td>
27
+ <td><%= sess.ended_at %></td>
28
+ <td>
29
+ <% if sess.started_at && sess.ended_at %>
30
+ <% duration = (sess.ended_at - sess.started_at).to_i %>
31
+
32
+ <% if duration < 60 %>
33
+ <%= "#{duration} sec" %>
34
+ <% else %>
35
+ <%= "#{duration / 60} min" %>
36
+ <% end %>
37
+ <% else %>
38
+ N/A
39
+ <% end %>
40
+ </td>
41
+ <td class="actions">
42
+ <% if sess.sync_result.present? %>
43
+ <%= link_to_with_icon 'activity.svg', Spree.t("admin.external_integrations.view_result"), "#session_result_modal_#{sess.id}",
44
+ class: 'btn btn-light btn-sm',
45
+ no_text: true,
46
+ data: { toggle: 'modal', target: "#session_result_modal_#{sess.id}" },
47
+ title: Spree.t("admin.external_integrations.sync_result") %>
48
+ <% end %>
49
+ </td>
50
+ </tr>
51
+ <% end %>
52
+ </tbody>
53
+ </table>
54
+
55
+ <% if sync_sessions.empty? %>
56
+ <small class="form-text text-muted"><%= Spree.t("admin.external_integrations.no_results") %></small>
57
+ <% end %>
58
+
59
+ <% if defined?(@sync_sessions) && @sync_sessions.respond_to?(:total_pages) %>
60
+ <div class="mt-3">
61
+ <%= paginate @sync_sessions, theme: 'admin-twitter-bootstrap-4' %>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+
66
+ <% sync_sessions.each do |sess| %>
67
+ <% if sess.sync_result.present? %>
68
+ <div
69
+ class="modal fade"
70
+ id="session_result_modal_<%= sess.id %>"
71
+ tabindex="-1"
72
+ role="dialog"
73
+ aria-labelledby="session_result_modal_label_<%= sess.id %>"
74
+ aria-hidden="true"
75
+ >
76
+ <div class="modal-dialog modal-lg" role="document">
77
+ <div class="modal-content">
78
+ <div class="modal-header">
79
+ <h5
80
+ class="modal-title"
81
+ id="session_result_modal_label_<%= sess.id %>"
82
+ >
83
+ <%= Spree.t("admin.external_integrations.sync_result") %> - Session #<%= sess.id %>
84
+ </h5>
85
+
86
+ <button
87
+ type="button"
88
+ class="close"
89
+ data-dismiss="modal"
90
+ aria-label="Close"
91
+ >
92
+ <span aria-hidden="true">&times;</span>
93
+ </button>
94
+ </div>
95
+
96
+ <div class="modal-body">
97
+ <pre
98
+ class="small mb-0 p-2 bg-light border rounded"
99
+ style="max-height: 500px; overflow-y: auto;"
100
+ ><code><%= JSON.pretty_generate(sess.sync_result) %></code></pre>
101
+ </div>
102
+
103
+ <div class="modal-footer">
104
+ <button
105
+ type="button"
106
+ class="btn btn-secondary"
107
+ data-dismiss="modal"
108
+ >
109
+ <%= Spree.t(:close) %>
110
+ </button>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ <% end %>
116
+ <% end %>
@@ -0,0 +1,42 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= @integration.name %>
3
+ <% end %>
4
+
5
+ <% content_for :page_tabs do %>
6
+ <li class="nav-item">
7
+ <%= link_to Spree.t(:edit),
8
+ edit_admin_integration_path(@integration),
9
+ class: "nav-link" %>
10
+ </li>
11
+
12
+ <li class="nav-item">
13
+ <%= link_to Spree.t(:integration_sync_sessions),
14
+ admin_integration_sessions_path(@integration),
15
+ class: "nav-link active" %>
16
+ </li>
17
+
18
+ <li class="nav-item">
19
+ <%= link_to Spree.t(:integration_mappings),
20
+ admin_integration_mappings_path(@integration),
21
+ class: "nav-link" %>
22
+ </li>
23
+ <% end %>
24
+
25
+ <div data-hook="admin_integrations_sessions" class="card mb-3">
26
+ <div class="card-header d-flex justify-content-between align-items-center">
27
+ <h5 class="card-title mb-0 h6">
28
+ <%= Spree.t(:integration_sync_sessions) %>
29
+ </h5>
30
+
31
+ <div class="card-header-actions">
32
+ <%= button_to Spree.t(:enqueue_full_sync),
33
+ enqueue_polling_admin_integration_path(@integration, sync_type: :full),
34
+ method: :post,
35
+ class: 'btn btn-primary btn-sm' %>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="card-body">
40
+ <%= render partial: 'integration_sync_sessions', locals: { integration: @integration } %>
41
+ </div>
42
+ </div>
@@ -0,0 +1,104 @@
1
+ <div data-hook="admin_external_integrations_form_fields" class="row">
2
+ <div class="col-6">
3
+ <%= f.field_container :type do %>
4
+ <%= f.label :type, raw(I18n.t('spree.admin.external_integrations.integration_type', default: 'Integration type') + required_span_tag) %>
5
+
6
+ <% if f.object.persisted? %>
7
+ <%= f.text_field :type, value: f.object.type, class: 'form-control', disabled: true, readonly: true %>
8
+ <%= f.hidden_field :type, value: f.object.type %>
9
+ <% else %>
10
+ <%= f.select :type,
11
+ options_for_select(@integration_type_options, f.object.type),
12
+ { include_blank: Spree.t(:choose_an_option) },
13
+ class: 'form-control select2',
14
+ required: true %>
15
+ <% end %>
16
+
17
+ <%= f.error_message_on :type %>
18
+ <% end %>
19
+ </div>
20
+
21
+ <div class="col-6">
22
+ <%= f.field_container :name do %>
23
+ <%= f.label :name, raw(Spree.t(:name) + required_span_tag) %>
24
+ <%= f.text_field :name, required: true, class: 'form-control', placeholder: Spree.t(:name) %>
25
+ <%= f.error_message_on :name %>
26
+ <% end %>
27
+ </div>
28
+
29
+ <div class="col-6">
30
+ <%= f.field_container :vendor_id do %>
31
+ <%= f.label :vendor_id, raw(I18n.t('spree.admin.external_integrations.vendor', default: 'Vendor') + required_span_tag) %>
32
+
33
+ <%= f.select :vendor_id,
34
+ options_from_collection_for_select(@vendors, :id, :name, f.object.vendor_id),
35
+ { include_blank: Spree.t(:choose_an_option) },
36
+ class: 'form-control select2',
37
+ required: true %>
38
+
39
+ <%= f.error_message_on :vendor_id %>
40
+ <% end %>
41
+ </div>
42
+
43
+ <div class="col-6">
44
+ <%= f.field_container :tenant_id do %>
45
+ <%= f.label :tenant_id, I18n.t('spree.admin.external_integrations.tenant', default: 'Tenant') %>
46
+
47
+ <%= f.select :tenant_id,
48
+ options_from_collection_for_select(@tenants, :id, :name, f.object.tenant_id),
49
+ { include_blank: Spree.t(:choose_an_option), prompt: Spree.t(:optional) },
50
+ class: 'form-control select2',
51
+ data: { allow_clear: true, placeholder: Spree.t(:optional) } %>
52
+
53
+ <%= f.error_message_on :tenant_id %>
54
+ <% end %>
55
+ </div>
56
+
57
+ <div class="col-6">
58
+ <%= f.field_container :status do %>
59
+ <%= f.label :status, raw(Spree.t(:status) + required_span_tag) %>
60
+
61
+ <%= f.select :status,
62
+ options_for_select(@status_options, f.object.status),
63
+ {},
64
+ class: 'form-control select2',
65
+ required: true %>
66
+
67
+ <%= f.error_message_on :status %>
68
+ <% end %>
69
+ </div>
70
+
71
+ <div class="col-6">
72
+ <%= f.field_container :conflict_strategy do %>
73
+ <%= f.label :conflict_strategy, raw(I18n.t('spree.admin.external_integrations.conflict_strategy', default: 'Conflict strategy') + required_span_tag) %>
74
+
75
+ <%= f.select :conflict_strategy,
76
+ options_for_select(@conflict_strategy_options, f.object.conflict_strategy),
77
+ {},
78
+ class: 'form-control select2',
79
+ required: true %>
80
+
81
+ <%= f.error_message_on :conflict_strategy %>
82
+ <% end %>
83
+ </div>
84
+
85
+ <div class="col-6">
86
+ <%= f.field_container :incremental_sync_interval_seconds do %>
87
+ <%= f.label :incremental_sync_interval_seconds, raw(I18n.t('spree.admin.external_integrations.incremental_sync_interval_seconds', default: 'Incremental sync interval (seconds)') + required_span_tag) %>
88
+ <%= f.number_field :incremental_sync_interval_seconds, class: 'form-control', min: 1, max: 3600, required: true %>
89
+ <%= f.error_message_on :incremental_sync_interval_seconds %>
90
+ <% end %>
91
+ </div>
92
+
93
+ <div class="col-6">
94
+ <%= f.field_container :full_sync_interval_hours do %>
95
+ <%= f.label :full_sync_interval_hours, raw(I18n.t('spree.admin.external_integrations.full_sync_interval_hours', default: 'Full sync interval (hours)') + required_span_tag) %>
96
+ <%= f.number_field :full_sync_interval_hours, class: 'form-control', min: 1, max: 168, required: true %>
97
+ <%= f.error_message_on :full_sync_interval_hours %>
98
+ <% end %>
99
+ </div>
100
+
101
+ <% if f.object.type == 'SpreeCmCommissioner::Integrations::StadiumXV1' || (f.object.new_record? && params[:type] == 'SpreeCmCommissioner::Integrations::StadiumXV1') %>
102
+ <%= render partial: 'stadium_x_v1_fields', locals: { f: f } %>
103
+ <% end %>
104
+ </div>
@@ -0,0 +1,29 @@
1
+ <!-- Stadium X V1 API Credentials -->
2
+ <div class="col-12">
3
+ <hr class="my-4">
4
+
5
+ <h6 class="text-muted">
6
+ <%= I18n.t('spree.admin.external_integrations.stadium_x_credentials', default: 'Stadium X API Credentials') %>
7
+ </h6>
8
+ </div>
9
+
10
+ <div class="col-6">
11
+ <%= f.field_container :public_key do %>
12
+ <%= f.label :public_key, raw(I18n.t('spree.admin.external_integrations.public_key', default: 'Public Key') + required_span_tag) %>
13
+ <%= text_field_tag 'spree_cm_commissioner_integration[private_metadata][public_key]', f.object.public_key, class: 'form-control', required: true %>
14
+ <% end %>
15
+ </div>
16
+
17
+ <div class="col-6">
18
+ <%= f.field_container :private_key do %>
19
+ <%= f.label :private_key, raw(I18n.t('spree.admin.external_integrations.private_key', default: 'Private Key') + required_span_tag) %>
20
+ <%= text_field_tag 'spree_cm_commissioner_integration[private_metadata][private_key]', f.object.private_key, class: 'form-control', required: true %>
21
+ <% end %>
22
+ </div>
23
+
24
+ <div class="col-6">
25
+ <%= f.field_container :base_url do %>
26
+ <%= f.label :base_url, raw(I18n.t('spree.admin.external_integrations.base_url', default: 'Base URL') + required_span_tag) %>
27
+ <%= text_field_tag 'spree_cm_commissioner_integration[private_metadata][base_url]', f.object.base_url, class: 'form-control', placeholder: 'https://api.stadiumx.com', required: true %>
28
+ <% end %>
29
+ </div>
@@ -0,0 +1,45 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= @object.name %>
3
+ <% end %>
4
+
5
+ <% content_for :page_tabs do %>
6
+ <li class="nav-item">
7
+ <%= link_to Spree.t(:edit),
8
+ edit_admin_integration_path(@object),
9
+ class: "nav-link active" %>
10
+ </li>
11
+
12
+ <li class="nav-item">
13
+ <%= link_to Spree.t(:integration_sync_sessions),
14
+ admin_integration_sessions_path(@object),
15
+ class: "nav-link" %>
16
+ </li>
17
+
18
+ <li class="nav-item">
19
+ <%= link_to Spree.t(:integration_mappings),
20
+ admin_integration_mappings_path(@object),
21
+ class: "nav-link" %>
22
+ </li>
23
+ <% end %>
24
+
25
+ <div data-hook="admin_integrations" class="card mb-3">
26
+ <div class="card-header">
27
+ <h5 class="card-title mb-0 h6"><%= Spree.t('external_integrations') %></h5>
28
+ </div>
29
+
30
+ <div class="card-body">
31
+ <div data-hook="admin_integrations_edit_form_header">
32
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
33
+ </div>
34
+
35
+ <div data-hook="admin_integrations_edit_form">
36
+ <%= form_with model: @object, url: admin_integration_path(@object), method: :patch, scope: :spree_cm_commissioner_integration do |f| %>
37
+ <%= render partial: 'form', locals: { f: f } %>
38
+
39
+ <div data-hook="admin_integrations_edit_form_buttons">
40
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.admin_integrations_path } %>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+ </div>
@@ -0,0 +1,75 @@
1
+ <% content_for :page_title do %>
2
+ <%= Spree.t('external_integrations') %>
3
+ <% end %>
4
+
5
+ <% content_for :page_actions do %>
6
+ <%= button_link_to Spree.t(:new_external_integration), new_admin_integration_path, class: "btn-success", icon: 'add.svg' %>
7
+ <% end if can? :create, SpreeCmCommissioner::Integration %>
8
+
9
+ <% if @integrations.any? %>
10
+ <div class="table-responsive border rounded bg-white mb-3">
11
+ <table class="table" id="listing_integrations" data-hook>
12
+ <thead class="text-muted">
13
+ <tr data-hook="admin_integrations_index_headers">
14
+ <th><%= Spree.t(:name) %></th>
15
+ <th><%= Spree.t(:base_url) %></th>
16
+ <th><%= Spree.t(:conflict_strategy) %></th>
17
+ <th><%= Spree.t(:incremental_sync_interval_seconds) %></th>
18
+ <th><%= Spree.t(:full_sync_interval_hours) %></th>
19
+ <th><%= Spree.t(:vendor) %></th>
20
+ <th><%= Spree.t(:status) %></th>
21
+ <th><%= Spree.t(:updated_at) %></th>
22
+ </tr>
23
+ </thead>
24
+
25
+ <tbody>
26
+ <% @integrations.each do |integration| %>
27
+ <tr
28
+ id="<%= spree_dom_id integration %>"
29
+ data-hook="admin_integrations_index_rows"
30
+ >
31
+ <td><%= integration.name %></td>
32
+
33
+ <td>
34
+ <%= integration_base_url_link(integration.base_url) %>
35
+ </td>
36
+
37
+ <td>
38
+ <%= integration_conflict_strategy_badge(integration.conflict_strategy) %>
39
+ </td>
40
+
41
+ <td>
42
+ <%= integration_interval_badge(integration.incremental_sync_interval_seconds, 's') %>
43
+ </td>
44
+
45
+ <td>
46
+ <%= integration_interval_badge(integration.full_sync_interval_hours, 'h') %>
47
+ </td>
48
+
49
+ <td><%= integration.vendor.name %></td>
50
+
51
+ <td>
52
+ <%= integration_status_badge(integration.status) %>
53
+ </td>
54
+
55
+ <td><%= integration.updated_at %></td>
56
+
57
+ <td
58
+ data-hook="admin_integrations_index_row_actions"
59
+ class="actions"
60
+ >
61
+ <span class="d-flex justify-content-end">
62
+ <%= link_to_edit integration, url: edit_admin_integration_path(integration), no_text: true if can?(:edit, integration) %>
63
+ <%= link_to_delete integration, url: admin_integration_path(integration), no_text: true if can?(:delete, integration) %>
64
+ </span>
65
+ </td>
66
+ </tr>
67
+ <% end %>
68
+ </tbody>
69
+ </table>
70
+ </div>
71
+ <% else %>
72
+ <small class="form-text text-muted">
73
+ <%= raw I18n.t('external_integrations.empty_info') %>
74
+ </small>
75
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= Spree.t(:new) %>
3
+ <% end %>
4
+
5
+ <div data-hook="admin_integrations" class="card mb-3">
6
+ <div class="card-header">
7
+ <h5 class="card-title mb-0 h6"><%= Spree.t('external_integrations') %></h5>
8
+ </div>
9
+
10
+ <div class="card-body">
11
+ <div data-hook="admin_integrations_new_form_header">
12
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
13
+ </div>
14
+
15
+ <div data-hook="admin_integrations_new_form">
16
+ <%= form_with model: @object, url: { action: 'create' }, scope: :spree_cm_commissioner_integration do |f| %>
17
+ <%= render partial: 'form', locals: { f: f } %>
18
+
19
+ <div data-hook="admin_integrations_new_form_buttons">
20
+ <%= render partial: 'spree/admin/shared/new_resource_links' %>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+ </div>
25
+ </div>