Compare commits

...

43 Commits

Author SHA1 Message Date
Em-AK
c72d17dc83 Run karma task only in test environment
and fails otherwise, as it needs the defaults defined in test environment
2017-04-28 11:28:24 +10:00
Em-AK
78ffdec693 Force english locale in test environment 2017-04-28 11:28:24 +10:00
Pierre de Lacroix
49c19a1d6a update wkhtmltopdf-binary and wicked_pdf 2017-04-27 18:38:47 +10:00
Pierre de Lacroix
e854eb0426 add tests
for methods Spree::Order#tax_adjustment_totals
and Spree::Adjustment#find_closest_tax_rate_from_included_tax
2017-04-27 17:32:36 +10:00
Pierre de Lacroix
4a9c17cb28 better tax aggregation 2017-04-27 17:32:36 +10:00
Matt-Yorkley
0d1547f439 Require OC ready_for in simple OC UI 2017-04-21 09:34:50 +10:00
Matt-Yorkley
fa5ed529cb Added OC form validation 2017-04-21 09:32:49 +10:00
Matt-Yorkley
accb3076e9 Updated translations 2017-04-21 09:32:49 +10:00
Matt-Yorkley
a4e4e1ec68 Require OC name 2017-04-21 09:32:49 +10:00
Maikel Linke
4809237ecc Fast fail set_order_cycles if distributor not ready 2017-04-19 14:28:17 +10:00
Maikel Linke
81877fedb6 Remove useless andand called on scope 2017-04-19 14:28:17 +10:00
Matt-Yorkley
1f2c6f2a85 Ensure shops display as closed when not configured for sales
squashme

squashme

squash

squash

squash
2017-04-19 14:28:17 +10:00
Matt-Yorkley
4fe5e60967 Updated controller stub to pass test 2017-04-19 14:28:17 +10:00
Matt-Yorkley
f4eb9cb790 Hubs display as closed when not configured for payment or shipping 2017-04-19 14:28:16 +10:00
Maikel Linke
775f9b3ada Move text from view to locale (i18n) 2017-04-12 10:06:53 +10:00
Maikel Linke
188b33921c Remove test entry in locale 2017-04-12 09:56:24 +10:00
Maikel Linke
20c033317f Remove unnecessary string interpolation from view 2017-04-12 09:49:24 +10:00
Matt-Yorkley
e7a5d063ac Update simple OC form 2017-04-12 09:41:40 +10:00
Matt-Yorkley
1fda781d7e Set maxlength on OC displayname field 2017-04-12 09:41:39 +10:00
Matt-Yorkley
45fc801a08 Added tooltips to OC form 2017-04-12 09:41:39 +10:00
Maikel Linke
21337a5b50 Merge tag 'v1.8.9' into transifex 2017-04-06 10:36:14 +10:00
Rob Harrington
c83ad2ecc4 Fixing broken limited reached page in registration flow 2017-04-05 17:02:40 +10:00
Matt-Yorkley
80d8d18eb2 Update terms of service config 2017-04-05 15:48:50 +10:00
Rob Harrington
903d1afb53 Stripping html tags from products description on new form as well 2017-04-05 14:29:23 +10:00
Matt-Yorkley
cd55d2e2ff Product Description - strip weird tags on paste
squashme
2017-04-05 11:29:35 +10:00
Rob Harrington
05cf8c4351 Sanitizing product description for textAngular input 2017-04-05 11:29:35 +10:00
Matt-Yorkley
b04d815408 Changes for code review
Fixed spec
2017-04-05 11:29:35 +10:00
Matt-Yorkley
7b370a2eb6 Removed underline option 2017-04-05 11:29:35 +10:00
Matt-Yorkley
5808b601b8 Added specs for HTML product description 2017-04-05 11:29:35 +10:00
Matt-Yorkley
fdcd3dc3e3 Fixed Capybara not interacting with textAngular 2017-04-05 11:29:35 +10:00
Matt-Yorkley
c4bd085393 Added Angular and textAngular to edit product page 2017-04-05 11:29:35 +10:00
Matt-Yorkley
0e91d01412 UX improvement for selected formatting options 2017-04-05 11:29:35 +10:00
Matt-Yorkley
fcb9e9fa56 Changed buttons 2017-04-05 11:29:35 +10:00
Matt-Yorkley
3591354cb1 Minor tweaks 2017-04-05 11:29:35 +10:00
Matt-Yorkley
b38eab11eb Fixed frontend HTML display 2017-04-05 11:29:35 +10:00
Matt-Yorkley
c43dea60b7 Product Descriptions formatting 2017-04-05 11:29:35 +10:00
Pierre de Lacroix
268bea25d0 add qz/ folder from ofn-qz gem to list of assets 2017-03-30 20:37:20 +02:00
Pierre de Lacroix
e94ae20b31 fix print_ticket authorization 2017-03-30 20:37:20 +02:00
Maikel Linke
a94961c0a7 Fixup merge conflicts and remove unused text 2017-03-29 14:58:19 +11:00
Lynne Davis
0d5fde919b Property name spans signle col heading 2017-03-29 14:47:17 +11:00
Matt-Yorkley
429ef4e2ba Altered product property headings for issue #522 2017-03-29 14:44:30 +11:00
Lynne Davis
e8999d23e1 Updated translations 2017-03-29 13:12:29 +11:00
Keir Osborn
209c9242d9 remove word-wrap class from enterprise.email_address and enterprise.website in javascripts/templates/partials/contact.html.haml 2017-03-24 12:55:30 +11:00
52 changed files with 369 additions and 85 deletions

View File

@@ -643,9 +643,8 @@ GEM
whenever (0.9.2)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
wicked_pdf (0.11.0)
rails
wkhtmltopdf-binary (0.9.9.3)
wicked_pdf (1.1.0)
wkhtmltopdf-binary (0.12.3.1)
xml-simple (1.1.4)
xpath (2.0.0)
nokogiri (~> 1.3)

View File

@@ -98,6 +98,14 @@ The site is configured to use
startup time while Rails loads. See the Zeus github page for
usage instructions.
Once [npm dependencies are
installed](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Karma), AngularJS tests can be run with:
./script/karma run
If you want karma to automatically rerun the tests on file modification, use:
./script/karma start
## Credits

View File

@@ -12,7 +12,10 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded

View File

@@ -13,7 +13,10 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded

View File

@@ -8,7 +8,10 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.init = (enterprises) ->
enterprise = enterprises[Object.keys(enterprises)[0]]

View File

@@ -10,7 +10,10 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$scope.init()
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded

View File

@@ -1 +1 @@
angular.module("admin.products", ["admin.utils"])
angular.module("admin.products", ["textAngular", "admin.utils"])

View File

@@ -0,0 +1,5 @@
angular.module("admin.utils").directive "textangularStrip", () ->
restrict: 'CA'
link: (scope, element, attrs) ->
scope.stripFormatting = ($html) ->
return String($html).replace(/<[^>]+>/gm, '')

View File

@@ -11,6 +11,14 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
text: ""
style: {}
invalidMessage: ""
setValidation: (isValid) ->
if isValid
StatusMessage.invalidMessage = ''
else
StatusMessage.invalidMessage = t("admin.form_invalid")
active: ->
@statusMessage.text != ''

View File

@@ -1,6 +1,9 @@
#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
.container
.eight.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage == ''", style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage !== ''" }, style: 'color: #da5354' }
{{ StatusMessage.invalidMessage || "&nbsp;" }}
.eight.columns.omega.text-right{ ng: { transclude: true } }

View File

@@ -3,9 +3,9 @@
%p.modal-header {{'contact' | t}}
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
%p.word-wrap{"ng-if" => "::enterprise.email_address"}
%p{"ng-if" => "::enterprise.email_address"}
%a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
%span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "enterprise.website"}
%p{"ng-if" => "enterprise.website"}
%a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}

View File

@@ -14,9 +14,9 @@
.filter-shopfront.property-selectors.inline-block
%filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
%div{"ng-if" => "product.description"}
%div{"ng-if" => "product.description_html"}
%hr
%p.text-small{"ng-bind" => "::product.description"}
%p.text-small{"ng-bind-html" => "::product.description_html"}
%hr
.columns.small-12.large-6

View File

@@ -74,6 +74,9 @@ form.order_cycle {
margin-bottom: 0.5em;
}
}
.icon-question-sign {
font-size: 18px;
}
table.exchanges {
tr td.active {
width: 20px;
@@ -113,6 +116,14 @@ form.order_cycle {
margin-right: 2em;
}
}
.collection-details {
input {
width: 90%
}
span {
font-size: 1rem
}
}
}
.coordinator-fees {
margin-top: 1em;
@@ -200,6 +211,13 @@ table#listing_enterprise_groups {
// textAngular wysiwyg
text-angular {
.ta-toolbar {
border: 1px solid #cdd9e4;
padding: 0.4em;
margin-bottom: -1px;
background-color: #f1f1f1;
border-radius: 0.25em 0.25em 0 0;
}
.ta-scroll-window > .ta-bind {
max-height: 400px;
min-height: 100px;
@@ -210,12 +228,18 @@ text-angular {
}
.ta-scroll-window.form-control {
min-height: 100px;
box-shadow: none !important;
}
.btn-group {
display: inline;
margin-right: 8px;
button {
padding: 5px 10px;
margin-right: 0.25em;
}
button.active:not(:hover) {
box-shadow: 0 0 0.7em rgba(0,0,0,0.3) inset;
background-color: #4583bf;
}
}
}

View File

@@ -23,6 +23,11 @@ class BaseController < ApplicationController
private
def set_order_cycles
unless @distributor.ready_for_checkout?
@order_cycles = OrderCycle.where('false')
return
end
@order_cycles = OrderCycle.with_distributor(@distributor).active
.order(@distributor.preferred_shopfront_order_cycle_order)

View File

@@ -54,12 +54,9 @@ module CheckoutHelper
end
def display_adjustment_tax_rates(adjustment)
tax_rate = (adjustment.included_tax / (adjustment.amount - adjustment.included_tax)).round(2)
if tax_rate == 0 || tax_rate.infinite?
""
else
number_to_percentage(tax_rate * 100, :precision => 1)
end
tax_rates = adjustment.tax_rates
return "" if adjustment.amount == adjustment.included_tax
tax_rates.map { |tr| number_to_percentage(tr.amount * 100, :precision => 1) }.join(", ")
end
def display_adjustment_amount(adjustment)

View File

@@ -175,7 +175,7 @@ class AbilityDecorator
def add_order_management_abilities(user)
# Enterprise User can only access orders that they are a distributor for
can [:index, :create], Spree::Order
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
can [:read, :update, :fire, :resend, :invoice, :print, :print_ticket], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)

View File

@@ -34,10 +34,30 @@ module Spree
included_tax > 0
end
def display_included_tax
Spree::Money.new(included_tax, { :currency => currency })
def tax_rates
case originator
when Spree::TaxRate
[originator]
when EnterpriseFee
case source
when Spree::LineItem
tax_category = originator.inherits_tax_category? ? source.product.tax_category : originator.tax_category
return tax_category ? tax_category.tax_rates.match(source.order) : []
when Spree::Order
return originator.tax_category ? originator.tax_category.tax_rates.match(source) : []
end
else
[find_closest_tax_rate_from_included_tax]
end
end
def find_closest_tax_rate_from_included_tax
approximation = (included_tax / (amount - included_tax))
return nil if approximation.infinite? or approximation.zero?
Spree::TaxRate.order("ABS(amount - #{approximation})").first
end
def self.without_callbacks
skip_callback :save, :after, :update_adjustable
skip_callback :destroy, :after, :update_adjustable

View File

@@ -6,7 +6,6 @@ Spree::AppConfiguration.class_eval do
# Terms of Service Preferences
preference :enterprises_require_tos, :boolean, default: false
preference :enterprise_tos_link, :string, default: "/Terms-of-service.pdf"
# Tax Preferences
preference :products_require_tax_category, :boolean, default: false

View File

@@ -234,12 +234,12 @@ Spree::Order.class_eval do
def tax_adjustment_totals
tax_adjustments.each_with_object(Hash.new) do |adjustment, hash|
if adjustment.originator_type == "Spree::TaxRate"
tax_rate = adjustment.originator.amount
else
tax_rate = (adjustment.included_tax / (adjustment.amount - adjustment.included_tax)).round(2)
end
hash.update({tax_rate => adjustment.included_tax}) { |_tax_rate, amount1, amount2| amount1 + amount2 }
tax_rates = adjustment.tax_rates
tax_rates_hash = Hash[tax_rates.collect do |tax_rate|
tax_amount = tax_rates.one? ? adjustment.included_tax : tax_rate.compute_tax(adjustment.amount)
[tax_rate.amount, tax_amount]
end]
hash.update(tax_rates_hash) { |_tax_rate, amount1, amount2| amount1 + amount2 }
end
end

View File

@@ -2,10 +2,6 @@
%fieldset.enterprise_toc.no-border-bottom
%legend{:align => "center"}= t(:enterprise_terms_of_service)
- [:enterprise_tos_link, :enterprises_require_tos].each do |pref|
- type = Spree::Config.preference_type(pref)
.field
= label_tag(pref, t(pref) + ': ') + tag(:br) if type != :boolean
= preference_field_tag(pref, Spree::Config[pref], :type => type)
= label_tag(pref, t(pref)) + tag(:br) if type == :boolean
.field
= preference_field_tag(:enterprises_require_tos, Spree::Config[:enterprises_require_tos], :type => Spree::Config.preference_type(:enterprises_require_tos))
= label_tag(:enterprises_require_tos, t(:enterprises_require_tos)) + tag(:br)

View File

@@ -9,8 +9,8 @@
%table.index
%thead
%tr{"data-hook" => "producer_properties_header"}
%th= t(:inherited_property)
%th= t(:value)
%th= t('admin.products.properties.inherited_property')
%th= t('admin.description')
%th.actions
%tbody#producer_properties{"data-hook" => ""}
- @product.supplier.producer_properties.each do |producer_property|

View File

@@ -0,0 +1,5 @@
/ replace "tr[data-hook='product_properties_header']"
%tr{"data-hook" => "product_properties_header"}
%th= t('admin.products.properties.property_name')
%th= t('admin.description')
%th.actions

View File

@@ -0,0 +1,3 @@
/ replace "[data-hook=admin_product_form_left] code[erb-loud]:contains('f.text_area :description')"
%text-angular{'id' => 'product_description', 'name' => 'product[description]', 'class' => 'text-angular', 'textangular-strip' => true, 'ta-paste' => "stripFormatting($html)", 'ta-toolbar' => "[['bold','italics','clear']]"}
= sanitize(@product.description)

View File

@@ -0,0 +1,2 @@
add_to_attributes 'fieldset.no-border-top'
attributes 'ng-app' => 'admin.products'

View File

@@ -72,7 +72,7 @@
= f.field_container :description do
= f.label :product_description, t(:product_description)
%br/
= f.text_area :description, class: 'fullwidth', rows: 3
%text-angular{'id' => 'product_description', 'name' => 'product[description]', 'class' => 'text-angular', 'textangular-strip' => true, 'ta-paste' => "stripFormatting($html)", 'ta-toolbar' => "[['bold','italics','clear']]"}
= f.error_message_on :description
.four.columns.omega{ style: "text-align: center" }
%fieldset.no-border-bottom{ id: "image" }

View File

@@ -37,7 +37,7 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
include ActionView::Helpers::SanitizeHelper
attributes :id, :name, :permalink
attributes :on_demand, :group_buy, :notes, :description
attributes :on_demand, :group_buy, :notes, :description, :description_html
attributes :properties_with_values
has_many :variants, serializer: Api::VariantSerializer
@@ -49,10 +49,17 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
has_many :images, serializer: Api::ImageSerializer
has_one :supplier, serializer: Api::IdSerializer
#return an unformatted descripton
def description
strip_tags object.description
end
#return a sanitized html description
def description_html
d = sanitize(object.description, options = {tags: "p, b, strong, em, i"})
d.to_s.html_safe
end
def properties_with_values
object.properties_including_inherited
end

View File

@@ -9,14 +9,16 @@
selected
- if type == 'supplier'
%td.receival-details
= text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions'
= text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => t('.receival_instructions_placeholder'), 'ng-model' => 'exchange.receival_instructions'
- if type == 'distributor'
%td.tags.panel-toggle.text-center{ name: "tags", ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } }
{{ exchange.tags.length }}
%td.collection-details
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'required' => 'required', 'placeholder' => t('.pickup_time_placeholder'), 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator', 'maxlength' => 35
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.edit.pickup_time_tip')}
%br/
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => 'Pick-up instructions', 'ng-model' => 'exchange.pickup_instructions', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => t('.pickup_instructions_placeholder'), 'ng-model' => 'exchange.pickup_instructions', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.edit.pickup_instructions_tip')}
%td.fees
%ol{ ng: { show: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } }
%li{'ng-repeat' => 'enterprise_fee in exchange.enterprise_fees'}

View File

@@ -4,11 +4,13 @@
.alpha.two.columns
= label_tag t('.ready_for')
.six.columns
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'placeholder' => t('.ready_for_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'required' => 'required', 'placeholder' => t('.ready_for_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30, 'maxlength' => 35
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.edit.pickup_time_tip')}
.two.columns
= label_tag t('.customer_instructions')
.six.columns.omega
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_instructions', 'placeholder' => t('.customer_instructions_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_instructions', 'size' => 30
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.edit.pickup_instructions_tip')}
= label_tag t('.products')
%table.exchanges

View File

@@ -28,8 +28,8 @@
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
%input.red{ type: "button", value: t(:update), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty" } }
%input.red{ type: "button", value: t('.update_and_close'), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } }
%input.red{ type: "button", value: t(:update), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
%input.red{ type: "button", value: t('.update_and_close'), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
%input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } }
- if order_cycles_simple_form

View File

@@ -7,7 +7,7 @@
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
%input.red{ type: "button", value: t(:create), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } }
%input.red{ type: "button", value: t(:create), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
%input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } }
- if order_cycles_simple_form

View File

@@ -4,8 +4,8 @@
%table.index.sortable{"data-hook" => "", "data-sortable-link" => main_app.update_positions_admin_enterprise_producer_properties_url(@enterprise)}
%thead
%tr{"data-hook" => "producer_properties_header"}
%th{colspan: "2"}= t('.property')
%th= t('.value')
%th{colspan: "2"}= t('admin.products.properties.property_name')
%th= t('admin.description')
%th.actions
%tbody#producer_properties{"data-hook" => ""}
= f.fields_for :producer_properties do |pp_form|

View File

@@ -6,7 +6,7 @@
= inject_enterprise_attributes
- steps = %w{about contact details finished images introduction}
- steps += %w{limit_reached logo promo social steps type}
- steps += %w{logo promo social steps type}
- steps.each do |step|
= render partial: "registration/steps/#{step}"
= render "modal"

View File

@@ -1,2 +1,4 @@
= render partial: "registration/steps/limit_reached"
/ Directive which loads the modal
%div{ "ofn-registration-limit-modal" => true }

View File

@@ -37,7 +37,7 @@
.small-12.columns{'ng-hide' => '!tos_required' }
%p.tos-message
#{t(:enterprise_tos_message)}
%a{href: "#{Spree::Config.enterprise_tos_link}", target: "_blank" } #{t(:enterprise_tos_link_text)}
%a{href: ContentConfig.footer_tos_url, target: "_blank" } #{t(:enterprise_tos_link_text)}
%p.tos-checkbox
%input{ type: 'checkbox', name: 'accept_terms', id: 'accept_terms', ng: { model: "tos_accepted" } }
%label{for: "accept_terms"} #{t(:enterprise_tos_agree)}

View File

@@ -105,6 +105,7 @@ module Openfoodnetwork
config.assets.precompile += ['mail/all.css']
config.assets.precompile += ['search/all.css', 'search/*.js']
config.assets.precompile += ['shared/*']
config.assets.precompile += ['qz/*']
config.active_support.escape_html_entities_in_json = true
end

View File

@@ -35,6 +35,7 @@ Openfoodnetwork::Application.configure do
# Tests assume English text on the site.
config.i18n.default_locale = "en"
I18n.locale = config.i18n.locale = config.i18n.default_locale
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,

View File

@@ -70,6 +70,8 @@ en-GB:
clear_all: Clear All
start_date: "Start Date"
end_date: "End Date"
unsaved_changes: "You have unsaved changes"
form_invalid: "Form contains missing or invalid fields"
columns: Columns
actions: Actions
viewing: "Viewing: %{current_view_name}"
@@ -198,7 +200,9 @@ en-GB:
variants_without_unit_value: "WARNING: Some variants do not have a unit value"
order_cycles:
edit:
choose_products_from: "Choose Products From:"
choose_products_from: 'Choose Products From:'
pickup_time_tip: When orders from this OC will be ready for the customer
pickup_instructions_tip: These instructions are shown to customers after they complete an order
enterprise:
select_outgoing_oc_products_from: Select outgoing OC products from
enterprises:

View File

@@ -105,10 +105,13 @@ en:
clear_all: Clear All
start_date: "Start Date"
end_date: "End Date"
unsaved_changes: "You have unsaved changes"
form_invalid: "Form contains missing or invalid fields"
columns: Columns
actions: Actions
viewing: "Viewing: %{current_view_name}"
description: Description
whats_this: What's this?
@@ -223,6 +226,9 @@ en:
inherits_properties?: Inherits Properties?
available_on: Available On
av_on: "Av. On"
properties:
property_name: Property Name
inherited_property: Inherited Property
variants:
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
@@ -271,10 +277,6 @@ en:
order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors."
variants_without_unit_value: "WARNING: Some variants do not have a unit value"
order_cycles:
edit:
choose_products_from: "Choose Products From:"
enterprise:
select_outgoing_oc_products_from: Select outgoing OC products from
@@ -463,6 +465,16 @@ en:
next_step: Next step
choose_starting_point: 'Choose your starting point:'
order_cycles:
edit:
advanced_settings: Advanced Settings
update_and_close: Update and Close
choose_products_from: 'Choose Products From:'
pickup_time_tip: When orders from this OC will be ready for the customer
pickup_instructions_tip: These instructions are shown to customers after they complete an order
exchange_form:
pickup_instructions_placeholder: "Pick-up instructions"
pickup_time_placeholder: "Ready for (ie. Date / Time)"
receival_instructions_placeholder: "Receival instructions"
advanced_settings:
title: Advanced Settings
choose_product_tip: You can opt to restrict all available products (both incoming and outgoing), to only those in %{inventory}'s inventory.
@@ -481,7 +493,7 @@ en:
distributor: Distributor
products: Products
tags: Tags
delivery_detaisl: Pickup / Delivery details
delivery_details: Pickup / Delivery details
debug_info: Debug information
name_and_timing_form:
name: Name
@@ -499,13 +511,7 @@ en:
customer_instructions_placeholder: Pick-up or delivery notes
products: Products
fees: Fees
edit:
advanced_settings: Advanced Settings
update_and_close: Update and Close
producer_properties:
form:
property: Property
value: Value
index:
title: Producer Properties
shared:

View File

@@ -32,21 +32,11 @@ module OpenFoodNetwork
end
def adjustment_tax(adjustable, adjustment)
tax_rates = rates_for(adjustable)
tax_rates = adjustment.tax_rates
tax_rates.select(&:included_in_price).sum do |rate|
rate.compute_tax adjustment.amount
end
end
def rates_for(adjustable)
case adjustable
when Spree::LineItem
tax_category = enterprise_fee.inherits_tax_category? ? adjustable.product.tax_category : enterprise_fee.tax_category
return tax_category ? tax_category.tax_rates.match(adjustable.order) : []
when Spree::Order
return enterprise_fee.tax_category ? enterprise_fee.tax_category.tax_rates.match(adjustable) : []
end
end
end
end

View File

@@ -1,7 +1,7 @@
module OpenFoodNetwork
class EnterpriseInjectionData
def active_distributors
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
@active_distributors ||= Enterprise.distributors_with_active_order_cycles.ready_for_checkout
end
def earliest_closing_times

View File

@@ -1,14 +1,22 @@
namespace :karma do
task :start => :environment do
task :start => :environment do |task|
continue_only_in_test_env task
with_tmp_config :start
end
task :run => :environment do
task :run => :environment do |task|
continue_only_in_test_env task
with_tmp_config :start, "--single-run"
end
private
def continue_only_in_test_env task
if Rails.env != 'test'
raise "Task must be called in test environment:\n bundle exec rake #{task.name} RAILS_ENV=test"
end
end
def with_tmp_config(command, args = nil)
Tempfile.open('karma_unit.js', Rails.root.join('tmp') ) do |f|
f.write unit_js(application_spec_files << i18n_file)

View File

@@ -14,4 +14,4 @@ echo "--- Bundling"
bundle install
echo "--- Running tests"
bundle exec rake karma:run
./script/karma run

13
script/karma Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
task="$1"
if [ "$task" = "run" ] || [ "$task" = "start" ]; then
exec bundle exec rake "karma:$task" RAILS_ENV=test
else
echo "Usage:"
echo " $0 run # to run the tests once"
echo " $0 start # to run the tests on every file modification"
exit 1
fi

View File

@@ -1,7 +1,9 @@
require 'spec_helper'
describe ShopController do
let(:distributor) { create(:distributor_enterprise) }
let!(:pm) { create(:payment_method) }
let!(:sm) { create(:shipping_method) }
let(:distributor) { create(:distributor_enterprise, payment_methods: [pm], shipping_methods: [sm]) }
it "redirects to the home page if no distributor is selected" do
spree_get :show

View File

@@ -6,7 +6,7 @@ describe ShopsController do
let!(:invisible_distributor) { create(:distributor_enterprise, visible: false) }
before do
Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] }
Enterprise.stub_chain("distributors_with_active_order_cycles.ready_for_checkout") { [distributor] }
end
# Exclusion from actual rendered view handled in features/consumer/home

View File

@@ -350,6 +350,8 @@ feature %q{
fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'New instructions 0'
fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1'
fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1'
fill_in 'order_cycle_outgoing_exchange_2_pickup_time', with: 'New time 2'
fill_in 'order_cycle_outgoing_exchange_2_pickup_instructions', with: 'New instructions 2'
page.find("table.exchanges tr.distributor-#{distributor.id} td.tags").click
within ".exchange-tags" do
@@ -616,6 +618,11 @@ feature %q{
select 'Permitted distributor', from: 'new_distributor_id'
click_button 'Add distributor'
fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time'
fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions'
fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'pickup time 2'
fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'pickup instructions'
# Should only have suppliers / distributors listed which the user is managing or
# has E2E permission to add products to order cycles
page.should_not have_select 'new_supplier_id', with_options: [supplier_unmanaged.name]

View File

@@ -35,7 +35,7 @@ feature %q{
fill_in 'product_on_hand', with: 5
select 'Test Tax Category', from: 'product_tax_category_id'
select 'Test Shipping Category', from: 'product_shipping_category_id'
fill_in 'product_description', with: "A description..."
page.find("input[name='product\[description\]']", visible: false).set('A description...')
click_button 'Create'
@@ -75,7 +75,8 @@ feature %q{
check 'product_on_demand'
select 'Test Tax Category', from: 'product_tax_category_id'
select 'Test Shipping Category', from: 'product_shipping_category_id'
fill_in 'product_description', with: "In demand, and on_demand! The hottest cakes in town."
#fill_in 'product_description', with: "In demand, and on_demand! The hottest cakes in town."
page.first("input[name='product\[description\]']", visible: false).set('In demand, and on_demand! The hottest cakes in town.')
click_button 'Create'

View File

@@ -98,6 +98,23 @@ feature "Registration", js: true do
expect(e.instagram).to eq "@InStAgRaM"
end
context "when the user has no more remaining enterprises" do
before do
user.update_attributes(enterprise_limit: 0)
end
it "displays the limit reached page" do
visit registration_path
expect(page).to have_selector "dd", text: "Login"
switch_to_login_tab
# Enter Login details
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_login_and_ensure_content I18n.t('limit_reached_headline')
end
end
end
describe "Terms of Service agreement" do

View File

@@ -0,0 +1,60 @@
require 'spec_helper'
feature "As a consumer I want to view products", js: true do
include AuthenticationWorkflow
include WebHelper
include ShopWorkflow
include UIComponentHelper
describe "Viewing a product" do
let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let(:supplier) { create(:supplier_enterprise) }
let(:oc1) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:variant) { product.variants.first }
let(:order) { create(:order, distributor: distributor) }
let(:exchange1) { oc1.exchanges.to_enterprises(distributor).outgoing.first }
before do
set_order order
end
describe "viewing HTML product descriptions" do
before do
exchange1.update_attribute :pickup_time, "monday"
add_variant_to_order_cycle(exchange1, variant)
end
it "shows HTML product description" do
product.description = "<p><b>Formatted</b> product description.</p>"
product.save!
visit shop_path
select "monday", :from => "order_cycle_id"
open_product_modal product
modal_should_be_open_for product
within(".reveal-modal") do
html.should include("<p><b>Formatted</b> product description.</p>")
end
end
it "does not show unsecure HTML" do
product.description = "<script>alert('Dangerous!');</script><p>Safe</p>"
product.save!
visit shop_path
select "monday", :from => "order_cycle_id"
open_product_modal product
modal_should_be_open_for product
within(".reveal-modal") do
html.should include("<p>Safe</p>")
html.should_not include("<script>alert('Dangerous!');</script>")
end
end
end
end
end

View File

@@ -6,8 +6,8 @@ feature 'Shops', js: true do
let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:invisible_distributor) { create(:distributor_enterprise, visible: false) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let!(:d1) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:d2) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) }
let!(:producer) { create(:supplier_enterprise) }
let!(:er) { create(:enterprise_relationship, parent: distributor, child: producer) }
@@ -57,6 +57,20 @@ feature 'Shops', js: true do
end
end
describe "showing available hubs" do
let!(:hub) { create(:distributor_enterprise, with_payment_and_shipping: false) }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [hub], coordinator: hub) }
let!(:producer) { create(:supplier_enterprise) }
let!(:er) { create(:enterprise_relationship, parent: hub, child: producer) }
it "does not show hubs that are not ready for checkout" do
visit shops_path
Enterprise.ready_for_checkout.should_not include hub
page.should_not have_content hub.name
end
end
describe "filtering by product property" do
let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) }
let!(:p1) { create(:simple_product, supplier: producer) }
@@ -92,7 +106,7 @@ feature 'Shops', js: true do
describe "taxon badges" do
let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) }
let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) }
let(:shop) { create(:distributor_enterprise) }
let(:shop) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let(:taxon_closed) { create(:taxon, name: 'Closed') }
describe "open shops" do

View File

@@ -1,3 +1,5 @@
require 'spec_helper'
module Spree
describe Adjustment do
it "has metadata" do
@@ -279,6 +281,21 @@ module Spree
adjustment.included_tax.should == 10.00
end
end
describe "getting the corresponding tax rate" do
let!(:adjustment_with_tax) { create(:adjustment, amount: 50, included_tax: 10) }
let!(:adjustment_without_tax) { create(:adjustment, amount: 50, included_tax: 0) }
let!(:tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new, amount: 0.25) }
let!(:other_tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new, amount: 0.3) }
it "returns nil if there is no included tax" do
adjustment_without_tax.find_closest_tax_rate_from_included_tax.should == nil
end
it "returns the most accurate tax rate" do
adjustment_with_tax.find_closest_tax_rate_from_included_tax.should == tax_rate
end
end
end
end
end

View File

@@ -276,6 +276,50 @@ describe Spree::Order do
end
end
describe "getting a hash of all taxes" do
let(:zone) { create(:zone_with_member) }
let(:coordinator) { create(:distributor_enterprise, charges_sales_tax: true) }
let(:tax_rate10) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.1, zone: zone) }
let(:tax_rate15) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.15, zone: zone) }
let(:tax_rate20) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.2, zone: zone) }
let(:tax_category10) { create(:tax_category, tax_rates: [tax_rate10]) }
let(:tax_category15) { create(:tax_category, tax_rates: [tax_rate15]) }
let(:tax_category20) { create(:tax_category, tax_rates: [tax_rate20]) }
let(:variant) { create(:variant, product: create(:product, tax_category: tax_category10)) }
let(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 46.0)) }
let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: tax_category20, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 48.0)) }
let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: [enterprise_fee], distributors: [coordinator], variants: [variant]) }
let!(:order) { create(:order, shipping_method: shipping_method, bill_address: create(:address), order_cycle: order_cycle, distributor: coordinator) }
let!(:line_item) { create(:line_item, order: order, variant: variant, price: 44.0) }
before do
Spree::Config.shipment_inc_vat = true
Spree::Config.shipping_tax_rate = tax_rate15.amount
order.create_shipment!
Spree::TaxRate.adjust(order)
order.reload.update_distribution_charge!
end
it "returns a hash with all 3 taxes" do
order.tax_adjustment_totals.size.should == 3
end
it "contains tax on line_item" do
order.tax_adjustment_totals[tax_rate10.amount].should == 4.0
end
it "contains tax on shipping_fee" do
order.tax_adjustment_totals[tax_rate15.amount].should == 6.0
end
it "contains tax on enterprise_fee" do
order.tax_adjustment_totals[tax_rate20.amount].should == 8.0
end
end
describe "setting the distributor" do
it "sets the distributor when no order cycle is set" do
d = create(:distributor_enterprise)