mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Compare commits
50 Commits
v1.8.8-rc1
...
v1.8.10-tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a5e4bb592 | ||
|
|
507e12adba | ||
|
|
5fa45c0716 | ||
|
|
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -717,6 +721,7 @@ DEPENDENCIES
|
||||
redcarpet
|
||||
representative_view
|
||||
roadie-rails (~> 1.0.3)
|
||||
roo (~> 2.7.0)
|
||||
rspec-rails
|
||||
rspec-retry
|
||||
sass (~> 3.3)
|
||||
|
||||
@@ -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, '')
|
||||
@@ -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
|
||||
|
||||
@@ -200,6 +200,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 +217,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
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -109,6 +109,7 @@ en:
|
||||
columns: Columns
|
||||
actions: Actions
|
||||
viewing: "Viewing: %{current_view_name}"
|
||||
description: Description
|
||||
|
||||
whats_this: What's this?
|
||||
|
||||
@@ -223,6 +224,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."
|
||||
|
||||
@@ -503,9 +507,6 @@ en:
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -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:"
|
||||
|
||||
@@ -11,7 +11,7 @@ sv:
|
||||
Var du gäst senast? Du behöver kanske skapa ett konto eller återställa ditt lösenord.
|
||||
enterprise_confirmations:
|
||||
enterprise:
|
||||
confirmed: Tack, din e-postadress har bekräftats.
|
||||
confirmed: Tack, din e-postadress har blivit bekräftad.
|
||||
not_confirmed: Din e-postadress kunde inte bekräftas. Du kanske redan har genomfört det här steget?
|
||||
confirmation_sent: "Bekräftelsemail skickat!"
|
||||
confirmation_not_sent: "Kunde ej skicka bekräftelsemail."
|
||||
@@ -28,6 +28,8 @@ sv:
|
||||
producers_join: Svenska producenter kan nu gå med i Open Food Network. Välkomna!
|
||||
charges_sales_tax: Debiterar Moms?
|
||||
print_invoice: "Skriv ut faktura"
|
||||
print_ticket: "Skriv ut kvitto"
|
||||
select_ticket_printer: "Välj skrivare för bijetterna"
|
||||
send_invoice: "Skicka faktura"
|
||||
resend_confirmation: "Skicka bekräftelse igen"
|
||||
view_order: "Se beställning"
|
||||
@@ -49,6 +51,17 @@ sv:
|
||||
say_yes: "Ja"
|
||||
then: då
|
||||
sort_order_cycles_on_shopfront_by: "Sortera Cykler Av Ordrar På Skyltfönster Efter"
|
||||
required_fields: Fält som måste fyllas i är märkta med en asterisk
|
||||
select_continue: Välj och fortsätt
|
||||
remove: Ta bort
|
||||
or: eller
|
||||
collapse_all: Minimera alla
|
||||
expand_all: Expandera alla
|
||||
loading: Laddning...
|
||||
show_more: Visa mer
|
||||
show_all: Visa alla
|
||||
show_all_with_more: "Visa alla (%{num} More)"
|
||||
cancel: Makulera
|
||||
admin:
|
||||
date: Datum
|
||||
email: epost
|
||||
@@ -73,55 +86,58 @@ sv:
|
||||
columns: Kolumner
|
||||
actions: Handlingar
|
||||
viewing: "Tittar på: %{current_view_name}"
|
||||
description: Beskrivning
|
||||
whats_this: Vad är detta?
|
||||
tag_has_rules: "Regler för denna tagg: %{num}"
|
||||
has_one_rule: "har en regel"
|
||||
has_n_rules: "har %{num} regler"
|
||||
unsaved_confirm_leave: "Det finns ändringar på denna sida som ej sparats. Fortsätta utan att spara?"
|
||||
unsaved_changes: "Du har ändringar som ej sparats"
|
||||
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: "Standardkonto för bealningsmetod"
|
||||
default_accounts_shipping_method: "Standardkonto för leveransmetod"
|
||||
edit:
|
||||
accounts_and_billing: "Konton och fakturering"
|
||||
accounts_administration_distributor: "distributör av bokföringsadministration"
|
||||
accounts_administration_distributor: "Kontoadministrations-distributör"
|
||||
admin_settings: "Inställningar"
|
||||
update_invoice: "Uppdatera fakturor"
|
||||
finalise_invoice: "Slutför fakturor"
|
||||
finalise_user_invoices: "Slutför användarafakturor"
|
||||
finalise_user_invoice_explained: "Använd den här knappen för att slutföra alla fakturor i systemet för den gångna månaden. Denna uppgift ställas in så att den utförs automatiskt en gång per månad."
|
||||
manually_run_task: "Manuellt utförda uppgifter"
|
||||
auto_update_invoices: "Automatisk uppdatering av fakturor varje natt kl 01:00"
|
||||
finalise_invoice: "Slutför fakturering"
|
||||
auto_finalise_invoices: "Slutför automatiskt fakturor månadsvis den 2:a dagen kl 01;30"
|
||||
manually_run_task: "Manuellt utförd bearbetning"
|
||||
update_user_invoice_explained: "Använd denna knapp för att omedelbart uppdatera denna månads fakturor för varje företag i systemet. Denna bearbetning kan köras automatiskt varje natt."
|
||||
finalise_user_invoices: "Slutför användarfakturor"
|
||||
finalise_user_invoice_explained: "Använd denna knapp för att slutföra alla fakturor i systemet avseende föregående kalendermånad. Det går att ställa in denna bearbetning så att den utförs automatiskt en gång per månad."
|
||||
update_user_invoices: "Uppdatera användarfakturor"
|
||||
update_user_invoice_explained: "Använd denna knapp för att omedelbart uppdatera alla fakturor innevarande månad för alla företag i systemet. Den här uppgiften kan ställas in så att den automatiskt utförs varje natt."
|
||||
auto_finalise_invoices: "Automatisk summering av fakturor den 2:a kl 01.30 "
|
||||
auto_update_invoices: "Automatisk uppdatering av fakturor varje natt kl 01.00"
|
||||
business_model_configuration:
|
||||
edit:
|
||||
business_model_configuration: "Konfigurera affärsmodell"
|
||||
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: "Försökstiden är (dagar)"
|
||||
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: "Företagsmodell"
|
||||
business_model_configuration_tip: "Beräkna det belopp som varje affär skall betala varje månad för användningen av OFN. "
|
||||
bill_calculation_settings: "Inställningar för beräkning av fakturor"
|
||||
bill_calculation_settings_tip: "Justera det belopp företag skall faktureras varje månad för användning av OFN."
|
||||
shop_trial_length: "Försöksperiod (Dagar)"
|
||||
shop_trial_length_tip: "Antal dagar som företag som definierats som affärer kan ha som försöksperiod."
|
||||
fixed_monthly_charge: "Bestämd månadsavgift"
|
||||
fixed_monthly_charge_tip: "En bestämd månadsavgift för alla företag som är definierade som affärer och som överskridit minsta fakturerade omsättningen (om sådan bestämts)."
|
||||
percentage_of_turnover: "Procent av omsättning"
|
||||
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ånatligt tak (exkl. moms)"
|
||||
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ånadskostnad (Inkl. Skatter)"
|
||||
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 den är större än noll, denna taxa (0,0 - 1,0) kommer att läggas till den totala omsättningen för varje affär och adderas till varje bestämd avgift (till vänster) vid beräkning av månadsavgiften."
|
||||
monthly_cap_excl_tax: "månadstak (exkl. GST)"
|
||||
monthly_cap_excl_tax_tip: "När det är större än noll, kommer detta värde att användas som tak för det belopp som affärer skall faktureras varje månad."
|
||||
tax_rate: "Skattesats"
|
||||
tax_rate_tip: "Skattesats som används på månadsfakturan som företag skall betala för att använda systemet."
|
||||
minimum_monthly_billable_turnover: "Minsta månatliga fakturerbara omsättning"
|
||||
minimum_monthly_billable_turnover_tip: "Minsta månadsomsättning i en en butiks skyltfönster får betala för att använda OFN. Företag vars omsättning understiger detta belopp under en månad behöver ej betala vare sig procentuellt eller med en fast taxa."
|
||||
example_bill_calculator: "Exempel på beräkning av en faktura"
|
||||
example_bill_calculator_legend: "Ändra omsättningen i exemplet för att åskådliggöra effekten av ändrade inställningar till vänster."
|
||||
example_monthly_turnover: "Exempel på månatlig omsättning"
|
||||
example_monthly_turnover_tip: "Ett exempel på månatlig omsättning för företaget som används för att beräkna en månatlig faktura i exemplet nedan."
|
||||
cap_reached?: "Är taket nått?"
|
||||
cap_reached?_tip: "Visar om taket (specificerat till vänster) har nåtts. med de givna inställningarna och omsättningen."
|
||||
included_tax: "Skatten är inkluderad"
|
||||
included_tax_tip: "All skatt är inkluderad i exemplets månadsfaktura enligt de inställningar och den omsättning som var angiven."
|
||||
total_monthly_bill_incl_tax: "Total månadsfaktura (inkl skatt)"
|
||||
total_monthly_bill_incl_tax_tip: "Exemplets totala månadsfaktura med skatt inkluderad enligt de inställningar och den omsättning som var angiven."
|
||||
customers:
|
||||
index:
|
||||
add_customer: "Lägg till kund"
|
||||
@@ -143,6 +159,28 @@ sv:
|
||||
edit: 'Ändra'
|
||||
update_address: 'Uppdatera Adress'
|
||||
confirm_delete: 'Vill du verkligen radera?'
|
||||
cache_settings:
|
||||
show:
|
||||
title: Inkomst
|
||||
distributor: Distributör
|
||||
order_cycle: Ordercykel
|
||||
status: Status
|
||||
diff: Differens
|
||||
contents:
|
||||
edit:
|
||||
title: Innehåll
|
||||
enterprise_fees:
|
||||
index:
|
||||
title: Företagsavgifter
|
||||
enterprise: Företag
|
||||
fee_type: Avgiftstyp
|
||||
name: Namn
|
||||
tax_category: Skattekategori
|
||||
calculator: Beräkning
|
||||
calculator_values: Beräknade summor
|
||||
enterprise_groups:
|
||||
index:
|
||||
new_button: Ny företagsgrupp
|
||||
products:
|
||||
bulk_edit:
|
||||
unit: Enhet
|
||||
@@ -152,7 +190,14 @@ sv:
|
||||
inherits_properties?: Ärva egenskaper?
|
||||
available_on: 'Tillgänglig '
|
||||
av_on: "Md. På"
|
||||
properties:
|
||||
property_name: Egendomens Namn
|
||||
inherited_property: Ärvd Egendom
|
||||
variants:
|
||||
to_order_tip: "Varor som framställs för en order har inget eget varunummer, ex formbröd som värms vid en order."
|
||||
variant_overrides:
|
||||
loading_flash:
|
||||
loading_inventory: LADDNING AV INVENTARIELISTA
|
||||
index:
|
||||
title: Lager
|
||||
description: Använd den här sidan som inventarieförteckning för ditt företag. Varje produkt som du angett här gäller framför de som du skrivit på "produktsidan"
|
||||
@@ -193,18 +238,95 @@ sv:
|
||||
max_fulfilled_units: "Maximalt expedierade enheter"
|
||||
order_error: "En del misstag måste korrigeras innan du kan uppdatera en order. Alla rödmarkerade fält innehåller fel."
|
||||
variants_without_unit_value: "VARNING: På en del ställen saknas antalsuppgift"
|
||||
order_cycles:
|
||||
edit:
|
||||
choose_products_from: "Välj Produkter Från:"
|
||||
enterprise:
|
||||
select_outgoing_oc_products_from: Välj utgående produkter i beställningsrunda från
|
||||
enterprises:
|
||||
index:
|
||||
producer?: Producent?
|
||||
title: Företag
|
||||
new_enterprise: Nya företag
|
||||
producer?: "Producent?"
|
||||
package: Paket
|
||||
status: Status
|
||||
manage: Hantera
|
||||
form:
|
||||
about_us:
|
||||
desc_short: Kort beskrivning
|
||||
desc_short_placeholder: Berätta om ditt företag i en eller två meningar
|
||||
desc_long: Om oss
|
||||
desc_long_placeholder: Berätta om kunder om dig själv. Denna information kommer att stå i din allmänna profil.
|
||||
business_details:
|
||||
abn: ABN
|
||||
abn_placeholder: ex 070 56 78 90
|
||||
acn: ACN
|
||||
acn_placeholder: ex 123 456 789
|
||||
contact:
|
||||
name: Namn
|
||||
name_placeholder: ex. Gustav Persson
|
||||
email_address: E-postadress
|
||||
email_address_placeholder: ex gustav@telia.com
|
||||
phone: Telefon
|
||||
phone_placeholder: ex 123 456 789
|
||||
website: Hemsida
|
||||
website_placeholder: ex www.shop.com
|
||||
enterprise_fees:
|
||||
name: Namn
|
||||
fee_type: Avgiftstyp
|
||||
manage_fees: Skötsel av företagsavgifter
|
||||
no_fees_yet: Du har inga företagsavgifter ännu.
|
||||
create_button: Skapa en nu
|
||||
images:
|
||||
logo: Logotyp
|
||||
promo_image_placeholder: 'Denna bild visas under "Om oss"'
|
||||
promo_image_note1: 'VAR VÄNLIG OBSERVERA:'
|
||||
promo_image_note2: Varje reklambild som laddas här kommer att beskäras till 1200 x 260.
|
||||
promo_image_note3: Logotypen visas högst upp på företagets profilsida och i rullgardinsmenyer.
|
||||
inventory_settings:
|
||||
text1: Du kan välja att hantera ditt varulager och dina priser via din
|
||||
inventory: Inventarielista
|
||||
text2: >
|
||||
Om använder inventeringsverktyget kan du välja huruvida nya produkter
|
||||
som lagts till av dina leverantörer behöver blir tillagda till din lagerförteckning
|
||||
innan de kan bli lagerförda. Om du inte använder din inventering för
|
||||
att hantera dina produkter bör du välja det 'rekommenderade' alternativet
|
||||
nedan:
|
||||
preferred_product_selection_from_inventory_only_yes: Nya produkter kan placeras i mitt skyltfönster (rekommenderas)
|
||||
preferred_product_selection_from_inventory_only_no: Ny produkter måste adderas till min inventarielista innan de kan placeras i mitt skyltfönster
|
||||
payment_methods:
|
||||
name: Namn
|
||||
applies: Gäller?
|
||||
manage: Bestäm betalningssätt
|
||||
not_method_yet: Du har ännu inte bestämt betalningssätt
|
||||
create_button: Skapa ett nytt betalningssätt
|
||||
create_one_button: Skapa en nu
|
||||
primary_details:
|
||||
name: Namn
|
||||
name_placeholder: t.ex. Professor Plums Biodynamiska Tryfflar
|
||||
groups: Grupper
|
||||
groups_tip: Bestäm i vilka grupper och områden du är medlem. Detta underlättar för kunder att hitta ditt företag.
|
||||
groups_placeholder: Starta din sökning efter tillgängliga grupper...
|
||||
primary_producer: Primär producent?
|
||||
primary_producer_tip: Välj "Producent" om du är en primär producent av livsmedel.
|
||||
producer: Producent
|
||||
any: Vilken som helst
|
||||
none: Ingen
|
||||
own: Egen
|
||||
sells: Säljer
|
||||
sells_tip: "Inget - företag säljer direkt till kunderna.<br />Egna - Företag säljer egna produkter till kunder.<br />Några - Företag kan sälja egna eller andra företags produkter.<br />"
|
||||
visible_in_search: Synlig vid sökning?
|
||||
visible_in_search_tip: Bestämmer om detta företag kommer att bli synligt för kunder då man söker på hemsidan.
|
||||
visible: Synlg
|
||||
not_visible: Ej synlig
|
||||
permalink: Permalänk (inga mellanrum)
|
||||
permalink_tip: "Denna permalänk används för att skapa URL:en till din butik: %{link}din-butiks-namn/butik"
|
||||
link_to_front: Länk till sitt skyltfönster
|
||||
link_to_front_tip: En direkt länk till ditt skyltfönster på OFN
|
||||
shipping_methods:
|
||||
name: Namn
|
||||
applies: Gäller?
|
||||
manage: Bestäm leversmetoder
|
||||
create_button: Skapa ny leveransmetod
|
||||
create_one_button: Skapa en nu
|
||||
no_method_yet: Du har inte bestämt leveransmetod ännu.
|
||||
shop_preferences:
|
||||
shopfront_requires_login: "Offentligt visat skyltfönster?"
|
||||
shopfront_requires_login_tip: "Välj om kunderna måste logga in för att se skyltfönstret eller om det skall visas för alla."
|
||||
@@ -214,6 +336,146 @@ sv:
|
||||
allow_guest_orders_tip: "Tillåt leveranskontroll som gäst eller registrerad kund"
|
||||
allow_guest_orders_false: "Kräv inloggning för att beställa"
|
||||
allow_guest_orders_true: "Tillåt checkout för gäster"
|
||||
shopfront_message_placeholder: >
|
||||
En frivillig förklaring för kunder som beskriver hur ditt skyltfönster
|
||||
skall användas. Den visas ovanför produktlistan i affärsfönstret.
|
||||
shopfront_closed_message_placeholder: >
|
||||
Ett meddelande som ger en mer detaljerad förklaring varför din affär
|
||||
är stängd/eller när kunder kan förvänta att den öppnar igen. Detta visas
|
||||
i din affär endast när du inte har en aktiv ordercykel (dvs när affären
|
||||
är stängd).
|
||||
open_date: Öppningsdatum
|
||||
close_date: Stängningsdatum
|
||||
social:
|
||||
twitter_placeholder: t.ex. @the_prof
|
||||
tag_rules:
|
||||
default_rules:
|
||||
by_default: Som standard
|
||||
no_rules_yet: Standardregler har ännu ej fasttällts
|
||||
add_new_button: '+ Lägg till ny standardregel'
|
||||
no_tags_yet: Inga etiketter finns för detta företag ännu
|
||||
no_rules_yet: Inga regler finns för denna etikett ännu
|
||||
for_customers_tagged: 'För kunder taggade med:'
|
||||
add_new_rule: '+ Lägg till en ny regel'
|
||||
add_new_tag: '+ Lägg till en ny etikett'
|
||||
users:
|
||||
email_confirmation_notice_html: "E-post bekräftelse har inte kommit. Vi har sänt en bekräftande e-post till %{email}."
|
||||
resend: Återsänd
|
||||
owner: 'Ägare'
|
||||
owner_tip: Den primära användaren är ansvarig för detta företag.
|
||||
notifications: Meddelanden
|
||||
notifications_tip: Meddelanden om order kommer att sändas till denna e-postadress,
|
||||
notifications_placeholder: ex gustav@telia.com
|
||||
notifications_note: 'Observera: En ny e-postadress behöver kanske bli bekräftad före användning'
|
||||
managers: Chefer
|
||||
managers_tip: Andra användare med tillstånd att leda detta företag.
|
||||
actions:
|
||||
edit_profile: Redigera profilen
|
||||
properties: Egenskaper
|
||||
payment_methods: Betalningssätt
|
||||
payment_methods_tip: Detta företag har inga betalningssätt
|
||||
shipping_methods: Transportsätt
|
||||
shipping_methods_tip: Detta företag har inga transportsätt
|
||||
enterprise_fees: Företagsavgifter
|
||||
enterprise_fees_tip: Detta företag har inga avgifter
|
||||
admin_index:
|
||||
name: Namn
|
||||
role: Roll
|
||||
sells: Säljer
|
||||
visible: Synligt?
|
||||
owner: Ägare
|
||||
producer: Producent
|
||||
change_type_form:
|
||||
producer_profile: Producentprofil
|
||||
connect_ofn: Ansluten via OFN
|
||||
always_free: ALLTID GRATIS
|
||||
producer_description_text: 'Lägg till dina produkter till OFN, tillåt lagringscentraler att lagra dina varor i sina lokaler. '
|
||||
producer_shop: Producentaffär
|
||||
sell_your_produce: Sälj dina egna produkter
|
||||
sell_description_text: Sälj dina egna produkter direkt till kunder via ditt eget OFN skyltfönster.
|
||||
sell_description_text2: En producentaffär är enbart för din produkt, Om du önskar sälja produkter odlade/producerade på annat håll, välj "Producer Hub".
|
||||
producer_hub: Producentcentral
|
||||
producer_hub_text: Sälj produkter av egen eller annans tillverkning
|
||||
producer_hub_description_text: Ditt företag är grunden i ert lokala livsmedelssystem. Du kan sälja av egen tillverkning likväl som varor samlade från andra företag genom ditt skyltfönster i OFN.
|
||||
profile: Enbart profil
|
||||
get_listing: Få en listning
|
||||
profile_description_text: Folk kan hitta och kontakta dig i OFN. Ditt företag kommer att markeras på en karta och det går att söka i listor.
|
||||
hub_shop: Affär vid leveranscentral
|
||||
hub_shop_text: 'Sälj andras produkter '
|
||||
hub_shop_description_text: Ditt företag är grunden i dit lokala livsmedelssystem. Du kan samla produkter från andra företag och sälja dem i din affär genom OFN.
|
||||
choose_option: Var vänlig välj ett av förslagen ovan.
|
||||
change_now: Ändra nu
|
||||
enterprise_user_index:
|
||||
loading_enterprises: LADDAR FÖRETAG
|
||||
no_enterprises_found: Inget företag kunde hittas.
|
||||
search_placeholder: Sök på namn
|
||||
manage: Administrera
|
||||
new_form:
|
||||
owner: Ägare
|
||||
owner_tip: Den primära användaren är ansvarig för detta företag.
|
||||
i_am_producer: Jag är en Tillverkare
|
||||
contact_name: Kontaktperson
|
||||
edit:
|
||||
editing: 'Redigera:'
|
||||
back_link: Åter till företagslistan
|
||||
new:
|
||||
title: Nya företag
|
||||
back_link: Åter till företagslistan
|
||||
welcome:
|
||||
welcome_title: Välkommen till OFN!
|
||||
welcome_text: Du har korrekt slutfört en
|
||||
next_step: Nästa steg
|
||||
choose_starting_point: 'Välj din startpunkt:'
|
||||
order_cycles:
|
||||
advanced_settings:
|
||||
title: Avancerade inställningar
|
||||
choose_product_tip: Du kan välja att begränsa alla tillgängliga produkter (både inkommande och utgående), till enbart dem i %{inventory}'s inventory.
|
||||
preferred_product_selection_from_coordinator_inventory_only_here: Enbart koordinatorns inventarielista
|
||||
preferred_product_selection_from_coordinator_inventory_only_all: Alla tillgängliga produkter
|
||||
save_reload: Spara och ladda om sidan
|
||||
coordinator_fees:
|
||||
add: Lägg till koordinatoravgift
|
||||
form:
|
||||
incoming: Inkommande
|
||||
supplier: Leverantör
|
||||
receival_details: Mottagna detaljer
|
||||
fees: Avgifter
|
||||
outgoing: Utgående
|
||||
distributor: Distributör
|
||||
products: Produkter
|
||||
tags: Etiketter
|
||||
delivery_detaisl: Hämtade/levererade detaljer
|
||||
debug_info: Felinformation
|
||||
name_and_timing_form:
|
||||
name: Namn
|
||||
orders_open: Order öppna till
|
||||
coordinator: Koordinator
|
||||
order_closes: Order stänger
|
||||
row:
|
||||
suppliers: leverantörer
|
||||
distributors: distributörer
|
||||
variants: varianter
|
||||
simple_form:
|
||||
ready_for: Klar för
|
||||
ready_for_placeholder: Datum/tid
|
||||
customer_instructions: Kundinstruktioner
|
||||
customer_instructions_placeholder: Hämtnings eller leverans meddelanden
|
||||
products: Produkter
|
||||
fees: Avgifter
|
||||
edit:
|
||||
advanced_settings: Avancerade inställningar
|
||||
update_and_close: Uppdatera och Stäng
|
||||
producer_properties:
|
||||
index:
|
||||
title: Producentegenskaper
|
||||
shared:
|
||||
user_guide_link:
|
||||
user_guide: Användarinstruktion
|
||||
invoice_settings:
|
||||
edit:
|
||||
title: Fakturainställningar
|
||||
invoice_style2?: Använd den alternativa faktureringsmodellen som inkluderar totala skattefördelning per beräkning och skatt per artikel (ännu inte passande för länder som visar priser exklusive skatt)
|
||||
enable_receipt_printing?: 'Visa alternativ för att skriva ut recept genom att använda termiska skrivare i ordermenyn? '
|
||||
home:
|
||||
hubs:
|
||||
show_closed_shops: "Visa stängda affärer"
|
||||
@@ -231,10 +493,29 @@ sv:
|
||||
require_customer_login: "Denna butik är endast för kunder."
|
||||
require_login_html: "Var vänlig %{login} om du redan har ett konto. I annat fall %{register} för att bli kund."
|
||||
require_customer_html: "Vänlig %{contact} %{enterprise} för att bli en kund."
|
||||
invoice_column_item: "Artikel"
|
||||
invoice_column_qty: "Antal"
|
||||
invoice_billing_address: "Faktureringsadress"
|
||||
invoice_column_tax: "VAT"
|
||||
invoice_column_price: "Pris"
|
||||
invoice_column_item: "Artikel"
|
||||
invoice_column_qty: "Antal"
|
||||
invoice_column_unit_price_with_taxes: "Styckpris (Inkl skatt)"
|
||||
invoice_column_unit_price_without_taxes: "Styckpris (Exkl skatt)"
|
||||
invoice_column_price_with_taxes: "Slutsumma (inkl skatt)"
|
||||
invoice_column_price_without_taxes: "Slutsumma (Exkl skatt)"
|
||||
invoice_column_tax_rate: "Skattesats"
|
||||
tax_invoice: "SKATTEFAKTURA"
|
||||
tax_total: "Summa skatter (%{rate}):"
|
||||
total_excl_tax: "Summa (Exkl skatt):"
|
||||
total_incl_tax: "Summa (Inkl skatt):"
|
||||
abn: "ABN:"
|
||||
acn: "ACN:"
|
||||
invoice_issued_on: "Faktura utfärdad på:"
|
||||
order_number: "Fakturanummer:"
|
||||
date_of_transaction: "Transaktionsdatum:"
|
||||
ticket_column_qty: "Kvantitet"
|
||||
ticket_column_item: "Vara"
|
||||
ticket_column_unit_price: "Styckpris"
|
||||
ticket_column_total_price: "Summa"
|
||||
logo: "Logotyp (640x130)"
|
||||
logo_mobile: "Mobil logotyp (75x26)"
|
||||
logo_mobile_svg: "Mobil logotyp (SVG)"
|
||||
@@ -258,10 +539,13 @@ sv:
|
||||
phone: Telefon
|
||||
next: Näst
|
||||
address: Adress
|
||||
address_placeholder: eg. 123 High Street
|
||||
address2: Adress (fortgående)
|
||||
city: Ort
|
||||
state: Region
|
||||
city_placeholder: eg. Northcote
|
||||
postcode: Postnummer
|
||||
postcode_placeholder: eg. 3070
|
||||
state: Region
|
||||
country: Land
|
||||
unauthorized: Obehörig
|
||||
terms_of_service: "Användarvillkor "
|
||||
@@ -394,6 +678,7 @@ sv:
|
||||
order_includes_tax: (inkluderar skatter)
|
||||
order_payment_paypal_successful: Din betalning via PayPal har gått bra.
|
||||
order_hub_info: Hub-information
|
||||
bom_tip: "Använd denna sida för att ändra produktkvantiteter på flera order samtidigt. Produkter kan också tas bort från fler order samtidigt, om så erfordras."
|
||||
unsaved_changes_warning: "Osparade förändringar existerar och kommer förloras om du fortsätter."
|
||||
unsaved_changes_error: "Fält med röda kanter innehåller fel."
|
||||
products: "Produkter"
|
||||
@@ -405,7 +690,7 @@ sv:
|
||||
email_registered: "är nu del av"
|
||||
email_userguide_html: "Användarhandboken med detaljerade uppgifter hur du skall skapa din Producer eller Hub finns här: %{link}"
|
||||
email_admin_html: "Du kan sköta ditt konto genom att logga in %{link} eller att klicka på kuggen i det övre högra hörnet på hemsidan och välja Administration."
|
||||
email_community_html: "Vi har också ett internetforum för gemensamma diskussioner som är relaterade till OFN programvara och den unika utmaningen att använda ett matföretag. Vi utvecklas ständigt och dina inlägg till detta forum påverkar vad som händer i fortsättningen."
|
||||
email_community_html: "Vi har också ett internetforum för gemensamma diskussioner som är relaterade till OFN programvara och den unika utmaningen att använda ett matföretag. Vi utvecklas ständigt och dina inlägg till detta forum påverkar vad som händer i fortsättningen. %{link}"
|
||||
join_community: "Gå med i gemenskapen"
|
||||
email_help: "Om du hamnar i svårigheter, titta i vårt FAQ, sök igenom vårt forum eller skicka en fråga till Support och du kommer att få hjälp."
|
||||
email_confirmation_greeting: "Hej, %{contact}!"
|
||||
@@ -479,6 +764,7 @@ sv:
|
||||
hubs_filter_by: "Filtrera med"
|
||||
hubs_filter_type: "Typ"
|
||||
hubs_filter_delivery: "Leverans"
|
||||
hubs_filter_property: "Egenskaper"
|
||||
hubs_matches: "Menade du?"
|
||||
hubs_intro: Handla i ditt närområde
|
||||
hubs_distance: Närmast till
|
||||
@@ -493,7 +779,7 @@ sv:
|
||||
products_edit_cart: "Redigera din varukorg "
|
||||
products_from: från
|
||||
products_change: "Inga ändringar att spara."
|
||||
products_update_error: "Sparandet misslyckades med följande fel:"
|
||||
products_update_error: "Sparandet misslyckades med följande fel(en):"
|
||||
products_update_error_msg: "Sparandet misslyckades."
|
||||
products_update_error_data: "Sparandet misslyckades beroende på felaktiga data."
|
||||
products_changes_saved: "Ändringar sparade."
|
||||
@@ -724,9 +1010,9 @@ sv:
|
||||
enterprise_long_desc_placeholder: "Detta är ditt tillfälle att beskriva ditt företag - vad som gör dig speciell och intressant. Vi föreslår att du begränsar beskrivningen till 600 tecken eller 150 ord."
|
||||
enterprise_long_desc_length: "%{num} tecken / upp till 600 rekommenderas"
|
||||
enterprise_abn: "ABN"
|
||||
enterprise_abn_placeholder: "ex 123 123 123"
|
||||
enterprise_abn_placeholder: "t.ex. 99 123 456 789"
|
||||
enterprise_acn: "ACN"
|
||||
enterprise_acn_placeholder: "ex 123 123 123"
|
||||
enterprise_acn_placeholder: "t.ex. 123 456 789"
|
||||
enterprise_tax_required: "Du måste göra ett val"
|
||||
enterprise_final_step: "Sista steget!"
|
||||
enterprise_social_text: "Hur kan folk hitta %{enterprise} på nätet?"
|
||||
@@ -744,7 +1030,7 @@ sv:
|
||||
registration_intro: "Nu kan du skapa en profil för din producent eller matställe"
|
||||
registration_action: "Låt oss börja!"
|
||||
registration_checklist: "Du kommer att behöva"
|
||||
registration_time: "5 - 10 minuter"
|
||||
registration_time: "5-10 minuter"
|
||||
registration_enterprise_address: "Företagets adress"
|
||||
registration_contact_details: "Uppgifter hur man får kontakt"
|
||||
registration_logo: "Din logotype"
|
||||
@@ -785,7 +1071,7 @@ sv:
|
||||
registration_detail_suburb_placeholder: "ex spånga"
|
||||
registration_detail_suburb_error: "Var vänlig skriv in en förort"
|
||||
registration_detail_postcode: "Postkod:"
|
||||
registration_detail_postcode_placeholder: "ex 123 123"
|
||||
registration_detail_postcode_placeholder: "t.ex. 3070"
|
||||
registration_detail_postcode_error: "Postkod saknas"
|
||||
registration_detail_state: "Land:"
|
||||
registration_detail_state_error: "Land måste fyllas i "
|
||||
@@ -835,12 +1121,12 @@ sv:
|
||||
admin_entreprise_groups_data_powertip_logo: "Detta är logotypen för gruppen"
|
||||
admin_entreprise_groups_data_powertip_promo_image: "Denna bild visas överst på gruppens profil"
|
||||
admin_entreprise_groups_contact: "Kontakt"
|
||||
admin_entreprise_groups_contact_phone_placeholder: "ex 123 123"
|
||||
admin_entreprise_groups_contact_address1_placeholder: "ex Höga gatan"
|
||||
admin_entreprise_groups_contact_phone_placeholder: "t.ex. 98 7654 3210"
|
||||
admin_entreprise_groups_contact_address1_placeholder: "t.ex. 123 Höga gatan"
|
||||
admin_entreprise_groups_contact_city: "Förort"
|
||||
admin_entreprise_groups_contact_city_placeholder: "ex Norrberga"
|
||||
admin_entreprise_groups_contact_zipcode: "Postnummer"
|
||||
admin_entreprise_groups_contact_zipcode_placeholder: "ex 123"
|
||||
admin_entreprise_groups_contact_zipcode_placeholder: "t.ex. 3070"
|
||||
admin_entreprise_groups_contact_state_id: "Region"
|
||||
admin_entreprise_groups_contact_country_id: "Land"
|
||||
admin_entreprise_groups_web: "Webb resurser"
|
||||
@@ -903,7 +1189,7 @@ sv:
|
||||
spree_admin_single_enterprise_alert_mail_confirmation: "Var vänlig och fastställ e-postadressen för"
|
||||
spree_admin_single_enterprise_alert_mail_sent: "Vi har sänt e-post till "
|
||||
spree_admin_overview_action_required: "Ett ställningstagande behövs"
|
||||
spree_admin_overview_check_your_inbox: "Var vänlig och kontrollera din inkorg för ytterligare instruktioner. Tack!"
|
||||
spree_admin_overview_check_your_inbox: "Var vänlig kontrollera din inkorg för ytterligare instruktioner. Tack!"
|
||||
change_package: "Ändra förpackning"
|
||||
spree_admin_single_enterprise_hint: "Tips: För att folk skall hitta dig lättare, slå på din reklam under"
|
||||
your_profil_live: "Din profil direkt"
|
||||
@@ -920,6 +1206,12 @@ sv:
|
||||
edit_profile_details_etc: "Ändra beskrivningen av din profil, bilder, etc"
|
||||
order_cycle: "Ordercykel"
|
||||
remove_tax: "Tag bort skatt"
|
||||
enterprise_terms_of_service: "Företagets servicevillkor"
|
||||
enterprises_require_tos: "Företag måste acceptera servicevillkoren"
|
||||
enterprise_tos_link: "Länk till företagets servicevillkor"
|
||||
enterprise_tos_message: "Vi vill arbeta med personer som delar våra mål och värderingar. Därför ber vi nya företag att samtycka med vår"
|
||||
enterprise_tos_link_text: "Servicevillkor"
|
||||
enterprise_tos_agree: "Jag samtycker med ovanstående servicevillkor "
|
||||
tax_settings: "Skatteskalor"
|
||||
products_require_tax_category: "produkter kräver skattekategori"
|
||||
admin_shared_address_1: "Adress"
|
||||
@@ -953,6 +1245,22 @@ sv:
|
||||
report_order_cycle: "Beställningsrundor"
|
||||
report_entreprises: "Företag:"
|
||||
report_users: "Användare:"
|
||||
report_tax_rates: "Skattesatser"
|
||||
report_tax_types: "Typer av skatt"
|
||||
report_header_order_number: "Ordernummer"
|
||||
report_header_date: "Datum"
|
||||
report_header_items: "Varor"
|
||||
report_header_items_total: "Varusumma %{currency_symbol}"
|
||||
report_header_taxable_items_total: "Skattepliktig varusumma (%{currency_symbol})"
|
||||
report_header_sales_tax: "Skatt på försäljning (%{currency_symbol})"
|
||||
report_header_delivery_charge: "Leveransavgift (%{currency_symbol})"
|
||||
report_header_tax_on_delivery: "Leveransskatt (%{currency_symbol})"
|
||||
report_header_tax_on_fees: "Skatt på avgifter (%{currency_symbol})"
|
||||
report_header_total_tax: "Summa skatter (%{currency_symbol})"
|
||||
report_header_customer: "Kund"
|
||||
report_header_distributor: "Distributör"
|
||||
report_header_total_excl_vat: "Summa exkl skatt (%{currency_symbol})"
|
||||
report_header_total_incl_vat: "Summa inkl skatt (%{currency_symbol})"
|
||||
initial_invoice_number: "Initialt fakturanummer"
|
||||
invoice_date: "Fakturadatum:"
|
||||
due_date: "Sista betalningsdag:"
|
||||
@@ -994,7 +1302,141 @@ sv:
|
||||
validation_msg_product_category_cant_be_blank: "^Produktkategori kan inte vara blank"
|
||||
validation_msg_tax_category_cant_be_blank: "^Skattekategori kan inte vara blank"
|
||||
validation_msg_is_associated_with_an_exising_customer: "är knuten till en existerande kund"
|
||||
js:
|
||||
admin:
|
||||
modals:
|
||||
got_it: Förstått
|
||||
tag_rule_help:
|
||||
title: Regler för etiketter
|
||||
overview: Översikt
|
||||
overview_text: >
|
||||
Etikettreglerna är ett sätt att beskriva vilka varor visas eller inte
|
||||
för olika kunder. Reglerna kan beröra leveransmetoder, betalningssätt,
|
||||
produkt- eller ordercykler.
|
||||
by_default_rules: "Regler för standard "
|
||||
by_default_rules_text: >
|
||||
Standardreglerna tillåter dig att dölja varor så att de ej visas som
|
||||
standard. Denna metod kan sedan upphävas av en "ej-standard" regel för
|
||||
de kunder som har särskilda etiketter.
|
||||
customer_tagged_rules: "Regler för kundetiketter"
|
||||
customer_tagged_rules_text: >
|
||||
Genom att skapa regler för en speciell kundetikett kan du upphäva standardförfarandet
|
||||
(vare sig det är att visa eller dölja varor) för kunder med den specifika
|
||||
etiketten.
|
||||
panels:
|
||||
save: SPARA
|
||||
saved: SPARAD
|
||||
saving: SPARANDE
|
||||
enterprise_package:
|
||||
hub_profile: Hubbprofil
|
||||
hub_profile_cost: "KOSTNAD: ALLTID GRATIS"
|
||||
hub_profile_text1: >
|
||||
Folk kan finna och kontakta dig på OFN. Ditt företag visas på en karta
|
||||
och går att söka på i olika listor.
|
||||
hub_profile_text2: >
|
||||
Att ha en profil samt ha förbindelser inom ditt lokala livsmedelssystem
|
||||
via OFN är alltid gratis.
|
||||
hub_shop: Affär vid leveranscentral
|
||||
hub_shop_text1: >
|
||||
hej
|
||||
hub_shop_text2: >
|
||||
Centraler kan finnas i olika former, antingen kan de var en kooperation,
|
||||
en inköpsgrupp, en vegan-box program eller en lokal speceriaffär.
|
||||
hub_shop_text3: >
|
||||
Om du också önskar att sälja dina egna produkter måste du ändra detta
|
||||
företag till att vara en producent.
|
||||
choose_package: Var vänlig välj en förpackning
|
||||
choose_package_text1: >
|
||||
Ditt företag kommer inte att var aktiverat förrän du valt förpackning
|
||||
från listan till vänster.
|
||||
choose_package_text2: >
|
||||
Klicka på ett alternativ för att se mer detaljerad information om varje
|
||||
förpackning och klicka på den röda SAVE knappen när du är klar!
|
||||
profile_only: Enbart profil
|
||||
profile_only_cost: "KOSTNAD: ALLTID GRATIS"
|
||||
profile_only_text1: >
|
||||
En profil gör att du blir synlig och kan kontaktas av andra och är ett
|
||||
sätt att berätta din historia.
|
||||
profile_only_text2: >
|
||||
Om du föredrar att fokusera på att framställa mat och vill slippa arbetet
|
||||
med försäljning till andra, behöver du inte en affär i OFN.
|
||||
profile_only_text3: >
|
||||
Lägg till dina produkter på OFN och tillåt centraler lagra dina produkter
|
||||
i sina lokaler.
|
||||
producer_shop: Producentaffär
|
||||
producer_shop_text1: >
|
||||
Sälj dina produkter direkt till kunder genom ditt alldeles egna OFN
|
||||
skyltfönster.
|
||||
producer_shop_text2: >
|
||||
En producentaffär är enbart för din tillverkning, om du önskar sälja produkter
|
||||
odlade/framställda på annat ställe, var vänlig välj "Producer Hub".
|
||||
producer_hub: Producentcentral
|
||||
producer_hub_text1: >
|
||||
Ditt företag är grunden i ditt lokala livsmedelssystem. Du kan sälja
|
||||
dina egna såväl som produkter inköpta från andra företag genom ditt
|
||||
skyltfönster på OFN.
|
||||
producer_hub_text2: >
|
||||
Producentcentraler kan finnas i många former, antingen en CSA. en vegan-box
|
||||
program, eller en kooperation med takträdgårdar.
|
||||
producer_hub_text3: >
|
||||
OFN försöker stödja så många olika former av centraler som möjligt,
|
||||
så oavsett din situation så försöker vi förse dig med de verktyg du
|
||||
behöver för att hantera din organisation eller lokala mataffär.
|
||||
get_listing: Få en listning
|
||||
always_free: ALLTID GRATIS
|
||||
sell_produce_others: 'Sälj andras produkter '
|
||||
sell_own_produce: Sälj dina egna produkter
|
||||
sell_both: Sälj produkter av egen eller annans tillverkning
|
||||
enterprise_producer:
|
||||
producer: Producent
|
||||
producer_text1: >
|
||||
Producenter gör smaskiga saker att äta eller dricka. Du är en producent
|
||||
om du odlar,brygger bakar, jäser, mjölkar eller mal.
|
||||
producer_text2: >
|
||||
Producenter kan också ha andra funktioner såsom att samla matvaror från
|
||||
andra företag och sälja dem genom en affär på OFN.
|
||||
non_producer: Icke-producenter
|
||||
non_producer_text1: >
|
||||
Icke-tllverkare kan inte framställa mat själva, vilket innebär att de
|
||||
kan ej framställa sina egna produkter för försäljning via OFN.
|
||||
non_producer_text2: >
|
||||
I stället icke-tillverkare specialisera sig på att sammanföra producenter
|
||||
med den slutliga köparen genom att plocka samman, sortera, förpacka
|
||||
sälja eller leverera mat.
|
||||
producer_desc: Producenter av mat
|
||||
producer_example: ex ODLARE, BAGERIER, BRYGGERIER, TILLVERKARE
|
||||
non_producer_desc: Andra matföretag
|
||||
non_producer_example: t.ex. matbutiker, matkooperativ, köpgrupper
|
||||
enterprise_status:
|
||||
status_title: "%{name} är uppsatt och redo att användas!"
|
||||
severity: Allvarlighet
|
||||
description: Beskrivning
|
||||
resolve: Besluta
|
||||
new_tag_rule_dialog:
|
||||
select_rule_type: "Välj en regeltyp:"
|
||||
out_of_stock:
|
||||
reduced_stock_available: Minska tillgängligt lager
|
||||
out_of_stock_text: >
|
||||
Under tiden du handlat har lagernivån för en eller flera produkter i din
|
||||
kundvagn minskat. Detta har ändrats:
|
||||
now_out_of_stock: Är nu slut
|
||||
only_n_remainging: "nu återstår endast %{num}"
|
||||
spree:
|
||||
admin:
|
||||
products:
|
||||
bulk_edit:
|
||||
header:
|
||||
title: 'Redigera Omfång av Produkter '
|
||||
indicators:
|
||||
title: LADDAR PRODUKTER
|
||||
no_products: "Inga produkter ännu. Varför lägger du inte till några?"
|
||||
no_results: "Ledsen, inga resultat stämmer överens"
|
||||
variants:
|
||||
autocomplete:
|
||||
producer_name: Producent
|
||||
date_picker:
|
||||
format: '%Y-%m-%d'
|
||||
js_format: 'åå-mm-dd'
|
||||
zipcode: Postkod
|
||||
shipment_states:
|
||||
backorder: restorder
|
||||
@@ -1013,6 +1455,10 @@ sv:
|
||||
processing: behandlas
|
||||
void: annulerad
|
||||
invalid: felaktig
|
||||
order_mailer:
|
||||
invoice_email:
|
||||
hi: "Hi %{name}"
|
||||
invoice_attached_text: Här är en faktura för din senaste order från
|
||||
order_state:
|
||||
address: adress
|
||||
adjustments: justeringar
|
||||
@@ -1026,3 +1472,6 @@ sv:
|
||||
resumed: återupptagen
|
||||
returned: returnerad
|
||||
skrill: skrill
|
||||
orders:
|
||||
invoice:
|
||||
tax_invoice: "SKATTEFAKTURA:"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
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
|
||||
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
|
||||
Reference in New Issue
Block a user