mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-12 03:50:22 +00:00
Merge pull request #12562 from rioug/product-supplier-id
[Product Refactor] Move supplier to Variant
This commit is contained in:
@@ -6,13 +6,6 @@
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
|
||||
Layout/EmptyLineBetweenDefs:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Layout/EmptyLines:
|
||||
@@ -108,7 +101,7 @@ Lint/UselessMethodDefinition:
|
||||
Exclude:
|
||||
- 'app/models/spree/gateway.rb'
|
||||
|
||||
# Offense count: 24
|
||||
# Offense count: 23
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
@@ -121,7 +114,6 @@ Metrics/AbcSize:
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
- 'app/models/enterprise_group.rb'
|
||||
- 'app/models/enterprise_relationship.rb'
|
||||
- 'app/models/product_import/entry_processor.rb'
|
||||
- 'app/models/spree/ability.rb'
|
||||
- 'app/models/spree/address.rb'
|
||||
- 'app/models/spree/order/checkout.rb'
|
||||
@@ -253,7 +245,7 @@ Metrics/MethodLength:
|
||||
- 'lib/spree/localized_number.rb'
|
||||
- 'lib/tasks/sample_data/product_factory.rb'
|
||||
|
||||
# Offense count: 48
|
||||
# Offense count: 49
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ModuleLength:
|
||||
Exclude:
|
||||
@@ -283,6 +275,7 @@ Metrics/ModuleLength:
|
||||
- 'spec/controllers/payment_gateways/stripe_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/adjustments_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/payment_methods_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/variants_controller_spec.rb'
|
||||
- 'spec/lib/open_food_network/address_finder_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
|
||||
@@ -893,7 +886,7 @@ Style/ReturnNilInPredicateMethodDefinition:
|
||||
- 'app/serializers/api/admin/customer_serializer.rb'
|
||||
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
|
||||
|
||||
# Offense count: 204
|
||||
# Offense count: 207
|
||||
Style/Send:
|
||||
Exclude:
|
||||
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
|
||||
|
||||
@@ -47,7 +47,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
removeClearedValues()
|
||||
params = {
|
||||
'q[name_cont]': $scope.q.query,
|
||||
'q[supplier_id_eq]': $scope.q.producerFilter,
|
||||
'q[variants_supplier_id_eq]': $scope.q.producerFilter,
|
||||
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter,
|
||||
'q[s]': $scope.sorting,
|
||||
import_date: $scope.q.importDateFilter,
|
||||
@@ -217,7 +217,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
products: productsToSubmit
|
||||
filters:
|
||||
'q[name_cont]': $scope.q.query
|
||||
'q[supplier_id_eq]': $scope.q.producerFilter
|
||||
'q[variants_supplier_id_eq]': $scope.q.producerFilter
|
||||
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter
|
||||
'q[s]': $scope.sorting
|
||||
import_date: $scope.q.importDateFilter
|
||||
@@ -314,9 +314,6 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("name")
|
||||
filteredProduct.name = product.name
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("producer_id")
|
||||
filteredProduct.supplier_id = product.producer_id
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("price")
|
||||
filteredProduct.price = product.price
|
||||
hasUpdatableProperty = true
|
||||
@@ -379,6 +376,9 @@ filterSubmitVariant = (variant) ->
|
||||
if variant.hasOwnProperty("display_as")
|
||||
filteredVariant.display_as = variant.display_as
|
||||
hasUpdatableProperty = true
|
||||
if variant.hasOwnProperty("producer_id")
|
||||
filteredVariant.supplier_id = variant.producer_id
|
||||
hasUpdatableProperty = true
|
||||
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Used like a regular angular filter where an object is passed
|
||||
# Adds the additional special case that a value of 0 for the filter
|
||||
# acts as a bypass for that particular attribute
|
||||
|
||||
# NOTE the name doesn't reflect what the filter does, it only fiters on the variant.producer_id
|
||||
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
|
||||
return (objects, filters) ->
|
||||
Object.keys(filters).reduce (filtered, attr) ->
|
||||
filter = filters[attr]
|
||||
return filtered if !filter? || filter == 0
|
||||
return $filter('filter')(filtered, (object) ->
|
||||
object[attr] == filter
|
||||
)
|
||||
, objects
|
||||
filter = filters["producer_id"]
|
||||
|
||||
return objects if !filter? || filter == 0
|
||||
|
||||
return $filter('filter')(objects, (product) ->
|
||||
for variant in product.variants
|
||||
return true if variant["producer_id"] == filter
|
||||
false
|
||||
, true)
|
||||
|
||||
@@ -27,7 +27,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
"order_bill_address_full_name_reversed",
|
||||
"order_bill_address_full_name_with_comma",
|
||||
"order_bill_address_full_name_with_comma_reversed",
|
||||
"variant_product_supplier_name",
|
||||
"variant_supplier_name",
|
||||
"order_email",
|
||||
"order_number",
|
||||
"product_name"].join("_or_") + "_cont"
|
||||
@@ -81,7 +81,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
"q[order_shipment_state_not_eq]": "shipped",
|
||||
"q[order_completed_at_not_null]": "true",
|
||||
"q[order_distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[variant_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
|
||||
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
|
||||
@@ -105,7 +105,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
|
||||
$scope.loadOrders()
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise]).then ->
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
@@ -133,7 +133,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
return $http(
|
||||
method: 'GET'
|
||||
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}&restock_items=#{restock_items}")
|
||||
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if lineItem.order.item_count == 1
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
|
||||
@@ -167,7 +167,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.cancelOrder(order, sendEmailCancellation, restock_items).then(-> $scope.refreshData())
|
||||
else
|
||||
Promise.all(LineItems.delete(item) for item in items).then(-> $scope.refreshData())
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
else
|
||||
ofnDeleteLineItemsAlert(() ->
|
||||
Promise.all(LineItems.delete(item) for item in lineItemsToDelete).then(-> $scope.refreshData())
|
||||
@@ -199,7 +199,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.getLineItemScale = (lineItem) ->
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
lineItem.units_product.variant_unit_scale
|
||||
else
|
||||
1
|
||||
@@ -252,7 +252,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
scale = $scope.getScale(unitsProduct, unitsVariant)
|
||||
if scale
|
||||
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
|
||||
else
|
||||
else
|
||||
''
|
||||
|
||||
$scope.fulfilled = (sumOfUnitValues) ->
|
||||
|
||||
@@ -2,10 +2,10 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant.id
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
if ngModel.$dirty && viewValue
|
||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||
scope.displayDirty()
|
||||
viewValue
|
||||
|
||||
@@ -2,4 +2,8 @@ angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
|
||||
return (products, hubPermissions, hub_id) ->
|
||||
return [] if !hub_id
|
||||
return [] if !hubPermissions[hub_id]
|
||||
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)
|
||||
|
||||
return $filter('filter')(products, ((product) ->
|
||||
for variant in product.variants
|
||||
return hubPermissions[hub_id].indexOf(variant.producer_id) > -1
|
||||
), true)
|
||||
|
||||
@@ -12,11 +12,11 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http
|
||||
@add(hub_id, variant_id, vo_id)
|
||||
@dirtyVariantOverrides[hub_id][variant_id][attr] = value
|
||||
|
||||
inherit: (hub_id, variant_id, vo_id) ->
|
||||
@add(hub_id, variant_id, vo_id)
|
||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id))
|
||||
inherit: (hub_id, variant, vo_id) ->
|
||||
@add(hub_id, variant.id, vo_id)
|
||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant))
|
||||
delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
|
||||
@dirtyVariantOverrides[hub_id][variant_id] = blankVo
|
||||
@dirtyVariantOverrides[hub_id][variant.id] = blankVo
|
||||
|
||||
count: ->
|
||||
count = 0
|
||||
|
||||
@@ -13,17 +13,18 @@ angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOve
|
||||
@variantOverrides[hub.id] ||= {}
|
||||
for product in products
|
||||
for variant in product.variants
|
||||
@inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id]
|
||||
@inherit(hub.id, variant) unless @variantOverrides[hub.id][variant.id]
|
||||
|
||||
inherit: (hub_id, variant_id) ->
|
||||
inherit: (hub_id, variant) ->
|
||||
# This method is called from the trackInheritance directive, to reinstate inheritance
|
||||
@variantOverrides[hub_id][variant_id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id
|
||||
@variantOverrides[hub_id][variant.id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant.id], @newFor(hub_id, variant)
|
||||
|
||||
newFor: (hub_id, variant_id) ->
|
||||
newFor: (hub_id, variant) ->
|
||||
# These properties need to match those checked in VariantOverrideSet.deletable?
|
||||
hub_id: hub_id
|
||||
variant_id: variant_id
|
||||
variant_id: variant.id
|
||||
producer_id: variant.producer_id
|
||||
sku: null
|
||||
price: null
|
||||
count_on_hand: null
|
||||
|
||||
@@ -4,15 +4,18 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.query = ""
|
||||
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.propertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.producerPropertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.filtersActive = true
|
||||
$scope.page = 1
|
||||
$scope.per_page = 10
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.supplied_taxons = null
|
||||
$scope.supplied_properties = null
|
||||
$scope.supplied_producer_properties = null
|
||||
$scope.showFilterSidebar = false
|
||||
$scope.activeTaxons = []
|
||||
$scope.activeProperties = []
|
||||
$scope.activeProducerProperties = []
|
||||
|
||||
# Update filters after initial load of shop tab
|
||||
$timeout =>
|
||||
@@ -45,6 +48,12 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
OrderCycleResource.producerProperties params, (data)=>
|
||||
$scope.supplied_producer_properties = {}
|
||||
data.map( (property) ->
|
||||
$scope.supplied_producer_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
$scope.loadMore = ->
|
||||
if ($scope.page * $scope.per_page) <= Products.products.length
|
||||
$scope.loadMoreProducts()
|
||||
@@ -52,6 +61,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProducerProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
|
||||
$scope.loadProducts = ->
|
||||
$scope.page = 1
|
||||
@@ -66,8 +76,9 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
id: $scope.order_cycle.order_cycle_id,
|
||||
page: page || $scope.page,
|
||||
per_page: $scope.per_page,
|
||||
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_supplier_name_cont]': $scope.query,
|
||||
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_variants_supplier_name_cont]': $scope.query,
|
||||
'q[with_properties][]': $scope.activeProperties,
|
||||
'q[with_variants_supplier_properties][]': $scope.activeProducerProperties,
|
||||
'q[variants_primary_taxon_id_in_any][]': $scope.activeTaxons
|
||||
}
|
||||
|
||||
@@ -86,6 +97,12 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProperties?
|
||||
|
||||
$scope.appliedProducerPropertiesList = ->
|
||||
$scope.activeProducerProperties.map( (property_id) ->
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProducerProperties?
|
||||
|
||||
|
||||
$scope.filtersJoinWord = ->
|
||||
$sce.trustAsHtml(" <span class='join-word'>#{t('products_or')}</span> ")
|
||||
|
||||
@@ -99,6 +116,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.clearFilters = ->
|
||||
$scope.taxonSelectors.clearAll()
|
||||
$scope.propertySelectors.clearAll()
|
||||
$scope.producerPropertySelectors.clearAll()
|
||||
|
||||
$scope.refreshStaleData = ->
|
||||
# If the products template has already been loaded but the controller is being initialized
|
||||
@@ -109,7 +127,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.loadProducts()
|
||||
|
||||
$scope.filtersCount = () ->
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive()
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive() + $scope.producerPropertySelectors.totalActive()
|
||||
|
||||
$scope.toggleFilterSidebar = ->
|
||||
$scope.showFilterSidebar = !$scope.showFilterSidebar
|
||||
|
||||
@@ -18,4 +18,11 @@ angular.module('Darkswarm').factory 'OrderCycleResource', ($resource) ->
|
||||
url: '/api/v0/order_cycles/:id/properties.json'
|
||||
params:
|
||||
id: '@id'
|
||||
'producerProperties':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/v0/order_cycles/:id/producer_properties.json'
|
||||
params:
|
||||
id: '@id'
|
||||
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ angular.module('Darkswarm').factory 'Products', (OrderCycleResource, OrderCycle,
|
||||
|
||||
dereference: ->
|
||||
for product in @fetched_products
|
||||
product.supplier = Shopfront.producers_by_id[product.supplier.id]
|
||||
product.supplier = Shopfront.producers_by_id[product.variants[0].supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
product.properties = angular.copy(product.properties_with_values)
|
||||
|
||||
@@ -189,10 +189,7 @@ module Admin
|
||||
.visible_enterprises
|
||||
|
||||
if enterprises.present?
|
||||
enterprises.includes(
|
||||
supplied_products:
|
||||
[:supplier, :variants, :image]
|
||||
)
|
||||
enterprises.includes(supplied_products: [:variants, :image])
|
||||
end
|
||||
when :index
|
||||
if spree_current_user.admin?
|
||||
|
||||
@@ -149,7 +149,7 @@ module Admin
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(supplier_id_in: @producer_id) if @producer_id.present?
|
||||
query.merge!(variants_supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
@@ -163,13 +163,13 @@ module Admin
|
||||
def product_query_includes
|
||||
[
|
||||
:image,
|
||||
:supplier,
|
||||
{ variants: [
|
||||
:default_price,
|
||||
:primary_taxon,
|
||||
:product,
|
||||
:stock_items,
|
||||
:tax_category,
|
||||
:supplier,
|
||||
] },
|
||||
]
|
||||
end
|
||||
|
||||
@@ -7,9 +7,11 @@ module Api
|
||||
include ApiActionCaching
|
||||
|
||||
skip_authorization_check
|
||||
skip_before_action :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
|
||||
skip_before_action :authenticate_user, :ensure_api_key, only: [
|
||||
:taxons, :properties, :producer_properties
|
||||
]
|
||||
|
||||
caches_action :taxons, :properties,
|
||||
caches_action :taxons, :properties, :producer_properties,
|
||||
expires_in: CacheService::FILTERS_EXPIRY,
|
||||
cache_path: proc { |controller| controller.request.url }
|
||||
|
||||
@@ -41,7 +43,13 @@ module Api
|
||||
|
||||
def properties
|
||||
render plain: ActiveModel::ArraySerializer.new(
|
||||
product_properties | producer_properties, each_serializer: Api::PropertySerializer
|
||||
product_properties, each_serializer: Api::PropertySerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
render plain: ActiveModel::ArraySerializer.new(
|
||||
load_producer_properties, each_serializer: Api::PropertySerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
@@ -58,7 +66,7 @@ module Api
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
def load_producer_properties
|
||||
producers = Enterprise.
|
||||
joins(:supplied_products).
|
||||
where(spree_products: { id: distributed_products })
|
||||
@@ -86,8 +94,9 @@ module Api
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle,
|
||||
customer).products_relation
|
||||
OrderCycles::DistributedProductsService.new(
|
||||
distributor, order_cycle, customer
|
||||
).products_supplier_relation.pluck(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,7 +54,7 @@ module Api
|
||||
end
|
||||
|
||||
def overridable
|
||||
@products = product_finder.paged_products_for_producers
|
||||
@products = product_finder.products_for_producers
|
||||
|
||||
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
|
||||
end
|
||||
|
||||
@@ -8,6 +8,7 @@ module Spree
|
||||
before_action :setup_property, only: [:index]
|
||||
|
||||
def index
|
||||
@supplier = @product.variants.first.supplier
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
end
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ module Spree
|
||||
include EnterprisesHelper
|
||||
|
||||
before_action :load_data
|
||||
before_action :load_producers, only: [:index, :new]
|
||||
before_action :load_form_data, only: [:index, :new, :create, :edit, :update]
|
||||
before_action :load_spree_api_key, only: [:index, :variant_overrides]
|
||||
before_action :strip_new_properties, only: [:create, :update]
|
||||
@@ -41,6 +42,7 @@ module Spree
|
||||
flash[:success] = flash_message_for(@object, :successfully_created)
|
||||
redirect_after_save
|
||||
else
|
||||
load_producers
|
||||
# Re-fill the form with deleted params on product
|
||||
@on_hand = request.params[:product][:on_hand]
|
||||
@on_demand = request.params[:product][:on_demand]
|
||||
@@ -52,14 +54,9 @@ module Spree
|
||||
def update
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
|
||||
original_supplier_id = @product.supplier_id
|
||||
delete_stock_params_and_set_after do
|
||||
params[:product] ||= {} if params[:clear_product_properties]
|
||||
if @object.update(permitted_resource_params)
|
||||
if original_supplier_id != @product.supplier_id
|
||||
ExchangeVariantDeleter.new.delete(@product)
|
||||
end
|
||||
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
end
|
||||
redirect_to spree.edit_admin_product_url(@object, @url_filters)
|
||||
@@ -157,12 +154,15 @@ module Spree
|
||||
end
|
||||
|
||||
def load_form_data
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
@import_dates = product_import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def load_producers
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
end
|
||||
|
||||
def product_import_dates
|
||||
options = [{ id: '0', name: '' }]
|
||||
product_import_dates_query.collect(&:import_date).
|
||||
@@ -173,12 +173,10 @@ module Spree
|
||||
|
||||
def product_import_dates_query
|
||||
Spree::Variant.
|
||||
select('DISTINCT spree_variants.import_date').
|
||||
joins(:product).
|
||||
where(spree_products: { supplier_id: editable_enterprises.collect(&:id) }).
|
||||
select('import_date').distinct.
|
||||
where(supplier_id: editable_enterprises.collect(&:id)).
|
||||
where.not(spree_variants: { import_date: nil }).
|
||||
where(spree_variants: { deleted_at: nil }).
|
||||
order('spree_variants.import_date DESC')
|
||||
order('import_date DESC')
|
||||
end
|
||||
|
||||
def strip_new_properties
|
||||
|
||||
@@ -46,7 +46,13 @@ module Spree
|
||||
def update
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
|
||||
original_supplier_id = @object.supplier_id
|
||||
|
||||
if @object.update(permitted_resource_params)
|
||||
if original_supplier_id != @object.supplier_id
|
||||
ExchangeVariantDeleter.new.delete(@object)
|
||||
end
|
||||
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters)
|
||||
else
|
||||
@@ -113,6 +119,8 @@ module Spree
|
||||
private
|
||||
|
||||
def load_data
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@tax_categories = TaxCategory.order(:name)
|
||||
@shipping_categories = ShippingCategory.order(:name)
|
||||
end
|
||||
|
||||
@@ -60,11 +60,12 @@ class ProducerMailer < ApplicationMailer
|
||||
|
||||
def line_items_from(order_cycle, producer)
|
||||
@line_items ||= Spree::LineItem.
|
||||
includes(variant: [:product]).
|
||||
includes(variant: :product).
|
||||
joins(variant: :product).
|
||||
from_order_cycle(order_cycle).
|
||||
sorted_by_name_and_unit_value.
|
||||
merge(Spree::Product.with_deleted.in_supplier(producer)).
|
||||
merge(Spree::Order.by_state(["complete", "resumed"]))
|
||||
merge(Spree::Variant.with_deleted.where(supplier: producer)).
|
||||
merge(Spree::Order.by_state(["complete", "resumed"])).
|
||||
sorted_by_name_and_unit_value
|
||||
end
|
||||
|
||||
def total_from_line_items(line_items)
|
||||
@@ -81,7 +82,7 @@ class ProducerMailer < ApplicationMailer
|
||||
line_items.map do |line_item|
|
||||
{
|
||||
sku: line_item.variant.sku,
|
||||
supplier_name: line_item.product.supplier.name,
|
||||
supplier_name: line_item.variant.supplier.name,
|
||||
product_and_full_name: line_item.product_and_full_name,
|
||||
quantity: line_item.quantity,
|
||||
first_name: line_item.order.billing_address.first_name,
|
||||
|
||||
@@ -39,13 +39,13 @@ class Enterprise < ApplicationRecord
|
||||
class_name: 'EnterpriseGroup'
|
||||
has_many :producer_properties, foreign_key: 'producer_id', dependent: :destroy
|
||||
has_many :properties, through: :producer_properties
|
||||
has_many :supplied_products, class_name: 'Spree::Product',
|
||||
foreign_key: 'supplier_id',
|
||||
dependent: :destroy
|
||||
has_many :supplied_variants, through: :supplied_products, source: :variants
|
||||
has_many :supplied_variants,
|
||||
class_name: 'Spree::Variant', foreign_key: 'supplier_id', dependent: :destroy
|
||||
has_many :supplied_products, through: :supplied_variants, source: :product
|
||||
has_many :distributed_orders, class_name: 'Spree::Order',
|
||||
foreign_key: 'distributor_id',
|
||||
dependent: :restrict_with_exception
|
||||
|
||||
belongs_to :address, class_name: 'Spree::Address'
|
||||
belongs_to :business_address, optional: true, class_name: 'Spree::Address', dependent: :destroy
|
||||
has_many :enterprise_fees, dependent: :restrict_with_exception
|
||||
@@ -167,7 +167,7 @@ class Enterprise < ApplicationRecord
|
||||
scope :is_distributor, -> { where.not(sells: 'none') }
|
||||
scope :is_hub, -> { where(sells: 'any') }
|
||||
scope :supplying_variant_in, lambda { |variants|
|
||||
joins(supplied_products: :variants).
|
||||
joins(:supplied_variants).
|
||||
where(spree_variants: { id: variants }).
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
@@ -205,14 +205,14 @@ class Enterprise < ApplicationRecord
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
|
||||
scope :distributing_products, lambda { |product_ids|
|
||||
scope :distributing_variants, lambda { |variants_ids|
|
||||
exchanges = joins("
|
||||
INNER JOIN exchanges
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = 'f')
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = false)
|
||||
").
|
||||
joins('INNER JOIN exchange_variants ON (exchange_variants.exchange_id = exchanges.id)').
|
||||
joins('INNER JOIN spree_variants ON (spree_variants.id = exchange_variants.variant_id)').
|
||||
where(spree_variants: { product_id: product_ids }).select('DISTINCT enterprises.id')
|
||||
where(spree_variants: { id: variants_ids }).select('DISTINCT enterprises.id')
|
||||
|
||||
where(id: exchanges)
|
||||
}
|
||||
@@ -598,7 +598,7 @@ class Enterprise < ApplicationRecord
|
||||
# Touch distributors without them touching their distributors.
|
||||
# We avoid an infinite loop and don't need to touch the whole distributor tree.
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(supplied_products.select(:id)).
|
||||
Enterprise.distributing_variants(supplied_variants.select(:id)).
|
||||
where.not(enterprises: { id: }).
|
||||
update_all(updated_at: Time.zone.now)
|
||||
end
|
||||
|
||||
@@ -108,6 +108,6 @@ class EnterpriseRelationship < ApplicationRecord
|
||||
|
||||
def child_variant_overrides
|
||||
VariantOverride.unscoped.for_hubs(child)
|
||||
.joins(variant: :product).where(spree_products: { supplier_id: parent })
|
||||
.joins(:variant).where(spree_variants: { supplier_id: parent } )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,10 +54,7 @@ module ProductImport
|
||||
if settings.importing_into_inventory?
|
||||
VariantOverride.for_hubs([enterprise_id]).count
|
||||
else
|
||||
Spree::Variant.
|
||||
joins(:product).
|
||||
where(spree_products: { supplier_id: enterprise_id }).
|
||||
count
|
||||
Spree::Variant.where(supplier_id: enterprise_id).count
|
||||
end
|
||||
|
||||
@enterprise_products[enterprise_id] = products_count
|
||||
@@ -169,7 +166,6 @@ module ProductImport
|
||||
product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
product.supplier_id = entry.producer_id
|
||||
|
||||
if product.save
|
||||
ensure_variant_updated(product, entry)
|
||||
@@ -228,10 +224,13 @@ module ProductImport
|
||||
# Ensure attributes are correctly copied to a new product's variant
|
||||
variant = product.variants.first
|
||||
variant.display_name = entry.display_name if entry.display_name
|
||||
variant.import_date = @import_time
|
||||
variant.supplier_id = entry.producer_id
|
||||
variant.save
|
||||
|
||||
# on_demand and on_hand require a stock level, which is created after the variant is created
|
||||
variant.on_demand = entry.on_demand if entry.on_demand
|
||||
variant.on_hand = entry.on_hand if entry.on_hand
|
||||
variant.import_date = @import_time
|
||||
variant.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,6 +73,7 @@ module ProductImport
|
||||
# Variant needs a product. Product needs to be assigned first in order for
|
||||
# delegate to work. name= will fail otherwise.
|
||||
new_variant = Spree::Variant.new(product_id:, **variant_attributes)
|
||||
new_variant.supplier_id = entry.producer_id
|
||||
|
||||
new_variant.save
|
||||
if new_variant.persisted?
|
||||
@@ -287,9 +288,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def inventory_validation(entry)
|
||||
products = Spree::Product.where(supplier_id: entry.producer_id,
|
||||
name: entry.name,
|
||||
deleted_at: nil)
|
||||
products = Spree::Product.in_supplier(entry.producer_id).where(name: entry.name)
|
||||
|
||||
if products.empty?
|
||||
mark_as_invalid(entry, attribute: 'name',
|
||||
@@ -358,9 +357,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def product_validation(entry)
|
||||
products = Spree::Product.where(supplier_id: entry.enterprise_id,
|
||||
name: entry.name,
|
||||
deleted_at: nil)
|
||||
products = Spree::Product.in_supplier(entry.enterprise_id).where(name: entry.name)
|
||||
|
||||
if products.empty?
|
||||
mark_as_new_product(entry)
|
||||
@@ -384,7 +381,6 @@ module ProductImport
|
||||
new_product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
new_product.supplier_id = entry.producer_id
|
||||
entry.on_hand = 0 if entry.on_hand.nil?
|
||||
|
||||
if new_product.valid?
|
||||
|
||||
@@ -189,7 +189,9 @@ module Spree
|
||||
:seo, :group_buy_options,
|
||||
:bulk_update, :clone, :delete,
|
||||
:destroy], Spree::Product do |product|
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? product.supplier
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include?(
|
||||
product.variants.first.supplier
|
||||
)
|
||||
end
|
||||
|
||||
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone], :products_v3
|
||||
@@ -198,11 +200,11 @@ module Spree
|
||||
can [:admin, :index, :read, :edit,
|
||||
:update, :search, :delete, :destroy], Spree::Variant do |variant|
|
||||
OpenFoodNetwork::Permissions.new(user).
|
||||
managed_product_enterprises.include? variant.product.supplier
|
||||
managed_product_enterprises.include? variant.supplier
|
||||
end
|
||||
|
||||
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
|
||||
next false unless vo.hub.present? && vo.variant&.product&.supplier.present?
|
||||
next false unless vo.hub.present? && vo.variant&.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
@@ -210,14 +212,14 @@ module Spree
|
||||
|
||||
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_producers.
|
||||
include? vo.variant.product.supplier
|
||||
include? vo.variant.supplier
|
||||
|
||||
hub_auth && producer_auth
|
||||
end
|
||||
|
||||
can [:admin, :create, :update], InventoryItem do |ii|
|
||||
next false unless ii.enterprise.present? &&
|
||||
ii.variant&.product&.supplier.present?
|
||||
ii.variant&.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
@@ -225,7 +227,7 @@ module Spree
|
||||
|
||||
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_producers.
|
||||
include? ii.variant.product.supplier
|
||||
include? ii.variant.supplier
|
||||
|
||||
hub_auth && producer_auth
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ module Spree
|
||||
|
||||
belongs_to :variant, -> { with_deleted }, class_name: "Spree::Variant"
|
||||
has_one :product, through: :variant
|
||||
has_one :supplier, through: :product
|
||||
has_one :supplier, through: :variant
|
||||
belongs_to :tax_category, class_name: "Spree::TaxCategory", optional: true
|
||||
|
||||
has_many :adjustments, as: :adjustable, dependent: :destroy
|
||||
@@ -85,13 +85,11 @@ module Spree
|
||||
where(order_cycles: { id: order_cycle })
|
||||
}
|
||||
|
||||
# Here we are simply joining the line item to its variant and product
|
||||
# We dont use joins here to avoid the default scopes,
|
||||
# and with that, include deleted variants and deleted products
|
||||
# Here we are simply joining the line item to its variant
|
||||
# We dont use joins here to avoid the default scopes, and with that, include deleted variants
|
||||
scope :supplied_by_any, lambda { |enterprises|
|
||||
product_ids = Spree::Product.unscoped.where(supplier_id: enterprises).select(:id)
|
||||
variant_ids = Spree::Variant.unscoped.where(product_id: product_ids).select(:id)
|
||||
where(spree_line_items: { variant_id: variant_ids })
|
||||
variant_ids = Spree::Variant.unscoped.where(supplier: enterprises).select(:id)
|
||||
where(variant_id: variant_ids)
|
||||
}
|
||||
|
||||
scope :with_tax, -> {
|
||||
|
||||
@@ -5,19 +5,15 @@ require 'open_food_network/property_merge'
|
||||
# PRODUCTS
|
||||
# Products represent an entity for sale in a store.
|
||||
# Products can have variations, called variants
|
||||
# Products properties include description, permalink, availability,
|
||||
# shipping category, etc. that do not change by variant.
|
||||
#
|
||||
# MASTER VARIANT
|
||||
# Every product has one master variant, which stores master price and sku, size and weight, etc.
|
||||
# Price, SKU, size, weight, etc. are all delegated to the master variant.
|
||||
# Contains on_hand inventory levels only when there are no variants for the product.
|
||||
# Products properties include description, meta_keywork, etc. that do not change by variant.
|
||||
#
|
||||
# VARIANTS
|
||||
# All variants can access the product properties directly (via reverse delegation).
|
||||
# Every product has at least one variant (standard variant), which stores price and availability,
|
||||
# shipping category, sku, size and weight, etc.
|
||||
# All variants can access the product name, description, and meta_keyword directly (via reverse
|
||||
# delegation).
|
||||
# Inventory units are tied to Variant.
|
||||
# The master variant can have inventory units, but not option values.
|
||||
# All other variants have option values and may have inventory units.
|
||||
# All variants have option values and may have inventory units.
|
||||
# Sum of on_hand each variant's inventory level determine "on_hand" level for the product.
|
||||
#
|
||||
module Spree
|
||||
@@ -26,15 +22,14 @@ module Spree
|
||||
include LogDestroyPerformer
|
||||
|
||||
self.belongs_to_required_by_default = false
|
||||
self.ignored_columns += [:supplier_id]
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :supplier_id, :meta_keywords, :sku
|
||||
searchable_associations :supplier, :properties, :variants
|
||||
searchable_attributes :meta_keywords, :sku
|
||||
searchable_associations :properties, :variants
|
||||
searchable_scopes :active, :with_properties
|
||||
|
||||
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
|
||||
|
||||
has_one :image, class_name: "Spree::Image", as: :viewable, dependent: :destroy
|
||||
|
||||
has_many :product_properties, dependent: :destroy
|
||||
@@ -45,7 +40,6 @@ module Spree
|
||||
has_many :prices, -> { order('spree_variants.id, currency') }, through: :variants
|
||||
|
||||
has_many :stock_items, through: :variants
|
||||
has_many :supplier_properties, through: :supplier, source: :properties
|
||||
has_many :variant_images, -> { order(:position) }, source: :images,
|
||||
through: :variants
|
||||
|
||||
@@ -68,28 +62,27 @@ module Spree
|
||||
accepts_nested_attributes_for :image
|
||||
accepts_nested_attributes_for :product_properties,
|
||||
allow_destroy: true,
|
||||
reject_if: lambda { |pp| pp[:property_name].blank? }
|
||||
reject_if: ->(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, :tax_category_id,
|
||||
:shipping_category_id, :primary_taxon_id
|
||||
:shipping_category_id, :primary_taxon_id, :supplier_id
|
||||
|
||||
after_create :ensure_standard_variant
|
||||
after_update :touch_supplier, if: :saved_change_to_primary_taxon_id?
|
||||
around_destroy :destruction
|
||||
after_save :update_units
|
||||
after_touch :touch_supplier
|
||||
|
||||
# -- Scopes
|
||||
scope :with_properties, ->(*property_ids) {
|
||||
left_outer_joins(:product_properties).
|
||||
left_outer_joins(:supplier_properties).
|
||||
where(inherits_properties: true).
|
||||
where(producer_properties: { property_id: property_ids }).
|
||||
or(
|
||||
where(spree_product_properties: { property_id: property_ids })
|
||||
)
|
||||
where(spree_product_properties: { property_id: property_ids })
|
||||
}
|
||||
|
||||
scope :with_order_cycles_outer, -> {
|
||||
scope :with_order_cycles_outer, lambda {
|
||||
joins("
|
||||
LEFT OUTER JOIN spree_variants AS o_spree_variants
|
||||
ON (o_spree_variants.product_id = spree_products.id)").
|
||||
@@ -111,9 +104,7 @@ module Spree
|
||||
where(import_date: import_date.all_day))
|
||||
}
|
||||
|
||||
scope :with_order_cycles_inner, -> {
|
||||
joins(variants: { exchanges: :order_cycle })
|
||||
}
|
||||
scope :with_order_cycles_inner, -> { joins(variants: { exchanges: :order_cycle }) }
|
||||
|
||||
scope :visible_for, lambda { |enterprise|
|
||||
joins('
|
||||
@@ -126,8 +117,9 @@ module Spree
|
||||
distinct
|
||||
}
|
||||
|
||||
# -- Scopes
|
||||
scope :in_supplier, lambda { |supplier| where(supplier_id: supplier) }
|
||||
scope :in_supplier, lambda { |supplier|
|
||||
distinct.joins(:variants).where(spree_variants: { supplier: })
|
||||
}
|
||||
|
||||
# Products distributed via the given distributor through an OC
|
||||
scope :in_distributor, lambda { |distributor|
|
||||
@@ -144,18 +136,6 @@ module Spree
|
||||
distinct
|
||||
}
|
||||
|
||||
# Products supplied by a given enterprise or distributed via that enterprise through an OC
|
||||
scope :in_supplier_or_distributor, lambda { |enterprise|
|
||||
enterprise = enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i
|
||||
|
||||
with_order_cycles_outer.
|
||||
where("
|
||||
spree_products.supplier_id = ?
|
||||
OR (o_exchanges.incoming = ? AND o_exchanges.receiver_id = ?)
|
||||
", enterprise, false, enterprise).
|
||||
select('distinct spree_products.*')
|
||||
}
|
||||
|
||||
# Products distributed by the given order cycle
|
||||
scope :in_order_cycle, lambda { |order_cycle|
|
||||
with_order_cycles_inner.
|
||||
@@ -170,27 +150,17 @@ module Spree
|
||||
where.not(order_cycles: { id: nil })
|
||||
}
|
||||
|
||||
scope :by_producer, -> { joins(:supplier).order('enterprises.name') }
|
||||
scope :by_name, -> { order('name') }
|
||||
scope :by_producer, -> { joins(variants: :supplier).order('enterprises.name') }
|
||||
scope :by_name, -> { order('spree_products.name') }
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
where(nil)
|
||||
else
|
||||
where(supplier_id: user.enterprises.select("enterprises.id"))
|
||||
in_supplier(user.enterprises)
|
||||
end
|
||||
}
|
||||
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where('1=0') if enterprise.blank?
|
||||
|
||||
permitted_producer_ids = EnterpriseRelationship.joins(:parent).permitting(enterprise.id)
|
||||
.with_permission(:add_to_order_cycle)
|
||||
.where(enterprises: { is_primary_producer: true })
|
||||
.pluck(:parent_id)
|
||||
where(spree_products: { supplier_id: [enterprise.id] | permitted_producer_ids })
|
||||
}
|
||||
|
||||
scope :active, lambda { where(spree_products: { deleted_at: nil }) }
|
||||
|
||||
def self.group_by_products_id
|
||||
@@ -236,7 +206,10 @@ module Spree
|
||||
ps = product_properties.all
|
||||
|
||||
if inherits_properties
|
||||
ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier.producer_properties)
|
||||
# NOTE: Set the supplier as the first variant supplier. If variants have different supplier,
|
||||
# result might not be correct
|
||||
supplier = variants.first.supplier
|
||||
ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier&.producer_properties || [])
|
||||
end
|
||||
|
||||
ps.
|
||||
@@ -263,8 +236,6 @@ module Spree
|
||||
|
||||
def destruction
|
||||
transaction do
|
||||
touch_distributors
|
||||
|
||||
ExchangeVariant.
|
||||
where(exchange_variants: { variant_id: variants.with_deleted.
|
||||
select(:id) }).destroy_all
|
||||
@@ -285,6 +256,7 @@ module Spree
|
||||
variant.tax_category_id = tax_category_id
|
||||
variant.shipping_category_id = shipping_category_id
|
||||
variant.primary_taxon_id = primary_taxon_id
|
||||
variant.supplier_id = supplier_id
|
||||
variants << variant
|
||||
end
|
||||
|
||||
@@ -319,11 +291,26 @@ module Spree
|
||||
def update_units
|
||||
return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
|
||||
|
||||
variants.each(&:update_units)
|
||||
variants.each do |v|
|
||||
if v.persisted?
|
||||
v.update_units
|
||||
else
|
||||
v.assign_units
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(id).each(&:touch)
|
||||
def touch_supplier
|
||||
return if variants.empty?
|
||||
|
||||
# Assume the product supplier is the supplier of the first variant
|
||||
# Will breack if product has mutiple variants with different supplier
|
||||
first_variant = variants.first
|
||||
|
||||
# The variant is invalid if no supplier is present, but this method can be triggered when
|
||||
# importing product. In this scenario the variant has not been updated with the supplier yet
|
||||
# hence the check.
|
||||
first_variant.supplier.touch if first_variant.supplier.present?
|
||||
end
|
||||
|
||||
def validate_image
|
||||
|
||||
@@ -6,6 +6,8 @@ module Spree
|
||||
has_many :products, through: :product_properties
|
||||
has_many :producer_properties, dependent: :destroy
|
||||
|
||||
after_touch :touch_producer_properties
|
||||
|
||||
validates :name, :presentation, presence: true
|
||||
|
||||
scope :sorted, -> { order(:name) }
|
||||
@@ -13,5 +15,11 @@ module Spree
|
||||
def property
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def touch_producer_properties
|
||||
producer_properties.each(&:touch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ module Spree
|
||||
taxons = {}
|
||||
|
||||
Spree::Taxon.
|
||||
joins(products: :supplier).
|
||||
joins(variants: :supplier).
|
||||
select('spree_taxons.*, enterprises.id AS enterprise_id').
|
||||
each do |t|
|
||||
taxons[t.enterprise_id.to_i] ||= Set.new
|
||||
|
||||
@@ -13,8 +13,8 @@ module Spree
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :sku, :display_as, :display_name, :primary_taxon_id
|
||||
searchable_associations :product, :default_price, :primary_taxon
|
||||
searchable_attributes :sku, :display_as, :display_name, :primary_taxon_id, :supplier_id
|
||||
searchable_associations :product, :default_price, :primary_taxon, :supplier
|
||||
searchable_scopes :active, :deleted
|
||||
|
||||
NAME_FIELDS = ["display_name", "display_as", "weight", "unit_value", "unit_description"].freeze
|
||||
@@ -23,12 +23,15 @@ module Spree
|
||||
meta_keywords
|
||||
variants_display_as
|
||||
variants_display_name
|
||||
supplier_name).join('_or_')}_cont".freeze
|
||||
variants_supplier_name).join('_or_')}_cont".freeze
|
||||
|
||||
belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product'
|
||||
belongs_to :product, -> {
|
||||
with_deleted
|
||||
}, touch: true, class_name: 'Spree::Product', optional: false
|
||||
belongs_to :tax_category, class_name: 'Spree::TaxCategory'
|
||||
belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', optional: false
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true, optional: false
|
||||
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
|
||||
|
||||
delegate :name, :name=, :description, :description=, :meta_keywords, to: :product
|
||||
|
||||
@@ -58,6 +61,7 @@ module Spree
|
||||
has_many :variant_overrides, dependent: :destroy
|
||||
has_many :inventory_items, dependent: :destroy
|
||||
has_many :semantic_links, dependent: :delete_all
|
||||
has_many :supplier_properties, through: :supplier, source: :properties
|
||||
|
||||
localize_number :price, :weight
|
||||
|
||||
@@ -94,7 +98,7 @@ module Spree
|
||||
after_save :save_default_price
|
||||
|
||||
# default variant scope only lists non-deleted variants
|
||||
scope :deleted, lambda { where.not(deleted_at: nil) }
|
||||
scope :deleted, -> { where.not(deleted_at: nil) }
|
||||
|
||||
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
|
||||
|
||||
@@ -139,11 +143,9 @@ module Spree
|
||||
.where("o_inventory_items.id IS NULL OR o_inventory_items.visible = (?)", true)
|
||||
}
|
||||
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where("1=0") if enterprise.blank?
|
||||
|
||||
joins(:product).
|
||||
where(spree_products: { id: Spree::Product.stockable_by(enterprise).select(:id) })
|
||||
scope :with_properties, lambda { |property_ids|
|
||||
left_outer_joins(:supplier_properties).
|
||||
where(producer_properties: { property_id: property_ids })
|
||||
}
|
||||
|
||||
# Define sope as class method to allow chaining with other scopes filtering id.
|
||||
@@ -250,8 +252,16 @@ module Spree
|
||||
end
|
||||
|
||||
def destruction
|
||||
exchange_variants.reload.destroy_all
|
||||
yield
|
||||
transaction do
|
||||
# Even tough Enterprise will touch associated variant distributors when touched,
|
||||
# the variant will be removed from the exchange by the time it's triggered,
|
||||
# so it won't be able to find the deleted variant's distributors.
|
||||
# This why we do it here
|
||||
touch_distributors
|
||||
|
||||
exchange_variants.reload.destroy_all
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_unit_value
|
||||
@@ -268,5 +278,9 @@ module Spree
|
||||
def convert_variant_weight_to_decimal
|
||||
self.weight = weight.to_d
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_variants(id).each(&:touch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,16 +31,17 @@ class ProductScopeQuery
|
||||
product_scope.find(@params[:product_id])
|
||||
end
|
||||
|
||||
def paged_products_for_producers
|
||||
def products_for_producers
|
||||
producer_ids = OpenFoodNetwork::Permissions.new(@user).
|
||||
variant_override_producers.by_name.select('enterprises.id')
|
||||
|
||||
# Use `order("enterprises.name")` instead of `by_producer scope`, the scope adds a join
|
||||
# on variants which messes our query
|
||||
Spree::Product.where(nil).
|
||||
merge(product_scope).
|
||||
includes(variants: [:product, :default_price, :stock_items]).
|
||||
where(supplier_id: producer_ids).
|
||||
by_producer.by_name.
|
||||
ransack(@params[:q]).result
|
||||
includes(variants: [:product, :default_price, :stock_items, :supplier]).
|
||||
where(variants: { supplier_id: producer_ids }).
|
||||
ransack(@params[:q]).result(distinct: true)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
|
||||
@@ -108,7 +108,7 @@ class ProductsReflex < ApplicationReflex
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(supplier_id_in: @producer_id) if @producer_id.present?
|
||||
query.merge!(variants_supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ module Api
|
||||
attributes :name, :supplier_name, :image_url, :variants
|
||||
|
||||
def supplier_name
|
||||
object.supplier&.name
|
||||
object.variants.first.supplier&.name
|
||||
end
|
||||
|
||||
def image_url
|
||||
|
||||
@@ -9,7 +9,7 @@ module Api
|
||||
has_one :order, serializer: Api::Admin::IdSerializer
|
||||
|
||||
def supplier
|
||||
{ id: object.product.supplier_id }
|
||||
{ id: object.supplier.id }
|
||||
end
|
||||
|
||||
def units_product
|
||||
|
||||
@@ -7,8 +7,6 @@ module Api
|
||||
:inherits_properties, :on_hand, :price, :import_date, :image_url,
|
||||
:thumb_url, :variants
|
||||
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
|
||||
def variants
|
||||
ActiveModel::ArraySerializer.new(
|
||||
object.variants,
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
module Api
|
||||
module Admin
|
||||
class ProductSimpleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :producer_id
|
||||
attributes :id, :name
|
||||
|
||||
has_many :variants, key: :variants, serializer: Api::Admin::VariantSimpleSerializer
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ module Api
|
||||
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
|
||||
|
||||
has_one :primary_taxon, key: :category_id, embed: :id
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
|
||||
def name
|
||||
if object.full_name.present?
|
||||
@@ -31,7 +32,7 @@ module Api
|
||||
end
|
||||
|
||||
def producer_name
|
||||
object.product.supplier.name
|
||||
object.supplier.name
|
||||
end
|
||||
|
||||
def image
|
||||
|
||||
@@ -6,7 +6,7 @@ module Api
|
||||
attributes :id, :name, :import_date,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display,
|
||||
:price, :on_demand, :on_hand
|
||||
:price, :on_demand, :on_hand, :producer_id
|
||||
|
||||
has_many :variant_overrides
|
||||
|
||||
@@ -27,6 +27,10 @@ module Api
|
||||
def price
|
||||
object.price.nil? ? 0.to_f : object.price
|
||||
end
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,6 @@ class Api::ProductSerializer < ActiveModel::Serializer
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
|
||||
has_one :image, serializer: Api::ImageSerializer
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
# return an unformatted descripton
|
||||
def description
|
||||
|
||||
@@ -9,6 +9,8 @@ class Api::VariantSerializer < ActiveModel::Serializer
|
||||
:tag_list, :thumb_url,
|
||||
:unit_price_price, :unit_price_unit
|
||||
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
delegate :price, to: :object
|
||||
|
||||
def fees
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
class Invoice
|
||||
class ProductSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
has_one :supplier, serializer: Invoice::EnterpriseSerializer
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,5 +4,6 @@ class Invoice
|
||||
class VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :display_name, :options_text
|
||||
has_one :product, serializer: Invoice::ProductSerializer
|
||||
has_one :supplier, serializer: Invoice::EnterpriseSerializer
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ class ExchangeProductsRenderer
|
||||
end
|
||||
|
||||
def supplied_products(enterprises_query_matcher)
|
||||
products_relation = Spree::Product.where(supplier_id: enterprises_query_matcher).order(:name)
|
||||
products_relation = Spree::Product.in_supplier(enterprises_query_matcher).order(:name)
|
||||
|
||||
filter_visible(products_relation)
|
||||
end
|
||||
@@ -95,7 +95,7 @@ class ExchangeProductsRenderer
|
||||
return enterprises if enterprises.empty?
|
||||
|
||||
enterprises.includes(
|
||||
supplied_products: [:supplier, :variants, :image]
|
||||
supplied_products: [{ variants: :supplier }, :image]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ExchangeVariantDeleter
|
||||
def delete(product)
|
||||
ExchangeVariant.
|
||||
where(variant_id: product.variants.select(:id)).
|
||||
delete_all
|
||||
def delete(variant)
|
||||
ExchangeVariant.where(variant_id: variant.id).delete_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,11 +11,8 @@ module OrderCycles
|
||||
@customer = customer
|
||||
end
|
||||
|
||||
def products_relation
|
||||
Spree::Product.where(id: stocked_products).group("spree_products.id")
|
||||
end
|
||||
|
||||
# Joins on the first product variant to allow us to filter product by taxon. This is so
|
||||
# TODO refactor products_taxons_relation and products_supplier_relation
|
||||
# Joins on the first product variant to allow us to filter product by taxon. # This is so
|
||||
# enterprise can display product sorted by category in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the category sorting won't work properly if there are multiple variant with different
|
||||
@@ -31,6 +28,28 @@ module OrderCycles
|
||||
group("spree_products.id, first_variant.primary_taxon_id")
|
||||
end
|
||||
|
||||
# Joins on the first product variant to allow us to filter product by supplier. This is so
|
||||
# enterprise can display product sorted by supplier in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the supplier sorting won't work properly if there are multiple variant with different
|
||||
# supplier for a given product.
|
||||
#
|
||||
def products_supplier_relation
|
||||
Spree::Product.where(id: stocked_products).
|
||||
joins("LEFT JOIN (SELECT DISTINCT ON(product_id) id, product_id, supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL) first_variant
|
||||
ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.supplier_id").
|
||||
group("spree_products.id, first_variant.supplier_id")
|
||||
end
|
||||
|
||||
def supplier_property_join(query)
|
||||
query.joins("
|
||||
JOIN enterprises ON enterprises.id = first_variant.supplier_id
|
||||
LEFT OUTER JOIN producer_properties ON producer_properties.producer_id = enterprises.id
|
||||
")
|
||||
end
|
||||
|
||||
def variants_relation
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
|
||||
@@ -83,7 +83,7 @@ module Permissions
|
||||
Spree::Order.with_line_items_variants_and_products_outer.
|
||||
where(
|
||||
distributor_id: granted_distributor_ids,
|
||||
spree_products: { supplier_id: enterprises_with_associated_orders }
|
||||
spree_variants: { supplier_id: enterprises_with_associated_orders }
|
||||
).
|
||||
where_clause.__send__(:predicates).
|
||||
reduce(:and)
|
||||
|
||||
@@ -4,11 +4,11 @@ module PermittedAttributes
|
||||
class Product
|
||||
def self.attributes
|
||||
[
|
||||
:id, :name, :description, :supplier_id, :price,
|
||||
:id, :name, :description, :price,
|
||||
:variant_unit, :variant_unit_scale, :variant_unit_with_scale, :unit_value,
|
||||
:unit_description, :variant_unit_name,
|
||||
:display_as, :sku, :group_buy, :group_buy_unit_size,
|
||||
:taxon_ids, :primary_taxon_id, :tax_category_id,
|
||||
:taxon_ids, :primary_taxon_id, :tax_category_id, :supplier_id,
|
||||
:meta_keywords, :notes, :inherits_properties,
|
||||
{ product_properties_attributes: [:id, :property_name, :value],
|
||||
variants_attributes: [PermittedAttributes::Variant.attributes],
|
||||
|
||||
@@ -7,7 +7,8 @@ module PermittedAttributes
|
||||
:id, :sku, :on_hand, :on_demand, :shipping_category_id,
|
||||
:price, :unit_value, :unit_description,
|
||||
:display_name, :display_as, :tax_category_id,
|
||||
:weight, :height, :width, :depth, :taxon_ids, :primary_taxon_id
|
||||
:weight, :height, :width, :depth, :taxon_ids, :primary_taxon_id,
|
||||
:supplier_id
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
class ProductsRenderer
|
||||
|
||||
class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
include Pagy::Backend
|
||||
|
||||
class NoProducts < RuntimeError; end
|
||||
@@ -34,12 +35,12 @@ class ProductsRenderer
|
||||
return unless order_cycle
|
||||
|
||||
@products ||= begin
|
||||
results = distributed_products.
|
||||
products_taxons_relation.
|
||||
results = products_relation.
|
||||
order(Arel.sql(products_order))
|
||||
|
||||
filter_and_paginate(results).
|
||||
each { |product| product_scoper.scope(product) } # Scope results with variant_overrides
|
||||
results = filter(results)
|
||||
# Scope results with variant_overrides
|
||||
paginate(results).each { |product| product_scoper.scope(product) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,10 +52,57 @@ class ProductsRenderer
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
|
||||
end
|
||||
|
||||
def filter_and_paginate(query)
|
||||
results = query.ransack(args[:q]).result
|
||||
# TODO refactor this, distributed_products should be able to give use the relation based
|
||||
# on the sorting method, same for ordering. It would prevent the SQL implementation from
|
||||
# leaking here
|
||||
def products_relation
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
return distributed_products.products_taxons_relation
|
||||
end
|
||||
|
||||
_pagy, paginated_results = pagy_arel(
|
||||
distributed_products.products_supplier_relation
|
||||
end
|
||||
|
||||
# TODO: refactor to address CyclomaticComplexity
|
||||
def filter(query) # rubocop:disable Metrics/CyclomaticComplexity
|
||||
supplier_properties = args[:q]&.slice("with_variants_supplier_properties")
|
||||
|
||||
ransack_results = query.ransack(args[:q]).result.to_a
|
||||
|
||||
return ransack_results if supplier_properties.blank?
|
||||
|
||||
with_properties = args[:q]&.dig("with_properties")
|
||||
supplier_properties_results = []
|
||||
|
||||
if supplier_properties.present?
|
||||
# We can't search on an association's scope with ransack, a work around is to define
|
||||
# the a scope on the parent (Spree::Product) but because we are joining on "first_variant"
|
||||
# to get the supplier it doesn't work, so we do the filtering manually here
|
||||
# see:
|
||||
# OrderCycleDistributedProducts#products_supplier_relation
|
||||
# OrderCycleDistributedProducts#supplier_property_join
|
||||
supplier_property_ids = supplier_properties["with_variants_supplier_properties"]
|
||||
supplier_properties_results = distributed_products.supplier_property_join(query).
|
||||
where(producer_properties: { property_id: supplier_property_ids }).
|
||||
where(inherits_properties: true)
|
||||
end
|
||||
|
||||
if supplier_properties_results.present? && with_properties.present?
|
||||
# apply "OR" between property search
|
||||
return ransack_results | supplier_properties_results
|
||||
end
|
||||
|
||||
# Intersect the result to apply "AND" with other search criteria
|
||||
return ransack_results.intersection(supplier_properties_results) \
|
||||
unless supplier_properties_results.empty?
|
||||
|
||||
# We should get here but just in case we return the ransack results
|
||||
ransack_results
|
||||
end
|
||||
|
||||
def paginate(results)
|
||||
_pagy, paginated_results = pagy_array(
|
||||
results,
|
||||
page: args[:page] || 1,
|
||||
items: args[:per_page] || DEFAULT_PER_PAGE
|
||||
@@ -67,12 +115,13 @@ class ProductsRenderer
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle, customer)
|
||||
end
|
||||
|
||||
# TODO refactor, see above
|
||||
def products_order
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
order_by_producer = distributor
|
||||
.preferred_shopfront_producer_order
|
||||
.split(",").map { |id| "spree_products.supplier_id=#{id} DESC" }
|
||||
.split(",").map { |id| "first_variant.supplier_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
"#{order_by_producer}, spree_products.name ASC, spree_products.id ASC"
|
||||
elsif distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
@@ -87,7 +136,6 @@ class ProductsRenderer
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def variants_for_shop
|
||||
@variants_for_shop ||= begin
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
|
||||
@@ -55,8 +55,6 @@ module Sets
|
||||
def update_product(product, attributes)
|
||||
return false unless update_product_only_attributes(product, attributes)
|
||||
|
||||
ExchangeVariantDeleter.new.delete(product) if product.saved_change_to_supplier_id?
|
||||
|
||||
update_product_variants(product, attributes)
|
||||
end
|
||||
|
||||
@@ -107,6 +105,8 @@ module Sets
|
||||
if variant.present?
|
||||
variant.assign_attributes(variant_attributes.except(:id))
|
||||
variant.save if variant.changed?
|
||||
|
||||
ExchangeVariantDeleter.new.delete(variant) if variant.saved_change_to_supplier_id?
|
||||
else
|
||||
variant = create_variant(product, variant_attributes)
|
||||
end
|
||||
|
||||
@@ -25,13 +25,8 @@
|
||||
-# empty
|
||||
%td.col-on_hand.align-right
|
||||
-# empty
|
||||
%td.col-producer.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :supplier_id,
|
||||
aria_label: t('.producer_field_name'),
|
||||
options: producer_options,
|
||||
selected_option: product.supplier_id,
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_producers')))
|
||||
%td.col-on_hand.align-right
|
||||
-# empty
|
||||
%td.col-category.align-left
|
||||
-# empty
|
||||
%td.col-tax_category.align-left
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
action: 'rails-nested-form:add->bulk-form#registerElements rails-nested-form:remove->bulk-form#toggleFormChanged' },
|
||||
class: (defined?(should_slide_in) && should_slide_in) ? 'slide-in' : '' }
|
||||
%tr
|
||||
= render partial: 'product_row', locals: { f: product_form, product:, producer_options:, product_index: }
|
||||
= render partial: 'product_row', locals: { f: product_form, product:, product_index: }
|
||||
|
||||
- product.variants.each_with_index do |variant, variant_index|
|
||||
= form.fields_for("products][#{product_index}][variants_attributes][", variant, index: variant_index) do |variant_form|
|
||||
%tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': variant.new_record? ? "true" : false }
|
||||
= render partial: 'variant_row', locals: { variant:, f: variant_form, category_options:, tax_category_options: }
|
||||
= render partial: 'variant_row', locals: { variant:, f: variant_form, category_options:, tax_category_options:, producer_options: }
|
||||
|
||||
= form.fields_for("products][#{product_index}][variants_attributes][NEW_RECORD", product.variants.build) do |new_variant_form|
|
||||
%template{ 'data-nested-form-target': "template" }
|
||||
%tr.condensed{ 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': "true" }
|
||||
= render partial: 'variant_row', locals: { variant: new_variant_form.object, f: new_variant_form, category_options:, tax_category_options: }
|
||||
= render partial: 'variant_row', locals: { variant: new_variant_form.object, f: new_variant_form, category_options:, tax_category_options:, producer_options: }
|
||||
|
||||
%tr{ 'data-nested-form-target': "target" }
|
||||
%tr.condensed
|
||||
|
||||
@@ -39,8 +39,13 @@
|
||||
= f.label :on_demand do
|
||||
= f.check_box :on_demand, 'data-action': 'change->toggle-control#disableIfPresent change->popout#closeIfChecked'
|
||||
= t(:on_demand)
|
||||
%td.col-producer.align-left
|
||||
-# empty producer name
|
||||
%td.col-producer.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :supplier_id,
|
||||
aria_label: t('.producer_field_name'),
|
||||
options: producer_options,
|
||||
selected_option: variant.supplier_id,
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_producers')))
|
||||
%td.col-category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :primary_taxon_id,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%tr.product.even
|
||||
%td.producer{ "ng-show": 'columns.producer.visible', "ng-bind-html": '::producersByID[product.producer_id].name' }
|
||||
%td.producer{ "ng-show": 'columns.producer.visible' }
|
||||
%td.product{ "ng-show": 'columns.product.visible', "ng-bind": '::product.name' }
|
||||
%td.sku{ "ng-show": 'columns.sku.visible' }
|
||||
%td.price{ "ng-show": 'columns.price.visible' }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%tr.variant{ id: "v_{{variant.id}}", "ng-repeat": 'variant in product.variants | inventoryVariants:hub_id:views' }
|
||||
%td.producer{ "ng-show": 'columns.producer.visible' }
|
||||
%td.producer{ "ng-show": 'columns.producer.visible', "ng-bind": '::producersByID[variant.producer_id].name' }
|
||||
%td.product{ "ng-show": 'columns.product.visible' }
|
||||
%span{ "ng-bind": '::variant.display_name || ""' }
|
||||
.variant-override-unit{ "ng-bind": '::variant.unit_to_display' }
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
= render 'admin/variant_overrides/loading_flash'
|
||||
= render 'admin/variant_overrides/controls'
|
||||
= render 'admin/variant_overrides/no_results'
|
||||
// filteredProducts is defined in admin/variant_overrides/products
|
||||
%div{ "ng-cloak": true, "ng-show": 'hub_id && filteredProducts.length > 0' }
|
||||
= render 'admin/variant_overrides/new_products'
|
||||
= render 'admin/variant_overrides/hidden_products'
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
= line_items.first.variant.sku
|
||||
- if @distributors_pickup_times.many?
|
||||
%td
|
||||
= line_items.first.product.supplier.name
|
||||
= line_items.first.variant.supplier.name
|
||||
%td
|
||||
= product_and_full_name
|
||||
%td.text-right
|
||||
|
||||
@@ -15,7 +15,7 @@ Orders summary
|
||||
= t :producer_mail_order_text
|
||||
\
|
||||
- @grouped_line_items.each_pair do |product_and_full_name, line_items|
|
||||
#{line_items.first.variant.sku} - #{raw(line_items.first.product.supplier.name)} - #{raw(product_and_full_name)} (QTY: #{line_items.sum(&:quantity)}) @ #{line_items.first.single_money} = #{Spree::Money.new(line_items.sum(&:total), currency: line_items.first.currency)}
|
||||
#{line_items.first.variant.sku} - #{raw(line_items.first.variant.supplier.name)} - #{raw(product_and_full_name)} (QTY: #{line_items.sum(&:quantity)}) @ #{line_items.first.single_money} = #{Spree::Money.new(line_items.sum(&:total), currency: line_items.first.currency)}
|
||||
\
|
||||
\
|
||||
#{t :total}: #{@total}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
= cache_with_locale do
|
||||
%span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" }
|
||||
%span{ "ng-show" => "query && ( appliedPropertiesList() || appliedProducerPropertiesList() || appliedTaxonsList() )" }
|
||||
= t :products_filters_in
|
||||
|
||||
%span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'}
|
||||
%span{ "ng-show" => "appliedPropertiesList() && appliedProducerPropertiesList()" }
|
||||
= t :products_or
|
||||
%span.applied-properties{'ng-bind-html' => 'appliedProducerPropertiesList()'}
|
||||
|
||||
%span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" }
|
||||
%span{ "ng-show" => "(appliedPropertiesList() || appliedProducerPropertiesList()) && appliedTaxonsList()" }
|
||||
= t :products_and
|
||||
|
||||
%span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'}
|
||||
|
||||
@@ -4,3 +4,7 @@
|
||||
|
||||
.filter-shopfront.property-selectors{ "ng-show": 'supplied_properties != null' }
|
||||
%filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
|
||||
|
||||
.filter-shopfront.property-selectors{ng: {show: 'supplied_producer_properties != null'}}
|
||||
%filter-selector{ 'selector-set' => "producerPropertySelectors", objects: "supplied_producer_properties", "active-selectors" => "activeProducerProperties"}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
= cache_with_locale do
|
||||
.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" }
|
||||
.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedProducerPropertiesList() || appliedTaxonsList()" }
|
||||
.small-12.columns
|
||||
.alert-box.search-alert.ng-scope
|
||||
%div{"ng-show" => "Products.products.length > 0"}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
= render 'spree/shared/line_item_name', line_item: item
|
||||
%br
|
||||
%small
|
||||
%em= item.variant.product.supplier.name
|
||||
%em= item.variant.supplier.name
|
||||
%td{:align => "right"}
|
||||
= item.quantity
|
||||
%td{:align => "right"}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
= render 'spree/shared/line_item_name', line_item: item
|
||||
%br
|
||||
%small
|
||||
%em= item.variant.product.supplier.name
|
||||
%em= item.variant.supplier.name
|
||||
%td{:align => "right"}
|
||||
= item.quantity
|
||||
%td{:align => "right"}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
= render partial: 'product_property_fields', locals: { f: pp_form }
|
||||
|
||||
= f.check_box :inherits_properties
|
||||
= f.label :inherits_properties, t('.inherits_properties_checkbox_hint', supplier: @product.supplier.name)
|
||||
= f.label :inherits_properties, t('.inherits_properties_checkbox_hint', supplier: @supplier.name)
|
||||
%br
|
||||
%br
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
%tbody#producer_properties
|
||||
- @product.supplier.producer_properties.each do |producer_property|
|
||||
- @supplier.producer_properties.each do |producer_property|
|
||||
%tr
|
||||
%td= producer_property.property.presentation
|
||||
%td= producer_property.value
|
||||
|
||||
@@ -27,12 +27,6 @@
|
||||
= f.text_field :variant_unit_name, {placeholder: t('admin.products.unit_name_placeholder')}
|
||||
= f.error_message_on :variant_unit_name
|
||||
|
||||
= f.field_container :supplier do
|
||||
= f.label :supplier, t(:spree_admin_supplier)
|
||||
%br
|
||||
= f.collection_select(:supplier_id, @producers, :id, :name, {:include_blank => true}, {:class => "select2"})
|
||||
= f.error_message_on :supplier
|
||||
|
||||
.clear
|
||||
|
||||
.clear
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
%a{class: 'image-modal'}
|
||||
%img{'ng-src' => '{{ product.thumb_url }}'}
|
||||
%td.producer{ 'ng-show' => 'columns.producer.visible' }
|
||||
%select.fullwidth{ "data-controller": "tom-select", 'ng-model' => 'product.producer_id', :name => 'producer_id', 'ofn-track-product' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
|
||||
%td.sku{ 'ng-show' => 'columns.sku.visible' }
|
||||
%input{ 'ng-model' => "product.sku", :name => 'product_sku', 'ofn-track-product' => 'sku', :type => 'text' }
|
||||
%td.name{ 'ng-show' => 'columns.name.visible' }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last", 'ofn-with-tip' => t('.new_variant') }
|
||||
%td{ 'ng-show' => 'columns.image.visible' }
|
||||
%td{ 'ng-show' => 'columns.producer.visible' }
|
||||
%select.fullwidth{ "data-controller": "tom-select", 'ng-model' => 'variant.producer_id', :name => 'producer_id', 'ofn-track-variant' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
|
||||
%td{ 'ng-show' => 'columns.sku.visible' }
|
||||
%input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' }
|
||||
%td{ 'ng-show' => 'columns.name.visible' }
|
||||
|
||||
@@ -76,4 +76,8 @@
|
||||
= f.label :primary_taxon, t('spree.admin.products.primary_taxon_form.product_category')
|
||||
= f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, { include_blank: true }, { class: "select2 fullwidth" })
|
||||
|
||||
.field
|
||||
= f.label :supplier, t(:spree_admin_supplier)
|
||||
= f.collection_select(:supplier_id, @producers, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
|
||||
|
||||
.clear
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
= render 'spree/shared/line_item_name', line_item: item
|
||||
%br
|
||||
%small
|
||||
%em= item.variant.product.supplier.name
|
||||
%em= item.variant.supplier.name
|
||||
%td
|
||||
- if item.variant.sku.blank?
|
||||
\-
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pagy/extras/arel'
|
||||
require 'pagy/extras/array'
|
||||
require 'pagy/extras/items'
|
||||
require 'pagy/extras/overflow'
|
||||
|
||||
|
||||
# Pagy Variables
|
||||
# See https://ddnexus.github.io/pagy/api/pagy#variables
|
||||
Pagy::DEFAULT[:items] = 100
|
||||
Pagy::DEFAULT[:items] = 100
|
||||
|
||||
# Items extra: Allow the client to request a custom number of items per page with an optional selector UI
|
||||
# Items extra: Allow the client to request a custom number of items per page with an optional
|
||||
# selector UI
|
||||
# See https://ddnexus.github.io/pagy/extras/items
|
||||
Pagy::DEFAULT[:items_param] = :per_page
|
||||
Pagy::DEFAULT[:max_items] = 100
|
||||
|
||||
@@ -69,7 +69,6 @@ en:
|
||||
name: "Product Name"
|
||||
price: "Price"
|
||||
primary_taxon: "Product Category"
|
||||
supplier: "Supplier"
|
||||
shipping_category_id: "Shipping Category"
|
||||
variant_unit: "Variant Unit"
|
||||
variant_unit_name: "Variant Unit Name"
|
||||
@@ -77,6 +76,7 @@ en:
|
||||
spree/variant:
|
||||
primary_taxon: "Product Category"
|
||||
shipping_category_id: "Shipping Category"
|
||||
supplier: "Supplier"
|
||||
spree/credit_card:
|
||||
base: "Credit Card"
|
||||
number: "Number"
|
||||
@@ -917,7 +917,6 @@ en:
|
||||
search_for_tax_categories: "Search for tax categories"
|
||||
category_field_name: "Category"
|
||||
tax_category_field_name: "Tax Category"
|
||||
product_row:
|
||||
producer_field_name: "Producer"
|
||||
clone:
|
||||
success: Successfully cloned the product
|
||||
|
||||
@@ -60,6 +60,7 @@ Openfoodnetwork::Application.routes.draw do
|
||||
get :products, on: :member
|
||||
get :taxons, on: :member
|
||||
get :properties, on: :member
|
||||
get :producer_properties, on: :member
|
||||
end
|
||||
|
||||
resources :exchanges, only: [:show], to: 'exchange_products#index' do
|
||||
|
||||
5
db/migrate/20240221060301_add_supplier_to_variants.rb
Normal file
5
db/migrate/20240221060301_add_supplier_to_variants.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddSupplierToVariants < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_reference :spree_variants, :supplier, foreign_key: { to_table: :enterprises }
|
||||
end
|
||||
end
|
||||
11
db/migrate/20240422064305_migrate_product_supplier.rb
Normal file
11
db/migrate/20240422064305_migrate_product_supplier.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class MigrateProductSupplier < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
ActiveRecord::Base.connection.execute(<<-SQL
|
||||
UPDATE spree_variants
|
||||
SET supplier_id = spree_products.supplier_id
|
||||
FROM spree_products
|
||||
WHERE spree_variants.product_id = spree_products.id
|
||||
SQL
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -977,10 +977,12 @@ ActiveRecord::Schema[7.0].define(version: 2024_06_25_024328) do
|
||||
t.bigint "tax_category_id"
|
||||
t.bigint "shipping_category_id"
|
||||
t.bigint "primary_taxon_id"
|
||||
t.bigint "supplier_id"
|
||||
t.index ["primary_taxon_id"], name: "index_spree_variants_on_primary_taxon_id"
|
||||
t.index ["product_id"], name: "index_variants_on_product_id"
|
||||
t.index ["shipping_category_id"], name: "index_spree_variants_on_shipping_category_id"
|
||||
t.index ["sku"], name: "index_spree_variants_on_sku"
|
||||
t.index ["supplier_id"], name: "index_spree_variants_on_supplier_id"
|
||||
t.index ["tax_category_id"], name: "index_spree_variants_on_tax_category_id"
|
||||
t.check_constraint "unit_value > 0::double precision", name: "positive_unit_value"
|
||||
end
|
||||
@@ -1221,6 +1223,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_06_25_024328) do
|
||||
add_foreign_key "spree_taxons", "spree_taxons", column: "parent_id", name: "spree_taxons_parent_id_fk"
|
||||
add_foreign_key "spree_users", "spree_addresses", column: "bill_address_id", name: "spree_users_bill_address_id_fk"
|
||||
add_foreign_key "spree_users", "spree_addresses", column: "ship_address_id", name: "spree_users_ship_address_id_fk"
|
||||
add_foreign_key "spree_variants", "enterprises", column: "supplier_id"
|
||||
add_foreign_key "spree_variants", "spree_products", column: "product_id", name: "spree_variants_product_id_fk"
|
||||
add_foreign_key "spree_variants", "spree_shipping_categories", column: "shipping_category_id"
|
||||
add_foreign_key "spree_variants", "spree_tax_categories", column: "tax_category_id"
|
||||
|
||||
@@ -20,12 +20,7 @@ module Catalog
|
||||
attr_reader :excluded_items_ids, :enterprise_ids
|
||||
|
||||
def enterprise_variants_relation
|
||||
relation = Spree::Variant
|
||||
.joins(:product)
|
||||
.where(
|
||||
spree_products: { supplier_id: enterprise_ids },
|
||||
spree_variants: { deleted_at: nil }
|
||||
)
|
||||
relation = Spree::Variant.where(supplier_id: enterprise_ids)
|
||||
|
||||
return relation if excluded_items_ids.blank?
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ module Catalog
|
||||
describe '#reset' do
|
||||
let(:supplier_ids) { enterprise.id }
|
||||
let(:product) { create(:product) }
|
||||
let(:enterprise) { product.supplier }
|
||||
let(:variant) { product.variants.first }
|
||||
let(:enterprise) { variant.supplier }
|
||||
|
||||
before { variant.on_hand = 2 }
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@ module DfcProvider
|
||||
)
|
||||
product = variant.product
|
||||
|
||||
if variant.new_record?
|
||||
variant.supplier = current_enterprise
|
||||
variant.save!
|
||||
end
|
||||
|
||||
product.save! if product.new_record?
|
||||
variant.save! if variant.new_record?
|
||||
|
||||
supplied_product = SuppliedProductBuilder.supplied_product(variant)
|
||||
render json: DfcIo.export(supplied_product)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class DfcBuilder
|
||||
def self.catalog_item(variant)
|
||||
id = urls.enterprise_catalog_item_url(
|
||||
enterprise_id: variant.product.supplier_id,
|
||||
enterprise_id: variant.supplier_id,
|
||||
id: variant.id,
|
||||
)
|
||||
product = SuppliedProductBuilder.supplied_product(variant)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class OfferBuilder < DfcBuilder
|
||||
def self.build(variant)
|
||||
id = urls.enterprise_offer_url(
|
||||
enterprise_id: variant.product.supplier_id,
|
||||
enterprise_id: variant.supplier_id,
|
||||
id: variant.id,
|
||||
)
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
class SuppliedProductBuilder < DfcBuilder
|
||||
def self.supplied_product(variant)
|
||||
id = urls.enterprise_supplied_product_url(
|
||||
enterprise_id: variant.product.supplier_id,
|
||||
enterprise_id: variant.supplier_id,
|
||||
id: variant.id,
|
||||
)
|
||||
product_uri = urls.enterprise_url(
|
||||
variant.product.supplier_id,
|
||||
variant.supplier_id,
|
||||
spree_product_id: variant.product_id
|
||||
)
|
||||
|
||||
@@ -29,14 +29,15 @@ class SuppliedProductBuilder < DfcBuilder
|
||||
if product
|
||||
Spree::Variant.new(
|
||||
product:,
|
||||
supplier:,
|
||||
price: 0,
|
||||
).tap do |variant|
|
||||
apply(supplied_product, variant)
|
||||
end
|
||||
else
|
||||
product = import_product(supplied_product)
|
||||
product.supplier = supplier
|
||||
product.ensure_standard_variant
|
||||
product.variants.first.supplier = supplier
|
||||
product.variants.first
|
||||
end.tap do |variant|
|
||||
link = supplied_product.semanticId
|
||||
|
||||
@@ -15,7 +15,7 @@ RSpec.describe "CatalogItems", type: :request, swagger_doc: "dfc.yaml",
|
||||
let(:product) {
|
||||
create(
|
||||
:base_product,
|
||||
id: 90_000, supplier: enterprise, name: "Apple", description: "Red",
|
||||
id: 90_000, name: "Apple", description: "Red",
|
||||
variants: [variant],
|
||||
primary_taxon: non_local_vegetable
|
||||
)
|
||||
@@ -27,7 +27,7 @@ RSpec.describe "CatalogItems", type: :request, swagger_doc: "dfc.yaml",
|
||||
dfc_id: "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#non-local-vegetable"
|
||||
)
|
||||
}
|
||||
let(:variant) { build(:base_variant, id: 10_001, unit_value: 1, sku: "AR") }
|
||||
let(:variant) { build(:base_variant, id: 10_001, unit_value: 1, sku: "AR", supplier: enterprise) }
|
||||
|
||||
before { login_as user }
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ RSpec.describe "Enterprises", type: :request, swagger_doc: "dfc.yaml", rswag_aut
|
||||
let!(:product) {
|
||||
create(
|
||||
:product_with_image,
|
||||
id: 90_000, supplier: enterprise, name: "Apple", description: "Round",
|
||||
id: 90_000, name: "Apple", description: "Round",
|
||||
variants: [variant],
|
||||
primary_taxon: non_local_vegetable
|
||||
)
|
||||
@@ -39,7 +39,9 @@ RSpec.describe "Enterprises", type: :request, swagger_doc: "dfc.yaml", rswag_aut
|
||||
dfc_id: "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#non-local-vegetable"
|
||||
)
|
||||
}
|
||||
let(:variant) { build(:base_variant, id: 10_001, unit_value: 1, sku: "APP") }
|
||||
let(:variant) {
|
||||
build(:base_variant, id: 10_001, unit_value: 1, sku: "APP", supplier: enterprise)
|
||||
}
|
||||
|
||||
before { login_as user }
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ RSpec.describe "Offers", type: :request, swagger_doc: "dfc.yaml", rswag_autodoc:
|
||||
create(
|
||||
:product,
|
||||
id: 90_000,
|
||||
supplier: enterprise, name: "Pesto", description: "Basil Pesto",
|
||||
name: "Pesto", description: "Basil Pesto",
|
||||
variants: [variant],
|
||||
)
|
||||
}
|
||||
let(:variant) { build(:base_variant, id: 10_001, unit_value: 1) }
|
||||
let(:variant) { build(:base_variant, id: 10_001, unit_value: 1, supplier: enterprise) }
|
||||
|
||||
before { login_as user }
|
||||
|
||||
|
||||
@@ -9,11 +9,13 @@ RSpec.describe "SuppliedProducts", type: :request, swagger_doc: "dfc.yaml", rswa
|
||||
create(
|
||||
:product_with_image,
|
||||
id: 90_000,
|
||||
supplier: enterprise, name: "Pesto", description: "Basil Pesto",
|
||||
name: "Pesto", description: "Basil Pesto",
|
||||
variants: [variant]
|
||||
)
|
||||
}
|
||||
let(:variant) { build(:base_variant, id: 10_001, unit_value: 1, primary_taxon: taxon) }
|
||||
let(:variant) {
|
||||
build(:base_variant, id: 10_001, unit_value: 1, primary_taxon: taxon, supplier: enterprise)
|
||||
}
|
||||
let(:taxon) {
|
||||
build(
|
||||
:taxon,
|
||||
|
||||
@@ -8,7 +8,7 @@ RSpec.describe DfcBuilder do
|
||||
describe ".catalog_item" do
|
||||
it "assigns a semantic id" do
|
||||
variant.id = 5
|
||||
variant.product.supplier_id = 7
|
||||
variant.supplier_id = 7
|
||||
|
||||
item = DfcBuilder.catalog_item(variant)
|
||||
|
||||
@@ -19,7 +19,7 @@ RSpec.describe DfcBuilder do
|
||||
|
||||
it "refers to a supplied product" do
|
||||
variant.id = 5
|
||||
variant.product.supplier_id = 7
|
||||
variant.supplier_id = 7
|
||||
|
||||
item = DfcBuilder.catalog_item(variant)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ require_relative "../spec_helper"
|
||||
RSpec.describe EnterpriseBuilder do
|
||||
subject(:builder) { described_class }
|
||||
let(:enterprise) {
|
||||
build(
|
||||
create(
|
||||
:enterprise,
|
||||
id: 10_000, name: "Fabi's Farm",
|
||||
description: "The place where stuff grows", abn: "123 456 789 0",
|
||||
@@ -13,7 +13,7 @@ RSpec.describe EnterpriseBuilder do
|
||||
)
|
||||
}
|
||||
let(:variant) {
|
||||
create(:product, supplier: enterprise, name: "Apple").variants.first
|
||||
create(:product, supplier_id: enterprise.id, name: "Apple").variants.first
|
||||
}
|
||||
|
||||
describe ".enterprise" do
|
||||
|
||||
@@ -7,13 +7,13 @@ RSpec.describe SuppliedProductBuilder do
|
||||
|
||||
subject(:builder) { described_class }
|
||||
let(:variant) {
|
||||
build(:variant, id: 5, product: spree_product, primary_taxon: taxon)
|
||||
create(:variant, id: 5, product: spree_product, primary_taxon: taxon, supplier:)
|
||||
}
|
||||
let(:spree_product) {
|
||||
create(:product, id: 6, supplier:)
|
||||
create(:product, id: 6)
|
||||
}
|
||||
let(:supplier) {
|
||||
build(:supplier_enterprise, id: 7)
|
||||
create(:supplier_enterprise, id: 7)
|
||||
}
|
||||
let(:taxon) {
|
||||
build(
|
||||
@@ -43,7 +43,7 @@ RSpec.describe SuppliedProductBuilder do
|
||||
variant.product.name = "Apple"
|
||||
product = builder.supplied_product(variant)
|
||||
|
||||
expect(product.name).to eq "Apple"
|
||||
expect(product.name).to match /Apple/
|
||||
end
|
||||
|
||||
it "assigns the variant name if present" do
|
||||
@@ -51,7 +51,7 @@ RSpec.describe SuppliedProductBuilder do
|
||||
variant.display_name = "Granny Smith"
|
||||
product = builder.supplied_product(variant)
|
||||
|
||||
expect(product.name).to eq "Apple - Granny Smith"
|
||||
expect(product.name).to match /Apple - Granny Smith/
|
||||
end
|
||||
|
||||
context "product_type mapping" do
|
||||
@@ -244,7 +244,7 @@ RSpec.describe SuppliedProductBuilder do
|
||||
|
||||
it "doesn't return a product of another enterprise" do
|
||||
variant.save!
|
||||
create(:product, id: 8, supplier: create(:enterprise))
|
||||
create(:product, id: 8, supplier_id: create(:enterprise).id)
|
||||
|
||||
supplied_product.spree_product_uri =
|
||||
"http://test.host/api/dfc/enterprises/7?spree_product_id=8"
|
||||
|
||||
@@ -8,15 +8,14 @@ module OrderManagement
|
||||
# - Variants of hub
|
||||
# - Variants that are in outgoing exchanges where the hub is receiver
|
||||
def self.eligible_variants(distributor)
|
||||
variant_conditions = ["spree_products.supplier_id IN (?)",
|
||||
permitted_producer_ids(distributor)]
|
||||
query = Spree::Variant.where(supplier_id: permitted_producer_ids(distributor))
|
||||
|
||||
exchange_variant_ids = outgoing_exchange_variant_ids(distributor)
|
||||
if exchange_variant_ids.present?
|
||||
variant_conditions[0] << " OR spree_variants.id IN (?)"
|
||||
variant_conditions << exchange_variant_ids
|
||||
query = query.or(Spree::Variant.where(id: exchange_variant_ids))
|
||||
end
|
||||
|
||||
Spree::Variant.joins(:product).where(*variant_conditions)
|
||||
query
|
||||
end
|
||||
|
||||
def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant)
|
||||
|
||||
@@ -8,18 +8,18 @@ module OrderManagement
|
||||
describe "creating a new subscription" do
|
||||
let!(:shop) { create(:distributor_enterprise) }
|
||||
let!(:customer) { create(:customer, enterprise: shop) }
|
||||
let!(:product1) { create(:product, supplier: shop) }
|
||||
let!(:product2) { create(:product, supplier: shop) }
|
||||
let!(:product3) { create(:product, supplier: shop) }
|
||||
let!(:product1) { create(:product) }
|
||||
let!(:product2) { create(:product) }
|
||||
let!(:product3) { create(:product) }
|
||||
let!(:variant1) {
|
||||
create(:variant, product: product1, unit_value: '100', price: 12.00)
|
||||
create(:variant, product: product1, unit_value: '100', price: 12.00, supplier: shop)
|
||||
}
|
||||
let!(:variant2) {
|
||||
create(:variant, product: product2, unit_value: '1000', price: 6.00)
|
||||
create(:variant, product: product2, unit_value: '1000', price: 6.00, supplier: shop)
|
||||
}
|
||||
let!(:variant3) {
|
||||
create(:variant, product: product2, unit_value: '1000',
|
||||
price: 2.50, on_hand: 1)
|
||||
price: 2.50, on_hand: 1, supplier: shop)
|
||||
}
|
||||
let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) }
|
||||
let!(:order_cycle1) {
|
||||
|
||||
@@ -8,8 +8,7 @@ module OrderManagement
|
||||
describe "variant eligibility for subscription" do
|
||||
let!(:shop) { create(:distributor_enterprise) }
|
||||
let!(:producer) { create(:supplier_enterprise) }
|
||||
let!(:product) { create(:product, supplier: producer) }
|
||||
let!(:variant) { product.variants.first }
|
||||
let!(:variant) { create(:variant, supplier: producer) }
|
||||
|
||||
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
|
||||
let!(:subscription) { create(:subscription, shop:, schedule:) }
|
||||
@@ -45,7 +44,7 @@ module OrderManagement
|
||||
context "if the supplier is permitted for the shop" do
|
||||
let!(:enterprise_relationship) {
|
||||
create(:enterprise_relationship, child: shop,
|
||||
parent: product.supplier,
|
||||
parent: variant.supplier,
|
||||
permissions_list: [:add_to_order_cycle])
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ module OrderManagement
|
||||
|
||||
context "if it is an incoming exchange where the shop is the receiver" do
|
||||
let!(:incoming_exchange) {
|
||||
order_cycle.exchanges.create(sender: product.supplier,
|
||||
order_cycle.exchanges.create(sender: variant.supplier,
|
||||
receiver: shop,
|
||||
incoming: true, variants: [variant])
|
||||
}
|
||||
@@ -72,7 +71,7 @@ module OrderManagement
|
||||
|
||||
context "if it is an outgoing exchange where the shop is the receiver" do
|
||||
let!(:outgoing_exchange) {
|
||||
order_cycle.exchanges.create(sender: product.supplier,
|
||||
order_cycle.exchanges.create(sender: variant.supplier,
|
||||
receiver: shop,
|
||||
incoming: false,
|
||||
variants: [variant])
|
||||
@@ -123,7 +122,7 @@ module OrderManagement
|
||||
|
||||
context "if it is an incoming exchange where the shop is the receiver" do
|
||||
let!(:incoming_exchange) {
|
||||
order_cycle.exchanges.create(sender: product.supplier,
|
||||
order_cycle.exchanges.create(sender: variant.supplier,
|
||||
receiver: shop,
|
||||
incoming: true,
|
||||
variants: [variant])
|
||||
@@ -138,7 +137,7 @@ module OrderManagement
|
||||
|
||||
context "if it is an outgoing exchange where the shop is the receiver" do
|
||||
let!(:outgoing_exchange) {
|
||||
order_cycle.exchanges.create(sender: product.supplier,
|
||||
order_cycle.exchanges.create(sender: variant.supplier,
|
||||
receiver: shop,
|
||||
incoming: false,
|
||||
variants: [variant])
|
||||
|
||||
@@ -151,7 +151,7 @@ module OpenFoodNetwork
|
||||
end
|
||||
|
||||
def all_variants_supplied_by(producer)
|
||||
Spree::Variant.joins(:product).where('spree_products.supplier_id = (?)', producer)
|
||||
Spree::Variant.where(supplier: producer)
|
||||
end
|
||||
|
||||
def no_variants
|
||||
@@ -163,9 +163,9 @@ module OpenFoodNetwork
|
||||
user_manages_coordinator_or(enterprise)
|
||||
end.map(&:id)
|
||||
|
||||
Spree::Variant.includes(product: :supplier).
|
||||
select("spree_variants.id, spree_variants.product_id, spree_products.supplier_id").
|
||||
joins(:product).where(spree_products: { supplier_id: valid_suppliers })
|
||||
Spree::Variant.includes(:supplier).
|
||||
select(:id, :product_id, :supplier_id).
|
||||
where(supplier_id: valid_suppliers)
|
||||
end
|
||||
|
||||
# Find the variants that a user is permitted see within outgoing exchanges
|
||||
@@ -185,13 +185,10 @@ module OpenFoodNetwork
|
||||
permitted_variants = variants_from_suppliers(producer_ids)
|
||||
|
||||
# PLUS my incoming producers' variants that are already in an outgoing exchange of this hub,
|
||||
# so things don't break. TODO: Remove this when all P-OC are sorted out
|
||||
active_variants = Spree::Variant.joins(:exchanges, :product).
|
||||
where("exchanges.receiver_id = (?)
|
||||
AND spree_products.supplier_id IN (?)
|
||||
AND incoming = 'f'",
|
||||
hub.id,
|
||||
managed_producer_ids)
|
||||
# so things don't break.
|
||||
# TODO: Remove this when all P-OC are sorted out
|
||||
active_variants = Spree::Variant.joins(:exchanges).
|
||||
where(exchanges: { receiver: hub, incoming: false }, supplier_id: managed_producer_ids)
|
||||
|
||||
Spree::Variant.where(id: permitted_variants | active_variants)
|
||||
end
|
||||
@@ -238,7 +235,7 @@ module OpenFoodNetwork
|
||||
end
|
||||
|
||||
def variants_from_suppliers(supplier_ids)
|
||||
Spree::Variant.joins(:product).where(spree_products: { supplier_id: supplier_ids })
|
||||
Spree::Variant.where(supplier_id: supplier_ids)
|
||||
end
|
||||
|
||||
def active_outgoing_variants(hub)
|
||||
|
||||
@@ -62,28 +62,26 @@ module OpenFoodNetwork
|
||||
def editable_products
|
||||
return Spree::Product.all if admin?
|
||||
|
||||
Spree::Product.where(supplier_id: @user.enterprises).or(
|
||||
Spree::Product.where(supplier_id: related_enterprises_granting(:manage_products))
|
||||
product_with_variants.where(spree_variants: { supplier_id: @user.enterprises }).or(
|
||||
product_with_variants.where(
|
||||
spree_variants: { supplier_id: related_enterprises_granting(:manage_products) }
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def visible_products
|
||||
return Spree::Product.all if admin?
|
||||
|
||||
Spree::Product.where(
|
||||
supplier_id: @user.enterprises
|
||||
).or(
|
||||
Spree::Product.where(
|
||||
supplier_id: related_enterprises_granting(:manage_products) |
|
||||
related_enterprises_granting(:add_to_order_cycle)
|
||||
product_with_variants.where(spree_variants: { supplier_id: @user.enterprises }).or(
|
||||
product_with_variants.where(
|
||||
spree_variants: {
|
||||
supplier_id: related_enterprises_granting(:manage_products) |
|
||||
related_enterprises_granting(:add_to_order_cycle)
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def product_ids_supplied_by(supplier_ids)
|
||||
Spree::Product.where(supplier_id: supplier_ids).select(:id)
|
||||
end
|
||||
|
||||
def managed_product_enterprises
|
||||
managed_and_related_enterprises_granting :manage_products
|
||||
end
|
||||
@@ -176,5 +174,9 @@ module OpenFoodNetwork
|
||||
def managed_enterprise_products
|
||||
Spree::Product.managed_by(@user)
|
||||
end
|
||||
|
||||
def product_with_variants
|
||||
Spree::Product.joins(:variants)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ module Reporting
|
||||
@orders ||= search_orders
|
||||
end
|
||||
|
||||
def list(line_item_includes = [variant: [product: :supplier]])
|
||||
def list(line_item_includes = [variant: [:supplier, :product]])
|
||||
line_items = order_permissions.visible_line_items.in_orders(orders.result)
|
||||
.order("supplier.name", "product.name", "variant.display_name")
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ module Reporting
|
||||
reflect query.join(association(Spree::Variant, :product))
|
||||
end
|
||||
|
||||
def joins_product_supplier
|
||||
reflect query.join(association(Spree::Product, :supplier, supplier_alias))
|
||||
def joins_variant_supplier
|
||||
reflect query.join(association(Spree::Variant, :supplier, supplier_alias))
|
||||
end
|
||||
|
||||
def joins_variant_shipping_category
|
||||
|
||||
@@ -22,7 +22,7 @@ module Reporting
|
||||
[
|
||||
{
|
||||
order: [:bill_address],
|
||||
variant: { product: :supplier }
|
||||
variant: [:product, :supplier]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
@@ -201,7 +201,6 @@ module Reporting
|
||||
# Includes:
|
||||
# * Line item
|
||||
# * Variant
|
||||
# * Product
|
||||
# * Tax category of product, if enterprise fee tells to inherit
|
||||
def include_line_item_source_details
|
||||
join_scope(
|
||||
@@ -224,13 +223,6 @@ module Reporting
|
||||
JOIN_STRING
|
||||
)
|
||||
|
||||
join_scope(
|
||||
<<~JOIN_STRING
|
||||
LEFT OUTER JOIN spree_products
|
||||
ON (spree_products.id = spree_variants.product_id)
|
||||
JOIN_STRING
|
||||
)
|
||||
|
||||
join_scope(
|
||||
<<~JOIN_STRING
|
||||
LEFT OUTER JOIN spree_tax_categories AS product_tax_categories
|
||||
@@ -324,7 +316,7 @@ module Reporting
|
||||
def filter_by_distribution(params)
|
||||
filter_scope(spree_orders: { distributor_id: params.distributor_ids }) \
|
||||
if params.distributor_ids.present?
|
||||
filter_scope(spree_products: { supplier_id: params.producer_ids }) \
|
||||
filter_scope(spree_variants: { supplier_id: params.producer_ids }) \
|
||||
if params.producer_ids.present?
|
||||
filter_scope(spree_orders: { order_cycle_id: params.order_cycle_ids }) \
|
||||
if params.order_cycle_ids.present?
|
||||
|
||||
@@ -49,11 +49,11 @@ module Reporting
|
||||
end
|
||||
|
||||
def supplier_name
|
||||
proc { |line_items| line_items.first.variant.product.supplier.name }
|
||||
proc { |line_items| line_items.first.variant.supplier.name }
|
||||
end
|
||||
|
||||
def supplier_charges_sales_tax?
|
||||
proc { |line_items| line_items.first.variant.product.supplier.charges_sales_tax }
|
||||
proc { |line_items| line_items.first.variant.supplier.charges_sales_tax }
|
||||
end
|
||||
|
||||
def product_name
|
||||
|
||||
@@ -93,7 +93,7 @@ module Reporting
|
||||
end
|
||||
|
||||
def line_item_includes
|
||||
[{ variant: { product: :supplier },
|
||||
[{ variant: [:product, :supplier],
|
||||
order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments,
|
||||
:user, :distributor, :shipments] }]
|
||||
end
|
||||
|
||||
@@ -40,7 +40,7 @@ module Reporting
|
||||
:adjustments,
|
||||
{ shipments: { shipping_rates: :shipping_method } }
|
||||
],
|
||||
variant: { product: :supplier }
|
||||
variant: [:product, :supplier]
|
||||
}]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ module Reporting
|
||||
end
|
||||
|
||||
def line_item_includes
|
||||
[{ variant: { product: :supplier } }]
|
||||
[{ variant: [:supplier, :product] }]
|
||||
end
|
||||
|
||||
def query_result
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user