Compare commits

...

67 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
Maikel Linke
ec36a843cf Merge tag 'v1.8.8' into transifex 2017-04-06 10:31:26 +10:00
Transifex-Openfoodnetwork
e99dbaf4d8 Updating translations for config/locales/es.yml [skip ci] 2017-04-05 19:40:18 +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
Transifex-Openfoodnetwork
6defb09d2e Updating translations for config/locales/fr.yml [skip ci] 2017-03-21 09:03:15 +11:00
Transifex-Openfoodnetwork
2774c09d7a Updating translations for config/locales/nb.yml [skip ci] 2017-03-20 07:10:53 +11:00
Matt-Yorkley
c62a044598 PI highlight invalid fields in feedback tables 2017-03-17 16:11:52 +11:00
Matt-Yorkley
f73fbe7f23 SpreadsheetEntry Class and PI refactor 2017-03-17 16:11:52 +11:00
Matt-Yorkley
5e1e4c1d19 Product Import UX review updates
Minor tweaks

Minor fix
2017-03-17 16:11:52 +11:00
Matt-Yorkley
cc5a335fb7 Refactor and additional permissions checks
Don't include non-permitted enterprises in existin product count

Tweaked feedback
2017-03-17 16:11:52 +11:00
Matt-Yorkley
91fc3f33a0 PI reset and save step improvements 2017-03-17 16:11:52 +11:00
Matt-Yorkley
648753b412 Improved save step UI 2017-03-17 16:11:52 +11:00
Matt-Yorkley
24fcc3dd34 PI reset absent products 2017-03-17 16:11:52 +11:00
Matt-Yorkley
14fb40a996 Product Import options and defaults
Added available_on test

Obscure case fix and extra spec
2017-03-17 16:11:52 +11:00
Matt-Yorkley
f4511fc74d PI permission test 2017-03-17 16:11:52 +11:00
Matt-Yorkley
3d0f192490 Product Import update 2017-03-17 16:11:52 +11:00
Matt-Yorkley
6b7cdf3a37 Product Import Refactoring 2017-03-17 16:11:52 +11:00
Matt-Yorkley
052d6c6b02 Product Import basic specs 2017-03-17 16:11:52 +11:00
Matt-Yorkley
6e5c878491 Product Import cancan permissions and ui tab 2017-03-17 16:11:52 +11:00
Matt-Yorkley
c0c6cd1a60 Product Import feature 2017-03-17 16:11:52 +11:00
Matt-Yorkley
2ad433590d Add roo gem 2017-03-17 16:11:52 +11:00
Rohan Mitchell
2cb3da56ab Fix regression: Transaction fee double-charged 2017-03-17 12:02:13 +11:00
Rohan Mitchell
8582e6d6b4 Add robustness check against intermittent spec failure 2017-03-17 12:02:13 +11:00
Rohan Mitchell
170101cbfe Avoid reloading order during checkout request, which clears credit card number 2017-03-17 12:02:13 +11:00
Maikel Linke
8107f49373 Merge remote-tracking branch 'origin/master' into transifex
Conflicts:
	config/locales/fr.yml
2017-03-16 09:30:22 +11:00
Transifex-Openfoodnetwork
f235099859 Updating translations for config/locales/fr.yml [skip ci] 2017-03-11 16:50:17 +11:00
81 changed files with 3914 additions and 562 deletions

View File

@@ -63,6 +63,7 @@ gem 'wkhtmltopdf-binary'
gem 'foreigner'
gem 'immigrant'
gem 'roo', '~> 2.7.0'
gem 'whenever', require: false

View File

@@ -558,6 +558,9 @@ GEM
roadie-rails (1.0.3)
rails (>= 3.0, < 4.2)
roadie (~> 3.0)
roo (2.7.1)
nokogiri (~> 1)
rubyzip (~> 1.1, < 2.0.0)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
@@ -576,6 +579,7 @@ GEM
rspec-retry (0.4.2)
rspec-core
ruby-progressbar (1.7.1)
rubyzip (1.2.0)
safe_yaml (0.9.5)
sass (3.3.14)
sass-rails (3.2.6)
@@ -639,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)
@@ -717,6 +720,7 @@ DEPENDENCIES
redcarpet
representative_view
roadie-rails (~> 1.0.3)
roo (~> 2.7.0)
rspec-rails
rspec-retry
sass (~> 3.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

@@ -0,0 +1,5 @@
angular.module("ofn.admin").controller "DropdownPanelsCtrl", ($scope) ->
$scope.active = false
$scope.togglePanel = ->
$scope.active = !$scope.active

View File

@@ -0,0 +1,15 @@
angular.module("ofn.admin").controller "ImportOptionsFormCtrl", ($scope, $rootScope, ProductImportService) ->
$scope.toggleResetAbsent = () ->
confirmed = confirm 'This will set stock level to zero on all products for this \n' +
'enterprise that are not present in the uploaded file.' if $scope.resetAbsent
if confirmed or !$scope.resetAbsent
ProductImportService.updateResetAbsent($scope.supplierId, $scope.resetCount, $scope.resetAbsent)
else
$scope.resetAbsent = false
$scope.resetTotal = ProductImportService.resetTotal
$rootScope.$watch 'resetTotal', (newValue) ->
$scope.resetTotal = newValue if newValue || newValue == 0

View File

@@ -0,0 +1,15 @@
angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
new class ProductImportService
suppliers: {}
resetTotal: 0
updateResetAbsent: (supplierId, resetCount, resetAbsent) ->
if resetAbsent
@suppliers[supplierId] = resetCount
@resetTotal += resetCount
else
@suppliers[supplierId] = null
@resetTotal -= resetCount
$rootScope.resetTotal = @resetTotal

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

@@ -0,0 +1,225 @@
div.panel-section {
.neutral {
color: #bfbfbf;
}
.warning {
color: #da5354;
}
.success {
color: #86d83a;
}
.info {
color: #68b7c0;
}
div.panel-header {
width: 100%;
//font-size: 1.5em;
clear: both;
//border: 1px solid #ccc;
float: left;
padding: 0.5em;
div {
font-size: 1.25em;
float: left;
}
div.header-caret {
width: 2em;
text-align: center;
min-height: 0.1em; //Empty div fix
}
div.header-icon {
width: 2.5em;
text-align: center;
padding-top: 0.18em;
i {
font-size: 1.5em;
line-height: 0.9em;
}
}
div.header-count {
min-width: 2em;
text-align: right;
padding-right: 0.5em;
}
div.header-description {
width: auto;
}
}
div.panel-header:hover {
cursor: pointer;
background-color: #f7f7f7;
}
div.panel-header.active {
background-color: #efefef;
text-shadow: 1px 1px 0px rgba(255,255,255,0.75);
}
div.panel-content {
width: 100%;
clear: both;
//border: 1px solid #ccc;
margin-bottom: 0.5em;
background-color: #f9f9f9;
padding: 1.5em;
div.table-wrap {
width: 100%;
overflow: auto;
border-right: 1px solid #ceede3;
max-height: 23em;
}
table {
background-color: white;
margin-bottom: 0;
td, th {
white-space: nowrap;
}
tr.error {
//background-color: #ffe6e4;
//color: #ee4728;
color: #c84C4c;
}
tr:hover td.invalid {
background-color: #ed5135;
}
tr i {
display: block;
margin-bottom: -0.2em;
font-size: 1.4em !important;
}
td.invalid {
background-color: #f05c51;
box-shadow: inset 0px 0px 1px red;
color: white;
}
}
div.import-errors {
margin-bottom: 0.85em;
p.line {
font-size: 1.15em;
margin-bottom: 0.2em;
color: #577084;
}
p.error {
color: #cb1b1b;
margin-bottom: 0.2em;
}
}
}
}
br.panels.clearfix {
clear: both;
}
table.import-settings {
background-color: transparent !important;
width: auto;
//select {
// width: 100%;
//}
tr {
}
tbody tr:hover td {
background-color: #f3f3f3;
}
td {
border: 0;
border-bottom: 1px solid #eee;
text-align: left;
input {
width: 15em;
}
input[type="checkbox"] {
width: auto;
}
}
td.description {
font-weight: bold;
padding-right: 2.5em;
}
tr:first-child td {
//border-top: 1px solid #eee;
border-top: 0;
}
tr:last-child td {
//border-top: 1px solid #eee;
border-bottom: 0;
}
}
.panel-section.import-settings {
.header-description {
padding-left: 1em;
}
span.header-error {
font-size: 0.85em;
color: #da5354;
}
.select2-search {
display: none;
}
.select2-results {
margin: 0;
}
}
.post-save-results {
p {
font-size: 1.25em;
margin-bottom: 0.5em;
strong {
margin-right: 0.2em;
min-width: 1.8em;
display: inline-block;
text-align: right;
}
i {
font-size: 1.4em;
vertical-align: middle;
position: relative;
}
i.fa-check-circle {
color: #86d83a;
}
i.fa-info-circle {
color: #68b7c0;
}
}
p.save-error {
color: #ee4728;
font-size: 1.05em;
margin-top: 0.4em;
}
}

View File

@@ -0,0 +1,62 @@
require 'roo'
class Admin::ProductImportController < Spree::Admin::BaseController
before_filter :validate_upload_presence, except: :index
def import
# Save uploaded file to tmp directory
@filepath = save_uploaded_file(params[:file])
@importer = ProductImporter.new(File.new(@filepath), editable_enterprises)
check_file_errors @importer
check_spreadsheet_has_data @importer
@tax_categories = Spree::TaxCategory.order('is_default DESC, name ASC')
@shipping_categories = Spree::ShippingCategory.order('name ASC')
end
def save
@importer = ProductImporter.new(File.new(params[:filepath]), editable_enterprises, params[:settings])
@importer.save_all if @importer.has_valid_entries?
end
private
def validate_upload_presence
unless params[:file] || (params[:filepath] && File.exist?(params[:filepath]))
redirect_to '/admin/product_import', notice: 'File not found or could not be opened'
return
end
end
def check_file_errors(importer)
if importer.errors.present?
redirect_to '/admin/product_import', notice: @importer.errors.full_messages.to_sentence
return
end
end
def check_spreadsheet_has_data(importer)
unless importer.item_count
redirect_to '/admin/product_import', notice: 'No data found in spreadsheet'
return
end
end
def save_uploaded_file(upload)
filename = 'import' + Time.now.strftime('%d-%m-%Y-%H-%M-%S')
extension = '.' + upload.original_filename.split('.').last
directory = 'tmp/product_import'
Dir.mkdir(directory) unless File.exists?(directory)
File.open(Rails.root.join(directory, filename+extension), 'wb') do |f|
f.write(upload.read)
f.path
end
end
# Define custom model class for Cancan permissions
def model_class
ProductImporter
end
end

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

@@ -0,0 +1,464 @@
require 'roo'
class ProductImporter
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :total_supplier_products
def initialize(file, editable_enterprises, import_settings={})
if file.is_a?(File)
@file = file
@sheet = open_spreadsheet
@entries = []
@valid_entries = {}
@invalid_entries = {}
@products_to_create = {}
@variants_to_create = {}
@variants_to_update = {}
@products_created = 0
@variants_created = 0
@variants_updated = 0
@import_settings = import_settings
@editable_enterprises = {}
editable_enterprises.map { |e| @editable_enterprises[e.name] = e.id }
@total_supplier_products = 0
@products_to_reset = {}
@updated_ids = []
init_product_importer if @sheet
else
self.errors.add(:importer, 'error: no file uploaded')
end
end
def persisted?
false #ActiveModel, not ActiveRecord
end
def has_valid_entries?
valid_count and valid_count > 0
end
def item_count
@sheet ? @sheet.last_row - 1 : 0
end
def products_to_reset
# Return indexed data about existing product count, reset count, and updates count per supplier
@products_to_reset.each do |supplier_id, values|
values[:updates_count] = 0 if values[:updates_count].blank?
if values[:updates_count] and values[:existing_products]
@products_to_reset[supplier_id][:reset_count] = values[:existing_products] - values[:updates_count]
end
end
@products_to_reset
end
def valid_count
@valid_entries.count
end
def invalid_count
@invalid_entries.count
end
def products_create_count
@products_to_create.count + @variants_to_create.count
end
def products_update_count
@variants_to_update.count
end
def suppliers_index
index = @suppliers_index || build_suppliers_index
index.sort_by{ |k,v| v.to_i }.reverse.to_h
end
def all_entries
invalid_entries.merge(products_to_create).merge(products_to_update).sort.to_h
end
def invalid_entries
@invalid_entries
end
def products_to_create
@products_to_create.merge(@variants_to_create)
end
def products_to_update
@variants_to_update
end
def products_created_count
@products_created + @variants_created
end
def products_updated_count
@variants_updated
end
def products_reset_count
@products_reset_count || 0
end
def total_saved_count
@products_created + @variants_created + @variants_updated
end
def save_all
save_all_valid
delete_uploaded_file
end
def permission_by_name?(supplier_name)
@editable_enterprises.has_key?(supplier_name)
end
def permission_by_id?(supplier_id)
@editable_enterprises.has_value?(Integer(supplier_id))
end
private
def init_product_importer
build_entries
build_categories_index
build_suppliers_index
validate_all
end
def open_spreadsheet
if accepted_mimetype
Roo::Spreadsheet.open(@file, extension: accepted_mimetype)
else
self.errors.add(:importer, 'could not process file: invalid filetype')
delete_uploaded_file
nil
end
end
def accepted_mimetype
File.extname(@file.path).in?('.csv', '.xls', '.xlsx', '.ods') ? @file.path.split('.').last.to_sym : false
end
def headers
@sheet.row(1)
end
def rows
return [] unless @sheet and @sheet.last_row
(2..@sheet.last_row).map do |i|
@sheet.row(i)
end
end
def build_entries
rows.each_with_index do |row, i|
row_data = Hash[[headers, row].transpose]
entry = SpreadsheetEntry.new(row_data)
entry.line_number = i+2
@entries.push entry
end
@entries
end
def validate_all
@entries.each do |entry|
supplier_validation(entry)
category_validation(entry)
set_update_status(entry)
mark_as_valid(entry) unless entry_invalid?(entry.line_number)
end
count_existing_products
delete_uploaded_file if item_count.zero? or valid_count.zero?
end
def count_existing_products
@suppliers_index.each do |supplier_name, supplier_id|
if supplier_id and permission_by_id?(supplier_id)
products_count = Spree::Variant.joins(:product).
where('spree_products.supplier_id IN (?)
AND spree_variants.is_master = false
AND spree_variants.deleted_at IS NULL', supplier_id).
count
if @products_to_reset[supplier_id]
@products_to_reset[supplier_id][:existing_products] = products_count
else
@products_to_reset[supplier_id] = {existing_products: products_count}
end
@total_supplier_products += products_count
end
end
end
def entry_invalid?(line_number)
!!@invalid_entries[line_number]
end
def supplier_validation(entry)
supplier_name = entry.supplier
if supplier_name.blank?
mark_as_invalid(entry, attribute: "supplier", error: "can't be blank")
return
end
unless supplier_exists?(supplier_name)
mark_as_invalid(entry, attribute: "supplier", error: "\"#{supplier_name}\" not found in database")
return
end
unless permission_by_name?(supplier_name)
mark_as_invalid(entry, attribute: "supplier", error: "\"#{supplier_name}\": you do not have permission to manage products for this enterprise")
return
end
entry.supplier_id = @suppliers_index[supplier_name]
end
def supplier_exists?(supplier_name)
@suppliers_index[supplier_name]
end
def category_validation(entry)
category_name = entry.category
if category_name.blank?
mark_as_invalid(entry, attribute: "category", error: "can't be blank")
return
end
if category_exists?(category_name)
entry.primary_taxon_id = @categories_index[category_name]
else
mark_as_invalid(entry, attribute: "category", error: "\"#{category_name}\" not found in database")
end
end
def category_exists?(category_name)
@categories_index[category_name]
end
def mark_as_valid(entry)
@valid_entries[entry.line_number] = entry
end
def mark_as_invalid(entry, options={})
entry.errors.add(options[:attribute], options[:error]) if options[:attribute] and options[:error]
entry.product_validations = options[:product_validations] if options[:product_validations]
@invalid_entries[entry.line_number] = entry
end
# Minimise db queries by getting a list of suppliers to look
# up, instead of doing a query for each entry in the spreadsheet
def build_suppliers_index
@suppliers_index = {}
@entries.each do |entry|
supplier_name = entry.supplier
supplier_id = @suppliers_index[supplier_name] ||
Enterprise.find_by_name(supplier_name, :select => 'id, name').try(:id)
@suppliers_index[supplier_name] = supplier_id
end
@suppliers_index
end
def build_categories_index
@categories_index = {}
@entries.each do |entry|
category_name = entry.category
category_id = @categories_index[category_name] ||
Spree::Taxon.find_by_name(category_name, :select => 'id, name').try(:id)
@categories_index[category_name] = category_id
end
@categories_index
end
def save_all_valid
already_created = {}
@products_to_create.each do |line_number, entry|
# If we've already added a new product with these attributes
# from this spreadsheet, mark this entry as a new variant with
# the new product id, as this is a now variant of that product...
if already_created[entry.supplier_id] and already_created[entry.supplier_id][entry.name]
product_id = already_created[entry.supplier_id][entry.name]
mark_as_new_variant(entry, product_id)
next
end
product = Spree::Product.new()
product.assign_attributes(entry.attributes.except('id'))
assign_defaults(product, entry.attributes)
if product.save
ensure_variant_updated(product, entry)
@products_created += 1
@updated_ids.push product.variants.first.id
else
self.errors.add("Line #{line_number}:", product.errors.full_messages) #TODO: change
end
already_created[entry.supplier_id] = {entry.name => product.id}
end
@variants_to_update.each do |line_number, entry|
variant = entry.product_object
assign_defaults(variant, entry.attributes)
if variant.valid? and variant.save
@variants_updated += 1
@updated_ids.push variant.id
else
self.errors.add("Line #{line_number}:", variant.errors.full_messages) #TODO: change
end
end
@variants_to_create.each do |line_number, entry|
new_variant = entry.product_object
assign_defaults(new_variant, entry.attributes)
if new_variant.valid? and new_variant.save
@variants_created += 1
@updated_ids.push new_variant.id
else
self.errors.add("Line #{line_number}:", new_variant.errors.full_messages)
end
end
self.errors.add(:importer, "did not save any products successfully") if total_saved_count.zero?
reset_absent_products
total_saved_count
end
def reset_absent_products
return if total_saved_count.zero?
enterprises_to_reset = []
@import_settings.each do |enterprise_id, settings|
enterprises_to_reset.push enterprise_id if settings['reset_all_absent'] and permission_by_id?(enterprise_id)
end
unless enterprises_to_reset.empty? or @updated_ids.empty?
# For selected enterprises; set stock to zero for all products
# that were not present in the uploaded spreadsheet
@products_reset_count = Spree::Variant.joins(:product).
where('spree_products.supplier_id IN (?)
AND spree_variants.id NOT IN (?)
AND spree_variants.is_master = false
AND spree_variants.deleted_at IS NULL', enterprises_to_reset, @updated_ids).
update_all(count_on_hand: 0)
end
end
def assign_defaults(object, entry)
@import_settings[entry['supplier_id'].to_s]['defaults'].each do |attribute, setting|
case setting['mode']
when 'overwrite_all'
object.assign_attributes(attribute => setting['value'])
when 'overwrite_empty'
if object.send(attribute).blank? or (attribute == 'on_hand' and entry['on_hand_nil'])
object.assign_attributes(attribute => setting['value'])
end
end
end
end
def ensure_variant_updated(product, entry)
# Ensure display_name and on_demand are copied to new product's variant
if entry.display_name || entry.on_demand
variant = product.variants.first
variant.display_name = entry.display_name if entry.display_name
variant.on_demand = entry.on_demand if entry.on_demand
variant.save
end
end
def set_update_status(entry)
# Find product with matching supplier and name
match = Spree::Product.where(supplier_id: entry.supplier_id, name: entry.name, deleted_at: nil).first
# If no matching product was found, create a new product
if match.nil?
mark_as_new_product(entry)
return
end
# Otherwise, if a variant exists with matching display_name and unit_value, update it
match.variants.each do |existing_variant|
if existing_variant.display_name == entry.display_name && existing_variant.unit_value == Float(entry.unit_value)
mark_as_existing_variant(entry, existing_variant)
return
end
end
# Otherwise, a variant with sufficiently matching attributes doesn't exist; create a new one
mark_as_new_variant(entry, match.id)
end
def mark_as_new_product(entry)
new_product = Spree::Product.new()
new_product.assign_attributes(entry.attributes.except('id'))
if new_product.valid?
@products_to_create[entry.line_number] = entry unless entry_invalid?(entry.line_number)
else
mark_as_invalid(entry, product_validations: new_product.errors)
end
end
def mark_as_existing_variant(entry, existing_variant)
existing_variant.assign_attributes(entry.attributes.except('id', 'product_id'))
check_on_hand_nil(entry, existing_variant)
if existing_variant.valid?
entry.product_object = existing_variant
@variants_to_update[entry.line_number] = entry unless entry_invalid?(entry.line_number)
updates_count_per_supplier(entry.supplier_id) unless entry_invalid?(entry.line_number)
else
mark_as_invalid(entry, product_validations: existing_variant.errors)
end
end
def mark_as_new_variant(entry, product_id)
new_variant = Spree::Variant.new(entry.attributes.except('id', 'product_id'))
new_variant.product_id = product_id
check_on_hand_nil(entry, new_variant)
if new_variant.valid?
entry.product_object = new_variant
@variants_to_create[entry.line_number] = entry unless entry_invalid?(entry.line_number)
else
mark_as_invalid(entry, product_validations: new_variant.errors)
end
end
def updates_count_per_supplier(supplier_id)
if @products_to_reset[supplier_id] and @products_to_reset[supplier_id][:updates_count]
@products_to_reset[supplier_id][:updates_count] += 1
else
@products_to_reset[supplier_id] = {updates_count: 1}
end
end
def check_on_hand_nil(entry, variant)
if entry.on_hand.blank?
variant.on_hand = 0
entry.on_hand_nil = true
end
end
def delete_uploaded_file
# Only delete if file is in '/tmp/product_import' directory
if @file.path == Rails.root.join('tmp', 'product_import').to_s
File.delete(@file)
end
end
end

View File

@@ -0,0 +1,68 @@
# Class for defining spreadsheet entry objects for use in ProductImporter
class SpreadsheetEntry
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :line_number, :valid, :product_object, :product_validations, :save_type, :on_hand_nil
attr_accessor :id, :product_id, :supplier, :supplier_id, :name, :display_name, :sku,
:unit_value, :unit_description, :variant_unit, :variant_unit_scale, :variant_unit_name,
:display_as, :category, :primary_taxon_id, :price, :on_hand, :on_demand,
:tax_category_id, :shipping_category_id, :description
def initialize(attrs)
@product_validations = {}
attrs.each do |k, v|
if self.respond_to?("#{k}=")
send("#{k}=", v) unless non_product_attributes.include?(k)
else
# Trying to assign unknown attribute. Record this and give feedback or just ignore silently?
end
end
end
def persisted?
false #ActiveModel
end
def has_errors?
self.errors.count > 0 or @product_validations.count > 0
end
def attributes
attrs = {}
self.instance_variables.each do |var|
attrs[var.to_s.delete("@")] = self.instance_variable_get(var)
end
attrs.except(*non_product_attributes)
end
def displayable_attributes
# Modified attributes list for displaying in user feedback
attrs = {}
self.instance_variables.each do |var|
attrs[var.to_s.delete("@")] = self.instance_variable_get(var)
end
attrs.except(*non_product_attributes, *non_display_attributes)
end
def invalid_attributes
invalid_attrs = {}
@product_validations.messages.merge(self.errors.messages).each do |attr, message|
invalid_attrs[attr.to_s] = "#{attr.to_s.capitalize} #{message.first}"
end
invalid_attrs.except(*non_product_attributes, *non_display_attributes)
end
private
def non_display_attributes
['id', 'product_id', 'variant_id', 'supplier_id', 'primary_taxon', 'primary_taxon_id', 'category_id', 'shipping_category_id', 'tax_category_id']
end
def non_product_attributes
['line_number', 'valid', 'errors', 'product_object', 'product_validations', 'save_type', 'on_hand_nil']
end
end

View File

@@ -155,6 +155,8 @@ class AbilityDecorator
can [:admin, :index, :read, :search], Spree::Taxon
can [:admin, :index, :read, :create, :edit], Spree::Classification
can [:admin, :index, :import, :save], ProductImporter
# Reports page
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
end
@@ -173,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

@@ -16,7 +16,7 @@ module Spree
adjustment.save
else
payment_method.create_adjustment(adjustment_label, order, self, true)
reload
association(:adjustment).reload
end
end

View File

@@ -12,7 +12,8 @@ module Spree
def adjust_with_included_tax(order)
adjust_without_included_tax(order)
order.reload
order.adjustments(:reload)
order.line_items(:reload)
(order.adjustments.tax + order.price_adjustments).each do |a|
a.set_absolute_included_tax! a.amount
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

@@ -0,0 +1,4 @@
/ insert_bottom "[data-hook='admin_product_sub_tabs']"
-# Commenting out for now, until product import is finished
-# = tab :product_import, label: "Import", url: main_app.admin_product_import_path, match_path: '/product_import'

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

@@ -0,0 +1,15 @@
- if entries && entries.count > 0
%div.table-wrap
%table
%thead
%th
%th Line
- entries.values.first.displayable_attributes.each do |key, value|
%th= key
- entries.each do |line_number, entry|
%tr{class: ('error' if entry.has_errors?)}
%td
%i{class: (entry.has_errors? ? 'fa fa-warning warning' : 'fa fa-check-circle success')}
%td= line_number
- entry.displayable_attributes.each do |key, value|
%td{class: ('invalid' if entry.has_errors? and entry.invalid_attributes[key])}= value

View File

@@ -0,0 +1,11 @@
- @importer.invalid_entries.each do |line_number, entry|
%div.import-errors
%p.line
%strong
Item line #{line_number}:
%span= entry.name
- if entry.display_name
( #{entry.display_name} )
- entry.invalid_attributes.each do |attr, error|
%p.error
&nbsp;-&nbsp; #{error}

View File

@@ -0,0 +1,49 @@
%h5 Import options and defaults
%br
- @importer.suppliers_index.each do |name, supplier_id|
- if name and supplier_id and @importer.permission_by_id?(supplier_id)
%div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}}
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}"}}
%div.header-icon.neutral
%i.fa.fa-edit
-#%div.header-count
-# %strong.invalid-count= @importer.invalid_count
%div.header-description
= name
%div.panel-content{ng: {hide: '!active'}}
= render 'options_form', supplier_id: supplier_id, name: name
- elsif name and supplier_id
%div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}}
%div.panel-header
%div.header-caret
-#%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}"}}
%div.header-icon.error
%i.fa.fa-warning
-#%div.header-count
-# %strong.invalid-count= @importer.invalid_count
%div.header-description
= name
%span.header-error= " - you do not have permission to manage this enterprise"
-#%div.panel-content{ng: {hide: '!active'}}
-# = render 'options_form', supplier_id: supplier_id, name: name
- elsif name
%div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}}
%div.panel-header
%div.header-caret
-#%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}"}}
%div.header-icon.error
%i.fa.fa-warning
-#%div.header-count
-# %strong.invalid-count= @importer.invalid_count
%div.header-description
= name
%span.header-error= " - enterprise could not be found in database"
-#%div.panel-content{ng: {hide: '!active'}}
-# = render 'options_form', supplier_id: supplier_id, name: name
%br.panels.clearfix
%br

View File

@@ -0,0 +1,83 @@
%h5 Import validation overview
%br
-#%div.panel-section
-# %div.panel-header
-# %div.header-caret
-# %div.header-icon.info
-# %i.fa.fa-info-circle
-# %div.header-count
-# %strong.existing-count= @importer.total_supplier_products
-# %div.header-description
-# Existing products in referenced enterprise(s)
-# -#%div.panel-content{ng: {hide: '!active'}}
-# -# Content goes here
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "count = #{@importer.item_count}"}}
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon.success
%i.fa.fa-info-circle.info
%div.header-count
%strong.item-count= @importer.item_count
%div.header-description
Entries found in imported file
%div.panel-content{ng: {hide: '!active || count == 0'}}
= render 'entries_table', entries: @importer.all_entries
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "count = #{@importer.invalid_count}"}}
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon.warning
%i.fa.fa-warning
%div.header-count
%strong.invalid-count= @importer.invalid_count
%div.header-description
Items contain errors and will not be imported
%div.panel-content{ng: {hide: '!active || count == 0'}}
= render 'errors_list'
%br
= render 'entries_table', entries: @importer.invalid_entries
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "count = #{@importer.products_create_count}"}}
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon.success
%i.fa.fa-check-circle
%div.header-count
%strong.create-count= @importer.products_create_count
%div.header-description
Products will be created
%div.panel-content{ng: {hide: '!active || count == 0'}}
= render 'entries_table', entries: @importer.products_to_create
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "count = #{@importer.products_update_count}"}}
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon.success
%i.fa.fa-check-circle
%div.header-count
%strong.update-count= @importer.products_update_count
%div.header-description
Products will be updated
%div.panel-content{ng: {hide: '!active || count == 0'}}
= render 'entries_table', entries: @importer.products_to_update
%div.panel-section{ng: {controller: 'ImportOptionsFormCtrl', hide: 'resetTotal == 0'}}
%div.panel-header
%div.header-caret
%div.header-icon.info
%i.fa.fa-info-circle
%div.header-count
%strong.reset-count
{{resetTotal}}
%div.header-description
Existing products will have their stock reset to zero
-#%div.panel-content{ng: {hide: '!active'}}
-# Content goes here
%br.panels.clearfix

View File

@@ -0,0 +1,35 @@
%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{supplier_id}; resetCount = #{@importer.products_to_reset[supplier_id][:reset_count]}"}}
%tr
%td.description
Remove absent products?
%td
= check_box_tag "settings[#{supplier_id}][reset_all_absent]", 1, false, :'ng-model' => 'resetAbsent', :'ng-change' => 'toggleResetAbsent()'
%td
%tr
%td.description
Set default stock level
%td
= select_tag "settings[#{supplier_id}][defaults][on_hand][mode]", options_for_select({"Don't overwrite" => :overwrite_none, "Overwrite all" => :overwrite_all, "Overwrite if empty" => :overwrite_empty}), {class: 'select2 fullwidth'}
%td
= number_field_tag "settings[#{supplier_id}][defaults][on_hand][value]", 0
%tr
%td.description
Set default tax category
%td
= select_tag "settings[#{supplier_id}][defaults][tax_category_id][mode]", options_for_select({"Don't overwrite" => :overwrite_none, "Overwrite all" => :overwrite_all, "Overwrite if empty" => :overwrite_empty}), {class: 'select2 fullwidth'}
%td
= select_tag "settings[#{supplier_id}][defaults][tax_category_id][value]", options_for_select(@tax_categories.map {|tc| [tc.name, tc.id]}), {prompt: 'None', class: 'select2 fullwidth'}
%tr
%td.description
Set default shipping category
%td
= select_tag "settings[#{supplier_id}][defaults][shipping_category_id][mode]", options_for_select({"Don't overwrite" => :overwrite_none, "Overwrite all" => :overwrite_all, "Overwrite if empty" => :overwrite_empty}), {class: 'select2 fullwidth'}
%td
= select_tag "settings[#{supplier_id}][defaults][shipping_category_id][value]", options_for_select(@shipping_categories.map {|sc| [sc.name, sc.id]}), {prompt: 'None', class: 'select2 fullwidth'}
%tr
%td.description
Set default available date
%td
= select_tag "settings[#{supplier_id}][defaults][available_on][mode]", options_for_select({"Don't overwrite" => :overwrite_none, "Overwrite all" => :overwrite_all, "Overwrite if empty" => :overwrite_empty}), {class: 'select2 fullwidth'}
%td
= text_field_tag "settings[#{supplier_id}][defaults][available_on][value]", nil, {class: 'datepicker', placeholder: 'Today'}

View File

@@ -0,0 +1,9 @@
%h5 Select a spreadsheet to upload
%br
= form_tag main_app.admin_product_import_path, multipart: true do
= file_field_tag :file
%br
%br
= submit_tag "Import"
%br
%br

View File

@@ -0,0 +1,34 @@
- content_for :page_title do
Product Import
= render partial: 'spree/admin/shared/product_sub_menu'
= form_tag main_app.admin_product_import_save_path, {'ng-app' => 'ofn.admin'} do
- if @importer.invalid_count && !@importer.has_valid_entries?
%h5 No valid entries found
%p There are no entries that can be saved
%br
= render 'import_options' if @importer.has_valid_entries?
= render 'import_review'
- if @importer.has_valid_entries?
- if @importer.invalid_count > 0
%br
%h5 Imported file contains some invalid entries
%p Save valid entries for now and discard the others?
- else
%h5 No errors detected!
%p Save all imported products?
%br
= hidden_field_tag :filepath, @filepath
= submit_tag "Save"
%a.button{href: main_app.admin_product_import_path} Cancel
- else
%br
%a.button{href: main_app.admin_product_import_path} Cancel

View File

@@ -0,0 +1,6 @@
- content_for :page_title do
Product Import
= render :partial => 'spree/admin/shared/product_sub_menu'
= render 'upload_form'

View File

@@ -0,0 +1,38 @@
- content_for :page_title do
Product Import
= render :partial => 'spree/admin/shared/product_sub_menu'
%h5 Import final results
%br
%div.post-save-results{ng: {app: 'ofn.admin'}}
%p
%i.fa{ng: {class: "{'fa-info-circle': #{@importer.products_created_count} == 0, 'fa-check-circle': #{@importer.products_created_count} != 0}"}}
%strong.created-count= @importer.products_created_count
Products created
%p
%i.fa{ng: {class: "{'fa-info-circle': #{@importer.products_updated_count} == 0, 'fa-check-circle': #{@importer.products_updated_count} != 0}"}}
%strong.updated-count= @importer.products_updated_count
Products updated
- if @importer.products_reset_count > 0
%p
%i.fa.fa-check-circle
%strong.reset-count= @importer.products_reset_count
Products had stock level reset to zero
%br
- if @importer.errors.count == 0
%p All #{@importer.total_saved_count} items saved successfully
- else
%h5 Save errors
- @importer.errors.full_messages.each do |error|
%p.save-error
&nbsp;-&nbsp; #{error}
%br
%a.button{href: main_app.admin_product_import_path} Back

View File

@@ -4,30 +4,30 @@
%legend
= t :checkout_your_order
%table
%tr
%tr.subtotal
%th
= t :checkout_cart_total
%td.cart-total.text-right= display_checkout_subtotal(@order)
- checkout_adjustments_for(current_order, exclude: [:shipping, :payment, :line_item]).reject{ |a| a.amount == 0 }.each do |adjustment|
%tr
%tr.adjustment
%th= adjustment.label
%td.text-right= adjustment.display_amount.to_html
%tr
%tr.shipping
%th
= t :checkout_shipping_price
%td.shipping.text-right {{ Checkout.shippingPrice() | localizeCurrency }}
%td.text-right {{ Checkout.shippingPrice() | localizeCurrency }}
%tr
%tr.transaction-fee
%th
= t :payment_method_fee
%td.text-right {{ Checkout.paymentPrice() | localizeCurrency }}
%tr
%tr.total
%th
= t :checkout_total_price
%td.total.text-right {{ Checkout.cartTotal() | localizeCurrency }}
%td.text-right {{ Checkout.cartTotal() | localizeCurrency }}
//= f.submit "Purchase", class: "button", "ofn-focus" => "accordion['payment']"
%a.button.secondary{href: cart_url}

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:

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ fr:
Créez votre compte ou réinitialisez votre mot de passe.
enterprise_confirmations:
enterprise:
confirmed: Merci, votre adresse email est confirmée.
confirmed: Merci, votre adresse email a bien été confirmée!
not_confirmed: Votre adresse email n'a pas pu être confirmée. Peut-être l'aviez-vous déjà confirmée?
confirmation_sent: "Un mail de confirmation a été envoyé!"
confirmation_not_sent: "Impossible d'envoyer le mail de confirmation."
@@ -28,8 +28,8 @@ fr:
producers_join: Les producteurs et autres hubs basés en France sont invités à rejoindre Open Food France.
charges_sales_tax: Soumis à la TVA?
print_invoice: "Imprimer la facture"
print_ticket: "Imprimer le ticket"
select_ticket_printer: "Choisir l'imprimante à tickets"
print_ticket: "Imprimer ticket de caisse"
select_ticket_printer: "Choisir l'imprimante tickets"
send_invoice: "Envoyer la facture"
resend_confirmation: "Renvoyer la confirmation"
view_order: "Voir la commande"
@@ -51,17 +51,17 @@ fr:
say_yes: "Oui"
then: puis
sort_order_cycles_on_shopfront_by: "Trier les cycles de vente par"
required_fields: "Les champs obligatoires sont annotés d'une astérisque"
select_continue: Sélectionner et Continuer
required_fields: Les champs obligatoires sont mentionnés par un asterisk
select_continue: Choisir et continuer
remove: Supprimer
or: ou
collapse_all: Réduire
expand_all: Etendre
loading: Chargement...
collapse_all: Tout masquer
expand_all: Tout afficher
loading: Chargement en cours...
show_more: Voir plus
show_all: Voir tout
show_all: Tout voir
show_all_with_more: "Voir tous (%{num} en plus)"
cancel: Annuler
admin:
date: Date
email: Email
@@ -90,51 +90,53 @@ fr:
tag_has_rules: "Règles existantes pour ce tag: %{num}"
has_one_rule: "a une règle"
has_n_rules: "a %{num} règles"
unsaved_confirm_leave: "Des modifications n'ont pas été sauvegardées et seront perdues si vous quittez la page. Souhaitez-vous quitter la page?"
unsaved_changes: "Des modifications n'ont pas été sauvegardées."
accounts_and_billing_settings:
method_settings:
default_accounts_payment_method: "Default Accounts Payment Method"
default_accounts_shipping_method: "Default Accounts Shipping Method"
default_accounts_payment_method: "Méthode de paiement par défault"
default_accounts_shipping_method: "Méthode d'envoi par défault"
edit:
accounts_and_billing: "Compte & Facturation"
accounts_administration_distributor: "entreprise d'administration des comptes (facturation des hubs)"
accounts_and_billing: "Comptes & Factures"
accounts_administration_distributor: "Entreprise d'administration des comptes (facturation des hubs)"
admin_settings: "Paramètres"
update_invoice: "Mettre à jour les factures"
auto_update_invoices: "Mettre à jour automatiquement les factures chaque nuit à 01:00"
finalise_invoice: "Finaliser les factures"
auto_finalise_invoices: "Finaliser automatiquement les factures le 2 de chaque mois à 01:30"
manually_run_task: "Tâche exécutée manuellement"
update_user_invoice_explained: "Cliquez ici pour mettre à jour immédiatement les factures pour le mois en cours pour toutes les entreprises utilisant le système. Cette tache peut être définie pour s'effectuer automatiquement chaque nuit."
finalise_user_invoices: "Finaliser les factures utilisateurs"
finalise_user_invoice_explained: "Cliquez ici pour finaliser toutes les factures pour le mois calendaire précédent. Cette tâche peut-être définie pour être opérée automatiquement une fois par mois."
manually_run_task: "Tache manuelle"
update_user_invoices: "Mettre à jour les factures utilisateurs"
update_user_invoice_explained: "Cliquez ici pour mettre à jour immédiatement les factures pour le mois en cours pour toutes les entreprises utilisant le système. Cette tache peut être définie pour s'effectuer automatiquement chaque nuit."
auto_finalise_invoices: "Finaliser automatiquement les factures le 2 de chaque mois à 01:30"
auto_update_invoices: "Mettre à jour automatiquement les factures chaque nuit à 01:00"
business_model_configuration:
edit:
business_model_configuration: "Configuration du modèle économique"
business_model_configuration_tip: "Configure the rate at which shops will be charged each month for use of the Open Food Network."
bill_calculation_settings: "Bill Calculation Settings"
bill_calculation_settings_tip: "Adjust the amount that enterprises will be billed each month for use of the OFN."
business_model_configuration: "Modèle économique"
business_model_configuration_tip: "Configurer la fréquence à laquelle les boutiques seront facturées chaque mois pour l'utilisation d'Open Food Network"
bill_calculation_settings: "Paramètres du calcul des frais"
bill_calculation_settings_tip: "Définir le montant qui sera facturé aux hubs tous les mois pour leur utilisation d'Open Food France."
shop_trial_length: "Durée de la période de test (jours)"
shop_trial_length_tip: "The length of time (in days) that enterprises who are set up as shops can run as a trial period."
fixed_monthly_charge: "Fixed Monthly Charge"
fixed_monthly_charge_tip: "A fixed monthly charge for all enterprises who are set up as a shop and have exceeded the minimum billable turnover (if set)."
shop_trial_length_tip: "La durée (en jours) de la période d'essai."
fixed_monthly_charge: "Charge mensuelle fixe"
fixed_monthly_charge_tip: "Le montant fixe mensuel facturé pour tous les hubs qui dépassent le seuil de chiffre d'affaire facturable (si défini)."
percentage_of_turnover: "Pourcentage du chiffre d'affaire"
percentage_of_turnover_tip: "When greater than zero, this rate (0.0 - 1.0) will be applied to the total turnover of each shop and added to any fixed charges (to the left) to calculate the monthly bill."
percentage_of_turnover_tip: "Quand supérieur à zéro, ce taux (0.0 - 1.0) sera appliqué au chiffre d'affaire du hub pour déterminer la commission à facturer, qui sera ajoutée aux autres charges (à gauche) pour calculer le montant à facturer pour le mois."
monthly_cap_excl_tax: "plafond mensuel (sans TVA)"
monthly_cap_excl_tax_tip: "When greater than zero, this value will be used as a cap on the amount that shops will be charged each month."
tax_rate: "Tax Rate"
tax_rate_tip: "Tax rate that applies to the the monthly bill that enterprises are charged for using the system."
minimum_monthly_billable_turnover: "Minimum Monthly Billable Turnover"
minimum_monthly_billable_turnover_tip: "Minimum monthly turnover before a shopfront will be charged for using OFN. Enterprises turning over less than this amount in a month will not be charged, either as a percentage or fixed rate."
example_bill_calculator: "Example Bill Calculator"
example_bill_calculator_legend: "Alter the example turnover to visualise the effect of the settings to the left."
example_monthly_turnover: "Example Monthly Turnover"
example_monthly_turnover_tip: "An example monthly turnover for an enterprise which will be used to generate calculate an example monthly bill below."
cap_reached?: "Cap Reached ?"
cap_reached?_tip: "Whether the cap (specified to the left) has been reached, given the settings and the turnover provided."
included_tax: "Included tax"
included_tax_tip: "The total tax included in the example monthly bill, given the settings and the turnover provided."
monthly_cap_excl_tax_tip: "Quand supérieure à zéro, cette valeur sert de limite supérieure facturable pour un mois."
tax_rate: "TVA applicable"
tax_rate_tip: "TVA applicable sur le service facturé par Open Food France."
minimum_monthly_billable_turnover: "Chiffre d'affaire minimum facturable (mensuel)"
minimum_monthly_billable_turnover_tip: "Chiffre d'affaire mensuel au delà duquel le hub devra payer le service Open Food France. Les hubs n'atteignant pas ce chiffre d'affaire mensuel ne seront pas facturés, ni sur le montant fixe ni sur la commission variable."
example_bill_calculator: "Exemple de calcul de facture"
example_bill_calculator_legend: "Changer le chiffre d'affaire pour voir l'impact des paramètres définis à gauche."
example_monthly_turnover: "Exemple de CA mensuel"
example_monthly_turnover_tip: "Exemple de chiffre d'affaire mensuel qui sert de base de calcul pour voir quel est le montant qui sera facturé au hub concerné."
cap_reached?: "Seuil atteint?"
cap_reached?_tip: "On voit ici si le seuil (défini à gauche) a été atteint, en fonction du chiffre d'affaire et du paramétrage du seuil."
included_tax: "Inclut TVA"
included_tax_tip: "TVA inclue dans l'exemple en cours, dépend du chiffre d'affaire et des paramétrages à gauche."
total_monthly_bill_incl_tax: "Facture mensuelle totale (taxes incluses)"
total_monthly_bill_incl_tax_tip: "The example total monthly bill with tax included, given the settings and the turnover provided."
total_monthly_bill_incl_tax_tip: "Exemple du total TTC facturé pour le mois, selon paramétrages et chiffre d'affaire du mois."
customers:
index:
add_customer: "Ajouter un acheteur"
@@ -158,7 +160,8 @@ fr:
confirm_delete: 'Confirmer suppression?'
cache_settings:
show:
distributor: Distributeur
title: Mise en cache
distributor: Hub (distributeur)
order_cycle: Cycle de vente
status: Statut
diff: Diff
@@ -167,16 +170,16 @@ fr:
title: Contenu
enterprise_fees:
index:
title: Marges de l'entreprise
title: Marges et commissions
enterprise: Entreprise
fee_type: Type de marge
name: Nom
tax_category: TVA appliquée
tax_category: TVA applicable
calculator: Calculateur
calculator_values: Valeurs applicables
calculator_values: Montants pour calculs
enterprise_groups:
index:
new_button: Nouveau groupe d'entreprise
new_button: Nouveau groupe d'entreprises
products:
bulk_edit:
unit: Unité
@@ -186,9 +189,11 @@ fr:
inherits_properties?: Hériter des propriétés producteur?
available_on: Disponible sur
av_on: "Disp. sur"
variants:
to_order_tip: "Les articles fabriqués sur commande n'ont pas un niveau de stock défini, comme des pains faits à la main."
variant_overrides:
loading_flash:
loading_inventory: LOADING INVENTORY
loading_inventory: Catalogue boutique en cours de chargement...
index:
title: Stock
description: Utilisez cette page pour gérer le catalogue de votre entreprise. Les détails produits saisis ici remplaceront ceux de la page "Produit" pour votre entreprise uniquement.
@@ -229,248 +234,247 @@ fr:
max_fulfilled_units: "Nombre max de lots commandés"
order_error: "Des erreurs doivent être résolues avant de pouvoir mettre à jour les commandes.\nLes champs entourés en rouge contiennent des erreurs."
variants_without_unit_value: "ATTENTION: certaines variantes n'ont pas de valeur unitaire"
order_cycles:
edit:
choose_products_from: "Cycle de vente:"
enterprise:
select_outgoing_oc_products_from: Sélectionner les produits sortants pour le cycle de vente parmi
enterprises:
index:
producer?: Producteur?
title: Entreprises
new_enterprise: Nouvelle entreprise
producer?: "Producteur?"
package: Pack
status: Statut
manage: Gérer
form:
about_us:
desc_short: Description courte
desc_short_placeholder: Dites nous à propos de votre entreprise en 2 lignes
desc_long: Description publique
desc_long_placeholder: Décrivez-vous à vos clients. Cette information apparaîtra sur votre profil public.
desc_short: Description (en bref)
desc_short_placeholder: Parlez de votre entreprise en une ou deux phrases
desc_long: A propos
desc_long_placeholder: Parlez de vous à vos acheteurs! Ces informations seront visibles sur votre profil public.
business_details:
abn: ABN
abn_placeholder: "ex: 99 123 456 789"
acn: ACN
acn_placeholder: "ex: 123 456 789"
abn: SIRET
abn_placeholder: 'ex: 404 833 048 00022'
acn: n° TVA intracommunautaire
acn_placeholder: 'ex: 404 833 048'
contact:
name: Prénom/Nom
name_placeholder: "ex: Maurice Batot"
email_address: Email Address
email_address_placeholder: "ex: maurice@tomates.fr"
phone: Téléphone
phone_placeholder: "ex: 98 7654 3210"
website: Site Web
website_placeholder: "ex: www.tomates.fr"
name: Nom
name_placeholder: 'ex: Bernard Michelet'
email_address: Adresse email
email_address_placeholder: 'ex: bernard@maferme.fr'
phone: n° téléphone
phone_placeholder: 'ex: 06 13 24 35 46'
website: Site internet
website_placeholder: 'ex: www.maferme.fr'
enterprise_fees:
name: Nom
fee_type: Type de marge
manage_fees: Gérer vos marges
no_fees_yet: Vous n'avez pas encore de marges définies.
create_button: Créer une nouvelle marge
manage_fees: Gérer les marges et commissions
no_fees_yet: Vous n'avez pas encore définies de commissions
create_button: Créer une commission
images:
logo: Logo
promo_image_placeholder: 'Cette image est affichée dans la partie "A Propos"'
promo_image_note1: 'NOTES :'
promo_image_note2: Toutes les images uploadées ici seront coupées pour 1200x260 pixels.
promo_image_note3: L'image promotionelle est affichée en haut de la page profil de l'entreprise et sur les pop-ups.
promo_image_placeholder: 'Cette image est affichée dans "A propos"'
promo_image_note1: 'ATTENTION:'
promo_image_note2: Votre bannière doit mesurer 1200 x 260, toute image non conforme sera rognée.
promo_image_note3: La bannière est affichée en haut de la page de votre entreprise et dans sa version condensée (pop-up).
inventory_settings:
text1: Vous pouvez gérer votre stock et vos prix via votre
inventory: inventaire
text1: Vous pouvez choisir de gérer vos stocks et prix via votre
inventory: catalogue boutique
text2: >
If you are using the inventory tool, you can select whether new products
added by your suppliers need to be added to your inventory before they can be
stocked. If you are not using your inventory to manage your products you should
select the 'recommended' option below:
preferred_product_selection_from_inventory_only_yes: Les nouveaux produits peuvent être mis dans mon magasin (Recommendé)
preferred_product_selection_from_inventory_only_no: Les nouveaux produits doivent être ajouté dans mon magasin avant de pouvoir être ajouté dans mon magasin
Si vous utilisez l'outil "catalogue boutique", vous pouvez choisir si
les nouveaux produits ajoutés par vos fournisseurs doivent être référencés
dans votre catalogue boutique avant qu'ils puissent mis en vente dans
votre boutique. Si vous n'utilisez pas cet outil, choisissez l'option
indiquant "recommandé" ci-dessous:
preferred_product_selection_from_inventory_only_yes: Les nouveaux produits des producteurs peuvent être ajoutés à ma boutique en ligne (recommandé)
preferred_product_selection_from_inventory_only_no: Les nouveaux produits des producteurs doivent être ajoutés à mon catalogue boutique avant de pouvoir être ajoutés à ma boutique en ligne
payment_methods:
name: Nom
applies: Applies?
manage: Gérer les moyens de paiement
not_method_yet: Vous n'avez encore aucun moyen de paiement.
create_button: Ajouter un nouveau moyen de paiement
create_one_button: Ajouter un nouveau moyen de paiement
applies: Active?
manage: Gérer les options de paiement
not_method_yet: Vous n'avez pas encore d'option de paiement.
create_button: Créer une nouvelle option de paiement
create_one_button: En créer une maintenant
primary_details:
name: Nom
name_placeholder: 'ex: Les Oranges Bleues'
name_placeholder: 'ex: La ferme bio de Bernard'
groups: Groupes
groups_tip: Select any groups or regions that you are a member of. This will help customers find your enterprise.
groups_placeholder: Commencer à taper dans rechercher les groupes disponibles...
primary_producer: Primary Producer
primary_producer_tip: Select 'Producer' if you are a primary producer of food.
groups_tip: Sélectionnez les groupes desquels vous êtes membres. Cela améliorera votre visibilité et permettra aux acheteurs de vous trouver plus facilement.
groups_placeholder: Commencer à taper pour voir les groupes disponibles...
primary_producer: Producteur?
primary_producer_tip: Cochez "producteur" si vous vendez des aliments que vous produisez vous-même (bruts ou transformés)
producer: Producteur
any: Any
none: None
own: Own
sells: Sells
sells_tip: "None - enterprise does not sell to customers directly.<br />Own - Enterprise sells own products to customers.<br />Any - Enterprise can sell own or other enterprises products.<br />"
visible_in_seach: Visible dans la recherche?
visible_in_seach_tip: Determines whether this enterprise will be visible to customers when searching the site.
any: Tous
none: Aucun
own: Les miens
sells: Produits vendus
sells_tip: "Aucun - l'entreprise ne vend pas en direct aux acheteurs.<br />Les miens - l'entreprise vend ses propres produits aux acheteurs.<br />Tous - l'entreprise vend ses propres produits et/ou les produits d'autres entreprises.<br />"
visible_in_search: Apparaît sur la plateforme?
visible_in_search_tip: Indiquez si vous souhaitez ou ne souhaitez pas que votre entreprise apparaisse sur la carte et dans la liste des boutiques.
visible: Visible
not_visible: Pas visible
permalink: Lien (pas d'espace)
permalink_tip: "This permalink is used to create the url to your shop: %{link} your-shop-name/shop"
link_to_front: Link to shop front
link_to_front_tip: A direct link to your shopfront on the Open Food Network.
not_visible: Invisible
permalink: Nom affiché dans l'URL (sans espace)
permalink_tip: "Ce nom permanent est utilisé pour créer l'url de votre boutique: %{link}ma-boutique/shop"
link_to_front: Lien URL de la boutique
link_to_front_tip: C'est le lien qui permet d'accéder en direct à votre boutique sur Open Food France.
shipping_methods:
name: Nom
applies: Applies?
manage: Gérer les moyens de livraison
create_button: Ajouter un nouveau moyen de livraison
create_one_button: Ajouter un nouveau moyen de livraison
no_method_yet: Vous n'avez encore aucun moyen de livraison défini.
applies: Active?
manage: Gérer les méthodes de livraison
create_button: Créer nouvelle méthode de livraison
create_one_button: Créer une commission
no_method_yet: Vous n'avez pas encore paramétré de méthode de livraison.
shop_preferences:
shopfront_requires_login: "Cette boutique exige un login?"
shopfront_requires_login_tip: "Les acheteurs doivent-ils se connecter pour accéder à la boutique?"
shopfront_requires_login_false: "Public"
shopfront_requires_login_true: "Demander aux acheteurs de se connecter"
allow_guest_orders: "Guest orders"
allow_guest_orders_tip: "Allow checkout as guest or require a registered user."
allow_guest_orders_false: "Require login to order"
allow_guest_orders_true: "Allow guest checkout"
shopfront_requires_login: "Boutique visible par tous?"
shopfront_requires_login_tip: "Choisissez si les acheteurs doivent être connectés pour voir la boutique ou si la boutique est visible par tout le monde."
shopfront_requires_login_false: "Visible par tous"
shopfront_requires_login_true: "Visible uniquement pour les acheteurs logués"
allow_guest_orders: "Commandes des invités"
allow_guest_orders_tip: "Autoriser la commande en tant qu'invité ou demander que l'acheteur soit logué."
allow_guest_orders_false: "Demander que l'acheteur se logue pour pouvoir commander"
allow_guest_orders_true: "Autoriser les commandes en mode invité"
shopfront_message_placeholder: >
An optional explanation for customers detailing how your shopfront works,
to be displayed above the product list on your shop page.
Vous pouvez ici expliquer à vos acheteurs comment votre boutique fonctionne.
Ce texte s'affiche dans votre boutique, au-dessus de la liste de produits.
shopfront_closed_message_placeholder: >
A message which provides a more detailed explanation about why your shop is
closed and/or when customers can expect it to open again. This is displayed
on your shop only when you have no active order cycles (ie. shop is closed).
open_date: Open Date
close_date: Close Date
Vous pouvez ici expliquer à vos acheteurs pourquoi votre boutique est
fermée et/ou quand elle ouvrira. Ce texte s'affiche uniquement quand
il n'y a pas de cycle de vente en cours (donc quand votre boutique est
fermée).
open_date: Date d'ouverture
close_date: Date de fermeture
social:
twitter_placeholder: 'ex: @lesorangesbleues'
twitter_placeholder: ex. @OpenFoodNet_fr
tag_rules:
default_rules:
by_default: Par défaut
no_rules_yet: Aucune règle encore ajoutée
add_new_button: + Ajouter une nouvelle règle
no_tags_yet: Aucun tag encore défini pour cette entreprise
no_rules_yet: Aucun régle encore définie pour ce tag
for_customers_tagged: 'For customers tagged:'
add_new_rule: '+ Ajouter une nouvelle régle'
by_default: Règles à appliquer "par défaut"
no_rules_yet: Aucune règle par défaut
add_new_button: '+ Ajouter une règle par défaut'
no_tags_yet: Aucun tag défini par cette entreprise pour le moment
no_rules_yet: Aucune règle ne concerne ce tag pour le moment
for_customers_tagged: 'Pour les acheteurs avec le tag:'
add_new_rule: '+ Ajouter une nouvelle règle'
add_new_tag: '+ Ajouter un nouveau tag'
users:
email_confirmation_text: Email confirmation is pending.<br />We've sent a confirmation email to
email_confirmation_notice_html: "L'email de confirmation n'a pas encore été validé. Il a été envoyé à %{email}."
resend: Renvoyer
owner: Propriétaire
owner_tip: Le premier utilisateur responsable pour l'entreprise.
owner: 'Manager principal'
owner_tip: Manager principal de cette entreprise.
notifications: Notifications
notifications_tip: Notifications about orders will be send to this email address.
notifications_placeholder: 'ex: gustav@truffles.com'
notifications_note: 'Note: A new email address may need to be confirmed prior to use'
notifications_tip: Une notification de commande sera envoyée à cette adresse email pour chaque commande passée dans votre boutique.
notifications_placeholder: 'ex: bernard@maferme.fr'
notifications_note: 'A noter: si vous saisissez une nouvelle adresse, un email de confirmation sera envoyé à cette adresse avec un lien de validation à cliquer.'
managers: Managers
managers_tip: The other users with permission to manage this enterprise.
managers_tip: Sélectionner ici les utilisateurs ayant la permission de gérer cette entreprise. Ils doivent avoir un compte sur Open Food France pour être sélectionnés.
actions:
edit_profile: Mettre à jour le profil
properties: Propriétés
edit_profile: Modifier le profil
properties: Labels / propriétés
payment_methods: Méthodes de paiement
payment_methods_tip: Cette entreprise n'a pas de méthode de paiement
payment_methods_tip: Cette entreprise n'a pas paramétré de méthode de paiement
shipping_methods: Méthodes de livraison
shipping_methods_tip: Cette entreprise n'a pas de méthode de livraisons
enterprise_fees: Enterprise Fees
enterprise_fees_tip: This enterprise has no fees
shipping_methods_tip: Cette entreprise a paramétré des méthodes de paiement
enterprise_fees: Marges et commissions
enterprise_fees_tip: Cette entreprise n'a pas paramétré de marges et commissions
admin_index:
name: Nom
role: Role
sells: Sells
sells: Produits vendus
visible: Visible?
owner: Owner
producer: Producer
owner: Manager principal
producer: Producteur
change_type_form:
producer_profile: Producer Profile
connect_ofn: Connect through OFN
always_free: ALWAYS FREE
producer_description_text: Add your products to Open Food Network, allowing hubs to stock your products in their stores.
producer_shop: Producer Shop
sell_your_produce: Sell your own produce
sell_description_text: Sell your products directly to customers through your very own Open Food Network shopfront.
sell_description_text2: A Producer Shop is for your produce only, if you want to sell produce grown/produced off site, select 'Producer Hub'.
producer_hub: Producer Hub
producer_hub_text: Sell produce from self and others
producer_hub_description_text: Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network.
profile: Profile Only
get_listing: Get a listing
profile_description_text: 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.
hub_shop: Hub Shop
hub_shop_text: Sell produce from others
hub_shop_description_text: 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.
choose_option: Please choose one of the options above.
change_now: Change now
producer_profile: Profil producteur
connect_ofn: Gagnez en visibilité via OFFrance
always_free: GRATUIT
producer_description_text: Saisissez votre catalogue produits sur Open Food France, ce qui permettra aux hubs-distributeurs utilisant la plateforme de les proposer dans leurs boutiques (sur votre autorisation).
producer_shop: Boutique Producteur
sell_your_produce: Vendez vos propres produits
sell_description_text: Vendez vos produits en direct aux mangeurs/restaurateurs/etc. via votre propre Boutique Producteur sur Open Food France.
sell_description_text2: Une Boutique Producteur vous permet de vendre uniquement vos propres produits. Si vous voulez vendre d'autres produits, sélectionnez "Hub Producteur"
producer_hub: Hub Producteur
producer_hub_text: Vendez vos produits et ceux d'autres fournisseurs
producer_hub_description_text: Vous vendez non seulement vos produits, mais aussi des produits d'autres producteurs de votre région, artisans, ou distributeurs afin de proposer une offre complète dans votre boutique. Vous soutenez ainsi le développement de votre système alimentaire territorial !
profile: Profil uniquement
get_listing: Référencez votre hub/point de vente
profile_description_text: Les visiteurs peuvent vous trouver sur Open Food France et vous contacter. Votre entreprise sera visible sur la carte.
hub_shop: Boutique Hub
hub_shop_text: Vendez des produits de multiples fournisseurs différents
hub_shop_description_text: Vous proposez des produits de différents producteurs de votre région, artisans, ou distributeurs afin de proposer une offre complète dans votre boutique. Vous soutenez ainsi le développement de votre système alimentaire territorial !
choose_option: Veuilliez choisir l'une des options ci-dessus.
change_now: Changer
enterprise_user_index:
search_placeholder: Search By Name
manage: Manage
loading_enterprises: CHARGEMENT DES ENTREPRISES
no_enterprises_found: Aucune entreprise trouvée.
search_placeholder: Recherche par nom
manage: Gérer
new_form:
owner: Owner
owner_tip: The primary user responsible for this enterprise.
i_am_producer: I am a Producer
contact_name: Nom du contact
owner: Manager principal
owner_tip: Le manager principal est l'individu qui porte la responsabilité principale de l'entreprise dans le contexte de l'utilisation d'Open Food France.
i_am_producer: Je suis un producteur
contact_name: Nom du contact principal
edit:
editing: 'Editing:'
back_link: Retour à la liste d'entreprise
index:
title: Enterprises
new_enterprise: Ajouter une nouvelle entreprise
editing: 'En modification:'
back_link: Revenir à la liste des entreprises
new:
title: Nouvelle entreprise
back_link: Retour à la liste d'entreprise
back_link: Revenir à la liste des entreprises
welcome:
welcome_title: Bienvenue chez Open Food France !
welcome_text: Vous avez bien créé une
welcome_title: Bienvenue sur Open Food France !
welcome_text: 'Vous avez créé avec succès '
next_step: Etape suivante
choose_starting_point: 'Choose your starting point:'
choose_starting_point: 'Choisissez par où commencer:'
order_cycles:
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.
preferred_product_selection_from_coordinator_inventory_only_here: Coordinator's Inventory Only
preferred_product_selection_from_coordinator_inventory_only_all: All Available Products
save_reload: Save and Reload Page
title: Paramétrages avancés
choose_product_tip: Vous pouvez choisir de limiter le choix des produits pouvant être mis en vente dans votre boutique à ceux figurant dans le catalogue boutique de %{inventory}.
preferred_product_selection_from_coordinator_inventory_only_here: Uniquement les produits du catalogue boutique
preferred_product_selection_from_coordinator_inventory_only_all: Tous les produits disponibles dans les catalogues producteurs
save_reload: Sauvegarder et rafraichir la page
coordinator_fees:
add: Add coordinator fee
add: Ajouter commission coordinateur
form:
incoming: Incoming
supplier: Supplier
products: Products
receival_details: Receival details
fees: Fees
outgoing: Outgoing
distributor: Distributor
products: Products
incoming: Produits entrants (constitution du catalogue boutique pour le cycle de vente)
supplier: Fournisseur
receival_details: Détails livraison produits
fees: Commission
outgoing: Produits sortants (vendus via un ou plusieurs hubs)
distributor: Hub (distributeur)
products: Produits
tags: Tags
delivery_detaisl: Pickup / Delivery details
debug_info: Debug information
delivery_detaisl: Détails de livraison / retrait
debug_info: Informations de débogage
name_and_timing_form:
name: Name
orders_open: Orders open
coordinator: Coordinator
order_closes: Orders close
name: Nom
orders_open: Commandes à partir de
coordinator: Coordinateur
order_closes: Commandes jusqu'au
row:
suppliers: suppliers
distributors: distributors
variants: variants
suppliers: fournisseurs
distributors: hubs-distributeurs
variants: variantes
simple_form:
ready_for: Ready for
ready_for_placeholder: Date / time
customer_instructions: Customer instructions
customer_instructions_placeholder: Pick-up or delivery notes
products: Products
fees: Fees
ready_for: 'Prêt pour '
ready_for_placeholder: Date / Heure
customer_instructions: Précisions pour l'acheteur
customer_instructions_placeholder: Commentaires pour le retrait / la livraison
products: Produits
fees: Commissions
edit:
advanced_settings: Advanced Settings
update_and_close: Update and Close
advanced_settings: Paramétrages avancés
update_and_close: Mettre à jour et fermer
producer_properties:
form:
property: Propriété
value: Valeur
property: Propriétés / labels
value: Détail
index:
title: Producer Properties
title: Propriétés / labels du producteur
shared:
user_guide_link:
user_guide: Guide l'utilisateur
user_guide: Guide utilisateur
invoice_settings:
edit:
title: Apparence de la facture
invoice_style2?: Utiliser le modèle de facture alternaltif qui comprend une séparation des taxes par taux et une indication du taux appliqué par item (pas encore adapté pour les pays affichant des prix hors taxe)
title: Paramètres de facturation
invoice_style2?: Utiliser le modèle de facture alternatif qui détaille le montant de TVA agrégé par taux et l'information du taux de TVA par produit (pas adapté pour les instances affichant les prix HT)
enable_receipt_printing?: Afficher les options d'impression de tickets de caisse dans le menu déroulant des commandes?
home:
hubs:
show_closed_shops: "Aficher les boutiques fermées"
@@ -486,39 +490,31 @@ fr:
register: "s'inscrire"
contact: "contact"
require_customer_login: "La boutique est réservée aux membres."
require_login_html: "Déjà inscrit? %{login}. Sinon, %{register} pour pouvoir faire vos achats."
require_customer_html: "Veuillez %{contact} %{enterprise} pour devenir membre."
require_login_html: "Déjà inscrit? %{Connectez-vous}. Sinon, %{inscrivez-vous} pour pouvoir faire vos achats."
require_customer_html: "Veuillez %{contacter} %{l'entreprise} pour devenir membre."
invoice_billing_address: "Adresse de facturation :"
invoice_column_tax: "TVA"
invoice_column_price: "Prix"
invoice_column_item: "Produit"
invoice_column_qty: "Qté"
invoice_column_tax: "TVA"
invoice_column_price: "Prix"
invoice_column_unit_price_with_taxes: "Prix unitaire (TTC)"
invoice_column_unit_price_without_taxes: "Prix unitaire (HT)"
invoice_column_price_with_taxes: "Prix total (TTC)"
invoice_column_price_without_taxes: "Prix total (HT)"
invoice_column_tax_rate: "Taux TVA"
invoice_column_unit_price_with_taxes: "Prix unitaire TTC"
invoice_column_unit_price_without_taxes: "Prix unitaire HT"
invoice_column_price_with_taxes: "Prix total TTC"
invoice_column_price_without_taxes: "Prix total HT"
invoice_column_tax_rate: "TVA applicable"
tax_invoice: "FACTURE"
tax_total: "Total TVA (%{rate}) :"
tax_total: "Total taxe (%{rate}) :"
total_excl_tax: "Total HT :"
total_incl_tax: "Total TTC :"
abn: "SIRET :"
acn: "n° TVA :"
invoice_issued_on: "Facture éditée le :"
order_number: "Numéro de facture :"
abn: "SIRET"
acn: "n° TVA intracommunautaire"
invoice_issued_on: "Date de facture :"
order_number: "N° de facture :"
date_of_transaction: "Date de la transaction :"
number:
percentage:
format:
format: "%n %"
ticket_column_qty: "Qté"
ticket_column_item: "Item"
ticket_column_unit_price: "Px Unit"
ticket_column_total_price: "Px Total"
ticket_column_item: "Produit"
ticket_column_unit_price: "Prix unitaire"
ticket_column_total_price: "Prix total"
logo: "Logo (640x130)"
logo_mobile: "Logo smartphone (75x26)"
logo_mobile_svg: "Logo smartphone (SVG)"
@@ -542,14 +538,13 @@ fr:
phone: Téléphone
next: Suivant
address: Adresse
address_placeholder: "ex: 16, rue du Marché"
address_placeholder: 'ex: 24 rue de la croix verte'
address2: Adresse (suite)
city: Ville
city_placeholder: "ex: Lille"
state: Département
city_placeholder: 'ex: Nantes'
postcode: Code postal
postcode_placeholder: "ex: 59000"
state: Région
postcode_placeholder: 'ex: 44000'
state: Département
country: Pays
unauthorized: Non authorisé
terms_of_service: "Conditions d'utilisation"
@@ -670,7 +665,7 @@ fr:
order_paid: RÉGLÉ
order_not_paid: NON RÉGLÉ
order_total: Total commande
order_payment: "Paiement par :"
order_payment: "Payer via:"
order_billing_address: Adresse de facturation
order_delivery_on: Livraison prévue
order_delivery_address: Adresse de livraison
@@ -682,6 +677,7 @@ fr:
order_includes_tax: (TVA inclue)
order_payment_paypal_successful: Votre paiement via PayPal a été réalisé avec succès.
order_hub_info: Hub Info
bom_tip: "Utilisez cette page pour modifier les quantités sur plusieurs commandes à la fois. Les produits peuvent aussi être supprimés des commandes si nécessaire."
unsaved_changes_warning: "Des modifications n'ont pas été enregistrées et seront perdues si vous continuez."
unsaved_changes_error: "Les champs entourés en rouge contiennent des erreurs."
products: "Produits"
@@ -722,7 +718,7 @@ fr:
email_payment_paid: RÉGLÉ
email_payment_not_paid: NON RÉGLÉ
email_payment_summary: Résumé du paiement
email_payment_method: "Moyen de paiement :"
email_payment_method: "Payer via :"
email_shipping_delivery_details: Détails de livraison
email_shipping_delivery_time: "Livré le:"
email_shipping_delivery_address: "Adresse de livraison:"
@@ -767,6 +763,7 @@ fr:
hubs_filter_by: "Filtrer par"
hubs_filter_type: "Catégorie"
hubs_filter_delivery: "Livraison"
hubs_filter_property: "Propriétés / labels"
hubs_matches: "Vous voulez dire?"
hubs_intro: Passez commande près de chez vous
hubs_distance: Le plus près de
@@ -1191,7 +1188,7 @@ fr:
spree_admin_single_enterprise_alert_mail_confirmation: "Veuillez confirmer l'adresse mail pour"
spree_admin_single_enterprise_alert_mail_sent: "Email envoyé à "
spree_admin_overview_action_required: "Action requise"
spree_admin_overview_check_your_inbox: "Veuillez vérifier votre messagerie et suivre les instructions. Merci!"
spree_admin_overview_check_your_inbox: "Veuillez vérifier votre boîte mail pour les prochaines étapes. Merci!"
change_package: "Changer de pack"
spree_admin_single_enterprise_hint: "Astuce: Pour permettre aux gens de vous trouver, activez votre visibilité "
your_profil_live: "Votre profil en ligne"
@@ -1208,6 +1205,12 @@ fr:
edit_profile_details_etc: "Modifier la description, les images, etc."
order_cycle: "Cycle de vente"
remove_tax: "Retirer TVA"
enterprise_terms_of_service: "Conditions Générales d'Utilisation"
enterprises_require_tos: "Les entreprises doivent accepter les Conditions Générales d'Utilisation"
enterprise_tos_link: "Lien vers les Conditions Générales d'Utilisation"
enterprise_tos_message: "Nous soutenons la mise en place d'un système alimentaire résilient et durable, et souhaitons œuvrer avec des entreprises qui partagent nos valeurs et notre vision. Ainsi, nous demandons aux entreprises s'enregistrant sur Open Food France de valider nos "
enterprise_tos_link_text: "Conditions d'utilisation"
enterprise_tos_agree: "J'adhère aux valeurs d'Open Food France et valide les Conditions Générales d'Utilisation."
tax_settings: "Paramètres TVA"
products_require_tax_category: "vous devez choisir la TVA applicable"
admin_shared_address_1: "Adresse"
@@ -1241,22 +1244,22 @@ fr:
report_order_cycle: "Cycle de vente:"
report_entreprises: "Entreprises:"
report_users: "Managers:"
report_tax_rates: "Taxes par taux"
report_tax_types: "Taxes par type"
report_header_order_number: "Numéro de commande"
report_tax_rates: "TVA par taux"
report_tax_types: "TVA par type de produit/service"
report_header_order_number: "N° commande"
report_header_date: "Date"
report_header_items: "Produits"
report_header_items_total: "Total des produits %{currency_symbol}"
report_header_taxable_items_total: "Total des produits taxés (%{currency_symbol})"
report_header_sales_tax: "Tax sur la vente (%{currency_symbol})"
report_header_items_total: "Montant des produits %{currency_symbol}"
report_header_taxable_items_total: "Montant produits soumis à TVA (%{currency_symbol})"
report_header_sales_tax: "TVA sur produits (%{currency_symbol})"
report_header_delivery_charge: "Frais de livraison (%{currency_symbol})"
report_header_tax_on_delivery: "Taxe sur la livraison (%{currency_symbol})"
report_header_tax_on_fees: "Taxes sur les charges (%{currency_symbol})"
report_header_total_tax: "Taxe totale (%{currency_symbol})"
report_header_customer: "Acheteur"
report_header_tax_on_delivery: "TVA sur livraison (%{currency_symbol})"
report_header_tax_on_fees: "TVA sur commission hub (%{currency_symbol})"
report_header_total_tax: "Total TVA (%{currency_symbol})"
report_header_customer: "Client"
report_header_distributor: "Distributeur"
report_header_total_excl_vat: "Total sans TVA (%{currency_symbol})"
report_header_total_incl_vat: "Total avec TVA (%{currency_symbol})"
report_header_total_excl_vat: "Total HT (%{currency_symbol})"
report_header_total_incl_vat: "Total TTC (%{currency_symbol})"
initial_invoice_number: "N° de facture initial:"
invoice_date: "Date de facture:"
due_date: "Date d'échéance:"
@@ -1298,7 +1301,155 @@ fr:
validation_msg_product_category_cant_be_blank: "^Veuillez sélectionner la catégorie produit"
validation_msg_tax_category_cant_be_blank: "^Veuillez sélectionner la TVA applicable"
validation_msg_is_associated_with_an_exising_customer: "est associé à un acheteur existant"
js:
admin:
modals:
got_it: J'ai compris
tag_rule_help:
title: Règles de tag
overview: Aperçu
overview_text: >
Les règles de tags vous permettent de paramétrer ce qui est vu ou pas
par tel ou tel type d'acheteur. Par exemple des options de livraison,
des méthodes de paiement, des produits, ou des cycles de vente.
by_default_rules: "Règles à appliquer \"par défaut\""
by_default_rules_text: >
Les règles de tag par défaut vous permettent de masquer des éléments
par défaut. Vous pouvez ensuite permettre à certains acheteurs, selon
les tags attribués, de voir ces éléments.
customer_tagged_rules: "Règles pour les acheteur avec un tag"
customer_tagged_rules_text: >
En créant une règle spécifique à un tag, vous pouvez modifier le contenu
vu par défaut (afficher ou masquer) par les acheteurs associés à ce
tag.
panels:
save: Enregistrer
saved: Enregistré
saving: En cours d'enregistrement
enterprise_package:
hub_profile: Profil Hub
hub_profile_cost: "COÛT: CONTRIBUTION LIBRE"
hub_profile_text1: >
Les visiteurs voient votre profil sur la carte, et peuvent vous contacter.
Vous augmentez ainsi votre visibilité.
hub_profile_text2: >
Créez votre profil et utilisez Open Food France pour vous connecter
à votre système alimentaire territorial.
hub_shop: Boutique Hub
hub_shop_text1: >
Vous proposez des produits de différents producteurs de votre région,
artisans, ou distributeurs afin de proposer une offre complète dans
votre boutique. Vous soutenez ainsi le développement de votre système
alimentaire territorial !
hub_shop_text2: >
Un hub n'a pas de modèle figé, il peut s'agir d'un groupement d'achat,
d'une AMAP, d'une épicerie coopérative, d'une épicerie locale de quartier
ou épicerie en circuit court en ligne, etc.
hub_shop_text3: >
Si vous produisez et voulez également vendre vos propres produits, vous
devez modifier le statut de votre entreprise, elle doit apparaitre en
tant que "producteur".
choose_package: Choisir le type de compte souhaité
choose_package_text1: >
Votre entreprise ne sera activée et visible que lorsque vous aurez choisi
le type de compte souhaité parmi les options à gauche.
choose_package_text2: >
Cliquez sur une option pour voir le détail du compte proposé, puis une
fois votre choix fait, cliquez sur le bouton rouge ENREGISTRER !
profile_only: Profil uniquement
profile_only_cost: "COÛT: CONTRIBUTION LIBRE"
profile_only_text1: >+
Gagnez en visibilité, racontez votre histoire, et affichez vos coordonnées
pour pouvoir être contactés.
profile_only_text2: >
Si vous souhaitez vous concentrer sur votre activité de production,
et laisser à d'autre le soin de distribuer vos produits, vous n'avez
pas besoin d'une boutique sur Open Food France.
profile_only_text3: >
Saisissez votre catalogue produits sur Open Food France, ce qui permettra
aux hubs-distributeurs utilisant la plateforme de les proposer dans
leurs boutiques (sur votre autorisation).
producer_shop: Boutique Producteur
producer_shop_text1: >
Vendez vos produits en direct aux mangeurs/restaurateurs/etc. via votre
propre Boutique Producteur sur Open Food France.
producer_shop_text2: >
Une Boutique Producteur vous permet de vendre uniquement vos propres
produits. Si vous voulez vendre d'autres produits, sélectionnez "Hub
Producteur"
producer_hub: Hub Producteur
producer_hub_text1: >
Vous vendez non seulement vos produits, mais aussi des produits d'autres
producteurs de votre région, artisans, ou distributeurs afin de proposer
une offre complète dans votre boutique. Vous soutenez ainsi le développement
de votre système alimentaire territorial !
producer_hub_text2: >
Un hub producteur peut prendre différentes formes, une boutique de vente
directe, un magasin de producteurs en ligne, un drive fermier, etc.
producer_hub_text3: >
Open Food France soutient tous les modèles de hubs alimentaires, nous
pensons que la résilience du système viendra de la diversité des modèles.
Donc quel que soit votre modèle, nous souhaitons vous apporter les outils
de gestion donc vous avez besoin pour opérer votre circuit court.
get_listing: Référencez votre entreprise
always_free: CONTRIBUTION LIBRE
sell_produce_others: Vendez des produits de multiples fournisseurs différents
sell_own_produce: Vendez vos propres produits
sell_both: Vendez vos produits et ceux d'autres fournisseurs
enterprise_producer:
producer: Producteur
producer_text1: >
Un producteur fabrique de bonnes choses à boire et à manger. Vous êtes
un producteur si vous les faites pousser, les élevez, les pétrissez,
transformez, fermentez, les réduisez en grains, etc.
producer_text2: >
Un producteur peut aussi avoir d'autres rôles, comme par exemple stocker
et distribuer des produits d'autres producteurs à travers une boutique
sur Open Food France.
non_producer: Pas producteur
non_producer_text1: >
Les entreprises qui ne produisent pas ne peuvent pas créer leur propre
catalogue produits pour les vendre sur Open Food France.
non_producer_text2: >
Ces entreprises vont plutôt faire le lien entre des producteurs et des
mangeurs/restaurateurs, en proposant un modèle opérationnel pour agréger,
préparer les commandes, ou encore livrer les produits.
producer_desc: Producteurs / transformateurs
producer_example: 'ex: maraichers, boulangers, brasseurs, artisans'
non_producer_desc: Autres entreprises de distribution alimentaire
non_producer_example: 'ex: épiceries, coopératives, groupements d''achats'
enterprise_status:
status_title: "%{name} est en place et prêt à démarrer!"
severity: Rigueur
description: Description
resolve: Résoudre
new_tag_rule_dialog:
select_rule_type: "Choisir le type de règle:"
out_of_stock:
reduced_stock_available: Stock disponible
out_of_stock_text: >
Pendant que vous faisiez vos achats, le niveau de stock disponible pour
un ou plusieurs produits dans votre panier est devenu insuffisant pour répondre
à votre demande. Voilà les modifications opérées:
now_out_of_stock: est maintenant en rupture de stock.
only_n_remainging: "plus que %{num} en stock."
spree:
admin:
products:
bulk_edit:
header:
title: Edition des produits par lots
indicators:
title: CHARGEMENT DES PRODUITS
no_products: "Aucun produit trouvé. Pourquoi n'en ajouté vous pas quelques-uns ?"
no_results: "Désolé, aucun résultat trouvé"
variants:
autocomplete:
producer_name: Producteur
date_picker:
format: '%Y-%m-%d'
js_format: 'aa-mm-jj'
zipcode: Code postal
shipment_states:
backorder: réapprovisionnement
@@ -1317,6 +1468,10 @@ fr:
processing: en traitement
void: annuler
invalid: Invalide
order_mailer:
invoice_email:
hi: "Bonjour %{name}"
invoice_attached_text: 'Veuillez trouver ci-joint la facture pour votre récente commande auprès de '
order_state:
address: adresse
adjustments: ajustements
@@ -1330,3 +1485,6 @@ fr:
resumed: recommencé
returned: retourné
skrill: cash
orders:
invoice:
tax_invoice: "FACTURE"

View File

@@ -11,7 +11,7 @@ nb:
Var du gjest forrige gang? Kanskje du må opprette en konto eller nullstille passordet.
enterprise_confirmations:
enterprise:
confirmed: Takk, din epostadresse er bekreftet.
confirmed: Takk, e-postadressen din er bekreftet.
not_confirmed: Din epostadresse kan ikke bekreftes. Kanskje du allerede har fullført dette steget?
confirmation_sent: "Bekreftelse på epost er sendt!"
confirmation_not_sent: "Kunne ikke sende bekreftelse på epost."
@@ -28,6 +28,8 @@ nb:
producers_join: Norske produsenter er nå velkommen til å bli med i Open Food Network.
charges_sales_tax: MVA-pliktig?
print_invoice: "Skriv ut Faktura"
print_ticket: "Skriv ut kvittering"
select_ticket_printer: "Velg skriver for kvitteringer"
send_invoice: "Send Faktura"
resend_confirmation: "Send Bekreftelse på nytt"
view_order: "Se Bestilling"
@@ -49,6 +51,17 @@ nb:
say_yes: "Ja"
then: vil
sort_order_cycles_on_shopfront_by: "Sorter Bestillingsrunder i Butikkvindu etter"
required_fields: Obligatoriske felt er merket med en stjerne
select_continue: Velg og fortsett
remove: Fjern
or: eller
collapse_all: Skjul alle
expand_all: Vis alle
loading: Laster...
show_more: Vis mer
show_all: Vis alle
show_all_with_more: "Vis Alle (%{num} Mer)"
cancel: Avbryt
admin:
date: Dato
email: Epost
@@ -77,51 +90,53 @@ nb:
tag_has_rules: "Gjeldende regler for denne merkelappen: %{num}"
has_one_rule: "har én regel"
has_n_rules: "har %{num} regler"
unsaved_confirm_leave: "Det finnes ulagrede endringer på denne siden. Fortsett uten å lagre?"
unsaved_changes: "Du har ulagrede endringer"
accounts_and_billing_settings:
method_settings:
default_accounts_payment_method: "Default Accounts Payment Method"
default_accounts_shipping_method: "Default Accounts Shipping Method"
default_accounts_payment_method: "Standard Betalingsmetode for Bokføring"
default_accounts_shipping_method: "Standard Leveringsmetode for Bokføring"
edit:
accounts_and_billing: "Bokføring & Fakturering"
accounts_administration_distributor: "bokføringsadministrasjon distributør"
accounts_administration_distributor: "Distributør bokføringsadministrasjon"
admin_settings: "Innstillinger"
update_invoice: "Oppdater Fakturaer"
finalise_invoice: "Sluttfør Fakturaer"
finalise_user_invoices: "Sluttfør Brukerfakturaer"
finalise_user_invoice_explained: "Bruk denne knappen til å sluttføre alle fakturaer i systemet for forrige kalendermåned. Denne oppgaven kan settes opp til å kjøres automatisk hver måned."
manually_run_task: "Gjør Oppgave Manuelt"
auto_update_invoices: "Auto-oppdater fakturaer hver natt kl. 1:00am"
finalise_invoice: "Ferdigstill Fakturaer"
auto_finalise_invoices: "Auto-ferdigstill fakturaer månedlig den 2. kl. 1:30am"
manually_run_task: "Kjør oppgaver manuelt"
update_user_invoice_explained: "Bruk denne knappen til umiddelbart å oppdatere fakturaer for denne måneden frem til i dag for hver bedriftsbruker i systemet. Denne oppgaven kan settes opp til å kjøre automatisk hver natt."
finalise_user_invoices: "Ferdigstill Brukerfakturaer"
finalise_user_invoice_explained: "Bruk denne knappen til å ferdigstille alle fakturaer i systemet for forrige kalendermåned. Denne oppgaven kan settes opp til å kjøre automatisk en gang i måneden."
update_user_invoices: "Oppdater Brukerfakturaer"
update_user_invoice_explained: "Bruk denne knappen til umiddelbart å oppdatere fakturaer for måneden frem til i dag for hver bedriftsbruker i systemet. Denne oppgaven kan settes opp til å kjøres automatisk hver natt."
auto_finalise_invoices: "Auto-sluttfør fakturaer månedlig på den 2. kl. 01:30"
auto_update_invoices: "Auto-oppdater fakturaer hver natt kl. 01:00"
business_model_configuration:
edit:
business_model_configuration: "Utforming av forretningsmodell"
business_model_configuration_tip: "Configure the rate at which shops will be charged each month for use of the Open Food Network."
bill_calculation_settings: "Bill Calculation Settings"
bill_calculation_settings_tip: "Adjust the amount that enterprises will be billed each month for use of the OFN."
shop_trial_length: "Lengden av prøveperioden (dager)"
shop_trial_length_tip: "The length of time (in days) that enterprises who are set up as shops can run as a trial period."
fixed_monthly_charge: "Fixed Monthly Charge"
fixed_monthly_charge_tip: "A fixed monthly charge for all enterprises who are set up as a shop and have exceeded the minimum billable turnover (if set)."
business_model_configuration: "Forretningsmodell"
business_model_configuration_tip: "Konfigurer hvor ofte butikker skal belastes hver måned for å bruke Open Food Network."
bill_calculation_settings: "Kalkulatorinnstillinger for Regninger"
bill_calculation_settings_tip: "Tilpass hvor mye bedrifter skal faktureres for hver måned for bruken av OFN."
shop_trial_length: "Lengde prøveperiode for butikker (dager)"
shop_trial_length_tip: "Antall dager prøveperiode for en bedrift som er satt opp som butikk."
fixed_monthly_charge: "Fast månedlig belastning"
fixed_monthly_charge_tip: "En fast månedlig belastning for alle bedrifter som er satt opp som butikk og har overskredet minimum fakturert omsetning (hvis konfigurert)."
percentage_of_turnover: "Prosent av omsetning"
percentage_of_turnover_tip: "When greater than zero, this rate (0.0 - 1.0) will be applied to the total turnover of each shop and added to any fixed charges (to the left) to calculate the monthly bill."
monthly_cap_excl_tax: "månedlig tak (eks. MVA)"
monthly_cap_excl_tax_tip: "When greater than zero, this value will be used as a cap on the amount that shops will be charged each month."
tax_rate: "Tax Rate"
tax_rate_tip: "Tax rate that applies to the the monthly bill that enterprises are charged for using the system."
minimum_monthly_billable_turnover: "Minimum Monthly Billable Turnover"
minimum_monthly_billable_turnover_tip: "Minimum monthly turnover before a shopfront will be charged for using OFN. Enterprises turning over less than this amount in a month will not be charged, either as a percentage or fixed rate."
example_bill_calculator: "Example Bill Calculator"
example_bill_calculator_legend: "Alter the example turnover to visualise the effect of the settings to the left."
example_monthly_turnover: "Example Monthly Turnover"
example_monthly_turnover_tip: "An example monthly turnover for an enterprise which will be used to generate calculate an example monthly bill below."
cap_reached?: "Cap Reached ?"
cap_reached?_tip: "Whether the cap (specified to the left) has been reached, given the settings and the turnover provided."
included_tax: "Included tax"
included_tax_tip: "The total tax included in the example monthly bill, given the settings and the turnover provided."
total_monthly_bill_incl_tax: "Total månedlig regning (Inkl. Avgift)"
total_monthly_bill_incl_tax_tip: "The example total monthly bill with tax included, given the settings and the turnover provided."
percentage_of_turnover_tip: "Når større enn null, gjelder denne raten (0.0 - 1.0) på total omsetning for hver butikk og lagt til enhver fast rate (til venstre) for å beregne den månedlige fakturaen."
monthly_cap_excl_tax: "månedlig lokk (eks. MVA)"
monthly_cap_excl_tax_tip: "Når større enn null vil denne verdien bli brukt som et lokk på hvor mye butikker vil bli belastet hver måned."
tax_rate: "Avgiftsrate"
tax_rate_tip: "Avgiftsrate som gjelder månedlig regning som bedrifter blir belastet for å bruke systemet."
minimum_monthly_billable_turnover: "Minimum Månedlig Fakturerbar Omsetning"
minimum_monthly_billable_turnover_tip: "Minimum månedlig omsetning før en butikk vil bli belastet for å bruke OFN. Bedrifter som omsetter for mindre enn dette på en måned vil ikke bli belastet, enten som en prosent eller en fast rate."
example_bill_calculator: "Eksempel på Regningskalkulator"
example_bill_calculator_legend: "Endre eksempelomsetningen for å se effekten av innstillingene til venstre."
example_monthly_turnover: "Eksempel på Månedlig Omsetning"
example_monthly_turnover_tip: "Eksempel på månedlig omsetning for en bedrift som vil bli brukt for å generere beregning av eksempel på månedlig regning under."
cap_reached?: "Lokk Nådd?"
cap_reached?_tip: "Om lokket (spesifisert til venstre) er nådd, gitt innstillingene og omsetningen som er satt."
included_tax: "Inkludert avgift"
included_tax_tip: "Total avgift inkludert i eksemplet på månedlig regning, gitt innstillingene og omsetningen som er satt."
total_monthly_bill_incl_tax: "Total Månedlig Regning (Inkl. Avgift)"
total_monthly_bill_incl_tax_tip: "Eksempel på total månedlig regning inkludert avgift, gitt innstillingene og omsetningen som er satt."
customers:
index:
add_customer: "Legge til kunde"
@@ -143,6 +158,28 @@ nb:
edit: 'Redigere'
update_address: 'Oppdater Adresse'
confirm_delete: 'Sikker på å slette?'
cache_settings:
show:
title: Mellomlagring
distributor: Distributør
order_cycle: Bestillingsrunde
status: Status
diff: Forskjell
contents:
edit:
title: Innhold
enterprise_fees:
index:
title: Bedriftsavgifter
enterprise: Bedrift
fee_type: Avfgiftstype
name: Navn
tax_category: Avgiftskategori
calculator: Kalkulator
calculator_values: Kalkulatorverdier
enterprise_groups:
index:
new_button: Ny Bedriftsgruppe
products:
bulk_edit:
unit: Enhet
@@ -152,7 +189,11 @@ nb:
inherits_properties?: Arver Egenskaper?
available_on: Tilgjengelig på
av_on: "Til. på"
variants:
to_order_tip: "Varer laget for bestilling har ikke et lagernivå, slik som ferske skiver brød laget for bestilling."
variant_overrides:
loading_flash:
loading_inventory: LASTER VARELAGER
index:
title: Varelager
description: Bruk denne siden til å administrere varelager for dine bedrifter. Eventuelle produktdetaljer satt her vil overstyre de som er satt på 'Produkter'-siden
@@ -193,18 +234,94 @@ nb:
max_fulfilled_units: "Max Oppfylte Enheter"
order_error: "Noen feil må løses før du kan oppdatere bestillinger.\nAlle felt med røde kanter inneholder feil."
variants_without_unit_value: "ADVARSEL: Noen varianter mangler enhetsverdi"
order_cycles:
edit:
choose_products_from: "Velg Produkter Fra:"
enterprise:
select_outgoing_oc_products_from: Velg utgående bestillingsrundeprodukter fra
enterprises:
index:
producer?: Produsent?
title: Bedrifter
new_enterprise: Ny Bedrift
producer?: "Produsent?"
package: Pakke
status: Status
manage: Administrer
form:
about_us:
desc_short: Kort Beskrivelse
desc_short_placeholder: Fortell oss om din bedrift med en eller to setninger
desc_long: Om Oss
desc_long_placeholder: Fortell kunder om deg selv. Denne informasjonen vises på din offentlige profil.
business_details:
abn: Org nr.
abn_placeholder: f.eks. 999 000 123
acn: Mva. nr.
acn_placeholder: f.eks. 999 000 123
contact:
name: Navn
name_placeholder: f.eks. Gustav Plum
email_address: Epostadresse
email_address_placeholder: f.eks. www.truffles.com
phone: Telefon
phone_placeholder: f.eks. 987 123 654
website: Hjemmeside
website_placeholder: f.eks. www.truffles.com
enterprise_fees:
name: Navn
fee_type: Avfgiftstype
manage_fees: Administrer Bedriftsavgifter
no_fees_yet: Du har ingen bedriftsavgifter ennå.
create_button: Opprett en nå
images:
logo: Logo
promo_image_placeholder: 'Dette bildet vises i "Om Oss"'
promo_image_note1: 'VENNLIGST MERK:'
promo_image_note2: Ethvert promobilde lastet opp her vil bli kuttet til 1200x260.
promo_image_note3: Promobildet vises på toppen av en bedrifts profilside og i pop-ups.
inventory_settings:
text1: Du kan velge å administrere lagernivå og priser inn via din
inventory: varelager
text2: >
Hvis du bruker varelagerverktøyet kan du velge om nye produkter lagt
til av dine leverandører må legges til ditt varelager før de kan bli
telt med. Hvis du ikke bruker varelageret ditt til å administrere dine
produkter burde du velge det 'anbefalte' alternativet nedenfor:
preferred_product_selection_from_inventory_only_yes: Nye produkter kan legges til mitt butikkvindu (anbefalt)
preferred_product_selection_from_inventory_only_no: Nye produkter må legges til mitt varelager før de kan legges til mitt butikkvindu
payment_methods:
name: Navn
applies: Gjelder?
manage: Administrer Betalingsmetoder
not_method_yet: Du har ingen betalingsmetoder ennå.
create_button: Opprett Ny Betalingsmetode
create_one_button: Opprett en nå
primary_details:
name: Navn
name_placeholder: f.eks. Professor Plums Biodynamiske Trøfler
groups: Grupper
groups_tip: Velg grupper eller regioner du er medlem av. Dette vil hjelpe kunder til å finne din bedrift.
groups_placeholder: Begynn å skriv for å søke etter tilgjengelige grupper...
primary_producer: Prmærprodusent?
primary_producer_tip: Velg 'Produsent' hvis du er en primærprodusent av mat.
producer: Produsent
any: Hvilken som helst
none: Ingen
own: Egen
sells: Selger
sells_tip: "Ingen - bedriften selger ikke til kunder direkte.<br />Egne - Bedrift selger egne produkter til kunder.<br />Både og - Bedrift kan selge egne eller andre bedrfiters produkter.<br />"
visible_in_search: Synlig i søk?
visible_in_search_tip: Bestemmer om denne bedriften vil være synlig til kunder når man søker på siden.
visible: Synlig
not_visible: Ikke synlig
permalink: Permalink (ingen mellomrom)
permalink_tip: "Denne permalinken brukes til å opprette url-en til din butikk: %{link}din-butikks-navn/shop"
link_to_front: Lenke til butikkvindu
link_to_front_tip: En direktelenke til ditt butikkvindu på Open Food Network.
shipping_methods:
name: Navn
applies: Gjelder?
manage: Administrer Leveringsmetoder
create_button: Opprett Ny Leveringsmetode
create_one_button: Opprett en nå
no_method_yet: Du har ingen leveringsmetoder ennå.
shop_preferences:
shopfront_requires_login: "Offentlig synlig butikk?"
shopfront_requires_login_tip: "Velg om kunder må logge inn for å se butikken eller om den er synlig for alle."
@@ -214,6 +331,149 @@ nb:
allow_guest_orders_tip: "Tillat gjestebestillinger eller krev brukerregistrering."
allow_guest_orders_false: "Krev innlogging for å bestille"
allow_guest_orders_true: "Tillat gjestebestilling"
shopfront_message_placeholder: >
En valgfri forklaring for kunder med detaljer om hvordan din nettbutikk
fungerer, for å vises over produktlisten på din side.
shopfront_closed_message_placeholder: >
En melding som gir en mer detaljert forklaring om hvorfor din butikk
er stengt og/eller når kunder kan forvente at den åpner igjen. Dette
vises på din butikk kun når du ikke har noen aktive bestillingsrunder
(dvs. butikk er stengt).
open_date: Åpningsdato
close_date: Stengedato
social:
twitter_placeholder: f.eks. @the_prof
tag_rules:
default_rules:
by_default: Som Standard
no_rules_yet: Ingen standard regler gjelder ennå
add_new_button: '+ Legg til en Ny Standard Regel'
no_tags_yet: Ingen emner gjelder for denne bedriften ennå
no_rules_yet: Ingen regler gjelder for dette emnet ennå
for_customers_tagged: 'For kunder merket:'
add_new_rule: '+ Legg til En Ny Regel'
add_new_tag: '+ Legg til Et Nytt Merke'
users:
email_confirmation_notice_html: "Epostbekreftelse venter. Vi har sendt en bekreftelsesepost til %{email}."
resend: Send på nytt
owner: 'Eier'
owner_tip: Primærbrukeren ansvarlig for denne bedriften.
notifications: Varslinger
notifications_tip: Varslinger om bestillinger vil bli sendt til denne epostadressen.
notifications_placeholder: f.eks. www.truffles.com
notifications_note: 'Merk: En ny epostadresse må kanskje bekreftes før bruk'
managers: Administratorer
managers_tip: Andre brukere med tilgang til å administrere denne bedriften.
actions:
edit_profile: Endre Profil
properties: Egenskaper
payment_methods: Betalingsmetoder
payment_methods_tip: Denne bedriften har ingen betalingsmetoder
shipping_methods: Leveringsmetoder
shipping_methods_tip: Denne bedriften har leveringsmetoder
enterprise_fees: Bedriftsavgifter
enterprise_fees_tip: Denne bedriften har ingen avgifter
admin_index:
name: Navn
role: Rolle
sells: Selger
visible: Synlig?
owner: Eier
producer: Produsent
change_type_form:
producer_profile: Produsentprofil
connect_ofn: Koble gjennom OFN
always_free: GRATIS FOR ALLTID
producer_description_text: Legg til dine produkter til Open Food Network, tillat hubs å lagre dine produkter i sine butikker.
producer_shop: Produsentbutikk
sell_your_produce: Selg dine egne produkter
sell_description_text: Selg dine produkter direkte til kunder gjennom din egen Open Food Network nettbutikk.
sell_description_text2: En Produsentbutikk er kun for dine produkter, hvis du ønsker å selge produkter dyrket/produsert en annen plass, velg 'Produsenthub'.
producer_hub: Produsenthub
producer_hub_text: Selg egne produkter og produkter fra andre
producer_hub_description_text: Din bedrift er ryggraden i ditt lokale matsystem. Du kan selge dine egne produkter og produkter samlet fra andre bedrifter gjennom din nettbutikk på Open Food Network.
profile: Kun Profil
get_listing: Få en oppføring
profile_description_text: Personer kan finne og kontakte deg på Open Food Network. Din bedrift vil være synlig på kartet, og vil være søkbar i oppføringer.
hub_shop: Hub butikk
hub_shop_text: Selg produkter fra andre
hub_shop_description_text: Din bedrift er ryggraden i ditt lokale matsystem. Du samler produkter fra andre bedrifter og kan selge de gjennom din butikk på Open Food Network.
choose_option: Vennligst velg en av alternativene over.
change_now: Endre nå
enterprise_user_index:
loading_enterprises: LASTER BEDRIFTER
no_enterprises_found: Ingen bedrifter funnet.
search_placeholder: Søk på navn
manage: Administrer
new_form:
owner: Eier
owner_tip: Primærbrukeren ansvarlig for denne bedriften.
i_am_producer: Jeg er en Produsent
contact_name: Kontaktnavn
edit:
editing: 'Endrer:'
back_link: Tilbake til bedriftsliste
new:
title: Ny Bedrift
back_link: Tilbake til bedriftsliste
welcome:
welcome_title: Velkommen til Open Food Network!
welcome_text: Du har opprettet en
next_step: Neste steg
choose_starting_point: 'Velg ditt startpunkt:'
order_cycles:
advanced_settings:
title: Avanserte Innstillinger
choose_product_tip: Du kan velge å begrense alle tilgjengelige produkter (både innkommende og utgående), til kun de i %{inventory}s varelager.
preferred_product_selection_from_coordinator_inventory_only_here: Kun Koordinators varelager
preferred_product_selection_from_coordinator_inventory_only_all: Alle tilgjengelige produkter
save_reload: Lagre og last siden på nytt
coordinator_fees:
add: Legg til koordinatoravgift
form:
incoming: Innkommende
supplier: Leverandør
receival_details: Mottaksdetaljer
fees: Avgifter
outgoing: Utgående
distributor: Distributør
products: Produkter
tags: Merker
delivery_detaisl: Hentings- / Leveringsdetaljer
debug_info: Debuginformasjon
name_and_timing_form:
name: Navn
orders_open: Bestillinger åpner
coordinator: Koordinator
order_closes: Bestillinger stenger
row:
suppliers: 'leverandører '
distributors: distributører
variants: varianter
simple_form:
ready_for: Klar til
ready_for_placeholder: Dato / tid
customer_instructions: Kundeinstruksjoner
customer_instructions_placeholder: Hente- eller leveringsmerknader
products: Produkter
fees: Avgifter
edit:
advanced_settings: Avanserte Innstillinger
update_and_close: Oppdater og Lukk
producer_properties:
form:
property: Egenskap
value: Verdi
index:
title: Produsentegenskaper
shared:
user_guide_link:
user_guide: Brukermanual
invoice_settings:
edit:
title: Fakturainnstillinger
invoice_style2?: Bruk den alternative fakturamodellen som inkluderer total avgiftsoppdeling pr rate og avgiftsrateinfo pr vare (passer ikke for land som viser priser ekskludert avgift)
enable_receipt_printing?: 'Vis valg for utskrift av kvitteringer ved bruk av kvitteringsprinter i nedtrekksmeny for bestillinger? '
home:
hubs:
show_closed_shops: "Vis stengte butikker"
@@ -231,10 +491,29 @@ nb:
require_customer_login: "Denne butikken er kun for kunder."
require_login_html: "Vennligst %{login} hvis du allerede har en konto. Hvis ikke, %{register} for å bli kunde."
require_customer_html: "Vennligst %{contact} %{enterprise} for å bli kunde."
invoice_column_item: "Vare"
invoice_column_qty: "Mengde"
invoice_billing_address: "Fakturaadresse:"
invoice_column_tax: "MVA"
invoice_column_price: "Pris"
invoice_column_item: "Vare"
invoice_column_qty: "Mengde"
invoice_column_unit_price_with_taxes: "Enhetspris (Inkl. avgift)"
invoice_column_unit_price_without_taxes: "Enhetspris (Eks. avgift)"
invoice_column_price_with_taxes: "Totalpris (Inkl. avgift)"
invoice_column_price_without_taxes: "Totalpris (Eks. avgift)"
invoice_column_tax_rate: "Avgiftsrate"
tax_invoice: "AVGIFTSFAKTURA"
tax_total: "Totalavgift (%{rate}):"
total_excl_tax: "Sum (Eks. avgift):"
total_incl_tax: "Sum (Inkl. avgift):"
abn: "ORG nr.:"
acn: "Org nr:"
invoice_issued_on: "Fakturadato:"
order_number: "Fakturanummer"
date_of_transaction: "Transaksjonsdato:"
ticket_column_qty: "Antall"
ticket_column_item: "Vare"
ticket_column_unit_price: "Enhetspris"
ticket_column_total_price: "Totalpris"
logo: "Logo (640x130)"
logo_mobile: "Mobil logo (75x26)"
logo_mobile_svg: "Mobil logo (SVG)"
@@ -258,10 +537,13 @@ nb:
phone: Telefon
next: Neste
address: Adresse
address_placeholder: f.eks. 123 High Street
address2: Adresse (forts.)
city: Kommune
state: Fylke
city_placeholder: f.eks. Northcote
postcode: Postnummer
postcode_placeholder: f.eks. 3070
state: Fylke
country: Land
unauthorized: Uautorisert
terms_of_service: "Vilkår"
@@ -394,6 +676,7 @@ nb:
order_includes_tax: (inkludert MVA)
order_payment_paypal_successful: Din betaling via PayPal har blitt godkjent.
order_hub_info: Hub info
bom_tip: "Bruk denne siden for å endre produktmengder på tvers av flere bestillinger. Produkter kan også fjernes fra bestillinger helt hvis påkrevd."
unsaved_changes_warning: "Ulagrede endringer finnes og vil gå tapt hvis du fortsetter."
unsaved_changes_error: "Felt med røde kanter inneholder feil."
products: "Produkter"
@@ -479,6 +762,7 @@ nb:
hubs_filter_by: "Filtrer"
hubs_filter_type: "Type"
hubs_filter_delivery: "Levering"
hubs_filter_property: "Egenskap"
hubs_matches: "Mente du?"
hubs_intro: Handle lokalt
hubs_distance: Nærmest
@@ -903,7 +1187,7 @@ nb:
spree_admin_single_enterprise_alert_mail_confirmation: "Vennligst bekreft epostadressen for"
spree_admin_single_enterprise_alert_mail_sent: "Vi har sendt epost til"
spree_admin_overview_action_required: "Handling Nødvendig"
spree_admin_overview_check_your_inbox: "Vennligst sjekk innboksen din for nærmere instruksjoner. Takk!"
spree_admin_overview_check_your_inbox: "Vennligst sjekk din innboks for nærmere instruksjoner. Takk!"
change_package: "Endre Pakke"
spree_admin_single_enterprise_hint: "Hint: For å hjelpe folk til å finne deg, skru på din synlighet under"
your_profil_live: "Din profil live"
@@ -920,6 +1204,12 @@ nb:
edit_profile_details_etc: "Endre din profilbeskrivelse, bilder, osv."
order_cycle: "Bestillingsrunde"
remove_tax: "Fjern avgift"
enterprise_terms_of_service: "Tjenestevilkår for Bedrifter"
enterprises_require_tos: "Bedrifter må godta Tjenestevilkår"
enterprise_tos_link: "Lenke til Tjenestevilkår for Bedrifter"
enterprise_tos_message: "Vi ønsker å jobbe med bedrifter som deler våre mål og verdier. Derfor ber vi nye bedrifter om å godta vår"
enterprise_tos_link_text: "Tjenestevilkår."
enterprise_tos_agree: "Jeg godtar Tjenestevilkårene over"
tax_settings: "Avgiftsinnstillinger"
products_require_tax_category: "produkter krever avgiftskategori"
admin_shared_address_1: "Adresse"
@@ -953,6 +1243,22 @@ nb:
report_order_cycle: "Bestillingsrunde:"
report_entreprises: "Bedrifter:"
report_users: "Brukere:"
report_tax_rates: "Avgiftsrater"
report_tax_types: "Avgiftstyper"
report_header_order_number: "Bestillingsnummer"
report_header_date: "Dato"
report_header_items: "Varer"
report_header_items_total: "Sum varer %{currency_symbol}"
report_header_taxable_items_total: "Sum varer som krever avgift (%{currency_symbol})"
report_header_sales_tax: "Salgsavgift (%{currency_symbol})"
report_header_delivery_charge: "Leveringsavgift (%{currency_symbol})"
report_header_tax_on_delivery: "Leveringsavgift (%{currency_symbol})"
report_header_tax_on_fees: "Avgiftsavgift (%{currency_symbol})"
report_header_total_tax: "Sum avgifter (%{currency_symbol})"
report_header_customer: "Kunde"
report_header_distributor: "Distributør"
report_header_total_excl_vat: "Sum eks. avgift (%{currency_symbol})"
report_header_total_incl_vat: "Sum inkl. avgift (%{currency_symbol})"
initial_invoice_number: "Første fakturanummer:"
invoice_date: "Fakturadato:"
due_date: "Tidsfrist:"
@@ -994,7 +1300,145 @@ nb:
validation_msg_product_category_cant_be_blank: "^Produktkategori kan ikke være blank"
validation_msg_tax_category_cant_be_blank: "^Avgiftskategori kan ikke være blank"
validation_msg_is_associated_with_an_exising_customer: "er assosiert med en eksisterende kunde"
js:
admin:
modals:
got_it: Har det
tag_rule_help:
title: Regler for Merkelapper
overview: Oversikt
overview_text: >
Merkelappregler gir en måte å forklare hvilke elementer som er synlige
eller ikke til hvilke kunder. Elementer kan være Leveringsmetoder, Betalingsmetoder,
Produkter og Bestillingsrunder.
by_default_rules: "'Standard...' Regler"
by_default_rules_text: >
Standardregler lar deg skjule elementer slik at de ikke er synlige som
standard. Denne oppførselen kan overskrives av ikke-standard regler
for kunder med spesielle merkelapper.
customer_tagged_rules: "Regler for 'Kunder merket...'"
customer_tagged_rules_text: >
Ved å opprette regler relatert til en spesifikk kundemerkelapp, kan
du overstyre standard oppførsel (enten det er for å vise eller skjule
elementer) for kunder med den spesifikke merkelappen.
panels:
save: LAGRE
saved: LAGRET
saving: LAGRER
enterprise_package:
hub_profile: Hubprofil
hub_profile_cost: "KOSTNAD: GRATIS FOR ALLTID"
hub_profile_text1: >
Personer kan finner og kontakte deg på Open Food Network. Din bedrift
vil være synlig på kartet og vil være søkbar i oppføringer.
hub_profile_text2: >
Å ha en profil og å knytte seg til ditt lokale matsystem gjennom Open
Food Network vil alltid være gratis.
hub_shop: Hub butikk
hub_shop_text1: >
Din bedrift er ryggraden i ditt lokale matsystem. Du samler produkter
fra andre bedrifter og kan selge det gjennom din butikk på Open Food
Network.
hub_shop_text2: >
Hubs kan ha mange former, enten de er et matsamvirke, en kjøpegruppe,
et grønnsakskasse program, eller en lokal dagligvare.
hub_shop_text3: >
Hvis du også ønsker å selge egne produkter må du endre denne bedriften
til å bli en produsent.
choose_package: Vennligst velg en pakke
choose_package_text1: >
Din bedrift vil ikke bli fullt aktivert før en pakke er valgt fra alternativene
til venstre.
choose_package_text2: >
Klikk på et valg for å se mer detaljert informasjon om hver pakke, og
klikk på den røde LAGRE-knappen når du er ferdig!
profile_only: Kun Profil
profile_only_cost: "KOSTNAD: GRATIS FOR ALLTID"
profile_only_text1: >
En profil gjør deg synlig og mulig å kontakte for andre og er en måte
å dele din historie på.
profile_only_text2: >
Hvis du foretrekker å fokusere på å produsere mat, og ønsker å overlate
jobben med å selge den til noen andre, trenger du ikke en butikk på
Open Food Network.
profile_only_text3: >
Legg dine produkter til Open Food Network, noe som muliggjør hubs å
lagre dine produkter i sine butikker.
producer_shop: Produsentbutikk
producer_shop_text1: >
Selg dine produkter direkte til kunder gjennom din helt egne Open Food
Network nettbutikk.
producer_shop_text2: >
En Produsentbutikk er kun for dine produkter, hvis du ønsker å selge
produkter dyrket/produsert av andre, vennligst velg 'Produsenthub'.
producer_hub: Produsenthub
producer_hub_text1: >
Din bedrift er ryggraden i ditt lokale matsystem. Du kan selge dine
egne produkter og produkter samlet fra andre bedrifter gjennom din nettbutikk
på Open Food Network.
producer_hub_text2: >
Produsenthubs kan ha mange former, enten de er et andelslandbruk, grønnsaksboks
program, eller et matsamvirke med en takhage.
producer_hub_text3: >
Open Food Network har som mål å støtte så mange hub-modeller som mulig,
så uansett hvilken situasjon du er i ønsker vi å gi deg de verktøy du
trenger for å kjøre din organisasjon eller lokale matforretning.
get_listing: Bli registrert
always_free: GRATIS FOR ALLTID
sell_produce_others: Selg produkter fra andre
sell_own_produce: Selg dine egne produkter
sell_both: Selg egne produkter og produkter fra andre
enterprise_producer:
producer: Produsent
producer_text1: >
Produsenter lager masse god mat og drikke. Du er en produsent hvis du
dyrker, driver med husdyrhold, brygger, baker, fermenterer, melker eller
former.
producer_text2: >
Produsenter kan også utføre andre funksjoner, slik som å samle mat fra
andre bedrifter og selge det gjennom en butikk på Open Food Network.
non_producer: Ikke-produsent
non_producer_text1: >
Ikke-produsenter produserer ikke noe mat selv, noe som gjør at de ikke
kan opprette egne produkter for salg gjennom Open Food Network.
non_producer_text2: >
I stedet, ikke-produsenter spesialiserer seg i å koble produsenter til
sluttbrukeren, enten det er ved å samle, vurdere, pakke, selge eller
levere mat.
producer_desc: Matprodusenter
producer_example: f.eks. DYRKERE, BAKERE, BRYGGERE, SKAPERE
non_producer_desc: Alle andre matbedrifter
non_producer_example: f.eks. Dagligvare, Matsamvirker, Kjøpegrupper
enterprise_status:
status_title: "%{name} er satt opp og klar!"
severity: Alvorlighetsgrad
description: Beskrivelse
resolve: Løse
new_tag_rule_dialog:
select_rule_type: "Velg en regeltype:"
out_of_stock:
reduced_stock_available: Redusert lager tilgjengelig
out_of_stock_text: >
Samtidig som du har handlet har lagernivået for en eller flere av produktene
i din handlekurv gått ned. Her er hva som er endret.
now_out_of_stock: er nå ikke på lager.
only_n_remainging: "har nå kun %{num} igjen."
spree:
admin:
products:
bulk_edit:
header:
title: Endre produkter i bulk
indicators:
title: LASTER PRODUKTER
no_products: "Ingen produkter ennå. Ønsker du å legge til noen?"
no_results: "Beklager, ingen resultater passer"
variants:
autocomplete:
producer_name: Produsent
date_picker:
format: '%Y-%m-%d'
js_format: 'åå-mm-dd'
zipcode: Postnummer
shipment_states:
backorder: restordre
@@ -1013,6 +1457,10 @@ nb:
processing: behandler
void: ugyldig
invalid: ugyldig
order_mailer:
invoice_email:
hi: "Hei %{name}"
invoice_attached_text: Vennligst finn vedlagt en faktura for din nylige bestilling fra
order_state:
address: adresse
adjustments: justeringer
@@ -1026,3 +1474,6 @@ nb:
resumed: gjenopptatt
returned: returnert
skrill: skrill
orders:
invoice:
tax_invoice: "AVGIFTSFAKTURA:"

View File

@@ -114,6 +114,10 @@ Openfoodnetwork::Application.routes.draw do
get '/inventory', to: 'variant_overrides#index'
get '/product_import', to: 'product_import#index'
post '/product_import', to: 'product_import#import'
post '/product_import/save', to: 'product_import#save', as: 'product_import_save'
resources :variant_overrides do
post :bulk_update, on: :collection
post :bulk_reset, on: :collection
@@ -237,7 +241,6 @@ Spree::Core::Engine.routes.prepend do
namespace :admin do
get '/search/known_users' => "search#known_users", :as => :search_known_users
get '/search/customers' => 'search#customers', :as => :search_customers
resources :products do

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

@@ -94,6 +94,7 @@ feature %q{
within (".side_menu") { click_link "Users" }
select2_search user.email, from: 'Owner'
expect(page).to have_no_selector '.select2-drop-mask' # Ensure select2 has finished
click_link "About"
fill_in 'enterprise_description', :with => 'Connecting farmers and eaters'

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

@@ -0,0 +1,343 @@
require 'spec_helper'
require 'open_food_network/permissions'
feature "Product Import", js: true do
include AuthenticationWorkflow
include WebHelper
let!(:admin) { create(:admin_user) }
let!(:user) { create_enterprise_user }
let!(:enterprise) { create(:supplier_enterprise, owner: user, name: "User Enterprise") }
let!(:enterprise2) { create(:supplier_enterprise, owner: admin, name: "Another Enterprise") }
let!(:category) { create(:taxon, name: 'Vegetables') }
let!(:category2) { create(:taxon, name: 'Cake') }
let!(:tax_category) { create(:tax_category) }
let!(:tax_category2) { create(:tax_category) }
let!(:shipping_category) { create(:shipping_category) }
let!(:product) { create(:simple_product, supplier: enterprise2, name: 'Hypothetical Cake') }
let!(:variant) { create(:variant, product_id: product.id, price: '8.50', on_hand: '100', unit_value: '500', display_name: 'Preexisting Banana') }
let!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans', unit_value: '500') }
let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts') }
let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage') }
let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce') }
describe "when importing products from uploaded file" do
before { quick_login_as_admin }
after { File.delete('/tmp/test.csv') }
it "validates entries and saves them if they are all valid" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
expect(page).to have_content "Select a spreadsheet to upload"
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "0"
expect(page).to have_selector '.create-count', text: "2"
expect(page).to have_selector '.update-count', text: "0"
click_button 'Save'
expect(page).to have_selector '.created-count', text: '2'
expect(page).to have_selector '.updated-count', text: '0'
potatoes = Spree::Product.find_by_name('Potatoes')
potatoes.supplier.should == enterprise
potatoes.on_hand.should == 6
potatoes.price.should == 6.50
end
it "displays info about invalid entries but still allows saving of valid entries" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["Good Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
csv << ["Bad Potatoes", "", "Vegetables", "6", "6.50", "1000", "", "1000"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
expect(page).to have_content "Select a spreadsheet to upload"
attach_file('file', '/tmp/test.csv')
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "1"
expect(page).to have_selector '.create-count', text: "1"
expect(page).to have_selector '.update-count', text: "0"
expect(page).to have_selector 'input[type=submit][value="Save"]'
click_button 'Save'
expect(page).to have_selector '.created-count', text: '1'
expect(page).to have_selector '.updated-count', text: '0'
Spree::Product.find_by_name('Bad Potatoes').should == nil
carrots = Spree::Product.find_by_name('Good Carrots')
carrots.supplier.should == enterprise
carrots.on_hand.should == 5
carrots.price.should == 3.20
end
it "displays info about invalid entries but no save button if all items are invalid" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["Bad Carrots", "Unkown Enterprise", "Mouldy vegetables", "666", "3.20", "", "weight", ""]
csv << ["Bad Potatoes", "", "Vegetables", "6", "6", "6", "", "1000"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
expect(page).to have_content "Select a spreadsheet to upload"
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "2"
expect(page).to have_selector '.create-count', text: "0"
expect(page).to have_selector '.update-count', text: "0"
expect(page).to_not have_selector 'input[type=submit][value="Save"]'
end
it "can add new variants to existing products and update price and stock level of existing products" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "display_name"]
csv << ["Hypothetical Cake", "Another Enterprise", "Cake", "5", "5.50", "500", "weight", "1", "Preexisting Banana"]
csv << ["Hypothetical Cake", "Another Enterprise", "Cake", "6", "3.50", "500", "weight", "1", "Emergent Coffee"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "0"
expect(page).to have_selector '.create-count', text: "1"
expect(page).to have_selector '.update-count', text: "1"
click_button 'Save'
expect(page).to have_selector '.created-count', text: '1'
expect(page).to have_selector '.updated-count', text: '1'
added_coffee = Spree::Variant.find_by_display_name('Emergent Coffee')
added_coffee.product.name.should == 'Hypothetical Cake'
added_coffee.price.should == 3.50
added_coffee.on_hand.should == 6
updated_banana = Spree::Variant.find_by_display_name('Preexisting Banana')
updated_banana.product.name.should == 'Hypothetical Cake'
updated_banana.price.should == 5.50
updated_banana.on_hand.should == 5
end
it "can add a new product and sub-variants of that product at the same time" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "display_name"]
csv << ["Potatoes", "User Enterprise", "Vegetables", "5", "3.50", "500", "weight", "1000", "Small Bag"]
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "5.50", "2000", "weight", "1000", "Big Bag"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "0"
expect(page).to have_selector '.create-count', text: "2"
expect(page).to have_selector '.update-count', text: "0"
click_button 'Save'
expect(page).to have_selector '.created-count', text: '2'
expect(page).to have_selector '.updated-count', text: '0'
small_bag = Spree::Variant.find_by_display_name('Small Bag')
small_bag.product.name.should == 'Potatoes'
small_bag.price.should == 3.50
small_bag.on_hand.should == 5
big_bag = Spree::Variant.find_by_display_name('Big Bag')
big_bag.product.name.should == 'Potatoes'
big_bag.price.should == 5.50
big_bag.on_hand.should == 6
end
end
describe "when dealing with uploaded files" do
before { quick_login_as_admin }
it "checks filetype on upload" do
File.write('/tmp/test.txt', "Wrong filetype!")
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.txt'
click_button 'Import'
expect(page).to have_content "Importer could not process file: invalid filetype"
expect(page).to_not have_selector 'input[type=submit][value="Save"]'
expect(page).to have_content "Select a spreadsheet to upload"
File.delete('/tmp/test.txt')
end
it "returns and error if nothing was uploaded" do
visit main_app.admin_product_import_path
click_button 'Import'
expect(page).to have_content "File not found or could not be opened"
end
it "handles cases where no meaningful data can be read from the file" do
File.write('/tmp/test.csv', "A22££S\\\\\n**VA,,,AF..D")
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.create-count', text: "0"
expect(page).to have_selector '.update-count', text: "0"
expect(page).to_not have_selector 'input[type=submit][value="Save"]'
File.delete('/tmp/test.csv')
end
end
describe "handling enterprise permissions" do
before { quick_login_as user }
it "only allows import into enterprises the user is permitted to manage" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["My Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
csv << ["Your Potatoes", "Another Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "1"
expect(page).to have_selector '.create-count', text: "1"
expect(page).to have_selector '.update-count', text: "0"
expect(page.body).to have_content 'you do not have permission'
click_button 'Save'
expect(page).to have_selector '.created-count', text: '1'
expect(page).to have_selector '.updated-count', text: '0'
Spree::Product.find_by_name('My Carrots').should be_a Spree::Product
Spree::Product.find_by_name('Your Potatoes').should == nil
end
end
describe "applying settings and defaults on import" do
before { quick_login_as_admin }
it "can set all products for an enterprise that are not present in the uploaded file to zero stock" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
csv << ["Beans", "User Enterprise", "Vegetables", "6", "6.50", "500", "weight", "1"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
expect(page).to have_selector '.item-count', text: "2"
expect(page).to have_selector '.invalid-count', text: "0"
expect(page).to have_selector '.create-count', text: "1"
expect(page).to have_selector '.update-count', text: "1"
expect(page).to_not have_selector '.reset-count'
within 'div.import-settings' do
find('div.header-description').click # Import settings tab
check "settings_#{enterprise.id}_reset_all_absent"
end
expect(page).to have_selector '.reset-count', text: "2"
click_button 'Save'
expect(page).to have_selector '.created-count', text: '1'
expect(page).to have_selector '.updated-count', text: '1'
expect(page).to have_selector '.reset-count', text: '2'
Spree::Product.find_by_name('Carrots').on_hand.should == 5 # Present in file, added
Spree::Product.find_by_name('Beans').on_hand.should == 6 # Present in file, updated
Spree::Product.find_by_name('Sprouts').on_hand.should == 0 # In enterprise, not in file
Spree::Product.find_by_name('Cabbage').on_hand.should == 0 # In enterprise, not in file
Spree::Product.find_by_name('Lettuce').on_hand.should == 100 # In different enterprise; unchanged
end
it "overwrites fields with selected defaults" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "tax_category_id", "available_on"]
csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1", tax_category.id, ""]
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000", "", ""]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
within 'div.import-settings' do
find('div.header-description').click # Import settings tab
expect(page).to have_selector "#settings_#{enterprise.id}_defaults_on_hand_mode", visible: false
# Overwrite stock level of all items to 9000
select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_on_hand_mode", visible: false
fill_in "settings_#{enterprise.id}_defaults_on_hand_value", with: '9000'
# Overwrite default tax category, but only where field is empty
select 'Overwrite if empty', from: "settings_#{enterprise.id}_defaults_tax_category_id_mode", visible: false
select tax_category2.name, from: "settings_#{enterprise.id}_defaults_tax_category_id_value", visible: false
# Set default shipping category (field not present in file)
select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_shipping_category_id_mode", visible: false
select shipping_category.name, from: "settings_#{enterprise.id}_defaults_shipping_category_id_value", visible: false
# Set available_on date
select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_available_on_mode", visible: false
find("input#settings_#{enterprise.id}_defaults_available_on_value").set '2020-01-01'
end
click_button 'Save'
expect(page).to have_selector '.created-count', text: '2'
expect(page).to have_selector '.updated-count', text: '0'
carrots = Spree::Product.find_by_name('Carrots')
carrots.on_hand.should == 9000
carrots.tax_category_id.should == tax_category.id
carrots.shipping_category_id.should == shipping_category.id
carrots.available_on.should be_within(1.day).of(Time.zone.local(2020, 1, 1))
potatoes = Spree::Product.find_by_name('Potatoes')
potatoes.on_hand.should == 9000
potatoes.tax_category_id.should == tax_category2.id
potatoes.shipping_category_id.should == shipping_category.id
potatoes.available_on.should be_within(1.day).of(Time.zone.local(2020, 1, 1))
end
end
end

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

@@ -1,6 +1,5 @@
require 'spec_helper'
feature "As a consumer I want to check out my cart", js: true, retry: 3 do
include AuthenticationWorkflow
include ShopWorkflow
@@ -31,7 +30,7 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
let(:sm2) { create(:shipping_method, require_ship_address: false, name: "Donkeys", description: "blue", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 4.56)) }
let(:sm3) { create(:shipping_method, require_ship_address: false, name: "Local", tag_list: "local") }
let!(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::PaymentMethod::Check") }
let!(:pm2) { create(:payment_method, distributors: [distributor]) }
let!(:pm2) { create(:payment_method, distributors: [distributor], calculator: Spree::Calculator::FlatRate.new(preferred_amount: 5.67)) }
let!(:pm3) do
Spree::Gateway::PayPalExpress.create!(name: "Paypal", environment: 'test', distributor_ids: [distributor.id]).tap do |pm|
pm.preferred_login = 'devnull-facilitator_api1.rohanmitchell.com'
@@ -40,6 +39,7 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
end
end
before do
distributor.shipping_methods << sm1
distributor.shipping_methods << sm2
@@ -328,37 +328,63 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
end
end
context "with a credit card payment method" do
let!(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::Gateway::Bogus") }
context "when we are charged a payment method fee (transaction fee)" do
it "creates a payment including the transaction fee" do
# Selecting the transaction fee, it is displayed
expect(page).to have_selector ".transaction-fee td", text: "$0.00"
expect(page).to have_selector ".total", text: "$11.23"
it "takes us to the order confirmation page when submitted with a valid credit card" do
toggle_payment
fill_in 'Card Number', with: "4111111111111111"
select 'February', from: 'secrets.card_month'
select (Date.current.year+1).to_s, from: 'secrets.card_year'
fill_in 'Security Code', with: '123'
choose "#{pm2.name} ($5.67)"
expect(page).to have_selector ".transaction-fee td", text: "$5.67"
expect(page).to have_selector ".total", text: "$16.90"
place_order
page.should have_content "Your order has been processed successfully"
expect(page).to have_content "Your order has been processed successfully"
# Order should have a payment with the correct amount
# There are two orders - our order and our new cart
o = Spree::Order.complete.first
o.payments.first.amount.should == 11.23
expect(o.adjustments.payment_fee.first.amount).to eq 5.67
expect(o.payments.first.amount).to eq(10 + 1.23 + 5.67) # items + fees + transaction
end
end
it "shows the payment processing failed message when submitted with an invalid credit card" do
toggle_payment
fill_in 'Card Number', with: "9999999988887777"
select 'February', from: 'secrets.card_month'
select (Date.current.year+1).to_s, from: 'secrets.card_year'
fill_in 'Security Code', with: '123'
describe "credit card payments" do
["Spree::Gateway::Bogus", "Spree::Gateway::BogusSimple"].each do |gateway_type|
context "with a credit card payment method using #{gateway_type}" do
let!(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: gateway_type) }
place_order
page.should have_content "Payment could not be processed, please check the details you entered"
it "takes us to the order confirmation page when submitted with a valid credit card" do
toggle_payment
fill_in 'Card Number', with: "4111111111111111"
select 'February', from: 'secrets.card_month'
select (Date.current.year+1).to_s, from: 'secrets.card_year'
fill_in 'Security Code', with: '123'
# Does not show duplicate shipping fee
visit checkout_path
page.should have_selector "th", text: "Shipping", count: 1
place_order
page.should have_content "Your order has been processed successfully"
# Order should have a payment with the correct amount
o = Spree::Order.complete.first
o.payments.first.amount.should == 11.23
end
it "shows the payment processing failed message when submitted with an invalid credit card" do
toggle_payment
fill_in 'Card Number', with: "9999999988887777"
select 'February', from: 'secrets.card_month'
select (Date.current.year+1).to_s, from: 'secrets.card_year'
fill_in 'Security Code', with: '123'
place_order
page.should have_content "Payment could not be processed, please check the details you entered"
# Does not show duplicate shipping fee
visit checkout_path
page.should have_selector "th", text: "Shipping", count: 1
end
end
end
end
end

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

@@ -0,0 +1,33 @@
require 'spec_helper'
require 'open_food_network/permissions'
describe ProductImporter do
include AuthenticationWorkflow
let!(:admin) { create(:admin_user) }
let!(:user) { create_enterprise_user }
let!(:enterprise) { create(:enterprise, owner: user, name: "Test Enterprise") }
let!(:category) { create(:taxon, name: 'Vegetables') }
let(:permissions) { OpenFoodNetwork::Permissions.new(user) }
describe "importing products from a spreadsheet" do
after { File.delete('/tmp/test-m.csv') }
it "validates the entries" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["Carrots", "Test Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
csv << ["Potatoes", "Test Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000"]
end
File.write('/tmp/test-m.csv', csv_data)
file = File.new('/tmp/test-m.csv')
importer = ProductImporter.new(file, permissions.editable_enterprises)
expect(importer.valid_count).to eq(2)
expect(importer.invalid_count).to eq(0)
end
end
# Test handling of filetypes
end

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)