mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Compare commits
67 Commits
v1.8.8-rc1
...
v1.8.10-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c72d17dc83 | ||
|
|
78ffdec693 | ||
|
|
49c19a1d6a | ||
|
|
e854eb0426 | ||
|
|
4a9c17cb28 | ||
|
|
0d1547f439 | ||
|
|
fa5ed529cb | ||
|
|
accb3076e9 | ||
|
|
a4e4e1ec68 | ||
|
|
4809237ecc | ||
|
|
81877fedb6 | ||
|
|
1f2c6f2a85 | ||
|
|
4fe5e60967 | ||
|
|
f4eb9cb790 | ||
|
|
775f9b3ada | ||
|
|
188b33921c | ||
|
|
20c033317f | ||
|
|
e7a5d063ac | ||
|
|
1fda781d7e | ||
|
|
45fc801a08 | ||
|
|
21337a5b50 | ||
|
|
ec36a843cf | ||
|
|
e99dbaf4d8 | ||
|
|
c83ad2ecc4 | ||
|
|
80d8d18eb2 | ||
|
|
903d1afb53 | ||
|
|
cd55d2e2ff | ||
|
|
05cf8c4351 | ||
|
|
b04d815408 | ||
|
|
7b370a2eb6 | ||
|
|
5808b601b8 | ||
|
|
fdcd3dc3e3 | ||
|
|
c4bd085393 | ||
|
|
0e91d01412 | ||
|
|
fcb9e9fa56 | ||
|
|
3591354cb1 | ||
|
|
b38eab11eb | ||
|
|
c43dea60b7 | ||
|
|
268bea25d0 | ||
|
|
e94ae20b31 | ||
|
|
a94961c0a7 | ||
|
|
0d5fde919b | ||
|
|
429ef4e2ba | ||
|
|
e8999d23e1 | ||
|
|
209c9242d9 | ||
|
|
6defb09d2e | ||
|
|
2774c09d7a | ||
|
|
c62a044598 | ||
|
|
f73fbe7f23 | ||
|
|
5e1e4c1d19 | ||
|
|
cc5a335fb7 | ||
|
|
91fc3f33a0 | ||
|
|
648753b412 | ||
|
|
24fcc3dd34 | ||
|
|
14fb40a996 | ||
|
|
f4511fc74d | ||
|
|
3d0f192490 | ||
|
|
6b7cdf3a37 | ||
|
|
052d6c6b02 | ||
|
|
6e5c878491 | ||
|
|
c0c6cd1a60 | ||
|
|
2ad433590d | ||
|
|
2cb3da56ab | ||
|
|
8582e6d6b4 | ||
|
|
170101cbfe | ||
|
|
8107f49373 | ||
|
|
f235099859 |
1
Gemfile
1
Gemfile
@@ -63,6 +63,7 @@ gem 'wkhtmltopdf-binary'
|
||||
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
gem 'roo', '~> 2.7.0'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
|
||||
10
Gemfile.lock
10
Gemfile.lock
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("ofn.admin").controller "DropdownPanelsCtrl", ($scope) ->
|
||||
$scope.active = false
|
||||
|
||||
$scope.togglePanel = ->
|
||||
$scope.active = !$scope.active
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.products", ["admin.utils"])
|
||||
angular.module("admin.products", ["textAngular", "admin.utils"])
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("admin.utils").directive "textangularStrip", () ->
|
||||
restrict: 'CA'
|
||||
link: (scope, element, attrs) ->
|
||||
scope.stripFormatting = ($html) ->
|
||||
return String($html).replace(/<[^>]+>/gm, '')
|
||||
@@ -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 != ''
|
||||
|
||||
|
||||
@@ -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 || " " }}
|
||||
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage !== ''" }, style: 'color: #da5354' }
|
||||
{{ StatusMessage.invalidMessage || " " }}
|
||||
.eight.columns.omega.text-right{ ng: { transclude: true } }
|
||||
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
225
app/assets/stylesheets/admin/product_import.css.scss
Normal file
225
app/assets/stylesheets/admin/product_import.css.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
62
app/controllers/admin/product_import_controller.rb
Normal file
62
app/controllers/admin/product_import_controller.rb
Normal 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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
464
app/models/product_importer.rb
Normal file
464
app/models/product_importer.rb
Normal 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
|
||||
68
app/models/spreadsheet_entry.rb
Normal file
68
app/models/spreadsheet_entry.rb
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ module Spree
|
||||
adjustment.save
|
||||
else
|
||||
payment_method.create_adjustment(adjustment_label, order, self, true)
|
||||
reload
|
||||
association(:adjustment).reload
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
add_to_attributes 'fieldset.no-border-top'
|
||||
attributes 'ng-app' => 'admin.products'
|
||||
@@ -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" }
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
15
app/views/admin/product_import/_entries_table.html.haml
Normal file
15
app/views/admin/product_import/_entries_table.html.haml
Normal 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
|
||||
11
app/views/admin/product_import/_errors_list.html.haml
Normal file
11
app/views/admin/product_import/_errors_list.html.haml
Normal 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
|
||||
- #{error}
|
||||
49
app/views/admin/product_import/_import_options.html.haml
Normal file
49
app/views/admin/product_import/_import_options.html.haml
Normal 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
|
||||
83
app/views/admin/product_import/_import_review.html.haml
Normal file
83
app/views/admin/product_import/_import_review.html.haml
Normal 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
|
||||
35
app/views/admin/product_import/_options_form.html.haml
Normal file
35
app/views/admin/product_import/_options_form.html.haml
Normal 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'}
|
||||
9
app/views/admin/product_import/_upload_form.html.haml
Normal file
9
app/views/admin/product_import/_upload_form.html.haml
Normal 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
|
||||
34
app/views/admin/product_import/import.html.haml
Normal file
34
app/views/admin/product_import/import.html.haml
Normal 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
|
||||
|
||||
|
||||
6
app/views/admin/product_import/index.html.haml
Normal file
6
app/views/admin/product_import/index.html.haml
Normal file
@@ -0,0 +1,6 @@
|
||||
- content_for :page_title do
|
||||
Product Import
|
||||
|
||||
= render :partial => 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= render 'upload_form'
|
||||
38
app/views/admin/product_import/save.html.haml
Normal file
38
app/views/admin/product_import/save.html.haml
Normal 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
|
||||
- #{error}
|
||||
|
||||
%br
|
||||
%a.button{href: main_app.admin_product_import_path} Back
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
= render partial: "registration/steps/limit_reached"
|
||||
|
||||
/ Directive which loads the modal
|
||||
%div{ "ofn-registration-limit-modal" => true }
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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 på 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:"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -14,4 +14,4 @@ echo "--- Bundling"
|
||||
bundle install
|
||||
|
||||
echo "--- Running tests"
|
||||
bundle exec rake karma:run
|
||||
./script/karma run
|
||||
|
||||
13
script/karma
Executable file
13
script/karma
Executable 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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]
|
||||
|
||||
343
spec/features/admin/product_import_spec.rb
Normal file
343
spec/features/admin/product_import_spec.rb
Normal 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
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
60
spec/features/consumer/shopping/products_spec.rb
Normal file
60
spec/features/consumer/shopping/products_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
33
spec/models/product_importer_spec.rb
Normal file
33
spec/models/product_importer_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user