Merge branch 'master' into active-products-translation-fix

* master: (46 commits)
  Wait for button to disappear before checking flash
  Use flash matcher in shipping method feature specs
  Add RSpec matchers for flash messages
  Do not expect modal open when checking spinner gone
  Update issue templates
  Update all locales with the latest Transifex translations
  Do not show table until first time dereferencing is done
  Remove unused have_no_selector argument in feature test
  Compile row ID with higher priority
  Compile edit link with higher priority
  Support selecting date in next months
  Improve sync between keyword filter and selecting all
  Wait for datepicker to associate and open before selecting date
  Update cancan permissions for second iteration of bulk invoices
  Fix shop accidentally becoming order coordinator
  Match date format in spec with import date filter
  Update name spaces for rake tasks to shorter 'ofn'.
  Refactor checking no preview image in specs
  Refactor checking of preview image path in specs
  Fix race condition in enterprise image feature specs
  ...
This commit is contained in:
Pau Perez
2019-02-11 16:12:52 +01:00
54 changed files with 1053 additions and 268 deletions

61
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,61 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## Description
<!-- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug -->
## Expected Behavior
<!-- Tell us what should happen -->
## Actual Behaviour
<!-- Tell us what happens instead -->
## Steps to Reproduce
<!-- Provide an unambiguous set of steps to reproduce this bug -->
<!-- Include code to reproduce if relevant -->
1.
2.
3.
4.
## Animated Gif/Screenshot
<!-- Provide a screenshot or brief animated gif reproducing the bug. Linux users can use
[Peek](https://github.com/phw/peek#ubuntu) while Mac users can use [Recordit](http://recordit.co/) -->
## Context
<!-- How has this bug affected you? What were you trying to accomplish? -->
## Severity
<!-- Assign a label and explain the impact.
bug-s1: a critical feature is broken: checkout, payments, signup, login
bug-s2: a non-critical feature is broken, no workaround
bug-s3: a feature is broken but there is a workaround
bug-s4: it's annoying, but you can use it
bug-s5: we can live with it, only a few users impacted
https://github.com/openfoodfoundation/openfoodnetwork/wiki/Bug-severity
-->
## Your Environment
<!-- Include relevant details about the environment you experienced the bug in -->
* Version used:
* Browser name and version:
* Operating System and version (desktop or mobile):
* OFN Platform instance where you discovered the bug, and which version of the software they are using.
## Possible Fix
<!-- Not obligatory, but suggest a fix or reason for the bug -->

View File

@@ -0,0 +1,35 @@
---
name: Feature template
about: Create feature epics that detail the larger feature or functionality to be
delivered.
title: ''
labels: ''
assignees: ''
---
## What is the problem we are solving
<!-- Describe the problem this feature is supposed to solve. Should be described in the icebox item (in Discourse). -->
## Success factors = expected outcome
<!-- Describe what is the expected outcome: when the feature is released, what would it look like? What will make you say "the problem is solved"? Can be metrics like "Platform has less than 1% service interruption", or yes/no statements like "People can checkout on mobiles. -->
## Useful information for inception
<!-- List here any information that can be useful for the inception stage. -->
## Link to the "Product Development - Backlog" item in Discourse
<!-- Put the link here, and put this epic link in the Discourse item as well for cross-referencing. -->
Add a custom footer
Pages 70
Home
Development environment setup
macOS (Sierra, HighSierra and Mojave)
OS X (El Capitan)
OS X (Mavericks)
Ubuntu
On Heroku
Rubocop
General guidelines
Spree Commerce customisation

View File

@@ -0,0 +1,18 @@
---
name: Story template
about: Create stories that are small chunks of work that devs will pick up and deliver
title: ''
labels: ''
assignees: ''
---
**## Description**
<!-- Describe the story in detail:
**- As a:** (enterprise user, super admin, user...)
**- On page:** (provide url of the page you want to modify. If not provide where will be created the new url and the name we want to give it)
**- I want to be able to do:** (specify the desired behavior)
(Link to others issues or resources to provide context > only if really necessary). -->
**## Acceptance Criteria**
<!-- Document the outcomes that need to be achieved before this component can be considered complete. -->

View File

@@ -39,7 +39,7 @@ before_script:
script:
- 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi'
- 'if [ "$RSPEC_ENGINES" = "true" ]; then bundle exec rake openfoodnetwork:specs:engines:rspec; else echo "Skipping RSpec run in engines"; fi'
- 'if [ "$RSPEC_ENGINES" = "true" ]; then bundle exec rake ofn:specs:engines:rspec; else echo "Skipping RSpec run in engines"; fi'
- "bundle exec rake 'knapsack:rspec[--format progress --tag ~performance]'"
after_success:

View File

@@ -75,7 +75,7 @@ Then the main application tests can be run with:
The tests of all custom engines can be run with:
bundle exec rake openfoodnetwork:specs:engines:rspec
bundle exec rake ofn:specs:engines:rspec
Note: If your OS is not explicitly supported in the setup guides then not all tests may pass. However, you may still be able to develop. Get in touch with the [#dev][slack-dev] channel on Slack to troubleshoot issues and determine if they will preclude you from contributing to OFN.

View File

@@ -22,13 +22,13 @@ gem 'spree_auth_devise', github: 'openfoodfoundation/spree_auth_devise', branch:
# - Change type of password from string to password to hide it in the form
gem 'spree_paypal_express', github: "openfoodfoundation/better_spree_paypal_express", branch: "spree-upgrade-intermediate"
#gem 'spree_paypal_express', github: "spree-contrib/better_spree_paypal_express", branch: "1-3-stable"
gem 'stripe', '~> 3.3.2'
gem 'stripe', '~> 4.5.0'
# We need at least this version to have Digicert's root certificate
# which is needed for Pin Payments (and possibly others).
gem 'activemerchant', '~> 1.78'
gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect
gem 'jwt', '~> 1.5'
gem 'jwt', '~> 2.1'
gem 'delayed_job_active_record'
gem 'daemons'

View File

@@ -252,6 +252,7 @@ GEM
compass (~> 1.0.0)
sass-rails (< 5.1)
sprockets (< 4.0)
connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
css_parser (1.6.0)
@@ -508,7 +509,7 @@ GEM
json_spec (1.1.5)
multi_json (~> 1.0)
rspec (>= 2.0, < 4.0)
jwt (1.5.6)
jwt (2.1.0)
kaminari (0.13.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
@@ -540,6 +541,8 @@ GEM
multi_xml (0.6.0)
multipart-post (2.0.0)
nenv (0.3.0)
net-http-persistent (3.0.0)
connection_pool (~> 2.2)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
notiffany (0.1.1)
@@ -629,7 +632,7 @@ GEM
trollop (~> 2.1)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.2.3)
redcarpet (3.4.0)
ref (2.0.0)
request_store (1.4.1)
rack (>= 1.4)
@@ -716,8 +719,9 @@ GEM
tilt (~> 1.1, != 1.3.0)
state_machine (1.2.0)
stringex (1.3.3)
stripe (3.3.2)
faraday (~> 0.9)
stripe (4.5.0)
faraday (~> 0.13)
net-http-persistent (~> 3.0)
therubyracer (0.12.0)
libv8 (~> 3.16.14.0)
ref
@@ -813,7 +817,7 @@ DEPENDENCIES
jquery-migrate-rails
jquery-rails
json_spec (~> 1.1.4)
jwt (~> 1.5)
jwt (~> 2.1)
knapsack
letter_opener (>= 1.4.1)
listen (= 3.0.8)
@@ -853,7 +857,7 @@ DEPENDENCIES
spree_paypal_express!
spring (= 1.7.2)
spring-commands-rspec
stripe (~> 3.3.2)
stripe (~> 4.5.0)
therubyracer (= 0.12.0)
timecop
truncate_html

View File

@@ -10,6 +10,7 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.updated_ids = []
$scope.update_errors = []
$scope.batchSize = 50
$scope.step = 'settings'
$scope.chunks = 0
$scope.completed = 0
@@ -51,19 +52,28 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.start = () ->
$scope.started = true
total = ams_data.item_count
size = 50
$scope.chunks = Math.ceil(total / size)
$scope.chunks = Math.ceil(total / $scope.batchSize)
i = 0
# Process only the first batch.
$scope.processBatch($scope.step, 0, $scope.chunks)
while i < $scope.chunks
start = (i*size)+1
end = (i+1)*size
if $scope.step == 'import'
$scope.processImport(start, end)
if $scope.step == 'save'
$scope.processSave(start, end)
i++
$scope.processBatch = (step, batchIndex, batchCount) ->
start = (batchIndex * $scope.batchSize) + 1
end = (batchIndex + 1) * $scope.batchSize
isLastBatch = batchCount == batchIndex + 1
promise = if step == 'import'
$scope.processImport(start, end)
else if step == 'save'
$scope.processSave(start, end)
return if isLastBatch
processNextBatch = ->
$scope.processBatch(step, batchIndex + 1, batchCount)
# Process next batch whether or not processing of the current batch succeeds.
promise.then(processNextBatch, processNextBatch)
$scope.processImport = (start, end) ->
$http(

View File

@@ -6,7 +6,11 @@ angular.module("admin.subscriptions").controller "SubscriptionController", ($sco
$scope.schedules = Schedules.all
$scope.paymentMethods = PaymentMethods.all
$scope.shippingMethods = ShippingMethods.all
$scope.distributor_id = $scope.subscription.shop_id # variant selector requires distributor_id
# Variant selector requires these
$scope.distributor_id = $scope.subscription.shop_id
$scope.eligible_for_subscriptions = true
$scope.view = if $scope.subscription.id? then 'review' else 'details'
$scope.nextCallbacks = {}
$scope.backCallbacks = {}

View File

@@ -22,6 +22,7 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) ->
q: term
distributor_id: scope.distributor_id
order_cycle_id: scope.order_cycle_id
eligible_for_subscriptions: scope.eligible_for_subscriptions
results: (data, page) ->
results: data
formatResult: (variant) ->

View File

@@ -4,7 +4,7 @@ Darkswarm.filter "date_in_words", ->
Darkswarm.filter "sensible_timeframe", (date_in_wordsFilter)->
(date) ->
if moment().add('days', 2) < moment(date)
if moment().add(2, 'days') < moment(date)
t 'orders_open'
else
t('closing') + date_in_wordsFilter(date)

View File

@@ -0,0 +1,7 @@
@import '../variables';
.admin-subscription-form-subscription-line-items {
.not-in-open-and-upcoming-order-cycles-warning {
color: $warning-red;
}
}

View File

@@ -0,0 +1,7 @@
@import '../variables';
.admin-subscription-review-subscription-line-items {
.not-in-open-and-upcoming-order-cycles-warning {
color: $warning-red;
}
}

View File

@@ -13,7 +13,8 @@ module Admin
def build
@subscription_line_item.assign_attributes(params[:subscription_line_item])
@subscription_line_item.price_estimate = price_estimate
render json: @subscription_line_item, serializer: Api::Admin::SubscriptionLineItemSerializer
render json: @subscription_line_item, serializer: Api::Admin::SubscriptionLineItemSerializer,
shop: @shop, schedule: @schedule
end
private
@@ -26,7 +27,7 @@ module Admin
@shop = Enterprise.managed_by(spree_current_user).find_by_id(params[:shop_id])
@schedule = permissions.editable_schedules.find_by_id(params[:schedule_id])
@order_cycle = @schedule.andand.current_or_next_order_cycle
@variant = Spree::Variant.stockable_by(@shop).find_by_id(params[:subscription_line_item][:variant_id])
@variant = variant_if_eligible(params[:subscription_line_item][:variant_id]) if @shop.present?
end
def new_actions
@@ -50,5 +51,9 @@ module Admin
OpenFoodNetwork::ScopeVariantToHub.new(@shop).scope(@variant)
@variant.price + fee_calculator.indexed_fees_for(@variant)
end
def variant_if_eligible(variant_id)
SubscriptionVariantsService.eligible_variants(@shop).find_by_id(variant_id)
end
end
end

View File

@@ -2,6 +2,7 @@ module Spree
module Admin
class InvoicesController < Spree::Admin::BaseController
respond_to :json
authorize_resource class: false
def create
invoice_service = BulkInvoiceService.new

View File

@@ -189,7 +189,7 @@ module ProductImport
products.flat_map(&:variants).each do |existing_variant|
unit_scale = existing_variant.product.variant_unit_scale
unscaled_units = entry.unscaled_units || 0
entry.unit_value = unscaled_units * unit_scale
entry.unit_value = unscaled_units * unit_scale unless unit_scale.nil?
if entry_matches_existing_variant?(entry, existing_variant)
variant_override = create_inventory_item(entry, existing_variant)

View File

@@ -210,9 +210,10 @@ class AbilityDecorator
# during the order creation process from the admin backend
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
end
can [:admin, :bulk_management, :managed, :bulk_invoice], Spree::Order do
can [:admin, :bulk_management, :managed], Spree::Order do
user.admin? || user.enterprises.any?(&:is_distributor)
end
can [:admin, :create, :show, :poll], :invoice
can [:admin, :visible], Enterprise
can [:admin, :index, :create, :update, :destroy], :line_item
can [:admin, :index, :create], Spree::LineItem

View File

@@ -1,7 +1,8 @@
module Api
module Admin
class SubscriptionLineItemSerializer < ActiveModel::Serializer
attributes :id, :variant_id, :quantity, :description, :price_estimate
attributes :id, :variant_id, :quantity, :description, :price_estimate,
:in_open_and_upcoming_order_cycles
def description
"#{object.variant.product.name} - #{object.variant.full_name}"
@@ -10,6 +11,22 @@ module Api
def price_estimate
object.price_estimate.andand.to_f || "?"
end
def in_open_and_upcoming_order_cycles
SubscriptionVariantsService.in_open_and_upcoming_order_cycles?(option_or_assigned_shop,
option_or_assigned_schedule,
object.variant)
end
private
def option_or_assigned_shop
@options[:shop] || object.subscription.andand.shop
end
def option_or_assigned_schedule
@options[:schedule] || object.subscription.andand.schedule
end
end
end
end

View File

@@ -97,15 +97,12 @@ class SubscriptionValidator
errors.add(:subscription_line_items, :not_available, name: name)
end
# TODO: Extract this into a separate class
def available_variant_ids
@available_variant_ids ||=
Spree::Variant.joins(exchanges: { order_cycle: :schedules })
.where(id: subscription_line_items.map(&:variant_id))
.where(schedules: { id: schedule }, exchanges: { incoming: false, receiver_id: shop })
.merge(OrderCycle.not_closed)
.select('DISTINCT spree_variants.id')
.pluck(:id)
return @available_variant_ids if @available_variant_ids.present?
subscription_variant_ids = subscription_line_items.map(&:variant_id)
@available_variant_ids = SubscriptionVariantsService.eligible_variants(shop)
.where(id: subscription_variant_ids).pluck(:id)
end
def build_msg_from(k, msg)

View File

@@ -0,0 +1,39 @@
class SubscriptionVariantsService
# Includes the following variants:
# - Variants of permitted producers
# - 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)]
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
end
Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions)
end
def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant)
scope = ExchangeVariant.joins(exchange: { order_cycle: :schedules })
.where(variant_id: variant, exchanges: { incoming: false, receiver_id: distributor })
.merge(OrderCycle.not_closed)
scope = scope.where(schedules: { id: schedule })
scope.any?
end
def self.permitted_producer_ids(distributor)
other_permitted_producer_ids = EnterpriseRelationship.joins(:parent)
.permitting(distributor).with_permission(:add_to_order_cycle)
.merge(Enterprise.is_primary_producer)
.pluck(:parent_id)
other_permitted_producer_ids | [distributor.id]
end
def self.outgoing_exchange_variant_ids(distributor)
ExchangeVariant.select("DISTINCT exchange_variants.variant_id").joins(:exchange)
.where(exchanges: { incoming: false, receiver_id: distributor.id })
.pluck(:variant_id)
end
end

View File

@@ -56,7 +56,7 @@
%input#edit-products{ type: "button", value: t(:edit), ng: { click: "setView('products')" } }
.row
.seven.columns.alpha.omega
%table#subscription-line-items
%table#subscription-line-items.admin-subscription-review-subscription-line-items
%colgroup
%col{:style => "width: 62%;"}/
%col{:style => "width: 14%;"}/
@@ -71,7 +71,10 @@
%span= t(:total)
%tbody
%tr.item{ id: "sli_{{$index}}", ng: { repeat: "item in subscription.subscription_line_items | filter:{ _destroy: '!true' }", class: { even: 'even', odd: 'odd' } } }
%td.description {{ item.description }}
%td
.description {{ item.description }}
.not-in-open-and-upcoming-order-cycles-warning{ ng: { if: '!item.in_open_and_upcoming_order_cycles' } }
= t(".no_open_or_upcoming_order_cycle")
%td.price.align-center {{ item.price_estimate | currency }}
%td.quantity {{ item.quantity }}
%td.total.align-center {{ (item.price_estimate * item.quantity) | currency }}

View File

@@ -1,4 +1,4 @@
%table#subscription-line-items
%table#subscription-line-items.admin-subscription-form-subscription-line-items
%colgroup
%col{:style => "width: 49%;"}/
%col{:style => "width: 14%;"}/
@@ -15,7 +15,10 @@
%th.orders-actions.actions
%tbody
%tr.item{ id: "sli_{{$index}}", ng: { repeat: "item in subscription.subscription_line_items | filter:{ _destroy: '!true' }", class: { even: 'even', odd: 'odd' } } }
%td.description {{ item.description }}
%td
.description {{ item.description }}
.not-in-open-and-upcoming-order-cycles-warning{ ng: { if: '!item.in_open_and_upcoming_order_cycles' } }
= t(".not_in_open_and_upcoming_order_cycles_warning")
%td.price.align-center {{ item.price_estimate | currency }}
%td.quantity
%input{ name: 'quantity', type: 'number', min: 0, ng: { model: 'item.quantity' } }

View File

@@ -112,7 +112,7 @@
.margin-bottom-50{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' }
%form{ name: 'bulk_order_form' }
%table.index#listing_orders.bulk{ :class => "sixteen columns alpha" }
%table.index#listing_orders.bulk{ :class => "sixteen columns alpha", ng: { show: "initialized" } }
%thead
%tr{ ng: { controller: "ColumnsCtrl" } }
%th.bulk
@@ -157,7 +157,7 @@
= t("admin.orders.bulk_management.ask")
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" }
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", ng: { attr: { id: "li_{{line_item.id}}" } } }
%td.bulk
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true }
%td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }}
@@ -181,6 +181,6 @@
%input.show-dirty{ :type => 'text', :name => 'price', :id => 'price', :ng => { value: 'line_item.price * line_item.quantity | currency:""', readonly: "true", class: '{"update-error": line_item.errors.price}' } }
%span.error{ ng: { bind: 'line_item.errors.price' } }
%td.actions
%a{ href: "/admin/orders/{{line_item.order.number}}/edit", :class => "edit-order icon-edit no-text", 'confirm-link-click' => 'confirmRefresh()' }
%a{ ng: { href: "/admin/orders/{{line_item.order.number}}/edit" }, :class => "edit-order icon-edit no-text", 'confirm-link-click' => 'confirmRefresh()' }
%td.actions
%a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" }

View File

@@ -1088,6 +1088,7 @@ en:
this_is_an_estimate: |
The displayed prices are only an estimate and calculated at the time the subscription is changed.
If you change prices or fees, orders will be updated, but the subscription will still display the old values.
not_in_open_and_upcoming_order_cycles_warning: "There are no open or upcoming order cycles for this product."
details:
details: Details
invalid_error: Oops! Please fill in all of the required fields...
@@ -1102,6 +1103,7 @@ en:
details: Details
address: Address
products: Products
no_open_or_upcoming_order_cycle: "No Upcoming Order Cycle"
product_already_in_order: This product has already been added to the order. Please edit the quantity directly.
orders:
number: Number

View File

@@ -1105,7 +1105,7 @@ fr_BE:
footer_legal_call: "Lire nos"
footer_legal_tos: "Termes et conditions"
footer_legal_visit: "Nous trouver sur"
footer_legal_text_html: "Open Food Network est une plateforme logicielle open source, libre et gratuite. Nos données sont protégées sous licence %{content_license} et notre code sous %{code_license}."
footer_legal_text_html: "Open Food Network est une plateforme logicielle open source et libre. Nos données sont protégées sous licence %{content_license} et notre code sous %{code_license}."
footer_data_text_with_privacy_policy_html: "Nous prenons soin de vos données. Voir notre %{privacy_policy} et %{cookies_policy}."
footer_data_text_without_privacy_policy_html: "Nous prenons soin de vos données. Voir notre %{cookies_policy}."
footer_data_privacy_policy: "politique de confidentialité"

View File

@@ -602,6 +602,7 @@ it:
desc_long_placeholder: Racconta di te ai consumatori. Questa informazione comparirà nel tuo profilo pubblico.
business_details:
abn: ABN
abn_placeholder: es. 99 123 456 789
acn: ACN
acn_placeholder: es. 123 456 789
display_invoice_logo: Mostra logo nelle fatture
@@ -1131,6 +1132,7 @@ it:
total_excl_tax: "Totale (Tasse escl.):"
total_incl_tax: "Totale (Tasse incl.):"
abn: "CF:"
acn: "ACN:"
invoice_issued_on: "Fattura emessa il"
order_number: "Numero fattura:"
date_of_transaction: "Data della transazione:"
@@ -1149,7 +1151,9 @@ it:
menu_5_title: "About"
menu_5_url: "http://www.openfoodnetwork.org/"
menu_6_title: "Connetti"
menu_6_url: "https://openfoodnetwork.org/au/connect/"
menu_7_title: "Impara"
menu_7_url: "https://openfoodnetwork.org/au/learn/"
logo: "Logo (640x130)"
logo_mobile: "Mobile logo (75x26)"
logo_mobile_svg: "Mobile logo (SVG)"
@@ -2216,6 +2220,9 @@ it:
validation_msg_tax: "^Categoria d'imposta richiesta"
validation_msg_tax_category_cant_be_blank: "^Categoria di tassa non può essere vuoto"
validation_msg_is_associated_with_an_exising_customer: "è associato con un cliente esistente"
content_configuration_pricing_table: "(TODO: tabella dei prezzi)"
content_configuration_case_studies: "(TODO: Casi studio)"
content_configuration_detail: "(TODO: Dettaglio)"
enterprise_name_error: "è già stato utilizzato. Se questo è il nome della tua azienda e vorresti reclamarne la proprietà, o se vuoi contattare questa azienda, puoi contattare l'attuale referente di questo profilo a %{email}."
enterprise_owner_error: "^%{email}non può gestire altre aziende (il limite è %{enterprise_limit})."
enterprise_role_uniqueness_error: "^Questo ruolo è già presente."
@@ -2253,6 +2260,7 @@ it:
back_to_orders_list: "Indietro alla lista delle gentili richieste"
no_orders_found: "Nessuna gentile richiesta trovata"
order_information: "Informazioni Gentile Richiesta"
date_completed: "Data di completamento"
amount: "Quantità"
state_names:
ready: Pronto
@@ -2275,6 +2283,7 @@ it:
choose: Scegli
resolve_errors: 'Per favore risolvi i seguenti errori:'
more_items: "+ %{count} ancora"
default_card_updated: Carta predefinita aggiornata
admin:
enterprise_limit_reached: "Hai raggiunto il limite standard di aziende per account. Scrivi a %{contact_email} se hai bisogno di aumentarlo."
modals:
@@ -2368,14 +2377,18 @@ it:
non_producer_example: es. Botteghe, Food Coop, GAS
enterprise_status:
status_title: "%{name} è impostato e pronto a partire!"
severity: Gravità
description: Descrizione
resolve: Risolvi
new_tag_rule_dialog:
select_rule_type: "Seleziona un tipo di regola:"
orders:
index:
per_page: "%{results} per pagina"
view_file: Vedi file
compiling_invoices: Compilazione fatture
bulk_invoice_created: Fattura all'ingrosso creata
bulk_invoice_failed: Creazione fattura all'ingrosso fallita
please_wait: Si prega di attendere che il PDF sia pronto prima di chiudere questo modale
resend_user_email_confirmation:
resend: "Invia nuovamente"
@@ -2394,6 +2407,7 @@ it:
'yes': "A richiesta"
variant_overrides:
on_demand:
use_producer_settings: "Usa le impostazioni dello stock del produttore"
'yes': "Sì"
'no': "No"
inventory_products: "Inventario Prodotti"
@@ -2401,6 +2415,9 @@ it:
new_products: "Nuovi Prodotti"
reset_stock_levels: Resetta le quantità disponibili alla quantità predefinita
changes_to: Cambia in
one_override: una si è sovrascritta
overrides: sovrascrive
remain_unsaved: restano da memorizzare
no_changes_to_save: Nessuna modifica da salvare.
no_authorisation: "Non abbiamo l'autorizzazione per salvare queste modifiche. "
some_trouble: "Abbiamo avuto problemi durante il salvataggio: %{errors}"
@@ -2449,16 +2466,23 @@ it:
admin:
orders:
index:
listing_orders: "Listino Ordini"
new_order: "Nuovo ordine"
capture: "Cattura"
ship: "Spedizione"
edit: "Modifica"
note: "Nota"
first: "Primo"
last: "Ultimo"
previous: "Precedente"
next: "Prossimo"
loading: "Caricamento"
no_orders_found: "Nessuna gentile richiesta trovata"
results_found: "%{number} Risultati trovati."
viewing: "Guardando da %{start} a %{end}"
print_invoices: "Stampa fatture"
invoice:
issued_on: Emesso il
tax_invoice: FATTURA DELLE TASSE
code: Codice
from: Da
@@ -2490,12 +2514,18 @@ it:
stripe_disabled_msg: I pagamenti Stripe sono stati disabilitati dall'amministratore di sistema.
request_failed_msg: Spiacenti, qualcosa è andato storto mentre cercavamo di verificare i dettagli dell'account con Stripe...
account_missing_msg: Non esiste un account Stripe per questa azienda.
connect_one: Connetti One
access_revoked_msg: L'accesso a questo account Stripe è stato revocato, per favore ricollega il tuo account.
status: Stato
connected: Connesso
account_id: Account ID
business_name: Ragione sociale
charges_enabled: Cambi Consentiti
payments:
source_forms:
stripe:
error_saving_payment: Errore memorizzando il pagamento
submitting_payment: Eseguendo il pagamento
products:
new:
title: 'Nuovo prodotto'
@@ -2514,7 +2544,9 @@ it:
inherits_properties?: Eredita proprietà?
available_on: Disponibile il
av_on: "Disp. il"
import_date: "Data di importazione"
products_variant:
variant_has_n_overrides: "Questa variante ha %{n} sostituzione/i"
new_variant: "Nuova variante"
product_name: Nome Prodotto
primary_taxon_form:
@@ -2523,14 +2555,19 @@ it:
group_buy: "Acquisto di gruppo?"
display_as:
display_as: Visualizza come
reports:
table:
select_and_search: "Seleziona filtri e clicca su %{option} per accedere al tuo dato"
users:
index:
listing_users: "Elenco Utenti"
new_user: "Nuovo utente"
user: "Utente"
enterprise_limit: "Limite azienda"
search: "Cerca"
email: "Email"
edit:
editing_user: "Modificando Utente"
back_to_users_list: "Torna all'elenco degli utenti"
general_settings: "Impostazioni generali"
form:
@@ -2548,6 +2585,7 @@ it:
edit:
legal_settings: "Impostazioni Legali"
cookies_consent_banner_toggle: "Mostra banner di consenso per i cookie"
privacy_policy_url: "Privacy Policy URL"
enterprises_require_tos: "Le aziende devono accettare i Termini di Servizio"
cookies_policy_matomo_section: "Visualizza la sezione di Matomo nella pagina della cookie policy"
cookies_policy_ga_section: "Visualizza la sezione di Google Analytics nella pagina della cookie policy"
@@ -2556,7 +2594,12 @@ it:
payment:
stripe:
choose_one: Scegli uno
enter_new_card: Inserire dettagli per una nuova carta
used_saved_card: "usare la carta salvata"
or_enter_new_card: "Oppure, inserire dettagli di una nuova carta"
remember_this_card: Ricordare questa Carta?
date_picker:
format: '%Y-%m-%d'
js_format: 'aa-mm-gg'
inventory: Inventario
orders:
@@ -2567,6 +2610,7 @@ it:
order_mailer:
invoice_email:
hi: "Ciao %{name}"
invoice_attached_text: 'Aggiunge una fattura per il tuo recente ordine di '
order_state:
address: indirizzo
adjustments: aggiustamenti
@@ -2659,5 +2703,6 @@ it:
authorised_shops: Negozi autorizzati
authorised_shops:
shop_name: "Nome del negozio"
allow_charges?: "Consentire ricarichi"
localized_number:
invalid_format: 'Formato non valido: inserire un numero.'

View File

@@ -1027,6 +1027,7 @@ nb:
this_is_an_estimate: |
De viste prisene er bare et estimat og beregnet på det tidspunktet abonnementet endres.
Hvis du endrer priser eller avgifter, vil ordrer bli oppdatert, men abonnementet vil fortsatt vise de gamle verdiene.
not_in_open_and_upcoming_order_cycles_warning: "Det er ingen åpne eller kommende bestillingsrunder for dette produktet."
details:
details: Detaljer
invalid_error: Oops! Vennligst fyll inn alle obligatoriske felter...
@@ -1041,6 +1042,7 @@ nb:
details: Detaljer
address: Adresse
products: Produkter
no_open_or_upcoming_order_cycle: "Ingen kommende bestillingsrunde"
product_already_in_order: Dette produktet er allerede lagt til i bestillingen. Vennligst rediger mengden direkte.
orders:
number: Antall

View File

@@ -13,7 +13,7 @@ job_type :enqueue_job, "cd :path; :environment_variable=:environment bundle exe
every 1.hour do
rake 'openfoodnetwork:cache:check_products_integrity'
rake 'ofn:cache:check_products_integrity'
end
every 1.day, at: '12:05am' do
@@ -35,10 +35,10 @@ every 5.minutes do
end
every 1.day, at: '1:00am' do
rake 'openfoodnetwork:billing:update_account_invoices'
rake 'ofn:billing:update_account_invoices'
end
# On the 2nd of every month at 1:30am
every '30 1 2 * *' do
rake 'openfoodnetwork:billing:finalize_account_invoices'
rake 'ofn:billing:finalize_account_invoices'
end

View File

@@ -33,6 +33,10 @@ module OpenFoodNetwork
Spree::Variant.where(is_master: false).ransack(search_params.merge(m: 'or')).result
end
def distributor
Enterprise.find params[:distributor_id]
end
def scope_to_schedule
@variants = @variants.in_schedule(params[:schedule_id])
end
@@ -42,12 +46,29 @@ module OpenFoodNetwork
end
def scope_to_distributor
distributor = Enterprise.find params[:distributor_id]
if params[:eligible_for_subscriptions]
scope_to_eligible_for_subscriptions_in_distributor
else
scope_to_available_for_orders_in_distributor
end
end
def scope_to_available_for_orders_in_distributor
@variants = @variants.in_distributor(distributor)
scope_variants_to_distributor(@variants, distributor)
end
def scope_to_eligible_for_subscriptions_in_distributor
eligible_variants_scope = SubscriptionVariantsService.eligible_variants(distributor)
@variants = @variants.merge(eligible_variants_scope)
scope_variants_to_distributor(@variants, distributor)
end
def scope_variants_to_distributor(variants, distributor)
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
# Perform scoping after all filtering is done.
# Filtering could be a problem on scoped variants.
@variants.each { |v| scoper.scope(v) }
variants.each { |v| scoper.scope(v) }
end
end
end

View File

@@ -1,4 +1,4 @@
namespace :openfoodnetwork do
namespace :ofn do
namespace :billing do
desc 'Update enterprise user invoices'
task update_account_invoices: :environment do

View File

@@ -1,6 +1,6 @@
require 'open_food_network/products_cache_integrity_checker'
namespace :openfoodnetwork do
namespace :ofn do
namespace :cache do
desc 'check the integrity of the products cache'
task :check_products_integrity => :environment do

View File

@@ -1,4 +1,4 @@
namespace :openfoodnetwork do
namespace :ofn do
namespace :data do
desc "Adding relationships based on recent order cycles"
task :create_order_cycle_relationships => :environment do

View File

@@ -1,4 +1,4 @@
namespace :openfoodnetwork do
namespace :ofn do
namespace :dev do
desc 'load sample data'
task load_sample_data: :environment do

View File

@@ -1,6 +1,6 @@
require 'csv'
namespace :openfoodnetwork do
namespace :ofn do
namespace :dev do
desc 'export enterprises to CSV'
task :export_enterprises => :environment do

View File

@@ -1,4 +1,4 @@
namespace :openfoodnetwork do
namespace :ofn do
namespace :specs do
namespace :engines do
def detect_engine_paths

View File

@@ -1 +1 @@
producer,distributor,name,display_name,units,unit_type,price,on_hand
producer,distributor,name,display_name,variant_unit_name,sku,units,unit_type,price,on_hand,on_demand
1 producer distributor name display_name variant_unit_name sku units unit_type price on_hand on_demand

View File

@@ -52,7 +52,7 @@ printf '\n\n' | bundle exec rake db:setup db:test:prepare
printf '\n'
# Load some default data for your environment
bundle exec rake openfoodnetwork:dev:load_sample_data
bundle exec rake ofn:dev:load_sample_data
printf '\n'
printf "${YELLOW}WELCOME TO OPEN FOOD NETWORK!\n"

View File

@@ -10,9 +10,9 @@ describe Admin::SubscriptionLineItemsController, type: :controller do
let(:unmanaged_shop) { create(:enterprise) }
let!(:product) { create(:product) }
let!(:variant) { create(:variant, product: product, unit_value: '100', price: 15.00, option_values: []) }
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant], enterprise_fees: [enterprise_fee]) }
let!(:enterprise_fee) { create(:enterprise_fee, amount: 3.50) }
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant], enterprise_fees: [enterprise_fee]) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
let(:unmanaged_schedule) { create(:schedule, order_cycles: [create(:simple_order_cycle, coordinator: unmanaged_shop)]) }
@@ -42,6 +42,8 @@ describe Admin::SubscriptionLineItemsController, type: :controller do
before { params.merge!(shop_id: shop.id) }
context "but the shop doesn't have permission to sell product in question" do
let!(:outgoing_exchange) { }
it "returns an error" do
spree_post :build, params
json_response = JSON.parse(response.body)

View File

@@ -341,7 +341,7 @@ describe Admin::SubscriptionsController, type: :controller do
end
context 'with subscription_line_items params' do
let!(:product2) { create(:product, supplier: shop) }
let!(:product2) { create(:product) }
let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) }
before do

View File

@@ -11,7 +11,9 @@ module Api
let!(:distributor2) { create(:distributor_enterprise) }
let!(:supplier) { create(:supplier_enterprise) }
let!(:coordinator) { create(:distributor_enterprise) }
let!(:coordinator2) { create(:distributor_enterprise) }
let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) }
let!(:order_cycle2) { create(:simple_order_cycle, coordinator: coordinator2) }
let!(:order1) do
create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now,
distributor: distributor, billing_address: create(:address) )
@@ -24,7 +26,9 @@ module Api
create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now,
distributor: distributor, billing_address: create(:address) )
end
let!(:order4) { create(:completed_order_with_fees) }
let!(:order4) do
create(:completed_order_with_fees, order_cycle: order_cycle2, distributor: distributor2)
end
let!(:order5) { create(:order, state: 'cart', completed_at: nil) }
let!(:line_item1) do
create(:line_item, order: order1,
@@ -148,7 +152,7 @@ module Api
get :index, per_page: 15, page: 1
pagination_data = {
'results' => 3,
'results' => 2,
'pages' => 1,
'page' => 1,
'per_page' => 15

View File

@@ -2,10 +2,11 @@ require 'spec_helper'
describe Spree::Admin::InvoicesController, type: :controller do
let(:order) { create(:order_with_totals_and_distribution) }
let(:user) { create(:admin_user) }
let(:enterprise_user) { create(:user) }
let!(:enterprise) { create(:enterprise, owner: enterprise_user) }
before do
allow(controller).to receive(:spree_current_user) { user }
allow(controller).to receive(:spree_current_user) { enterprise_user }
end
describe "#create" do

View File

@@ -439,7 +439,7 @@ feature %q{
expect(page).to have_selector "tr#li_#{li3.id}"
fill_in "quick_search", :with => o1.email
expect(page).to have_selector "tr#li_#{li1.id}"
expect(page).to have_no_selector "tr#li_#{li2.id}", true
expect(page).to have_no_selector "tr#li_#{li2.id}"
expect(page).to have_no_selector "tr#li_#{li3.id}"
end
end
@@ -567,6 +567,7 @@ feature %q{
context "when a filter has been applied" do
it "only toggles checkboxes which are in filteredLineItems" do
fill_in "quick_search", with: o1.number
expect(page).to have_no_selector "tr#li_#{li2.id}"
check "toggle_bulk"
fill_in "quick_search", with: ''
expect(find("tr#li_#{li1.id} input[type='checkbox'][name='bulk']").checked?).to be true
@@ -577,11 +578,13 @@ feature %q{
it "only applies the delete action to filteredLineItems" do
check "toggle_bulk"
fill_in "quick_search", with: o1.number
expect(page).to have_no_selector "tr#li_#{li2.id}"
find("div#bulk-actions-dropdown").click
find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click
fill_in "quick_search", with: ''
expect(page).to have_no_selector "tr#li_#{li1.id}"
fill_in "quick_search", with: ''
expect(page).to have_selector "tr#li_#{li2.id}"
expect(page).to have_no_selector "tr#li_#{li1.id}"
end
end
end
@@ -740,10 +743,11 @@ feature %q{
end
def select_date(date)
current_month = Time.zone.today.strftime("%B")
target_month = date.strftime("%B")
# Wait for datepicker to open and be associated to the datepicker trigger.
expect(page).to have_selector("#ui-datepicker-div")
navigate_datepicker_to_month date
find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-prev').click if current_month != target_month
find('#ui-datepicker-div .ui-datepicker-calendar .ui-state-default', text: date.strftime("%e").to_s.strip, exact_text: true).click
end
end

View File

@@ -762,9 +762,9 @@ feature %q{
# Shows spinner whilst loading
expect(page).to have_css "img.spinner", visible: true
expect(page).to have_no_css "img.spinner", visible: true
end
expect(page).to have_no_css "img.spinner", visible: true
expect(page).to have_no_selector "div.reveal-modal"
within "table#listing_products tr#p_#{product.id}" do

View File

@@ -35,8 +35,7 @@ feature "Managing enterprise images" do
go_to_images
within ".page-admin-enterprises-form__logo-field-group" do
expect(page).to have_selector(".image-field-group__preview-image")
expect(html).to include("logo-white.png")
expect_preview_image "logo-white.png"
end
# Replacing image
@@ -47,8 +46,7 @@ feature "Managing enterprise images" do
go_to_images
within ".page-admin-enterprises-form__logo-field-group" do
expect(page).to have_selector(".image-field-group__preview-image")
expect(html).to include("logo-black.png")
expect_preview_image "logo-black.png"
end
# Removing image
@@ -60,7 +58,7 @@ feature "Managing enterprise images" do
expect(page).to have_content("Logo removed successfully")
within ".page-admin-enterprises-form__logo-field-group" do
expect(page).to have_no_selector(".image-field-group__preview-image")
expect_no_preview_image
end
end
@@ -73,8 +71,7 @@ feature "Managing enterprise images" do
go_to_images
within ".page-admin-enterprises-form__promo-image-field-group" do
expect(page).to have_selector(".image-field-group__preview-image")
expect(html).to include("logo-white.jpg")
expect_preview_image "logo-white.jpg"
end
# Replacing image
@@ -85,8 +82,7 @@ feature "Managing enterprise images" do
go_to_images
within ".page-admin-enterprises-form__promo-image-field-group" do
expect(page).to have_selector(".image-field-group__preview-image")
expect(html).to include("logo-black.jpg")
expect_preview_image "logo-black.jpg"
end
# Removing image
@@ -98,9 +94,17 @@ feature "Managing enterprise images" do
expect(page).to have_content("Promo image removed successfully")
within ".page-admin-enterprises-form__promo-image-field-group" do
expect(page).to have_no_selector(".image-field-group__preview-image")
expect_no_preview_image
end
end
end
end
def expect_preview_image(file_name)
expect(page).to have_selector(".image-field-group__preview-image[src*='#{file_name}']")
end
def expect_no_preview_image
expect(page).to have_no_selector(".image-field-group__preview-image")
end
end

View File

@@ -46,7 +46,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_no_selector '.invalid-count'
@@ -89,7 +89,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "2"
@@ -112,7 +112,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
expect(page).to have_selector '.item-count', text: "1"
expect(page).to have_selector '.create-count', text: "1"
@@ -142,7 +142,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
save_data
@@ -164,7 +164,7 @@ feature "Product Import", js: true do
end
expect(page).to have_selector 'div#s2id_import_date_filter'
import_time = carrots.import_date.to_date.to_formatted_s(:long).gsub(' ', ' ')
import_time = carrots.import_date.to_date.to_formatted_s(:long)
select2_select import_time, from: "import_date_filter"
expect(page).to have_field "product_name", with: carrots.name
@@ -188,7 +188,7 @@ feature "Product Import", js: true do
click_button 'Upload'
import_data
proceed_to_validation
save_data
@@ -213,7 +213,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
expect(page).to have_selector '.item-count', text: "3"
expect(page).to_not have_selector '.invalid-count'
@@ -252,7 +252,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
expect(page).to have_selector '.item-count', text: "3"
expect(page).to have_no_selector '.invalid-count'
@@ -292,6 +292,49 @@ feature "Product Import", js: true do
expect(page).to have_content 'Cabbage'
end
end
it "handles on_demand and on_hand validations with inventory" do
csv_data = CSV.generate do |csv|
csv << ["name", "distributor", "producer", "category", "on_hand", "price", "units", "on_demand"]
csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", nil, "3.20", "500", "true"]
csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "6", "6.50", "500", "false"]
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "Vegetables", nil, "1.50", "500", nil]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
select2_select I18n.t('admin.product_import.index.inventories'), from: "settings_import_into"
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
expect(page).to have_selector '.item-count', text: "3"
expect(page).to have_no_selector '.invalid-count'
expect(page).to have_selector '.inv-create-count', text: '2'
expect(page).to have_selector '.inv-update-count', text: '1'
save_data
expect(page).to have_selector '.inv-created-count', text: '2'
expect(page).to have_selector '.inv-updated-count', text: '1'
beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
expect(Float(beans_override.price)).to eq 3.20
expect(beans_override.count_on_hand).to be_nil
expect(beans_override.on_demand).to be_truthy
expect(Float(sprouts_override.price)).to eq 6.50
expect(sprouts_override.count_on_hand).to eq 6
expect(sprouts_override.on_demand).to eq false
expect(Float(cabbage_override.price)).to eq 1.50
expect(cabbage_override.count_on_hand).to be_nil
expect(cabbage_override.on_demand).to be_nil
end
end
describe "when dealing with uploaded files" do
@@ -349,7 +392,7 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
import_data
proceed_to_validation
expect(page).to have_content I18n.t('admin.product_import.import.validation_overview')
expect(page).to have_selector '.item-count', text: "2"
@@ -361,9 +404,61 @@ feature "Product Import", js: true do
end
end
describe "handling a large file (120 data rows)" do
let!(:producer) { enterprise }
let(:tmp_csv_path) { "/tmp/test.csv" }
before do
quick_login_as admin
visit main_app.admin_product_import_path
end
context "when importing to product list" do
def write_tmp_csv_file
CSV.open(tmp_csv_path, "w") do |csv|
csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type",
"tax_category", "shipping_category"]
120.times do |i|
csv << ["Imported Product #{i + 1}", producer.name, category.name, 1, "1.00", "500",
"g", tax_category.name, shipping_category.name]
end
end
end
before { write_tmp_csv_file }
it "validates and saves all batches" do
# Upload and validate file.
attach_file "file", tmp_csv_path
click_button I18n.t("admin.product_import.index.upload")
proceed_to_validation
# Check that all rows are validated.
heading = "120 #{I18n.t("admin.product_import.import.products_to_create")}"
find(".panel-header", text: heading).click
expect(page).to have_content "Imported Product 10"
expect(page).to have_content "Imported Product 60"
expect(page).to have_content "Imported Product 110"
# Save file.
proceed_with_save
# Be extra patient.
expect_progress_percentages "33%", "67%", "100%"
expect_import_completed
# Check that all rows are saved.
expect(producer.supplied_products.find_by_name("Imported Product 10")).to be_present
expect(producer.supplied_products.find_by_name("Imported Product 60")).to be_present
expect(producer.supplied_products.find_by_name("Imported Product 110")).to be_present
end
end
end
private
def import_data
def proceed_to_validation
expect(page).to have_selector 'a.button.proceed', visible: true
click_link I18n.t('admin.product_import.import.import')
expect(page).to have_selector 'form.product-import', visible: true
@@ -372,8 +467,22 @@ feature "Product Import", js: true do
def save_data
expect(page).to have_selector 'a.button.proceed', visible: true
click_link I18n.t('admin.product_import.import.save')
proceed_with_save
expect(page).to have_selector 'div.save-results', visible: true
expect_import_completed
end
def expect_progress_percentages(*percentages)
percentages.each do |percentage|
expect(page).to have_selector ".progress-interface", text: percentage
end
end
def proceed_with_save
click_link I18n.t("admin.product_import.import.save")
end
def expect_import_completed
expect(page).to have_content I18n.t('admin.product_import.save_results.final_results')
end
end

View File

@@ -29,10 +29,13 @@ feature 'shipping methods' do
fill_in 'shipping_method_name', with: 'Carrier Pidgeon'
check "shipping_method_distributor_ids_#{d1.id}"
check "shipping_method_distributor_ids_#{d2.id}"
click_button 'Create'
click_button I18n.t("actions.create")
expect(page).to have_no_button I18n.t("actions.create")
# Then the shipping method should have its distributor set
flash_message.should == 'Shipping method "Carrier Pidgeon" has been successfully created!'
message = "Shipping method \"Carrier Pidgeon\" has been successfully created!"
expect(page).to have_flash_message message
sm = Spree::ShippingMethod.last
sm.name.should == 'Carrier Pidgeon'
@@ -98,9 +101,12 @@ feature 'shipping methods' do
expect(page).to have_css '.tag-item'
end
click_button 'Create'
click_button I18n.t("actions.create")
expect(page).to have_no_button I18n.t("actions.create")
message = "Shipping method \"Teleport\" has been successfully created!"
expect(page).to have_flash_message message
flash_message.should == 'Shipping method "Teleport" has been successfully created!'
expect(first('tags-input .tag-list ti-tag-item')).to have_content "local"
shipping_method = Spree::ShippingMethod.find_by_name('Teleport')

View File

@@ -145,23 +145,25 @@ feature 'Subscriptions' do
let!(:customer_user) { create(:user) }
let!(:credit_card1) { create(:credit_card, user: customer_user, cc_type: 'visa', last_digits: 1111, month: 10, year: 2030) }
let!(:customer) { create(:customer, enterprise: shop, bill_address: address, user: customer_user, allow_charges: true) }
let!(:product1) { create(:product, supplier: shop) }
let!(:product2) { create(:product, supplier: shop) }
let!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) }
let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) }
let!(:test_product) { create(:product, supplier: shop, distributors: []) }
let!(:test_variant) { create(:variant, product: test_product, unit_value: "100", price: 12.00, option_values: []) }
let!(:shop_product) { create(:product, supplier: shop, distributors: [shop]) }
let!(:shop_variant) { create(:variant, product: shop_product, unit_value: "1000", price: 6.00, option_values: []) }
let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) }
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant1, variant2], enterprise_fees: [enterprise_fee]) }
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [test_variant, shop_variant], enterprise_fees: [enterprise_fee]) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
let!(:payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) }
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
it "passes the smoke test" do
before do
visit admin_subscriptions_path
click_link 'New Subscription'
select2_select shop.name, from: 'new_subscription_shop_id'
click_button 'Continue'
click_link "New Subscription"
select2_select shop.name, from: "new_subscription_shop_id"
click_button "Continue"
end
it "passes the smoke test" do
select2_select customer.email, from: 'customer_id'
select2_select schedule.name, from: 'schedule_id'
select2_select payment_method.name, from: 'payment_method_id'
@@ -215,11 +217,9 @@ feature 'Subscriptions' do
expect(page).to have_content 'Please add at least one product'
# Adding a product and getting a price estimate
select2_search_async product1.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop'
fill_in 'add_quantity', with: 2
click_link 'Add'
add_variant_to_subscription test_variant, 2
within 'table#subscription-line-items tr.item', match: :first do
expect(page).to have_selector 'td.description', text: "#{product1.name} - #{variant1.full_name}"
expect(page).to have_selector '.description', text: "#{test_product.name} - #{test_variant.full_name}"
expect(page).to have_selector 'td.price', text: "$13.75"
expect(page).to have_input 'quantity', with: "2"
expect(page).to have_selector 'td.total', text: "$27.50"
@@ -241,11 +241,9 @@ feature 'Subscriptions' do
click_button('edit-products')
# Adding a new product
select2_search_async product2.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop'
fill_in 'add_quantity', with: 3
click_link 'Add'
add_variant_to_subscription shop_variant, 3
within 'table#subscription-line-items tr.item', match: :first do
expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}"
expect(page).to have_selector '.description', text: "#{shop_product.name} - #{shop_variant.full_name}"
expect(page).to have_selector 'td.price', text: "$7.75"
expect(page).to have_input 'quantity', with: "3"
expect(page).to have_selector 'td.total', text: "$23.25"
@@ -264,7 +262,7 @@ feature 'Subscriptions' do
# Prices are shown in the index
within 'table#subscription-line-items tr.item', match: :first do
expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}"
expect(page).to have_selector '.description', text: "#{shop_product.name} - #{shop_variant.full_name}"
expect(page).to have_selector 'td.price', text: "$7.75"
expect(page).to have_input 'quantity', with: "3"
expect(page).to have_selector 'td.total', text: "$23.25"
@@ -282,142 +280,249 @@ feature 'Subscriptions' do
# Standing Line Items are created
expect(subscription.subscription_line_items.count).to eq 1
subscription_line_item = subscription.subscription_line_items.first
expect(subscription_line_item.variant).to eq variant2
expect(subscription_line_item.variant).to eq shop_variant
expect(subscription_line_item.quantity).to eq 3
end
end
context 'editing an existing subscription' do
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!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) }
let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) }
let!(:variant3) { create(:variant, product: product3, unit_value: '10000', price: 22.00, option_values: []) }
let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) }
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant1, variant2], enterprise_fees: [enterprise_fee]) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) }
let!(:payment_method) { create(:payment_method, distributors: [shop]) }
let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) }
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
let!(:subscription) {
create(:subscription,
shop: shop,
customer: customer,
schedule: schedule,
payment_method: payment_method,
shipping_method: shipping_method,
subscription_line_items: [create(:subscription_line_item, variant: variant1, quantity: 2, price_estimate: 13.75)],
with_proxy_orders: true)
}
context 'editing an existing subscription' do
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!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) }
let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) }
let!(:variant3) { create(:variant, product: product3, unit_value: '10000', price: 22.00, option_values: []) }
let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) }
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant1, variant2], enterprise_fees: [enterprise_fee]) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) }
let!(:payment_method) { create(:payment_method, distributors: [shop]) }
let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) }
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
let!(:subscription) {
create(:subscription,
shop: shop,
customer: customer,
schedule: schedule,
payment_method: payment_method,
shipping_method: shipping_method,
subscription_line_items: [create(:subscription_line_item, variant: variant1, quantity: 2, price_estimate: 13.75)],
with_proxy_orders: true)
}
it "passes the smoke test" do
visit edit_admin_subscription_path(subscription)
it "passes the smoke test" do
visit edit_admin_subscription_path(subscription)
# Customer and Schedule cannot be edited
click_button 'edit-details'
expect(page).to have_selector '#s2id_customer_id.select2-container-disabled'
expect(page).to have_selector '#s2id_schedule_id.select2-container-disabled'
# Customer and Schedule cannot be edited
click_button 'edit-details'
expect(page).to have_selector '#s2id_customer_id.select2-container-disabled'
expect(page).to have_selector '#s2id_schedule_id.select2-container-disabled'
# Can't use a Stripe payment method because customer does not allow it
select2_select stripe_payment_method.name, from: 'payment_method_id'
expect(page).to have_content I18n.t('admin.subscriptions.details.charges_not_allowed')
click_button 'Save Changes'
expect(page).to have_content 'Credit card charges are not allowed by this customer'
select2_select payment_method.name, from: 'payment_method_id'
click_button 'Review'
# Can't use a Stripe payment method because customer does not allow it
select2_select stripe_payment_method.name, from: 'payment_method_id'
expect(page).to have_content I18n.t('admin.subscriptions.details.charges_not_allowed')
click_button 'Save Changes'
expect(page).to have_content 'Credit card charges are not allowed by this customer'
select2_select payment_method.name, from: 'payment_method_id'
click_button 'Review'
# Existing products should be visible
click_button 'edit-products'
within "#sli_0" do
expect(page).to have_selector 'td.description', text: "#{product1.name} - #{variant1.full_name}"
expect(page).to have_selector 'td.price', text: "$13.75"
expect(page).to have_input 'quantity', with: "2"
expect(page).to have_selector 'td.total', text: "$27.50"
# Existing products should be visible
click_button 'edit-products'
within "#sli_0" do
expect(page).to have_selector '.description', text: "#{product1.name} - #{variant1.full_name}"
expect(page).to have_selector 'td.price', text: "$13.75"
expect(page).to have_input 'quantity', with: "2"
expect(page).to have_selector 'td.total', text: "$27.50"
# Remove variant1 from the subscription
find("a.delete-item").click
end
# Attempting to submit without a product
click_button 'Save Changes'
expect(page).to have_content 'Please add at least one product'
# Add variant2 to the subscription
select2_search_async product2.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop'
fill_in 'add_quantity', with: 1
click_link 'Add'
within "#sli_0" do
expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}"
expect(page).to have_selector 'td.price', text: "$7.75"
expect(page).to have_input 'quantity', with: "1"
expect(page).to have_selector 'td.total', text: "$7.75"
end
# Total should be $7.75
expect(page).to have_selector '#order_form_total', text: "$7.75"
# Add variant3 to the subscription (even though it is not available)
select2_search_async product3.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop'
fill_in 'add_quantity', with: 1
click_link 'Add'
within "#sli_1" do
expect(page).to have_selector 'td.description', text: "#{product3.name} - #{variant3.full_name}"
expect(page).to have_selector 'td.price', text: "$22.00"
expect(page).to have_input 'quantity', with: "1"
expect(page).to have_selector 'td.total', text: "$22.00"
end
# Total should be $29.75
expect(page).to have_selector '#order_form_total', text: "$29.75"
click_button 'Save Changes'
expect(page).to have_content "#{product3.name} - #{variant3.full_name} is not available from the selected schedule"
# Remove variant3 from the subscription
within '#sli_1' do
find("a.delete-item").click
end
click_button 'Save Changes'
expect(page).to have_current_path admin_subscriptions_path
select2_select shop.name, from: "shop_id"
expect(page).to have_selector "td.items.panel-toggle"
first("td.items.panel-toggle").click
# Total should be $7.75
expect(page).to have_selector '#order_form_total', text: "$7.75"
expect(page).to have_selector 'tr.item', count: 1
expect(subscription.reload.subscription_line_items.length).to eq 1
expect(subscription.subscription_line_items.first.variant).to eq variant2
# Remove variant1 from the subscription
find("a.delete-item").click
end
context "with initialised order that has been changed" do
let(:proxy_order) { subscription.proxy_orders.first }
let(:order) { proxy_order.initialise_order! }
let(:line_item) { order.line_items.first }
# Attempting to submit without a product
click_button 'Save Changes'
expect(page).to have_content 'Please add at least one product'
before { line_item.update_attributes(quantity: 3) }
# Add variant2 to the subscription
add_variant_to_subscription(variant2, 1)
within "#sli_0" do
expect(page).to have_selector '.description', text: "#{product2.name} - #{variant2.full_name}"
expect(page).to have_selector 'td.price', text: "$7.75"
expect(page).to have_input 'quantity', with: "1"
expect(page).to have_selector 'td.total', text: "$7.75"
end
it "reports issues encountered during the update" do
visit edit_admin_subscription_path(subscription)
click_button 'edit-products'
# Total should be $7.75
expect(page).to have_selector '#order_form_total', text: "$7.75"
within "#sli_0" do
fill_in 'quantity', with: "1"
end
# Add variant3 to the subscription (even though it is not available)
add_variant_to_subscription(variant3, 1)
within "#sli_1" do
expect(page).to have_selector '.description', text: "#{product3.name} - #{variant3.full_name}"
expect(page).to have_selector 'td.price', text: "$22.00"
expect(page).to have_input 'quantity', with: "1"
expect(page).to have_selector 'td.total', text: "$22.00"
end
click_button 'Save Changes'
expect(page).to have_content 'Saved'
# Total should be $29.75
expect(page).to have_selector '#order_form_total', text: "$29.75"
expect(page).to have_selector "#order_update_issues_dialog .message", text: I18n.t("admin.subscriptions.order_update_issues_msg")
# Remove variant3 from the subscription
within '#sli_1' do
find("a.delete-item").click
end
click_button 'Save Changes'
expect(page).to have_current_path admin_subscriptions_path
select2_select shop.name, from: "shop_id"
expect(page).to have_selector "td.items.panel-toggle"
first("td.items.panel-toggle").click
# Total should be $7.75
expect(page).to have_selector '#order_form_total', text: "$7.75"
expect(page).to have_selector 'tr.item', count: 1
expect(subscription.reload.subscription_line_items.length).to eq 1
expect(subscription.subscription_line_items.first.variant).to eq variant2
end
context "with initialised order that has been changed" do
let(:proxy_order) { subscription.proxy_orders.first }
let(:order) { proxy_order.initialise_order! }
let(:line_item) { order.line_items.first }
before { line_item.update_attributes(quantity: 3) }
it "reports issues encountered during the update" do
visit edit_admin_subscription_path(subscription)
click_button 'edit-products'
within "#sli_0" do
fill_in 'quantity', with: "1"
end
click_button 'Save Changes'
expect(page).to have_content 'Saved'
expect(page).to have_selector "#order_update_issues_dialog .message", text: I18n.t("admin.subscriptions.order_update_issues_msg")
end
end
end
describe "allowed variants" do
let!(:customer) { create(:customer, enterprise: shop, allow_charges: true) }
let!(:credit_card) { create(:credit_card, user: customer.user) }
let!(:shop_product) { create(:product, supplier: shop, distributors: [shop]) }
let!(:shop_variant) { create(:variant, product: shop_product, unit_value: "2000") }
let!(:permitted_supplier) do
create(:supplier_enterprise).tap do |supplier|
create(:enterprise_relationship, child: shop, parent: supplier, permissions_list: [:add_to_order_cycle])
end
end
let!(:permitted_supplier_product) { create(:product, supplier: permitted_supplier, distributors: [shop]) }
let!(:permitted_supplier_variant) { create(:variant, product: permitted_supplier_product, unit_value: "2000") }
let!(:incoming_exchange_product) { create(:product, distributors: [shop]) }
let!(:incoming_exchange_variant) do
create(:variant, product: incoming_exchange_product, unit_value: "2000").tap do |variant|
create(:exchange, order_cycle: order_cycle, incoming: true, receiver: shop, variants: [variant])
end
end
let!(:outgoing_exchange_product) { create(:product, distributors: [shop]) }
let!(:outgoing_exchange_variant) do
create(:variant, product: outgoing_exchange_product, unit_value: "2000").tap do |variant|
create(:exchange, order_cycle: order_cycle, incoming: false, receiver: shop, variants: [variant])
end
end
let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) }
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
let!(:payment_method) { create(:stripe_payment_method, distributors: [shop], preferred_enterprise_id: shop.id) }
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
before do
visit admin_subscriptions_path
click_link "New Subscription"
select2_select shop.name, from: "new_subscription_shop_id"
click_button "Continue"
end
it "permit creating and editing of the subscription" do
# Fill in other details
fill_in_subscription_basic_details
click_button "Next"
expect(page).to have_content "BILLING ADDRESS"
click_button "Next"
# Add products
expect(page).to have_content "NAME OR SKU"
add_variant_to_subscription shop_variant, 3
expect_not_in_open_or_upcoming_order_cycle_warning 1
add_variant_to_subscription permitted_supplier_variant, 4
expect_not_in_open_or_upcoming_order_cycle_warning 2
add_variant_to_subscription incoming_exchange_variant, 5
expect_not_in_open_or_upcoming_order_cycle_warning 3
add_variant_to_subscription outgoing_exchange_variant, 6
expect_not_in_open_or_upcoming_order_cycle_warning 3
click_button "Next"
# Submit form
expect {
click_button "Create Subscription"
expect(page).to have_current_path admin_subscriptions_path
}.to change(Subscription, :count).by(1)
# Subscription line items are created
subscription = Subscription.last
expect(subscription.subscription_line_items.count).to eq 4
# Edit the subscription
visit edit_admin_subscription_path(subscription)
# Remove shop_variant from the subscription
click_button "edit-products"
within "#sli_0" do
expect(page).to have_selector ".description", text: shop_variant.name
find("a.delete-item").click
end
# Submit form
click_button "Save Changes"
expect(page).to have_current_path admin_subscriptions_path
# Subscription is saved
visit edit_admin_subscription_path(subscription)
expect(page).to have_selector "#subscription-line-items .item", count: 3
end
end
end
def fill_in_subscription_basic_details
select2_select customer.email, from: "customer_id"
select2_select schedule.name, from: "schedule_id"
select2_select payment_method.name, from: "payment_method_id"
select2_select shipping_method.name, from: "shipping_method_id"
find_field("begins_at").click
choose_today_from_datepicker
end
def expect_not_in_open_or_upcoming_order_cycle_warning(count)
expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: count
end
def add_variant_to_subscription(variant, quantity)
row_count = all("#subscription-line-items .item").length
variant_name = variant.full_name.present? ? "#{variant.name} - #{variant.full_name}" : variant.name
select2_search variant.name, from: I18n.t(:name_or_sku), dropdown_css: ".select2-drop", select_text: variant_name
fill_in "add_quantity", with: quantity
click_link "Add"
expect(page).to have_selector("#subscription-line-items .item", count: row_count + 1)
end
def variant_not_in_open_or_upcoming_order_cycle_warning
I18n.t("not_in_open_and_upcoming_order_cycles_warning",
scope: "admin.subscriptions.subscription_line_items")
end
end

View File

@@ -26,7 +26,7 @@ describe ProductImport::ProductImporter do
let!(:variant) { create(:variant, product_id: product.id, price: '8.50', on_hand: '100', unit_value: '500', display_name: 'Preexisting Banana') }
let!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans', unit_value: '500', primary_taxon_id: category.id, description: nil) }
let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts', unit_value: '500', primary_taxon_id: category.id) }
let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '500', primary_taxon_id: category.id) }
let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '1', variant_unit_scale: nil, variant_unit: "items", variant_unit_name: "Whole", primary_taxon_id: category.id) }
let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500', primary_taxon_id: category.id) }
let!(:product6) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Beetroot', unit_value: '500', on_demand: true, variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) }
let!(:product7) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Tomato', unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) }
@@ -465,50 +465,106 @@ describe ProductImport::ProductImporter do
end
describe "importing items into inventory" do
before do
csv_data = CSV.generate do |csv|
csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type"]
csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g"]
csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g"]
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "500", "g"]
describe "creating and updating inventory" do
before do
csv_data = CSV.generate do |csv|
csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type", "variant_unit_name"]
csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g", ""]
csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g", ""]
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "1", "", "Whole"]
end
File.write('/tmp/test-m.csv', csv_data)
file = File.new('/tmp/test-m.csv')
settings = {'import_into' => 'inventories'}
@importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings)
end
File.write('/tmp/test-m.csv', csv_data)
file = File.new('/tmp/test-m.csv')
settings = {'import_into' => 'inventories'}
@importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings)
end
after { File.delete('/tmp/test-m.csv') }
after { File.delete('/tmp/test-m.csv') }
it "validates entries" do
@importer.validate_entries
entries = JSON.parse(@importer.entries_json)
it "validates entries" do
@importer.validate_entries
entries = JSON.parse(@importer.entries_json)
expect(filter('valid', entries)).to eq 3
expect(filter('invalid', entries)).to eq 0
expect(filter('create_inventory', entries)).to eq 2
expect(filter('update_inventory', entries)).to eq 1
expect(filter('valid', entries)).to eq 3
expect(filter('invalid', entries)).to eq 0
expect(filter('create_inventory', entries)).to eq 2
expect(filter('update_inventory', entries)).to eq 1
end
it "saves and updates inventory" do
@importer.save_entries
expect(@importer.inventory_created_count).to eq 2
expect(@importer.inventory_updated_count).to eq 1
expect(@importer.updated_ids).to be_a(Array)
expect(@importer.updated_ids.count).to eq 3
beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
expect(Float(beans_override.price)).to eq 3.20
expect(beans_override.count_on_hand).to eq 5
expect(Float(sprouts_override.price)).to eq 6.50
expect(sprouts_override.count_on_hand).to eq 6
expect(Float(cabbage_override.price)).to eq 1.50
expect(cabbage_override.count_on_hand).to eq 2001
end
end
it "saves and updates inventory" do
@importer.save_entries
describe "updating existing inventory referenced by display_name" do
before do
csv_data = CSV.generate do |csv|
csv << ["name", "display_name", "distributor", "producer", "on_hand", "price", "units"]
csv << ["Oats", "Porridge Oats", "Another Enterprise", "User Enterprise", "900", "", "500"]
end
File.write('/tmp/test-m.csv', csv_data)
file = File.new('/tmp/test-m.csv')
settings = {'import_into' => 'inventories'}
@importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings)
end
after { File.delete('/tmp/test-m.csv') }
expect(@importer.inventory_created_count).to eq 2
expect(@importer.inventory_updated_count).to eq 1
expect(@importer.updated_ids).to be_a(Array)
expect(@importer.updated_ids.count).to eq 3
it "updates inventory item correctly" do
@importer.save_entries
beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
expect(@importer.inventory_created_count).to eq 1
expect(Float(beans_override.price)).to eq 3.20
expect(beans_override.count_on_hand).to eq 5
override = VariantOverride.where(variant_id: variant2.id, hub_id: enterprise2.id).first
visible = InventoryItem.where(variant_id: variant2.id, enterprise_id: enterprise2.id).first.visible
expect(Float(sprouts_override.price)).to eq 6.50
expect(sprouts_override.count_on_hand).to eq 6
expect(override.count_on_hand).to eq 900
expect(visible).to be_truthy
end
end
expect(Float(cabbage_override.price)).to eq 1.50
expect(cabbage_override.count_on_hand).to eq 2001
describe "updating existing item that was set to hidden in inventory" do
before do
InventoryItem.create(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id, visible: false)
csv_data = CSV.generate do |csv|
csv << ["name", "distributor", "producer", "on_hand", "price", "units", "variant_unit_name"]
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "1", "Whole"]
end
File.write('/tmp/test-m.csv', csv_data)
file = File.new('/tmp/test-m.csv')
settings = {'import_into' => 'inventories'}
@importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings)
end
after { File.delete('/tmp/test-m.csv') }
it "sets the item to visible in inventory when the item is updated" do
@importer.save_entries
expect(@importer.inventory_updated_count).to eq 1
override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
visible = InventoryItem.where(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id).first.visible
expect(override.count_on_hand).to eq 900
expect(visible).to be_truthy
end
end
end

View File

@@ -1,8 +1,11 @@
require "spec_helper"
describe SubscriptionValidator do
let(:shop) { instance_double(Enterprise, name: "Shop") }
let(:owner) { create(:user) }
let(:shop) { create(:enterprise, name: "Shop", owner: owner) }
describe "delegation" do
let(:subscription) { create(:subscription) }
let(:subscription) { create(:subscription, shop: shop) }
let(:validator) { SubscriptionValidator.new(subscription) }
it "delegates to subscription" do
@@ -438,6 +441,7 @@ describe SubscriptionValidator do
context "but some variants are unavailable" do
let(:product) { instance_double(Spree::Product, name: "some_name") }
before do
allow(validator).to receive(:available_variant_ids) { [variant2.id] }
allow(variant1).to receive(:product) { product }
@@ -451,7 +455,9 @@ describe SubscriptionValidator do
end
context "and all requested variants are available" do
before { allow(validator).to receive(:available_variant_ids) { [variant1.id, variant2.id] } }
before do
allow(validator).to receive(:available_variant_ids) { [variant1.id, variant2.id] }
end
it "returns true" do
expect(validator.valid?).to be true

View File

@@ -0,0 +1,130 @@
require "spec_helper"
describe SubscriptionVariantsService do
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!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
let!(:subscription) { create(:subscription, shop: shop, schedule: schedule) }
let!(:subscription_line_item) do
create(:subscription_line_item, subscription: subscription, variant: variant)
end
let(:current_order_cycle) do
create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.ago,
orders_close_at: 1.week.from_now)
end
let(:future_order_cycle) do
create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.from_now,
orders_close_at: 2.weeks.from_now)
end
let(:past_order_cycle) do
create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.weeks.ago,
orders_close_at: 1.week.ago)
end
let!(:order_cycle) { current_order_cycle }
context "if the shop is the supplier for the product" do
let!(:producer) { shop }
it "is eligible" do
expect(described_class.eligible_variants(shop)).to include(variant)
end
end
context "if the supplier is permitted for the shop" do
let!(:enterprise_relationship) { create(:enterprise_relationship, child: shop, parent: product.supplier, permissions_list: [:add_to_order_cycle]) }
it "is eligible" do
expect(described_class.eligible_variants(shop)).to include(variant)
end
end
context "if the variant is involved in an exchange" do
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
context "if it is an incoming exchange where the shop is the receiver" do
let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) }
it "is not eligible" do
expect(described_class.eligible_variants(shop)).to_not include(variant)
end
end
context "if it is an outgoing exchange where the shop is the receiver" do
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) }
context "if the order cycle is currently open" do
let!(:order_cycle) { current_order_cycle }
it "is eligible" do
expect(described_class.eligible_variants(shop)).to include(variant)
end
end
context "if the order cycle opens in the future" do
let!(:order_cycle) { future_order_cycle }
it "is eligible" do
expect(described_class.eligible_variants(shop)).to include(variant)
end
end
context "if the order cycle closed in the past" do
let!(:order_cycle) { past_order_cycle }
it "is eligible" do
expect(described_class.eligible_variants(shop)).to include(variant)
end
end
end
end
context "if the variant is unrelated" do
it "is not eligible" do
expect(described_class.eligible_variants(shop)).to_not include(variant)
end
end
end
describe "checking if variant in open and upcoming order cycles" do
let!(:shop) { create(:enterprise) }
let!(:product) { create(:product) }
let!(:variant) { product.variants.first }
let!(:schedule) { create(:schedule) }
context "if the variant is involved in an exchange" do
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) }
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
context "if it is an incoming exchange where the shop is the receiver" do
let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) }
it "is is false" do
expect(described_class).not_to be_in_open_and_upcoming_order_cycles(shop, schedule, variant)
end
end
context "if it is an outgoing exchange where the shop is the receiver" do
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) }
it "is true" do
expect(described_class).to be_in_open_and_upcoming_order_cycles(shop, schedule, variant)
end
end
end
context "if the variant is unrelated" do
it "is false" do
expect(described_class).to_not be_in_open_and_upcoming_order_cycles(shop, schedule, variant)
end
end
end
end

View File

@@ -136,6 +136,7 @@ RSpec.configure do |config|
config.extend Spree::Api::TestingSupport::Setup, :type => :controller
config.include Spree::Api::TestingSupport::Helpers, :type => :controller
config.include OpenFoodNetwork::ControllerHelper, :type => :controller
config.include Features::DatepickerHelper, type: :feature
config.include OpenFoodNetwork::FeatureToggleHelper
config.include OpenFoodNetwork::FiltersHelper
config.include OpenFoodNetwork::EnterpriseGroupsHelper

View File

@@ -0,0 +1,33 @@
module Features
module DatepickerHelper
def choose_today_from_datepicker
within(".ui-datepicker-calendar") do
find(".ui-datepicker-today").click
end
end
def navigate_datepicker_to_month(date, reference_date = Time.zone.today)
month_and_year = date.strftime("%B %Y")
until datepicker_month_and_year == month_and_year.upcase
if date < reference_date
navigate_datepicker_to_previous_month
elsif date > reference_date
navigate_datepicker_to_next_month
end
end
end
def navigate_datepicker_to_previous_month
find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-prev').click
end
def navigate_datepicker_to_next_month
find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-next').click
end
def datepicker_month_and_year
find("#ui-datepicker-div .ui-datepicker-title").text
end
end
end

View File

@@ -0,0 +1,31 @@
RSpec::Matchers.define :have_flash_message do |message|
match do |node|
@message, @node = message, node
# Ignore leading and trailing whitespace. Later versions of Capybara have :exact_text option.
# The :exact option is not supported in has_selector?.
message_substring_regex = substring_match_regex(message)
node.has_selector?(".flash", text: message_substring_regex, visible: false)
end
failure_message do |actual|
"expected to find flash message ##{@message}"
end
match_when_negated do |node|
@message, @node = message, node
# Ignore leading and trailing whitespace. Later versions of Capybara have :exact_text option.
# The :exact option is not supported in has_selector?.
message_substring_regex = substring_match_regex(message)
node.has_no_selector?(".flash", text: message_substring_regex, visible: false)
end
failure_message_when_negated do |actual|
"expected not to find flash message ##{@message}"
end
def substring_match_regex(text)
/\A\s*#{Regexp.escape(text)}\s*\Z/
end
end

View File

@@ -105,6 +105,16 @@ module WebHelper
targetted_select2(value, options)
end
# Support having different texts to search for and to click in the select2
# field.
#
# This overrides the method in Spree.
def targetted_select2_search(value, options)
page.execute_script %Q{$('#{options[:from]}').select2('open')}
page.execute_script "$('#{options[:dropdown_css]} input.select2-input').val('#{value}').trigger('keyup-change');"
select_select2_result(options[:select_text] || value)
end
def multi_select2_select(value, options)
find("#s2id_#{options[:from]}").find('ul li.select2-search-field').click
select_select2_result(value)