mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Auto-merged master into uk/order_cycle_report on deployment.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, Columns, pendingChanges, shops) ->
|
||||
angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagsResource, $q, Columns, pendingChanges, shops) ->
|
||||
$scope.shop = {}
|
||||
$scope.shops = shops
|
||||
$scope.submitAll = pendingChanges.submitAll
|
||||
@@ -12,6 +12,16 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerR
|
||||
if $scope.shop.id?
|
||||
$scope.customers = index {enterprise_id: $scope.shop.id}
|
||||
|
||||
$scope.findTags = (query) ->
|
||||
defer = $q.defer()
|
||||
params =
|
||||
enterprise_id: $scope.shop.id
|
||||
TagsResource.index params, (data) =>
|
||||
filtered = data.filter (tag) ->
|
||||
tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
|
||||
defer.resolve filtered
|
||||
defer.promise
|
||||
|
||||
$scope.add = (email) ->
|
||||
params =
|
||||
enterprise_id: $scope.shop.id
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
angular.module("admin.customers").factory 'TagsResource', ($resource) ->
|
||||
$resource('/admin/tags.json', {}, {
|
||||
'index':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
cache: true
|
||||
params:
|
||||
enterprise_id: '@enterprise_id'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (DirtyProducts) ->
|
||||
angular.module("ofn.admin").directive "ofnTrackMaster", (DirtyProducts) ->
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
@@ -6,4 +6,3 @@ angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (Dirty
|
||||
DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue
|
||||
scope.displayDirtyProducts()
|
||||
viewValue
|
||||
]
|
||||
@@ -1,7 +0,0 @@
|
||||
angular.module('ofn.admin').filter "translate", ->
|
||||
(key, options) ->
|
||||
t(key, options)
|
||||
|
||||
angular.module('ofn.admin').filter "t", ->
|
||||
(key, options) ->
|
||||
t(key, options)
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils'])
|
||||
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'templates'])
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) ->
|
||||
restrict: "E"
|
||||
template: "<tags-input ng-model='object[tagsAttr]'>"
|
||||
templateUrl: "admin/tags_input.html"
|
||||
scope:
|
||||
object: "="
|
||||
tagsAttr: "@?"
|
||||
tagListAttr: "@?"
|
||||
findTags: "&"
|
||||
link: (scope, element, attrs) ->
|
||||
$timeout ->
|
||||
scope.tagsAttr ||= "tags"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("admin.utils").filter "translate", ->
|
||||
(key, options) ->
|
||||
t(key, options)
|
||||
|
||||
angular.module("admin.utils").filter "t", ->
|
||||
(key, options) ->
|
||||
t(key, options)
|
||||
@@ -7,6 +7,22 @@ Darkswarm.directive 'mapSearch', ($timeout)->
|
||||
link: (scope, elem, attrs, ctrl)->
|
||||
$timeout =>
|
||||
map = ctrl.getMap()
|
||||
|
||||
# Use OSM tiles server
|
||||
map.mapTypes.set 'OSM', new (google.maps.ImageMapType)(
|
||||
getTileUrl: (coord, zoom) ->
|
||||
# "Wrap" x (logitude) at 180th meridian properly
|
||||
# NB: Don't touch coord.x because coord param is by reference, and changing its x property breakes something in Google's lib
|
||||
tilesPerGlobe = 1 << zoom
|
||||
x = coord.x % tilesPerGlobe
|
||||
if x < 0
|
||||
x = tilesPerGlobe + x
|
||||
# Wrap y (latitude) in a like manner if you want to enable vertical infinite scroll
|
||||
'http://tile.openstreetmap.org/' + zoom + '/' + x + '/' + coord.y + '.png'
|
||||
tileSize: new (google.maps.Size)(256, 256)
|
||||
name: 'OpenStreetMap'
|
||||
maxZoom: 18)
|
||||
|
||||
input = (document.getElementById("pac-input"))
|
||||
map.controls[google.maps.ControlPosition.TOP_LEFT].push input
|
||||
searchBox = new google.maps.places.SearchBox((input))
|
||||
@@ -21,7 +37,7 @@ Darkswarm.directive 'mapSearch', ($timeout)->
|
||||
#map.setCenter place.geometry.location
|
||||
map.fitBounds place.geometry.viewport
|
||||
#map.fitBounds bounds
|
||||
|
||||
|
||||
# Bias the SearchBox results towards places that are within the bounds of the
|
||||
# current map's viewport.
|
||||
google.maps.event.addListener map, "bounds_changed", ->
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
Darkswarm.directive "ofnOnHand", ->
|
||||
restrict: 'A'
|
||||
require: "ngModel"
|
||||
|
||||
link: (scope, elem, attr, ngModel) ->
|
||||
# In cases where this field gets its value from the HTML element rather than the model,
|
||||
# initialise the model with the HTML value.
|
||||
if scope.$eval(attr.ngModel) == undefined
|
||||
ngModel.$setViewValue elem.val()
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
on_hand = parseInt(attr.ofnOnHand)
|
||||
if parseInt(viewValue) > on_hand
|
||||
alert t('insufficient_stock', {on_hand: on_hand})
|
||||
viewValue = on_hand
|
||||
ngModel.$setViewValue viewValue
|
||||
ngModel.$render()
|
||||
|
||||
viewValue
|
||||
@@ -54,7 +54,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
if li.quantity > li.variant.count_on_hand
|
||||
li.quantity = li.variant.count_on_hand
|
||||
scope.variants.push li.variant
|
||||
if li.max_quantity > li.variant.count_on_hand
|
||||
if li.variant.count_on_hand == 0 && li.max_quantity > li.variant.count_on_hand
|
||||
li.max_quantity = li.variant.count_on_hand
|
||||
scope.variants.push(li.variant) unless li.variant in scope.variants
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
Darkswarm.factory "MapConfiguration", ->
|
||||
new class MapConfiguration
|
||||
options:
|
||||
center:
|
||||
center:
|
||||
latitude: -37.4713077
|
||||
longitude: 144.7851531
|
||||
zoom: 12
|
||||
additional_options: {}
|
||||
#mapTypeId: 'satellite'
|
||||
additional_options:
|
||||
# mapTypeId: 'satellite'
|
||||
mapTypeId: 'OSM'
|
||||
mapTypeControl: false
|
||||
streetViewControl: false
|
||||
styles: [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]},{"featureType":"road","elementType": "labels.icon","stylers":[{"visibility":"off"}]}]
|
||||
|
||||
|
||||
8
app/assets/javascripts/templates/admin/tag.html.haml
Normal file
8
app/assets/javascripts/templates/admin/tag.html.haml
Normal file
@@ -0,0 +1,8 @@
|
||||
.tag-template
|
||||
%div
|
||||
%span.tag-with-rules{ ng: { if: "data.rules" }, "ofn-with-tip" => "{{ 'admin.tag_has_rules' | t:{num: data.rules} }}" }
|
||||
{{$getDisplayText()}}
|
||||
%span{ ng: { if: "!data.rules" } }
|
||||
{{$getDisplayText()}}
|
||||
%a.remove-button{ ng: {click: "$removeTag()"} }
|
||||
✖
|
||||
@@ -0,0 +1,11 @@
|
||||
.autocomplete-template
|
||||
%span.tag-with-rules{ ng: { if: "data.rules" } }
|
||||
{{$getDisplayText()}}
|
||||
%span.tag-with-rules{ ng: { if: "data.rules == 1" } }
|
||||
—
|
||||
= t 'admin.has_one_rule'
|
||||
%span.tag-with-rules{ ng: { if: "data.rules > 1" } }
|
||||
—
|
||||
= t 'admin.has_n_rules', { num: '{{data.rules}}' }
|
||||
%span{ ng: { if: "!data.rules" } }
|
||||
{{$getDisplayText()}}
|
||||
@@ -0,0 +1,7 @@
|
||||
%tags-input{ template: 'admin/tag.html', ng: { model: 'object[tagsAttr]' } }
|
||||
%auto-complete{source: "findTags({query: $query})",
|
||||
template: "admin/tag_autocomplete.html",
|
||||
"min-length" => "0",
|
||||
"load-on-focus" => "true",
|
||||
"load-on-empty" => "true",
|
||||
"max-results-to-show" => "32"}
|
||||
@@ -7,6 +7,6 @@
|
||||
placeholder: "0",
|
||||
"ofn-disable-scroll" => true,
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
"ofn-on-hand" => "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
"ng-disabled" => "!variant.on_demand && variant.count_on_hand == 0",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
placeholder: "{{'shop_variant_quantity_min' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
"ofn-on-hand" => "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
%span.bulk-input
|
||||
%input.bulk.second{type: :number,
|
||||
@@ -18,6 +18,6 @@
|
||||
"ng-model" => "variant.line_item.max_quantity",
|
||||
placeholder: "{{'shop_variant_quantity_max' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
min: "{{variant.line_item.quantity}}",
|
||||
name: "variant_attributes[{{variant.id}}][max_quantity]",
|
||||
id: "variants_{{variant.id}}_max"}
|
||||
|
||||
3
app/assets/stylesheets/admin/customers.css.scss
Normal file
3
app/assets/stylesheets/admin/customers.css.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.tag-with-rules {
|
||||
color: black;
|
||||
}
|
||||
@@ -32,6 +32,13 @@
|
||||
.debit
|
||||
color: $clr-brick
|
||||
|
||||
.invalid
|
||||
color: $ofn-grey
|
||||
.credit
|
||||
color: $ofn-grey
|
||||
.debit
|
||||
color: $ofn-grey
|
||||
|
||||
.distributor-balance.paid
|
||||
visibility: hidden
|
||||
|
||||
|
||||
@@ -22,7 +22,29 @@
|
||||
background: rgba(255,255,255,0.85)
|
||||
width: 50%
|
||||
margin-top: 1.2rem
|
||||
margin-left: 1rem
|
||||
@media all and (max-width: 768px)
|
||||
width: 80%
|
||||
&:active, &:focus, &.active
|
||||
background: rgba(255,255,255, 1)
|
||||
|
||||
.map-footer
|
||||
position: fixed
|
||||
z-index: 2
|
||||
width: 100%
|
||||
height: 23px
|
||||
left: 80px
|
||||
right: 0
|
||||
bottom: 6px
|
||||
margin: 0
|
||||
padding: 6px
|
||||
font-size: 14px
|
||||
font-weight: bold
|
||||
text-shadow: 2px 2px #aaa
|
||||
color: #fff
|
||||
|
||||
a, a:hover, a:active, a:focus
|
||||
color: #fff
|
||||
|
||||
@media all and (max-width: 1025px)
|
||||
left: 0px
|
||||
28
app/controllers/admin/tags_controller.rb
Normal file
28
app/controllers/admin/tags_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
module Admin
|
||||
class TagsController < Spree::Admin::BaseController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
serialiser = ActiveModel::ArraySerializer.new(tags_of_enterprise)
|
||||
render json: serialiser.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enterprise
|
||||
Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
|
||||
end
|
||||
|
||||
def tags_of_enterprise
|
||||
return [] unless enterprise
|
||||
tag_rule_map = enterprise.rules_per_tag
|
||||
tag_rule_map.keys.map do |tag|
|
||||
{ text: tag, rules: tag_rule_map[tag] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
module Spree
|
||||
module Admin
|
||||
GeneralSettingsController.class_eval do
|
||||
end
|
||||
|
||||
|
||||
module GeneralSettingsEditPreferences
|
||||
def edit
|
||||
super
|
||||
@preferences_general << :bugherd_api_key
|
||||
end
|
||||
end
|
||||
GeneralSettingsController.send(:prepend, GeneralSettingsEditPreferences)
|
||||
end
|
||||
end
|
||||
@@ -16,6 +16,7 @@ Spree::OrdersController.class_eval do
|
||||
# Patching to redirect to shop if order is empty
|
||||
def edit
|
||||
@order = current_order(true)
|
||||
@insufficient_stock_lines = @order.insufficient_stock_lines
|
||||
|
||||
if @order.line_items.empty?
|
||||
redirect_to main_app.shop_path
|
||||
@@ -28,6 +29,41 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
@insufficient_stock_lines = []
|
||||
@order = current_order
|
||||
unless @order
|
||||
flash[:error] = t(:order_not_found)
|
||||
redirect_to root_path and return
|
||||
end
|
||||
|
||||
if @order.update_attributes(params[:order])
|
||||
@order.line_items = @order.line_items.select {|li| li.quantity > 0 }
|
||||
@order.restart_checkout_flow
|
||||
|
||||
render :edit and return unless apply_coupon_code
|
||||
|
||||
fire_event('spree.order.contents_changed')
|
||||
respond_with(@order) do |format|
|
||||
format.html do
|
||||
if params.has_key?(:checkout)
|
||||
@order.next_transition.run_callbacks if @order.cart?
|
||||
redirect_to checkout_state_path(@order.checkout_steps.first)
|
||||
else
|
||||
redirect_to cart_path
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
# Show order with original values, not newly entered ones
|
||||
@insufficient_stock_lines = @order.insufficient_stock_lines
|
||||
@order.line_items(true)
|
||||
respond_with(@order)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def populate
|
||||
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
|
||||
# during cart population, for both taxation and enterprise fees. This operation triggers a
|
||||
@@ -55,6 +91,7 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Report the stock levels in the order for all variant ids requested
|
||||
def stock_levels(order, variant_ids)
|
||||
stock_levels = li_stock_levels(order)
|
||||
|
||||
@@ -4,19 +4,26 @@ class EnterpriseMailer < Spree::BaseMailer
|
||||
|
||||
def welcome(enterprise)
|
||||
@enterprise = enterprise
|
||||
mail(:to => enterprise.email, :from => from_address,
|
||||
:subject => "#{enterprise.name} is now on #{Spree::Config[:site_name]}")
|
||||
subject = t('enterprise_mailer.welcome.subject',
|
||||
enterprise: @enterprise.name,
|
||||
sitename: Spree::Config[:site_name])
|
||||
mail(:to => enterprise.email,
|
||||
:from => from_address,
|
||||
:subject => subject)
|
||||
end
|
||||
|
||||
def confirmation_instructions(record, token, opts={})
|
||||
def confirmation_instructions(record, token)
|
||||
@token = token
|
||||
find_enterprise(record)
|
||||
mail(subject: "Please confirm your email for #{@enterprise.name}",
|
||||
to: ( @enterprise.unconfirmed_email || @enterprise.email ),
|
||||
from: from_address)
|
||||
subject = t('enterprise_mailer.confirmation_instructions.subject',
|
||||
enterprise: @enterprise.name)
|
||||
mail(to: (@enterprise.unconfirmed_email || @enterprise.email),
|
||||
from: from_address,
|
||||
subject: subject)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_enterprise(enterprise)
|
||||
@enterprise = enterprise.is_a?(Enterprise) ? enterprise : Enterprise.find(enterprise)
|
||||
end
|
||||
|
||||
@@ -352,6 +352,20 @@ class Enterprise < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def rules_per_tag
|
||||
tag_rule_map = {}
|
||||
tag_rules.each do |rule|
|
||||
rule.preferred_customer_tags.split(",").each do |tag|
|
||||
if tag_rule_map[tag]
|
||||
tag_rule_map[tag] += 1
|
||||
else
|
||||
tag_rule_map[tag] = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
tag_rule_map
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def devise_mailer
|
||||
|
||||
@@ -101,11 +101,6 @@ class AbilityDecorator
|
||||
can [:print], Spree::Order do |order|
|
||||
order.user == user
|
||||
end
|
||||
|
||||
can [:create], Customer
|
||||
can [:destroy], Customer do |customer|
|
||||
user.enterprises.include? customer.enterprise
|
||||
end
|
||||
end
|
||||
|
||||
def add_product_management_abilities(user)
|
||||
@@ -221,7 +216,9 @@ class AbilityDecorator
|
||||
# Reports page
|
||||
can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], :report
|
||||
|
||||
can [:admin, :index, :update], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
|
||||
can [:create], Customer
|
||||
can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
|
||||
can [:admin, :index], :tag
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -23,4 +23,7 @@ Spree::AppConfiguration.class_eval do
|
||||
|
||||
# Monitoring
|
||||
preference :last_job_queue_heartbeat_at, :string, default: nil
|
||||
|
||||
# External services
|
||||
preference :bugherd_api_key, :string, default: nil
|
||||
end
|
||||
|
||||
@@ -44,12 +44,7 @@ Spree::LineItem.class_eval do
|
||||
|
||||
|
||||
def cap_quantity_at_stock!
|
||||
attrs = {}
|
||||
|
||||
attrs[:quantity] = variant.on_hand if quantity > variant.on_hand
|
||||
attrs[:max_quantity] = variant.on_hand if (max_quantity || 0) > variant.on_hand
|
||||
|
||||
update_attributes!(attrs) if attrs.any?
|
||||
update_attributes!(quantity: variant.on_hand) if quantity > variant.on_hand
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ Spree::OrderPopulator.class_eval do
|
||||
on_hand = variant.on_hand
|
||||
on_hand = [quantity, max_quantity].compact.max if Spree::Config.allow_backorders
|
||||
quantity_to_add = [quantity, on_hand].min
|
||||
max_quantity_to_add = [max_quantity, on_hand].min if max_quantity
|
||||
max_quantity_to_add = max_quantity # max_quantity is not capped
|
||||
|
||||
[quantity_to_add, max_quantity_to_add]
|
||||
end
|
||||
|
||||
@@ -51,11 +51,16 @@ Spree.user_class.class_eval do
|
||||
|
||||
# Returns Enterprise IDs for distributors that the user has shopped at
|
||||
def enterprises_ordered_from
|
||||
orders.where(state: :complete).map(&:distributor_id).uniq
|
||||
enterprise_ids = orders.where(state: :complete).map(&:distributor_id).uniq
|
||||
# Exclude the accounts distributor
|
||||
if Spree::Config.accounts_distributor_id
|
||||
enterprise_ids = enterprise_ids.keep_if { |a| a != Spree::Config.accounts_distributor_id }
|
||||
end
|
||||
enterprise_ids
|
||||
end
|
||||
|
||||
# Returns orders and their associated payments for all distributors that have been ordered from
|
||||
def compelete_orders_by_distributor
|
||||
def complete_orders_by_distributor
|
||||
Enterprise
|
||||
.includes(distributed_orders: { payments: :payment_method })
|
||||
.where(enterprises: { id: enterprises_ordered_from },
|
||||
@@ -65,8 +70,8 @@ Spree.user_class.class_eval do
|
||||
|
||||
def orders_by_distributor
|
||||
# Remove uncompleted payments as these will not be reflected in order balance
|
||||
data_array = compelete_orders_by_distributor.to_a
|
||||
remove_uncompleted_payments(data_array)
|
||||
data_array = complete_orders_by_distributor.to_a
|
||||
remove_payments_in_checkout(data_array)
|
||||
data_array.sort! { |a, b| b.distributed_orders.length <=> a.distributed_orders.length }
|
||||
end
|
||||
|
||||
@@ -78,10 +83,10 @@ Spree.user_class.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
def remove_uncompleted_payments(enterprises)
|
||||
def remove_payments_in_checkout(enterprises)
|
||||
enterprises.each do |enterprise|
|
||||
enterprise.distributed_orders.each do |order|
|
||||
order.payments.keep_if { |payment| payment.state == "completed" }
|
||||
order.payments.keep_if { |payment| payment.state != "checkout" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,10 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def tags
|
||||
object.tag_list.map{ |t| { text: t } }
|
||||
tag_rule_map = object.enterprise.rules_per_tag
|
||||
object.tag_list.map do |tag|
|
||||
{ text: tag, rules: tag_rule_map[tag] }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module Api
|
||||
class PaymentSerializer < ActiveModel::Serializer
|
||||
attributes :amount, :updated_at, :payment_method
|
||||
attributes :amount, :updated_at, :payment_method, :state
|
||||
def payment_method
|
||||
object.payment_method.name
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
%input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" }
|
||||
%td.tags{ 'ng-show' => 'columns.tags.visible' }
|
||||
.tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"}
|
||||
%tags_with_translation{ object: 'customer' }
|
||||
%tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }
|
||||
%input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' }
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
- if Rails.env.staging? or Rails.env.production?
|
||||
- if (Rails.env.staging? || Rails.env.production?) && Spree::Config.bugherd_api_key.present?
|
||||
:javascript
|
||||
(function (d, t) {
|
||||
var bh = d.createElement(t), s = d.getElementsByTagName(t)[0];
|
||||
bh.type = 'text/javascript';
|
||||
bh.src = '//www.bugherd.com/sidebarv2.js?apikey=4ftxjbgwx7y6ssykayr04w';
|
||||
bh.src = '//www.bugherd.com/sidebarv2.js?apikey=#{Spree::Config.bugherd_api_key}';
|
||||
s.parentNode.insertBefore(bh, s);
|
||||
})(document, 'script');
|
||||
|
||||
|
||||
-#- elsif Rails.env.production?
|
||||
-#:javascript
|
||||
-#(function (d, t) {
|
||||
-#var bh = d.createElement(t), s = d.getElementsByTagName(t)[0];
|
||||
-#bh.type = 'text/javascript';
|
||||
-#bh.src = '//www.bugherd.com/sidebarv2.js?apikey=xro3uv55objies58o2wrua';
|
||||
-#s.parentNode.insertBefore(bh, s);
|
||||
-#})(document, 'script');
|
||||
|
||||
@@ -9,3 +9,6 @@
|
||||
%map-search
|
||||
%markers{models: "OfnMap.enterprises", fit: "true",
|
||||
coords: "'self'", icon: "'icon'", click: "'reveal'"}
|
||||
|
||||
.map-footer
|
||||
%a{:href => "http://www.openstreetmap.org/copyright"} © OpenStreetMap contributors
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
= render 'spree/shared/line_item_name', line_item: line_item
|
||||
|
||||
- if @order.insufficient_stock_lines.include? line_item
|
||||
- if @insufficient_stock_lines.include? line_item
|
||||
%span.out-of-stock
|
||||
= variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock)
|
||||
%br/
|
||||
@@ -30,7 +30,7 @@
|
||||
-# "price-breakdown-placement" => "left",
|
||||
-# "price-breakdown-animation" => true}
|
||||
%td.text-center.cart-item-quantity{"data-hook" => "cart_item_quantity"}
|
||||
= item_form.number_field :quantity, :min => 0, :class => "line_item_quantity", :size => 5
|
||||
= item_form.number_field :quantity, :min => 0, "ofn-on-hand" => variant.on_hand, "ng-model" => "line_item_#{line_item.id}", :class => "line_item_quantity", :size => 5
|
||||
%td.cart-item-total.text-right{"data-hook" => "cart_item_total"}
|
||||
= line_item.display_amount_with_adjustments.to_html unless line_item.quantity.nil?
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
%td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","bo-text" => "order.total | localizeCurrency"}
|
||||
%td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "bo-text" => "order.outstanding_balance | localizeCurrency"}
|
||||
%td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "bo-text" => "order.running_balance | localizeCurrency"}
|
||||
%tr.payment-row{"ng-repeat" => "payment in order.payments"}
|
||||
%td.order1= t :payment
|
||||
%tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"}
|
||||
%td.order1{"bo-text" => "payment.payment_method"}
|
||||
%td.order2{"bo-text" => "payment.updated_at"}
|
||||
%td.order3.show-for-large-up{"bo-text" => "payment.payment_method"}
|
||||
%td.order3.show-for-large-up
|
||||
%i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"}
|
||||
%span{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"}
|
||||
%td.order4.show-for-large-up
|
||||
%td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"}
|
||||
%td.order6.show-for-large-up
|
||||
|
||||
@@ -1006,7 +1006,6 @@ Please follow the instructions there to make your enterprise visible on the Open
|
||||
validation_msg_product_category_cant_be_blank: "^Product Category cant be blank"
|
||||
validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank"
|
||||
validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer"
|
||||
|
||||
spree:
|
||||
shipment_states:
|
||||
backorder: backorder
|
||||
@@ -1024,6 +1023,7 @@ Please follow the instructions there to make your enterprise visible on the Open
|
||||
pending: pending
|
||||
processing: processing
|
||||
void: void
|
||||
invalid: invalid
|
||||
order_state:
|
||||
address: address
|
||||
adjustments: adjustments
|
||||
|
||||
@@ -32,6 +32,11 @@ en:
|
||||
not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step?
|
||||
confirmation_sent: "Confirmation email sent!"
|
||||
confirmation_not_sent: "Could not send a confirmation email."
|
||||
enterprise_mailer:
|
||||
confirmation_instructions:
|
||||
subject: "Please confirm the email address for %{enterprise}"
|
||||
welcome:
|
||||
subject: "%{enterprise} is now on %{sitename}"
|
||||
home: "OFN"
|
||||
title: Open Food Network
|
||||
welcome_to: 'Welcome to '
|
||||
@@ -80,6 +85,10 @@ en:
|
||||
|
||||
whats_this: What's this?
|
||||
|
||||
tag_has_rules: "Existing rules for this tag: %{num}"
|
||||
has_one_rule: "has one rule"
|
||||
has_n_rules: "has %{num} rules"
|
||||
|
||||
customers:
|
||||
index:
|
||||
add_customer: "Add customer"
|
||||
@@ -1031,6 +1040,7 @@ Please follow the instructions there to make your enterprise visible on the Open
|
||||
pending: pending
|
||||
processing: processing
|
||||
void: void
|
||||
invalid: invalid
|
||||
order_state:
|
||||
address: address
|
||||
adjustments: adjustments
|
||||
|
||||
@@ -117,6 +117,8 @@ Openfoodnetwork::Application.routes.draw do
|
||||
|
||||
resources :customers, only: [:index, :create, :update, :destroy]
|
||||
|
||||
resources :tags, only: [:index], format: :json
|
||||
|
||||
resource :content
|
||||
|
||||
resource :accounts_and_billing_settings, only: [:edit, :update] do
|
||||
|
||||
@@ -15,6 +15,7 @@ describe Spree::OrdersController do
|
||||
controller.stub(:current_order_cycle).and_return(order_cycle)
|
||||
controller.stub(:current_order).and_return order
|
||||
order.stub_chain(:line_items, :empty?).and_return true
|
||||
order.stub(:insufficient_stock_lines).and_return []
|
||||
session[:access_token] = order.token
|
||||
spree_get :edit
|
||||
response.should redirect_to shop_path
|
||||
|
||||
22
spec/features/admin/external_services_spec.rb
Normal file
22
spec/features/admin/external_services_spec.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require 'spec_helper'
|
||||
|
||||
feature 'External services' do
|
||||
include AuthenticationWorkflow
|
||||
|
||||
describe "bugherd" do
|
||||
before do
|
||||
Spree::Config.bugherd_api_key = nil
|
||||
login_to_admin_section
|
||||
end
|
||||
|
||||
it "lets me set an API key" do
|
||||
visit spree.edit_admin_general_settings_path
|
||||
|
||||
fill_in 'bugherd_api_key', with: 'abc123'
|
||||
click_button 'Update'
|
||||
|
||||
page.should have_content 'General Settings has been successfully updated!'
|
||||
expect(Spree::Config.bugherd_api_key).to eq 'abc123'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,6 +13,8 @@ feature %q{
|
||||
let!(:distributor2) { create(:distributor_enterprise) }
|
||||
let!(:distributor_credit) { create(:distributor_enterprise) }
|
||||
let!(:distributor_without_orders) { create(:distributor_enterprise) }
|
||||
let!(:accounts_distributor) {create :distributor_enterprise}
|
||||
let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: user) }
|
||||
let!(:d1o1) { create(:completed_order_with_totals, distributor_id: distributor1.id, user_id: user.id, total: 10000)}
|
||||
let!(:d1o2) { create(:order_without_full_payment, distributor_id: distributor1.id, user_id: user.id, total: 5000)}
|
||||
let!(:d2o1) { create(:completed_order_with_totals, distributor_id: distributor2.id, user_id: user.id)}
|
||||
@@ -21,19 +23,24 @@ feature %q{
|
||||
|
||||
|
||||
before do
|
||||
Spree::Config.accounts_distributor_id = accounts_distributor.id
|
||||
credit_order.update!
|
||||
login_as user
|
||||
visit "/account"
|
||||
end
|
||||
|
||||
it "shows all hubs that have been ordered from with balance or credit" do
|
||||
# Single test to avoid re-rendering page
|
||||
expect(page).to have_content distributor1.name
|
||||
expect(page).to have_content distributor2.name
|
||||
expect(page).not_to have_content distributor_without_orders.name
|
||||
# Exclude the special Accounts & Billing distributor
|
||||
expect(page).not_to have_content accounts_distributor.name
|
||||
expect(page).to have_content distributor1.name + " " + "Balance due"
|
||||
expect(page).to have_content distributor_credit.name + " Credit"
|
||||
end
|
||||
|
||||
|
||||
it "reveals table of orders for distributors when clicked" do
|
||||
expand_active_table_node distributor1.name
|
||||
expect(page).to have_link "Order " + d1o1.number, href:"/orders/#{d1o1.number}"
|
||||
|
||||
57
spec/features/consumer/external_services_spec.rb
Normal file
57
spec/features/consumer/external_services_spec.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
require 'spec_helper'
|
||||
|
||||
feature 'External services' do
|
||||
include AuthenticationWorkflow
|
||||
include WebHelper
|
||||
|
||||
describe "bugherd" do
|
||||
describe "limiting inclusion by environment" do
|
||||
before { Spree::Config.bugherd_api_key = 'abc123' }
|
||||
|
||||
it "is not included in test" do
|
||||
visit root_path
|
||||
expect(script_content(with: 'bugherd')).to be_nil
|
||||
end
|
||||
|
||||
it "is not included in dev" do
|
||||
Rails.env.stub(:development?) { true }
|
||||
visit root_path
|
||||
expect(script_content(with: 'bugherd')).to be_nil
|
||||
end
|
||||
|
||||
it "is included in staging" do
|
||||
Rails.env.stub(:staging?) { true }
|
||||
visit root_path
|
||||
expect(script_content(with: 'bugherd')).not_to be_nil
|
||||
end
|
||||
|
||||
it "is included in production" do
|
||||
Rails.env.stub(:production?) { true }
|
||||
visit root_path
|
||||
expect(script_content(with: 'bugherd')).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "in an environment where BugHerd is displayed" do
|
||||
before { Rails.env.stub(:staging?) { true } }
|
||||
|
||||
context "when there is no API key set" do
|
||||
before { Spree::Config.bugherd_api_key = nil }
|
||||
|
||||
it "does not include the BugHerd script" do
|
||||
visit root_path
|
||||
expect(script_content(with: 'bugherd')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when an API key is set" do
|
||||
before { Spree::Config.bugherd_api_key = 'abc123' }
|
||||
|
||||
it "includes the BugHerd script, with the correct API key" do
|
||||
visit root_path
|
||||
expect(script_content(with: 'bugherd')).to include 'abc123'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,25 +7,53 @@ feature "full-page cart", js: true do
|
||||
include UIComponentHelper
|
||||
|
||||
describe "viewing the cart" do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product.tax_category) }
|
||||
let(:product) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) }
|
||||
let(:variant) { product.variants.first }
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
|
||||
before do
|
||||
add_enterprise_fee enterprise_fee
|
||||
set_order order
|
||||
add_product_to_cart
|
||||
visit spree.cart_path
|
||||
end
|
||||
|
||||
describe "tax" do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product.tax_category) }
|
||||
let(:product) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) }
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
|
||||
before do
|
||||
add_enterprise_fee enterprise_fee
|
||||
set_order order
|
||||
add_product_to_cart
|
||||
visit spree.cart_path
|
||||
end
|
||||
|
||||
it "shows the total tax for the order, including product tax and tax on fees" do
|
||||
page.should have_selector '.tax-total', text: '11.00' # 10 + 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating quantities with insufficient stock available" do
|
||||
let(:li) { order.line_items(true).last }
|
||||
|
||||
before do
|
||||
variant.update_attributes! on_hand: 2
|
||||
end
|
||||
|
||||
it "prevents me from entering an invalid value" do
|
||||
visit spree.cart_path
|
||||
|
||||
accept_alert 'Insufficient stock available, only 2 remaining' do
|
||||
fill_in "order_line_items_attributes_0_quantity", with: '4'
|
||||
end
|
||||
|
||||
page.should have_field "order_line_items_attributes_0_quantity", with: '2'
|
||||
end
|
||||
|
||||
it "shows the quantities saved, not those submitted" do
|
||||
fill_in "order_line_items_attributes_0_quantity", with: '4'
|
||||
|
||||
click_button 'Update'
|
||||
|
||||
page.should have_field "order[line_items_attributes][0][quantity]", with: '1'
|
||||
page.should have_content "Insufficient stock available, only 2 remaining"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -238,6 +238,17 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
Spree::LineItem.where(id: li).should be_empty
|
||||
end
|
||||
|
||||
it "alerts us when we enter a quantity greater than the stock available" do
|
||||
variant.update_attributes on_hand: 5
|
||||
visit shop_path
|
||||
|
||||
accept_alert 'Insufficient stock available, only 5 remaining' do
|
||||
fill_in "variants[#{variant.id}]", with: '10'
|
||||
end
|
||||
|
||||
page.should have_field "variants[#{variant.id}]", with: '5'
|
||||
end
|
||||
|
||||
describe "when a product goes out of stock just before it's added to the cart" do
|
||||
it "stops the attempt, shows an error message and refreshes the products asynchronously" do
|
||||
variant.update_attributes! on_hand: 0
|
||||
@@ -259,7 +270,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
# Update amount available in product list
|
||||
# If amount falls to zero, variant should be greyed out and input disabled
|
||||
page.should have_selector "#variant-#{variant.id}.out-of-stock"
|
||||
page.should have_selector "#variants_#{variant.id}[max='0']"
|
||||
page.should have_selector "#variants_#{variant.id}[ofn-on-hand='0']"
|
||||
page.should have_selector "#variants_#{variant.id}[disabled='disabled']"
|
||||
end
|
||||
|
||||
@@ -288,33 +299,32 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
# Update amount available in product list
|
||||
# If amount falls to zero, variant should be greyed out and input disabled
|
||||
page.should have_selector "#variant-#{variant.id}.out-of-stock"
|
||||
page.should have_selector "#variants_#{variant.id}_max[max='0']"
|
||||
page.should have_selector "#variants_#{variant.id}_max[disabled='disabled']"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the update is for another product" do
|
||||
it "updates quantity" do
|
||||
fill_in "variants[#{variant.id}]", with: '1'
|
||||
fill_in "variants[#{variant.id}]", with: '2'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
variant.update_attributes! on_hand: 0
|
||||
variant.update_attributes! on_hand: 1
|
||||
|
||||
fill_in "variants[#{variant2.id}]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
within(".out-of-stock-modal") do
|
||||
page.should have_content "stock levels for one or more of the products in your cart have reduced"
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock."
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} now only has 1 remaining"
|
||||
end
|
||||
end
|
||||
|
||||
context "group buy products" do
|
||||
let(:product) { create(:simple_product, group_buy: true) }
|
||||
|
||||
it "updates max_quantity" do
|
||||
fill_in "variants[#{variant.id}]", with: '1'
|
||||
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '2'
|
||||
it "does not update max_quantity" do
|
||||
fill_in "variants[#{variant.id}]", with: '2'
|
||||
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '3'
|
||||
wait_until { !cart_dirty }
|
||||
variant.update_attributes! on_hand: 1
|
||||
|
||||
@@ -325,6 +335,9 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
page.should have_content "stock levels for one or more of the products in your cart have reduced"
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} now only has 1 remaining"
|
||||
end
|
||||
|
||||
page.should have_field "variants[#{variant.id}]", with: '1'
|
||||
page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: '3'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,3 +47,32 @@ describe "CustomersCtrl", ->
|
||||
http.flush()
|
||||
expect(scope.customers.length).toBe 1
|
||||
expect(scope.customers[0]).not.toAngularEqual customer
|
||||
|
||||
describe "scope.findTags", ->
|
||||
tags = [
|
||||
{ text: 'one' }
|
||||
{ text: 'two' }
|
||||
{ text: 'three' }
|
||||
]
|
||||
beforeEach ->
|
||||
http.expectGET('/admin/tags.json?enterprise_id=1').respond 200, tags
|
||||
|
||||
it "retrieves the tag list", ->
|
||||
promise = scope.findTags('')
|
||||
result = null
|
||||
promise.then (data) ->
|
||||
result = data
|
||||
http.flush()
|
||||
expect(result).toAngularEqual tags
|
||||
|
||||
it "filters the tag list", ->
|
||||
filtered_tags = [
|
||||
{ text: 'two' }
|
||||
{ text: 'three' }
|
||||
]
|
||||
promise = scope.findTags('t')
|
||||
result = null
|
||||
promise.then (data) ->
|
||||
result = data
|
||||
http.flush()
|
||||
expect(result).toAngularEqual filtered_tags
|
||||
|
||||
@@ -159,12 +159,12 @@ describe 'Cart service', ->
|
||||
expect(li.quantity).toEqual 5
|
||||
expect(li.max_quantity).toBeUndefined()
|
||||
|
||||
it "reduces the max_quantity in the cart", ->
|
||||
it "does not reduce the max_quantity in the cart", ->
|
||||
li = {variant: {id: 1}, quantity: 6, max_quantity: 7}
|
||||
stockLevels = {1: {quantity: 5, max_quantity: 5, on_hand: 5}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.max_quantity).toEqual 5
|
||||
expect(li.max_quantity).toEqual 7
|
||||
|
||||
it "resets the count on hand available", ->
|
||||
li = {variant: {id: 1}, quantity: 6}
|
||||
|
||||
@@ -12,7 +12,7 @@ describe EnterpriseMailer do
|
||||
EnterpriseMailer.confirmation_instructions(enterprise, 'token').deliver
|
||||
ActionMailer::Base.deliveries.count.should == 1
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
expect(mail.subject).to eq "Please confirm your email for #{enterprise.name}"
|
||||
expect(mail.subject).to eq "Please confirm the email address for #{enterprise.name}"
|
||||
expect(mail.to).to include enterprise.email
|
||||
expect(mail.reply_to).to be_nil
|
||||
end
|
||||
@@ -28,7 +28,7 @@ describe EnterpriseMailer do
|
||||
EnterpriseMailer.confirmation_instructions(enterprise, 'token').deliver
|
||||
ActionMailer::Base.deliveries.count.should == 1
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
expect(mail.subject).to eq "Please confirm your email for #{enterprise.name}"
|
||||
expect(mail.subject).to eq "Please confirm the email address for #{enterprise.name}"
|
||||
expect(mail.to).to include enterprise.unconfirmed_email
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,9 +55,9 @@ module Spree
|
||||
li.reload.quantity.should == 5
|
||||
end
|
||||
|
||||
it "caps max_quantity" do
|
||||
it "does not cap max_quantity" do
|
||||
li.cap_quantity_at_stock!
|
||||
li.reload.max_quantity.should == 5
|
||||
li.reload.max_quantity.should == 10
|
||||
end
|
||||
|
||||
it "works for products without max_quantity" do
|
||||
|
||||
@@ -213,8 +213,8 @@ module Spree
|
||||
op.quantities_to_add(v, 5, 6).should == [5, 6]
|
||||
end
|
||||
|
||||
it "returns a limited amount when not entirely available" do
|
||||
op.quantities_to_add(v, 15, 16).should == [10, 10]
|
||||
it "also returns the full amount when not entirely available" do
|
||||
op.quantities_to_add(v, 15, 16).should == [10, 16]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,11 +90,17 @@ describe Spree.user_class do
|
||||
let!(:d1_order_for_u2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u2.id) }
|
||||
let!(:d1o3) { create(:order, state: 'cart', distributor: distributor1, user_id: u1.id) }
|
||||
let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id) }
|
||||
let!(:accounts_distributor) {create :distributor_enterprise}
|
||||
let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: u1) }
|
||||
|
||||
let!(:completed_payment) { create(:payment, order: d1o1, state: 'completed') }
|
||||
let!(:payment) { create(:payment, order: d1o2, state: 'invalid') }
|
||||
let!(:payment) { create(:payment, order: d1o2, state: 'checkout') }
|
||||
|
||||
it "returns enterprises that the user has ordered from" do
|
||||
before do
|
||||
Spree::Config.accounts_distributor_id = accounts_distributor.id
|
||||
end
|
||||
|
||||
it "returns enterprises that the user has ordered from, excluding accounts distributor" do
|
||||
expect(u1.enterprises_ordered_from).to eq [distributor1.id]
|
||||
end
|
||||
|
||||
@@ -114,8 +120,8 @@ describe Spree.user_class do
|
||||
expect(u1.orders_by_distributor.first.distributed_orders).not_to include d1o3
|
||||
end
|
||||
|
||||
it "doesn't return uncompleted payments" do
|
||||
expect(u1.orders_by_distributor.first.distributed_orders.map(&:payments).flatten).not_to include payment
|
||||
it "doesn't return payments that are still at checkout stage" do
|
||||
expect(u1.orders_by_distributor.first.distributed_orders.map{|o| o.payments}.flatten).not_to include payment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
14
spec/serializers/admin/customer_serializer_spec.rb
Normal file
14
spec/serializers/admin/customer_serializer_spec.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
describe Api::Admin::CustomerSerializer do
|
||||
let(:customer) { create(:customer, tag_list: "one, two, three") }
|
||||
let!(:tag_rule) { create(:tag_rule, enterprise: customer.enterprise, preferred_customer_tags: "two") }
|
||||
|
||||
it "serializes a customer" do
|
||||
serializer = Api::Admin::CustomerSerializer.new customer
|
||||
result = JSON.parse(serializer.to_json)
|
||||
expect(result['email']).to eq customer.email
|
||||
tags = result['tags']
|
||||
expect(tags.length).to eq 3
|
||||
expect(tags[0]).to eq({ "text" => 'one', "rules" => nil })
|
||||
expect(tags[1]).to eq({ "text" => 'two', "rules" => 1 })
|
||||
end
|
||||
end
|
||||
@@ -115,6 +115,24 @@ module WebHelper
|
||||
DirtyFormDialog.new(page)
|
||||
end
|
||||
|
||||
# Fetch the content of a script block
|
||||
# eg. script_content with: 'my-script.com'
|
||||
# Returns nil if not found
|
||||
# Raises an exception if multiple matching blocks are found
|
||||
def script_content(opts={})
|
||||
elems = page.all('script', visible: false)
|
||||
|
||||
elems = elems.to_a.select { |e| e.text(:all).include? opts[:with] } if opts[:with]
|
||||
|
||||
if elems.none?
|
||||
nil
|
||||
elsif elems.many?
|
||||
raise "Multiple results returned for script_content"
|
||||
else
|
||||
elems.first.text(:all)
|
||||
end
|
||||
end
|
||||
|
||||
# http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara
|
||||
# Do not use this without good reason. Capybara's built-in waiting is very effective.
|
||||
def wait_until(secs=nil)
|
||||
|
||||
Reference in New Issue
Block a user