mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-13 04:00:21 +00:00
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:
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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 -->
|
||||
35
.github/ISSUE_TEMPLATE/feature-template.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/feature-template.md
vendored
Normal 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
|
||||
18
.github/ISSUE_TEMPLATE/story-template.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/story-template.md
vendored
Normal 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. -->
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -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'
|
||||
|
||||
16
Gemfile.lock
16
Gemfile.lock
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
@import '../variables';
|
||||
|
||||
.admin-subscription-form-subscription-line-items {
|
||||
.not-in-open-and-upcoming-order-cycles-warning {
|
||||
color: $warning-red;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@import '../variables';
|
||||
|
||||
.admin-subscription-review-subscription-line-items {
|
||||
.not-in-open-and-upcoming-order-cycles-warning {
|
||||
color: $warning-red;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
39
app/services/subscription_variants_service.rb
Normal file
39
app/services/subscription_variants_service.rb
Normal 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
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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' } }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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é"
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace :openfoodnetwork do
|
||||
namespace :ofn do
|
||||
namespace :billing do
|
||||
desc 'Update enterprise user invoices'
|
||||
task update_account_invoices: :environment do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace :openfoodnetwork do
|
||||
namespace :ofn do
|
||||
namespace :dev do
|
||||
desc 'load sample data'
|
||||
task load_sample_data: :environment do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace :openfoodnetwork do
|
||||
namespace :ofn do
|
||||
namespace :specs do
|
||||
namespace :engines do
|
||||
def detect_engine_paths
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
130
spec/services/subscription_variants_service_spec.rb
Normal file
130
spec/services/subscription_variants_service_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
33
spec/support/features/datepicker_helper.rb
Normal file
33
spec/support/features/datepicker_helper.rb
Normal 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
|
||||
31
spec/support/matchers/flash_message_matchers.rb
Normal file
31
spec/support/matchers/flash_message_matchers.rb
Normal 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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user