Merge remote-tracking branch 'origin/master' into transifex

This commit is contained in:
Maikel Linke
2016-09-21 16:36:22 +10:00
60 changed files with 662 additions and 138 deletions

View File

@@ -1,5 +1,6 @@
angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filter, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) ->
angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filter, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops, availableCountries) ->
$scope.shops = shops
$scope.availableCountries = availableCountries
$scope.RequestMonitor = RequestMonitor
$scope.submitAll = pendingChanges.submitAll
$scope.add = Customers.add

View File

@@ -0,0 +1,36 @@
angular.module("admin.customers").directive 'editAddressDialog', ($compile, $templateCache, $filter, DialogDefaults, Customers, StatusMessage) ->
restrict: 'A'
scope: true
link: (scope, element, attr) ->
scope.errors = []
scope.$watch 'address.country_id', (newVal) ->
if newVal
scope.states = scope.filter_states(newVal)
scope.updateAddress = ->
scope.edit_address_form.$setPristine()
if scope.edit_address_form.$valid
Customers.update(scope.address, scope.customer, scope.addressType).$promise.then (data) ->
scope.customer = data
template.dialog('close')
StatusMessage.display('success', t('admin.customers.index.update_address_success'))
else
scope.errors.push(t('admin.customers.index.update_address_error'))
template = $compile($templateCache.get('admin/edit_address_dialog.html'))(scope)
template.dialog(DialogDefaults)
element.bind 'click', (e) ->
if e.target.id == 'bill-address-link'
scope.addressType = 'bill_address'
else
scope.addressType = 'ship_address'
scope.address = scope.customer[scope.addressType]
template.dialog('open')
scope.$apply()
scope.filter_states = (countryID) ->
$filter('filter')(scope.availableCountries, {id: countryID})[0].states

View File

@@ -1,4 +1,4 @@
angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, DialogDefaults, CurrentShop, Customers) ->
angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $templateCache, DialogDefaults, CurrentShop, Customers) ->
restrict: 'A'
scope: true
link: (scope, element, attr) ->

View File

@@ -14,4 +14,8 @@ angular.module("admin.customers").factory 'CustomerResource', ($resource) ->
method: 'DELETE'
params:
id: '@id'
'update':
method: 'PUT'
params:
id: '@id'
})

View File

@@ -25,3 +25,11 @@ angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestM
request = CustomerResource.index(params, (data) => @customers = data)
RequestMonitor.load(request.$promise)
request.$promise
update: (address, customer, addressType) ->
params =
id: customer.id
customer:
"#{addressType}_attributes": address
CustomerResource.update params

View File

@@ -1,5 +1,5 @@
angular.module("admin.indexUtils").factory "switchClass", ($timeout) ->
return (element,classToAdd,removeClasses,timeout) ->
return (element, classToAdd, removeClasses, timeout) ->
$timeout.cancel element.timeout if element.timeout
element.removeClass className for className in removeClasses
element.addClass classToAdd

View File

@@ -1,5 +1,5 @@
Darkswarm.filter 'properties', ()->
# Filter anything that responds to object.properties
Darkswarm.filter 'properties', ->
# Filter anything that responds to object.supplied_properties
(objects, ids) ->
objects ||= []
ids ?= []
@@ -7,10 +7,7 @@ Darkswarm.filter 'properties', ()->
# No properties selected, pass all objects through.
objects
else
objects.filter (obj)->
properties = obj.properties
# Combine object properties with supplied properties, if they exist.
# properties = properties.concat obj.supplied_properties if obj.supplied_properties
# Match property array.
properties.some (property)->
objects.filter (obj) ->
properties = obj.supplied_properties || obj.properties
properties.some (property) ->
property.id in ids

View File

@@ -1,7 +1,12 @@
Darkswarm.filter 'propertiesOf', ->
(objects)->
(objects) ->
properties = {}
for object in objects
for property in object.properties
properties[property.id] = property
if object.supplied_properties?
for property in object.supplied_properties
properties[property.id] = property
else
for property in object.properties
properties[property.id] = property
properties

View File

@@ -0,0 +1,61 @@
#edit-address-dialog
%h2 {{ addressType === 'bill_address' ? "#{t('admin.customers.index.edit_bill_address')}" : "#{t('admin.customers.index.edit_ship_address')}" }}
%form{ name: 'edit_address_form', novalidate: true, ng: { submit: 'updateAddress()'}}
.row
= t('admin.customers.index.required_fileds')
(
%span.required *
)
.error{ ng: { repeat: "error in errors", bind: "error" } }
%table.no-borders
%tr
%td{style: 'width: 30%'}
= t('spree.street_address')
%span.required *
%td
%input{ type: 'text', name: 'address1', required: true, ng: { model: 'address.address1'} }
%tr
%td
= t('spree.street_address_1')
%td
%input{ type: 'text', name: 'address2', ng: { model: 'address.address2'} }
%tr
%td
= t('spree.phone')
%span.required *
%td
%input{ type: 'text', name: 'phone', required: true, ng: { model: 'address.phone'} }
%tr
%td
= t('spree.city')
%span.required *
%td
%input{ type: 'text', name: 'city', required: true, ng: { model: 'address.city'} }
%tr
%td
= t('spree.zipcode')
%span.required *
%td
%input{ type: 'text', name: 'zipcode', required: true, ng: { model: 'address.zipcode'} }
%tr
%td
= t('spree.country')
%span.required *
%td
%select{name: 'country', required: true, ng: {model: 'address.country_id', options: 'country.id as country.name for country in availableCountries'}}
%option{value: ''}
= t('admin.customers.index.select_country')
%tr
%td
= t('spree.state')
%span.required *
%td
%select{name: 'state', required: true, ng: {model: 'address.state_id', options: 'state.id as state.name for state in states'}}
%option{value: ''}
= t('admin.customers.index.select_state')
.text-center
%input.button.red.icon-plus{ type: 'submit', value: 'Update Address'}

View File

@@ -10,7 +10,7 @@
%span.filter-shopfront.property-selectors.pad-top
%ul.inline-block
%li{"ng-repeat" => "property in enterprise.properties"}
%li{"ng-repeat" => "property in enterprise.supplied_properties"}
%a.button.tiny{"ng-bind" => "property.presentation"}
.about-container.pad-top

View File

@@ -1,7 +1,8 @@
#trial_progress_bar
position: fixed
left: 0px
bottom: 0px
width: 100%
width: 100vw
padding: 8px 10px
font-weight: bold
background-color: #5498da

View File

@@ -5,3 +5,8 @@ input.ng-invalid {
border: solid 1px red;
}
}
select.ng-invalid {
border: solid 1px red;
}

View File

@@ -6,9 +6,9 @@ module Admin
{name: 'Producer signup page', preferences: [:producer_signup_pricing_table_html, :producer_signup_case_studies_html, :producer_signup_detail_html]},
{name: 'Hub signup page', preferences: [:hub_signup_pricing_table_html, :hub_signup_case_studies_html, :hub_signup_detail_html]},
{name: 'Group signup page', preferences: [:group_signup_pricing_table_html, :group_signup_case_studies_html, :group_signup_detail_html]},
{name: 'Footer', preferences: [:footer_logo,
{name: 'Footer and External Links', preferences: [:footer_logo,
:footer_facebook_url, :footer_twitter_url, :footer_instagram_url, :footer_linkedin_url, :footer_googleplus_url, :footer_pinterest_url,
:footer_email, :footer_links_md, :footer_about_url, :footer_tos_url]}]
:footer_email, :community_forum_url, :footer_links_md, :footer_about_url, :footer_tos_url]}]
end
def update

View File

@@ -16,6 +16,10 @@ module Admin
private
def free_use?
Spree::Config[:account_invoices_monthly_fixed] == 0 && Spree::Config[:account_invoices_monthly_rate] == 0
end
def fixed_description
fixed_amount = Spree::Money.new(Spree::Config[:account_invoices_monthly_fixed], {currency: Spree::Config[:currency]} ).rounded
monthly_bill_includes_fixed? ? "#{fixed_amount}" : ""

View File

@@ -39,6 +39,10 @@ module Admin
admin_inject_json_ams_array ngModule, "shops", @shops, Api::Admin::IdNameSerializer
end
def admin_inject_available_countries(ngModule='admin.customers')
admin_inject_json_ams_array ngModule, 'availableCountries', available_countries, Api::CountrySerializer
end
def admin_inject_hubs(opts={module: 'ofn.admin'})
admin_inject_json_ams_array opts[:module], "hubs", @hubs, Api::Admin::IdNameSerializer
end

View File

@@ -0,0 +1,28 @@
require_dependency 'spree/calculator'
class Calculator::FlatPercentPerItem < Spree::Calculator
# Spree's FlatPercentItemTotal calculator sums all amounts, and then calculates a percentage
# on them.
# In the cart, we display line item individual amounts rounded, so to have consistent
# calculations we do the same internally. Here, we round adjustments at the individual
# item level first, then multiply by the item quantity.
preference :flat_percent, :decimal, :default => 0
attr_accessible :preferred_flat_percent
def self.description
I18n.t(:flat_percent_per_item)
end
def compute(object)
line_items_for(object).sum do |li|
unless li.price.present? && li.quantity.present?
raise ArgumentError, "object must respond to #price and #quantity"
end
value = (li.price * BigDecimal(self.preferred_flat_percent.to_s) / 100.0).round(2)
value * li.quantity
end
end
end

View File

@@ -34,6 +34,8 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
# Footer
preference :footer_logo, :file
has_attached_file :footer_logo, default_url: "/assets/ofn-logo-footer.png"
#Other
preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet"
preference :footer_twitter_url, :string, default: "https://twitter.com/OpenFoodNet"
preference :footer_instagram_url, :string, default: ""
@@ -41,6 +43,7 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
preference :footer_googleplus_url, :string, default: ""
preference :footer_pinterest_url, :string, default: ""
preference :footer_email, :string, default: "hello@openfoodnetwork.org"
preference :community_forum_url, :string, default: "http://community.openfoodnetwork.org"
preference :footer_links_md, :text, default: <<-EOS
[Newsletter sign-up](/)

View File

@@ -6,8 +6,17 @@ class Customer < ActiveRecord::Base
has_many :orders, class_name: Spree::Order
before_destroy :check_for_orders
belongs_to :bill_address, foreign_key: :bill_address_id, class_name: Spree::Address
alias_attribute :billing_address, :bill_address
accepts_nested_attributes_for :bill_address
belongs_to :ship_address, foreign_key: :ship_address_id, class_name: Spree::Address
alias_attribute :shipping_address, :ship_address
accepts_nested_attributes_for :ship_address
before_validation :downcase_email
before_validation :empty_code
before_validation :set_unused_address_fields
validates :code, uniqueness: { scope: :enterprise_id, allow_nil: true }
validates :email, presence: true, uniqueness: { scope: :enterprise_id, message: I18n.t('validation_msg_is_associated_with_an_exising_customer') }
@@ -27,6 +36,11 @@ class Customer < ActiveRecord::Base
self.code = nil if code.blank?
end
def set_unused_address_fields
bill_address.firstname = bill_address.lastname = 'unused' if bill_address.present?
ship_address.firstname = ship_address.lastname = 'unused' if ship_address.present?
end
def associate_user
self.user = user || Spree::User.find_by_email(email)
end

View File

@@ -8,6 +8,15 @@ class ProducerProperty < ActiveRecord::Base
after_destroy :refresh_products_cache_from_destroy
scope :sold_by, ->(shop) {
joins(producer: {supplied_products: {variants: {exchanges: :order_cycle}}}).
merge(Exchange.outgoing).
merge(Exchange.to_enterprise(shop)).
merge(OrderCycle.active).
select('DISTINCT producer_properties.*')
}
def property_name
property.name if property
end

View File

@@ -1,7 +0,0 @@
module Spree
class Gateway::PayPalExpress < Gateway
# Something odd is happening with class inheritance here, this class (defined in spree_paypal_express gem)
# doesn't seem to pick up attr_accessible from the Gateway class, so we redefine the attrs we need here
attr_accessible :tag_list
end
end

View File

@@ -1,18 +1,5 @@
Spree::Gateway.class_eval do
acts_as_taggable
# Due to class load order, when config.cache_classes is enabled (ie. staging and production
# environments), this association isn't inherited from PaymentMethod. As a result, creating
# payment methods using payment gateways results in:
# undefined method `association_class' for nil:NilClass
# To avoid that, we redefine this association here.
has_and_belongs_to_many :distributors, join_table: 'distributors_payment_methods', :class_name => 'Enterprise', foreign_key: 'payment_method_id', association_foreign_key: 'distributor_id'
# Default to live
preference :server, :string, :default => 'live'
preference :test_mode, :boolean, :default => false
attr_accessible :tag_list
end

View File

@@ -1,7 +1,6 @@
Spree::PaymentMethod.class_eval do
acts_as_taggable
# See gateway_decorator.rb when modifying this association
has_and_belongs_to_many :distributors, join_table: 'distributors_payment_methods', :class_name => 'Enterprise', association_foreign_key: 'distributor_id'
attr_accessible :distributor_ids, :tag_list
@@ -45,26 +44,20 @@ Spree::PaymentMethod.class_eval do
def has_distributor?(distributor)
self.distributors.include?(distributor)
end
end
# Ensure that all derived classes also allow distributor_ids
Spree::Gateway.providers.each do |p|
p.attr_accessible :distributor_ids
p.instance_eval do
def clean_name
case name
when "Spree::PaymentMethod::Check"
"Cash/EFT/etc. (payments for which automatic validation is not required)"
when "Spree::Gateway::Migs"
"MasterCard Internet Gateway Service (MIGS)"
when "Spree::Gateway::Pin"
"Pin Payments"
when "Spree::Gateway::PayPalExpress"
"PayPal Express"
else
i = name.rindex('::') + 2
name[i..-1]
end
def self.clean_name
case name
when "Spree::PaymentMethod::Check"
"Cash/EFT/etc. (payments for which automatic validation is not required)"
when "Spree::Gateway::Migs"
"MasterCard Internet Gateway Service (MIGS)"
when "Spree::Gateway::Pin"
"Pin Payments"
when "Spree::Gateway::PayPalExpress"
"PayPal Express"
else
i = name.rindex('::') + 2
name[i..-1]
end
end
end

View File

@@ -1,11 +1,22 @@
module Spree
Property.class_eval do
has_many :producer_properties
scope :applied_by, ->(enterprise) {
select('DISTINCT spree_properties.*').
joins(:product_properties).
where('spree_product_properties.product_id IN (?)', enterprise.supplied_product_ids)
}
scope :sold_by, ->(shop) {
joins(products: {variants: {exchanges: :order_cycle}}).
merge(Exchange.outgoing).
merge(Exchange.to_enterprise(shop)).
merge(OrderCycle.active).
select('DISTINCT spree_properties.*')
}
after_save :refresh_products_cache
# When a Property is destroyed, dependent-destroy will destroy all ProductProperties,

View File

@@ -1,5 +0,0 @@
/ insert_top "[data-hook='admin_footer_scripts']"
- enterprise = spree_current_user.enterprises.first if OpenFoodNetwork::Permissions.new(spree_current_user).manages_one_enterprise?
= render 'spree/admin/shared/trial_progress_bar', enterprise: enterprise

View File

@@ -1,5 +1,8 @@
class Api::Admin::CustomerSerializer < ActiveModel::Serializer
attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list
attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list, :name
has_one :ship_address, serializer: Api::AddressSerializer
has_one :bill_address, serializer: Api::AddressSerializer
def tag_list
object.tag_list.join(",")

View File

@@ -21,7 +21,8 @@ end
class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
attributes :orders_close_at, :active
has_many :properties, serializer: Api::PropertySerializer
has_many :supplied_properties, serializer: Api::PropertySerializer
has_many :distributed_properties, serializer: Api::PropertySerializer
def orders_close_at
options[:data].earliest_closing_times[object.id]
@@ -31,13 +32,22 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
options[:data].active_distributors.andand.include? object
end
def properties
def supplied_properties
# This results in 3 queries per enterprise
product_properties = Spree::Property.applied_by(object)
producer_properties = object.properties
OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties
end
def distributed_properties
# This results in 3 queries per enterprise
product_properties = Spree::Property.sold_by(object)
ids = ProducerProperty.sold_by(object).pluck(:property_id)
producer_properties = Spree::Property.where(id: ids)
OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties
end
end
class Api::CachedEnterpriseSerializer < ActiveModel::Serializer

View File

@@ -12,6 +12,7 @@
= admin_inject_column_preferences module: 'admin.customers'
= admin_inject_shops
= admin_inject_available_countries
%div{ ng: { controller: 'customersCtrl' } }
.row.filters
@@ -37,6 +38,7 @@
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1
=t :loading_customers
.row{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredCustomers.length == 0'}
%h1#no_results
=t :no_customers_found
@@ -48,18 +50,25 @@
%table.index#customers
%col.email{ width: "20%", 'ng-show' => 'columns.email.visible' }
%col.code{ width: "20%", 'ng-show' => 'columns.code.visible' }
%col.tags{ width: "50%", 'ng-show' => 'columns.tags.visible' }
%col.name{ width: "20%", 'ng-show' => 'columns.name.visible' }
%col.code{ width: "10%", 'ng-show' => 'columns.code.visible' }
%col.tags{ width: "20%", 'ng-show' => 'columns.tags.visible' }
%col.bill_address{ width: "10%", 'ng-show' => 'columns.bill_address.visible' }
%col.ship_address{ width: "10%", 'ng-show' => 'columns.ship_address.visible' }
%col.actions{ width: "10%"}
%thead
%tr{ ng: { controller: "ColumnsCtrl" } }
-# %th.bulk
-# %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" }
%th.email{ 'ng-show' => 'columns.email.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" } Email
%a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" }=t('admin.email')
%th.name{ 'ng-show' => 'columns.name.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'customer.name'; reverse = !reverse" }=t('admin.name')
%th.code{ 'ng-show' => 'columns.code.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'customer.code'; reverse = !reverse" } Code
%th.tags{ 'ng-show' => 'columns.tags.visible' } Tags
%a{ :href => '', 'ng-click' => "predicate = 'customer.code'; reverse = !reverse" }=t('admin.customers.index.code')
%th.tags{ 'ng-show' => 'columns.tags.visible' }=t('admin.tags')
%th.bill_address{ 'ng-show' => 'columns.bill_address.visible' }=t('admin.customers.index.bill_address')
%th.ship_address{ 'ng-show' => 'columns.ship_address.visible' }=t('admin.customers.index.ship_address')
%th.actions
Ask?&nbsp;
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
@@ -67,6 +76,8 @@
-# %td.bulk
-# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' }
%td.email{ 'ng-show' => 'columns.email.visible', "ng-bind" => '::customer.email' }
%td.name{ 'ng-show' => 'columns.name.visible'}
%input{ type: 'text', name: 'name', ng: { model: 'customer.name' }, 'obj-for-update' => 'customer', 'attr-for-update' => 'name'}
%td.code{ 'ng-show' => 'columns.code.visible' }
%input{ type: 'text', name: 'code', ng: {model: 'customer.code', change: 'checkForDuplicateCodes()'}, "obj-for-update" => "customer", "attr-for-update" => "code" }
%i.icon-warning-sign{ ng: {if: 'duplicate'} }
@@ -74,6 +85,10 @@
%td.tags{ 'ng-show' => 'columns.tags.visible' }
.tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"}
%tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' }
%td.bill_address{ 'ng-show' => 'columns.bill_address.visible' }
%a{ id: 'bill-address-link', href: '#', "ng-bind" => "customer.bill_address ? customer.bill_address.address1 : '#{t('admin.customers.index.edit')}' | limitTo: 15", 'edit-address-dialog' => true }
%td.ship_address{ 'ng-show' => 'columns.ship_address.visible' }
%a{ id: 'ship-address-link', href: '#', "ng-bind" => "customer.ship_address ? customer.ship_address.address1 : '#{t('admin.customers.index.edit')}' | limitTo: 15", 'edit-address-dialog' => true }
%td.actions
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }

View File

@@ -50,7 +50,9 @@
-# At the end of your trial, there is a one-off $200 fee to fully activate your account. Then you will be billed for 2% of your actual transactions, capped at $50 a month (so if you dont sell anything you dont pay anything, but you never pay more than $50 a month).
- else
.shop_profile.option.one-third.column.alpha
.two.columns.alpha
&nbsp;
.shop_profile.option.six.columns
%a.full-width.button.selector{ ng: { click: "sells='none'", class: "{selected: sells=='none'}" } }
.top
%h3 Profile Only
@@ -59,25 +61,29 @@
%p.description
People can find and contact you on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings.
.full_hub.option.one-third.column
.full_hub.option.six.columns
%a.full-width.button.selector{ ng: { click: "sells='any'", class: "{selected: sells=='any'}" } }
.top
%h3 Hub Shop
%p Sell produce from others
.bottom
%monthly-pricing-description{ joiner: "newline" }
%p.description
Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network.
.two.columns.omega
&nbsp;
.row
.sixteen.columns.alpha
%span.error{ ng: { show: "(change_type.sells.$error.required || change_type.sells.$error.pattern) && submitted" } }
Please choose one of the options above.
- if @enterprise.sells == 'unspecified' && @enterprise.shop_trial_start_date.nil?
%input.button.big{ type: 'submit', value: 'Start 30 Day Trial', ng: { click: "submit(change_type)", show: "sells=='own' || sells=='any'" } }
%input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)", hide: "sells=='own' || sells=='any'" } }
-if free_use?
%input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)" } }
- else
- trial_length = Spree::Config[:shop_trial_length_days]
%input.button.big{ type: 'submit', value: "Start #{trial_length}-Day Shop Trial", ng: { click: "submit(change_type)", show: "sells=='own' || sells=='any'" } }
%input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)", hide: "sells=='own' || sells=='any'" } }
- elsif @enterprise.sells == 'unspecified'
%input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)" } }
- else

View File

@@ -1 +1 @@
= button_link_to "User Guide", "http://www.openfoodnetwork.org/platform/user-guide/", :icon => 'icon-external-link', :id => 'user_guide_link', target: '_blank'
= button_link_to "User Guide", "http://www.openfoodnetwork.org/platform/user-guide/", :icon => 'icon-external-link', target: '_blank'

View File

@@ -13,7 +13,7 @@
= t :email_admin_html, link: link_to('Admin Panel', spree.admin_url)
%p
= t :email_community_html, link: link_to('Join the community.', 'http://community.openfoodnetwork.org/')
= t :email_community_html, link: link_to(t(:join_the_community), ContentConfig.community_forum_url)
%p
= t :email_help

View File

@@ -4,9 +4,13 @@
%label
= t :hubs_buy
.trans-sentence
%span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
%render-svg{path: "{{taxon.icon}}"}
%span{"ng-bind" => "::taxon.name"}
%div
%span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
%render-svg{path: "{{taxon.icon}}"}
%span{"ng-bind" => "::taxon.name"}
%div
%span.fat-properties{"ng-repeat" => "property in hub.distributed_properties"}
%span{"ng-bind" => "property.presentation"}
%div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"}
&nbsp;
.columns.small-12.medium-3.large-2.fat

View File

@@ -21,7 +21,7 @@
%render-svg{path: "{{taxon.icon}}"}
%span{"ng-bind" => "::taxon.name"}
%div
%span.fat-properties{"ng-repeat" => "property in producer.properties"}
%span.fat-properties{"ng-repeat" => "property in producer.supplied_properties"}
%span{"ng-bind" => "property.presentation"}
%div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}

View File

@@ -1,11 +1,11 @@
- content_for :page_title do
%h1
= @enterprise.name
%span.small<
= "(#{enterprise_type_name(@enterprise)})"
= @enterprise.name
%span.small<
= "(#{enterprise_type_name(@enterprise)})"
- content_for :page_actions do
= render 'admin/shared/user_guide_link'
%li#user_guide_link
= render 'admin/shared/user_guide_link'
:javascript
function toggleType(){
@@ -17,7 +17,7 @@
}
$("#package_selection").slideToggle()
}
#package_button
%li#package_button
%button#toggle_type{ onClick: 'toggleType()' }
= t "change_package"
%i.icon-chevron-down
@@ -98,3 +98,5 @@
%a.button.bottom{href: main_app.admin_order_cycles_path}
= t "manage_order_cycles"
%span.icon-arrow-right
= render 'spree/admin/shared/trial_progress_bar', enterprise: @enterprise

View File

@@ -21,9 +21,7 @@
%p.lead
= t :email_signup_text
%p
= t :email_signup_help_html
%a{:href => "mailto:hello@openfoodnetwork.org", :target => "_blank"}
hello@openfoodnetwork.org
= t :email_signup_help_html, email: mail_to(ContentConfig.footer_email)
= render 'shared/mailers/signoff'

View File

@@ -28,7 +28,7 @@ module Openfoodnetwork
initializer "spree.register.calculators" do |app|
app.config.spree.calculators.shipping_methods << OpenFoodNetwork::Calculator::Weight
app.config.spree.calculators.enterprise_fees = [Spree::Calculator::FlatPercentItemTotal,
app.config.spree.calculators.enterprise_fees = [Calculator::FlatPercentPerItem,
Spree::Calculator::FlatRate,
Spree::Calculator::FlexiRate,
Spree::Calculator::PerItem,

View File

@@ -9,6 +9,9 @@
require 'spree/product_filters'
require "#{Rails.root}/app/models/spree/payment_method_decorator"
require "#{Rails.root}/app/models/spree/gateway_decorator"
Spree.config do |config|
config.shipping_instructions = true
config.address_requires_state = true

View File

@@ -118,6 +118,16 @@ en:
add_a_new_customer_for: Add a new customer for %{shop_name}
code: Code
duplicate_code: "This code is used already."
bill_address: "Billing Address"
ship_address: "Shipping Address"
update_address_success: 'Address updated successfully.'
update_address_error: 'Sorry! Please input all of the required fields!'
edit_bill_address: 'Edit Billing Address'
edit_ship_address: 'Edit Shipping Address'
required_fileds: 'Required fields are denoted with an asterisk '
select_country: 'Select Country'
select_state: 'Select State'
edit: 'Edit'
products:
bulk_edit:
@@ -411,6 +421,7 @@ en:
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next.
%{link}"
join_community: "Join the community"
email_help: "If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!"
email_confirmation_greeting: "Hi, %{contact}!"
email_confirmation_profile_created: "A profile for %{name} has been successfully created!
@@ -458,7 +469,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
email_signup_text: "Thanks for joining the network.
If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food!
If you are a producer or food enterprise, we are excited to have you as a part of the network."
email_signup_help_html: "We welcome all your questions and feedback; you can use the <em>Send Feedback</em> button on the site or email us at"
email_signup_help_html: "We welcome all your questions and feedback; you can use the <em>Send Feedback</em> button on the site or email us at %{email}"
producer_mail_greeting: "Dear"
producer_mail_text_before: "We now have all the consumer orders for the next food drop."
@@ -900,6 +911,7 @@ Please follow the instructions there to make your enterprise visible on the Open
tax_category: "Tax Category"
calculator: "Calculator"
calculator_values: "Calculator values"
flat_percent_per_item: "Flat Percent (per item)"
new_order_cycles: "New Order Cycles"
select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle"
edit_order_cycle: "Edit Order Cycle"

View File

@@ -0,0 +1,12 @@
class AddBillAddressAndShipAddressToCustomer < ActiveRecord::Migration
def change
add_column :customers, :bill_address_id, :integer
add_column :customers, :ship_address_id, :integer
add_index :customers, :bill_address_id
add_index :customers, :ship_address_id
add_foreign_key :customers, :spree_addresses, column: :bill_address_id
add_foreign_key :customers, :spree_addresses, column: :ship_address_id
end
end

View File

@@ -0,0 +1,5 @@
class AddNameToCustomer < ActiveRecord::Migration
def change
add_column :customers, :name, :string
end
end

View File

@@ -0,0 +1,29 @@
class SwapCalculatorToFlatPercentPerItem < ActiveRecord::Migration
class Spree::Calculator < ActiveRecord::Base
end
def up
Spree::Calculator.where(calculable_type: "EnterpriseFee", type: 'Spree::Calculator::FlatPercentItemTotal').each do |c|
swap_calculator_type c, 'Calculator::FlatPercentPerItem'
end
end
def down
Spree::Calculator.where(calculable_type: "EnterpriseFee", type: 'Spree::Calculator::FlatPercentPerItem').each do |c|
swap_calculator_type c, 'Calculator::FlatPercentItemTotal'
end
end
private
def swap_calculator_type(calculator, to_class)
value = calculator.preferred_flat_percent
calculator.type = to_class
calculator.save
calculator = Spree::Calculator.find calculator.id
calculator.preferred_flat_percent = value
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20160707023818) do
ActiveRecord::Schema.define(:version => 20160819065331) do
create_table "account_invoices", :force => true do |t|
t.integer "user_id", :null => false
@@ -79,16 +79,21 @@ ActiveRecord::Schema.define(:version => 20160707023818) do
add_index "coordinator_fees", ["order_cycle_id"], :name => "index_coordinator_fees_on_order_cycle_id"
create_table "customers", :force => true do |t|
t.string "email", :null => false
t.integer "enterprise_id", :null => false
t.string "email", :null => false
t.integer "enterprise_id", :null => false
t.string "code"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "bill_address_id"
t.integer "ship_address_id"
t.string "name"
end
add_index "customers", ["bill_address_id"], :name => "index_customers_on_bill_address_id"
add_index "customers", ["email"], :name => "index_customers_on_email"
add_index "customers", ["enterprise_id", "code"], :name => "index_customers_on_enterprise_id_and_code", :unique => true
add_index "customers", ["ship_address_id"], :name => "index_customers_on_ship_address_id"
add_index "customers", ["user_id"], :name => "index_customers_on_user_id"
create_table "delayed_jobs", :force => true do |t|
@@ -577,9 +582,9 @@ ActiveRecord::Schema.define(:version => 20160707023818) do
t.string "email"
t.text "special_instructions"
t.integer "distributor_id"
t.integer "order_cycle_id"
t.string "currency"
t.string "last_ip_address"
t.integer "order_cycle_id"
t.integer "cart_id"
t.integer "customer_id"
end
@@ -1111,6 +1116,8 @@ ActiveRecord::Schema.define(:version => 20160707023818) do
add_foreign_key "coordinator_fees", "order_cycles", name: "coordinator_fees_order_cycle_id_fk"
add_foreign_key "customers", "enterprises", name: "customers_enterprise_id_fk"
add_foreign_key "customers", "spree_addresses", name: "customers_bill_address_id_fk", column: "bill_address_id"
add_foreign_key "customers", "spree_addresses", name: "customers_ship_address_id_fk", column: "ship_address_id"
add_foreign_key "customers", "spree_users", name: "customers_user_id_fk", column: "user_id"
add_foreign_key "distributors_payment_methods", "enterprises", name: "distributors_payment_methods_distributor_id_fk", column: "distributor_id"

View File

@@ -27,9 +27,12 @@ module OpenFoodNetwork
def customers_index_columns
node = 'admin.customers.index'
{
email: { name: I18n.t("admin.email"), visible: true },
code: { name: I18n.t("#{node}.code"), visible: true },
tags: { name: I18n.t("admin.tags"), visible: true }
email: { name: I18n.t("admin.email"), visible: true },
name: { name: I18n.t("admin.name"), visible: true },
code: { name: I18n.t("#{node}.code"), visible: true },
tags: { name: I18n.t("admin.tags"), visible: true },
bill_address: { name: I18n.t("#{node}.bill_address"), visible: true },
ship_address: { name: I18n.t("#{node}.ship_address"), visible: true }
}
end

View File

@@ -104,8 +104,7 @@ module OpenFoodNetwork
def calculate_fee_for(variant, enterprise_fee)
# Spree's Calculator interface accepts Orders or LineItems,
# so we meet that interface with a struct.
# Amount is faked, this is a method on LineItem
line_item = OpenStruct.new variant: variant, quantity: 1, amount: variant.price
line_item = OpenStruct.new variant: variant, quantity: 1, price: variant.price, amount: variant.price
enterprise_fee.compute_amount(line_item)
end

View File

@@ -270,6 +270,7 @@ FactoryGirl.define do
enterprise
code { SecureRandom.base64(150) }
user
bill_address { create(:address) }
end
factory :billable_period do

View File

@@ -80,8 +80,10 @@ feature 'Customers' do
within "tr#c_#{customer1.id}" do
fill_in "code", with: "new-customer-code"
expect(page).to have_css "input[name=code].update-pending"
end
within "tr#c_#{customer1.id}" do
fill_in "name", with: "customer abc"
expect(page).to have_css "input[name=name].update-pending"
find(:css, "tags-input .tags input").set "awesome\n"
expect(page).to have_css ".tag_watcher.update-pending"
end
@@ -89,18 +91,22 @@ feature 'Customers' do
# Every says it updated
expect(page).to have_css "input[name=code].update-success"
expect(page).to have_css "input[name=name].update-success"
expect(page).to have_css ".tag_watcher.update-success"
# And it actually did
expect(customer1.reload.code).to eq "new-customer-code"
expect(customer1.reload.name).to eq "customer abc"
expect(customer1.tag_list).to eq ["awesome"]
# Clearing attributes
within "tr#c_#{customer1.id}" do
fill_in "code", with: ""
expect(page).to have_css "input[name=code].update-pending"
end
within "tr#c_#{customer1.id}" do
fill_in "name", with: ""
expect(page).to have_css "input[name=name].update-pending"
find("tags-input li.tag-item a.remove-button").trigger('click')
expect(page).to have_css ".tag_watcher.update-pending"
end
@@ -108,10 +114,12 @@ feature 'Customers' do
# Every says it updated
expect(page).to have_css "input[name=code].update-success"
expect(page).to have_css "input[name=name].update-success"
expect(page).to have_css ".tag_watcher.update-success"
# And it actually did
expect(customer1.reload.code).to be nil
expect(customer1.reload.name).to eq ''
expect(customer1.tag_list).to eq []
end
@@ -142,6 +150,52 @@ feature 'Customers' do
expect(customer2.reload.code).to be nil
end
describe 'updating a customer addresses' do
before do
select2_select managed_distributor2.name, from: "shop_id"
end
it 'updates the existing billing address' do
expect(page).to have_content 'BILLING ADDRESS'
first('#bill-address-link').click
expect(page).to have_content 'Edit Billing Address'
fill_in 'address1', with: "New Address1"
click_button 'Update Address'
expect(page).to have_content 'Address updated successfully.'
expect(page).to have_link 'New Address1'
expect(customer4.reload.bill_address.address1).to eq 'New Address1'
end
it 'creates a new shipping address' do
expect(page).to have_content 'SHIPPING ADDRESS'
first('#ship-address-link').click
expect(page).to have_content 'Edit Shipping Address'
fill_in 'address1', with: "New Address1"
fill_in 'phone', with: "12345678"
fill_in 'city', with: "Melbourne"
fill_in 'zipcode', with: "3000"
select 'Australia', from: 'country'
select 'Victoria', from: 'state'
click_button 'Update Address'
expect(page).to have_content 'Address updated successfully.'
expect(page).to have_link 'New Address1'
ship_address = customer4.reload.ship_address
expect(ship_address.address1).to eq 'New Address1'
expect(ship_address.phone).to eq '12345678'
expect(ship_address.city).to eq 'Melbourne'
end
end
describe "creating a new customer" do
context "when no shop has been selected" do
it "asks the user to select a shop" do

View File

@@ -77,13 +77,13 @@ feature %q{
page.should have_select "enterprise_fee_set_collection_attributes_0_fee_type", selected: 'Admin'
page.should have_selector "input[value='Greetings!']"
page.should have_select 'enterprise_fee_set_collection_attributes_0_tax_category_id', selected: 'Inherit From Product'
page.should have_selector "option[selected]", text: 'Flat Percent'
page.should have_selector "option[selected]", text: 'Flat Percent (per item)'
fee.reload
fee.enterprise.should == enterprise
fee.name.should == 'Greetings!'
fee.fee_type.should == 'admin'
fee.calculator_type.should == "Spree::Calculator::FlatPercentItemTotal"
fee.calculator_type.should == "Calculator::FlatPercentPerItem"
# Sets tax_category and inherits_tax_category
fee.tax_category.should == nil

View File

@@ -10,20 +10,42 @@ feature "full-page cart", js: true 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_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product_tax.variants.first, product_fee.variants.first]) }
let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product_tax.tax_category) }
let(:product_tax) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) }
let(:product_fee) { create(:simple_product, supplier: supplier, price: 0.86, on_hand: 100) }
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 "fees" do
let(:percentage_fee) { create(:enterprise_fee, calculator: Calculator::FlatPercentPerItem.new(preferred_flat_percent: 20)) }
before do
add_enterprise_fee percentage_fee
add_product_to_cart order, product_fee, quantity: 8
visit spree.cart_path
end
it "rounds fee calculations correctly" do
# $0.86 + 20% = $1.032
# Fractional cents should be immediately rounded down and not carried through
expect(page).to have_selector '.cart-item-price', text: '$1.03'
expect(page).to have_selector '.cart-item-total', text: '$8.24'
expect(page).to have_selector '.order-total.item-total', text: '$8.24'
expect(page).to have_selector '.order-total.grand-total', text: '$8.24'
end
end
describe "tax" do
before do
add_enterprise_fee enterprise_fee
add_product_to_cart order, product_tax
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
@@ -31,12 +53,15 @@ feature "full-page cart", js: true do
describe "updating quantities with insufficient stock available" do
let(:li) { order.line_items(true).last }
let(:variant) { product_tax.variants.first }
before do
variant.update_attributes! on_hand: 2
add_product_to_cart order, product_tax
end
it "prevents me from entering an invalid value" do
# Given we have 2 on hand, and we've loaded the page after that fact
variant.update_attributes! on_hand: 2
visit spree.cart_path
accept_alert 'Insufficient stock available, only 2 remaining' do
@@ -47,6 +72,10 @@ feature "full-page cart", js: true do
end
it "shows the quantities saved, not those submitted" do
# Given we load the page with 3 on hand, then the number available drops to 2
visit spree.cart_path
variant.update_attributes! on_hand: 2
fill_in "order_line_items_attributes_0_quantity", with: '4'
click_button 'Update'

View File

@@ -18,7 +18,7 @@ feature "As a consumer I want to check out my cart", js: true do
before do
set_order order
add_product_to_cart
add_product_to_cart order, product
end
it "does not not render the login form when logged in" do

View File

@@ -23,7 +23,7 @@ feature "As a consumer I want to check out my cart", js: true do
add_enterprise_fee enterprise_fee
set_order order
add_product_to_cart
add_product_to_cart order, product
end
describe "with shipping and payment methods" do

View File

@@ -28,7 +28,7 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do
let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3, default_stock: nil, resettable: false) }
let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0, default_stock: nil, resettable: false) }
let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6, default_stock: nil, resettable: false) }
let(:ef) { create(:enterprise_fee, enterprise: hub, fee_type: 'packing', calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)) }
let(:ef) { create(:enterprise_fee, enterprise: hub, fee_type: 'packing', calculator: Calculator::FlatPercentPerItem.new(preferred_flat_percent: 10)) }
before do
outgoing_exchange.variants = [v1, v2, v3, v4, v5, v6]
@@ -91,17 +91,17 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do
page.should have_field "order[line_items_attributes][0][quantity]", with: '2'
page.should have_selector "tr.line-item.variant-#{v1.id} .cart-item-total", text: '$122.22'
page.should have_selector "#edit-cart .item-total", text: '$122.21'
page.should have_selector "#edit-cart .grand-total", text: '$122.21'
page.should have_selector "#edit-cart .item-total", text: '$122.22'
page.should have_selector "#edit-cart .grand-total", text: '$122.22'
end
it "shows the correct prices in the checkout" do
fill_in "variants[#{v1.id}]", with: "2"
click_checkout
page.should have_selector 'form.edit_order .cart-total', text: '$122.21'
page.should have_selector 'form.edit_order .cart-total', text: '$122.22'
page.should have_selector 'form.edit_order .shipping', text: '$0.00'
page.should have_selector 'form.edit_order .total', text: '$122.21'
page.should have_selector 'form.edit_order .total', text: '$122.22'
end
end
@@ -115,7 +115,7 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do
o = Spree::Order.complete.last
o.line_items.first.price.should == 55.55
o.total.should == 122.21
o.total.should == 122.22
end
it "subtracts stock from the override" do

View File

@@ -56,12 +56,36 @@ feature 'Shops', js: true do
expect(page).to have_current_path enterprise_shop_path(distributor)
end
describe "property badges" do
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
let(:product) { create(:simple_product, supplier: producer) }
before do
product.set_property 'Local', 'XYZ 123'
end
it "shows property badges" do
# Given a shop with a product with a property
# And the product's producer has a producer property
# When I go to the shops path
visit shops_path
# And I open the shop
expand_active_table_node distributor.name
# Then I should see both properties
expect(page).to have_content 'Local' # Product property
expect(page).to have_content 'Organic' # Producer property
end
end
describe "hub producer modal" do
let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) }
let!(:taxon) { create(:taxon, name: 'Fruit') }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
it "should show hub producer modals" do
it "shows hub producer modals" do
expand_active_table_node distributor.name
expect(page).to have_content producer.name
open_enterprise_modal producer

View File

@@ -15,11 +15,15 @@ describe "CustomersCtrl", ->
{ name: "Shop 3", id: 3 }
]
availableCountries = [
{id: 109, name: "Australia", states: [{id: 55, name: "ACT", abbr: "ACT"}]}
]
inject ($controller, $rootScope, _CustomerResource_, $httpBackend) ->
scope = $rootScope
http = $httpBackend
$controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: shops}
$controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: shops, availableCountries: availableCountries}
jasmine.addMatchers
toDeepEqual: (util, customEqualityTesters) ->
compare: (actual, expected) ->

View File

@@ -66,7 +66,7 @@ module OpenFoodNetwork
end
describe "summing percentage fees for the variant" do
let!(:enterprise_fee1) { create(:enterprise_fee, amount: 20, fee_type: "admin", calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20)) }
let!(:enterprise_fee1) { create(:enterprise_fee, amount: 20, fee_type: "admin", calculator: ::Calculator::FlatPercentPerItem.new(preferred_flat_percent: 20)) }
let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1], variants: [product1.master]) }

View File

@@ -0,0 +1,13 @@
describe Calculator::FlatPercentPerItem do
let(:calculator) { Calculator::FlatPercentPerItem.new preferred_flat_percent: 20 }
it "calculates for a simple line item" do
line_item = Spree::LineItem.new price: 50, quantity: 2
expect(calculator.compute(line_item)).to eq 20
end
it "rounds fractional cents before summing" do
line_item = Spree::LineItem.new price: 0.86, quantity: 8
expect(calculator.compute(line_item)).to eq 1.36
end
end

View File

@@ -18,6 +18,26 @@ describe Customer, type: :model do
end
end
describe 'update shipping address' do
let(:customer) { create(:customer) }
it 'updates the shipping address' do
expect(customer.shipping_address).to be_nil
ship_address = {zipcode: "3127",
city: "Melbourne",
state_id: 1,
phone: "455500146",
address1: "U 3/32 Florence Road Surrey Hills2",
country_id: 1}
customer.update_attributes!(ship_address_attributes: ship_address)
expect(customer.ship_address.city).to eq 'Melbourne'
expect(customer.ship_address.firstname).to eq 'unused'
expect(customer.ship_address.lastname).to eq 'unused'
end
end
describe 'creation callbacks' do
let!(:user1) { create(:user) }
let!(:user2) { create(:user) }

View File

@@ -75,7 +75,7 @@ describe EnterpriseFee do
it "returns fees with any other calculator" do
ef1 = create(:enterprise_fee, calculator: Spree::Calculator::DefaultTax.new)
ef2 = create(:enterprise_fee, calculator: Spree::Calculator::FlatPercentItemTotal.new)
ef2 = create(:enterprise_fee, calculator: Calculator::FlatPercentPerItem.new)
ef3 = create(:enterprise_fee, calculator: Spree::Calculator::PerItem.new)
ef4 = create(:enterprise_fee, calculator: Spree::Calculator::PriceSack.new)
@@ -93,7 +93,7 @@ describe EnterpriseFee do
it "does not return fees with any other calculator" do
ef1 = create(:enterprise_fee, calculator: Spree::Calculator::DefaultTax.new)
ef2 = create(:enterprise_fee, calculator: Spree::Calculator::FlatPercentItemTotal.new)
ef2 = create(:enterprise_fee, calculator: Calculator::FlatPercentPerItem.new)
ef3 = create(:enterprise_fee, calculator: Spree::Calculator::PerItem.new)
ef4 = create(:enterprise_fee, calculator: Spree::Calculator::PriceSack.new)

View File

@@ -1,14 +1,70 @@
require 'spec_helper'
describe ProducerProperty do
describe "products caching" do
let(:producer) { create(:supplier_enterprise) }
let(:pp) { producer.producer_properties.first }
let(:producer) { create(:supplier_enterprise) }
let(:pp) { producer.producer_properties.first }
before do
producer.set_producer_property 'Organic Certified', 'NASAA 54321'
end
describe ".sold_by" do
let!(:shop) { create(:distributor_enterprise) }
let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) }
let(:product) { create(:simple_product, supplier: producer) }
let(:producer_other) { create(:supplier_enterprise) }
let(:product_other) { create(:simple_product, supplier: producer_other) }
let(:pp_other) { producer_other.producer_properties.first }
before do
producer.set_producer_property 'Organic Certified', 'NASAA 54321'
producer_other.set_producer_property 'Spiffy', 'Ya'
end
describe "with an associated producer property" do
it "returns the producer property" do
expect(ProducerProperty.sold_by(shop)).to eq [pp]
end
end
describe "with a producer property for a producer not carried by that shop" do
let!(:exchange) { create(:exchange, order_cycle: oc, incoming: true, sender: producer_other, receiver: oc.coordinator) }
it "doesn't return the producer property" do
expect(ProducerProperty.sold_by(shop)).not_to include pp_other
end
end
describe "with a producer property for a product in a different shop" do
let(:shop_other) { create(:distributor_enterprise) }
let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) }
let!(:exchange) { create(:exchange, order_cycle: oc, incoming: false, sender: oc.coordinator, receiver: shop_other, variants: [product_other.variants.first]) }
it "doesn't return the producer property" do
expect(ProducerProperty.sold_by(shop)).not_to include pp_other
end
end
describe "with a producer property for a product in a closed order cycle" do
before do
oc.update_attributes! orders_close_at: 1.week.ago
end
it "doesn't return the producer property" do
expect(ProducerProperty.sold_by(shop)).not_to include pp
end
end
describe "with a duplicate producer property" do
let(:product2) { create(:simple_product, supplier: producer) }
let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first, product2.variants.first]) }
it "doesn't return duplicates" do
expect(ProducerProperty.sold_by(shop).to_a.count).to eq 1
end
end
end
describe "products caching" do
it "refreshes the products cache on change" do
expect(OpenFoodNetwork::ProductsCache).to receive(:producer_property_changed).with(pp)
pp.value = 123

View File

@@ -30,6 +30,58 @@ module Spree
expect(Spree::Property.applied_by(producer).to_a.count).to eq 1
end
end
describe ".sold_by" do
let!(:shop) { create(:distributor_enterprise) }
let!(:shop_other) { create(:distributor_enterprise) }
let!(:product) { create(:simple_product) }
let!(:product_other_ex) { create(:simple_product) }
let!(:product_no_oc) { create(:simple_product) }
let!(:product_closed_oc) { create(:simple_product) }
let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) }
let!(:oc_closed) { create(:closed_order_cycle, distributors: [shop], variants: [product_closed_oc.variants.first]) }
let!(:exchange_other_shop) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: shop_other, variants: [product_other_ex.variants.first]) }
let(:property) { product.properties.last }
let(:property_other_ex) { product_other_ex.properties.last }
let(:property_no_oc) { product_no_oc.properties.last }
let(:property_closed_oc) { product_closed_oc.properties.last }
before do
product.set_property 'Organic', 'NASAA 12345'
product_other_ex.set_property 'Biodynamic', 'ASDF 12345'
product_no_oc.set_property 'Shiny', 'Very'
product_closed_oc.set_property 'Spiffy', 'Ooh yeah'
end
it "returns the property" do
expect(Property.sold_by(shop)).to eq [property]
end
it "doesn't return the property from another exchange" do
expect(Property.sold_by(shop)).not_to include property_other_ex
end
it "doesn't return the property with no order cycle" do
expect(Property.sold_by(shop)).not_to include property_no_oc
end
it "doesn't return the property from a closed order cycle" do
expect(Property.sold_by(shop)).not_to include property_closed_oc
end
context "with another product in the order cycle" do
let!(:product2) { create(:simple_product) }
let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first, product2.variants.first]) }
before do
product2.set_property 'Organic', 'NASAA 12345'
end
it "doesn't return duplicates" do
expect(Property.sold_by(shop).to_a.count).to eq 1
end
end
end
end
describe "callbacks" do

View File

@@ -11,5 +11,9 @@ describe Api::Admin::CustomerSerializer do
expect(tags.length).to eq 3
expect(tags[0]).to eq({ "text" => 'one', "rules" => nil })
expect(tags[1]).to eq({ "text" => 'two', "rules" => 1 })
expect(result['bill_address']['id']).to eq customer.bill_address.id
expect(result['bill_address']['address1']).to eq customer.bill_address.address1
expect(result['ship_address']).to be nil
end
end

View File

@@ -16,9 +16,9 @@ module ShopWorkflow
ApplicationController.any_instance.stub(:session).and_return({order_id: order.id, access_token: order.token})
end
def add_product_to_cart
def add_product_to_cart(order, product, quantity: 1)
populator = Spree::OrderPopulator.new(order, order.currency)
populator.populate(variants: {product.variants.first.id => 1})
populator.populate(variants: {product.variants.first.id => quantity})
# Recalculate fee totals
order.update_distribution_charge!