Compare commits

...

31 Commits

Author SHA1 Message Date
David Cook
fd889133cb Enable master variants with associated order cycles
There's only 5 in UK prod. keeping them is easier than figuring out if it's safe to delete.

(cherry picked from commit 4bf65e330b)
2023-06-23 14:16:45 +10:00
David Cook
4ac3dda398 Delete stock_items for master variants
All variants have stock_items records, but master variants never use them, so these were always redundant.

(cherry picked from commit 2025a98f58)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
9d5d269f1f Set master variants which are associated to line items to non-master
Line items which reference a master variant is a scenario that in theory shouldn't have been valid or even possible for at least 5-6 years, and these old bits of data in theory should have been cleaned up at the time those changes were made. But a couple of servers have some really old data that's not in a nice state.

Here we can just flip the is_master flag to false for those specific (legacy data) cases before deleting any other master variants, to keep the legacy line item data intact.

(cherry picked from commit c88799618f)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
f4405775f6 Remove line items related to master variants
These shouldn't technically exist, but apparently they can be present if the dataset is old enough. They can trigger a foreign key violation if they are present when a master variant is deleted, so they need to be dropped if present.

(cherry picked from commit f9185ea56e)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
3a38361857 Remove inventory units related to master variants
These shouldn't technically exist, but apparently they can be present if the dataset is old enough. They can trigger a foreign key violation if they are present when a master variant is deleted, so they need to be dropped if present.

(cherry picked from commit 6f5d3ceacc)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
68b59ab7a6 Remove array syntax on new product form for product image
(cherry picked from commit 24aa55e053)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
a56541216e Update ScopeVariantsForSearch logic to match both product and variant SKUs
(cherry picked from commit ae24b2d688)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
05d9646f3e Blank out product SKU when cloning a product
This was effectively being done before for the product's sku (stored on the master variant) via the #duplicate_variant method, but now it needs to be done explicitly on the product in #duplicate_product

(cherry picked from commit 0f253bb2a0)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
b1de64bf3e Remove unnecessary iterator
(cherry picked from commit 15000c7ed1)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
e976cc6d95 Disable variant is_master column
(cherry picked from commit 733dd3c428)
2023-06-23 14:16:45 +10:00
Matt-Yorkley
9a89b22364 Remove master images data migration tests
(cherry picked from commit be72bbebb9)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
1b304e2aa4 Remove is_master from variant serializer
(cherry picked from commit fbd09869bb)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
48cdca59fd Remove superfluous method from products controller
(cherry picked from commit ced60d4382)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
9095abfed2 Delete master variants
(cherry picked from commit b59bdc75e9)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
aa9fd682d8 Remove is_master and not_master scopes
(cherry picked from commit 1daab8994d)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
5b73ccb213 Remove unused is_master references in tests
(cherry picked from commit 0703bb4583)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
dda3cfa58d Remove master variant validation conditionals
(cherry picked from commit 85059bfb26)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
9da649a296 Improve validation feedback on new variant page and add test coverage
(cherry picked from commit 8247dce2dc)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
2e53b9a0c6 Fix flaky spec: use milliseconds in cache service and remove sleep
(cherry picked from commit 618900767f)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
527e305e2f Set unit_value to true when cloning a product
This value doesn't get persisted, but it's presence is validated. The product's duplicated variants store the actual :unit_value attribute

(cherry picked from commit 1e36043a2e)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
89b59f97ee Reorganise associations, validations, scopes and callbacks for clarity
(cherry picked from commit 1922598d2d)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
26d3cffba3 Remove master variant from product
(cherry picked from commit 3ef7d2c9ff)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
86703bb545 Migrate first master variant image to product image
(cherry picked from commit 21cba0aa13)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
8e99f496ff Migrate product image from master variant to product
(cherry picked from commit 7dc1091bc2)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
2f2506e698 Migrate master variant :sku to product
(cherry picked from commit d8649fc9fb)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
7ef9c2f56a Stop storing unit_value and unit_description on master variant
(cherry picked from commit 6b9b5ea347)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
ee4402f751 Stop using master variant for storing :display_as value
(cherry picked from commit 8c0b8dad85)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
79a2d1228d Stop using master variant as a potential store for prices
(cherry picked from commit 1b06c20197)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
da3202460c Update old specs that rely on master variant instead of real variants
(cherry picked from commit 80a0138b48)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
75ccc5c72f Simplify product images delegation mess
(cherry picked from commit d4188da7c1)
2023-06-23 14:16:44 +10:00
Matt-Yorkley
aca72e6071 Remove dead code
(cherry picked from commit 42a5a48816)
2023-06-23 14:16:44 +10:00
91 changed files with 577 additions and 678 deletions

View File

@@ -33,6 +33,4 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) ->
variantsOf: (product) -> variantsOf: (product) ->
if product.variants.length > 0 if product.variants.length > 0
variant.id for variant in product.variants variant.id for variant in product.variants
else
[product.master_id]
}) })

View File

@@ -1,15 +0,0 @@
$ ->
($ '#new_image_link').click (event) ->
event.preventDefault()
($ '.no-objects-found').hide()
($ this).hide()
$.ajax
type: 'GET'
url: @href
data: (
authenticity_token: AUTH_TOKEN
)
success: (r) ->
($ '#images').html r

View File

@@ -1,7 +0,0 @@
($ '#cancel_link').click (event) ->
event.preventDefault()
($ '.no-objects-found').show()
($ '#new_image_link').show()
($ '#images').html('')

View File

@@ -33,9 +33,9 @@ angular.module('Darkswarm').factory 'Products', (OrderCycleResource, OrderCycle,
prices = (v.price for v in product.variants) prices = (v.price for v in product.variants)
product.price = Math.min.apply(null, prices) product.price = Math.min.apply(null, prices)
product.hasVariants = product.variants?.length > 0 product.hasVariants = product.variants?.length > 0
product.primaryImage = product.images[0]?.small_url if product.images product.primaryImage = product.image?.small_url if product.image
product.primaryImageOrMissing = product.primaryImage || "/noimage/small.png" product.primaryImageOrMissing = product.primaryImage || "/noimage/small.png"
product.largeImage = product.images[0]?.large_url if product.images product.largeImage = product.image?.large_url if product.image
dereference: -> dereference: ->
for product in @fetched_products for product in @fetched_products

View File

@@ -6,7 +6,7 @@ class ProductComponent < ViewComponentReflex::Component
def initialize(product:, columns:) def initialize(product:, columns:)
super super
@product = product @product = product
@image = @product.images[0] if product.images.any? @image = @product.image if product.image.present?
@columns = columns.map do |c| @columns = columns.map do |c|
{ {
id: c[:value], id: c[:value],
@@ -28,7 +28,7 @@ class ProductComponent < ViewComponentReflex::Component
when 'price' when 'price'
@product.price @product.price
when 'unit' when 'unit'
"#{@product.unit_value} #{@product.variant_unit}" "#{@product.variants.first.unit_value} #{@product.variant_unit}"
when 'producer' when 'producer'
@product.supplier.name @product.supplier.name
when 'category' when 'category'

View File

@@ -168,7 +168,7 @@ class ProductsTableComponent < ViewComponentReflex::Component
def product_query_includes def product_query_includes
[ [
master: [:images], :image,
variants: [ variants: [
:default_price, :default_price,
:stock_locations, :stock_locations,

View File

@@ -188,7 +188,7 @@ module Admin
if enterprises.present? if enterprises.present?
enterprises.includes( enterprises.includes(
supplied_products: supplied_products:
[:supplier, :variants, { master: [:images] }] [:supplier, :variants, :image]
) )
end end
when :index when :index

View File

@@ -9,9 +9,9 @@ module Api
product = Spree::Product.find(params[:product_id]) product = Spree::Product.find(params[:product_id])
authorize! :update, product authorize! :update, product
image = product.images.first || Spree::Image.new( image = product.image || Spree::Image.new(
viewable_id: product.master.id, viewable_id: product.id,
viewable_type: 'Spree::Variant' viewable_type: 'Spree::Product'
) )
success_status = image.persisted? ? :ok : :created success_status = image.persisted? ? :ok : :created

View File

@@ -116,7 +116,7 @@ module Api
def product_query_includes def product_query_includes
[ [
master: { images: { attachment_attachment: :blob } }, image: { attachment_attachment: :blob },
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides] variants: [:default_price, :stock_locations, :stock_items, :variant_overrides]
] ]
end end

View File

@@ -56,9 +56,9 @@ module Api
def scope def scope
if @product if @product
variants = if current_api_user.has_spree_role?("admin") || params[:show_deleted] variants = if current_api_user.has_spree_role?("admin") || params[:show_deleted]
@product.variants_including_master.with_deleted @product.variants.with_deleted
else else
@product.variants_including_master @product.variants
end end
else else
variants = Spree::Variant.where(nil) variants = Spree::Variant.where(nil)

View File

@@ -25,6 +25,7 @@ module Spree
set_viewable set_viewable
@object.attributes = permitted_resource_params @object.attributes = permitted_resource_params
if @object.save if @object.save
flash[:success] = flash_message_for(@object, :successfully_created) flash[:success] = flash_message_for(@object, :successfully_created)
redirect_to spree.admin_product_images_url(params[:product_id], @url_filters) redirect_to spree.admin_product_images_url(params[:product_id], @url_filters)
@@ -62,20 +63,28 @@ module Spree
private private
def collection
parent.image
end
def find_resource
parent.image
end
def build_resource
Spree::Image.new(viewable: parent)
end
def location_after_save def location_after_save
spree.admin_product_images_url(@product) spree.admin_product_images_url(@product)
end end
def load_data def load_data
@product = Product.find_by(permalink: params[:product_id]) @product = Product.find_by(permalink: params[:product_id])
@variants = @product.variants.collect do |variant|
[variant.options_text, variant.id]
end
@variants.insert(0, [Spree.t(:all), @product.master.id])
end end
def set_viewable def set_viewable
@image.viewable_type = 'Spree::Variant' @image.viewable_type = 'Spree::Product'
@image.viewable_id = params[:image][:viewable_id] @image.viewable_id = params[:image][:viewable_id]
end end

View File

@@ -121,8 +121,7 @@ module Spree
end end
def product_includes def product_includes
[{ variants: [:images] }, [:image, { variants: [:images] }]
{ master: [:images, :default_price] }]
end end
def collection_actions def collection_actions
@@ -182,7 +181,6 @@ module Spree
joins(:product). joins(:product).
where('spree_products.supplier_id IN (?)', editable_enterprises.collect(&:id)). where('spree_products.supplier_id IN (?)', editable_enterprises.collect(&:id)).
where('spree_variants.import_date IS NOT NULL'). where('spree_variants.import_date IS NOT NULL').
where(spree_variants: { is_master: false }).
where(spree_variants: { deleted_at: nil }). where(spree_variants: { deleted_at: nil }).
order('spree_variants.import_date DESC') order('spree_variants.import_date DESC')
end end
@@ -208,7 +206,7 @@ module Spree
end end
def set_stock_levels(product, on_hand, on_demand) def set_stock_levels(product, on_hand, on_demand)
variant = product_variant(product) variant = product.variants.first
begin begin
variant.on_demand = on_demand if on_demand.present? variant.on_demand = on_demand if on_demand.present?
@@ -228,14 +226,6 @@ module Spree
end end
end end
def product_variant(product)
if product.variants.any?
product.variants.first
else
product.master
end
end
def set_product_master_variant_price_to_zero def set_product_master_variant_price_to_zero
@product.price = 0 if @product.price.nil? @product.price = 0 if @product.price.nil?
end end

View File

@@ -7,8 +7,6 @@ module Spree
class VariantsController < ::Admin::ResourceController class VariantsController < ::Admin::ResourceController
belongs_to 'spree/product', find_by: :permalink belongs_to 'spree/product', find_by: :permalink
before_action :assign_default_attributes, only: :new
def index def index
@url_filters = ::ProductFilters.new.extract(request.query_parameters) @url_filters = ::ProductFilters.new.extract(request.query_parameters)
end end
@@ -45,6 +43,7 @@ module Spree
flash[:success] = flash_message_for(@object, :successfully_created) flash[:success] = flash_message_for(@object, :successfully_created)
redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters) redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters)
else else
flash[:error] = @object.errors.full_messages.to_sentence if @object.errors.any?
redirect_to spree.new_admin_product_variant_url(params[:product_id], @url_filters) redirect_to spree.new_admin_product_variant_url(params[:product_id], @url_filters)
end end
@@ -83,13 +82,6 @@ module Spree
@object.save @object.save
end end
def assign_default_attributes
@object.attributes = @object.product.master.
attributes.except('id', 'created_at', 'deleted_at', 'sku', 'is_master')
# Shallow Clone of the default price to populate the price field.
@object.default_price = @object.product.master.default_price.clone
end
def collection def collection
@deleted = params.key?(:deleted) && params[:deleted] == "on" ? "checked" : "" @deleted = params.key?(:deleted) && params[:deleted] == "on" ? "checked" : ""

View File

@@ -5,11 +5,7 @@ module Spree
module ImagesHelper module ImagesHelper
def options_text_for(image) def options_text_for(image)
if image.viewable.is_a?(Spree::Variant) if image.viewable.is_a?(Spree::Variant)
if image.viewable.is_master? image.viewable.options_text
I18n.t(:all)
else
image.viewable.options_text
end
else else
I18n.t(:all) I18n.t(:all)
end end

View File

@@ -160,7 +160,7 @@ class Enterprise < ApplicationRecord
scope :is_distributor, -> { where('sells != ?', 'none') } scope :is_distributor, -> { where('sells != ?', 'none') }
scope :is_hub, -> { where(sells: 'any') } scope :is_hub, -> { where(sells: 'any') }
scope :supplying_variant_in, lambda { |variants| scope :supplying_variant_in, lambda { |variants|
joins(supplied_products: :variants_including_master). joins(supplied_products: :variants).
where('spree_variants.id IN (?)', variants). where('spree_variants.id IN (?)', variants).
select('DISTINCT enterprises.*') select('DISTINCT enterprises.*')
} }
@@ -389,7 +389,7 @@ class Enterprise < ApplicationRecord
def current_distributed_taxons def current_distributed_taxons
Spree::Taxon Spree::Taxon
.select("DISTINCT spree_taxons.*") .select("DISTINCT spree_taxons.*")
.joins(products: :variants_including_master) .joins(products: :variants)
.joins("INNER JOIN (#{current_exchange_variants.to_sql}) \ .joins("INNER JOIN (#{current_exchange_variants.to_sql}) \
AS exchange_variants ON spree_variants.id = exchange_variants.variant_id") AS exchange_variants ON spree_variants.id = exchange_variants.variant_id")
end end

View File

@@ -55,7 +55,7 @@ class Exchange < ApplicationRecord
} }
scope :with_product, lambda { |product| scope :with_product, lambda { |product|
joins(:exchange_variants). joins(:exchange_variants).
where('exchange_variants.variant_id IN (?)', product.variants_including_master.select(&:id)) where('exchange_variants.variant_id IN (?)', product.variants.select(&:id))
} }
scope :by_enterprise_name, -> { scope :by_enterprise_name, -> {
joins('INNER JOIN enterprises AS sender ON (sender.id = exchanges.sender_id)'). joins('INNER JOIN enterprises AS sender ON (sender.id = exchanges.sender_id)').

View File

@@ -55,7 +55,6 @@ module ProductImport
VariantOverride.for_hubs([enterprise_id]).count VariantOverride.for_hubs([enterprise_id]).count
else else
Spree::Variant. Spree::Variant.
not_master.
joins(:product). joins(:product).
where('spree_products.supplier_id IN (?)', enterprise_id). where('spree_products.supplier_id IN (?)', enterprise_id).
count count

View File

@@ -28,42 +28,73 @@ module Spree
acts_as_paranoid acts_as_paranoid
searchable_attributes :supplier_id, :primary_taxon_id, :meta_keywords searchable_attributes :supplier_id, :primary_taxon_id, :meta_keywords, :sku
searchable_associations :supplier, :properties, :primary_taxon, :variants, :master searchable_associations :supplier, :properties, :primary_taxon, :variants
searchable_scopes :active, :with_properties searchable_scopes :active, :with_properties
has_many :product_properties, dependent: :destroy
has_many :properties, through: :product_properties
has_many :classifications, dependent: :delete_all
has_many :taxons, through: :classifications
belongs_to :tax_category, class_name: 'Spree::TaxCategory' belongs_to :tax_category, class_name: 'Spree::TaxCategory'
belongs_to :shipping_category, class_name: 'Spree::ShippingCategory' belongs_to :shipping_category, class_name: 'Spree::ShippingCategory'
belongs_to :supplier, class_name: 'Enterprise', touch: true belongs_to :supplier, class_name: 'Enterprise', touch: true
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true
has_one :master, has_one :image, class_name: "Spree::Image", as: :viewable, dependent: :destroy
-> { where is_master: true },
class_name: 'Spree::Variant',
dependent: :destroy
has_many :variants, -> { has_many :product_properties, dependent: :destroy
where(is_master: false).order("spree_variants.position ASC") has_many :properties, through: :product_properties
}, class_name: 'Spree::Variant' has_many :classifications, dependent: :delete_all
has_many :taxons, through: :classifications
has_many :variants_including_master, has_many :variants, -> { order("spree_variants.position ASC") }, class_name: 'Spree::Variant',
-> { order("spree_variants.position ASC") }, dependent: :destroy
class_name: 'Spree::Variant',
dependent: :destroy
has_many :prices, -> { has_many :prices, -> {
order('spree_variants.position, spree_variants.id, currency') order('spree_variants.position, spree_variants.id, currency')
}, through: :variants }, through: :variants
has_many :stock_items, through: :variants has_many :stock_items, through: :variants
has_many :supplier_properties, through: :supplier, source: :properties has_many :supplier_properties, through: :supplier, source: :properties
has_many :variant_images, -> { order(:position) }, source: :images,
through: :variants
validates :name, presence: true
validates :permalink, presence: true
validates :shipping_category, presence: true
validates :supplier, presence: true
validates :primary_taxon, presence: true
validates :tax_category, presence: true,
if: proc { Spree::Config[:products_require_tax_category] }
validates :variant_unit, presence: true
validates :unit_value, presence:
{ if: ->(p) { %w(weight volume).include?(p.variant_unit) && new_record? } }
validates :variant_unit_scale,
presence: { if: ->(p) { %w(weight volume).include? p.variant_unit } }
validates :variant_unit_name,
presence: { if: ->(p) { p.variant_unit == 'items' } }
validate :validate_image
accepts_nested_attributes_for :variants, allow_destroy: true
accepts_nested_attributes_for :image
accepts_nested_attributes_for :product_properties,
allow_destroy: true,
reject_if: lambda { |pp| pp[:property_name].blank? }
# Transient attributes used temporarily when creating a new product,
# these values are persisted on the product's variant
attr_accessor :price, :display_as, :unit_value, :unit_description
make_permalink order: :name
after_initialize :set_available_on_to_now, if: :new_record?
before_validation :sanitize_permalink
before_save :add_primary_taxon_to_taxons
before_destroy :punch_permalink
after_save :remove_previous_primary_taxon_from_taxons
after_create :ensure_standard_variant
after_save :update_units
scope :with_properties, ->(*property_ids) { scope :with_properties, ->(*property_ids) {
left_outer_joins(:product_properties). left_outer_joins(:product_properties).
@@ -75,58 +106,6 @@ module Spree
) )
} }
delegate_belongs_to :master, :sku, :price, :currency, :display_amount, :display_price, :weight,
:height, :width, :depth, :is_master, :cost_currency,
:price_in, :amount_in, :unit_value, :unit_description
delegate :images_attributes=, :display_as=, to: :master
after_create :set_master_variant_defaults
after_save :save_master
delegate :images, to: :master, prefix: true
alias_method :images, :master_images
has_many :variant_images, -> { order(:position) }, source: :images,
through: :variants_including_master
accepts_nested_attributes_for :variants, allow_destroy: true
validates :name, presence: true
validates :permalink, presence: true
validates :price, presence: true, if: proc { Spree::Config[:require_master_price] }
validates :shipping_category, presence: true
validates :supplier, presence: true
validates :primary_taxon, presence: true
validates :tax_category, presence: true,
if: proc { Spree::Config[:products_require_tax_category] }
validates :variant_unit, presence: true
validates :unit_value, presence: { if: ->(p) { %w(weight volume).include? p.variant_unit } }
validates :variant_unit_scale,
presence: { if: ->(p) { %w(weight volume).include? p.variant_unit } }
validates :variant_unit_name,
presence: { if: ->(p) { p.variant_unit == 'items' } }
validate :validate_image_for_master
accepts_nested_attributes_for :product_properties,
allow_destroy: true,
reject_if: lambda { |pp| pp[:property_name].blank? }
make_permalink order: :name
after_initialize :ensure_master
after_initialize :set_available_on_to_now, if: :new_record?
before_validation :sanitize_permalink
before_save :add_primary_taxon_to_taxons
after_save :remove_previous_primary_taxon_from_taxons
after_save :ensure_standard_variant
after_save :update_units
before_destroy :punch_permalink
# -- Joins
scope :with_order_cycles_outer, -> { scope :with_order_cycles_outer, -> {
joins(" joins("
LEFT OUTER JOIN spree_variants AS o_spree_variants LEFT OUTER JOIN spree_variants AS o_spree_variants
@@ -150,7 +129,7 @@ module Spree
} }
scope :with_order_cycles_inner, -> { scope :with_order_cycles_inner, -> {
joins(variants_including_master: { exchanges: :order_cycle }) joins(variants: { exchanges: :order_cycle })
} }
scope :visible_for, lambda { |enterprise| scope :visible_for, lambda { |enterprise|
@@ -283,13 +262,6 @@ module Spree
stock_items.sum(&:count_on_hand) stock_items.sum(&:count_on_hand)
end end
# Master variant may be deleted (i.e. when the product is deleted)
# which would make AR's default finder return nil.
# This is a stopgap for that little problem.
def master
super || variants_including_master.with_deleted.find_by(is_master: true)
end
def properties_including_inherited def properties_including_inherited
# Product properties override producer properties # Product properties override producer properties
ps = product_properties.all ps = product_properties.all
@@ -325,7 +297,7 @@ module Spree
touch_distributors touch_distributors
ExchangeVariant. ExchangeVariant.
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted. where('exchange_variants.variant_id IN (?)', variants.with_deleted.
select(:id)).destroy_all select(:id)).destroy_all
super super
@@ -334,39 +306,6 @@ module Spree
private private
# ensures the master variant is flagged as such
def set_master_variant_defaults
master.is_master = true
end
# Here we rescue errors when saving master variants (without the need for a
# validates_associated on master) and we get more specific data about the errors
def save_master
if master && (
master.changed? || master.new_record? || (
master.default_price && (
master.default_price.changed? || master.default_price.new_record?
)
)
)
master.save!
end
# If the master cannot be saved, the Product object will get its errors
# and will be destroyed
rescue ActiveRecord::RecordInvalid
master.errors.each do |error|
errors.add error.attribute, error.message
end
raise
end
def ensure_master
return unless new_record?
self.master ||= Variant.new
end
def punch_permalink def punch_permalink
# Punch permalink with date prefix # Punch permalink with date prefix
update_attribute :permalink, "#{Time.now.to_i}_#{permalink}" update_attribute :permalink, "#{Time.now.to_i}_#{permalink}"
@@ -379,7 +318,7 @@ module Spree
def update_units def update_units
return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name? return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
variants_including_master.each(&:update_units) variants.each(&:update_units)
end end
def touch_distributors def touch_distributors
@@ -397,11 +336,14 @@ module Spree
end end
def ensure_standard_variant def ensure_standard_variant
return unless master.valid? && variants.empty? return unless variants.empty?
variant = master.dup variant = Spree::Variant.new
variant.product = self variant.product = self
variant.is_master = false variant.price = price
variant.display_as = display_as
variant.unit_value = unit_value
variant.unit_description = unit_description
variants << variant variants << variant
end end
@@ -413,8 +355,8 @@ module Spree
self.permalink = create_unique_permalink(requested.parameterize) self.permalink = create_unique_permalink(requested.parameterize)
end end
def validate_image_for_master def validate_image
return if master.images.all?(&:valid?) return if image.blank? || image.valid?
errors.add(:base, I18n.t('spree.admin.products.image_not_processable')) errors.add(:base, I18n.t('spree.admin.products.image_not_processable'))
end end

View File

@@ -79,7 +79,7 @@ module Spree
taxons = Spree::Taxon taxons = Spree::Taxon
.select("DISTINCT spree_taxons.id, ents_and_vars.enterprise_id") .select("DISTINCT spree_taxons.id, ents_and_vars.enterprise_id")
.joins(products: :variants_including_master) .joins(products: :variants)
.joins(" .joins("
INNER JOIN (#{ents_and_vars.to_sql}) AS ents_and_vars INNER JOIN (#{ents_and_vars.to_sql}) AS ents_and_vars
ON spree_variants.id = ents_and_vars.variant_id") ON spree_variants.id = ents_and_vars.variant_id")

View File

@@ -13,6 +13,8 @@ module Spree
acts_as_paranoid acts_as_paranoid
self.ignored_columns = [:is_master]
searchable_attributes :sku, :display_as, :display_name searchable_attributes :sku, :display_as, :display_name
searchable_associations :product, :default_price searchable_associations :product, :default_price
searchable_scopes :active, :deleted searchable_scopes :active, :deleted
@@ -53,10 +55,9 @@ module Spree
localize_number :price, :weight localize_number :price, :weight
validate :check_price validate :check_currency
validates :price, numericality: { greater_than_or_equal_to: 0 }, validates :price, numericality: { greater_than_or_equal_to: 0 },
presence: true, presence: true
if: proc { Spree::Config[:require_master_price] }
validates :unit_value, presence: true, if: ->(variant) { validates :unit_value, presence: true, if: ->(variant) {
%w(weight volume).include?(variant.product&.variant_unit) %w(weight volume).include?(variant.product&.variant_unit)
@@ -89,7 +90,6 @@ module Spree
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) } scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
scope :not_master, -> { where(is_master: false) }
scope :in_order_cycle, lambda { |order_cycle| scope :in_order_cycle, lambda { |order_cycle|
with_order_cycles_inner. with_order_cycles_inner.
merge(Exchange.outgoing). merge(Exchange.outgoing).
@@ -198,15 +198,7 @@ module Spree
private private
# Ensures a new variant takes the product master price when price is not supplied def check_currency
def check_price
if price.nil? && Spree::Config[:require_master_price]
raise 'No master variant found to infer price' unless product&.master
raise 'Must supply price for variant or master.price for product.' if self == product.master
self.price = product.master.price
end
return unless currency.nil? return unless currency.nil?
self.currency = Spree::Config[:currency] self.currency = Spree::Config[:currency]

View File

@@ -4,18 +4,14 @@ module Api
module Admin module Admin
module ForOrderCycle module ForOrderCycle
class SuppliedProductSerializer < ActiveModel::Serializer class SuppliedProductSerializer < ActiveModel::Serializer
attributes :name, :supplier_name, :image_url, :master_id, :variants attributes :name, :supplier_name, :image_url, :variants
def supplier_name def supplier_name
object.supplier&.name object.supplier&.name
end end
def image_url def image_url
object.images.first&.url(:mini) object.image&.url(:mini)
end
def master_id
object.master.id
end end
def variants def variants

View File

@@ -5,7 +5,7 @@ module Api
class ProductSerializer < ActiveModel::Serializer class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name,
:inherits_properties, :on_hand, :price, :available_on, :permalink_live, :inherits_properties, :on_hand, :price, :available_on, :permalink_live,
:tax_category_id, :import_date, :image_url, :thumb_url, :variants, :master :tax_category_id, :import_date, :image_url, :thumb_url, :variants
has_one :supplier, key: :producer_id, embed: :id has_one :supplier, key: :producer_id, embed: :id
has_one :primary_taxon, key: :category_id, embed: :id has_one :primary_taxon, key: :category_id, embed: :id
@@ -19,19 +19,12 @@ module Api
) )
end end
def master
Api::Admin::VariantSerializer.new(
object.master,
image: thumb_url
)
end
def image_url def image_url
object.images.first&.url(:product) || Spree::Image.default_image_url(:product) object.image&.url(:product) || Spree::Image.default_image_url(:product)
end end
def thumb_url def thumb_url
object.images.first&.url(:mini) || Spree::Image.default_image_url(:mini) object.image&.url(:mini) || Spree::Image.default_image_url(:mini)
end end
def on_hand def on_hand

View File

@@ -33,7 +33,7 @@ module Api
end end
def image def image
options[:image] || object.product.images.first&.url(:mini) options[:image] || object.product.image&.url(:mini)
end end
def in_stock def in_stock

View File

@@ -12,7 +12,7 @@ class Api::ProductSerializer < ActiveModel::Serializer
has_one :primary_taxon, serializer: Api::TaxonSerializer has_one :primary_taxon, serializer: Api::TaxonSerializer
has_many :taxons, serializer: Api::IdSerializer has_many :taxons, serializer: Api::IdSerializer
has_many :images, serializer: Api::ImageSerializer has_one :image, serializer: Api::ImageSerializer
has_one :supplier, serializer: Api::IdSerializer has_one :supplier, serializer: Api::IdSerializer
# return an unformatted descripton # return an unformatted descripton

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::VariantSerializer < ActiveModel::Serializer class Api::VariantSerializer < ActiveModel::Serializer
attributes :id, :is_master, :product_name, :sku, attributes :id, :product_name, :sku,
:options_text, :unit_value, :unit_description, :unit_to_display, :options_text, :unit_value, :unit_description, :unit_to_display,
:display_as, :display_name, :name_to_display, :display_as, :display_name, :name_to_display,
:price, :on_demand, :on_hand, :price, :on_demand, :on_hand,
@@ -40,7 +40,7 @@ class Api::VariantSerializer < ActiveModel::Serializer
end end
def thumb_url def thumb_url
object.product.images.first&.url(:mini) || Spree::Image.default_image_url(:mini) object.product.image&.url(:mini) || Spree::Image.default_image_url(:mini)
end end
def unit_price_price def unit_price_price

View File

@@ -19,7 +19,7 @@ class CacheService
# Gets the :updated_at value of the most recently updated record for a given class, and returns # Gets the :updated_at value of the most recently updated record for a given class, and returns
# it as a timestamp, eg: `1583836069`. # it as a timestamp, eg: `1583836069`.
def self.latest_timestamp_by_class(cached_class) def self.latest_timestamp_by_class(cached_class)
cached_class.maximum(:updated_at).to_i cached_class.maximum(:updated_at).to_f
end end
def self.home_stats(statistic, &block) def self.home_stats(statistic, &block)

View File

@@ -18,7 +18,6 @@ class ExchangeProductsRenderer
def exchange_variants(incoming, enterprise) def exchange_variants(incoming, enterprise)
variants_relation = Spree::Variant. variants_relation = Spree::Variant.
not_master.
where(product_id: exchange_products(incoming, enterprise).select(&:id)) where(product_id: exchange_products(incoming, enterprise).select(&:id))
filter_visible(variants_relation) filter_visible(variants_relation)
@@ -96,7 +95,7 @@ class ExchangeProductsRenderer
return enterprises if enterprises.empty? return enterprises if enterprises.empty?
enterprises.includes( enterprises.includes(
supplied_products: [:supplier, :variants, { master: [:images] }] supplied_products: [:supplier, :variants, :image]
) )
end end
end end

View File

@@ -8,8 +8,8 @@ class ImageImporter
Spree::Image.create( Spree::Image.create(
attachment: { io: file, filename: filename }, attachment: { io: file, filename: filename },
viewable_id: product.master.id, viewable_id: product.id,
viewable_type: Spree::Variant, viewable_type: Spree::Product,
) )
end end
end end

View File

@@ -11,7 +11,7 @@ module PermittedAttributes
:meta_keywords, :notes, :inherits_properties, :meta_keywords, :notes, :inherits_properties,
{ product_properties_attributes: [:id, :property_name, :value], { product_properties_attributes: [:id, :property_name, :value],
variants_attributes: [PermittedAttributes::Variant.attributes], variants_attributes: [PermittedAttributes::Variant.attributes],
images_attributes: [:attachment] } image_attributes: [:attachment] }
] ]
end end
end end

View File

@@ -97,7 +97,7 @@ class ProductsRenderer
end end
def variants_for_shop_by_id def variants_for_shop_by_id
index_by_product_id variants_for_shop.reject(&:is_master) index_by_product_id variants_for_shop
end end
def index_by_product_id(variants) def index_by_product_id(variants)

View File

@@ -100,7 +100,7 @@ module Sets
end end
def create_or_update_variant(product, variant_attributes) def create_or_update_variant(product, variant_attributes)
variant = find_model(product.variants_including_master, variant_attributes[:id]) variant = find_model(product.variants, variant_attributes[:id])
if variant.present? if variant.present?
variant.update(variant_attributes.except(:id)) variant.update(variant_attributes.except(:id))
else else

View File

@@ -1,13 +1,11 @@
%div %div
= f.hidden_field :viewable_id, value: @product.id
.four.columns.alpha .four.columns.alpha
.field .field
= f.label t('spree.filename') = f.label t('spree.filename')
%br/ %br/
= f.file_field :attachment = f.file_field :attachment
.field
= f.label Spree::Variant.model_name.human
%br/
= f.select :viewable_id, @variants, {}, {class: 'select2 fullwidth'}
.field.omega.five.columns .field.omega.five.columns
= f.label t('spree.alt_text') = f.label t('spree.alt_text')
%br/ %br/

View File

@@ -6,7 +6,7 @@
#images #images
- unless @product.images.any? || @product.variant_images.any? - unless @product.image.present?
.no-objects-found .no-objects-found
= t('spree.no_images_found') = t('spree.no_images_found')
\. \.
@@ -25,16 +25,14 @@
%th= t('spree.alt_text') %th= t('spree.alt_text')
%th.actions %th.actions
%tbody %tbody
- (@product.variant_images).each do |image| - image = @product.image
- tr_class = cycle('odd', 'even') %tr{id: spree_dom_id(image) }
- tr_id = spree_dom_id(image) %td.no-border
%tr{class: tr_class, id: tr_id } %span.handle
%td.no-border %td
%span.handle = link_to image_tag(image.url(:mini)), image.url(:product)
%td %td= options_text_for(image)
= link_to image_tag(image.url(:mini)), image.url(:product) %td= image.alt
%td= options_text_for(image) %td.actions
%td= image.alt = link_to_with_icon 'icon-edit', t('spree.edit'), edit_admin_product_image_url(@product, image, @url_filters), no_text: true, data: { action: 'edit'}
%td.actions = link_to_delete image, { url: admin_product_image_url(@product, image, @url_filters), no_text: true }
= link_to_with_icon 'icon-edit', t('spree.edit'), edit_admin_product_image_url(@product, image, @url_filters), no_text: true, data: { action: 'edit'}
= link_to_delete image, { url: admin_product_image_url(@product, image, @url_filters), no_text: true }

View File

@@ -7,5 +7,3 @@
.form-buttons.filter-actions.actions .form-buttons.filter-actions.actions
= button t('actions.create'), 'icon-ok' = button t('actions.create'), 'icon-ok'
= link_to_with_icon 'icon-remove', t('spree.actions.cancel'), admin_product_images_url(@product, @url_filters), id: 'cancel_link', class: 'button' = link_to_with_icon 'icon-remove', t('spree.actions.cancel'), admin_product_images_url(@product, @url_filters), id: 'cancel_link', class: 'button'
= javascript_include_tag 'admin/spree/images/new.js'

View File

@@ -1,4 +1,4 @@
.six.columns.omega{ "ng-if" => "product.variant_unit_with_scale != 'items'" } .six.columns.omega{ "ng-if" => "product.variant_unit_with_scale != 'items'" }
= f.field_container :display_as do = f.field_container :display_as do
= f.label :product_display_as, t('.display_as') = f.label :product_display_as, t('.display_as')
%input#product_display_as.fullwidth{name: "product[display_as]", placeholder: "{{ placeholder_text }}", type: "text", value: @product.master.display_as} %input#product_display_as.fullwidth{name: "product[display_as]", placeholder: "{{ placeholder_text }}", type: "text", value: @product.display_as}

View File

@@ -35,8 +35,8 @@
= f.label :unit_value, t(".value"), 'ng-disabled' => "!hasUnit(product)" = f.label :unit_value, t(".value"), 'ng-disabled' => "!hasUnit(product)"
%span.required * %span.required *
%input.fullwidth{ id: 'product_unit_value', 'ng-model' => 'product.master.unit_value_with_description', :type => 'text', placeholder: "eg. 2", 'ng-disabled' => "!hasUnit(product)" } %input.fullwidth{ id: 'product_unit_value', 'ng-model' => 'product.master.unit_value_with_description', :type => 'text', placeholder: "eg. 2", 'ng-disabled' => "!hasUnit(product)" }
%input{ type: 'hidden', 'ng-value': 'product.master.unit_value', "ng-init": "product.master.unit_value='#{@product.master.unit_value}'", name: 'product[unit_value]' } %input{ type: 'hidden', 'ng-value': 'product.master.unit_value', "ng-init": "product.master.unit_value='#{@product.unit_value}'", name: 'product[unit_value]' }
%input{ type: 'hidden', 'ng-value': 'product.master.unit_description', "ng-init": "product.master.unit_description='#{@product.master.unit_description}'", name: 'product[unit_description]' } %input{ type: 'hidden', 'ng-value': 'product.master.unit_description', "ng-init": "product.master.unit_description='#{@product.unit_description}'", name: 'product[unit_description]' }
= f.error_message_on :unit_value = f.error_message_on :unit_value
= render 'display_as', f: f = render 'display_as', f: f
.six.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" } .six.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" }
@@ -101,7 +101,7 @@
.row .row
= image_tag Spree::Image.default_image_url(:product), class: "four columns alpha" = image_tag Spree::Image.default_image_url(:product), class: "four columns alpha"
.row .row
= f.fields_for 'images_attributes[]', f.object.images.build do |image_fields| = f.fields_for 'image_attributes', f.object.build_image do |image_fields|
= image_fields.file_field :attachment = image_fields.file_field :attachment
.sixteen.columns.alpha .sixteen.columns.alpha
.form-buttons.filter-actions.actions .form-buttons.filter-actions.actions

View File

@@ -1 +1 @@
= image_tag(variant.product.images.first&.url(:mini) || Spree::Image.default_image_url(:mini)) = image_tag(variant.product.image&.url(:mini) || Spree::Image.default_image_url(:mini))

View File

@@ -0,0 +1,24 @@
class AddSkuToProduct < ActiveRecord::Migration[7.0]
def up
add_column :spree_products, :sku, :string, limit: 255, default: "", null: false
migrate_master_sku
end
def down
remove_column :spree_products, :sku
end
private
def migrate_master_sku
ActiveRecord::Base.connection.execute(<<-SQL
UPDATE spree_products
SET sku = spree_variants.sku
FROM spree_variants
WHERE spree_variants.product_id = spree_products.id
AND spree_variants.is_master = true
SQL
)
end
end

View File

@@ -0,0 +1,46 @@
class MigrateMasterImageToProduct < ActiveRecord::Migration[7.0]
def up
# Multiple images can be present per variant, ordered by the `position` column.
# In some cases if the image was deleted and another was added, the numbering can be off,
# so the positions of the images might be 3,4,5 or 2,3 and the "first" image would be position 2
# or position 3 in those cases (instead of 1).
#
# This finds the image for each variant with the lowest `position` out of the current images and
# sets it's `position` to 1 (if it's not already 1) to make it easier to operate on the "first" image.
renumber_first_image
# Switches the association of the first image for each master variant to it's product
migrate_master_images
end
def renumber_first_image
ActiveRecord::Base.connection.execute(<<-SQL
UPDATE spree_assets
SET position = '1'
FROM (
SELECT DISTINCT ON (viewable_id) id, viewable_id, position
FROM spree_assets
WHERE spree_assets.viewable_type = 'Spree::Variant'
ORDER BY viewable_id, position ASC
) variant_first_image
WHERE spree_assets.id = variant_first_image.id
AND spree_assets.position != '1'
SQL
)
end
def migrate_master_images
ActiveRecord::Base.connection.execute(<<-SQL
UPDATE spree_assets
SET viewable_type = 'Spree::Product',
viewable_id = spree_variants.product_id
FROM spree_variants
WHERE spree_variants.id = spree_assets.viewable_id
AND spree_variants.is_master = true
AND spree_variants.deleted_at IS NULL
AND spree_assets.viewable_type = 'Spree::Variant'
AND spree_assets.position = '1'
SQL
)
end
end

View File

@@ -0,0 +1,86 @@
class RemoveMasterVariants < ActiveRecord::Migration[7.0]
def change
if ActiveRecord::Base.connection.table_exists? "spree_option_values_variants"
delete_master_option_values
end
handle_master_line_items
handle_master_exchange_variants
delete_master_inventory_units
delete_master_variant_prices
delete_master_stock_items
delete_master_variants
end
private
def handle_master_line_items
ActiveRecord::Base.connection.execute(<<-SQL
UPDATE spree_variants
SET is_master = false
FROM spree_line_items
WHERE spree_variants.is_master = true
AND spree_variants.id = spree_line_items.variant_id
SQL
)
end
def handle_master_exchange_variants
ActiveRecord::Base.connection.execute(<<-SQL
UPDATE spree_variants
SET is_master = false
FROM exchange_variants
WHERE spree_variants.is_master = true
AND spree_variants.id = exchange_variants.variant_id
SQL
)
end
def delete_master_inventory_units
ActiveRecord::Base.connection.execute(<<-SQL
DELETE FROM spree_inventory_units
USING spree_variants
WHERE spree_variants.is_master = true
AND spree_variants.id = spree_inventory_units.variant_id
SQL
)
end
def delete_master_option_values
ActiveRecord::Base.connection.execute(<<-SQL
DELETE FROM spree_option_values_variants
USING spree_variants
WHERE spree_variants.is_master = true
AND spree_variants.id = spree_option_values_variants.variant_id
SQL
)
end
def delete_master_variant_prices
ActiveRecord::Base.connection.execute(<<-SQL
DELETE FROM spree_prices
USING spree_variants
WHERE spree_variants.is_master = true
AND spree_variants.id = spree_prices.variant_id
SQL
)
end
def delete_master_stock_items
ActiveRecord::Base.connection.execute(<<-SQL
DELETE FROM spree_stock_items
USING spree_variants
WHERE spree_variants.is_master = true
AND spree_variants.id = spree_stock_items.variant_id
SQL
)
end
def delete_master_variants
ActiveRecord::Base.connection.execute(<<-SQL
DELETE FROM spree_variants
WHERE is_master = true
SQL
)
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_05_22_120633) do ActiveRecord::Schema[7.0].define(version: 2023_06_05_133804) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements" enable_extension "pg_stat_statements"
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -745,6 +745,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_22_120633) do
t.text "notes" t.text "notes"
t.integer "primary_taxon_id", null: false t.integer "primary_taxon_id", null: false
t.boolean "inherits_properties", default: true, null: false t.boolean "inherits_properties", default: true, null: false
t.string "sku", limit: 255, default: "", null: false
t.index ["available_on"], name: "index_products_on_available_on" t.index ["available_on"], name: "index_products_on_available_on"
t.index ["deleted_at"], name: "index_products_on_deleted_at" t.index ["deleted_at"], name: "index_products_on_deleted_at"
t.index ["name"], name: "index_products_on_name" t.index ["name"], name: "index_products_on_name"

View File

@@ -24,7 +24,7 @@ module Catalog
.joins(:product) .joins(:product)
.where( .where(
spree_products: { supplier_id: enterprise_ids }, spree_products: { supplier_id: enterprise_ids },
spree_variants: { is_master: false, deleted_at: nil } spree_variants: { deleted_at: nil }
) )
return relation if excluded_items_ids.blank? return relation if excluded_items_ids.blank?

View File

@@ -9,7 +9,7 @@ class VariantFetcher
end end
def scope def scope
Spree::Variant.not_master. Spree::Variant.
joins(:product). joins(:product).
where(spree_products: { supplier: @enterprise }) where(spree_products: { supplier: @enterprise })
end end

View File

@@ -13,7 +13,7 @@ describe "Enterprises", type: :request do
expect(response).to have_http_status :ok expect(response).to have_http_status :ok
expect(response.body).to include(product.name) expect(response.body).to include(product.name)
expect(response.body).to include(product.sku) expect(response.body).to include(product.variants.first.sku)
expect(response.body).to include("offers/#{product.variants.first.id}") expect(response.body).to include("offers/#{product.variants.first.id}")
end end

View File

@@ -16,7 +16,7 @@ module OrderManagement
variant_conditions << exchange_variant_ids variant_conditions << exchange_variant_ids
end end
Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions) Spree::Variant.joins(:product).where(*variant_conditions)
end end
def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant) def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant)

View File

@@ -98,7 +98,7 @@ module OpenFoodNetwork
@order_cycle @order_cycle
).pluck(:id).uniq ).pluck(:id).uniq
product_ids = Spree::Product.joins(:variants_including_master). product_ids = Spree::Product.joins(:variants).
where("spree_variants.id IN (?)", variant_ids).pluck(:id).uniq where("spree_variants.id IN (?)", variant_ids).pluck(:id).uniq
producers_active_ids = Enterprise.joins(:supplied_products). producers_active_ids = Enterprise.joins(:supplied_products).
@@ -304,7 +304,7 @@ module OpenFoodNetwork
hubs.select("enterprises.id"), hubs.select("enterprises.id"),
@order_cycle).pluck(:id).uniq @order_cycle).pluck(:id).uniq
product_ids = Spree::Product.joins(:variants_including_master). product_ids = Spree::Product.joins(:variants).
where(spree_variants: { id: variant_ids }).pluck(:id).uniq where(spree_variants: { id: variant_ids }).pluck(:id).uniq
producer_ids = Enterprise.joins(:supplied_products). producer_ids = Enterprise.joins(:supplied_products).

View File

@@ -29,11 +29,11 @@ module OpenFoodNetwork
attr_reader :params attr_reader :params
def search_params def search_params
{ product_name_cont: params[:q], sku_cont: params[:q] } { product_name_cont: params[:q], sku_cont: params[:q], product_sku_cont: params[:q] }
end end
def query_scope def query_scope
Spree::Variant.where(is_master: false). Spree::Variant.
ransack(search_params.merge(m: 'or')). ransack(search_params.merge(m: 'or')).
result. result.
order("spree_products.name, display_name, display_as, spree_products.variant_unit_name"). order("spree_products.name, display_name, display_as, spree_products.variant_unit_name").

View File

@@ -33,7 +33,6 @@ module Reporting
def child_variants def child_variants
Spree::Variant. Spree::Variant.
where(is_master: false).
joins(:product). joins(:product).
merge(visible_products). merge(visible_products).
order('spree_products.name') order('spree_products.name')

View File

@@ -21,20 +21,17 @@ module Spree
product.dup.tap do |new_product| product.dup.tap do |new_product|
new_product.name = "COPY OF #{product.name}" new_product.name = "COPY OF #{product.name}"
new_product.taxons = product.taxons new_product.taxons = product.taxons
new_product.sku = ""
new_product.created_at = nil new_product.created_at = nil
new_product.deleted_at = nil new_product.deleted_at = nil
new_product.updated_at = nil new_product.updated_at = nil
new_product.unit_value = true
new_product.product_properties = reset_properties new_product.product_properties = reset_properties
new_product.master = duplicate_master new_product.image = duplicate_image(product.image) if product.image
new_product.variants = duplicate_variants new_product.variants = duplicate_variants
end end
end end
def duplicate_master
master = product.master
duplicate_variant(master)
end
def duplicate_variants def duplicate_variants
product.variants.map do |variant| product.variants.map do |variant|
duplicate_variant(variant) duplicate_variant(variant)

View File

@@ -46,7 +46,7 @@ namespace :ofn do
end end
# For each variant in the exchange # For each variant in the exchange
products = Spree::Product.joins(:variants_including_master).where( products = Spree::Product.joins(:variants).where(
'spree_variants.id IN (?)', exchange.variants 'spree_variants.id IN (?)', exchange.variants
).pluck(:id).uniq ).pluck(:id).uniq
producers = Enterprise.joins(:supplied_products).where("spree_products.id IN (?)", producers = Enterprise.joins(:supplied_products).where("spree_products.id IN (?)",

View File

@@ -25,7 +25,7 @@ namespace :ofn do
next next
end end
if product.images.first.nil? if product.image.nil?
ImageImporter.new.import(entry[:image_url], product) ImageImporter.new.import(entry[:image_url], product)
puts " image added." puts " image added."
else else

View File

@@ -24,13 +24,13 @@ describe Admin::ReportsController, type: :controller do
create(:simple_order_cycle, coordinator: coordinator1, create(:simple_order_cycle, coordinator: coordinator1,
distributors: [distributor1, distributor2], distributors: [distributor1, distributor2],
suppliers: [supplier1, supplier2, supplier3], suppliers: [supplier1, supplier2, supplier3],
variants: [product1.master, product3.master]) variants: [product1.variants.first, product3.variants.first])
} }
let(:ocB) { let(:ocB) {
create(:simple_order_cycle, coordinator: coordinator2, create(:simple_order_cycle, coordinator: coordinator2,
distributors: [distributor1, distributor2], distributors: [distributor1, distributor2],
suppliers: [supplier1, supplier2, supplier3], suppliers: [supplier1, supplier2, supplier3],
variants: [product2.master]) variants: [product2.variants.first])
} }
# orderA1 can only be accessed by supplier1, supplier3 and distributor1 # orderA1 can only be accessed by supplier1, supplier3 and distributor1
@@ -38,8 +38,8 @@ describe Admin::ReportsController, type: :controller do
order = create(:order, distributor: distributor1, bill_address: bill_address, order = create(:order, distributor: distributor1, bill_address: bill_address,
ship_address: ship_address, special_instructions: instructions, ship_address: ship_address, special_instructions: instructions,
order_cycle: ocA) order_cycle: ocA)
order.line_items << create(:line_item, variant: product1.master) order.line_items << create(:line_item, variant: product1.variants.first)
order.line_items << create(:line_item, variant: product3.master) order.line_items << create(:line_item, variant: product3.variants.first)
order.finalize! order.finalize!
order.save order.save
order order
@@ -49,7 +49,7 @@ describe Admin::ReportsController, type: :controller do
order = create(:order, distributor: distributor2, bill_address: bill_address, order = create(:order, distributor: distributor2, bill_address: bill_address,
ship_address: ship_address, special_instructions: instructions, ship_address: ship_address, special_instructions: instructions,
order_cycle: ocA) order_cycle: ocA)
order.line_items << create(:line_item, variant: product2.master) order.line_items << create(:line_item, variant: product2.variants.first)
order.finalize! order.finalize!
order.save order.save
order order
@@ -59,8 +59,8 @@ describe Admin::ReportsController, type: :controller do
order = create(:order, distributor: distributor1, bill_address: bill_address, order = create(:order, distributor: distributor1, bill_address: bill_address,
ship_address: ship_address, special_instructions: instructions, ship_address: ship_address, special_instructions: instructions,
order_cycle: ocB) order_cycle: ocB)
order.line_items << create(:line_item, variant: product1.master) order.line_items << create(:line_item, variant: product1.variants.first)
order.line_items << create(:line_item, variant: product3.master) order.line_items << create(:line_item, variant: product3.variants.first)
order.finalize! order.finalize!
order.save order.save
order order
@@ -70,7 +70,7 @@ describe Admin::ReportsController, type: :controller do
order = create(:order, distributor: distributor2, bill_address: bill_address, order = create(:order, distributor: distributor2, bill_address: bill_address,
ship_address: ship_address, special_instructions: instructions, ship_address: ship_address, special_instructions: instructions,
order_cycle: ocB) order_cycle: ocB)
order.line_items << create(:line_item, variant: product2.master) order.line_items << create(:line_item, variant: product2.variants.first)
order.finalize! order.finalize!
order.save order.save
order order

View File

@@ -25,7 +25,7 @@ describe Api::V0::ProductImagesController, type: :controller do
} }
expect(response.status).to eq 201 expect(response.status).to eq 201
expect(product_without_image.reload.images.first.id).to eq json_response['id'] expect(product_without_image.reload.image.id).to eq json_response['id']
end end
it "updates an existing product image" do it "updates an existing product image" do
@@ -34,7 +34,7 @@ describe Api::V0::ProductImagesController, type: :controller do
} }
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(product_with_image.reload.images.first.id).to eq json_response['id'] expect(product_with_image.reload.image.id).to eq json_response['id']
end end
it "reports errors when saving fails" do it "reports errors when saving fails" do
@@ -43,7 +43,7 @@ describe Api::V0::ProductImagesController, type: :controller do
} }
expect(response.status).to eq 422 expect(response.status).to eq 422
expect(product_without_image.images.count).to eq 0 expect(product_without_image.image).to be_nil
expect(json_response["id"]).to eq nil expect(json_response["id"]).to eq nil
expect(json_response["errors"]).to include "Attachment has an invalid content type" expect(json_response["errors"]).to include "Attachment has an invalid content type"
end end

View File

@@ -14,10 +14,7 @@ describe Api::V0::ProductsController, type: :controller do
} }
let(:product_other_supplier) { create(:product, supplier: supplier2) } let(:product_other_supplier) { create(:product, supplier: supplier2) }
let(:product_with_image) { create(:product_with_image, supplier: supplier) } let(:product_with_image) { create(:product_with_image, supplier: supplier) }
let(:attributes) { let(:all_attributes) { ["id", "name", "available_on", "variants"] }
["id", "name", "supplier", "price", "on_hand", "available_on", "permalink_live"]
}
let(:all_attributes) { ["id", "name", "price", "available_on", "variants"] }
let(:variants_attributes) { let(:variants_attributes) {
["id", "options_text", "unit_value", "unit_description", "unit_to_display", "on_demand", ["id", "options_text", "unit_value", "unit_description", "unit_to_display", "on_demand",
"display_as", "display_name", "name_to_display", "sku", "on_hand", "price"] "display_as", "display_name", "name_to_display", "sku", "on_hand", "price"]
@@ -36,8 +33,8 @@ describe Api::V0::ProductsController, type: :controller do
end end
it "gets a single product" do it "gets a single product" do
product.master.images.create!(attachment: image("thinking-cat.jpg")) product.create_image!(attachment: image("thinking-cat.jpg"))
product.variants.create!(unit_value: "1", unit_description: "thing") product.variants.create!(unit_value: "1", unit_description: "thing", price: 1)
product.variants.first.images.create!(attachment: image("thinking-cat.jpg")) product.variants.first.images.create!(attachment: image("thinking-cat.jpg"))
product.set_property("spree", "rocks") product.set_property("spree", "rocks")
api_get :show, id: product.to_param api_get :show, id: product.to_param
@@ -115,7 +112,7 @@ describe Api::V0::ProductsController, type: :controller do
it "can create a new product" do it "can create a new product" do
api_post :create, product: { name: "The Other Product", api_post :create, product: { name: "The Other Product",
price: 19.99, price: 123.45,
shipping_category_id: create(:shipping_category).id, shipping_category_id: create(:shipping_category).id,
supplier_id: supplier.id, supplier_id: supplier.id,
primary_taxon_id: FactoryBot.create(:taxon).id, primary_taxon_id: FactoryBot.create(:taxon).id,
@@ -125,6 +122,7 @@ describe Api::V0::ProductsController, type: :controller do
expect(all_attributes.all?{ |attr| json_response.keys.include? attr }).to eq(true) expect(all_attributes.all?{ |attr| json_response.keys.include? attr }).to eq(true)
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(Spree::Product.last.variants.first.price).to eq 123.45
end end
it "cannot create a new product with invalid attributes" do it "cannot create a new product with invalid attributes" do
@@ -133,7 +131,7 @@ describe Api::V0::ProductsController, type: :controller do
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.")
errors = json_response["errors"] errors = json_response["errors"]
expect(errors.keys).to match_array(["name", "price", "primary_taxon", "shipping_category", expect(errors.keys).to match_array(["name", "primary_taxon", "shipping_category",
"supplier", "variant_unit"]) "supplier", "variant_unit"])
end end
@@ -210,13 +208,6 @@ describe Api::V0::ProductsController, type: :controller do
spree_post :clone, product_id: product.id, format: :json spree_post :clone, product_id: product.id, format: :json
expect(Spree::Product.second.variants.count).not_to eq Spree::Product.first.variants.count expect(Spree::Product.second.variants.count).not_to eq Spree::Product.first.variants.count
end end
# price info: it does not consider price changes; it considers the price set upon product creation
it '(does not) clone price which was updated' do
product.update_attribute(:price, 2.22)
spree_post :clone, product_id: product.id, format: :json
expect(json_response['price']).not_to eq(2.22)
end
end end
context 'as an administrator' do context 'as an administrator' do

View File

@@ -5,10 +5,10 @@ require 'spec_helper'
describe Api::V0::VariantsController, type: :controller do describe Api::V0::VariantsController, type: :controller do
render_views render_views
let(:supplier) { FactoryBot.create(:supplier_enterprise) } let(:supplier) { create(:supplier_enterprise) }
let!(:variant1) { FactoryBot.create(:variant) } let!(:variant1) { create(:variant) }
let!(:variant2) { FactoryBot.create(:variant) } let!(:variant2) { create(:variant) }
let!(:variant3) { FactoryBot.create(:variant) } let!(:variant3) { create(:variant) }
let(:attributes) { let(:attributes) {
[:id, :options_text, :price, :on_hand, :unit_value, :unit_description, :on_demand, :display_as, [:id, :options_text, :price, :on_hand, :unit_value, :unit_description, :on_demand, :display_as,
:display_name] :display_name]
@@ -22,10 +22,7 @@ describe Api::V0::VariantsController, type: :controller do
let(:current_api_user) { build(:user) } let(:current_api_user) { build(:user) }
let!(:product) { create(:product) } let!(:product) { create(:product) }
let!(:variant) do let!(:variant) { product.variants.first }
variant = product.master
variant
end
it "retrieves a list of variants with appropriate attributes" do it "retrieves a list of variants with appropriate attributes" do
get :index, format: :json get :index, format: :json
@@ -45,18 +42,18 @@ describe Api::V0::VariantsController, type: :controller do
# Regression test for spree#2141 # Regression test for spree#2141
context "a deleted variant" do context "a deleted variant" do
before do before do
expect(Spree::Variant.count).to eq 11 expect(Spree::Variant.count).to eq 7
variant.update_column(:deleted_at, Time.zone.now) variant.update_column(:deleted_at, Time.zone.now)
end end
it "is not returned in the results" do it "is not returned in the results" do
api_get :index api_get :index
expect(json_response.count).to eq(10) expect(json_response.count).to eq(6)
end end
it "is not returned even when show_deleted is passed" do it "is not returned even when show_deleted is passed" do
api_get :index, show_deleted: true api_get :index, show_deleted: true
expect(json_response.count).to eq(10) expect(json_response.count).to eq(6)
end end
end end
@@ -91,25 +88,37 @@ describe Api::V0::VariantsController, type: :controller do
context "as an enterprise user" do context "as an enterprise user" do
let(:current_api_user) { create(:user, enterprises: [supplier]) } let(:current_api_user) { create(:user, enterprises: [supplier]) }
let(:supplier_other) { create(:supplier_enterprise) } let(:supplier_other) { create(:supplier_enterprise) }
let(:product) { create(:product, supplier: supplier) } let!(:product) { create(:product, supplier: supplier) }
let(:variant) { product.master } let(:variant) { product.variants.first }
let(:product_other) { create(:product, supplier: supplier_other) } let(:product_other) { create(:product, supplier: supplier_other) }
let(:variant_other) { product_other.master } let(:variant_other) { product_other.variants.first }
it "deletes a variant" do context "with a single remaining variant" do
api_delete :destroy, id: variant.to_param it "does not delete the variant" do
api_delete :destroy, id: variant.id
expect(response.status).to eq(204) expect(variant.reload.deleted_at).to be_nil
expect { variant.reload }.not_to raise_error end
expect(variant.deleted_at).to be_present
end end
it "is denied access to soft deleting another enterprises' variant" do context "with more than one variants" do
api_delete :destroy, id: variant_other.to_param let(:variant_to_delete) { create(:variant, product: product) }
assert_unauthorized! it "deletes a variant" do
expect { variant_other.reload }.not_to raise_error api_delete :destroy, id: variant_to_delete.id
expect(variant_other.deleted_at).to be_nil
expect(response.status).to eq(204)
expect { variant_to_delete.reload }.not_to raise_error
expect(variant_to_delete.deleted_at).to be_present
end
it "is denied access to soft deleting another enterprises' variant" do
api_delete :destroy, id: variant_other.to_param
assert_unauthorized!
expect { variant_other.reload }.not_to raise_error
expect(variant_other.deleted_at).to be_nil
end
end end
end end
@@ -117,7 +126,8 @@ describe Api::V0::VariantsController, type: :controller do
let(:current_api_user) { create(:admin_user) } let(:current_api_user) { create(:admin_user) }
let(:product) { create(:product) } let(:product) { create(:product) }
let(:variant) { product.master } let(:variant) { product.variants.first }
let!(:variant2) { create(:variant, product: product) }
context "deleted variants" do context "deleted variants" do
before do before do
@@ -133,7 +143,8 @@ describe Api::V0::VariantsController, type: :controller do
it "can create a new variant" do it "can create a new variant" do
original_number_of_variants = variant.product.variants.count original_number_of_variants = variant.product.variants.count
api_post :create, variant: { sku: "12345", unit_value: "1", unit_description: "L" }, api_post :create, variant: { sku: "12345", unit_value: "1",
unit_description: "L", price: "1" },
product_id: variant.product.to_param product_id: variant.product.to_param
expect(attributes.all?{ |attr| json_response.include? attr.to_s }).to eq(true) expect(attributes.all?{ |attr| json_response.include? attr.to_s }).to eq(true)

View File

@@ -158,8 +158,8 @@ describe Spree::Admin::ProductsController, type: :controller do
tempfile: Tempfile.new('unsupported_image_format.exr') tempfile: Tempfile.new('unsupported_image_format.exr')
) )
product_attrs_with_image = product_attrs.merge( product_attrs_with_image = product_attrs.merge(
images_attributes: { image_attributes: {
'0' => { attachment: product_image } attachment: product_image
} }
) )

View File

@@ -11,7 +11,7 @@ module Spree
describe "deleted variants" do describe "deleted variants" do
let(:product) { create(:product, name: 'Product A') } let(:product) { create(:product, name: 'Product A') }
let(:deleted_variant) do let(:deleted_variant) do
deleted_variant = product.variants.create(unit_value: "2") deleted_variant = product.variants.create(unit_value: "2", price: 1)
deleted_variant.delete deleted_variant.delete
deleted_variant deleted_variant
end end

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
factory :asset, class: Spree::Asset do
viewable { nil }
position { 1 }
type { "Spree::Image" }
end
end

View File

@@ -32,8 +32,8 @@ FactoryBot.define do
proxy.exchanges.incoming.each do |exchange| proxy.exchanges.incoming.each do |exchange|
product = create(:product, supplier: exchange.sender) product = create(:product, supplier: exchange.sender)
Spree::Image.create( Spree::Image.create(
viewable_id: product.master.id, viewable_id: product.id,
viewable_type: 'Spree::Variant', viewable_type: 'Spree::Product',
alt: "position 1", alt: "position 1",
attachment: Rack::Test::UploadedFile.new(white_logo_path), attachment: Rack::Test::UploadedFile.new(white_logo_path),
position: 1 position: 1

View File

@@ -32,7 +32,6 @@ FactoryBot.define do
tax_category { |r| Spree::TaxCategory.first || r.association(:tax_category) } tax_category { |r| Spree::TaxCategory.first || r.association(:tax_category) }
after(:create) do |product, evaluator| after(:create) do |product, evaluator|
product.master.on_hand = evaluator.on_hand
product.variants.first.on_hand = evaluator.on_hand product.variants.first.on_hand = evaluator.on_hand
product.reload product.reload
end end
@@ -42,8 +41,8 @@ FactoryBot.define do
factory :product_with_image, parent: :product do factory :product_with_image, parent: :product do
after(:create) do |product| after(:create) do |product|
Spree::Image.create(attachment: white_logo_file, Spree::Image.create(attachment: white_logo_file,
viewable_id: product.master.id, viewable_id: product.id,
viewable_type: 'Spree::Variant') viewable_type: 'Spree::Product')
end end
end end
@@ -53,8 +52,6 @@ FactoryBot.define do
on_hand { 5 } on_hand { 5 }
end end
after(:create) do |product, evaluator| after(:create) do |product, evaluator|
product.master.on_demand = evaluator.on_demand
product.master.on_hand = evaluator.on_hand
product.variants.first.on_demand = evaluator.on_demand product.variants.first.on_demand = evaluator.on_demand
product.variants.first.on_hand = evaluator.on_hand product.variants.first.on_hand = evaluator.on_hand
product.reload product.reload

View File

@@ -27,8 +27,8 @@ FactoryBot.define do
product_1 = create(:product) product_1 = create(:product)
product_2 = create(:product) product_2 = create(:product)
stock_location.stock_items.where(variant_id: product_1.master.id).first.adjust_count_on_hand(10) stock_location.stock_items.where(variant_id: product_1.variants.first.id).first.adjust_count_on_hand(10)
stock_location.stock_items.where(variant_id: product_2.master.id).first.adjust_count_on_hand(20) stock_location.stock_items.where(variant_id: product_2.variants.first.id).first.adjust_count_on_hand(20)
end end
end end
end end

View File

@@ -45,14 +45,7 @@ describe 'Enterprise service', ->
expect(Enterprise.suppliedVariants(1)).toEqual [10, 10] expect(Enterprise.suppliedVariants(1)).toEqual [10, 10]
describe "finding the variants of a product", -> describe "finding the variants of a product", ->
it "returns the master for products without variants", ->
p =
master_id: 1
variants: []
expect(Enterprise.variantsOf(p)).toEqual [1]
it "returns the variant ids for products with variants", -> it "returns the variant ids for products with variants", ->
p = p =
master_id: 1
variants: [{id: 2}, {id: 3}] variants: [{id: 2}, {id: 3}]
expect(Enterprise.variantsOf(p)).toEqual [2, 3] expect(Enterprise.variantsOf(p)).toEqual [2, 3]

View File

@@ -29,9 +29,9 @@ describe 'Products service', ->
id: 9 id: 9
master: {} master: {}
variants: [] variants: []
images: [ image: {
large_url: 'foo.png' large_url: 'foo.png'
] }
currentOrder = currentOrder =
line_items: [] line_items: []
currentHub = currentHub =

View File

@@ -25,32 +25,32 @@ module OpenFoodNetwork
describe "supplier fees" do describe "supplier fees" do
let!(:exchange1) { let!(:exchange1) {
create(:exchange, order_cycle: order_cycle, sender: supplier1, receiver: coordinator, incoming: true, create(:exchange, order_cycle: order_cycle, sender: supplier1, receiver: coordinator, incoming: true,
enterprise_fees: [enterprise_fee1], variants: [product1.master]) enterprise_fees: [enterprise_fee1], variants: [product1.variants.first])
} }
let!(:exchange2) { let!(:exchange2) {
create(:exchange, order_cycle: order_cycle, sender: supplier2, receiver: coordinator, incoming: true, create(:exchange, order_cycle: order_cycle, sender: supplier2, receiver: coordinator, incoming: true,
enterprise_fees: [enterprise_fee2], variants: [product2.master]) enterprise_fees: [enterprise_fee2], variants: [product2.variants.first])
} }
it "calculates via regular computation" do it "calculates via regular computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_for(product1.master)).to eq(20) order_cycle).fees_for(product1.variants.first)).to eq(20)
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_for(product2.master)).to eq(3) order_cycle).fees_for(product2.variants.first)).to eq(3)
end end
it "calculates via indexed computation" do it "calculates via indexed computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_for(product1.master)).to eq(20) order_cycle).indexed_fees_for(product1.variants.first)).to eq(20)
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_for(product2.master)).to eq(3) order_cycle).indexed_fees_for(product2.variants.first)).to eq(3)
end end
end end
describe "coordinator fees" do describe "coordinator fees" do
let!(:exchange) { let!(:exchange) {
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false, create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [], variants: [product1.master]) enterprise_fees: [], variants: [product1.variants.first])
} }
before do before do
@@ -59,29 +59,29 @@ module OpenFoodNetwork
it "sums via regular computation" do it "sums via regular computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_for(product1.master)).to eq(23) order_cycle).fees_for(product1.variants.first)).to eq(23)
end end
it "sums via indexed computation" do it "sums via indexed computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_for(product1.master)).to eq(23) order_cycle).indexed_fees_for(product1.variants.first)).to eq(23)
end end
end end
describe "distributor fees" do describe "distributor fees" do
let!(:exchange) { let!(:exchange) {
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false, create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1, enterprise_fee2, enterprise_fee3], variants: [product1.master]) enterprise_fees: [enterprise_fee1, enterprise_fee2, enterprise_fee3], variants: [product1.variants.first])
} }
it "sums via regular computation" do it "sums via regular computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_for(product1.master)).to eq(23) order_cycle).fees_for(product1.variants.first)).to eq(23)
end end
it "sums via indexed computation" do it "sums via indexed computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_for(product1.master)).to eq(23) order_cycle).indexed_fees_for(product1.variants.first)).to eq(23)
end end
end end
end end
@@ -93,17 +93,17 @@ module OpenFoodNetwork
} }
let!(:exchange) { let!(:exchange) {
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false, create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1], variants: [product1.master]) enterprise_fees: [enterprise_fee1], variants: [product1.variants.first])
} }
it "sums via regular computation" do it "sums via regular computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_for(product1.master)).to eq(2.00) order_cycle).fees_for(product1.variants.first)).to eq(2.00)
end end
it "sums via indexed computation" do it "sums via indexed computation" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_for(product1.master)).to eq(2.00) order_cycle).indexed_fees_for(product1.variants.first)).to eq(2.00)
end end
end end
end end
@@ -123,27 +123,27 @@ module OpenFoodNetwork
enterprise_fees: [ enterprise_fees: [
ef_admin, ef_sales, ef_packing, ef_transport, ef_fundraising ef_admin, ef_sales, ef_packing, ef_transport, ef_fundraising
], ],
variants: [product1.master]) variants: [product1.variants.first])
} }
describe "regular computation" do describe "regular computation" do
it "returns the fees names" do it "returns the fees names" do
expect(EnterpriseFeeCalculator expect(EnterpriseFeeCalculator
.new(distributor, order_cycle).fees_name_by_type_for(product1.master)) .new(distributor, order_cycle).fees_name_by_type_for(product1.variants.first))
.to eq({ admin: "Admin", fundraising: "Fundraising", packing: "Packing", .to eq({ admin: "Admin", fundraising: "Fundraising", packing: "Packing",
sales: "Sales", transport: "Transport" }) sales: "Sales", transport: "Transport" })
end end
it "returns a breakdown of fees" do it "returns a breakdown of fees" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_by_type_for(product1.master)).to eq(admin: 1.23, sales: 4.56, packing: 7.89, order_cycle).fees_by_type_for(product1.variants.first)).to eq(admin: 1.23, sales: 4.56, packing: 7.89,
transport: 0.12, fundraising: 3.45) transport: 0.12, fundraising: 3.45)
end end
it "filters out zero fees" do it "filters out zero fees" do
ef_admin.calculator.update_attribute :preferred_amount, 0 ef_admin.calculator.update_attribute :preferred_amount, 0
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).fees_by_type_for(product1.master)).to eq(sales: 4.56, packing: 7.89, transport: 0.12, order_cycle).fees_by_type_for(product1.variants.first)).to eq(sales: 4.56, packing: 7.89, transport: 0.12,
fundraising: 3.45) fundraising: 3.45)
end end
end end
@@ -151,14 +151,14 @@ module OpenFoodNetwork
describe "indexed computation" do describe "indexed computation" do
it "returns a breakdown of fees" do it "returns a breakdown of fees" do
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_by_type_for(product1.master)).to eq(admin: 1.23, sales: 4.56, order_cycle).indexed_fees_by_type_for(product1.variants.first)).to eq(admin: 1.23, sales: 4.56,
packing: 7.89, transport: 0.12, fundraising: 3.45) packing: 7.89, transport: 0.12, fundraising: 3.45)
end end
it "filters out zero fees" do it "filters out zero fees" do
ef_admin.calculator.update_attribute :preferred_amount, 0 ef_admin.calculator.update_attribute :preferred_amount, 0
expect(EnterpriseFeeCalculator.new(distributor, expect(EnterpriseFeeCalculator.new(distributor,
order_cycle).indexed_fees_by_type_for(product1.master)).to eq(sales: 4.56, packing: 7.89, order_cycle).indexed_fees_by_type_for(product1.variants.first)).to eq(sales: 4.56, packing: 7.89,
transport: 0.12, fundraising: 3.45) transport: 0.12, fundraising: 3.45)
end end
end end
@@ -166,14 +166,14 @@ module OpenFoodNetwork
describe "creating adjustments" do describe "creating adjustments" do
let(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) } let(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) }
let!(:line_item) { create(:line_item, order: order, variant: product1.master) } let!(:line_item) { create(:line_item, order: order, variant: product1.variants.first) }
let(:enterprise_fee_line_item) { create(:enterprise_fee) } let(:enterprise_fee_line_item) { create(:enterprise_fee) }
let(:enterprise_fee_order) { let(:enterprise_fee_order) {
create(:enterprise_fee, calculator: Calculator::FlatRate.new(preferred_amount: 2)) create(:enterprise_fee, calculator: Calculator::FlatRate.new(preferred_amount: 2))
} }
let!(:exchange) { let!(:exchange) {
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor,
incoming: false, variants: [product1.master]) incoming: false, variants: [product1.variants.first])
} }
before { order.reload } before { order.reload }

View File

@@ -33,6 +33,15 @@ describe OpenFoodNetwork::ScopeVariantsForSearch do
expect(result).to include v1, v2 expect(result).to include v1, v2
expect(result).to_not include v3, v4 expect(result).to_not include v3, v4
end end
context "matching both product SKUs and variant SKUs" do
let!(:v5) { create(:variant, sku: "Product 1b") }
it "returns all variants whose SKU or product's SKU match the query" do
expect(result).to include v1, v2, v5
expect(result).to_not include v3, v4
end
end
end end
context "when a schedule_id is specified" do context "when a schedule_id is specified" do

View File

@@ -718,7 +718,7 @@ describe Reporting::Reports::EnterpriseFeeSummary::Base do
end end
def default_variant_options def default_variant_options
{ product: product, producer: producer, is_master: false, coordinator: coordinator, { product: product, producer: producer, coordinator: coordinator,
distributor: distributor, order_cycle: order_cycle } distributor: distributor, order_cycle: order_cycle }
end end

View File

@@ -105,7 +105,7 @@ module Reporting
end end
describe "Filtering variants" do describe "Filtering variants" do
let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) } let(:variants) { Spree::Variant.where(nil).joins(:product) }
describe "based on report type" do describe "based on report type" do
it "returns only variants on hand" do it "returns only variants on hand" do

View File

@@ -8,8 +8,8 @@ describe Spree::Core::ProductDuplicator do
name: "foo", name: "foo",
taxons: [], taxons: [],
product_properties: [property], product_properties: [property],
master: master_variant, variants: [variant],
variants: [variant] image: image
end end
let(:new_product) do let(:new_product) do
@@ -25,14 +25,6 @@ describe Spree::Core::ProductDuplicator do
double 'New Property' double 'New Property'
end end
let(:master_variant) do
double 'Variant',
sku: "12345",
price: 19.99,
currency: "AUD",
images: [image]
end
let(:variant) do let(:variant) do
double 'Variant 1', double 'Variant 1',
sku: "67890", sku: "67890",
@@ -41,11 +33,6 @@ describe Spree::Core::ProductDuplicator do
images: [image_variant] images: [image_variant]
end end
let(:new_master_variant) do
double 'New Variant',
sku: "12345"
end
let(:new_variant) do let(:new_variant) do
double 'New Variant 1', double 'New Variant 1',
sku: "67890" sku: "67890"
@@ -72,7 +59,6 @@ describe Spree::Core::ProductDuplicator do
before do before do
expect(product).to receive(:dup).and_return(new_product) expect(product).to receive(:dup).and_return(new_product)
expect(master_variant).to receive(:dup).and_return(new_master_variant)
expect(variant).to receive(:dup).and_return(new_variant) expect(variant).to receive(:dup).and_return(new_variant)
expect(image).to receive(:dup).and_return(new_image) expect(image).to receive(:dup).and_return(new_image)
expect(image_variant).to receive(:dup).and_return(new_image_variant) expect(image_variant).to receive(:dup).and_return(new_image_variant)
@@ -83,18 +69,14 @@ describe Spree::Core::ProductDuplicator do
duplicator = Spree::Core::ProductDuplicator.new(product) duplicator = Spree::Core::ProductDuplicator.new(product)
expect(new_product).to receive(:name=).with("COPY OF foo") expect(new_product).to receive(:name=).with("COPY OF foo")
expect(new_product).to receive(:taxons=).with([]) expect(new_product).to receive(:taxons=).with([])
expect(new_product).to receive(:sku=).with("")
expect(new_product).to receive(:product_properties=).with([new_property]) expect(new_product).to receive(:product_properties=).with([new_property])
expect(new_product).to receive(:created_at=).with(nil) expect(new_product).to receive(:created_at=).with(nil)
expect(new_product).to receive(:unit_value=).with(true)
expect(new_product).to receive(:updated_at=).with(nil) expect(new_product).to receive(:updated_at=).with(nil)
expect(new_product).to receive(:deleted_at=).with(nil) expect(new_product).to receive(:deleted_at=).with(nil)
expect(new_product).to receive(:master=).with(new_master_variant)
expect(new_product).to receive(:variants=).with([new_variant]) expect(new_product).to receive(:variants=).with([new_variant])
expect(new_product).to receive(:image=).with(new_image)
expect(new_master_variant).to receive(:sku=).with("")
expect(new_master_variant).to receive(:deleted_at=).with(nil)
expect(new_master_variant).to receive(:images=).with([new_image])
expect(new_master_variant).to receive(:price=).with(master_variant.price)
expect(new_master_variant).to receive(:currency=).with(master_variant.currency)
expect(new_variant).to receive(:sku=).with("") expect(new_variant).to receive(:sku=).with("")
expect(new_variant).to receive(:deleted_at=).with(nil) expect(new_variant).to receive(:deleted_at=).with(nil)

View File

@@ -5,25 +5,6 @@ require "spec_helper"
describe ProductStock do describe ProductStock do
let(:product) { create(:simple_product) } let(:product) { create(:simple_product) }
context "when product has no variants" do
before do
product.variants.destroy
product.variants.reload
end
describe "product.on_demand" do
it "is master.on_demand" do
expect(product.on_demand).to eq(product.master.on_demand)
end
end
describe "product.on_hand" do
it "is master.on_hand" do
expect(product.on_hand).to eq(product.master.on_hand)
end
end
end
context "when product has one variant" do context "when product has one variant" do
describe "product.on_demand" do describe "product.on_demand" do
it "is the products first variant on_demand" do it "is the products first variant on_demand" do

View File

@@ -487,7 +487,7 @@ describe Enterprise do
s = create(:supplier_enterprise) s = create(:supplier_enterprise)
d = create(:distributor_enterprise) d = create(:distributor_enterprise)
p = create(:product) p = create(:product)
create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master]) create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.variants.first])
expect(Enterprise.distributors_with_active_order_cycles).to eq([d]) expect(Enterprise.distributors_with_active_order_cycles).to eq([d])
end end
@@ -496,19 +496,12 @@ describe Enterprise do
d = create(:distributor_enterprise) d = create(:distributor_enterprise)
p = create(:product) p = create(:product)
create(:simple_order_cycle, orders_open_at: 10.days.from_now, create(:simple_order_cycle, orders_open_at: 10.days.from_now,
orders_close_at: 17.days.from_now, suppliers: [s], distributors: [d], variants: [p.master]) orders_close_at: 17.days.from_now, suppliers: [s], distributors: [d], variants: [p.variants.first])
expect(Enterprise.distributors_with_active_order_cycles).not_to include d expect(Enterprise.distributors_with_active_order_cycles).not_to include d
end end
end end
describe "supplying_variant_in" do describe "supplying_variant_in" do
it "finds producers by supply of master variant" do
s = create(:supplier_enterprise)
p = create(:simple_product, supplier: s)
expect(Enterprise.supplying_variant_in([p.master])).to eq([s])
end
it "finds producers by supply of variant" do it "finds producers by supply of variant" do
s = create(:supplier_enterprise) s = create(:supplier_enterprise)
p = create(:simple_product, supplier: s) p = create(:simple_product, supplier: s)
@@ -523,7 +516,7 @@ describe Enterprise do
p1 = create(:simple_product, supplier: s1) p1 = create(:simple_product, supplier: s1)
p2 = create(:simple_product, supplier: s2) p2 = create(:simple_product, supplier: s2)
expect(Enterprise.supplying_variant_in([p1.master, p2.master])).to match_array [s1, s2] expect(Enterprise.supplying_variant_in([p1.variants.first, p2.variants.first])).to match_array [s1, s2]
end end
it "does not return duplicates" do it "does not return duplicates" do
@@ -531,7 +524,7 @@ describe Enterprise do
p1 = create(:simple_product, supplier: s) p1 = create(:simple_product, supplier: s)
p2 = create(:simple_product, supplier: s) p2 = create(:simple_product, supplier: s)
expect(Enterprise.supplying_variant_in([p1.master, p2.master])).to eq([s]) expect(Enterprise.supplying_variant_in([p1.variants.first, p2.variants.first])).to eq([s])
end end
end end
@@ -541,14 +534,14 @@ describe Enterprise do
it "returns enterprises distributing via an order cycle" do it "returns enterprises distributing via an order cycle" do
order_cycle = create(:simple_order_cycle, distributors: [distributor], order_cycle = create(:simple_order_cycle, distributors: [distributor],
variants: [product.master]) variants: [product.variants.first])
expect(Enterprise.distributing_products(product.id)).to eq([distributor]) expect(Enterprise.distributing_products(product.id)).to eq([distributor])
end end
it "does not return duplicate enterprises" do it "does not return duplicate enterprises" do
another_product = create(:product) another_product = create(:product)
order_cycle = create(:simple_order_cycle, distributors: [distributor], order_cycle = create(:simple_order_cycle, distributors: [distributor],
variants: [product.master, another_product.master]) variants: [product.variants.first, another_product.variants.first])
expect(Enterprise.distributing_products([product.id, expect(Enterprise.distributing_products([product.id,
another_product.id])).to eq([distributor]) another_product.id])).to eq([distributor])
end end
@@ -660,13 +653,13 @@ describe Enterprise do
end end
describe "finding variants distributed by the enterprise" do describe "finding variants distributed by the enterprise" do
it "finds variants, including master, distributed by order cycle" do it "finds variants distributed by order cycle" do
distributor = create(:distributor_enterprise) distributor = create(:distributor_enterprise)
product = create(:product) product = create(:product)
variant = product.variants.first variant = product.variants.first
create(:simple_order_cycle, distributors: [distributor], variants: [variant]) create(:simple_order_cycle, distributors: [distributor], variants: [variant])
expect(distributor.distributed_variants).to match_array [product.master, variant] expect(distributor.distributed_variants).to match_array [variant]
end end
end end
@@ -858,7 +851,7 @@ describe Enterprise do
:simple_order_cycle, :simple_order_cycle,
suppliers: [supplier], suppliers: [supplier],
distributors: [distributor], distributors: [distributor],
variants: [product.master] variants: [product.variants.first]
) )
expect(distributor.plus_parents_and_order_cycle_producers(order_cycle)).to eq([supplier]) expect(distributor.plus_parents_and_order_cycle_producers(order_cycle)).to eq([supplier])
end end
@@ -872,7 +865,7 @@ describe Enterprise do
:simple_order_cycle, :simple_order_cycle,
distributors: [distributor], distributors: [distributor],
suppliers: [supplier], suppliers: [supplier],
variants: [product.master] variants: [product.variants.first]
) )
create(:enterprise_relationship, parent: distributor, create(:enterprise_relationship, parent: distributor,
child: supplier, permissions: [permission]) child: supplier, permissions: [permission])
@@ -890,7 +883,7 @@ describe Enterprise do
:simple_order_cycle, :simple_order_cycle,
suppliers: [supplier], suppliers: [supplier],
distributors: [distributor], distributors: [distributor],
variants: [product.master] variants: [product.variants.first]
) )
expected = supplier.plus_parents_and_order_cycle_producers(order_cycle) expected = supplier.plus_parents_and_order_cycle_producers(order_cycle)
expect(expected).not_to include(distributor) expect(expected).not_to include(distributor)
@@ -908,7 +901,7 @@ permissions: [permission])
:simple_order_cycle, :simple_order_cycle,
suppliers: [sender], suppliers: [sender],
distributors: [distributor], distributors: [distributor],
variants: [product.master] variants: [product.variants.first]
) )
expected = supplier.plus_parents_and_order_cycle_producers(order_cycle) expected = supplier.plus_parents_and_order_cycle_producers(order_cycle)
expect(expected).to include(sender) expect(expected).to include(sender)

View File

@@ -38,7 +38,7 @@ describe Exchange do
e = create(:exchange) e = create(:exchange)
p = create(:product) p = create(:product)
e.exchange_variants.create(variant: p.master) e.exchange_variants.create(variant: p.variants.first)
expect(e.variants.count).to eq(1) expect(e.variants.count).to eq(1)
end end
@@ -243,16 +243,7 @@ describe Exchange do
expect(Exchange.with_any_variant([v1.id, v2.id, v3.id])).to eq([ex]) expect(Exchange.with_any_variant([v1.id, v2.id, v3.id])).to eq([ex])
end end
it "finds exchanges with a particular product's master variant" do it "finds exchanges with a particular product's variant" do
p = create(:simple_product)
ex = create(:exchange)
ex.variants << p.master
p.reload
expect(Exchange.with_product(p)).to eq([ex])
end
it "finds exchanges with a particular product's non-master variant" do
p = create(:simple_product) p = create(:simple_product)
v = create(:variant, product: p) v = create(:variant, product: p)
ex = create(:exchange) ex = create(:exchange)

View File

@@ -153,10 +153,10 @@ describe OrderCycle do
it "checks for variants" do it "checks for variants" do
p1 = create(:simple_product) p1 = create(:simple_product)
p2 = create(:simple_product) p2 = create(:simple_product)
oc = create(:simple_order_cycle, suppliers: [p1.supplier], variants: [p1.master]) oc = create(:simple_order_cycle, suppliers: [p1.supplier], variants: [p1.variants.first])
expect(oc).to have_variant(p1.master) expect(oc).to have_variant(p1.variants.first)
expect(oc).not_to have_variant(p2.master) expect(oc).not_to have_variant(p2.variants.first)
end end
describe "product exchanges" do describe "product exchanges" do
@@ -193,18 +193,18 @@ describe OrderCycle do
p1_v_deleted.deleted_at = Time.zone.now p1_v_deleted.deleted_at = Time.zone.now
p1_v_deleted.save p1_v_deleted.save
e0.variants << p0.master e0.variants << p0.variants.first
e1.variants << p1.master e1.variants << p1.variants.first
e1.variants << p2.master e1.variants << p2.variants.first
e1.variants << p2_v e1.variants << p2_v
e2.variants << p1.master e2.variants << p1.variants.first
e2.variants << p1_v_deleted e2.variants << p1_v_deleted
e2.variants << p1_v_visible e2.variants << p1_v_visible
e2.variants << p1_v_hidden e2.variants << p1_v_hidden
end end
it "reports on the variants exchanged" do it "reports on the variants exchanged" do
expect(oc.variants).to match_array [p0.master, p1.master, p2.master, p2_v, p1_v_visible, expect(oc.variants).to match_array [p0.variants.first, p1.variants.first, p2.variants.first, p2_v, p1_v_visible,
p1_v_hidden] p1_v_hidden]
end end
@@ -213,11 +213,11 @@ describe OrderCycle do
end end
it "reports on the variants supplied" do it "reports on the variants supplied" do
expect(oc.supplied_variants).to match_array [p0.master] expect(oc.supplied_variants).to match_array [p0.variants.first]
end end
it "reports on the variants distributed" do it "reports on the variants distributed" do
expect(oc.distributed_variants).to match_array [p1.master, p2.master, p2_v, p1_v_visible, expect(oc.distributed_variants).to match_array [p1.variants.first, p2.variants.first, p2_v, p1_v_visible,
p1_v_hidden] p1_v_hidden]
end end
@@ -236,7 +236,7 @@ describe OrderCycle do
end end
it "returns all variants in the outgoing exchange for the distributor provided" do it "returns all variants in the outgoing exchange for the distributor provided" do
expect(oc.variants_distributed_by(d2)).to include p1.master, p1_v_visible expect(oc.variants_distributed_by(d2)).to include p1.variants.first, p1_v_visible
expect(oc.variants_distributed_by(d2)).not_to include p1_v_hidden, p1_v_deleted expect(oc.variants_distributed_by(d2)).not_to include p1_v_hidden, p1_v_deleted
expect(oc.variants_distributed_by(d1)).to include p2_v expect(oc.variants_distributed_by(d1)).to include p2_v
end end

View File

@@ -151,51 +151,51 @@ describe ProductImport::ProductImporter do
carrots = Spree::Product.find_by(name: 'Carrots') carrots = Spree::Product.find_by(name: 'Carrots')
expect(carrots.supplier).to eq enterprise expect(carrots.supplier).to eq enterprise
expect(carrots.on_hand).to eq 5 expect(carrots.on_hand).to eq 5
expect(carrots.price).to eq 3.20 expect(carrots.variants.first.price).to eq 3.20
expect(carrots.unit_value).to eq 500 expect(carrots.variants.first.unit_value).to eq 500
expect(carrots.variant_unit).to eq 'weight' expect(carrots.variant_unit).to eq 'weight'
expect(carrots.variant_unit_scale).to eq 1 expect(carrots.variant_unit_scale).to eq 1
expect(carrots.on_demand).to_not eq true expect(carrots.variants.first.on_demand).to_not eq true
expect(carrots.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(carrots.variants.first.import_date).to be_within(1.minute).of Time.zone.now
potatoes = Spree::Product.find_by(name: 'Potatoes') potatoes = Spree::Product.find_by(name: 'Potatoes')
expect(potatoes.supplier).to eq enterprise expect(potatoes.supplier).to eq enterprise
expect(potatoes.on_hand).to eq 6 expect(potatoes.on_hand).to eq 6
expect(potatoes.price).to eq 6.50 expect(potatoes.variants.first.price).to eq 6.50
expect(potatoes.unit_value).to eq 2000 expect(potatoes.variants.first.unit_value).to eq 2000
expect(potatoes.variant_unit).to eq 'weight' expect(potatoes.variant_unit).to eq 'weight'
expect(potatoes.variant_unit_scale).to eq 1000 expect(potatoes.variant_unit_scale).to eq 1000
expect(potatoes.on_demand).to_not eq true expect(potatoes.variants.first.on_demand).to_not eq true
expect(potatoes.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(potatoes.variants.first.import_date).to be_within(1.minute).of Time.zone.now
pea_soup = Spree::Product.find_by(name: 'Pea Soup') pea_soup = Spree::Product.find_by(name: 'Pea Soup')
expect(pea_soup.supplier).to eq enterprise expect(pea_soup.supplier).to eq enterprise
expect(pea_soup.on_hand).to eq 8 expect(pea_soup.on_hand).to eq 8
expect(pea_soup.price).to eq 5.50 expect(pea_soup.variants.first.price).to eq 5.50
expect(pea_soup.unit_value).to eq 0.75 expect(pea_soup.variants.first.unit_value).to eq 0.75
expect(pea_soup.variant_unit).to eq 'volume' expect(pea_soup.variant_unit).to eq 'volume'
expect(pea_soup.variant_unit_scale).to eq 0.001 expect(pea_soup.variant_unit_scale).to eq 0.001
expect(pea_soup.on_demand).to_not eq true expect(pea_soup.variants.first.on_demand).to_not eq true
expect(pea_soup.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(pea_soup.variants.first.import_date).to be_within(1.minute).of Time.zone.now
salad = Spree::Product.find_by(name: 'Salad') salad = Spree::Product.find_by(name: 'Salad')
expect(salad.supplier).to eq enterprise expect(salad.supplier).to eq enterprise
expect(salad.on_hand).to eq 7 expect(salad.on_hand).to eq 7
expect(salad.price).to eq 4.50 expect(salad.variants.first.price).to eq 4.50
expect(salad.unit_value).to eq 1 expect(salad.variants.first.unit_value).to eq 1
expect(salad.variant_unit).to eq 'items' expect(salad.variant_unit).to eq 'items'
expect(salad.variant_unit_scale).to eq nil expect(salad.variant_unit_scale).to eq nil
expect(salad.on_demand).to_not eq true expect(salad.variants.first.on_demand).to_not eq true
expect(salad.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(salad.variants.first.import_date).to be_within(1.minute).of Time.zone.now
buns = Spree::Product.find_by(name: 'Hot Cross Buns') buns = Spree::Product.find_by(name: 'Hot Cross Buns')
expect(buns.supplier).to eq enterprise expect(buns.supplier).to eq enterprise
expect(buns.on_hand).to eq 7 expect(buns.on_hand).to eq 7
expect(buns.price).to eq 3.50 expect(buns.variants.first.price).to eq 3.50
expect(buns.unit_value).to eq 1 expect(buns.variants.first.unit_value).to eq 1
expect(buns.variant_unit).to eq 'items' expect(buns.variant_unit).to eq 'items'
expect(buns.variant_unit_scale).to eq nil expect(buns.variant_unit_scale).to eq nil
expect(buns.on_demand).to eq true expect(buns.variants.first.on_demand).to eq true
expect(buns.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(buns.variants.first.import_date).to be_within(1.minute).of Time.zone.now
end end
end end
@@ -232,7 +232,7 @@ describe ProductImport::ProductImporter do
carrots = Spree::Product.find_by(name: 'Good Carrots') carrots = Spree::Product.find_by(name: 'Good Carrots')
expect(carrots.supplier).to eq enterprise expect(carrots.supplier).to eq enterprise
expect(carrots.on_hand).to eq 5 expect(carrots.on_hand).to eq 5
expect(carrots.price).to eq 3.20 expect(carrots.variants.first.price).to eq 3.20
expect(carrots.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(carrots.variants.first.import_date).to be_within(1.minute).of Time.zone.now
expect(Spree::Product.find_by(name: 'Bad Potatoes')).to eq nil expect(Spree::Product.find_by(name: 'Bad Potatoes')).to eq nil
@@ -275,7 +275,7 @@ describe ProductImport::ProductImporter do
carrots = Spree::Product.find_by(name: 'Good Carrots') carrots = Spree::Product.find_by(name: 'Good Carrots')
expect(carrots.on_hand).to eq 5 expect(carrots.on_hand).to eq 5
expect(carrots.price).to eq 3.20 expect(carrots.variants.first.price).to eq 3.20
expect(carrots.primary_taxon.name).to eq "Vegetables" expect(carrots.primary_taxon.name).to eq "Vegetables"
expect(carrots.shipping_category).to eq shipping_category expect(carrots.shipping_category).to eq shipping_category
expect(carrots.supplier).to eq enterprise expect(carrots.supplier).to eq enterprise

View File

@@ -337,7 +337,7 @@ describe Spree::Ability do
is_expected.to have_ability([:admin, :read, :update, :bulk_update, :clone, :destroy], is_expected.to have_ability([:admin, :read, :update, :bulk_update, :clone, :destroy],
for: p1) for: p1)
is_expected.to have_ability( is_expected.to have_ability(
[:admin, :index, :read, :edit, :update, :search, :destroy, :delete], for: p1.master [:admin, :index, :read, :edit, :update, :search, :destroy, :delete], for: p1.variants.first
) )
end end
@@ -346,7 +346,7 @@ describe Spree::Ability do
is_expected.to have_ability([:admin, :read, :update, :bulk_update, :clone, :destroy], is_expected.to have_ability([:admin, :read, :update, :bulk_update, :clone, :destroy],
for: p_related) for: p_related)
is_expected.to have_ability( is_expected.to have_ability(
[:admin, :index, :read, :edit, :update, :search, :destroy, :delete], for: p_related.master [:admin, :index, :read, :edit, :update, :search, :destroy, :delete], for: p_related.variants.first
) )
end end
@@ -354,7 +354,7 @@ describe Spree::Ability do
is_expected.not_to have_ability([:admin, :read, :update, :bulk_update, :clone, :destroy], is_expected.not_to have_ability([:admin, :read, :update, :bulk_update, :clone, :destroy],
for: p2) for: p2)
is_expected.not_to have_ability([:admin, :index, :read, :edit, :update, :search, :destroy], is_expected.not_to have_ability([:admin, :index, :read, :edit, :update, :search, :destroy],
for: p2.master) for: p2.variants.first)
end end
it "should not be able to access admin actions on orders" do it "should not be able to access admin actions on orders" do
@@ -369,13 +369,13 @@ describe Spree::Ability do
is_expected.to have_ability([:create], for: Spree::Variant) is_expected.to have_ability([:create], for: Spree::Variant)
is_expected.to have_ability( is_expected.to have_ability(
[:admin, :index, :read, :create, :edit, :search, :update, :destroy, [:admin, :index, :read, :create, :edit, :search, :update, :destroy,
:delete], for: p1.master :delete], for: p1.variants.first
) )
end end
it "should not be able to read/write other enterprises' product variants" do it "should not be able to read/write other enterprises' product variants" do
is_expected.not_to have_ability( is_expected.not_to have_ability(
[:admin, :index, :read, :create, :edit, :search, :update, :destroy], for: p2.master [:admin, :index, :read, :create, :edit, :search, :update, :destroy], for: p2.variants.first
) )
end end
@@ -554,10 +554,10 @@ describe Spree::Ability do
end end
describe "variant overrides" do describe "variant overrides" do
let(:vo1) { create(:variant_override, hub: d1, variant: p1.master) } let(:vo1) { create(:variant_override, hub: d1, variant: p1.variants.first) }
let(:vo2) { create(:variant_override, hub: d1, variant: p2.master) } let(:vo2) { create(:variant_override, hub: d1, variant: p2.variants.first) }
let(:vo3) { create(:variant_override, hub: d2, variant: p1.master) } let(:vo3) { create(:variant_override, hub: d2, variant: p1.variants.first) }
let(:vo4) { create(:variant_override, hub: d2, variant: p2.master) } let(:vo4) { create(:variant_override, hub: d2, variant: p2.variants.first) }
let!(:er1) { let!(:er1) {
create(:enterprise_relationship, parent: s1, child: d1, create(:enterprise_relationship, parent: s1, child: d1,

View File

@@ -6,7 +6,7 @@ describe Spree::Asset do
describe "#viewable" do describe "#viewable" do
it "touches association" do it "touches association" do
product = create(:product) product = create(:product)
asset = Spree::Asset.create! { |a| a.viewable = product.master } asset = Spree::Asset.create! { |a| a.viewable = product }
product.update_column(:updated_at, 1.day.ago) product.update_column(:updated_at, 1.day.ago)

View File

@@ -9,7 +9,7 @@ module Spree
subject { subject {
Spree::Image.create!( Spree::Image.create!(
attachment: black_logo_file, attachment: black_logo_file,
viewable: product.master, viewable: product,
) )
} }
let(:product) { create(:product) } let(:product) { create(:product) }

View File

@@ -477,7 +477,7 @@ module Spree
describe "inheriting units" do describe "inheriting units" do
let!(:p) { let!(:p) {
create(:product, variant_unit: "weight", variant_unit_scale: 1, create(:product, variant_unit: "weight", variant_unit_scale: 1,
master: create(:variant, unit_value: 1000 )) unit_value: 1000 )
} }
let!(:v) { p.variants.first } let!(:v) { p.variants.first }
let!(:o) { create(:order) } let!(:o) { create(:order) }

View File

@@ -16,71 +16,21 @@ module Spree
it 'duplicates product' do it 'duplicates product' do
clone = product.duplicate clone = product.duplicate
expect(clone.name).to eq 'COPY OF ' + product.name expect(clone.name).to eq 'COPY OF ' + product.name
expect(clone.master.sku).to eq '' expect(clone.sku).to eq ""
expect(clone.images.size).to eq product.images.size expect(clone.image).to eq product.image
end
end
context "product has no variants" do
context "#destroy" do
it "should set deleted_at value" do
product.destroy
expect(product.deleted_at).to_not be_nil
expect(product.master.deleted_at).to_not be_nil
end
end end
end end
context "product has variants" do context "product has variants" do
before do before do
create(:variant, product: product) product.reload.variants << create(:variant, product: product)
end end
context "#destroy" do context "#destroy" do
it "should set deleted_at value" do it "should set deleted_at value" do
product.destroy product.destroy
expect(product.deleted_at).to_not be_nil expect(product.deleted_at).to_not be_nil
expect(product.variants_including_master.all? { |v| !v.deleted_at.nil? }).to be_truthy expect(product.variants.all? { |v| !v.deleted_at.nil? }).to be_truthy
end
end
end
context "#price" do
# Regression test for Spree #1173
it 'strips non-price characters' do
product.price = "$10"
expect(product.price).to eq 10.0
end
end
context "#display_price" do
before { product.price = 10.55 }
context "with display_currency set to true" do
before { Spree::Config[:display_currency] = true }
it "shows the currency" do
expect(product.display_price.to_s).to eq "$10.55 #{Spree::Config[:currency]}"
end
end
context "with display_currency set to false" do
before { Spree::Config[:display_currency] = false }
it "does not include the currency" do
expect(product.display_price.to_s).to eq "$10.55"
end
end
context "with currency set to JPY" do
before do
product.master.default_price.currency = 'JPY'
product.master.default_price.save!
Spree::Config[:currency] = 'JPY'
end
it "displays the currency in yen" do
expect(product.display_price.to_s).to eq "¥11"
end end
end end
end end
@@ -91,12 +41,6 @@ module Spree
expect(product.variants.to_sql).to match(/ORDER BY spree_variants.position ASC/) expect(product.variants.to_sql).to match(/ORDER BY spree_variants.position ASC/)
end end
end end
context 'with master variant' do
it 'sorts variants by position' do
expect(product.variants_including_master.to_sql).to match(/ORDER BY spree_variants.position ASC/)
end
end
end end
end end
@@ -281,7 +225,7 @@ module Spree
context "has stock movements" do context "has stock movements" do
let(:product) { create(:product) } let(:product) { create(:product) }
let(:variant) { product.master } let(:variant) { product.variants.first }
let(:stock_item) { variant.stock_items.first } let(:stock_item) { variant.stock_items.first }
it "doesnt raise ReadOnlyRecord error" do it "doesnt raise ReadOnlyRecord error" do
@@ -312,14 +256,6 @@ module Spree
expect(build(:simple_product, supplier: nil)).not_to be_valid expect(build(:simple_product, supplier: nil)).not_to be_valid
end end
it "does not save when master is invalid" do
product = build(:product)
product.variant_unit = 'weight'
product.master.unit_value = nil
expect(product.save).to eq(false)
end
it "defaults available_on to now" do it "defaults available_on to now" do
Timecop.freeze do Timecop.freeze do
product = Product.new product = Product.new
@@ -411,16 +347,10 @@ module Spree
product.save! product.save!
end end
it "copies the properties on master variant to the first standard variant" do it "copies properties to the first standard variant" do
expect(product.variants.reload.length).to eq 1 expect(product.variants.reload.length).to eq 1
standard_variant = product.variants.reload.first standard_variant = product.variants.reload.first
expect(standard_variant.price).to eq product.master.price expect(standard_variant.price).to eq 4.27
end
it "only duplicates master with after_save when no standard variants exist" do
expect(product).to receive :ensure_standard_variant
product.name = "Something else"
expect{ product.save! }.to_not change{ product.variants.count }
end end
end end
@@ -461,11 +391,11 @@ module Spree
end end
end end
describe "#validate_image_for_master" do describe "#validate_image" do
let(:product) { build_stubbed(:simple_product) } let(:product) { create(:product_with_image) }
context "when the image attached to the master variant is invalid" do context "when the image is invalid" do
before { product.master.images.new.errors.add(:image_not_processable, "invalid") } before { expect(product.image).to receive(:valid?).and_return(false) }
it "adds an error message to the base object" do it "adds an error message to the base object" do
expect(product).not_to be_valid expect(product).not_to be_valid
@@ -565,8 +495,8 @@ module Spree
d2 = create(:distributor_enterprise) d2 = create(:distributor_enterprise)
p1 = create(:product) p1 = create(:product)
p2 = create(:product) p2 = create(:product)
create(:simple_order_cycle, suppliers: [s], distributors: [d1], variants: [p1.master]) create(:simple_order_cycle, suppliers: [s], distributors: [d1], variants: [p1.variants.first])
create(:simple_order_cycle, suppliers: [s], distributors: [d2], variants: [p2.master]) create(:simple_order_cycle, suppliers: [s], distributors: [d2], variants: [p2.variants.first])
expect(Product.in_distributor(d1)).to eq([p1]) expect(Product.in_distributor(d1)).to eq([p1])
end end
@@ -590,7 +520,7 @@ module Spree
p = create(:product) p = create(:product)
oc = create(:simple_order_cycle, coordinator: c, suppliers: [s], distributors: [d]) oc = create(:simple_order_cycle, coordinator: c, suppliers: [s], distributors: [d])
ex = oc.exchanges.incoming.first ex = oc.exchanges.incoming.first
ex.variants << p.master ex.variants << p.variants.first
expect(Product.in_distributor(d)).to be_empty expect(Product.in_distributor(d)).to be_empty
end end
@@ -642,8 +572,8 @@ module Spree
d2 = create(:distributor_enterprise) d2 = create(:distributor_enterprise)
p1 = create(:product) p1 = create(:product)
p2 = create(:product) p2 = create(:product)
create(:simple_order_cycle, suppliers: [s], distributors: [d1], variants: [p1.master]) create(:simple_order_cycle, suppliers: [s], distributors: [d1], variants: [p1.variants.first])
create(:simple_order_cycle, suppliers: [s], distributors: [d2], variants: [p2.master]) create(:simple_order_cycle, suppliers: [s], distributors: [d2], variants: [p2.variants.first])
expect(Product.in_supplier_or_distributor(d1)).to eq([p1]) expect(Product.in_supplier_or_distributor(d1)).to eq([p1])
end end
@@ -651,7 +581,7 @@ module Spree
s = create(:supplier_enterprise) s = create(:supplier_enterprise)
d = create(:distributor_enterprise) d = create(:distributor_enterprise)
p = create(:product, supplier: s) p = create(:product, supplier: s)
create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master]) create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.variants.first])
[s, d].each { |e| expect(Product.in_supplier_or_distributor(e)).to eq([p]) } [s, d].each { |e| expect(Product.in_supplier_or_distributor(e)).to eq([p]) }
end end
end end
@@ -664,9 +594,9 @@ module Spree
p1 = create(:product) p1 = create(:product)
p2 = create(:product) p2 = create(:product)
oc1 = create(:simple_order_cycle, suppliers: [s], distributors: [d1], oc1 = create(:simple_order_cycle, suppliers: [s], distributors: [d1],
variants: [p1.master]) variants: [p1.variants.first])
oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2], oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2],
variants: [p2.master]) variants: [p2.variants.first])
expect(Product.in_order_cycle(oc1)).to eq([p1]) expect(Product.in_order_cycle(oc1)).to eq([p1])
end end
end end
@@ -680,9 +610,9 @@ module Spree
p2 = create(:product) p2 = create(:product)
p3 = create(:product) p3 = create(:product)
oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2], oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2],
variants: [p2.master], orders_open_at: 8.days.ago, orders_close_at: 1.day.ago) variants: [p2.variants.first], orders_open_at: 8.days.ago, orders_close_at: 1.day.ago)
oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d3], oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d3],
variants: [p3.master], orders_close_at: Date.tomorrow) variants: [p3.variants.first], orders_close_at: Date.tomorrow)
expect(Product.in_an_active_order_cycle).to eq([p3]) expect(Product.in_an_active_order_cycle).to eq([p3])
end end
end end
@@ -860,8 +790,8 @@ module Spree
d2 = create(:distributor_enterprise) d2 = create(:distributor_enterprise)
p1 = create(:product) p1 = create(:product)
p2 = create(:product) p2 = create(:product)
oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.master]) oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.master]) oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first])
expect(p1).to be_in_distributor d1 expect(p1).to be_in_distributor d1
expect(p1).not_to be_in_distributor d2 expect(p1).not_to be_in_distributor d2
@@ -872,8 +802,8 @@ module Spree
d2 = create(:distributor_enterprise) d2 = create(:distributor_enterprise)
p1 = create(:product) p1 = create(:product)
p2 = create(:product) p2 = create(:product)
oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.master]) oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.master]) oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first])
expect(p1).to be_in_order_cycle oc1 expect(p1).to be_in_order_cycle oc1
expect(p1).not_to be_in_order_cycle oc2 expect(p1).not_to be_in_order_cycle oc2
@@ -899,18 +829,6 @@ module Spree
expect(v.reload.unit_presentation).to eq "1L" expect(v.reload.unit_presentation).to eq "1L"
end end
it "updates its master variant's unit values" do
p.master.update!(unit_value: 1)
p.reload
expect(p.master.unit_presentation).to eq "1g"
p.update!(variant_unit: 'volume', variant_unit_scale: 0.001)
p.reload
expect(p.master.unit_presentation).to eq "1L"
end
end end
end end
@@ -933,13 +851,7 @@ module Spree
create(:exchange, order_cycle: oc, incoming: true, sender: s, receiver: oc.coordinator) create(:exchange, order_cycle: oc, incoming: true, sender: s, receiver: oc.coordinator)
} }
it "removes the master variant from all order cycles" do it "removes all variants from order cycles" do
e.variants << p.master
p.destroy
expect(e.variants.reload).to be_empty
end
it "removes all other variants from order cycles" do
e.variants << v e.variants << v
p.destroy p.destroy
expect(e.variants.reload).to be_empty expect(e.variants.reload).to be_empty

View File

@@ -232,16 +232,16 @@ describe Spree::Variant do
let!(:d2) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) }
let!(:p1) { create(:simple_product) } let!(:p1) { create(:simple_product) }
let!(:p2) { create(:simple_product) } let!(:p2) { create(:simple_product) }
let!(:oc1) { create(:simple_order_cycle, distributors: [d1], variants: [p1.master]) } let!(:oc1) { create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first]) }
let!(:oc2) { create(:simple_order_cycle, distributors: [d2], variants: [p2.master]) } let!(:oc2) { create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first]) }
it "shows variants in an order cycle distribution" do it "shows variants in an order cycle distribution" do
expect(Spree::Variant.in_distributor(d1)).to eq([p1.master]) expect(Spree::Variant.in_distributor(d1)).to eq([p1.variants.first])
end end
it "doesn't show duplicates" do it "doesn't show duplicates" do
oc_dup = create(:simple_order_cycle, distributors: [d1], variants: [p1.master]) oc_dup = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
expect(Spree::Variant.in_distributor(d1)).to eq([p1.master]) expect(Spree::Variant.in_distributor(d1)).to eq([p1.variants.first])
end end
end end
@@ -250,18 +250,18 @@ describe Spree::Variant do
let!(:d2) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) }
let!(:p1) { create(:product) } let!(:p1) { create(:product) }
let!(:p2) { create(:product) } let!(:p2) { create(:product) }
let!(:oc1) { create(:simple_order_cycle, distributors: [d1], variants: [p1.master]) } let!(:oc1) { create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first]) }
let!(:oc2) { create(:simple_order_cycle, distributors: [d2], variants: [p2.master]) } let!(:oc2) { create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first]) }
it "shows variants in an order cycle" do it "shows variants in an order cycle" do
expect(Spree::Variant.in_order_cycle(oc1)).to eq([p1.master]) expect(Spree::Variant.in_order_cycle(oc1)).to eq([p1.variants.first])
end end
it "doesn't show duplicates" do it "doesn't show duplicates" do
ex = create(:exchange, order_cycle: oc1, sender: oc1.coordinator, receiver: d2) ex = create(:exchange, order_cycle: oc1, sender: oc1.coordinator, receiver: d2)
ex.variants << p1.master ex.variants << p1.variants.first
expect(Spree::Variant.in_order_cycle(oc1)).to eq([p1.master]) expect(Spree::Variant.in_order_cycle(oc1)).to eq([p1.variants.first])
end end
end end
@@ -520,10 +520,6 @@ describe Spree::Variant do
variant.unit_value = nil variant.unit_value = nil
expect(variant).not_to be_valid expect(variant).not_to be_valid
end end
it "has a valid master variant" do
expect(product.master).to be_valid
end
end end
end end
@@ -552,10 +548,6 @@ describe Spree::Variant do
expect(variant).to be_valid expect(variant).to be_valid
expect(variant.unit_value).to eq 1.0 expect(variant.unit_value).to eq 1.0
end end
it "has a valid master variant" do
expect(product.master).to be_valid
end
end end
context "when the product's unit is non-weight" do context "when the product's unit is non-weight" do

View File

@@ -28,7 +28,7 @@ describe Api::ProductSerializer do
it "serializes various attributes" do it "serializes various attributes" do
expect(serializer.serializable_hash.keys).to eq [ expect(serializer.serializable_hash.keys).to eq [
:id, :name, :permalink, :meta_keywords, :group_buy, :notes, :description, :description_html, :id, :name, :permalink, :meta_keywords, :group_buy, :notes, :description, :description_html,
:properties_with_values, :variants, :primary_taxon, :taxons, :images, :supplier :properties_with_values, :variants, :primary_taxon, :taxons, :image, :supplier
] ]
end end

View File

@@ -11,7 +11,6 @@ describe Api::VariantSerializer do
to include( to include(
:id, :id,
:name_to_display, :name_to_display,
:is_master,
:on_hand, :on_hand,
:name_to_display, :name_to_display,
:unit_to_display, :unit_to_display,

View File

@@ -20,7 +20,7 @@ describe CacheService do
end end
describe "#cached_data_by_class" do describe "#cached_data_by_class" do
let(:timestamp) { Time.now.to_i } let(:timestamp) { Time.now.to_f }
before do before do
allow(rails_cache).to receive(:fetch) allow(rails_cache).to receive(:fetch)
@@ -42,10 +42,12 @@ describe CacheService do
it "gets the :updated_at value of the last record for a given class and returns a timestamp" do it "gets the :updated_at value of the last record for a given class and returns a timestamp" do
taxon1.touch taxon1.touch
expect(CacheService.latest_timestamp_by_class(Spree::Taxon)).to eq taxon1.updated_at.to_i expect(CacheService.latest_timestamp_by_class(Spree::Taxon)).
to eq taxon1.reload.updated_at.to_f
taxon2.touch taxon2.touch
expect(CacheService.latest_timestamp_by_class(Spree::Taxon)).to eq taxon2.updated_at.to_i expect(CacheService.latest_timestamp_by_class(Spree::Taxon)).
to eq taxon2.reload.updated_at.to_f
end end
end end
end end

View File

@@ -14,8 +14,8 @@ describe ImageImporter do
Spree::Image.count Spree::Image.count
}.by(1) }.by(1)
expect(product.images.count).to eq 1 expect(product.image).to_not be_nil
expect(product.reload.images.first.attachment_blob.byte_size).to eq 6274 expect(product.reload.image.attachment_blob.byte_size).to eq 6274
end end
end end
end end

View File

@@ -72,8 +72,8 @@ describe '
it "displays 'on demand' for any variant that is available on demand" do it "displays 'on demand' for any variant that is available on demand" do
p1 = FactoryBot.create(:product) p1 = FactoryBot.create(:product)
v1 = FactoryBot.create(:variant, product: p1, is_master: false, on_hand: 4) v1 = FactoryBot.create(:variant, product: p1, on_hand: 4)
v2 = FactoryBot.create(:variant, product: p1, is_master: false, on_hand: 0, on_demand: true) v2 = FactoryBot.create(:variant, product: p1, on_hand: 0, on_demand: true)
visit spree.admin_products_path visit spree.admin_products_path
expect(page).to have_selector "a.view-variants", count: 1 expect(page).to have_selector "a.view-variants", count: 1
@@ -128,10 +128,10 @@ describe '
p1 = FactoryBot.create(:product) p1 = FactoryBot.create(:product)
v0 = p1.variants.first v0 = p1.variants.first
v0.update_attribute(:on_demand, false) v0.update_attribute(:on_demand, false)
v1 = FactoryBot.create(:variant, product: p1, is_master: false, on_hand: 15) v1 = FactoryBot.create(:variant, product: p1, on_hand: 15)
v1.update_attribute(:on_demand, false) v1.update_attribute(:on_demand, false)
p1.variants << v1 p1.variants << v1
v2 = FactoryBot.create(:variant, product: p1, is_master: false, on_hand: 6) v2 = FactoryBot.create(:variant, product: p1, on_hand: 6)
v2.update_attribute(:on_demand, false) v2.update_attribute(:on_demand, false)
p1.variants << v2 p1.variants << v2
@@ -146,15 +146,14 @@ describe '
end end
it "displays a price input (for each variant) for each product" do it "displays a price input (for each variant) for each product" do
p1 = FactoryBot.create(:product, price: 2.0) p1 = create(:product, price: 2.0)
v1 = FactoryBot.create(:variant, product: p1, is_master: false, price: 12.75) v1 = create(:variant, product: p1, price: 12.75)
v2 = FactoryBot.create(:variant, product: p1, is_master: false, price: 2.50) v2 = create(:variant, product: p1, price: 2.50)
visit spree.admin_products_path visit spree.admin_products_path
expect(page).to have_selector "a.view-variants", count: 1 expect(page).to have_selector "a.view-variants", count: 1
all("a.view-variants").each(&:click) all("a.view-variants").each(&:click)
expect(page).to have_field "price", with: "2.0", visible: false
expect(page).to have_field "variant_price", with: "12.75" expect(page).to have_field "variant_price", with: "12.75"
expect(page).to have_field "variant_price", with: "2.5" expect(page).to have_field "variant_price", with: "2.5"
end end
@@ -162,9 +161,9 @@ describe '
it "displays a unit value field (for each variant) for each product" do it "displays a unit value field (for each variant) for each product" do
p1 = FactoryBot.create(:product, price: 2.0, variant_unit: "weight", p1 = FactoryBot.create(:product, price: 2.0, variant_unit: "weight",
variant_unit_scale: "1000") variant_unit_scale: "1000")
v1 = FactoryBot.create(:variant, product: p1, is_master: false, price: 12.75, v1 = FactoryBot.create(:variant, product: p1, price: 12.75,
unit_value: 1200, unit_description: "(small bag)", display_as: "bag") unit_value: 1200, unit_description: "(small bag)", display_as: "bag")
v2 = FactoryBot.create(:variant, product: p1, is_master: false, price: 2.50, v2 = FactoryBot.create(:variant, product: p1, price: 2.50,
unit_value: 4800, unit_description: "(large bag)", display_as: "bin") unit_value: 4800, unit_description: "(large bag)", display_as: "bin")
visit spree.admin_products_path visit spree.admin_products_path
@@ -229,9 +228,9 @@ describe '
end end
context "creating new variants" do context "creating new variants" do
let!(:product) { create(:product, variant_unit: 'weight', variant_unit_scale: 1000) }
before do before do
# Given a product without variants or a unit
p = FactoryBot.create(:product, variant_unit: 'weight', variant_unit_scale: 1000)
login_as_admin login_as_admin
visit spree.admin_products_path visit spree.admin_products_path
@@ -279,18 +278,19 @@ describe '
end end
context "handle the 'on_demand' variant case creation" do context "handle the 'on_demand' variant case creation" do
let(:v1) { create(:variant, product: product, on_hand: 4) }
let(:v2) { create(:variant, product: product, on_demand: true) }
before do before do
p = Spree::Product.first product.variants << v1
p.master.update_attribute(:on_hand, 5) product.variants << v2
p.save
v1 = FactoryBot.create(:variant, product: p, is_master: false, on_hand: 4) visit spree.admin_products_path
v2 = FactoryBot.create(:variant, product: p, is_master: false, on_demand: true) page.find('a.view-variants').click
p.variants << v1
p.variants << v2
end end
it "when variant unit value is: '120'" do it "when variant unit value is: '120'" do
within "tr#v_#{Spree::Variant.second.id}" do within "tr#v_#{v2.id}" do
page.find(".add-variant").click page.find(".add-variant").click
end end
@@ -304,7 +304,7 @@ describe '
end end
it "creating a variant with unit value is: '120g' and 'on_hand' filled" do it "creating a variant with unit value is: '120g' and 'on_hand' filled" do
within "tr#v_#{Spree::Variant.second.id}" do within "tr#v_#{v2.id}" do
page.find(".add-variant").click page.find(".add-variant").click
end end
@@ -319,7 +319,7 @@ describe '
end end
it "creating a variant with unit value is: '120g' and 'on_demand' checked" do it "creating a variant with unit value is: '120g' and 'on_demand' checked" do
within "tr#v_#{Spree::Variant.second.id}" do within "tr#v_#{v2.id}" do
page.find(".add-variant").trigger("click") page.find(".add-variant").trigger("click")
end end
@@ -401,10 +401,10 @@ describe '
end end
it "updating a product with variants" do it "updating a product with variants" do
s1 = FactoryBot.create(:supplier_enterprise) s1 = create(:supplier_enterprise)
s2 = FactoryBot.create(:supplier_enterprise) s2 = create(:supplier_enterprise)
p = FactoryBot.create(:product, supplier: s1, available_on: Date.current, variant_unit: 'volume', variant_unit_scale: 0.001, p = create(:product, supplier: s1, available_on: Date.current, variant_unit: 'volume', variant_unit_scale: 0.001,
price: 3.0, unit_value: 0.25, unit_description: '(bottle)' ) price: 3.0, unit_value: 0.25, unit_description: '(bottle)' )
v = p.variants.first v = p.variants.first
v.update_attribute(:sku, "VARIANTSKU") v.update_attribute(:sku, "VARIANTSKU")
v.update_attribute(:on_demand, false) v.update_attribute(:on_demand, false)

View File

@@ -94,7 +94,7 @@ describe "Product Import" do
potatoes = Spree::Product.find_by(name: 'Potatoes') potatoes = Spree::Product.find_by(name: 'Potatoes')
expect(potatoes.supplier).to eq enterprise expect(potatoes.supplier).to eq enterprise
expect(potatoes.on_hand).to eq 6 expect(potatoes.on_hand).to eq 6
expect(potatoes.price).to eq 6.50 expect(potatoes.variants.first.price).to eq 6.50
expect(potatoes.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(potatoes.variants.first.import_date).to be_within(1.minute).of Time.zone.now
wait_until { page.find("a.button.view").present? } wait_until { page.find("a.button.view").present? }

View File

@@ -103,17 +103,17 @@ describe '
expect(product.supplier).to eq(@supplier) expect(product.supplier).to eq(@supplier)
expect(product.variant_unit).to eq('weight') expect(product.variant_unit).to eq('weight')
expect(product.variant_unit_scale).to eq(1000) expect(product.variant_unit_scale).to eq(1000)
expect(product.unit_value).to eq(5000) expect(product.variants.first.unit_value).to eq(5000)
expect(product.unit_description).to eq("") expect(product.variants.first.unit_description).to eq("")
expect(product.variant_unit_name).to eq("") expect(product.variant_unit_name).to eq("")
expect(product.primary_taxon_id).to eq(taxon.id) expect(product.primary_taxon_id).to eq(taxon.id)
expect(product.price.to_s).to eq('19.99') expect(product.variants.first.price.to_s).to eq('19.99')
expect(product.on_hand).to eq(5) expect(product.on_hand).to eq(5)
expect(product.tax_category_id).to eq(tax_category.id) expect(product.tax_category_id).to eq(tax_category.id)
expect(product.shipping_category).to eq(shipping_category) expect(product.shipping_category).to eq(shipping_category)
expect(product.description).to eq("<p>A description...</p>") expect(product.description).to eq("<p>A description...</p>")
expect(product.group_buy).to be_falsey expect(product.group_buy).to be_falsey
expect(product.master.unit_presentation).to eq("5kg") expect(product.variants.first.unit_presentation).to eq("5kg")
end end
it "creating an on-demand product" do it "creating an on-demand product" do
@@ -527,10 +527,6 @@ describe '
page.find('a#new_image_link').click page.find('a#new_image_link').click
uri = URI.parse(current_url)
# we stay on the same url as the new image content is loaded via an ajax call
expect("#{uri.path}?#{uri.query}").to eq spree.admin_product_images_path(product, filter)
expected_cancel_link = Regexp.new(Regexp.escape(spree.admin_product_images_path(product, expected_cancel_link = Regexp.new(Regexp.escape(spree.admin_product_images_path(product,
filter))) filter)))
expect(page).to have_link('Cancel', href: expected_cancel_link) expect(page).to have_link('Cancel', href: expected_cancel_link)
@@ -565,8 +561,8 @@ describe '
it "loading edit product image page including url filter" do it "loading edit product image page including url filter" do
product = create(:simple_product, supplier: @supplier2) product = create(:simple_product, supplier: @supplier2)
image = white_logo_file image = white_logo_file
image_object = Spree::Image.create(viewable_id: product.master.id, image_object = Spree::Image.create(viewable_id: product.id,
viewable_type: 'Spree::Variant', alt: "position 1", viewable_type: 'Spree::Product', alt: "position 1",
attachment: image, position: 1) attachment: image, position: 1)
visit spree.admin_product_images_path(product, filter) visit spree.admin_product_images_path(product, filter)
@@ -586,8 +582,8 @@ describe '
it "updating a product image including url filter" do it "updating a product image including url filter" do
product = create(:simple_product, supplier: @supplier2) product = create(:simple_product, supplier: @supplier2)
image = white_logo_file image = white_logo_file
image_object = Spree::Image.create(viewable_id: product.master.id, image_object = Spree::Image.create(viewable_id: product.id,
viewable_type: 'Spree::Variant', alt: "position 1", viewable_type: 'Spree::Product', alt: "position 1",
attachment: image, position: 1) attachment: image, position: 1)
file_path = Rails.root + "spec/support/fixtures/thinking-cat.jpg" file_path = Rails.root + "spec/support/fixtures/thinking-cat.jpg"
@@ -608,7 +604,7 @@ describe '
product = create(:simple_product, supplier: @supplier2) product = create(:simple_product, supplier: @supplier2)
image = white_logo_file image = white_logo_file
Spree::Image.create(viewable_id: product.master.id, viewable_type: 'Spree::Variant', Spree::Image.create(viewable_id: product.id, viewable_type: 'Spree::Product',
alt: "position 1", attachment: image, position: 1) alt: "position 1", attachment: image, position: 1)
visit spree.admin_product_images_path(product) visit spree.admin_product_images_path(product)
@@ -623,25 +619,25 @@ describe '
it "deleting product images" do it "deleting product images" do
product = create(:simple_product, supplier: @supplier2) product = create(:simple_product, supplier: @supplier2)
image = white_logo_file image = white_logo_file
Spree::Image.create(viewable_id: product.master.id, viewable_type: 'Spree::Variant', Spree::Image.create(viewable_id: product.id, viewable_type: 'Spree::Product',
alt: "position 1", attachment: image, position: 1) alt: "position 1", attachment: image, position: 1)
visit spree.admin_product_images_path(product) visit spree.admin_product_images_path(product)
expect(page).to have_selector "table.index td img" expect(page).to have_selector "table.index td img"
expect(product.reload.images.count).to eq 1 expect(product.reload.image).to_not be_nil
accept_alert do accept_alert do
page.find('a.delete-resource').click page.find('a.delete-resource').click
end end
expect(page).to_not have_selector "table.index td img" expect(page).to_not have_selector "table.index td img"
expect(product.reload.images.count).to eq 0 expect(product.reload.image).to be_nil
end end
it "deleting product image including url filter" do it "deleting product image including url filter" do
product = create(:simple_product, supplier: @supplier2) product = create(:simple_product, supplier: @supplier2)
image = white_logo_file image = white_logo_file
Spree::Image.create(viewable_id: product.master.id, viewable_type: 'Spree::Variant', Spree::Image.create(viewable_id: product.id, viewable_type: 'Spree::Product',
alt: "position 1", attachment: image, position: 1) alt: "position 1", attachment: image, position: 1)
visit spree.admin_product_images_path(product, filter) visit spree.admin_product_images_path(product, filter)

View File

@@ -45,7 +45,7 @@ describe "Packing Reports" do
create(:line_item_with_shipment, variant: variant1, quantity: 1, order: order1) create(:line_item_with_shipment, variant: variant1, quantity: 1, order: order1)
create(:line_item_with_shipment, variant: variant2, quantity: 3, order: order1) create(:line_item_with_shipment, variant: variant2, quantity: 3, order: order1)
create(:line_item_with_shipment, variant: product2.master, quantity: 3, order: order2) create(:line_item_with_shipment, variant: product2.variants.first, quantity: 3, order: order2)
end end
describe "Pack By Customer" do describe "Pack By Customer" do

View File

@@ -568,7 +568,7 @@ describe '
let(:order_cycle) { let(:order_cycle) {
create(:simple_order_cycle, coordinator: distributor1, create(:simple_order_cycle, coordinator: distributor1,
coordinator_fees: [enterprise_fee1, enterprise_fee2], coordinator_fees: [enterprise_fee1, enterprise_fee2],
distributors: [distributor1], variants: [product1.master]) distributors: [distributor1], variants: [product1.variants.first])
} }
let!(:zone) { create(:zone_with_member) } let!(:zone) { create(:zone_with_member) }
@@ -752,7 +752,7 @@ describe '
def xero_invoice_li_row(line_item, opts = {}) def xero_invoice_li_row(line_item, opts = {})
tax_type = line_item.has_tax? ? 'GST on Income' : 'GST Free Income' tax_type = line_item.has_tax? ? 'GST on Income' : 'GST Free Income'
xero_invoice_row line_item.product.sku, line_item.product_and_full_name, xero_invoice_row line_item.variant.sku, line_item.product_and_full_name,
line_item.price.to_s, line_item.quantity.to_s, tax_type, opts line_item.price.to_s, line_item.quantity.to_s, tax_type, opts
end end

View File

@@ -60,11 +60,24 @@ describe '
# Expect variant_weight to accept 3 decimal places # Expect variant_weight to accept 3 decimal places
fill_in 'variant_weight', with: '1.234' fill_in 'variant_weight', with: '1.234'
fill_in 'unit_value_human', with: 1
click_button 'Create' click_button 'Create'
# Then the variant should have been created # Then the variant should have been created
expect(page).to have_content "Variant \"#{product.name}\" has been successfully created!" expect(page).to have_content "Variant \"#{product.name}\" has been successfully created!"
end end
it "show validation errors if present" do
product = create(:simple_product, variant_unit: "volume", variant_unit_scale: "1")
login_as_admin
visit spree.admin_product_variants_path product
click_link 'New Variant'
fill_in 'unit_value_human', with: 0
click_button 'Create'
expect(page).to have_content "Unit value must be greater than 0"
end
end end
describe "viewing product variant" do describe "viewing product variant" do

View File

@@ -57,9 +57,6 @@ describe "Darkswarm data caching", caching: true do
expect(page).to have_content property.presentation expect(page).to have_content property.presentation
end end
# Update rows which should also update the timestamp.
# The timestamp represents seconds, so waiting one second is enough.
sleep 1
taxon.update!(name: "Changed Taxon") taxon.update!(name: "Changed Taxon")
property.update!(presentation: "Changed Property") property.update!(presentation: "Changed Property")

View File

@@ -33,7 +33,7 @@ describe "spree/orders/show.html.haml" do
end end
it "shows product images" do it "shows product images" do
order.line_items.first.variant.product.images << Spree::Image.new( order.line_items.first.variant.product.image = Spree::Image.new(
attachment: fixture_file_upload("logo.png", "image/png") attachment: fixture_file_upload("logo.png", "image/png")
) )
@@ -43,7 +43,7 @@ describe "spree/orders/show.html.haml" do
end end
it "handles broken images" do it "handles broken images" do
image, = order.line_items.first.variant.product.images << Spree::Image.new( image, = order.line_items.first.variant.product.image = Spree::Image.new(
attachment: fixture_file_upload("logo.png", "image/png") attachment: fixture_file_upload("logo.png", "image/png")
) )
# This image is not "variable" and can't be resized: # This image is not "variable" and can't be resized: