spree_cm_commissioner 1.10.0 → 1.11.0.pre.pre
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 +131 -98
- data/.gitignore +2 -1
- data/.vscode/settings.json +1 -1
- data/Gemfile.lock +22 -1
- data/Rakefile +33 -4
- data/app/controllers/spree/admin/stock_managements_controller.rb +56 -1
- data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
- data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
- data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
- data/app/finders/spree_cm_commissioner/accommodations/find.rb +40 -0
- data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +35 -0
- data/app/interactors/spree_cm_commissioner/create_event.rb +23 -0
- data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
- data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
- data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +24 -0
- data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +27 -8
- data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
- data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
- data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +26 -0
- data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
- data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
- data/app/models/spree_cm_commissioner/inventory.rb +11 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +55 -0
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +16 -5
- data/app/models/spree_cm_commissioner/order_decorator.rb +15 -0
- data/app/models/spree_cm_commissioner/place.rb +11 -2
- data/app/models/spree_cm_commissioner/product_decorator.rb +9 -2
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +40 -0
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
- data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
- data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +27 -0
- data/app/models/spree_cm_commissioner/stock/availability_checker.rb +27 -25
- data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
- data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
- data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
- data/app/models/spree_cm_commissioner/stock_item_decorator.rb +18 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +11 -0
- data/app/models/spree_cm_commissioner/taxon_option_type.rb +8 -0
- data/app/models/spree_cm_commissioner/taxon_option_value.rb +8 -0
- data/app/models/spree_cm_commissioner/trip.rb +0 -11
- data/app/models/spree_cm_commissioner/trip_stop.rb +11 -4
- data/app/models/spree_cm_commissioner/variant_decorator.rb +39 -27
- data/app/models/spree_cm_commissioner/vendor_stop.rb +2 -1
- data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +54 -0
- data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
- data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
- data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
- data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
- data/app/serializers/spree/v2/tenant/guest_serializer.rb +1 -0
- data/app/services/spree_cm_commissioner/aes_encryption_service.rb +6 -4
- data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +2 -0
- data/app/views/spree/admin/stock_managements/_events_popover.html.erb +23 -0
- data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +3 -1
- data/app/views/spree/admin/stock_managements/calendar.html.erb +35 -0
- data/app/views/spree/admin/stock_managements/index.html.erb +40 -5
- data/config/initializers/spree_permitted_attributes.rb +5 -0
- data/config/routes.rb +11 -2
- data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
- data/db/migrate/20250418072528_add_nested_set_columns_to_places.rb +10 -0
- data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
- data/db/migrate/20250430091742_create_cm_taxon_option_types.rb +9 -0
- data/db/migrate/20250430092928_create_cm_taxon_option_values.rb +9 -0
- data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
- data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
- data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
- data/db/migrate/20250506092929_add_trip_count_to_cm_vendor_stops.rb +5 -0
- data/docker-compose.yml +1 -1
- data/lib/generators/spree_cm_commissioner/install/install_generator.rb +11 -3
- data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
- data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
- data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
- data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
- data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/place_factory.rb +11 -1
- data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -5
- data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
- data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +39 -6
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +34 -0
- data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
- data/lib/tasks/ensure_correct_product_type.rake +7 -0
- data/lib/tasks/generate_inventory_items.rake +7 -0
- data/lib/tasks/migrate_and_rebuild_place_hierarchy.rake +9 -0
- data/lib/tasks/update_orphan_root_places.rake +7 -0
- data/spree_cm_commissioner.gemspec +5 -0
- metadata +88 -7
- data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
- data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
@@ -0,0 +1,40 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module RedisStock
|
3
|
+
class CachedInventoryItemsBuilder
|
4
|
+
attr_reader :inventory_items
|
5
|
+
|
6
|
+
def initialize(inventory_items)
|
7
|
+
@inventory_items = inventory_items
|
8
|
+
end
|
9
|
+
|
10
|
+
# output: [ CachedInventoryItem(...), CachedInventoryItem(...) ]
|
11
|
+
def call
|
12
|
+
keys = inventory_items.map { |item| "inventory:#{item.id}" }
|
13
|
+
return [] unless keys.any?
|
14
|
+
|
15
|
+
counts = SpreeCmCommissioner.redis_pool.with { |redis| redis.mget(*keys) }
|
16
|
+
inventory_items.map.with_index do |inventory_item, i|
|
17
|
+
::SpreeCmCommissioner::CachedInventoryItem.new(
|
18
|
+
inventory_key: keys[i],
|
19
|
+
active: inventory_item.active?,
|
20
|
+
quantity_available: cache_inventory(keys[i], inventory_item, counts[i]),
|
21
|
+
inventory_item_id: inventory_item.id,
|
22
|
+
variant_id: inventory_item.variant_id
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def cache_inventory(key, inventory_item, count_in_redis)
|
30
|
+
return count_in_redis.to_i if count_in_redis.present?
|
31
|
+
|
32
|
+
SpreeCmCommissioner.redis_pool.with do |redis|
|
33
|
+
redis.set(key, inventory_item.quantity_available, ex: inventory_item.redis_expired_in)
|
34
|
+
end
|
35
|
+
|
36
|
+
inventory_item.quantity_available
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module RedisStock
|
3
|
+
class InventoryUpdater
|
4
|
+
class UnableToRestock < StandardError; end
|
5
|
+
class UnableToUnstock < StandardError; end
|
6
|
+
|
7
|
+
def initialize(line_item_ids)
|
8
|
+
@line_item_ids = line_item_ids
|
9
|
+
end
|
10
|
+
|
11
|
+
def unstock!
|
12
|
+
keys, quantities, inventory_ids = extract_inventory_data
|
13
|
+
|
14
|
+
raise UnableToUnstock, Spree.t(:insufficient_stock_lines_present) unless unstock(keys, quantities)
|
15
|
+
|
16
|
+
inventory_id_and_quantities = inventory_ids.map.with_index do |inventory_id, i|
|
17
|
+
{ inventory_id: inventory_id, quantity: -quantities[i] }
|
18
|
+
end
|
19
|
+
|
20
|
+
schedule_sync_inventory(inventory_id_and_quantities)
|
21
|
+
end
|
22
|
+
|
23
|
+
def restock!
|
24
|
+
keys, quantities, inventory_ids = extract_inventory_data
|
25
|
+
|
26
|
+
raise UnableToRestock unless restock(keys, quantities)
|
27
|
+
|
28
|
+
inventory_id_and_quantities = inventory_ids.map.with_index do |inventory_id, i|
|
29
|
+
{ inventory_id: inventory_id, quantity: quantities[i] }
|
30
|
+
end
|
31
|
+
|
32
|
+
schedule_sync_inventory(inventory_id_and_quantities)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# only unstock or restock when variants is available, track inventory, not backorderable & not need_confirmation.
|
38
|
+
# otherwise, we can ignore them.
|
39
|
+
def line_items
|
40
|
+
return @line_items if defined?(@line_items)
|
41
|
+
|
42
|
+
@line_items = Spree::LineItem.where(id: @line_item_ids).includes(variant: :stock_items)
|
43
|
+
@line_items = @line_items.map do |line_item|
|
44
|
+
next unless line_item.variant.available?
|
45
|
+
next unless line_item.variant.should_track_inventory?
|
46
|
+
next if line_item.variant.backorderable?
|
47
|
+
next if line_item.variant.need_confirmation?
|
48
|
+
|
49
|
+
line_item
|
50
|
+
end.compact
|
51
|
+
end
|
52
|
+
|
53
|
+
def unstock(keys, quantities)
|
54
|
+
SpreeCmCommissioner.redis_pool.with do |redis|
|
55
|
+
redis.eval(unstock_redis_script, keys: keys, argv: quantities)
|
56
|
+
end.positive?
|
57
|
+
end
|
58
|
+
|
59
|
+
def restock(keys, quantities)
|
60
|
+
SpreeCmCommissioner.redis_pool.with do |redis|
|
61
|
+
redis.eval(restock_redis_script, keys: keys, argv: quantities)
|
62
|
+
end.positive?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return: [CachedInventoryItem(...), CachedInventoryItem(...)]
|
66
|
+
def cached_inventory_items
|
67
|
+
@cached_inventory_items ||= SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(line_item_ids: line_items.pluck(:id))
|
68
|
+
.call.values.flatten
|
69
|
+
end
|
70
|
+
|
71
|
+
def extract_inventory_data
|
72
|
+
keys = []
|
73
|
+
quantities = []
|
74
|
+
inventory_ids = []
|
75
|
+
|
76
|
+
cached_inventory_items.each do |cached_inventory_item|
|
77
|
+
keys << cached_inventory_item.inventory_key
|
78
|
+
quantities << line_items.find { |item| item.variant_id == cached_inventory_item.variant_id }.quantity
|
79
|
+
inventory_ids << cached_inventory_item.inventory_item_id
|
80
|
+
end
|
81
|
+
|
82
|
+
[keys, quantities, inventory_ids]
|
83
|
+
end
|
84
|
+
|
85
|
+
def unstock_redis_script
|
86
|
+
<<~LUA
|
87
|
+
local keys = KEYS
|
88
|
+
local quantities = ARGV
|
89
|
+
|
90
|
+
-- Check availability first
|
91
|
+
for i, key in ipairs(keys) do
|
92
|
+
local current = tonumber(redis.call('GET', key) or 0)
|
93
|
+
if current - tonumber(quantities[i]) < 0 then
|
94
|
+
return 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
-- Apply updates
|
99
|
+
for i, key in ipairs(keys) do
|
100
|
+
redis.call('DECRBY', key, tonumber(quantities[i]))
|
101
|
+
end
|
102
|
+
|
103
|
+
return 1
|
104
|
+
LUA
|
105
|
+
end
|
106
|
+
|
107
|
+
def restock_redis_script
|
108
|
+
<<~LUA
|
109
|
+
local keys = KEYS
|
110
|
+
local quantities = ARGV
|
111
|
+
|
112
|
+
-- Apply restock updates
|
113
|
+
for i, key in ipairs(keys) do
|
114
|
+
redis.call('INCRBY', key, tonumber(quantities[i]))
|
115
|
+
end
|
116
|
+
|
117
|
+
return 1
|
118
|
+
LUA
|
119
|
+
end
|
120
|
+
|
121
|
+
def schedule_sync_inventory(inventory_id_and_quantities)
|
122
|
+
SpreeCmCommissioner::InventoryItemSyncerJob.perform_later(inventory_id_and_quantities:)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module RedisStock
|
3
|
+
class LineItemsCachedInventoryItemsBuilder
|
4
|
+
attr_reader :line_item_ids
|
5
|
+
|
6
|
+
def initialize(line_item_ids:)
|
7
|
+
@line_item_ids = line_item_ids
|
8
|
+
end
|
9
|
+
|
10
|
+
# return list of inventory items group by :line_item_id:
|
11
|
+
# {
|
12
|
+
# 1: [ CachedInventoryItem(...), CachedInventoryItem(...) ],
|
13
|
+
# 2: [ CachedInventoryItem(...), CachedInventoryItem(...) ],
|
14
|
+
# }
|
15
|
+
def call
|
16
|
+
cached_inventory_items.group_by do |cached_inventory_item|
|
17
|
+
line_item = line_items.find { |item| item.variant_id == cached_inventory_item.variant_id }
|
18
|
+
line_item.id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def cached_inventory_items
|
23
|
+
@cached_inventory_items ||= SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items)
|
24
|
+
.call
|
25
|
+
end
|
26
|
+
|
27
|
+
def inventory_items
|
28
|
+
@inventory_items ||= line_items.flat_map(&:inventory_items)
|
29
|
+
end
|
30
|
+
|
31
|
+
def line_items
|
32
|
+
@line_items ||= Spree::LineItem.where(id: line_item_ids).includes(:inventory_items)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module RedisStock
|
3
|
+
class VariantCachedInventoryItemsBuilder
|
4
|
+
attr_reader :variant_id, :from_date, :to_date
|
5
|
+
|
6
|
+
def initialize(variant_id:, from_date: nil, to_date: nil)
|
7
|
+
@variant_id = variant_id
|
8
|
+
@from_date = from_date
|
9
|
+
@to_date = to_date
|
10
|
+
end
|
11
|
+
|
12
|
+
# output: [ CachedInventoryItem(...), CachedInventoryItem(...) ]
|
13
|
+
def call
|
14
|
+
::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items).call
|
15
|
+
end
|
16
|
+
|
17
|
+
def inventory_items
|
18
|
+
variant = Spree::Variant.find(variant_id)
|
19
|
+
|
20
|
+
inventory_items = variant.inventory_items
|
21
|
+
inventory_items.where(inventory_date: from_date..to_date) if variant.permanent_stock?
|
22
|
+
|
23
|
+
inventory_items
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,47 +1,49 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
module Stock
|
3
3
|
class AvailabilityChecker
|
4
|
-
attr_reader :variant, :error_message
|
4
|
+
attr_reader :variant, :options, :error_message
|
5
5
|
|
6
|
-
def initialize(variant)
|
6
|
+
def initialize(variant, options = {})
|
7
7
|
@variant = variant
|
8
|
+
@options = options
|
8
9
|
@error_message = nil
|
9
10
|
end
|
10
11
|
|
11
|
-
def can_supply?(quantity = 1
|
12
|
+
def can_supply?(quantity = 1)
|
12
13
|
return false unless variant.available?
|
13
14
|
return true unless variant.should_track_inventory?
|
14
15
|
return true if variant.backorderable?
|
15
16
|
return true if variant.need_confirmation?
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
return variant.stock_items.sum(:count_on_hand) >= quantity if variant.delivery_required?
|
18
|
+
variant_available?(quantity)
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
def variant_available?(quantity = 1)
|
22
|
+
return false if cached_inventory_items.empty?
|
23
|
+
|
24
|
+
cached_inventory_items.all? do |cached_inventory_item|
|
25
|
+
cached_inventory_item.active? && cached_inventory_item.quantity_available >= quantity
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
def cached_inventory_items
|
30
|
+
return @cached_inventory_items if defined?(@cached_inventory_items)
|
31
|
+
|
32
|
+
if variant.permanent_stock?
|
33
|
+
return [] if options[:from_date].blank? || options[:to_date].blank?
|
34
|
+
|
35
|
+
@cached_inventory_items = builder_klass.new(
|
36
|
+
variant_id: variant.id,
|
37
|
+
from_date: options[:from_date].to_date,
|
38
|
+
to_date: options[:to_date].to_date
|
39
|
+
).call
|
40
|
+
else
|
41
|
+
@cached_inventory_items = builder_klass.new(variant_id: variant.id).call
|
42
|
+
end
|
36
43
|
end
|
37
44
|
|
38
|
-
def
|
39
|
-
SpreeCmCommissioner::
|
40
|
-
variant: variant,
|
41
|
-
from_date: options[:from_date].to_date,
|
42
|
-
to_date: options[:to_date].to_date,
|
43
|
-
except_line_item_id: options[:except_line_item_id]
|
44
|
-
).available?(quantity)
|
45
|
+
def builder_klass
|
46
|
+
::SpreeCmCommissioner::RedisStock::VariantCachedInventoryItemsBuilder
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
@@ -3,7 +3,8 @@ module SpreeCmCommissioner
|
|
3
3
|
module AvailabilityValidatorDecorator
|
4
4
|
# override
|
5
5
|
def item_available?(line_item, quantity)
|
6
|
-
SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item)
|
6
|
+
SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item)
|
7
|
+
.can_supply?(quantity)
|
7
8
|
end
|
8
9
|
end
|
9
10
|
end
|
@@ -8,14 +8,14 @@ module SpreeCmCommissioner
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def can_supply?(quantity)
|
11
|
-
AvailabilityChecker.new(line_item.variant
|
11
|
+
::SpreeCmCommissioner::Stock::AvailabilityChecker.new(line_item.variant, options)
|
12
|
+
.can_supply?(quantity)
|
12
13
|
end
|
13
14
|
|
14
15
|
def options
|
15
16
|
{
|
16
17
|
from_date: line_item.from_date,
|
17
|
-
to_date: line_item.to_date
|
18
|
-
except_line_item_id: line_item.id
|
18
|
+
to_date: line_item.to_date
|
19
19
|
}
|
20
20
|
end
|
21
21
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# On top of SpreeCmCommissioner::Stock::AvailabilityChecker, this class does the same thing but in bulk check with redis.
|
2
|
+
# Which mean, it check basic condition with db first, once pass, it check with redis.
|
3
|
+
module SpreeCmCommissioner
|
4
|
+
module Stock
|
5
|
+
class OrderAvailabilityChecker
|
6
|
+
attr_reader :order
|
7
|
+
|
8
|
+
def initialize(order)
|
9
|
+
@order = order
|
10
|
+
end
|
11
|
+
|
12
|
+
def can_supply_all?
|
13
|
+
insufficient_stock_lines.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def insufficient_stock_lines
|
17
|
+
cached_inventory_items_group_by_line_item_id.map do |line_item_id, cached_inventory_items|
|
18
|
+
line_item = order.line_items.find { |item| item.id == line_item_id }
|
19
|
+
line_item unless sufficient_stock_for?(line_item, cached_inventory_items)
|
20
|
+
end.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
# {
|
24
|
+
# 1: [ {inventory_key: "inventory:1", active: true, quantity_available: 5, inventory_item_id: 1, variant_id: 1}],
|
25
|
+
# 2: [ {inventory_key: "inventory:2", active: true, quantity_available: 5, inventory_item_id: 2, variant_id: 2}],
|
26
|
+
# }
|
27
|
+
def cached_inventory_items_group_by_line_item_id
|
28
|
+
@cached_inventory_items_group_by_line_item_id ||=
|
29
|
+
::SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(
|
30
|
+
line_item_ids: order.line_items.pluck(:id)
|
31
|
+
).call
|
32
|
+
end
|
33
|
+
|
34
|
+
def sufficient_stock_for?(line_item, cached_inventory_items)
|
35
|
+
return false unless line_item.variant.available?
|
36
|
+
return true unless line_item.variant.should_track_inventory?
|
37
|
+
return true if line_item.variant.backorderable?
|
38
|
+
return true if line_item.variant.need_confirmation?
|
39
|
+
|
40
|
+
cached_inventory_items.all? { |item| item.active? && item.quantity_available >= line_item.quantity }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -3,11 +3,29 @@ module SpreeCmCommissioner
|
|
3
3
|
def self.prepended(base)
|
4
4
|
base.has_one :vendor, through: :variant
|
5
5
|
base.after_save :update_vendor_total_inventory, if: :saved_change_to_count_on_hand?
|
6
|
+
|
7
|
+
base.after_commit :create_inventory_items, on: :create
|
8
|
+
base.after_commit :adjust_inventory_items_async, on: :destroy
|
6
9
|
end
|
7
10
|
|
8
11
|
def update_vendor_total_inventory
|
9
12
|
SpreeCmCommissioner::VendorJob.perform_later(vendor.id) if vendor.present?
|
10
13
|
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def create_inventory_items
|
18
|
+
if variant.permanent_stock?
|
19
|
+
SpreeCmCommissioner::Stock::PermanentInventoryItemsGeneratorJob.perform_later(variant_ids: [variant.id])
|
20
|
+
else
|
21
|
+
variant.create_default_non_permanent_inventory_item! unless variant.default_inventory_item_exist?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# When admin delete stock item, it will deduct stock from inventory item
|
26
|
+
def adjust_inventory_items_async
|
27
|
+
SpreeCmCommissioner::Stock::InventoryItemsAdjusterJob.perform_later(variant_id: variant_id, quantity: -count_on_hand)
|
28
|
+
end
|
11
29
|
end
|
12
30
|
end
|
13
31
|
|
@@ -57,6 +57,9 @@ module SpreeCmCommissioner
|
|
57
57
|
base.has_many :event_blazer_queries, class_name: 'SpreeCmCommissioner::TaxonBlazerQuery'
|
58
58
|
base.has_many :blazer_queries, through: :event_blazer_queries, class_name: 'Blazer::Query'
|
59
59
|
|
60
|
+
base.has_many :taxon_option_types, class_name: 'SpreeCmCommissioner::TaxonOptionType'
|
61
|
+
base.has_many :taxon_option_values, class_name: 'SpreeCmCommissioner::TaxonOptionValue'
|
62
|
+
|
60
63
|
def base.active_homepage_events
|
61
64
|
joins(:homepage_section_relatables)
|
62
65
|
.joins("INNER JOIN spree_taxons taxon ON taxon.id = cm_homepage_section_relatables.relatable_id
|
@@ -96,6 +99,14 @@ module SpreeCmCommissioner
|
|
96
99
|
.uniq
|
97
100
|
end
|
98
101
|
|
102
|
+
def selected_option_types
|
103
|
+
taxon_option_types.pluck(:option_type_id)
|
104
|
+
end
|
105
|
+
|
106
|
+
def selected_option_values
|
107
|
+
taxon_option_values.pluck(:option_value_id)
|
108
|
+
end
|
109
|
+
|
99
110
|
def event_url
|
100
111
|
"https://#{Spree::Store.default.url}/t/#{permalink}"
|
101
112
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require_dependency 'spree_cm_commissioner'
|
2
|
+
|
3
|
+
module SpreeCmCommissioner
|
4
|
+
class TaxonOptionType < ApplicationRecord
|
5
|
+
belongs_to :taxon, class_name: 'Spree::Taxon', dependent: :destroy
|
6
|
+
belongs_to :option_type, class_name: 'Spree::OptionType', dependent: :destroy
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require_dependency 'spree_cm_commissioner'
|
2
|
+
|
3
|
+
module SpreeCmCommissioner
|
4
|
+
class TaxonOptionValue < ApplicationRecord
|
5
|
+
belongs_to :taxon, class_name: 'Spree::Taxon', dependent: :destroy
|
6
|
+
belongs_to :option_value, class_name: 'Spree::OptionValue', dependent: :destroy
|
7
|
+
end
|
8
|
+
end
|
@@ -14,23 +14,12 @@ module SpreeCmCommissioner
|
|
14
14
|
validates :duration, numericality: { greater_than: 0 }
|
15
15
|
validate :origin_and_destination_cannot_be_the_same
|
16
16
|
|
17
|
-
has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
|
18
|
-
|
19
|
-
after_commit :create_trip_stops
|
20
|
-
|
21
|
-
accepts_nested_attributes_for :trip_stops, allow_destroy: true
|
22
|
-
|
23
17
|
def convert_duration_to_seconds
|
24
18
|
return if hours.blank? && minutes.blank? && seconds.blank?
|
25
19
|
|
26
20
|
self.duration = (hours.to_i * 3600) + (minutes.to_i * 60) + seconds.to_i
|
27
21
|
end
|
28
22
|
|
29
|
-
def create_trip_stops
|
30
|
-
trip_stops.create(stop_type: :boarding, stop_id: origin_id)
|
31
|
-
trip_stops.create(stop_type: :drop_off, stop_id: destination_id)
|
32
|
-
end
|
33
|
-
|
34
23
|
def duration_in_hms
|
35
24
|
return { hours: 0, minutes: 0, seconds: 0 } if duration.nil?
|
36
25
|
|
@@ -8,8 +8,8 @@ module SpreeCmCommissioner
|
|
8
8
|
belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
|
9
9
|
|
10
10
|
before_validation :set_stop_name
|
11
|
-
|
12
|
-
|
11
|
+
after_destroy :decrement_trip_count
|
12
|
+
after_commit :create_vendor_stop
|
13
13
|
validates :stop_id, uniqueness: { scope: :trip_id }
|
14
14
|
|
15
15
|
def set_stop_name
|
@@ -17,13 +17,20 @@ module SpreeCmCommissioner
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def create_vendor_stop
|
20
|
-
vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create
|
20
|
+
vendor_stop = vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create
|
21
|
+
vendor_stop.update(trip_count: vendor_stop.trip_count.to_i + 1)
|
21
22
|
end
|
22
23
|
|
23
24
|
private
|
24
25
|
|
26
|
+
def decrement_trip_count
|
27
|
+
vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).find_each do |vendor_stop|
|
28
|
+
vendor_stop.update(trip_count: [vendor_stop.trip_count - 1, 0].max)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
25
32
|
def vendor
|
26
|
-
Spree::Product.find(trip
|
33
|
+
Spree::Product.find(trip&.product_id).vendor
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
module VariantDecorator
|
3
|
-
def self.prepended(base)
|
3
|
+
def self.prepended(base) # rubocop:disable Metrics/AbcSize
|
4
|
+
base.include SpreeCmCommissioner::ProductType
|
4
5
|
base.include SpreeCmCommissioner::ProductDelegation
|
5
6
|
base.include SpreeCmCommissioner::VariantOptionsConcern
|
6
|
-
|
7
7
|
base.after_commit :update_vendor_price
|
8
8
|
base.validate :validate_option_types
|
9
9
|
base.before_save -> { self.track_inventory = false }, if: :subscribable?
|
@@ -21,14 +21,29 @@ module SpreeCmCommissioner
|
|
21
21
|
base.has_many :variant_guest_card_class, class_name: 'SpreeCmCommissioner::VariantGuestCardClass'
|
22
22
|
base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variant_guest_card_class
|
23
23
|
|
24
|
+
base.has_many :inventory_items, class_name: 'SpreeCmCommissioner::InventoryItem'
|
25
|
+
|
24
26
|
base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
|
27
|
+
base.scope :with_permanent_stock, -> { where(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
|
28
|
+
base.scope :with_non_permanent_stock, -> { where.not(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
|
29
|
+
|
25
30
|
base.has_one :trip,
|
26
31
|
class_name: 'SpreeCmCommissioner::Trip'
|
27
32
|
base.has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy, foreign_key: :trip_id
|
28
33
|
base.accepts_nested_attributes_for :option_values
|
29
|
-
|
30
|
-
base.
|
34
|
+
|
35
|
+
base.before_save -> { self.product_type = product.product_type }, if: -> { product_type.nil? }
|
36
|
+
|
37
|
+
base.after_commit :sync_trip, if: :transit?
|
31
38
|
base.accepts_nested_attributes_for :trip_stops, allow_destroy: true
|
39
|
+
base.after_commit :create_trip_stops, if: :transit?
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_trip_stops
|
43
|
+
return if is_master?
|
44
|
+
|
45
|
+
trip_stops.find_or_create_by(stop_type: :boarding, stop_id: options.origin)
|
46
|
+
trip_stops.find_or_create_by(stop_type: :drop_off, stop_id: options.destination)
|
32
47
|
end
|
33
48
|
|
34
49
|
def delivery_required?
|
@@ -44,14 +59,26 @@ module SpreeCmCommissioner
|
|
44
59
|
super || product.discontinued?
|
45
60
|
end
|
46
61
|
|
47
|
-
def permanent_stock?
|
48
|
-
accommodation?
|
49
|
-
end
|
50
|
-
|
51
62
|
def event
|
52
63
|
taxons.event.first
|
53
64
|
end
|
54
65
|
|
66
|
+
def default_inventory_item_exist?
|
67
|
+
inventory_items.exists?(inventory_date: nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_default_non_permanent_inventory_item!(quantity_available: nil, max_capacity: nil)
|
71
|
+
return if product_type.blank? # handle in case product not exist for variant.
|
72
|
+
return unless should_track_inventory?
|
73
|
+
return if default_inventory_item_exist?
|
74
|
+
|
75
|
+
inventory_items.create!(
|
76
|
+
product_type: product_type,
|
77
|
+
quantity_available: [0, quantity_available || total_on_hand].max,
|
78
|
+
max_capacity: [0, max_capacity || total_on_hand].max
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
55
82
|
# override
|
56
83
|
def options_text
|
57
84
|
@options_text ||= Spree::Variants::VisableOptionsPresenter.new(self).to_sentence
|
@@ -67,30 +94,15 @@ module SpreeCmCommissioner
|
|
67
94
|
"#{display_sku} - #{display_price}"
|
68
95
|
end
|
69
96
|
|
70
|
-
def transit?
|
71
|
-
product.product_type == 'transit'
|
72
|
-
end
|
73
|
-
|
74
97
|
# override
|
75
|
-
def in_stock?
|
76
|
-
|
98
|
+
def in_stock?(options = {})
|
99
|
+
SpreeCmCommissioner::Stock::AvailabilityChecker.new(self, options).can_supply?
|
77
100
|
end
|
78
101
|
|
79
102
|
private
|
80
103
|
|
81
|
-
def total_purchases
|
82
|
-
Spree::LineItem.complete.where(variant_id: id).sum(:quantity).to_i
|
83
|
-
end
|
84
|
-
|
85
|
-
def available_quantity
|
86
|
-
stock_count = stock_items.sum(&:count_on_hand)
|
87
|
-
return stock_count if delivery_required?
|
88
|
-
|
89
|
-
stock_count - total_purchases
|
90
|
-
end
|
91
|
-
|
92
104
|
def update_vendor_price
|
93
|
-
return unless vendor.present? &&
|
105
|
+
return unless vendor.present? && product_type == vendor&.primary_product_type
|
94
106
|
|
95
107
|
vendor.update(min_price: price) if price < vendor.min_price
|
96
108
|
vendor.update(max_price: price) if price > vendor.max_price
|
@@ -116,7 +128,7 @@ module SpreeCmCommissioner
|
|
116
128
|
end
|
117
129
|
|
118
130
|
def sync_trip
|
119
|
-
return unless
|
131
|
+
return unless transit?
|
120
132
|
|
121
133
|
trip = SpreeCmCommissioner::Trip.find_or_initialize_by(variant_id: id)
|
122
134
|
|
@@ -4,6 +4,7 @@ module SpreeCmCommissioner
|
|
4
4
|
belongs_to :vendor, class_name: 'Spree::Vendor'
|
5
5
|
belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
|
6
6
|
|
7
|
-
|
7
|
+
validates :trip_count, numericality: { greater_than_or_equal_to: 0 }
|
8
|
+
enum :stop_type, { boarding: 0, drop_off: 1 }
|
8
9
|
end
|
9
10
|
end
|