Compare commits

...

50 Commits

Author SHA1 Message Date
Transifex-Openfoodnetwork
6a5e4bb592 Updating translations for config/locales/sv.yml [skip ci] 2017-04-30 23:38:53 +10:00
Transifex-Openfoodnetwork
507e12adba Updating translations for config/locales/fr.yml [skip ci] 2017-04-20 22:05:19 +10:00
Transifex-Openfoodnetwork
5fa45c0716 Updating translations for config/locales/es.yml [skip ci] 2017-04-08 01:22:10 +10:00
Maikel Linke
21337a5b50 Merge tag 'v1.8.9' into transifex 2017-04-06 10:36:14 +10:00
Maikel Linke
ec36a843cf Merge tag 'v1.8.8' into transifex 2017-04-06 10:31:26 +10:00
Transifex-Openfoodnetwork
e99dbaf4d8 Updating translations for config/locales/es.yml [skip ci] 2017-04-05 19:40:18 +10:00
Rob Harrington
c83ad2ecc4 Fixing broken limited reached page in registration flow 2017-04-05 17:02:40 +10:00
Matt-Yorkley
80d8d18eb2 Update terms of service config 2017-04-05 15:48:50 +10:00
Rob Harrington
903d1afb53 Stripping html tags from products description on new form as well 2017-04-05 14:29:23 +10:00
Matt-Yorkley
cd55d2e2ff Product Description - strip weird tags on paste
squashme
2017-04-05 11:29:35 +10:00
Rob Harrington
05cf8c4351 Sanitizing product description for textAngular input 2017-04-05 11:29:35 +10:00
Matt-Yorkley
b04d815408 Changes for code review
Fixed spec
2017-04-05 11:29:35 +10:00
Matt-Yorkley
7b370a2eb6 Removed underline option 2017-04-05 11:29:35 +10:00
Matt-Yorkley
5808b601b8 Added specs for HTML product description 2017-04-05 11:29:35 +10:00
Matt-Yorkley
fdcd3dc3e3 Fixed Capybara not interacting with textAngular 2017-04-05 11:29:35 +10:00
Matt-Yorkley
c4bd085393 Added Angular and textAngular to edit product page 2017-04-05 11:29:35 +10:00
Matt-Yorkley
0e91d01412 UX improvement for selected formatting options 2017-04-05 11:29:35 +10:00
Matt-Yorkley
fcb9e9fa56 Changed buttons 2017-04-05 11:29:35 +10:00
Matt-Yorkley
3591354cb1 Minor tweaks 2017-04-05 11:29:35 +10:00
Matt-Yorkley
b38eab11eb Fixed frontend HTML display 2017-04-05 11:29:35 +10:00
Matt-Yorkley
c43dea60b7 Product Descriptions formatting 2017-04-05 11:29:35 +10:00
Pierre de Lacroix
268bea25d0 add qz/ folder from ofn-qz gem to list of assets 2017-03-30 20:37:20 +02:00
Pierre de Lacroix
e94ae20b31 fix print_ticket authorization 2017-03-30 20:37:20 +02:00
Maikel Linke
a94961c0a7 Fixup merge conflicts and remove unused text 2017-03-29 14:58:19 +11:00
Lynne Davis
0d5fde919b Property name spans signle col heading 2017-03-29 14:47:17 +11:00
Matt-Yorkley
429ef4e2ba Altered product property headings for issue #522 2017-03-29 14:44:30 +11:00
Lynne Davis
e8999d23e1 Updated translations 2017-03-29 13:12:29 +11:00
Keir Osborn
209c9242d9 remove word-wrap class from enterprise.email_address and enterprise.website in javascripts/templates/partials/contact.html.haml 2017-03-24 12:55:30 +11:00
Transifex-Openfoodnetwork
6defb09d2e Updating translations for config/locales/fr.yml [skip ci] 2017-03-21 09:03:15 +11:00
Transifex-Openfoodnetwork
2774c09d7a Updating translations for config/locales/nb.yml [skip ci] 2017-03-20 07:10:53 +11:00
Matt-Yorkley
c62a044598 PI highlight invalid fields in feedback tables 2017-03-17 16:11:52 +11:00
Matt-Yorkley
f73fbe7f23 SpreadsheetEntry Class and PI refactor 2017-03-17 16:11:52 +11:00
Matt-Yorkley
5e1e4c1d19 Product Import UX review updates
Minor tweaks

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -155,6 +155,8 @@ class AbilityDecorator
can [:admin, :index, :read, :search], Spree::Taxon
can [:admin, :index, :read, :create, :edit], Spree::Classification
can [:admin, :index, :import, :save], ProductImporter
# Reports page
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
end
@@ -173,7 +175,7 @@ class AbilityDecorator
def add_order_management_abilities(user)
# Enterprise User can only access orders that they are a distributor for
can [:index, :create], Spree::Order
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
can [:read, :update, :fire, :resend, :invoice, :print, :print_ticket], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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:
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:"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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