spree_cm_commissioner 2.14.6 → 2.14.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/models/spree_cm_commissioner/integration.rb +1 -1
- data/app/models/spree_cm_commissioner/integration_mapping.rb +27 -0
- data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +1 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +33 -11
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +3 -2
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +7 -3
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +44 -32
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +1 -1
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +1 -1
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +12 -1
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +2 -2
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +1 -1
- data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +2 -1
- data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +2 -1
- data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +10 -0
- data/app/views/spree/admin/integrations/_form.html.erb +1 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2828d409efc450a1771a72ddee9866379996fbe09b29ee4eec5b446ed4d6acfc
|
|
4
|
+
data.tar.gz: 0cf499cd5d3bb7947385761cfff6c569a9180f96fe60426fa3ba802dece704a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c2531492454f38d71fbcb6115b97d442b29631b49b92cc5c8961cc395b761014bf24c53a99301150a5480ac42484838319dbac8c7d8f3d924708a6585bc7f9b1
|
|
7
|
+
data.tar.gz: 7e4bf0769cfdc8364a492fdb6fabbce2d72f1da7df9289dbbf68562312d3604880918f5eab80aa4b3e5f4098366fa8bc7c2ae38907bd8fc2a9b5fae60e92bcc0
|
data/Gemfile.lock
CHANGED
|
@@ -11,7 +11,7 @@ module SpreeCmCommissioner
|
|
|
11
11
|
has_many :integration_mappings, class_name: 'SpreeCmCommissioner::IntegrationMapping', dependent: :destroy, inverse_of: :integration
|
|
12
12
|
has_many :integration_sync_sessions, class_name: 'SpreeCmCommissioner::IntegrationSyncSession', dependent: :destroy, inverse_of: :integration
|
|
13
13
|
|
|
14
|
-
validates :incremental_sync_interval_seconds, presence: true, numericality: { only_integer: true, greater_than: 0
|
|
14
|
+
validates :incremental_sync_interval_seconds, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
|
15
15
|
validates :full_sync_interval_hours, presence: true, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 168 }
|
|
16
16
|
|
|
17
17
|
# Listed from the folder rather than `descendants`, which misses subclasses Zeitwerk hasn't
|
|
@@ -2,6 +2,8 @@ module SpreeCmCommissioner
|
|
|
2
2
|
class IntegrationMapping < Base
|
|
3
3
|
include StoreMetadata
|
|
4
4
|
|
|
5
|
+
CONFIRM_INTERVAL = 1.hour
|
|
6
|
+
|
|
5
7
|
enum status: { active: 0, archived: 1 }
|
|
6
8
|
|
|
7
9
|
# polymorphic (Spree::Taxon, Spree::Product, Spree::Variant, Spree::Vendor)
|
|
@@ -30,8 +32,33 @@ module SpreeCmCommissioner
|
|
|
30
32
|
save!
|
|
31
33
|
end
|
|
32
34
|
|
|
35
|
+
# For polling, which confirms the same mappings every few seconds. last_synced_at is indexed, so each save rewrites
|
|
36
|
+
# every index on this table; this skips the save while nothing a reader uses has changed. `ignoring` lists payload
|
|
37
|
+
# keys that move on every poll (seats, for one), at any depth.
|
|
38
|
+
def confirm_active!(external_payload:, ignoring: [])
|
|
39
|
+
return mark_as_active!(external_payload: external_payload) unless recently_confirmed?(external_payload, ignoring)
|
|
40
|
+
|
|
41
|
+
# as_json gives the string keys a save would: SyncTripStop reads this payload straight after.
|
|
42
|
+
self.external_payload = external_payload.as_json
|
|
43
|
+
end
|
|
44
|
+
|
|
33
45
|
private
|
|
34
46
|
|
|
47
|
+
def recently_confirmed?(new_payload, ignoring)
|
|
48
|
+
return false unless persisted? && active? && last_synced_at.present?
|
|
49
|
+
return false if last_synced_at <= CONFIRM_INTERVAL.ago
|
|
50
|
+
|
|
51
|
+
stable_payload(new_payload.as_json, ignoring) == stable_payload(external_payload.as_json, ignoring)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def stable_payload(value, ignoring)
|
|
55
|
+
case value
|
|
56
|
+
when Hash then value.except(*ignoring).transform_values { |nested| stable_payload(nested, ignoring) }
|
|
57
|
+
when Array then value.map { |nested| stable_payload(nested, ignoring) }
|
|
58
|
+
else value
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
35
62
|
def validate_internal_exists
|
|
36
63
|
return if internal_type.safe_constantize&.exists?(id: internal_id)
|
|
37
64
|
|
|
@@ -6,6 +6,7 @@ class SpreeCmCommissioner::Integrations::BookMeBusV1 < SpreeCmCommissioner::Inte
|
|
|
6
6
|
store_private_metadata :access_token, :string
|
|
7
7
|
store_private_metadata :token_expires_at, :datetime
|
|
8
8
|
store_private_metadata :operator_id, :string
|
|
9
|
+
store_private_metadata :sync_days, :integer, default: 7
|
|
9
10
|
# override
|
|
10
11
|
def sync_manager
|
|
11
12
|
SpreeCmCommissioner::Integrations::BookMeBusV1::SyncManager.new(integration: self)
|
data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb
CHANGED
|
@@ -41,31 +41,53 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
41
41
|
available_seats = external_trip.available_seats
|
|
42
42
|
return if available_seats.nil? # unknown, not zero - don't zero out real inventory
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
sync_stock_item(stock_item, vehicle_capacity(external_trip, available_seats.to_i))
|
|
45
|
+
sync_inventory_item_for_date(stock_item.variant, external_trip, available_seats.to_i)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# count_on_hand is one number shared by every date and seeds new inventory items, so it holds the vehicle's seats.
|
|
49
|
+
# A date's own seats would rewrite it (and enqueue VendorJob) on most polls, and could seed new dates with 0.
|
|
50
|
+
def vehicle_capacity(external_trip, available_seats)
|
|
51
|
+
number_of_seats = external_trip.number_of_seats.to_i
|
|
52
|
+
|
|
53
|
+
number_of_seats.positive? ? number_of_seats : available_seats
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def sync_stock_item(stock_item, seats)
|
|
57
|
+
difference = seats - stock_item.count_on_hand.to_i
|
|
46
58
|
return if difference.zero?
|
|
47
59
|
|
|
48
60
|
@sync_result.track(:stock_item, stock_item) do
|
|
49
61
|
stock_item.adjust_count_on_hand(difference)
|
|
50
62
|
@sync_result.increment_metric(:inventory, :adjusted, difference.abs)
|
|
51
63
|
end
|
|
52
|
-
|
|
53
|
-
adjust_inventory_item_for_date(stock_item.variant, external_trip, difference)
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
# StockItem.count_on_hand isn't date-aware and isn't read for real availability checks
|
|
57
|
-
# (see docs/concepts/inventory-system.md)
|
|
58
|
-
#
|
|
59
|
-
# If
|
|
60
|
-
#
|
|
61
|
-
def
|
|
67
|
+
# (see docs/concepts/inventory-system.md), so each date's InventoryItem is set to that date's own seats_remaining,
|
|
68
|
+
# never moved by its difference from another date's seats: an item seeded from a different count (0 after a sold-out
|
|
69
|
+
# first date) would go negative and fail the whole trip's sync. If the item doesn't exist yet, the permanent items
|
|
70
|
+
# job seeds it later from count_on_hand.
|
|
71
|
+
def sync_inventory_item_for_date(variant, external_trip, available_seats)
|
|
62
72
|
trip_date = parse_trip_date(external_trip.on_date)
|
|
63
73
|
return unless trip_date
|
|
64
74
|
|
|
65
75
|
inventory_item = variant.inventory_items.find_by(inventory_date: trip_date)
|
|
66
|
-
return
|
|
76
|
+
return if inventory_item.nil? || inventory_item.quantity_available == seats_for_sale(inventory_item, available_seats)
|
|
77
|
+
|
|
78
|
+
inventory_item.with_lock do
|
|
79
|
+
SpreeCmCommissioner::InventoryItems::AdjustAvailableQuantity.call!(
|
|
80
|
+
inventory_item: inventory_item,
|
|
81
|
+
delta: seats_for_sale(inventory_item, available_seats) - inventory_item.quantity_available,
|
|
82
|
+
caller_source: self.class.name,
|
|
83
|
+
lock: false
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
# BookMeBus doesn't know about seats an operator withheld here, so its count still includes them.
|
|
89
|
+
def seats_for_sale(inventory_item, available_seats)
|
|
90
|
+
[available_seats - inventory_item.quantity_locked - inventory_item.locked_count, 0].max
|
|
69
91
|
end
|
|
70
92
|
|
|
71
93
|
def parse_trip_date(date_string)
|
|
@@ -33,7 +33,8 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
33
33
|
status: :active,
|
|
34
34
|
vendor: @vendor,
|
|
35
35
|
product_type: :transit,
|
|
36
|
-
|
|
36
|
+
# Keeps the first value: a fresh time counts as a change, so every poll would save the product and purge its API cache.
|
|
37
|
+
available_on: product.available_on || Time.current,
|
|
37
38
|
shipping_category: default_shipping_category,
|
|
38
39
|
stores: [Spree::Store.default],
|
|
39
40
|
option_types: [seat_type_option].compact,
|
|
@@ -42,7 +43,7 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
42
43
|
tracker.save_if_changed!(product)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
mapping.
|
|
46
|
+
mapping.confirm_active!(external_payload: { trip_id: external_trip.trip_id })
|
|
46
47
|
product
|
|
47
48
|
end
|
|
48
49
|
|
data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb
CHANGED
|
@@ -135,11 +135,12 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
135
135
|
tracker.save_if_changed!(trip_stop)
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
mapping.
|
|
138
|
+
mapping.confirm_active!(
|
|
139
139
|
external_payload: stop_time_data.merge(
|
|
140
140
|
'stop_name' => stop_data['name'],
|
|
141
141
|
'location_name' => location_data['name']
|
|
142
|
-
)
|
|
142
|
+
),
|
|
143
|
+
ignoring: SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Trip::VOLATILE_PAYLOAD_KEYS
|
|
143
144
|
)
|
|
144
145
|
|
|
145
146
|
mapping
|
|
@@ -177,7 +178,10 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
177
178
|
tracker.save_if_changed!(stop_vendor_place)
|
|
178
179
|
end
|
|
179
180
|
|
|
180
|
-
mapping.
|
|
181
|
+
mapping.confirm_active!(
|
|
182
|
+
external_payload: external_stop.to_h.merge('place_type' => external_stop.vendor_place_type.to_s),
|
|
183
|
+
ignoring: SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Trip::VOLATILE_PAYLOAD_KEYS
|
|
184
|
+
)
|
|
181
185
|
|
|
182
186
|
# Return the appropriate place based on stop type:
|
|
183
187
|
# - BRANCHES: return location_vendor_place.place (the city/location)
|
|
@@ -279,6 +279,10 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
279
279
|
class SyncTrips # rubocop:disable Metrics/ClassLength
|
|
280
280
|
attr_reader :integration, :sync_result, :route, :vendor
|
|
281
281
|
|
|
282
|
+
# Booked seats are only fetched for the dates the incremental sync keeps fresh (today and tomorrow); further out,
|
|
283
|
+
# SyncItemAvailability fetches them when a customer opens the trip.
|
|
284
|
+
SEAT_STATUS_DAYS_AHEAD = 1
|
|
285
|
+
|
|
282
286
|
def initialize(integration:, sync_result: nil, route: nil)
|
|
283
287
|
@integration = integration
|
|
284
288
|
@sync_result = sync_result || SpreeCmCommissioner::Integrations::Base::SyncResult.new
|
|
@@ -286,6 +290,7 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
286
290
|
@vendor = integration.vendor
|
|
287
291
|
@location_mappings = {}
|
|
288
292
|
@location_id_cache = {}
|
|
293
|
+
@seat_layout_synced_variant_ids = Set.new
|
|
289
294
|
@variant_ids_needing_items = Set.new
|
|
290
295
|
end
|
|
291
296
|
|
|
@@ -293,7 +298,11 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
293
298
|
# sync or [today, tomorrow] for incremental. Cleanup/route-stops/touch run once for the
|
|
294
299
|
# whole window (see sync_items!) so a trip that doesn't run every day doesn't get archived
|
|
295
300
|
# and reactivated on each date it's absent/present within one sync run.
|
|
296
|
-
|
|
301
|
+
# @param full_sync [Boolean] false on incremental sync. It runs every few seconds over too few dates to tell: a trip
|
|
302
|
+
# missing from today and tomorrow may simply not run those days, so only a full sync archives trips, and a layout
|
|
303
|
+
# rarely changes, so only a full sync refreshes one already in place.
|
|
304
|
+
def call(on_dates:, full_sync: true)
|
|
305
|
+
@full_sync = full_sync
|
|
297
306
|
preload_location_mappings!
|
|
298
307
|
preload_location_id_cache!
|
|
299
308
|
sync_items!(Array(on_dates))
|
|
@@ -368,7 +377,12 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
368
377
|
|
|
369
378
|
# Nothing came back for any date in the window - don't touch route stops/cleanup based
|
|
370
379
|
# on an empty result (could be a transient outage rather than a genuinely dead route).
|
|
371
|
-
|
|
380
|
+
if all_external_trips.empty?
|
|
381
|
+
# Incremental sync takes the route synced longest ago, so a route BookMeBus confirmed empty must still move to
|
|
382
|
+
# the back of the line, or every poll would pick it again and no other route would ever refresh.
|
|
383
|
+
touch_route_mapping if bookable_ids_by_date.any?
|
|
384
|
+
return
|
|
385
|
+
end
|
|
372
386
|
|
|
373
387
|
# A trip that's still active overall (present on some dates in the window) but confirmed
|
|
374
388
|
# absent/unbookable on ONE of them needs that specific date's inventory corrected - the
|
|
@@ -382,7 +396,7 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
382
396
|
|
|
383
397
|
# Clean up stale trip mappings using the full window's synced ids - archiving per single
|
|
384
398
|
# date would flap a non-daily trip's mapping active/archived across this run.
|
|
385
|
-
cleanup_stale_mappings!(synced_trip_ids)
|
|
399
|
+
cleanup_stale_mappings!(synced_trip_ids) if @full_sync
|
|
386
400
|
|
|
387
401
|
# Update route mapping timestamp
|
|
388
402
|
touch_route_mapping
|
|
@@ -493,8 +507,6 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
493
507
|
sync_result: sync_result,
|
|
494
508
|
vendor: vendor
|
|
495
509
|
).call(variant: variant, external_trip: external_trip)
|
|
496
|
-
|
|
497
|
-
restore_inventory_if_previously_zeroed!(variant, external_trip)
|
|
498
510
|
end
|
|
499
511
|
|
|
500
512
|
@variant_ids_needing_items << variant.id if permanent_items_needed?(service_calendar, variant, external_trip)
|
|
@@ -566,24 +578,46 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
566
578
|
tracker.save_if_changed!(trip)
|
|
567
579
|
end
|
|
568
580
|
|
|
569
|
-
mapping.
|
|
581
|
+
mapping.confirm_active!(
|
|
582
|
+
external_payload: external_trip.to_h,
|
|
583
|
+
ignoring: SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Trip::VOLATILE_PAYLOAD_KEYS
|
|
584
|
+
)
|
|
570
585
|
mapping
|
|
571
586
|
end
|
|
572
587
|
|
|
588
|
+
# A trip's seat layout is the same on every date, so it is synced once per run (on the first bookable date, which
|
|
589
|
+
# also refreshes it every run). Later dates only matter for which seats are booked, so they are synced again only
|
|
590
|
+
# when BookMeBus reports that for the trip and the date is close enough to matter. The trip is marked done only
|
|
591
|
+
# after a successful sync, so a failed first fetch is retried on the next date.
|
|
573
592
|
def sync_seat_layout!(vehicle_type, external_trip, variant)
|
|
574
|
-
inventory_item = find_inventory_item_for_date(variant, external_trip)
|
|
575
|
-
|
|
576
593
|
return unless vehicle_type
|
|
594
|
+
return if seat_layout_current?(variant) && !booked_seats_needed?(external_trip)
|
|
577
595
|
|
|
578
|
-
SyncSeatLayout.new(
|
|
596
|
+
layout = SyncSeatLayout.new(
|
|
579
597
|
integration: integration,
|
|
580
598
|
sync_result: sync_result
|
|
581
599
|
).call(
|
|
582
600
|
vehicle_type: vehicle_type,
|
|
583
601
|
external_trip: external_trip,
|
|
584
602
|
variant: variant,
|
|
585
|
-
inventory_item:
|
|
603
|
+
inventory_item: find_inventory_item_for_date(variant, external_trip)
|
|
586
604
|
)
|
|
605
|
+
|
|
606
|
+
@seat_layout_synced_variant_ids << variant.id if layout
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# Checks for assigned blocks, not just a layout: a trip copies its vehicle's layout on save, before any seat is
|
|
610
|
+
# assigned to it, and that first sync must still run.
|
|
611
|
+
def seat_layout_current?(variant)
|
|
612
|
+
return true if @seat_layout_synced_variant_ids.include?(variant.id)
|
|
613
|
+
|
|
614
|
+
!@full_sync && variant.trip&.seat_layout&.blocks&.exists?(variant_id: variant.id)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def booked_seats_needed?(external_trip)
|
|
618
|
+
trip_date = parse_trip_date(external_trip.on_date)
|
|
619
|
+
|
|
620
|
+
external_trip.per_seat_status? && trip_date.present? && trip_date <= Date.current + SEAT_STATUS_DAYS_AHEAD.days
|
|
587
621
|
end
|
|
588
622
|
|
|
589
623
|
def sync_trip_stop!(trip_mapping)
|
|
@@ -684,28 +718,6 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
684
718
|
end
|
|
685
719
|
end
|
|
686
720
|
|
|
687
|
-
# Mirrors SyncItemAvailability#restore_inventory_if_previously_zeroed! - SyncInventory's own
|
|
688
|
-
# delta is computed against StockItem#count_on_hand, which zero_out_inventory_for_date! never
|
|
689
|
-
# touches (deliberately - it's a date-independent aggregate), so once available_seats matches
|
|
690
|
-
# that unrelated baseline again the delta comes back 0 and a previously-zeroed InventoryItem
|
|
691
|
-
# would otherwise stay stuck at 0 even after the trip is genuinely bookable again.
|
|
692
|
-
def restore_inventory_if_previously_zeroed!(variant, external_trip)
|
|
693
|
-
return unless variant&.persisted?
|
|
694
|
-
|
|
695
|
-
available_seats = external_trip.available_seats
|
|
696
|
-
return if available_seats.to_i.zero?
|
|
697
|
-
|
|
698
|
-
inventory_item = find_inventory_item_for_date(variant, external_trip)
|
|
699
|
-
return unless inventory_item
|
|
700
|
-
|
|
701
|
-
inventory_item.with_lock do
|
|
702
|
-
next unless inventory_item.quantity_available.to_i.zero?
|
|
703
|
-
|
|
704
|
-
set_quantity_available_with_redis_sync!(inventory_item, available_seats.to_i)
|
|
705
|
-
sync_result.increment_metric(:inventory, :restored_after_unavailable_date)
|
|
706
|
-
end
|
|
707
|
-
end
|
|
708
|
-
|
|
709
721
|
# See SyncItemAvailability#set_quantity_available_with_redis_sync! for the full reasoning:
|
|
710
722
|
# the real checkout-time availability check reads Redis, not this DB row directly, so
|
|
711
723
|
# quantity_available must go through adjust_quantity! (which keeps both in sync) rather than
|
|
@@ -41,7 +41,7 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
41
41
|
tracker.save_if_changed!(variant)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
mapping.
|
|
44
|
+
mapping.confirm_active!(external_payload: { trip_id: external_trip.trip_id })
|
|
45
45
|
variant
|
|
46
46
|
end
|
|
47
47
|
|
data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb
CHANGED
|
@@ -59,7 +59,7 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Polling
|
|
|
59
59
|
tracker.save_if_changed!(vehicle_type)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
mapping.
|
|
62
|
+
mapping.confirm_active!(external_payload: external_trip.vehicle_type)
|
|
63
63
|
vehicle_type
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
module SpreeCmCommissioner::Integrations::BookMeBusV1::Resources
|
|
2
|
-
class Trip < Base
|
|
2
|
+
class Trip < Base # rubocop:disable Metrics/ClassLength
|
|
3
|
+
# Parts of a stored trip or stop-time payload that move between dates and polls. Nothing reads them back from the
|
|
4
|
+
# stored mapping, so a change in only these needs no write. See IntegrationMapping#confirm_active!.
|
|
5
|
+
VOLATILE_PAYLOAD_KEYS = %w[
|
|
6
|
+
on_date seats_remaining number_of_seats total_sold local_price non_local_price price service_available not_allowed departure_over
|
|
7
|
+
hits_origin hits_destination
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
3
10
|
attr_accessor :id,
|
|
4
11
|
:type,
|
|
5
12
|
:trip_id,
|
|
@@ -84,6 +91,10 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::Resources
|
|
|
84
91
|
seats_remaining
|
|
85
92
|
end
|
|
86
93
|
|
|
94
|
+
# Whether BookMeBus reports which seats are booked on a date: false for auto-assigned-seat trips, and a private
|
|
95
|
+
# taxi is booked as a whole vehicle, so seat status does not apply there either.
|
|
96
|
+
def per_seat_status? = is_selectable == true && route_type != 'private_taxi'
|
|
97
|
+
|
|
87
98
|
# BookMe+ charges the non-local (foreigner) price by default, falling back to local_price
|
|
88
99
|
# only when BookMeBus doesn't provide one. Single source of truth for both trip-creation
|
|
89
100
|
# (SyncTrips) and per-date availability refresh (SyncItemAvailability) so they can't diverge.
|
|
@@ -103,9 +103,9 @@ module SpreeCmCommissioner::Integrations::BookMeBusV1::SyncStrategies
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def sync_trips_for_route!(route)
|
|
106
|
-
# Sync trips for the
|
|
106
|
+
# Sync trips for the configured number of days (default: 7)
|
|
107
107
|
today = Date.current
|
|
108
|
-
on_dates = (0
|
|
108
|
+
on_dates = (0...@integration.sync_days).map { |days_ahead| (today + days_ahead).strftime('%Y-%m-%d') }
|
|
109
109
|
|
|
110
110
|
SpreeCmCommissioner::Integrations::BookMeBusV1::Polling::SyncTrips.new(
|
|
111
111
|
integration: @integration,
|
|
@@ -38,7 +38,8 @@ module SpreeCmCommissioner::Integrations::Larryta::Polling
|
|
|
38
38
|
status: :active,
|
|
39
39
|
vendor: @vendor,
|
|
40
40
|
product_type: :transit,
|
|
41
|
-
|
|
41
|
+
# Keeps the first value: a fresh time counts as a change, so every poll would save the product and purge its API cache.
|
|
42
|
+
available_on: product.available_on || Time.current,
|
|
42
43
|
shipping_category: default_shipping_category,
|
|
43
44
|
stores: [Spree::Store.default],
|
|
44
45
|
option_types: [seat_type_option].compact,
|
|
@@ -35,7 +35,8 @@ module SpreeCmCommissioner::Integrations::VireakBuntham::Polling
|
|
|
35
35
|
status: :active,
|
|
36
36
|
vendor: @vendor,
|
|
37
37
|
product_type: :transit,
|
|
38
|
-
|
|
38
|
+
# Keeps the first value: a fresh time counts as a change, so every poll would save the product and purge its API cache.
|
|
39
|
+
available_on: product.available_on || Time.current,
|
|
39
40
|
shipping_category: default_shipping_category,
|
|
40
41
|
stores: [Spree::Store.default],
|
|
41
42
|
option_types: [seat_type_option].compact,
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
<% end %>
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
|
+
<div class="col-6">
|
|
32
|
+
<%= f.field_container :sync_days do %>
|
|
33
|
+
<%= f.label :sync_days, raw(I18n.t('spree.admin.external_integrations.sync_days', default: 'Sync Days') + required_span_tag) %>
|
|
34
|
+
<%= number_field_tag 'spree_cm_commissioner_integration[private_metadata][sync_days]', f.object.sync_days || 7, class: 'form-control', min: 1, max: 30, required: true %>
|
|
35
|
+
<small class="form-text text-muted">
|
|
36
|
+
<%= I18n.t('spree.admin.external_integrations.sync_days_description', default: 'Number of days to sync trips for (default: 7)') %>
|
|
37
|
+
</small>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
31
41
|
<div class="col-12">
|
|
32
42
|
<hr class="my-4">
|
|
33
43
|
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
<div class="col-6">
|
|
86
86
|
<%= f.field_container :incremental_sync_interval_seconds do %>
|
|
87
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,
|
|
88
|
+
<%= f.number_field :incremental_sync_interval_seconds, class: 'form-control', min: 1, required: true %>
|
|
89
89
|
<%= f.error_message_on :incremental_sync_interval_seconds %>
|
|
90
90
|
<% end %>
|
|
91
91
|
</div>
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_cm_commissioner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.14.
|
|
4
|
+
version: 2.14.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-11-
|
|
11
|
+
date: 2026-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|