diff --git a/.gitignore b/.gitignore index 47302a67..c2d5f548 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Gemfile.lock coverage .ruby-gemset .ruby-version +gemfiles/*.gemfile.lock diff --git a/.travis.yml b/.travis.yml index 3351b5e2..8c8ad0c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,29 @@ --- +sudo: required +dist: trusty + language: ruby -sudo: false -cache: bundler -before_script: - - sh -e /etc/init.d/xvfb start - - export DISPLAY=:99.0 - - bundle exec rake test_app + +env: + - DB=postgres + - DB=mysql + +gemfile: + - gemfiles/spree_3_1.gemfile + - gemfiles/spree_3_2.gemfile + - gemfiles/spree_master.gemfile + script: - - bundle exec rspec spec + - bundle exec rake test_app + - bundle exec rake spec + rvm: - - 2.2.4 - - 2.3.0 + - 2.3.1 + - 2.2.5 + +addons: + apt: + packages: + - mysql-server-5.6 + - mysql-client-core-5.6 + - mysql-client-5.6 diff --git a/Appraisals b/Appraisals new file mode 100644 index 00000000..1c5a9816 --- /dev/null +++ b/Appraisals @@ -0,0 +1,13 @@ +appraise 'spree-3-1' do + gem 'spree', '~> 3.1.0' +end + +appraise 'spree-3-2' do + gem 'spree', '~> 3.2.0' + gem 'rails-controller-testing' +end + +appraise 'spree-master' do + gem 'spree', github: 'spree/spree', branch: 'master' + gem 'rails-controller-testing' +end diff --git a/Gemfile b/Gemfile index 978cef5b..aa91e542 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,4 @@ source "https://rubygems.org" gem 'spree', github: 'spree/spree', branch: 'master' -gem 'pry-rails' - gemspec diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb new file mode 100644 index 00000000..f62923ca --- /dev/null +++ b/app/models/spree/order_decorator.rb @@ -0,0 +1,14 @@ +module Spree + Order.class_eval do + after_update :clean_inventory_units + + # during checkout each inventory_unit is saved twice + # second unit is not associated to shipment and is unnecessary + def clean_inventory_units + if state == 'delivery' + units = inventory_units.where(shipment: nil) + InventoryUnit.destroy(units.ids) if units.any? + end + end + end +end diff --git a/app/models/spree/order_inventory_assembly.rb b/app/models/spree/order_inventory_assembly.rb index 6f0a7334..3df6be85 100644 --- a/app/models/spree/order_inventory_assembly.rb +++ b/app/models/spree/order_inventory_assembly.rb @@ -9,6 +9,7 @@ def initialize(line_item) @order = line_item.order @line_item = line_item @product = line_item.product + @variant = line_item.variant end def verify(shipment = nil) @@ -25,6 +26,32 @@ def verify(shipment = nil) private + def add_to_shipment(shipment, quantity) + units = shipment.inventory_units + if variant.should_track_inventory? + on_hand, back_order = shipment.stock_location.fill_status(variant, quantity) + + on_hand.times { units << set_up_invetory_unit('on_hand', variant, order, line_item) } + back_order.times { units << set_up_invetory_unit('backordered', variant, order, line_item) } + else + quantity.times { units << set_up_invetory_unit('on_hand', variant, order, line_item) } + end + + shipment.inventory_units = units + shipment.save + + # adding to this shipment, and removing from stock_location + if order.completed? + shipment.stock_location.unstock(variant, quantity, shipment) + end + + quantity + end + + def set_up_invetory_unit(state, variant, order, line_item) + InventoryUnit.new(state: state, variant_id: variant.id, order_id: order.id, line_item_id: line_item.id) + end + def verify_parts(shipment, total_parts, existing_parts) if existing_parts < total_parts verifiy_add_to_shipment(shipment, total_parts, existing_parts) diff --git a/app/models/spree/stock/coordinator_decorator.rb b/app/models/spree/stock/coordinator_decorator.rb new file mode 100644 index 00000000..71aa0625 --- /dev/null +++ b/app/models/spree/stock/coordinator_decorator.rb @@ -0,0 +1,45 @@ +module Spree + module Stock + Coordinator.class_eval do + attr_reader :allocated_inventory_units + + def initialize(order, inventory_units = nil) + @order = order + @inventory_units = inventory_units || InventoryUnitBuilder.new(order).units + @allocated_inventory_units = [] + end + + def build_packages(packages = Array.new) + stock_locations_with_requested_variants.each do |stock_location| + packer = build_packer(stock_location, unallocated_inventory_units) + packages += packer.packages + @allocated_inventory_units += packer.allocated_inventory_units + end + + packages + end + + private + + def unallocated_inventory_units + if inventory_units.map(&:id).include?(nil) + allocated_inventory_units.each do |allocated| + inventory_units.each_with_index do |unit, index| + if should_delete_item?(allocated, unit) + inventory_units.delete_at(index) + break + end + end + end + inventory_units + else + inventory_units - allocated_inventory_units + end + end + + def should_delete_item?(allocated, unit) + allocated.line_item_id == unit.line_item_id && allocated.variant_id == unit.variant_id + end + end + end +end diff --git a/app/models/spree/stock/packer_decorator.rb b/app/models/spree/stock/packer_decorator.rb new file mode 100644 index 00000000..41f39b11 --- /dev/null +++ b/app/models/spree/stock/packer_decorator.rb @@ -0,0 +1,33 @@ +module Spree + module Stock + Packer.class_eval do + attr_reader :allocated_inventory_units + + def initialize(stock_location, inventory_units, splitters = [Splitter::Base]) + @stock_location = stock_location + @inventory_units = inventory_units + @splitters = splitters + @allocated_inventory_units = [] + end + + def default_package + package = Package.new(stock_location) + + inventory_units.each do |inventory_unit| + variant = inventory_unit.variant + unit = inventory_unit.dup # Can be used by others, do not use directly + if variant.should_track_inventory? + next unless stock_location.stock_item(variant) + on_hand, backordered = stock_location.fill_status(variant, 1) + package.add(unit, :backordered) if backordered > 0 + package.add(unit, :on_hand) if on_hand > 0 + else + package.add unit + end + allocated_inventory_units << unit + end + package + end + end + end +end \ No newline at end of file diff --git a/app/models/spree/stock/prioritizer_decorator.rb b/app/models/spree/stock/prioritizer_decorator.rb new file mode 100644 index 00000000..ba025ed7 --- /dev/null +++ b/app/models/spree/stock/prioritizer_decorator.rb @@ -0,0 +1,18 @@ +module Spree + module Stock + Prioritizer.class_eval do + + private + + def hash_item(item) + shipment = item.inventory_unit.shipment + inventory_unit = item.inventory_unit + if shipment.present? + inventory_unit.hash ^ shipment.hash + else + inventory_unit.hash + end + end + end + end +end \ No newline at end of file diff --git a/gemfiles/spree_3_1.gemfile b/gemfiles/spree_3_1.gemfile new file mode 100644 index 00000000..925b090b --- /dev/null +++ b/gemfiles/spree_3_1.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "spree", "~> 3.1.0" + +gemspec :path => "../" diff --git a/gemfiles/spree_3_2.gemfile b/gemfiles/spree_3_2.gemfile new file mode 100644 index 00000000..844fc70a --- /dev/null +++ b/gemfiles/spree_3_2.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "spree", "~> 3.2.0" +gem "rails-controller-testing" + +gemspec :path => "../" diff --git a/gemfiles/spree_master.gemfile b/gemfiles/spree_master.gemfile new file mode 100644 index 00000000..c382de2c --- /dev/null +++ b/gemfiles/spree_master.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "spree", :github => "spree/spree", :branch => "master" +gem "rails-controller-testing" + +gemspec :path => "../" diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index 11509893..3aaad281 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -8,7 +8,7 @@ background do bundle.master.parts << [parts] - line_item.update_attributes!(quantity: 3) + line_item.reload.update_attributes!(quantity: 3) order.reload.create_proposed_shipments order.finalize! end diff --git a/spec/models/spree/assign_part_to_bundle_form_spec.rb b/spec/models/spree/assign_part_to_bundle_form_spec.rb index 61fd58dd..acf001b1 100644 --- a/spec/models/spree/assign_part_to_bundle_form_spec.rb +++ b/spec/models/spree/assign_part_to_bundle_form_spec.rb @@ -17,7 +17,7 @@ module Spree bundle = create(:product) part = create(:product, can_be_part: true) assignment = AssembliesPart.create( - assembly_id: bundle.id, + assembly_id: bundle.master.id, count: 1, part_id: part.id ) @@ -37,7 +37,7 @@ module Spree bundle = create(:product) part = create(:product, can_be_part: true) - part_options = { count: 2, part_id: part.id, assembly_id: bundle.id } + part_options = { count: 2, part_id: part.id, assembly_id: bundle.master.id } command = AssignPartToBundleForm.new(bundle, part_options) diff --git a/spec/models/spree/order_contents_spec.rb b/spec/models/spree/order_contents_spec.rb index 5933b390..c6bb6a82 100644 --- a/spec/models/spree/order_contents_spec.rb +++ b/spec/models/spree/order_contents_spec.rb @@ -50,10 +50,10 @@ create(:variant_in_stock, product: shirt, option_values: [blue_option]) assembly_part_keychain = create(:assemblies_part, - assembly_id: assembly.id, + assembly_id: assembly.master.id, part_id: keychain.master.id) assembly_part_shirt = create(:assemblies_part, - assembly_id: assembly.id, + assembly_id: assembly.master.id, part_id: shirt.master.id, variant_selection_deferred: true) assembly.reload diff --git a/spec/models/spree/order_inventory_assembly_spec.rb b/spec/models/spree/order_inventory_assembly_spec.rb index b7c087de..d8f95742 100644 --- a/spec/models/spree/order_inventory_assembly_spec.rb +++ b/spec/models/spree/order_inventory_assembly_spec.rb @@ -146,30 +146,44 @@ module Spree line_item_quantity: 2, parts: [{ count: 1 }, { count: 1 }, { count: 3 }] ) + shipped_shipment = create(:shipment, state: 'shipped') - InventoryUnit.all[0..2].each do |unit| + shipped_variant = variants.first + shipped = InventoryUnit.where(variant: shipped_variant) + + shipped.each do |unit| unit.update_attribute(:shipment_id, shipped_shipment.id) + unit.update_attribute(:state, 'shipped') end - inventory = OrderInventoryAssembly.new(line_item) + new_quantity = 1 - line_item.update_column(:quantity, 1) - inventory.verify + line_item.update_column(:quantity, new_quantity) + line_item.reload - expect(inventory.inventory_units.count).to eq 6 + inventory = OrderInventoryAssembly.new(line_item) + inventory.verify unshipped_units = unshipped_shipment.inventory_units - expect(unshipped_units.count).to eq 3 + shipped_units = shipped_shipment.inventory_units + + units_last_variant = new_quantity * variants.last.assemblies_variants.first.count + units_second_variant = new_quantity * variants.second.assemblies_variants.first.count + units_first_variant = shipped.count + + expected_units_count = units_first_variant + units_second_variant + units_last_variant + + expect(inventory.inventory_units.count).to eq expected_units_count + + expect(unshipped_units.count).to eq(expected_units_count - shipped.count) unshipped_units.each do |unit| - expect(unit.variant).to eq variants[2] + expect(unit.variant).not_to eq shipped_variant end - shipped_units = shipped_shipment.inventory_units - expect(shipped_units.count).to eq 3 - shipped_units[0..1].each do |unit| - expect(unit.variant).to eq variants[0] + expect(shipped_units.count).to eq(shipped.count) + shipped_units.each do |unit| + expect(unit.variant).to eq shipped_variant end - expect(shipped_units[2].variant).to eq variants[1] end end end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb new file mode 100644 index 00000000..9614e28c --- /dev/null +++ b/spec/models/spree/order_spec.rb @@ -0,0 +1,56 @@ +module Spree + describe Order, type: :model do + + let(:order) { create(:order_with_line_items) } + let(:line_item) { order.line_items.first } + let(:bundle) { line_item.product } + let(:parts) { (1..3).map { create(:variant) } } + + before do + bundle.master.parts << [parts] + order.contents.update_cart({line_items_attributes: { + id: line_item.id, + quantity: 3, + options: {} + }}) + order.next + order.reload.create_proposed_shipments + order.finalize! + end + + context "product assembly is added to shipment of completed order" do + it "sets items number bigger than existing item number - adds inventory units to shipment package" do + order.contents.update_cart({line_items_attributes: { + id: line_item.id, + quantity: 4, + options: {} + }}) + + line_item_quantity = line_item.reload.quantity + units_quantity = 0 + order.reload.shipments.each { |s| units_quantity += s.inventory_units.count } + + expect(line_item_quantity).to eq(4) + expect(units_quantity).to eq(4 * 3) + end + end + + context "product assembly is removed from shipment of completed order" do + it "sets items number lower than existing item number - removes inventory units from shipment package" do + + order.contents.update_cart({line_items_attributes: { + id: line_item.id, + quantity: 1, + options: {} + }}) + + line_item_quantity = line_item.reload.quantity + units_quantity = 0 + order.reload.shipments.each { |s| units_quantity += s.inventory_units.count } + + expect(line_item_quantity).to eq(1) + expect(units_quantity).to eq(1 * 3) + end + end + end +end diff --git a/spree_product_assembly.gemspec b/spree_product_assembly.gemspec index 3a0fc2d8..70b7993f 100644 --- a/spree_product_assembly.gemspec +++ b/spree_product_assembly.gemspec @@ -17,18 +17,21 @@ Gem::Specification.new do |s| s.add_dependency 'spree_backend', '>= 3.1.0', '< 4.0' - s.add_development_dependency 'active_model_serializers', '~> 0.8.3' - s.add_development_dependency 'capybara', '~> 2.5' - s.add_development_dependency 'capybara-screenshot', '~> 1.0.11' - s.add_development_dependency 'coffee-rails', '~> 4.0.0' - s.add_development_dependency 'database_cleaner', '~> 1.4' - s.add_development_dependency 'factory_girl', '~> 4.4' + s.add_development_dependency 'active_model_serializers' + s.add_development_dependency 'capybara' + s.add_development_dependency 'capybara-screenshot' + s.add_development_dependency 'coffee-rails' + s.add_development_dependency 'database_cleaner' + s.add_development_dependency 'factory_girl' s.add_development_dependency 'ffaker' s.add_development_dependency 'launchy' s.add_development_dependency 'pg' - s.add_development_dependency 'poltergeist', '~> 1.6' - s.add_development_dependency 'rspec-rails', '~> 3.4.0' - s.add_development_dependency 'sass-rails', '~> 5.0.0' + s.add_development_dependency 'poltergeist' + s.add_development_dependency 'rspec-rails' + s.add_development_dependency 'sass-rails' s.add_development_dependency 'simplecov' s.add_development_dependency 'sqlite3' + s.add_development_dependency 'mysql2' + s.add_development_dependency 'pry-rails' + s.add_development_dependency 'appraisal' end