diff --git a/.codeclimate.yml b/.codeclimate.yml index 9646b677e9a..60c015c60e7 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -2,7 +2,7 @@ version: "2" plugins: rubocop: enabled: true - channel: "rubocop-0-55" + channel: "rubocop-0-57" scss-lint: enabled: true checks: @@ -19,23 +19,23 @@ checks: argument-count: enabled: false complex-logic: - enabled: true + enabled: false file-lines: - enabled: true + enabled: false method-complexity: - enabled: true + enabled: false method-count: enabled: false method-lines: enabled: false nested-control-flow: - enabled: true + enabled: false return-statements: - enabled: true + enabled: false similar-code: - enabled: true + enabled: false identical-code: - enabled: true + enabled: false exclude_patterns: - "spec/**/*" - "vendor/**/*" diff --git a/.gitignore b/.gitignore index 060eb24f85e..4c0d217d06b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ libpeerconnection.log /config/application.yml node_modules vendor/bundle/ +coverage diff --git a/.rspec_parallel b/.rspec_parallel deleted file mode 100644 index cee31855e2a..00000000000 --- a/.rspec_parallel +++ /dev/null @@ -1,4 +0,0 @@ ---format Fuubar ---format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log ---format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log ---tag ~performance diff --git a/.rubocop.yml b/.rubocop.yml index 80d4c1bf7e6..2bf1f9b60e8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,18 +4,16 @@ inherit_from: AllCops: TargetRubyVersion: 2.1 TargetRailsVersion: 3.2 - Include: - - '**/Rakefile' - - '**/config.ru' Exclude: - 'db/**/*' - 'config/**/*' - 'script/**/*' - 'vendor/**/*' - 'node_modules/**/*' - - !ruby/regexp /old_and_unused\.rb$/ # The parser gem fails to parse this file with out current Ruby version. - 'spec/factories.rb' + # Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase + - 'engines/web/Gemfile' # OFN SETTINGS # Cop settings that have been agreed upon by the OFN community @@ -33,6 +31,9 @@ Style/HashSyntax: Enabled: true EnforcedStyle: ruby19_no_mixed_keys +Style/Send: + Enabled: true + Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented @@ -174,28 +175,28 @@ Lint/AssignmentInCondition: StyleGuide: http://relaxed.ruby.style/#lintassignmentincondition Metrics/AbcSize: - Enabled: false + Max: 15 Metrics/BlockNesting: - Enabled: false + Max: 3 Metrics/ClassLength: - Enabled: false + Max: 100 Metrics/ModuleLength: - Enabled: false + Max: 100 Metrics/CyclomaticComplexity: - Enabled: false + Max: 6 Metrics/LineLength: - Enabled: false + Max: 100 Metrics/MethodLength: - Enabled: false + Max: 10 Metrics/ParameterLists: - Enabled: false + Max: 5 Metrics/PerceivedComplexity: - Enabled: false + Max: 7 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index eaff617a863..378129593e8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 1400` -# on 2018-08-06 18:22:59 +0800 using RuboCop version 0.55.0. +# on 2018-09-19 19:24:45 +0200 using RuboCop version 0.57.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 35 +# Offense count: 32 # Cop supports --auto-correct. # Configuration parameters: Include, TreatCommentsAsGroupSeparators. # Include: **/*.gemfile, **/Gemfile, **/gems.rb @@ -14,20 +14,20 @@ Bundler/OrderedGems: Exclude: - 'Gemfile' -# Offense count: 116 +# Offense count: 115 # Cop supports --auto-correct. Layout/AlignArray: Exclude: - - 'app/controllers/admin/contents_controller.rb' - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/order_and_distributor_report.rb' - 'lib/open_food_network/orders_and_fulfillments_report.rb' - 'lib/open_food_network/packing_report.rb' - - 'spec/controllers/spree/orders_controller_spec.rb' + - 'spec/controllers/cart_controller_spec.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' + - 'spec/services/cart_service_spec.rb' -# Offense count: 127 +# Offense count: 121 # Cop supports --auto-correct. # Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. # SupportedHashRocketStyles: key, separator, table @@ -35,7 +35,6 @@ Layout/AlignArray: # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit Layout/AlignHash: Exclude: - - 'app/overrides/add_capture_order_shortcut.rb' - 'app/overrides/replace_shipping_address_form_with_distributor_details.rb' - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/open_food_network/orders_and_fulfillments_report.rb' @@ -64,7 +63,6 @@ Layout/AlignHash: # SupportedStyles: with_first_parameter, with_fixed_indentation Layout/AlignParameters: Exclude: - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/injection_helper.rb' - 'app/models/enterprise.rb' - 'app/models/enterprise_group.rb' @@ -77,9 +75,11 @@ Layout/AlignParameters: - 'lib/tasks/dev.rake' - 'spec/controllers/enterprises_controller_spec.rb' - 'spec/controllers/shop_controller_spec.rb' + - 'spec/features/admin/enterprise_fees_spec.rb' - 'spec/features/admin/enterprise_relationships_spec.rb' - 'spec/features/admin/order_cycles_spec.rb' - 'spec/features/consumer/shopping/checkout_spec.rb' + - 'spec/features/consumer/shopping/orders_spec.rb' - 'spec/helpers/enterprises_helper_spec.rb' - 'spec/lib/open_food_network/user_balance_calculator_spec.rb' - 'spec/serializers/variant_serializer_spec.rb' @@ -104,9 +104,16 @@ Layout/BlockEndNewline: # Offense count: 1 # Cop supports --auto-correct. +Layout/ClosingHeredocIndentation: + Exclude: + - 'app/models/content_configuration.rb' + +# Offense count: 2 +# Cop supports --auto-correct. Layout/ClosingParenthesisIndentation: Exclude: - - 'spec/features/admin/order_cycles_spec.rb' + - 'spec/controllers/spree/admin/orders/customer_details_controller_spec.rb' + - 'spec/serializers/variant_serializer_spec.rb' # Offense count: 8 # Cop supports --auto-correct. @@ -119,7 +126,7 @@ Layout/ElseAlignment: - 'app/serializers/api/admin/order_cycle_serializer.rb' - 'lib/open_food_network/sales_tax_report.rb' -# Offense count: 205 +# Offense count: 201 # Cop supports --auto-correct. Layout/EmptyLines: Exclude: @@ -162,7 +169,6 @@ Layout/EmptyLines: - 'app/models/spree/line_item_decorator.rb' - 'app/models/spree/option_type_decorator.rb' - 'app/models/spree/option_value_decorator.rb' - - 'app/models/spree/order_populator_decorator.rb' - 'app/models/spree/payment_decorator.rb' - 'app/models/spree/preference_decorator.rb' - 'app/models/spree/preferences/file_configuration.rb' @@ -185,7 +191,6 @@ Layout/EmptyLines: - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/open_food_network/products_cache.rb' - 'lib/open_food_network/products_cache_integrity_checker.rb' - - 'lib/open_food_network/products_cache_refreshment.rb' - 'lib/open_food_network/products_renderer.rb' - 'lib/open_food_network/property_merge.rb' - 'lib/open_food_network/reports/bulk_coop_report.rb' @@ -216,6 +221,7 @@ Layout/EmptyLines: - 'spec/features/admin/order_cycles_spec.rb' - 'spec/features/admin/orders_spec.rb' - 'spec/features/admin/payment_method_spec.rb' + - 'spec/features/admin/product_import_spec.rb' - 'spec/features/admin/products_spec.rb' - 'spec/features/admin/reports_spec.rb' - 'spec/features/admin/shipping_methods_spec.rb' @@ -243,7 +249,6 @@ Layout/EmptyLines: - 'spec/models/product_distribution_spec.rb' - 'spec/models/spree/adjustment_spec.rb' - 'spec/models/spree/line_item_spec.rb' - - 'spec/models/spree/order_populator_spec.rb' - 'spec/models/spree/order_spec.rb' - 'spec/models/spree/product_spec.rb' - 'spec/models/spree/shipping_method_spec.rb' @@ -253,6 +258,7 @@ Layout/EmptyLines: - 'spec/serializers/admin/for_order_cycle/enterprise_serializer_spec.rb' - 'spec/serializers/admin/for_order_cycle/supplied_product_serializer_spec.rb' - 'spec/serializers/credit_card_serializer_spec.rb' + - 'spec/services/cart_service_spec.rb' - 'spec/support/delayed_job_helper.rb' - 'spec/support/matchers/table_matchers.rb' @@ -262,7 +268,7 @@ Layout/EmptyLinesAroundArguments: Exclude: - 'spec/archive/features/consumer/checkout_spec.rb' -# Offense count: 64 +# Offense count: 61 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, no_empty_lines @@ -282,7 +288,6 @@ Layout/EmptyLinesAroundBlockBody: - 'lib/tasks/users.rake' - 'spec/controllers/admin/order_cycles_controller_spec.rb' - 'spec/controllers/admin/tag_rules_controller_spec.rb' - - 'spec/controllers/cart_controller_spec.rb' - 'spec/controllers/spree/admin/orders_controller_spec.rb' - 'spec/controllers/spree/admin/reports_controller_spec.rb' - 'spec/controllers/spree/api/orders_controller_spec.rb' @@ -293,6 +298,7 @@ Layout/EmptyLinesAroundBlockBody: - 'spec/features/admin/orders_spec.rb' - 'spec/features/admin/reports_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' + - 'spec/features/consumer/cookies_spec.rb' - 'spec/features/consumer/shopping/embedded_groups_spec.rb' - 'spec/features/consumer/shopping/embedded_shopfronts_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' @@ -308,8 +314,8 @@ Layout/EmptyLinesAroundBlockBody: - 'spec/lib/open_food_network/referer_parser_spec.rb' - 'spec/lib/open_food_network/user_balance_calculator_spec.rb' - 'spec/models/billable_period_spec.rb' - - 'spec/models/cart_spec.rb' - 'spec/models/product_distribution_spec.rb' + - 'spec/models/product_import/product_list_spec.rb' - 'spec/models/spree/ability_spec.rb' - 'spec/models/spree/product_spec.rb' - 'spec/models/tag_rule/filter_payment_methods_spec.rb' @@ -321,7 +327,7 @@ Layout/EmptyLinesAroundBlockBody: - 'spec/support/matchers/select2_matchers.rb' - 'spec/support/matchers/table_matchers.rb' -# Offense count: 26 +# Offense count: 24 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only @@ -331,7 +337,6 @@ Layout/EmptyLinesAroundClassBody: - 'app/controllers/admin/cache_settings_controller.rb' - 'app/controllers/admin/enterprise_fees_controller.rb' - 'app/controllers/admin/inventory_items_controller.rb' - - 'app/controllers/admin/invoice_settings_controller.rb' - 'app/controllers/admin/tag_rules_controller.rb' - 'app/controllers/api/enterprises_controller.rb' - 'app/controllers/application_controller.rb' @@ -363,7 +368,7 @@ Layout/EndAlignment: - 'app/serializers/api/admin/for_order_cycle/supplied_product_serializer.rb' - 'app/serializers/api/admin/order_cycle_serializer.rb' -# Offense count: 48 +# Offense count: 49 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. Layout/ExtraSpacing: @@ -389,6 +394,7 @@ Layout/ExtraSpacing: - 'spec/features/admin/reports_spec.rb' - 'spec/features/consumer/groups_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' + - 'spec/helpers/cookies_policy_helper_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb' - 'spec/lib/open_food_network/reports/rule_spec.rb' - 'spec/models/enterprise_fee_spec.rb' @@ -406,7 +412,7 @@ Layout/ExtraSpacing: # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: consistent, special_for_inner_method_call, special_for_inner_method_call_in_parentheses +# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses Layout/FirstParameterIndentation: Exclude: - 'spec/controllers/spree/admin/orders/customer_details_controller_spec.rb' @@ -418,7 +424,7 @@ Layout/FirstParameterIndentation: Layout/IndentArray: EnforcedStyle: consistent -# Offense count: 51 +# Offense count: 53 # Cop supports --auto-correct. # Configuration parameters: IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces @@ -438,12 +444,11 @@ Layout/IndentationConsistency: - 'spec/models/spree/line_item_spec.rb' - 'spec/models/spree/product_spec.rb' -# Offense count: 21 +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: Width, IgnoredPatterns. Layout/IndentationWidth: Exclude: - - 'app/controllers/admin/invoice_settings_controller.rb' - 'app/controllers/admin/order_cycles_controller.rb' - 'app/controllers/api/order_cycles_controller.rb' - 'app/models/spree/line_item_decorator.rb' @@ -461,6 +466,12 @@ Layout/IndentationWidth: - 'spec/models/enterprise_spec.rb' - 'spec/models/spree/calculator/flexi_rate_spec.rb' +# Offense count: 1 +# Cop supports --auto-correct. +Layout/LeadingBlankLines: + Exclude: + - 'lib/tasks/dev.rake' + # Offense count: 46 # Cop supports --auto-correct. Layout/LeadingCommentSpace: @@ -470,7 +481,6 @@ Layout/LeadingCommentSpace: - 'app/models/content_configuration.rb' - 'app/models/spree/inventory_unit_decorator.rb' - 'app/models/spree/taxon_decorator.rb' - - 'app/overrides/add_capture_order_shortcut.rb' - 'app/serializers/api/address_serializer.rb' - 'app/serializers/api/enterprise_serializer.rb' - 'app/serializers/api/product_serializer.rb' @@ -522,7 +532,7 @@ Layout/MultilineHashBraceLayout: - 'lib/spree/product_filters.rb' - 'spec/support/request/authentication_workflow.rb' -# Offense count: 7 +# Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: symmetrical, new_line, same_line @@ -530,9 +540,9 @@ Layout/MultilineMethodCallBraceLayout: Exclude: - 'app/helpers/spree/orders_helper.rb' - 'app/models/spree/variant_decorator.rb' - - 'app/overrides/add_capture_order_shortcut.rb' - 'lib/open_food_network/products_renderer.rb' - 'spec/features/admin/order_cycles_spec.rb' + - 'spec/features/consumer/shopping/orders_spec.rb' - 'spec/lib/open_food_network/products_and_inventory_report_spec.rb' # Offense count: 4 @@ -544,7 +554,7 @@ Layout/MultilineMethodCallIndentation: - 'spec/lib/open_food_network/cached_products_renderer_spec.rb' - 'spec/serializers/variant_serializer_spec.rb' -# Offense count: 30 +# Offense count: 28 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented @@ -560,7 +570,6 @@ Layout/MultilineOperationIndentation: - 'app/models/variant_override_set.rb' - 'lib/open_food_network/accounts_and_billing_settings_validator.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - - 'lib/open_food_network/products_cache_refreshment.rb' - 'lib/open_food_network/sales_tax_report.rb' - 'lib/open_food_network/users_and_enterprises_report.rb' @@ -613,7 +622,7 @@ Layout/SpaceAfterSemicolon: Exclude: - 'spec/controllers/spree/admin/base_controller_spec.rb' -# Offense count: 62 +# Offense count: 59 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: space, no_space @@ -674,11 +683,11 @@ Layout/SpaceAroundOperators: - 'lib/spree/product_filters.rb' - 'spec/controllers/admin/enterprises_controller_spec.rb' - 'spec/controllers/cart_controller_spec.rb' - - 'spec/controllers/spree/orders_controller_spec.rb' - 'spec/features/admin/bulk_order_management_spec.rb' - 'spec/features/admin/bulk_product_update_spec.rb' - 'spec/features/consumer/shopping/checkout_spec.rb' - 'spec/helpers/checkout_helper_spec.rb' + - 'spec/helpers/cookies_policy_helper_spec.rb' - 'spec/helpers/order_cycles_helper_spec.rb' - 'spec/jobs/update_billable_periods_spec.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' @@ -715,7 +724,7 @@ Layout/SpaceInLambdaLiteral: - 'app/models/spree/product_decorator.rb' - 'app/models/spree/variant_decorator.rb' -# Offense count: 130 +# Offense count: 128 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. # SupportedStyles: space, no_space, compact @@ -730,7 +739,6 @@ Layout/SpaceInsideArrayLiteralBrackets: - 'lib/open_food_network/payments_report.rb' - 'lib/open_food_network/users_and_enterprises_report.rb' - 'spec/controllers/admin/variant_overrides_controller_spec.rb' - - 'spec/controllers/cart_controller_spec.rb' - 'spec/features/admin/reports_spec.rb' - 'spec/jobs/update_billable_periods_spec.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' @@ -791,7 +799,7 @@ Layout/SpaceInsideBlockBraces: - 'spec/spec_helper.rb' - 'spec/support/cancan_helper.rb' -# Offense count: 772 +# Offense count: 734 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space, compact @@ -799,7 +807,6 @@ Layout/SpaceInsideBlockBraces: Layout/SpaceInsideHashLiteralBraces: Exclude: - 'app/controllers/admin/cache_settings_controller.rb' - - 'app/controllers/admin/contents_controller.rb' - 'app/controllers/admin/enterprise_relationships_controller.rb' - 'app/controllers/admin/enterprise_roles_controller.rb' - 'app/controllers/api/statuses_controller.rb' @@ -807,7 +814,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'app/controllers/spree/admin/line_items_controller_decorator.rb' - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'app/controllers/spree/admin/search_controller_decorator.rb' - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/admin/business_model_configuration_helper.rb' - 'app/helpers/admin/injection_helper.rb' - 'app/helpers/angular_form_builder.rb' @@ -824,11 +830,9 @@ Layout/SpaceInsideHashLiteralBraces: - 'app/models/enterprise_relationship.rb' - 'app/models/producer_property.rb' - 'app/models/spree/gateway/stripe_connect.rb' - - 'app/models/spree/order_populator_decorator.rb' - 'app/models/spree/product_decorator.rb' - 'app/models/spree/property_decorator.rb' - 'app/models/spree/shipping_method_decorator.rb' - - 'app/overrides/add_capture_order_shortcut.rb' - 'app/serializers/api/admin/enterprise_fee_serializer.rb' - 'app/serializers/api/admin/order_cycle_serializer.rb' - 'lib/open_food_network/feature_toggle.rb' @@ -888,7 +892,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'spec/models/spree/ability_spec.rb' - 'spec/models/spree/gateway/stripe_connect_spec.rb' - 'spec/models/spree/image_spec.rb' - - 'spec/models/spree/order_populator_spec.rb' - 'spec/models/spree/order_spec.rb' - 'spec/models/spree/product_spec.rb' - 'spec/models/spree/shipping_method_spec.rb' @@ -900,6 +903,7 @@ Layout/SpaceInsideHashLiteralBraces: - 'spec/requests/checkout/failed_checkout_spec.rb' - 'spec/requests/checkout/stripe_connect_spec.rb' - 'spec/serializers/enterprise_serializer_spec.rb' + - 'spec/services/cart_service_spec.rb' - 'spec/services/order_syncer_spec.rb' - 'spec/services/subscription_form_spec.rb' - 'spec/spec_helper.rb' @@ -928,17 +932,24 @@ Layout/SpaceInsideStringInterpolation: - 'lib/open_food_network/users_and_enterprises_report.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: IndentationWidth. Layout/Tab: Exclude: - - 'app/controllers/admin/invoice_settings_controller.rb' - 'app/models/spree/line_item_decorator.rb' - 'spec/lib/spree/product_filters_spec.rb' - 'spec/models/spree/line_item_spec.rb' -# Offense count: 60 +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: final_newline, final_blank_line +Layout/TrailingBlankLines: + Exclude: + - 'spec/controllers/cart_controller_spec.rb' + +# Offense count: 64 # Cop supports --auto-correct. # Configuration parameters: AllowInHeredoc. Layout/TrailingWhitespace: @@ -954,8 +965,10 @@ Layout/TrailingWhitespace: - 'app/views/json/_producer.rabl' - 'app/views/json/partials/_producer.rabl' - 'spec/controllers/admin/column_preferences_controller_spec.rb' - - 'spec/features/admin/enterprise_user_spec.rb' + - 'spec/features/admin/customers_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' + - 'spec/features/consumer/cookies_spec.rb' + - 'spec/helpers/cookies_policy_helper_spec.rb' - 'spec/helpers/enterprises_helper_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb' - 'spec/lib/open_food_network/group_buy_report_spec.rb' @@ -979,14 +992,13 @@ Lint/DuplicateMethods: - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/subscription_summary.rb' -# Offense count: 18 +# Offense count: 16 Lint/IneffectiveAccessModifier: Exclude: - 'app/models/column_preference.rb' - 'app/models/variant_override.rb' - 'lib/open_food_network/feature_toggle.rb' - 'lib/open_food_network/products_cache.rb' - - 'lib/open_food_network/products_cache_refreshment.rb' - 'lib/open_food_network/property_merge.rb' - 'spec/lib/open_food_network/reports/report_spec.rb' @@ -1038,7 +1050,7 @@ Lint/UnderscorePrefixedVariableName: Exclude: - 'spec/support/cancan_helper.rb' -# Offense count: 123 +# Offense count: 121 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -1050,7 +1062,6 @@ Lint/UnusedBlockArgument: - 'app/models/column_preference.rb' - 'app/models/model_set.rb' - 'app/models/spree/order_decorator.rb' - - 'app/models/spree/order_populator_decorator.rb' - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/open_food_network/enterprise_fee_calculator.rb' - 'lib/open_food_network/group_buy_report.rb' @@ -1089,26 +1100,24 @@ Lint/UnusedMethodArgument: - 'lib/open_food_network/paperclippable.rb' - 'lib/open_food_network/rack_request_blocker.rb' -# Offense count: 7 +# Offense count: 6 # Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'app/models/column_preference.rb' - 'lib/open_food_network/feature_toggle.rb' - 'lib/open_food_network/products_cache.rb' - - 'lib/open_food_network/products_cache_refreshment.rb' - 'lib/open_food_network/property_merge.rb' - 'lib/open_food_network/reports/bulk_coop_report.rb' - 'spec/lib/open_food_network/reports/report_spec.rb' -# Offense count: 288 +# Offense count: 246 # Configuration parameters: CheckForMethodsWithNoSideEffects. Lint/Void: Exclude: - 'app/serializers/api/enterprise_serializer.rb' - 'spec/archive/features/consumer/checkout_spec.rb' - 'spec/controllers/api/order_cycles_controller_spec.rb' - - 'spec/controllers/cart_controller_spec.rb' - 'spec/controllers/checkout_controller_spec.rb' - 'spec/controllers/enterprises_controller_spec.rb' - 'spec/controllers/shop_controller_spec.rb' @@ -1117,10 +1126,8 @@ Lint/Void: - 'spec/controllers/spree/admin/variants_controller_spec.rb' - 'spec/controllers/spree/api/products_controller_spec.rb' - 'spec/controllers/spree/api/variants_controller_spec.rb' - - 'spec/controllers/spree/orders_controller_spec.rb' - 'spec/controllers/user_registrations_controller_spec.rb' - 'spec/features/admin/bulk_product_update_spec.rb' - - 'spec/features/admin/enterprise_fees_spec.rb' - 'spec/features/admin/enterprise_groups_spec.rb' - 'spec/features/admin/enterprises/index_spec.rb' - 'spec/features/admin/enterprises_spec.rb' @@ -1145,14 +1152,12 @@ Lint/Void: - 'spec/lib/open_food_network/reports/report_spec.rb' - 'spec/lib/open_food_network/reports/rule_spec.rb' - 'spec/mailers/order_mailer_spec.rb' - - 'spec/models/cart_spec.rb' - 'spec/models/enterprise_relationship_spec.rb' - 'spec/models/enterprise_spec.rb' - 'spec/models/exchange_spec.rb' - 'spec/models/order_cycle_spec.rb' - 'spec/models/spree/adjustment_spec.rb' - 'spec/models/spree/line_item_spec.rb' - - 'spec/models/spree/order_populator_spec.rb' - 'spec/models/spree/order_spec.rb' - 'spec/models/spree/payment_method_spec.rb' - 'spec/models/spree/payment_spec.rb' @@ -1162,10 +1167,53 @@ Lint/Void: - 'spec/serializers/enterprise_serializer_spec.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 993 +# Offense count: 195 +Metrics/AbcSize: + Max: 293 + +# Offense count: 1010 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 776 + Max: 787 + +# Offense count: 1 +# Configuration parameters: CountBlocks. +Metrics/BlockNesting: + Max: 4 + +# Offense count: 23 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 331 + +# Offense count: 38 +Metrics/CyclomaticComplexity: + Max: 23 + +# Offense count: 6683 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 623 + +# Offense count: 163 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 95 + +# Offense count: 27 +# Configuration parameters: CountComments. +Metrics/ModuleLength: + Max: 633 + +# Offense count: 6 +# Configuration parameters: CountKeywordArgs. +Metrics/ParameterLists: + Max: 8 + +# Offense count: 30 +Metrics/PerceivedComplexity: + Max: 21 # Offense count: 7 Naming/AccessorMethodName: @@ -1180,14 +1228,6 @@ Naming/BinaryOperatorParameterName: Exclude: - 'app/models/exchange.rb' -# Offense count: 2 -# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. -# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS -Naming/FileName: - Exclude: - - 'Gemfile' - - 'Guardfile' - # Offense count: 1 # Configuration parameters: Blacklist. # Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) @@ -1228,12 +1268,11 @@ Naming/PredicateName: - 'lib/open_food_network/packing_report.rb' - 'lib/tasks/data.rake' -# Offense count: 14 +# Offense count: 13 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: io, id, to, by, on, in, at Naming/UncommunicativeMethodParamName: Exclude: - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/admin/injection_helper.rb' - 'app/helpers/spree/admin/base_helper_decorator.rb' - 'app/helpers/spree/base_helper_decorator.rb' @@ -1279,6 +1318,15 @@ Performance/DoubleStartEndWith: Exclude: - 'app/helpers/application_helper.rb' +# Offense count: 4 +# Cop supports --auto-correct. +Performance/InefficientHashSearch: + Exclude: + - 'app/models/spree/payment_method_decorator.rb' + - 'app/models/spree/preferences/file_configuration.rb' + - 'lib/stripe/account_connector.rb' + - 'lib/stripe/webhook_handler.rb' + # Offense count: 3 # Cop supports --auto-correct. Performance/RedundantBlockCall: @@ -1350,6 +1398,8 @@ Rails/Delegate: - 'app/serializers/api/variant_serializer.rb' # Offense count: 8 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: slashes, arguments Rails/FilePath: Exclude: - 'lib/tasks/karma.rake' @@ -1381,14 +1431,13 @@ Rails/HasAndBelongsToMany: - 'app/models/spree/line_item_decorator.rb' - 'app/models/spree/payment_method_decorator.rb' -# Offense count: 31 +# Offense count: 29 # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/HasManyOrHasOneDependent: Exclude: - 'app/models/account_invoice.rb' - 'app/models/billable_period.rb' - - 'app/models/cart.rb' - 'app/models/customer.rb' - 'app/models/enterprise.rb' - 'app/models/order_cycle.rb' @@ -1420,6 +1469,7 @@ Rails/HttpStatus: - 'app/controllers/api/customers_controller.rb' - 'app/controllers/api/enterprises_controller.rb' - 'app/controllers/application_controller.rb' + - 'app/controllers/cart_controller.rb' - 'app/controllers/checkout_controller.rb' - 'app/controllers/enterprises_controller.rb' - 'app/controllers/line_items_controller.rb' @@ -1427,7 +1477,6 @@ Rails/HttpStatus: - 'app/controllers/spree/admin/line_items_controller_decorator.rb' - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'app/controllers/spree/credit_cards_controller.rb' - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/controllers/spree/store_controller_decorator.rb' - 'app/controllers/stripe/callbacks_controller.rb' - 'app/controllers/stripe/webhooks_controller.rb' @@ -1474,7 +1523,7 @@ Rails/ReadWriteAttribute: Exclude: - 'app/models/enterprise.rb' -# Offense count: 47 +# Offense count: 46 # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/ScopeArgs: @@ -1562,7 +1611,7 @@ Style/BarePercentLiterals: - 'spec/features/admin/variants_spec.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 210 +# Offense count: 207 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: braces, no_braces, context_dependent @@ -1573,7 +1622,6 @@ Style/BracesAroundHashParameters: - 'app/controllers/checkout_controller.rb' - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'app/controllers/spree/admin/search_controller_decorator.rb' - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/admin/account_helper.rb' - 'app/helpers/admin/business_model_configuration_helper.rb' - 'app/helpers/angular_form_builder.rb' @@ -1583,7 +1631,6 @@ Style/BracesAroundHashParameters: - 'app/jobs/update_account_invoices.rb' - 'app/jobs/update_billable_periods.rb' - 'app/models/billable_period.rb' - - 'app/models/cart.rb' - 'app/models/exchange.rb' - 'app/models/spree/adjustment_decorator.rb' - 'app/models/spree/line_item_decorator.rb' @@ -1627,11 +1674,11 @@ Style/BracesAroundHashParameters: - 'spec/models/billable_period_spec.rb' - 'spec/models/product_distribution_spec.rb' - 'spec/models/spree/ability_spec.rb' - - 'spec/models/spree/order_populator_spec.rb' - 'spec/models/spree/order_spec.rb' - 'spec/models/spree/product_spec.rb' - 'spec/models/spree/taxon_spec.rb' - 'spec/serializers/admin/customer_serializer_spec.rb' + - 'spec/services/cart_service_spec.rb' - 'spec/spec_helper.rb' - 'spec/support/cancan_helper.rb' - 'spec/support/request/authentication_workflow.rb' @@ -1643,7 +1690,7 @@ Style/CaseEquality: - 'app/helpers/angular_form_helper.rb' - 'spec/models/spree/payment_spec.rb' -# Offense count: 86 +# Offense count: 85 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle. # SupportedStyles: nested, compact @@ -1653,7 +1700,6 @@ Style/ClassAndModuleChildren: - 'app/controllers/admin/accounts_and_billing_settings_controller.rb' - 'app/controllers/admin/business_model_configuration_controller.rb' - 'app/controllers/admin/cache_settings_controller.rb' - - 'app/controllers/admin/invoice_settings_controller.rb' - 'app/controllers/spree/store_controller_decorator.rb' - 'app/helpers/angular_form_helper.rb' - 'app/models/calculator/flat_percent_per_item.rb' @@ -1756,20 +1802,15 @@ Style/CommentedKeyword: Exclude: - 'app/controllers/application_controller.rb' -# Offense count: 10 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'app/controllers/checkout_controller.rb' - - 'app/controllers/spree/admin/base_controller_decorator.rb' - - 'app/controllers/spree/admin/payment_methods_controller_decorator.rb' - 'app/controllers/spree/admin/search_controller_decorator.rb' - - 'app/helpers/spree/admin/orders_helper_decorator.rb' - - 'app/models/spree/calculator/per_item_decorator.rb' - 'app/models/spree/line_item_decorator.rb' - - 'app/models/spree/payment_decorator.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' # Offense count: 2 @@ -1822,7 +1863,7 @@ Style/FormatStringToken: - 'lib/open_food_network/sales_tax_report.rb' - 'spec/models/enterprise_spec.rb' -# Offense count: 83 +# Offense count: 79 # Configuration parameters: MinBodyLength. Style/GuardClause: Exclude: @@ -1853,7 +1894,6 @@ Style/GuardClause: - 'app/models/producer_property.rb' - 'app/models/spree/classification_decorator.rb' - 'app/models/spree/order_decorator.rb' - - 'app/models/spree/order_populator_decorator.rb' - 'app/models/spree/preference_decorator.rb' - 'app/models/spree/product_decorator.rb' - 'app/models/spree/user_decorator.rb' @@ -1861,7 +1901,6 @@ Style/GuardClause: - 'lib/open_food_network/accounts_and_billing_settings_validator.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' - 'lib/open_food_network/products_cache.rb' - - 'lib/open_food_network/products_cache_refreshment.rb' - 'lib/open_food_network/products_renderer.rb' - 'lib/open_food_network/rack_request_blocker.rb' - 'lib/open_food_network/variant_and_line_item_naming.rb' @@ -1872,7 +1911,7 @@ Style/GuardClause: - 'spec/support/request/distribution_helper.rb' - 'spec/support/request/shop_workflow.rb' -# Offense count: 968 +# Offense count: 930 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys @@ -1887,14 +1926,12 @@ Style/HashSyntax: - 'app/controllers/admin/tag_rules_controller.rb' - 'app/controllers/api/enterprises_controller.rb' - 'app/controllers/checkout_controller.rb' - - 'app/controllers/open_food_network/cart_controller.rb' - 'app/controllers/spree/admin/line_items_controller_decorator.rb' - 'app/controllers/spree/admin/orders_controller_decorator.rb' - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'app/controllers/spree/admin/search_controller_decorator.rb' - 'app/controllers/spree/admin/shipping_methods_controller_decorator.rb' - 'app/controllers/spree/api/products_controller_decorator.rb' - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/controllers/spree/paypal_controller_decorator.rb' - 'app/controllers/spree/store_controller_decorator.rb' - 'app/controllers/user_passwords_controller.rb' @@ -1910,7 +1947,6 @@ Style/HashSyntax: - 'app/mailers/spree/user_mailer_decorator.rb' - 'app/models/billable_period.rb' - 'app/models/calculator/flat_percent_per_item.rb' - - 'app/models/cart.rb' - 'app/models/enterprise.rb' - 'app/models/enterprise_fee.rb' - 'app/models/enterprise_group.rb' @@ -1934,12 +1970,10 @@ Style/HashSyntax: - 'app/models/spree/product_set.rb' - 'app/models/spree/taxon_decorator.rb' - 'app/models/spree/user_decorator.rb' - - 'app/overrides/add_capture_order_shortcut.rb' - 'app/overrides/add_distributor_details_js_to_product.rb' - 'app/overrides/add_distributor_details_to_product.rb' - 'app/overrides/add_distributor_to_add_to_cart_form.rb' - 'app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb' - - 'app/overrides/add_orders_admin_sub_menu.rb' - 'app/overrides/add_source_to_product.rb' - 'app/overrides/remove_search_bar.rb' - 'app/overrides/remove_side_bar.rb' @@ -1982,13 +2016,11 @@ Style/HashSyntax: - 'spec/controllers/spree/api/products_controller_spec.rb' - 'spec/controllers/spree/api/variants_controller_spec.rb' - 'spec/controllers/spree/credit_cards_controller_spec.rb' - - 'spec/controllers/spree/orders_controller_spec.rb' - 'spec/controllers/spree/user_sessions_controller_spec.rb' - 'spec/controllers/user_registrations_controller_spec.rb' - 'spec/features/admin/bulk_order_management_spec.rb' - 'spec/features/admin/bulk_product_update_spec.rb' - 'spec/features/admin/customers_spec.rb' - - 'spec/features/admin/enterprise_fees_spec.rb' - 'spec/features/admin/enterprise_groups_spec.rb' - 'spec/features/admin/enterprises_spec.rb' - 'spec/features/admin/order_cycles_spec.rb' @@ -2001,6 +2033,8 @@ Style/HashSyntax: - 'spec/features/admin/subscriptions_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' - 'spec/features/consumer/account/cards_spec.rb' + - 'spec/features/consumer/cookies_spec.rb' + - 'spec/features/consumer/footer_links_spec.rb' - 'spec/features/consumer/shopping/products_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' - 'spec/jobs/subscription_placement_job_spec.rb' @@ -2011,7 +2045,6 @@ Style/HashSyntax: - 'spec/lib/open_food_network/tag_rule_applicator_spec.rb' - 'spec/mailers/order_mailer_spec.rb' - 'spec/models/calculator/weight_spec.rb' - - 'spec/models/cart_spec.rb' - 'spec/models/enterprise_fee_spec.rb' - 'spec/models/enterprise_spec.rb' - 'spec/models/exchange_spec.rb' @@ -2066,20 +2099,18 @@ Style/MethodCallWithoutArgsParentheses: Exclude: - 'app/controllers/spree/admin/payment_methods_controller_decorator.rb' - 'app/views/json/_groups.rabl' - - 'spec/controllers/spree/orders_controller_spec.rb' + - 'spec/controllers/cart_controller_spec.rb' - 'spec/features/consumer/registration_spec.rb' - 'spec/models/spree/payment_method_spec.rb' - 'spec/support/request/ui_component_helper.rb' -# Offense count: 13 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline Style/MethodDefParentheses: Exclude: - - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/enterprises_helper.rb' - - 'app/models/cart.rb' - 'app/models/spree/product_decorator.rb' - 'lib/open_food_network/distribution_change_validator.rb' - 'lib/open_food_network/feature_toggle.rb' @@ -2089,7 +2120,7 @@ Style/MethodDefParentheses: - 'spec/support/request/web_helper.rb' # Offense count: 1 -Style/MethodMissing: +Style/MissingRespondToMissing: Exclude: - 'app/helpers/application_helper.rb' @@ -2145,13 +2176,12 @@ Style/Next: Exclude: - 'lib/tasks/data.rake' -# Offense count: 7 +# Offense count: 6 # Cop supports --auto-correct. Style/NilComparison: Exclude: - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/order_grouper.rb' - - 'spec/features/admin/enterprise_fees_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' - 'spec/models/order_cycle_spec.rb' - 'spec/models/spree/order_spec.rb' @@ -2202,7 +2232,7 @@ Style/OneLineConditional: # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: AllowSafeAssignment. +# Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. Style/ParenthesesAroundCondition: Exclude: - 'app/controllers/checkout_controller.rb' @@ -2259,7 +2289,7 @@ Style/RedundantParentheses: - 'spec/controllers/admin/enterprises_controller_spec.rb' - 'spec/features/admin/bulk_product_update_spec.rb' -# Offense count: 10 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: @@ -2270,7 +2300,6 @@ Style/RedundantReturn: - 'app/models/enterprise_fee.rb' - 'app/models/spree/adjustment_decorator.rb' - 'app/models/spree/classification_decorator.rb' - - 'app/models/spree/order_populator_decorator.rb' - 'app/serializers/api/admin/enterprise_serializer.rb' # Offense count: 98 @@ -2306,7 +2335,7 @@ Style/RedundantSelf: - 'lib/open_food_network/reports/report.rb' - 'lib/open_food_network/variant_and_line_item_naming.rb' -# Offense count: 12 +# Offense count: 16 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed @@ -2319,6 +2348,7 @@ Style/RegexpLiteral: - 'app/models/enterprise_group.rb' - 'app/models/spree/preference_decorator.rb' - 'lib/discourse/single_sign_on.rb' + - 'spec/mailers/subscription_mailer_spec.rb' - 'spec/models/content_configuration_spec.rb' # Offense count: 4 @@ -2412,6 +2442,14 @@ Style/UnlessElse: - 'app/models/enterprise.rb' - 'lib/open_food_network/order_grouper.rb' +# Offense count: 5 +# Cop supports --auto-correct. +Style/UnneededCondition: + Exclude: + - 'app/controllers/admin/resource_controller.rb' + - 'app/controllers/application_controller.rb' + - 'app/serializers/api/order_serializer.rb' + # Offense count: 33 # Cop supports --auto-correct. Style/UnneededInterpolation: @@ -2458,7 +2496,7 @@ Style/UnneededPercentQ: - 'spec/features/consumer/producers_spec.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 6607 +# Offense count: 6683 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: diff --git a/Gemfile b/Gemfile index 2b09728c389..0e88665db5a 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,8 @@ gem 'i18n-js', '~> 3.0.0' # Patched version. See http://rubysec.com/advisories/CVE-2015-5312/. gem 'nokogiri', '>= 1.6.7.1' +gem 'web', path: './engines/web' + gem 'pg' # OFN-maintained and patched version of Spree v2.0.4. See @@ -50,7 +52,6 @@ gem 'aws-sdk' gem 'db2fog' gem 'andand' gem 'truncate_html' -gem 'representative_view' gem 'rabl' # AMS is pinned to 0.8.4 because 0.9.x is a complete re-write, as is 0.10.x @@ -91,8 +92,10 @@ group :assets do gem 'compass-rails' gem 'coffee-rails', '~> 3.2.1' - # See https://github.com/sstephenson/execjs#readme for more supported runtimes - gem 'therubyracer' + gem 'mini_racer' + # We found that the following version of libv8 breaks the compilation of mini_racer. + # Nothing else depends on libv8. + gem 'libv8', '!= 6.7.288.46.1' gem 'uglifier', '>= 1.0.3' @@ -107,7 +110,6 @@ gem 'foundation_rails_helper', github: 'willrjmarshall/foundation_rails_helper', gem 'jquery-rails', '3.0.0' gem 'jquery-migrate-rails' -gem 'css_splitter' gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', ref: '60da2ae4c44cbb4c8d602f59fb5fff8d0f21db3c' @@ -132,6 +134,7 @@ end group :test do gem 'webmock' + gem 'simplecov', require: false # See spec/spec_helper.rb for instructions #gem 'perftools.rb' end @@ -145,7 +148,6 @@ group :development do gem 'guard-livereload' gem 'guard-rails' gem 'guard-rspec', '~> 4.7.3' - gem 'parallel_tests' gem 'rubocop', '>= 0.49.1' # 1.0.9 fixed openssl issues on macOS https://github.com/eventmachine/eventmachine/issues/602 diff --git a/Gemfile.lock b/Gemfile.lock index dbda50ce5a1..d501e460df8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,10 +118,15 @@ GIT activemodel (>= 3.0) railties (>= 3.0) +PATH + remote: engines/web + specs: + web (0.0.1) + GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.2) + CFPropertyList (2.3.6) actionmailer (3.2.22.5) actionpack (= 3.2.22.5) mail (~> 2.5.4) @@ -232,15 +237,13 @@ GEM safe_yaml (~> 1.0.0) css_parser (1.3.5) addressable - css_splitter (0.4.5) - sprockets (>= 2.0.0) daemons (1.2.6) dalli (2.7.2) database_cleaner (0.7.1) - db2fog (0.8.0) - activerecord (~> 3.0) + db2fog (0.9.0) + activerecord (>= 3.2.0, < 5.0) fog (~> 1.0) - rails (~> 3.0) + rails (>= 3.2.0, < 5.0) debugger-linecache (1.2.0) deface (1.0.0) colorize (>= 0.5.8) @@ -260,13 +263,15 @@ GEM devise (>= 2.1.0) diff-lcs (1.3) diffy (3.1.0) + docile (1.3.1) + dry-inflector (0.1.2) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) erubis (2.7.0) eventmachine (1.2.7) - excon (0.45.4) - execjs (2.6.0) + excon (0.62.0) + execjs (2.7.0) factory_bot (4.10.0) activesupport (>= 3.0.0) factory_bot_rails (4.10.0) @@ -281,19 +286,26 @@ GEM rails (>= 3, < 5) fission (0.5.0) CFPropertyList (~> 2.2) - fog (1.36.0) + fog (1.41.0) fog-aliyun (>= 0.1.0) fog-atmos fog-aws (>= 0.6.0) fog-brightbox (~> 0.4) - fog-core (~> 1.32) + fog-cloudatcost (~> 0.1.0) + fog-core (~> 1.45) + fog-digitalocean (>= 0.3.0) + fog-dnsimple (~> 1.0) fog-dynect (~> 0.0.2) fog-ecloud (~> 0.1) fog-google (<= 0.1.0) + fog-internet-archive + fog-joyent fog-json fog-local + fog-openstack fog-powerdns (>= 0.1.1) fog-profitbricks + fog-rackspace fog-radosgw (>= 0.0.2) fog-riakcs fog-sakuracloud (>= 0.0.4) @@ -303,32 +315,47 @@ GEM fog-terremark fog-vmfusion fog-voxel + fog-vsphere (>= 0.4.0) fog-xenserver fog-xml (~> 0.1.1) ipaddress (~> 0.5) - nokogiri (~> 1.5, >= 1.5.11) - fog-aliyun (0.1.0) - fog-core (~> 1.27) - fog-json (~> 1.0) + json (>= 1.8, < 2.0) + fog-aliyun (0.3.2) + fog-core + fog-json ipaddress (~> 0.8) xml-simple (~> 1.1) fog-atmos (0.1.0) fog-core fog-xml - fog-aws (0.7.6) - fog-core (~> 1.27) + fog-aws (2.0.1) + fog-core (~> 1.38) fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) - fog-brightbox (0.9.0) - fog-core (~> 1.22) + fog-brightbox (0.16.1) + dry-inflector + fog-core fog-json - inflecto (~> 0.0.2) - fog-core (1.35.0) + mime-types + fog-cloudatcost (0.1.2) + fog-core (~> 1.36) + fog-json (~> 1.0) + fog-xml (~> 0.1) + ipaddress (~> 0.8) + fog-core (1.45.0) builder - excon (~> 0.45) + excon (~> 0.58) formatador (~> 0.2) - fog-dynect (0.0.2) + fog-digitalocean (0.4.0) + fog-core + fog-json + fog-xml + ipaddress (>= 0.5) + fog-dnsimple (1.0.0) + fog-core (~> 1.38) + fog-json (~> 1.0) + fog-dynect (0.0.3) fog-core fog-json fog-xml @@ -339,20 +366,35 @@ GEM fog-core fog-json fog-xml - fog-json (1.0.2) - fog-core (~> 1.0) + fog-internet-archive (0.0.1) + fog-core + fog-json + fog-xml + fog-joyent (0.0.1) + fog-core (~> 1.42) + fog-json (>= 1.0) + fog-json (1.2.0) + fog-core multi_json (~> 1.10) - fog-local (0.2.1) - fog-core (~> 1.27) - fog-powerdns (0.1.1) - fog-core (~> 1.27) - fog-json (~> 1.0) - fog-xml (~> 0.1) - fog-profitbricks (0.0.5) + fog-local (0.6.0) + fog-core (>= 1.27, < 3.0) + fog-openstack (0.1.25) + fog-core (~> 1.40) + fog-json (>= 1.0) + ipaddress (>= 0.8) + fog-powerdns (0.2.0) fog-core + fog-json fog-xml - nokogiri - fog-radosgw (0.0.4) + fog-profitbricks (4.1.1) + fog-core (~> 1.42) + fog-json (~> 1.0) + fog-rackspace (0.1.6) + fog-core (>= 1.35) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.8) + fog-radosgw (0.0.5) fog-core (>= 1.21.0) fog-json fog-xml (>= 0.0.1) @@ -360,13 +402,13 @@ GEM fog-core fog-json fog-xml - fog-sakuracloud (1.4.0) + fog-sakuracloud (1.7.5) fog-core fog-json fog-serverlove (0.1.2) fog-core fog-json - fog-softlayer (1.0.2) + fog-softlayer (1.1.4) fog-core fog-json fog-storm_on_demand (0.1.1) @@ -381,12 +423,15 @@ GEM fog-voxel (0.1.0) fog-core fog-xml - fog-xenserver (0.2.2) + fog-vsphere (2.3.0) + fog-core + rbvmomi (~> 1.9) + fog-xenserver (0.3.0) fog-core fog-xml - fog-xml (0.1.2) + fog-xml (0.1.3) fog-core - nokogiri (~> 1.5, >= 1.5.11) + nokogiri (>= 1.5.11, < 2.0.0) foreigner (1.7.4) activerecord (>= 3.0.0) formatador (0.2.5) @@ -436,8 +481,8 @@ GEM i18n (>= 0.6.6, < 2) immigrant (0.3.6) activerecord (>= 3.0) - inflecto (0.0.2) - ipaddress (0.8.0) + ipaddress (0.8.3) + jaro_winkler (1.5.1) journey (1.0.4) jquery-migrate-rails (1.2.1) jquery-rails (3.0.0) @@ -449,7 +494,7 @@ GEM json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) - jwt (1.5.4) + jwt (1.5.6) kaminari (0.14.1) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -460,7 +505,7 @@ GEM addressable (~> 2.3) letter_opener (1.6.0) launchy (~> 2.2) - libv8 (3.16.14.19) + libv8 (6.3.292.48.1) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -472,6 +517,8 @@ GEM mime-types (1.25.1) mini_mime (1.0.1) mini_portile2 (2.1.0) + mini_racer (0.1.15) + libv8 (~> 6.3) momentjs-rails (2.5.1) railties (>= 3.1) money (5.1.1) @@ -491,9 +538,9 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (2.1.2) + oj (3.6.10) orm_adapter (0.5.0) - paper_trail (3.0.8) + paper_trail (3.0.9) activerecord (>= 3.0, < 5.0) activesupport (>= 3.0, < 5.0) paperclip (3.4.2) @@ -503,12 +550,10 @@ GEM cocaine (~> 0.5.0) mime-types parallel (1.11.2) - parallel_tests (2.14.1) - parallel paranoia (1.3.4) activerecord (~> 3.1) - parser (2.4.0.0) - ast (~> 2.2) + parser (2.5.1.0) + ast (~> 2.4.0) paypal-sdk-core (0.2.10) multi_json (~> 1.0) xml-simple @@ -569,18 +614,14 @@ GEM rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) + rbvmomi (1.13.0) + builder (~> 3.0) + json (>= 1.8) + nokogiri (~> 1.5) + trollop (~> 2.1) rdoc (3.12.2) json (~> 1.4) redcarpet (3.2.3) - ref (2.0.0) - representative (1.0.5) - activesupport (>= 2.2.2) - builder (>= 2.1.2) - i18n (>= 0.4.1) - nokogiri (>= 1.4.2) - representative_view (1.2.2) - actionpack (> 2.3.0, < 4.0.0) - representative (~> 1.0.2) roadie (3.0.1) css_parser (~> 1.3.4) nokogiri (~> 1.6.0) @@ -614,10 +655,11 @@ GEM rspec-expectations (~> 3.7.0) rspec-mocks (~> 3.7.0) rspec-support (~> 3.7.0) - rspec-retry (0.5.6) - rspec-core (> 3.3, < 3.8) + rspec-retry (0.6.1) + rspec-core (> 3.3) rspec-support (3.7.1) - rubocop (0.55.0) + rubocop (0.57.2) + jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) @@ -626,7 +668,7 @@ GEM unicode-display_width (~> 1.0, >= 1.0.1) ruby-ole (1.2.12.1) ruby-progressbar (1.10.0) - rubyzip (1.2.1) + rubyzip (1.2.2) safe_yaml (1.0.4) sass (3.3.14) sass-rails (3.2.6) @@ -639,6 +681,11 @@ GEM shellany (0.0.1) shoulda-matchers (2.8.0) activesupport (>= 3.0.0) + simplecov (0.16.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) skylight (1.6.1) activesupport (>= 3.0.0) spinjs-rails (1.3) @@ -654,24 +701,21 @@ GEM stringex (1.5.1) stripe (3.3.2) faraday (~> 0.9) - therubyracer (0.12.3) - libv8 (~> 3.16.14.15) - ref thor (0.20.0) tilt (1.4.1) timecop (0.9.1) treetop (1.4.15) polyglot polyglot (>= 0.3.1) + trollop (2.9.9) truncate_html (0.9.2) turbo-sprockets-rails3 (0.3.6) railties (> 3.2.8, < 4.0.0) sprockets (>= 2.0.0) tzinfo (0.3.54) - uglifier (2.7.1) - execjs (>= 0.3.0) - json (>= 1.8.0) - unicode-display_width (1.3.0) + uglifier (4.1.19) + execjs (>= 0.3.0, < 3) + unicode-display_width (1.3.2) unicorn (4.9.0) kgio (~> 2.6) rack @@ -722,7 +766,7 @@ DEPENDENCIES capybara (>= 2.15.4) coffee-rails (~> 3.2.1) compass-rails - css_splitter + web! custom_error_message! daemons dalli @@ -756,7 +800,9 @@ DEPENDENCIES jwt (~> 1.5) knapsack letter_opener (>= 1.4.1) + libv8 (!= 6.7.288.46.1) listen (= 3.0.8) + mini_racer momentjs-rails nokogiri (>= 1.6.7.1) oauth2 (~> 1.2.0) @@ -764,7 +810,6 @@ DEPENDENCIES oj paper_trail (~> 3.0.8) paperclip (~> 3.4.1) - parallel_tests pg poltergeist (>= 1.16.0) pry-byebug (>= 3.4.3) @@ -774,7 +819,6 @@ DEPENDENCIES rails (~> 3.2.22) rails-i18n (~> 3.0.0) redcarpet - representative_view roadie-rails (~> 1.0.3) roo (~> 2.7.0) roo-xls (~> 1.1.0) @@ -785,6 +829,7 @@ DEPENDENCIES sass-rails (~> 3.2.3) shoulda-matchers simple_form! + simplecov skylight (< 2.0) spinjs-rails spree! @@ -792,7 +837,6 @@ DEPENDENCIES spree_i18n! spree_paypal_express! stripe (~> 3.3.2) - therubyracer timecop truncate_html turbo-sprockets-rails3 diff --git a/README.md b/README.md index f65b505a1c4..c2b07601a77 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/openfoodfoundation/openfoodnetwork.svg?branch=master)](https://travis-ci.org/openfoodfoundation/openfoodnetwork) +[![Build Status](https://semaphoreci.com/api/v1/openfoodfoundation/openfoodnetwork-2/branches/master/badge.svg)](https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2) [![Code Climate](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork.png)](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork) [![View performance data on Skylight](https://badges.skylight.io/status/EiXQ6sSKij8y.svg)](https://oss.skylight.io/app/applications/EiXQ6sSKij8y) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index df8cef60e56..f03c5417d17 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -61,7 +61,7 @@ //= require moment/nb.js //= require moment/pt-br.js //= require moment/sv.js -//= require ../shared/mm-foundation-tpls-0.8.0.min.js +//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js //= require angularjs-file-upload //= require_tree . diff --git a/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee b/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee index f69c5365d5e..1006ac7aa46 100644 --- a/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee @@ -8,8 +8,8 @@ angular.module("admin.enterprise_groups") { name: 'users', label: t('users'), icon_class: "icon-user" } { name: 'about', label: t('about'), icon_class: "icon-pencil" } { name: 'images', label: t('images'), icon_class: "icon-picture" } - { name: 'contact', label: t('admin_entreprise_groups_contact'), icon_class: "icon-phone" } - { name: 'web', label: t('admin_entreprise_groups_web'), icon_class: "icon-globe" } + { name: 'contact', label: t('admin_enterprise_groups_contact'), icon_class: "icon-phone" } + { name: 'web', label: t('admin_enterprise_groups_web'), icon_class: "icon-globe" } ] $scope.select(0) diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee index bb1ae6b31a0..4acd61e84bf 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee @@ -1,5 +1,5 @@ angular.module("admin.enterprises") - .controller "enterpriseCtrl", ($scope, $http, $window, NavigationCheck, enterprise, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage) -> + .controller "enterpriseCtrl", ($scope, $http, $window, NavigationCheck, enterprise, Enterprises, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage) -> $scope.Enterprise = enterprise $scope.PaymentMethods = EnterprisePaymentMethods.paymentMethods $scope.ShippingMethods = EnterpriseShippingMethods.shippingMethods @@ -67,3 +67,27 @@ angular.module("admin.enterprises") $scope.resetModal = -> $scope.newUser = $scope.invite_errors = $scope.invite_success = null + + $scope.removeLogo = -> + return unless confirm(t("admin.enterprises.remove_logo.immediate_removal_warning")) + + Enterprises.removeLogo($scope.Enterprise).then (data) -> + $scope.Enterprise = angular.copy(data) + $scope.$emit("enterprise:updated", $scope.Enterprise) + + StatusMessage.display("success", t("admin.enterprises.remove_logo.removed_successfully")) + , (response) -> + if response.data.error? + StatusMessage.display("failure", response.data.error) + + $scope.removePromoImage = -> + return unless confirm(t("admin.enterprises.remove_promo_image.immediate_removal_warning")) + + Enterprises.removePromoImage($scope.Enterprise).then (data) -> + $scope.Enterprise = angular.copy(data) + $scope.$emit("enterprise:updated", $scope.Enterprise) + + StatusMessage.display("success", t("admin.enterprises.remove_promo_image.removed_successfully")) + , (response) -> + if response.data.error? + StatusMessage.display("failure", response.data.error) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/order_cycles_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/order_cycles_controller.js.coffee index 75f379856d0..7ed549e5258 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/order_cycles_controller.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/order_cycles_controller.js.coffee @@ -3,8 +3,12 @@ angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, C $scope.columns = Columns.columns $scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form) $scope.ordersCloseAtLimit = -31 # days - $scope.involvingFilter = 0 - $scope.scheduleFilter = 0 + + $scope.resetSelectFilters = -> + $scope.scheduleFilter = 0 + $scope.involvingFilter = 0 + $scope.query = '' + $scope.resetSelectFilters() compileData = -> for schedule in $scope.schedules diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 59d9b4f994a..cac1c9ca87f 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -156,7 +156,7 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S if response.data.errors? StatusMessage.display('failure', response.data.errors[0]) else - StatusMessage.display('failure', 'Failed to create order cycle') + StatusMessage.display('failure', t('js.order_cycles.create_failure')) update: (destination, form) -> return unless @confirmNoDistributors() @@ -171,7 +171,7 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S if response.data.errors? StatusMessage.display('failure', response.data.errors[0]) else - StatusMessage.display('failure', 'Failed to create order cycle') + StatusMessage.display('failure', t('js.order_cycles.update_failure')) confirmNoDistributors: -> diff --git a/app/assets/javascripts/admin/orders/controllers/order_controller.js.coffee b/app/assets/javascripts/admin/orders/controllers/order_controller.js.coffee new file mode 100644 index 00000000000..822e81eb49c --- /dev/null +++ b/app/assets/javascripts/admin/orders/controllers/order_controller.js.coffee @@ -0,0 +1,26 @@ +angular.module("admin.orders").controller "orderCtrl", ($scope, shops, orderCycles, $compile, $attrs, Orders) -> + $scope.$compile = $compile + $scope.shops = shops + $scope.orderCycles = orderCycles + + $scope.distributor_id = parseInt($attrs.ofnDistributorId) + $scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId) + + $scope.validOrderCycle = (oc) -> + $scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id) + + $scope.distributorHasOrderCycles = (distributor) -> + (oc for oc in $scope.orderCycles when @orderCycleHasDistributor(oc, distributor.id)).length > 0 + + $scope.orderCycleHasDistributor = (oc, distributor_id) -> + distributor_ids = (d.id for d in oc.distributors) + distributor_ids.indexOf(distributor_id) != -1 + + $scope.distributionChosen = -> + $scope.distributor_id && $scope.order_cycle_id + + for oc in $scope.orderCycles + oc.name_and_status = "#{oc.name} (#{oc.status})" + + for shop in $scope.shops + shop.disabled = !$scope.distributorHasOrderCycles(shop) diff --git a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee index 060b23bed96..b2c1e692f07 100644 --- a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee +++ b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee @@ -1,26 +1,47 @@ -angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attrs, shops, orderCycles) -> - $scope.$compile = $compile - $scope.shops = shops - $scope.orderCycles = orderCycles +angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor, Orders, SortOptions) -> + $scope.RequestMonitor = RequestMonitor + $scope.pagination = Orders.pagination + $scope.orders = Orders.all + $scope.sortOptions = SortOptions + $scope.per_page_options = [ + {id: 15, name: t('js.admin.orders.index.per_page', results: 15)}, + {id: 50, name: t('js.admin.orders.index.per_page', results: 50)}, + {id: 100, name: t('js.admin.orders.index.per_page', results: 100)} + ] - $scope.distributor_id = parseInt($attrs.ofnDistributorId) - $scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId) + $scope.initialise = -> + $scope.per_page = 15 + $scope.q = { + completed_at_not_null: true + } + $scope.fetchResults() - $scope.validOrderCycle = (oc) -> - $scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id) + $scope.fetchResults = (page=1) -> + Orders.index({ + 'q[created_at_lt]': $scope['q']['created_at_lt'], + 'q[created_at_gt]': $scope['q']['created_at_gt'], + 'q[state_eq]': $scope['q']['state_eq'], + 'q[number_cont]': $scope['q']['number_cont'], + 'q[email_cont]': $scope['q']['email_cont'], + 'q[bill_address_firstname_start]': $scope['q']['bill_address_firstname_start'], + 'q[bill_address_lastname_start]': $scope['q']['bill_address_lastname_start'], + 'q[completed_at_not_null]': $scope['q']['completed_at_not_null'], + 'q[inventory_units_shipment_id_null]': $scope['q']['inventory_units_shipment_id_null'], + 'q[distributor_id_in]': $scope['q']['distributor_id_in'], + 'q[order_cycle_id_in]': $scope['q']['order_cycle_id_in'], + 'q[order_cycle_id_in]': $scope['q']['order_cycle_id_in'], + 'q[s]': $scope.sorting || 'id desc', + per_page: $scope.per_page, + page: page + }) - $scope.distributorHasOrderCycles = (distributor) -> - (oc for oc in orderCycles when @orderCycleHasDistributor(oc, distributor.id)).length > 0 + $scope.$watch 'sortOptions', (sort) -> + if sort && sort.predicate != "" + $scope.sorting = sort.predicate + ' desc' if sort.reverse + $scope.sorting = sort.predicate + ' asc' if !sort.reverse + $scope.fetchResults() + , true - $scope.orderCycleHasDistributor = (oc, distributor_id) -> - distributor_ids = (d.id for d in oc.distributors) - distributor_ids.indexOf(distributor_id) != -1 - - $scope.distributionChosen = -> - $scope.distributor_id && $scope.order_cycle_id - - for oc in $scope.orderCycles - oc.name_and_status = "#{oc.name} (#{oc.status})" - - for shop in $scope.shops - shop.disabled = !$scope.distributorHasOrderCycles(shop) + $scope.changePage = (newPage) -> + $scope.page = newPage + $scope.fetchResults(newPage) diff --git a/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee b/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee index 75b506fc664..d9b73cdabf8 100644 --- a/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee +++ b/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee @@ -51,7 +51,7 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt $scope.start = () -> $scope.started = true total = ams_data.item_count - size = 100 + size = 50 $scope.chunks = Math.ceil(total / size) i = 0 diff --git a/app/assets/javascripts/admin/product_import/filters/filter_entries.js.coffee b/app/assets/javascripts/admin/product_import/filters/filter_entries.js.coffee index 4a1007b3a92..efbcf5652b4 100644 --- a/app/assets/javascripts/admin/product_import/filters/filter_entries.js.coffee +++ b/app/assets/javascripts/admin/product_import/filters/filter_entries.js.coffee @@ -10,7 +10,7 @@ angular.module("admin.productImport").filter 'entriesFilterValid', -> if type == 'valid' and validates_as != '' \ or type == 'invalid' and validates_as == '' \ - or type == 'create_product' and validates_as == 'new_product' or validates_as == 'new_variant' \ + or type == 'create_product' and (validates_as == 'new_product' or validates_as == 'new_variant') \ or type == 'update_product' and validates_as == 'existing_variant' \ or type == 'create_inventory' and validates_as == 'new_inventory_item' \ or type == 'update_inventory' and validates_as == 'existing_inventory_item' diff --git a/app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee index 7cdd5b7bce2..ec89bbda36a 100644 --- a/app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee @@ -8,4 +8,10 @@ angular.module("admin.resources").factory 'EnterpriseResource', ($resource) -> isArray: true 'update': method: 'PUT' + 'removeLogo': + url: '/api/enterprises/:id/logo.json' + method: 'DELETE' + 'removePromoImage': + url: '/api/enterprises/:id/promo_image.json' + method: 'DELETE' }) diff --git a/app/assets/javascripts/admin/resources/resources/order_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/order_resource.js.coffee index 317b384485a..d5679c629ed 100644 --- a/app/assets/javascripts/admin/resources/resources/order_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/order_resource.js.coffee @@ -2,7 +2,6 @@ angular.module("admin.resources").factory 'OrderResource', ($resource) -> $resource('/admin/orders/:id/:action.json', {}, { 'index': method: 'GET' - isArray: true 'update': method: 'PUT' }) diff --git a/app/assets/javascripts/admin/resources/services/enterprises.js.coffee b/app/assets/javascripts/admin/resources/services/enterprises.js.coffee index 0b7fa6e8702..435cc885000 100644 --- a/app/assets/javascripts/admin/resources/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/resources/services/enterprises.js.coffee @@ -38,3 +38,17 @@ angular.module("admin.resources").factory 'Enterprises', ($q, EnterpriseResource resetAttribute: (enterprise, attribute) -> enterprise[attribute] = @pristineByID[enterprise.id][attribute] + + performActionOnEnterpriseResource = (resourceAction) -> + (enterprise) -> + deferred = $q.defer() + resourceAction({id: enterprise.permalink}, ((data) => + @pristineByID[enterprise.id] = angular.copy(data) + deferred.resolve(data) + ), ((response) -> + deferred.reject(response) + )) + deferred.promise + + removeLogo: performActionOnEnterpriseResource(EnterpriseResource.removeLogo) + removePromoImage: performActionOnEnterpriseResource(EnterpriseResource.removePromoImage) diff --git a/app/assets/javascripts/admin/resources/services/orders.js.coffee b/app/assets/javascripts/admin/resources/services/orders.js.coffee index da3f409149a..5eef10eecbc 100644 --- a/app/assets/javascripts/admin/resources/services/orders.js.coffee +++ b/app/assets/javascripts/admin/resources/services/orders.js.coffee @@ -1,18 +1,30 @@ -angular.module("admin.resources").factory 'Orders', ($q, OrderResource) -> +angular.module("admin.resources").factory 'Orders', ($q, OrderResource, RequestMonitor) -> new class Orders + all: [] byID: {} pristineByID: {} + pagination: {} index: (params={}, callback=null) -> - OrderResource.index params, (data) => + request = OrderResource.index params, (data) => @load(data) (callback || angular.noop)(data) + RequestMonitor.load(request.$promise) + @all - load: (orders) -> - for order in orders + load: (data) -> + angular.extend(@pagination, data.pagination) + @clearData() + for order in data.orders + @all.push order @byID[order.id] = order @pristineByID[order.id] = angular.copy(order) + clearData: -> + @all.length = 0 + @byID = {} + @pristineByID = {} + save: (order) -> deferred = $q.defer() order.$update({id: order.number}) diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index 32ee0b2255b..81324c0c56c 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -11,7 +11,7 @@ #= require lodash.underscore.js #= require angular-scroll.min.js #= require angular-google-maps.min.js -#= require ../shared/mm-foundation-tpls-0.8.0.min.js +#= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js #= require ../shared/ng-infinite-scroll.min.js #= require ../shared/angular-local-storage.js #= require ../shared/angular-slideables.js diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js deleted file mode 100644 index 8f4e630b9f2..00000000000 --- a/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * angular-mm-foundation - * http://pineconellc.github.io/angular-foundation/ - - * Version: 0.8.0 - 2015-10-13 - * License: MIT - * (c) Pinecone, LLC - */ -angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","mm.foundation.dropdownToggle","mm.foundation.interchange","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.topbar","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/topbar/has-dropdown.html","template/topbar/toggle-top-bar.html","template/topbar/top-bar-dropdown.html","template/topbar/top-bar-section.html","template/topbar/top-bar.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b&&"undefined"!=typeof b.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.mediaQueries",[]).factory("matchMedia",["$document","$window",function(a,b){return b.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a[0])}]).factory("mediaQueries",["$document","matchMedia",function(a,b){var c=angular.element(a[0].querySelector("head"));c.append(''),c.append(''),c.append(''),c.append('');var d=/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,e={topbar:getComputedStyle(c[0].querySelector("meta.foundation-mq-topbar")).fontFamily.replace(d,""),small:getComputedStyle(c[0].querySelector("meta.foundation-mq-small")).fontFamily.replace(d,""),medium:getComputedStyle(c[0].querySelector("meta.foundation-mq-medium")).fontFamily.replace(d,""),large:getComputedStyle(c[0].querySelector("meta.foundation-mq-large")).fontFamily.replace(d,"")};return{topbarBreakpoint:function(){return!b(e.topbar).matches},small:function(){return b(e.small).matches},medium:function(){return b(e.medium).matches},large:function(){return b(e.large).matches}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position","mm.foundation.mediaQueries"]).controller("DropdownToggleController",["$scope","$attrs","mediaQueries",function(a,b,c){this.small=function(){return c.small()&&!c.medium()}}]).directive("dropdownToggle",["$document","$window","$location","$position",function(a,b,c,d){var e=null,f=angular.noop;return{restrict:"CA",controller:"DropdownToggleController",link:function(c,g,h,i){var j=g.parent(),k=angular.element(a[0].querySelector(h.dropdownToggle)),l=function(){return j.hasClass("has-dropdown")},m=function(c){k=angular.element(a[0].querySelector(h.dropdownToggle));var m=g===e;if(c.preventDefault(),c.stopPropagation(),e&&f(),!m&&!g.hasClass("disabled")&&!g.prop("disabled")){k.css("display","block"),k.addClass("f-open-dropdown");var n=d.offset(g),o=d.offset(angular.element(k[0].offsetParent)),p=k.prop("offsetWidth"),q={top:n.top-o.top+n.height+"px"};if(i.small())q.left=Math.max((o.width-p)/2,8)+"px",q.position="absolute",q.width="95%",q["max-width"]="none";else{var r=Math.round(n.left-o.left),s=b.innerWidth-p-8;r>s&&(r=s,k.removeClass("left").addClass("right")),q.left=r+"px",q.position=null,q["max-width"]=null}k.css(q),g.addClass("expanded"),l()&&j.addClass("hover"),e=g,f=function(){a.off("click",f),k.css("display","none"),k.removeClass("f-open-dropdown"),g.removeClass("expanded"),f=angular.noop,e=null,j.hasClass("hover")&&j.removeClass("hover")},a.on("click",f)}};k&&k.css("display","none"),c.$watch("$location.path",function(){f()}),g.on("click",m),g.on("$destroy",function(){g.off("click",m)})}}}]),angular.module("mm.foundation.interchange",["mm.foundation.mediaQueries"]).factory("interchangeQueries",["$document",function(a){for(var b,c,d={"default":"only screen",landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},e="foundation-mq-",f=["small","medium","large","xlarge","xxlarge"],g=angular.element(a[0].querySelector("head")),h=0;h'),b=getComputedStyle(g[0].querySelector("meta."+e+f[h])),c=b.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,""),d[f[h]]=c;return d}]).factory("interchangeQueriesManager",["interchangeQueries",function(a){return{add:function(b,c){return b&&c&&angular.isString(b)&&angular.isString(c)&&!a[b]?(a[b]=c,!0):!1}}}]).factory("interchangeTools",["$window","matchMedia","interchangeQueries",function(a,b,c){var d=function(a){for(var b,c=a.split(/\[(.*?)\]/),d=c.length,e=/^(.+)\,\ \((.+)\)$/,f={};d--;)c[d].replace(/[\W\d]+/,"").length>4&&(b=e.exec(c[d]),b&&3===b.length&&(f[b[2]]=b[1]));return f},e=function(a){var d,e,f;for(d in a)if(e=c[d]||d,f=b(e),f.matches)return a[d]};return{parseAttribute:d,findCurrentMediaFile:e}}]).directive("interchange",["$window","$rootScope","interchangeTools",function(a,b,c){var d=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;return{restrict:"A",scope:!0,priority:450,compile:function(e,f){return"DIV"!==e[0].nodeName||d.test(f.interchange)||e.html(''),{pre:function(){},post:function(d,e,f){var g,h;switch(h=e&&e[0]&&e[0].nodeName,d.fileMap=c.parseAttribute(f.interchange),h){case"DIV":g=c.findCurrentMediaFile(d.fileMap),d.type=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(g)?"background":"include";break;case"IMG":d.type="image";break;default:return}var i=function(a){var f=c.findCurrentMediaFile(d.fileMap);if(!d.currentFile||d.currentFile!==f){switch(d.currentFile=f,d.type){case"image":e.attr("src",d.currentFile);break;case"background":e.css("background-image","url("+d.currentFile+")")}b.$emit("replace",e,d),a&&d.$apply()}};i(),a.addEventListener("resize",i),d.$on("$destroy",function(){a.removeEventListener("resize",i)})}}}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0?c[0].querySelectorAll("[autofocus]")[0].focus():c[0].focus()})}}}]).factory("$modalStack",["$window","$transition","$timeout","$document","$compile","$rootScope","$$stackedMap",function(a,b,c,d,e,f,g){function h(){for(var a=-1,b=q.keys(),c=0;c0),j()})}function j(){if(m&&-1==h()){var a=n;k(m,n,150,function(){a.$destroy(),a=null}),m=void 0,n=void 0}}function k(a,d,e,f){function g(){g.done||(g.done=!0,a.remove(),f&&f())}d.animate=!1;var h=b.transitionEndEventName;if(h){var i=c(g,e);a.bind(h,function(){c.cancel(i),g(),d.$apply()})}else c(g,0)}function l(b,c){angular.isUndefined(c)&&(c=0);var d=a.pageYOffset||0;return c+d}var m,n,o,p="modal-open",q=g.createNew(),r={};return f.$watch(h,function(a){n&&(n.index=a)}),d.bind("keydown",function(a){var b;27===a.which&&(b=q.top(),b&&b.value.keyboard&&f.$apply(function(){r.dismiss(b.key)}))}),r.open=function(b,c){b.options={deferred:c.deferred,modalScope:c.scope,backdrop:c.backdrop,keyboard:c.keyboard,parent:c.parent},q.add(b,b.options);var g=d.find(c.parent).eq(0),i=h();i>=0&&!m&&(n=f.$new(!0),n.index=i,m=e("
")(n),g.append(m));var j=angular.element('
');g.append(j[0]),o=parseInt(a.getComputedStyle(j[0]).top)||0;var k=l(j,o);j.remove();var r=angular.element('
').attr({"window-class":c.windowClass,index:q.length()-1,animate:"animate"});r.html(c.content);var s=e(r)(c.scope);q.top().value.modalDomEl=s,g.append(s),g.addClass(p)},r.reposition=function(a){var b=q.get(a).value;if(b){var c=b.modalDomEl,d=l(c,o);c.css("top",d+"px")}},r.close=function(a,b){var c=q.get(a);c&&(c.value.deferred.resolve(b),i(a))},r.dismiss=function(a,b){var c=q.get(a);c&&(c.value.deferred.reject(b),i(a))},r.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},r.getTop=function(){return q.top()},r}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)},reposition:function(){h.reposition(k)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,parent:b.parent||"body"})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d[l+"Placement"]=d[l+"Placement"]||null,d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)&&a?a:o.placement}),d[l+"PopupDelay"]=d[l+"PopupDelay"]||null,d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(angular.isFunction(x.show)?C():(c.unbind(x.show,k),c.unbind(x.hide,m)))},C=function(){};d[l+"Trigger"]=d[l+"Trigger"]||null,d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
'}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];angular.isUndefined(a.openOnLoad)&&(a.openOnLoad=!0),b.select=function(a){angular.forEach(c,function(a){a.active=!1}),a.active=!0},b.addTab=function(d){c.push(d),a.openOnLoad&&(1===c.length||d.active)&&b.select(d)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{openOnLoad:"=?"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.$watch("active",function(a){angular.isFunction(h)&&(h(b.$parent,a),a?(f.select(b),b.onSelect()):b.onDeselect())}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.topbar",["mm.foundation.mediaQueries"]).factory("closest",[function(){return function(a,b){for(var c=function(a,b){for(var c=(a.parentNode||a.document).querySelectorAll(b),d=-1;c[++d]&&c[d]!=a;);return!!c[d]},d=a[0];d;){if(c(d,b))return angular.element(d);d=d.parentElement}return!1}}]).directive("topBar",["$timeout","$compile","$window","$document","mediaQueries",function(a,b,c,d,e){return{scope:{stickyClass:"@",backText:"@",stickyOn:"=",customBackText:"=",isHover:"=",mobileShowParentLink:"=",scrolltop:"="},restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar.html",transclude:!0,controller:["$window","$scope","closest",function(b,c,f){c.settings={},c.settings.stickyClass=c.stickyClass||"sticky",c.settings.backText=c.backText||"Back",c.settings.stickyOn=c.stickyOn||"all",c.settings.customBackText=void 0===c.customBackText?!0:c.customBackText,c.settings.isHover=void 0===c.isHover?!0:c.isHover,c.settings.mobileShowParentLink=void 0===c.mobileShowParentLink?!0:c.mobileShowParentLink,c.settings.scrolltop=void 0===c.scrolltop?!0:c.scrolltop,this.settings=c.settings,c.index=0;var g=function(a){var b=a.offsetHeight,c=a.currentStyle||getComputedStyle(a);return b+=parseInt(c.marginTop,10)+parseInt(c.marginBottom,10)},h=[];this.addSection=function(a){h.push(a)},this.removeSection=function(a){var b=h.indexOf(a);b>-1&&h.splice(b,1)};var i=/rtl/i.test(d.find("html").attr("dir"))?"right":"left";c.$watch("index",function(a){for(var b=0;bb&&!g.hasClass("fixed")?(g.addClass("fixed"),h.css("padding-top",a.originalHeight+"px")):c.pageYOffset<=b&&g.hasClass("fixed")&&(g.removeClass("fixed"),h.css("padding-top",""))}},l=function(){var b=e.topbarBreakpoint();if(i!==b){i=e.topbarBreakpoint(),f.removeClass("expanded"),f.parent().removeClass("expanded"),a.height="";var c=angular.element(f[0].querySelectorAll("section"));angular.forEach(c,function(a){angular.element(a.querySelectorAll("li.moved")).removeClass("moved")}),a.$apply()}},m=function(){k(),a.$apply()};if(a.toggle=function(b){if(!e.topbarBreakpoint())return!1;var d=void 0===b?!f.hasClass("expanded"):b;d?f.addClass("expanded"):f.removeClass("expanded"),a.settings.scrolltop?!d&&f.hasClass("fixed")?(f.parent().addClass("fixed"),f.removeClass("fixed"),h.css("padding-top",a.originalHeight+"px")):d&&f.parent().hasClass("fixed")&&(f.parent().removeClass("fixed"),f.addClass("fixed"),h.css("padding-top",""),c.scrollTo(0,0)):(j()&&f.parent().addClass("fixed"),f.parent().hasClass("fixed")&&(d?(f.addClass("fixed"),f.parent().addClass("expanded"),h.css("padding-top",a.originalHeight+"px")):(f.removeClass("fixed"),f.parent().removeClass("expanded"),k())))},g.hasClass("fixed")||j()){a.stickyTopbar=!0,a.height=g[0].offsetHeight;var n=g[0].getBoundingClientRect().top+c.pageYOffset}else a.height=f[0].offsetHeight;a.originalHeight=a.height,a.$watch("height",function(a){a?f.css("height",a+"px"):f.css("height","")}),angular.element(c).bind("resize",l),angular.element(c).bind("scroll",m),a.$on("$destroy",function(){angular.element(c).unbind("scroll",l),angular.element(c).unbind("resize",m)}),g.hasClass("fixed")&&h.css("padding-top",a.originalHeight+"px")}}}]).directive("toggleTopBar",["closest",function(a){return{scope:{},require:"^topBar",restrict:"A",replace:!0,templateUrl:"template/topbar/toggle-top-bar.html",transclude:!0,link:function(b,c,d,e){c.bind("click",function(b){var c=a(angular.element(b.currentTarget),"li");c.hasClass("back")||c.hasClass("has-dropdown")||e.toggle()}),b.$on("$destroy",function(){c.unbind("click")})}}}]).directive("topBarSection",["$compile","closest",function(a,b){return{scope:{},require:"^topBar",restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar-section.html",transclude:!0,link:function(a,c,d,e){var f=c;a.reset=function(){angular.element(f[0].querySelectorAll("li.moved")).removeClass("moved")},a.move=function(a,b){f.css("left"===a?{left:-100*b+"%"}:{right:-100*b+"%"})},e.addSection(a),a.$on("$destroy",function(){e.removeSection(a)});var g=f[0].querySelectorAll("li>a");angular.forEach(g,function(c){var d=angular.element(c),f=b(d,"li");f.hasClass("has-dropdown")||f.hasClass("back")||f.hasClass("title")||(d.bind("click",function(){e.toggle(!1)}),a.$on("$destroy",function(){d.bind("click")}))})}}}]).directive("hasDropdown",["mediaQueries",function(a){return{scope:{},require:"^topBar",restrict:"A",templateUrl:"template/topbar/has-dropdown.html",replace:!0,transclude:!0,link:function(b,c,d,e){b.triggerLink=c.children("a")[0];var f=angular.element(b.triggerLink);f.bind("click",function(a){e.forward(a)}),b.$on("$destroy",function(){f.unbind("click")}),c.bind("mouseenter",function(){e.settings.isHover&&!a.topbarBreakpoint()&&c.addClass("not-click")}),c.bind("click",function(){e.settings.isHover||a.topbarBreakpoint()||c.toggleClass("not-click")}),c.bind("mouseleave",function(){c.removeClass("not-click")}),b.$on("$destroy",function(){c.unbind("click"),c.unbind("mouseenter"),c.unbind("mouseleave")})},controller:["$window","$scope",function(a,b){this.triggerLink=b.triggerLink}]}}]).directive("topBarDropdown",["$compile",function(a){return{scope:{},require:["^topBar","^hasDropdown"],restrict:"A",replace:!0,templateUrl:"template/topbar/top-bar-dropdown.html",transclude:!0,link:function(b,c,d,e){var f,g=e[0],h=e[1],i=angular.element(h.triggerLink),j=i.attr("href");b.linkText=i.text(),b.back=function(a){g.back(a)},b.backText=g.settings.customBackText?g.settings.backText:"« "+i.html(),f=angular.element(g.settings.mobileShowParentLink&&j&&j.length>1?'
  • {{backText}}
  • {{linkText}}
  • ':'
  • {{backText}}
  • '),a(f)(b),c.prepend(f)}}}]),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){try{return parseInt(a.localStorage.getItem("mm.tour.step"),10)}catch(b){if("SecurityError"!==b.name)throw b}}function c(){try{a.localStorage.setItem("mm.tour.step",e)}catch(b){if("SecurityError"!==b.name)throw b}}function d(a){e=a,c()}var e=b(),f={};this.add=function(a,b){f[a]=b},this.has=function(a){return!!f[a]},this.isActive=function(){return e>0},this.current=function(a){return a?void d(e):e},this.start=function(){d(1)},this.next=function(){d(e+1)},this.end=function(){d(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()},b.$on("$locationChangeSuccess",b.endTour)}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("
    ");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m)if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1}),j.bind("focus",function(){m=!0});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
    \n {{heading}}\n
    \n
    \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
    \n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","
    \n \n ×\n
    \n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
    \n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'
    \n
    \n
    \n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
    \n \n
    \n

    \n

    \n
    \n
    \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
    \n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
    \n \n
    \n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
    \n {{heading}}\n
    \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/topbar/has-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/has-dropdown.html",'
  • ')}]),angular.module("template/topbar/toggle-top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/toggle-top-bar.html",'')}]),angular.module("template/topbar/top-bar-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-dropdown.html",'')}]),angular.module("template/topbar/top-bar-section.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-section.html",'
    ')}]),angular.module("template/topbar/top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar.html",'')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'
    \n \n
    \n

    \n

    \n Next\n End\n ×\n
    \n
    \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","
      \n"+'
    • \n
      \n
    • \n
    \n')}]); diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.9.0-20180826174721.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.9.0-20180826174721.min.js new file mode 100644 index 00000000000..1df49b5e6ea --- /dev/null +++ b/app/assets/javascripts/shared/mm-foundation-tpls-0.9.0-20180826174721.min.js @@ -0,0 +1,10 @@ +/* + * angular-mm-foundation + * http://pineconellc.github.io/angular-foundation/ + + * Version: 0.9.0-20180826174721 - 2018-08-27 + * License: MIT + * (c) Pinecone, LLC + */ +angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","mm.foundation.dropdownToggle","mm.foundation.interchange","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.topbar","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/topbar/has-dropdown.html","template/topbar/toggle-top-bar.html","template/topbar/top-bar-dropdown.html","template/topbar/top-bar-section.html","template/topbar/top-bar.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b&&"undefined"!=typeof b.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.mediaQueries",[]).factory("matchMedia",["$document","$window",function(a,b){return b.matchMedia||function(a,b){var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='­',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(a[0])}]).factory("mediaQueries",["$document","matchMedia",function(a,b){var c=angular.element(a[0].querySelector("head"));c.append(''),c.append(''),c.append(''),c.append('');var d=/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,e={topbar:getComputedStyle(c[0].querySelector("meta.foundation-mq-topbar")).fontFamily.replace(d,""),small:getComputedStyle(c[0].querySelector("meta.foundation-mq-small")).fontFamily.replace(d,""),medium:getComputedStyle(c[0].querySelector("meta.foundation-mq-medium")).fontFamily.replace(d,""),large:getComputedStyle(c[0].querySelector("meta.foundation-mq-large")).fontFamily.replace(d,"")};return{topbarBreakpoint:function(){return!b(e.topbar).matches},small:function(){return b(e.small).matches},medium:function(){return b(e.medium).matches},large:function(){return b(e.large).matches}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position","mm.foundation.mediaQueries"]).controller("DropdownToggleController",["$scope","$attrs","mediaQueries",function(a,b,c){this.small=function(){return c.small()&&!c.medium()}}]).directive("dropdownToggle",["$document","$window","$location","$position",function(a,b,c,d){var e=null,f=angular.noop;return{restrict:"CA",controller:"DropdownToggleController",link:function(c,g,h,i){var j=g.parent(),k=angular.element(a[0].querySelector(h.dropdownToggle)),l=function(){return j.hasClass("has-dropdown")},m=function(c){k=angular.element(a[0].querySelector(h.dropdownToggle));var m=g===e;if(c.preventDefault(),c.stopPropagation(),e&&f(),!m&&!g.hasClass("disabled")&&!g.prop("disabled")){k.css("display","block"),k.addClass("f-open-dropdown");var n=d.offset(g),o=d.offset(angular.element(k[0].offsetParent)),p=k.prop("offsetWidth"),q={top:n.top-o.top+n.height+"px"};if(i.small())q.left=Math.max((o.width-p)/2,8)+"px",q.position="absolute",q.width="95%",q["max-width"]="none";else{var r=Math.round(n.left-o.left),s=b.innerWidth-p-8;r>s&&(r=s,k.removeClass("left").addClass("right")),q.left=r+"px",q.position=null,q["max-width"]=null}k.css(q),g.addClass("expanded"),l()&&j.addClass("hover"),e=g,f=function(b){a.off("click",f),k.css("display","none"),k.removeClass("f-open-dropdown"),g.removeClass("expanded"),f=angular.noop,e=null,j.hasClass("hover")&&j.removeClass("hover")},a.on("click",f)}};k&&k.css("display","none"),c.$watch("$location.path",function(){f()}),g.on("click",m),g.on("$destroy",function(){g.off("click",m)})}}}]),angular.module("mm.foundation.interchange",["mm.foundation.mediaQueries"]).factory("interchangeQueries",["$document",function(a){for(var b,c,d={"default":"only screen",landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},e="foundation-mq-",f=["small","medium","large","xlarge","xxlarge"],g=angular.element(a[0].querySelector("head")),h=0;h'),b=getComputedStyle(g[0].querySelector("meta."+e+f[h])),c=b.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,""),d[f[h]]=c;return d}]).factory("interchangeQueriesManager",["interchangeQueries",function(a){return{add:function(b,c){return b&&c&&angular.isString(b)&&angular.isString(c)&&!a[b]?(a[b]=c,!0):!1}}}]).factory("interchangeTools",["$window","matchMedia","interchangeQueries",function(a,b,c){var d=function(a){for(var b,c=a.split(/\[(.*?)\]/),d=c.length,e=/^(.+)\,\ \((.+)\)$/,f={};d--;)c[d].replace(/[\W\d]+/,"").length>4&&(b=e.exec(c[d]),b&&3===b.length&&(f[b[2]]=b[1]));return f},e=function(a){var d,e,f;for(d in a)if(e=c[d]||d,f=b(e),f.matches)return a[d]};return{parseAttribute:d,findCurrentMediaFile:e}}]).directive("interchange",["$window","$rootScope","interchangeTools",function(a,b,c){var d=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;return{restrict:"A",scope:!0,priority:450,compile:function(e,f){return"DIV"!==e[0].nodeName||d.test(f.interchange)||e.html(''),{pre:function(a,b,c){},post:function(d,e,f){var g,h;switch(h=e&&e[0]&&e[0].nodeName,d.fileMap=c.parseAttribute(f.interchange),h){case"DIV":g=c.findCurrentMediaFile(d.fileMap),/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(g)?d.type="background":d.type="include";break;case"IMG":d.type="image";break;default:return}var i=function(a){var f=c.findCurrentMediaFile(d.fileMap);if(!d.currentFile||d.currentFile!==f){switch(d.currentFile=f,d.type){case"image":e.attr("src",d.currentFile);break;case"background":e.css("background-image","url("+d.currentFile+")")}b.$emit("replace",e,d),a&&d.$apply()}};i(),a.addEventListener("resize",i),d.$on("$destroy",function(){a.removeEventListener("resize",i)})}}}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(a){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0?c[0].querySelectorAll("[autofocus]")[0].focus():c[0].focus()})}}}]).factory("$modalStack",["$window","$transition","$timeout","$document","$compile","$rootScope","$$stackedMap",function(a,b,c,d,e,f,g){function h(){for(var a=-1,b=q.keys(),c=0;c0),j()})}function j(){if(m&&-1==h()){var a=n;k(m,n,150,function(){a.$destroy(),a=null}),m=void 0,n=void 0}}function k(a,d,e,f){function g(){g.done||(g.done=!0,a.remove(),f&&f())}d.animate=!1;var h=b.transitionEndEventName;if(h){var i=c(g,e);a.bind(h,function(){c.cancel(i),g(),d.$apply()})}else c(g,0)}function l(b,c){angular.isUndefined(c)&&(c=0);var d=a.pageYOffset||0;return c+d}var m,n,o,p="modal-open",q=g.createNew(),r={};return f.$watch(h,function(a){n&&(n.index=a)}),d.bind("keydown",function(a){var b;27===a.which&&(b=q.top(),b&&b.value.keyboard&&f.$apply(function(){r.dismiss(b.key)}))}),r.open=function(b,c){b.options={deferred:c.deferred,modalScope:c.scope,backdrop:c.backdrop,keyboard:c.keyboard,parent:c.parent},q.add(b,b.options);var g=d.find(c.parent).eq(0),i=h();i>=0&&!m&&(n=f.$new(!0),n.index=i,m=e("
    ")(n),g.append(m));var j=angular.element('
    ');g.append(j[0]),o=parseInt(a.getComputedStyle(j[0]).top)||0;var k=l(j,o);j.remove();var r=angular.element('
    ').attr({"window-class":c.windowClass,index:q.length()-1,animate:"animate"});r.html(c.content);var s=e(r)(c.scope);q.top().value.modalDomEl=s,g.append(s),g.addClass(p)},r.reposition=function(a){var b=q.get(a).value;if(b){var c=b.modalDomEl,d=l(c,o);c.css("top",d+"px")}},r.close=function(a,b){var c=q.get(a);c&&(c.value.deferred.resolve(b),i(a))},r.dismiss=function(a,b){var c=q.get(a);c&&(c.value.deferred.reject(b),i(a))},r.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},r.getTop=function(){return q.top()},r}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a,e){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)},reposition:function(){h.reposition(k)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,parent:b.parent||"body"})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c,d){var e=angular.element(a),f=b.sidebar=c;b.hide=function(){f.removeClass("move-left"),f.removeClass("move-right")},e.bind("resize.body",b.hide),b.$on("$destroy",function(){e.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
    ';return{restrict:"EA",scope:!0,compile:function(a,b){var c=f(s);return function(a,b,d){function f(){a.tt_isOpen?m():k()}function k(){(!z||a.$eval(d[l+"Enable"]))&&(a.tt_popupDelay?(v=g(p,a.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){a.$apply(function(){q()})}function p(){return a.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):b.after(t),A(),a.tt_isOpen=!0,a.$digest(),A):angular.noop}function q(){a.tt_isOpen=!1,g.cancel(v),a.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=c(a,function(){}),a.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var c,d,e,f;switch(c=w?j.offset(b):j.position(b),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),a.tt_placement){case"right":f={top:c.top+c.height/2-e/2,left:c.left+c.width+10};break;case"bottom":f={top:c.top+c.height+10,left:c.left};break;case"left":f={top:c.top+c.height/2-e/2,left:c.left-d-10};break;default:f={top:c.top-e-10,left:c.left}}f.top+="px",f.left+="px",t.css(f)};a.tt_isOpen=!1,d.$observe(e,function(b){a.tt_content=b,!b&&a.tt_isOpen&&q()}),d.$observe(l+"Title",function(b){a.tt_title=b}),d[l+"Placement"]=d[l+"Placement"]||null,d.$observe(l+"Placement",function(b){a.tt_placement=angular.isDefined(b)&&b?b:o.placement}),d[l+"PopupDelay"]=d[l+"PopupDelay"]||null,d.$observe(l+"PopupDelay",function(b){var c=parseInt(b,10);a.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(angular.isFunction(x.show)?C():(b.unbind(x.show,k),b.unbind(x.hide,m)))},C=function(){};d[l+"Trigger"]=d[l+"Trigger"]||null,d.$observe(l+"Trigger",function(c){B(),C(),x=n(c),angular.isFunction(x.show)?C=a.$watch(function(){return x.show(a,b,d)},function(a){return g(a?p:q)}):x.show===x.hide?b.bind(x.show,f):(b.bind(x.show,k),b.bind(x.hide,m)),y=!0});var D=a.$eval(d[l+"Animation"]);a.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(b){w=angular.isDefined(b)?h(b)(a):w}),w&&a.$on("$locationChangeSuccess",function(){a.tt_isOpen&&q()}),a.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
    '}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=angular.isDefined(b.ratingStates)?this.createRateObjects(angular.copy(a.$parent.$eval(b.ratingStates))):this.createRateObjects(new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];angular.isUndefined(a.openOnLoad)&&(a.openOnLoad=!0),b.select=function(a){angular.forEach(c,function(a){a.active=!1}),a.active=!0},b.addTab=function(d){c.push(d),a.openOnLoad&&(1===c.length||d.active)&&b.select(d)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{openOnLoad:"=?"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.$watch("active",function(a){angular.isFunction(h)&&(h(b.$parent,a),a?(f.select(b),b.onSelect()):b.onDeselect())}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b,c,d){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.topbar",["mm.foundation.mediaQueries"]).factory("closest",[function(){return function(a,b){for(var c=function(a,b){for(var c=(a.parentNode||a.document).querySelectorAll(b),d=-1;c[++d]&&c[d]!=a;);return!!c[d]},d=a[0];d;){if(c(d,b))return angular.element(d);d=d.parentElement}return!1}}]).directive("topBar",["$timeout","$compile","$window","$document","mediaQueries",function(a,b,c,d,e){return{scope:{stickyClass:"@",backText:"@",stickyOn:"=",customBackText:"=",isHover:"=",mobileShowParentLink:"=",scrolltop:"="},restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar.html",transclude:!0,controller:["$window","$scope","closest",function(b,c,f){c.settings={},c.settings.stickyClass=c.stickyClass||"sticky",c.settings.backText=c.backText||"Back",c.settings.stickyOn=c.stickyOn||"all",c.settings.customBackText=void 0===c.customBackText?!0:c.customBackText,c.settings.isHover=void 0===c.isHover?!0:c.isHover,c.settings.mobileShowParentLink=void 0===c.mobileShowParentLink?!0:c.mobileShowParentLink,c.settings.scrolltop=void 0===c.scrolltop?!0:c.scrolltop,this.settings=c.settings,c.index=0;var g=function(a){var b=a.offsetHeight,c=a.currentStyle||getComputedStyle(a);return b+=parseInt(c.marginTop,10)+parseInt(c.marginBottom,10)},h=[];this.addSection=function(a){h.push(a)},this.removeSection=function(a){var b=h.indexOf(a);b>-1&&h.splice(b,1)};var i=/rtl/i.test(d.find("html").attr("dir"))?"right":"left";c.$watch("index",function(a){for(var b=0;bb&&!h.hasClass("fixed")?(h.addClass("fixed"),i.css("padding-top",a.originalHeight+"px")):c.pageYOffset<=b&&h.hasClass("fixed")&&(h.removeClass("fixed"),i.css("padding-top",""))}},m=function(){var b=e.topbarBreakpoint();if(j!==b){j=e.topbarBreakpoint(),g.removeClass("expanded"),g.parent().removeClass("expanded"),a.height="";var c=angular.element(g[0].querySelectorAll("section"));angular.forEach(c,function(a){angular.element(a.querySelectorAll("li.moved")).removeClass("moved")}),a.$apply()}},n=function(){l(),a.$apply()};if(a.toggle=function(b){if(!e.topbarBreakpoint())return!1;var d=void 0===b?!g.hasClass("expanded"):b;d?g.addClass("expanded"):g.removeClass("expanded"),a.settings.scrolltop?!d&&g.hasClass("fixed")?(g.parent().addClass("fixed"),g.removeClass("fixed"),i.css("padding-top",a.originalHeight+"px")):d&&g.parent().hasClass("fixed")&&(g.parent().removeClass("fixed"),g.addClass("fixed"),i.css("padding-top",""),c.scrollTo(0,0)):(k()&&g.parent().addClass("fixed"),g.parent().hasClass("fixed")&&(d?(g.addClass("fixed"),g.parent().addClass("expanded"),i.css("padding-top",a.originalHeight+"px")):(g.removeClass("fixed"),g.parent().removeClass("expanded"),l())))},h.hasClass("fixed")||k()){a.stickyTopbar=!0,a.height=h[0].offsetHeight;var o=h[0].getBoundingClientRect().top+c.pageYOffset}else a.height=g[0].offsetHeight;a.originalHeight=a.height,a.$watch("height",function(a){a?g.css("height",a+"px"):g.css("height","")}),angular.element(c).bind("resize",m),angular.element(c).bind("scroll",n),a.$on("$destroy",function(){angular.element(c).unbind("resize",m),angular.element(c).unbind("scroll",n)}),h.hasClass("fixed")&&i.css("padding-top",a.originalHeight+"px")}}}]).directive("toggleTopBar",["closest",function(a){return{scope:{},require:"^topBar",restrict:"A",replace:!0,templateUrl:"template/topbar/toggle-top-bar.html",transclude:!0,link:function(b,c,d,e){c.bind("click",function(b){var c=a(angular.element(b.currentTarget),"li");c.hasClass("back")||c.hasClass("has-dropdown")||e.toggle()}),b.$on("$destroy",function(){c.unbind("click")})}}}]).directive("topBarSection",["$compile","closest",function(a,b){return{scope:{},require:"^topBar",restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar-section.html",transclude:!0,link:function(a,c,d,e){var f=c;a.reset=function(){angular.element(f[0].querySelectorAll("li.moved")).removeClass("moved")},a.move=function(a,b){"left"===a?f.css({left:-100*b+"%"}):f.css({right:-100*b+"%"})},e.addSection(a),a.$on("$destroy",function(){e.removeSection(a)});var g=f[0].querySelectorAll("li>a");angular.forEach(g,function(c){var d=angular.element(c),f=b(d,"li");f.hasClass("has-dropdown")||f.hasClass("back")||f.hasClass("title")||(d.bind("click",function(){e.toggle(!1)}),a.$on("$destroy",function(){d.bind("click")}))})}}}]).directive("hasDropdown",["mediaQueries",function(a){return{scope:{},require:"^topBar",restrict:"A",templateUrl:"template/topbar/has-dropdown.html",replace:!0,transclude:!0,link:function(b,c,d,e){b.triggerLink=c.children("a")[0];var f=angular.element(b.triggerLink);f.bind("click",function(a){e.forward(a)}),b.$on("$destroy",function(){f.unbind("click")}),c.bind("mouseenter",function(){e.settings.isHover&&!a.topbarBreakpoint()&&c.addClass("not-click")}),c.bind("click",function(b){e.settings.isHover||a.topbarBreakpoint()||c.toggleClass("not-click")}),c.bind("mouseleave",function(){c.removeClass("not-click")}),b.$on("$destroy",function(){c.unbind("click"),c.unbind("mouseenter"),c.unbind("mouseleave")})},controller:["$window","$scope",function(a,b){this.triggerLink=b.triggerLink}]}}]).directive("topBarDropdown",["$compile",function(a){return{scope:{},require:["^topBar","^hasDropdown"],restrict:"A",replace:!0,templateUrl:"template/topbar/top-bar-dropdown.html",transclude:!0,link:function(b,c,d,e){var f,g=e[0],h=e[1],i=angular.element(h.triggerLink),j=i.attr("href");b.linkText=i.text(),b.back=function(a){g.back(a)},g.settings.customBackText?b.backText=g.settings.backText:b.backText="« "+i.html(),f=g.settings.mobileShowParentLink&&j&&j.length>1?angular.element('
  • {{backText}}
  • {{linkText}}
  • '):angular.element('
  • {{backText}}
  • '),a(f)(b),c.prepend(f)}}}]),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){try{return parseInt(a.localStorage.getItem("mm.tour.step"),10)}catch(b){if("SecurityError"!==b.name)throw b}}function c(){try{a.localStorage.setItem("mm.tour.step",e)}catch(b){if("SecurityError"!==b.name)throw b}}function d(a){e=a,c()}var e=b(),f={};this.add=function(a,b){f[a]=b},this.has=function(a){return!!f[a]},this.isActive=function(){return e>0},this.current=function(a){return a?void d(e):e},this.start=function(){d(1)},this.next=function(){d(e+1)},this.end=function(){d(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()},b.$on("$locationChangeSuccess",b.endTour)}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=i.$eval(k.typeaheadFocusFirst)!==!1,v=b(k.ngModel).assign,w=g.parse(k.typeahead),x=angular.element("
    ");x.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&x.attr("template-url",k.typeaheadTemplateUrl);var y=i.$new();i.$on("$destroy",function(){y.$destroy()});var z=function(){y.matches=[],y.activeIdx=-1},A=function(a){var b={$viewValue:a};q(i,!0),c.when(w.source(i,b)).then(function(c){if(a===l.$viewValue&&m)if(c.length>0){y.activeIdx=u?0:-1,y.matches.length=0;for(var d=0;d=n?o>0?(B&&d.cancel(B),B=d(function(){A(a)},o)):A(a):(q(i,!1),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[w.itemName]=a,b=w.viewMapper(i,d),d[w.itemName]=void 0,c=w.viewMapper(i,d),b!==c?b:a)}),y.select=function(a){var b,c,d={};d[w.itemName]=c=y.matches[a].model,b=w.modelMapper(i,d),v(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:w.viewMapper(i,d)}),z(),j[0].focus()},j.bind("keydown",function(a){0!==y.matches.length&&-1!==h.indexOf(a.which)&&(-1!=y.activeIdx||13!==a.which&&9!==a.which)&&(a.preventDefault(),40===a.which?(y.activeIdx=(y.activeIdx+1)%y.matches.length,y.$digest()):38===a.which?(y.activeIdx=(y.activeIdx>0?y.activeIdx:y.matches.length)-1,y.$digest()):13===a.which||9===a.which?y.$apply(function(){y.select(y.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),y.$digest()))}),j.bind("blur",function(a){m=!1}),j.bind("focus",function(a){m=!0});var C=function(a){j[0]!==a.target&&(z(),y.$digest())};e.bind("click",C),i.$on("$destroy",function(){e.unbind("click",C)});var D=a(x)(y);t?e.find("body").append(D):j.after(D)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).then(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
    \n {{heading}}\n
    \n
    \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
    \n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","
    \n \n ×\n
    \n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
    \n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'
    \n
    \n
    \n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
    \n \n
    \n

    \n

    \n
    \n
    \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
    \n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
    \n \n
    \n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
    \n {{heading}}\n
    \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/topbar/has-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/has-dropdown.html",'
  • ')}]),angular.module("template/topbar/toggle-top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/toggle-top-bar.html",'')}]),angular.module("template/topbar/top-bar-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-dropdown.html",'')}]),angular.module("template/topbar/top-bar-section.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-section.html",'
    ')}]),angular.module("template/topbar/top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar.html",'')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'
    \n \n
    \n

    \n

    \n Next\n End\n ×\n
    \n
    \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","
      \n"+'
    • \n
      \n
    • \n
    \n')}]); \ No newline at end of file diff --git a/app/assets/javascripts/store/all.js b/app/assets/javascripts/store/all.js deleted file mode 100644 index a8386e4b7f4..00000000000 --- a/app/assets/javascripts/store/all.js +++ /dev/null @@ -1,12 +0,0 @@ -// This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically -// be included in the compiled file accessible from http://example.com/assets/application.js -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// - -//= require 'jquery' -//= require store/spree_frontend -//= require store/spree_auth - -//= require_tree . diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml index f2d480c8b9c..51aa9d7fbca 100644 --- a/app/assets/javascripts/templates/signup.html.haml +++ b/app/assets/javascripts/templates/signup.html.haml @@ -4,6 +4,9 @@ .large-12.columns .alert-box.success{ng: {show: 'messages != null'}} {{ messages }} + .large-12.columns + .alert-box.alert{ng: {show: 'errors.message != null'}} + {{ errors.message }} .row .large-12.columns %label{for: "email"} {{'signup_email' | t}} diff --git a/app/assets/stylesheets/admin/all.scss b/app/assets/stylesheets/admin/all.scss index c6b7412c790..637f0b5d2cb 100644 --- a/app/assets/stylesheets/admin/all.scss +++ b/app/assets/stylesheets/admin/all.scss @@ -15,4 +15,6 @@ */ @import 'variables'; -@import '**/*'; +@import 'components/*'; +@import '*'; +@import 'pages/*'; diff --git a/app/assets/stylesheets/admin/components/pagination.scss b/app/assets/stylesheets/admin/components/pagination.scss new file mode 100644 index 00000000000..05cbf34ca7c --- /dev/null +++ b/app/assets/stylesheets/admin/components/pagination.scss @@ -0,0 +1,20 @@ +@import "admin/variables"; + +.pagination { + text-align: center; + margin: 2em 0 1em; + + button { + margin: 0 0.35em; + + &.active { + background-color: darken($spree-blue, 15%); + cursor: default; + } + + &.disabled { + background-color: $disabled_button; + cursor: default; + } + } +} diff --git a/app/assets/stylesheets/admin/components/per_page_controls.scss b/app/assets/stylesheets/admin/components/per_page_controls.scss new file mode 100644 index 00000000000..34ae9f7737d --- /dev/null +++ b/app/assets/stylesheets/admin/components/per_page_controls.scss @@ -0,0 +1,7 @@ +.per-page { + float: left; + + .per-page-feedback { + margin-left: 1em; + } +} diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 46e6b8aa0f7..0955d9fd462 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -87,3 +87,19 @@ div#group_buy_calculation { th.actions { white-space: nowrap; } + +table.index td.actions { + text-align: left; +} + +.orders-loading { + margin-top: 1em; + + img { + width: 85px; + } + + span { + font-size: 1.2em; + } +} diff --git a/app/assets/stylesheets/admin/pages/product_image_upload_modal.css.scss b/app/assets/stylesheets/admin/pages/product_image_upload_modal.css.scss new file mode 100644 index 00000000000..4dffade73ef --- /dev/null +++ b/app/assets/stylesheets/admin/pages/product_image_upload_modal.css.scss @@ -0,0 +1,20 @@ +@import '../variables'; + +.reveal-modal.product-image-upload { + width: 300px; + + .close-reveal-modal { + color: $modal-close-button-color; + font-size: 23px; + right: .45rem; + top: .35rem; + + :hover { + color: $modal-close-button-hover-color; + } + } + + label { + margin-top: 20px; + } +} diff --git a/app/assets/stylesheets/admin/product_import.css.scss b/app/assets/stylesheets/admin/product_import.css.scss index d8ad019af9a..20dcc481f1a 100644 --- a/app/assets/stylesheets/admin/product_import.css.scss +++ b/app/assets/stylesheets/admin/product_import.css.scss @@ -1,15 +1,22 @@ @import "variables"; -div.panel-section { +$pi-red: $warning-red; +$pi-green: lighten($spree-green, 10%); +$pi-orange: $bright-orange; +$pi-blue: lighten($spree-blue, 10%); +$pi-light-yellow: #faffaf; + +// scss-lint:disable NestingDepth - .neutral { - color: #bfbfbf; +div.panel-section { + .error { + color: $pi-red; } .warning { - color: $warning-red; + color: $bright-orange; } .success { - color: #86d83a; + color: $pi-green; } .info { color: #68b7c0; @@ -85,21 +92,29 @@ div.panel-section { td, th { white-space: nowrap; } - tr.error { - color: #c84C4c; - } - tr:hover td.invalid { - background-color: #ed5135; - } - tr i { - display: block; - margin-bottom: -0.2em; - font-size: 1.4em !important; + + tr { + &.error { + color: #c84C4c; + } + + &:hover td.invalid { + background-color: darken(#f05c51, 5%); + } + + 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; + + td { + &.invalid { + background-color: #f05c51; + box-shadow: inset 0px 0px 1px red; + color: white; + } } } @@ -123,52 +138,6 @@ br.panels.clearfix { clear: both; } -table.import-settings { - background-color: transparent !important; - width: auto; - - 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: 0; - } - tr:last-child td { - border-bottom: 0; - } - div.select2-container { - width: 13.5em; - } - div.select2-container.select2-container-disabled { - a.select2-choice, span.select2-arrow { - background-color: #d5d5d5; - } - } - input[disabled], input:disabled { - background-color: #d5d5d5; - opacity: 1; - border-color: transparent; - color: white !important; - } -} - .panel-section.import-settings { .header-description { @@ -177,7 +146,7 @@ table.import-settings { span.header-error { font-size: 0.85em; - color: $warning-red; + color: $pi-red; } .select2-search { @@ -208,10 +177,10 @@ table.import-settings { } i.fa-check-circle { - color: #86d83a; + color: $pi-green; } i.fa-info-circle { - color: #68b7c0; + color: $pi-blue; } } @@ -234,6 +203,10 @@ form.product-import, div.post-save-results, div.import-wrapper { div.import-wrapper { + .alert-box { + margin: 0 0 1.75em; + } + .ng-hide:not(.ng-hide-animate) { // We have to use !important here to override angular's display properties // scss-lint:disable ImportantRule @@ -281,7 +254,7 @@ div.progress-bar { span.progress-track{ display: block; - background: #b7ea53; + background: lighten($pi-green, 10%); height: 100%; border-radius: 0.3em; box-shadow: inset 0 0 3px rgba(0,0,0,0.3); @@ -312,7 +285,7 @@ div.progress-bar { span.category { display: inline-block; - background-color: lighten($spree-blue, 10%); + background-color: $pi-blue; color: white; padding: 0.3em 0.6em; margin: 0 0.4em 0.5em 0; diff --git a/app/assets/stylesheets/admin/products.css.scss b/app/assets/stylesheets/admin/products.css.scss index 933f43a00be..30cf3b55584 100644 --- a/app/assets/stylesheets/admin/products.css.scss +++ b/app/assets/stylesheets/admin/products.css.scss @@ -98,25 +98,6 @@ table#listing_products.bulk { } } -.reveal-modal.product-image-upload { - width: 300px; - a.close-reveal-modal { - font-size: 23px; - color: #de6060; - right: 0.45rem; - top: 0.35rem; - :hover { - color: #bf4545; - } - } - div.image-preview { - //float: left; - } - label { - margin-top: 20px; - } -} - form#image_upload { text-align: center; .spinner { diff --git a/app/assets/stylesheets/admin/variables.css.scss b/app/assets/stylesheets/admin/variables.css.scss index c7f904a38a3..72f806af7dc 100644 --- a/app/assets/stylesheets/admin/variables.css.scss +++ b/app/assets/stylesheets/admin/variables.css.scss @@ -6,7 +6,13 @@ $spree-light-blue: #eff5fc; $warning-red: #da5354; $warning-orange: #da7f52; +$bright-orange: #ffa92e; $medium-grey: #919191; $pale-blue: #cee1f4; +$light-grey: #ccc; $admin-table-border: $pale-blue; + +$modal-close-button-color: #de6060; +$modal-close-button-hover-color: #bf4545; +$disabled-button: $light-grey; diff --git a/app/assets/stylesheets/darkswarm/all.scss b/app/assets/stylesheets/darkswarm/all.scss index 6cfc32e605c..2f73f5e796e 100644 --- a/app/assets/stylesheets/darkswarm/all.scss +++ b/app/assets/stylesheets/darkswarm/all.scss @@ -9,8 +9,11 @@ @import 'foundation'; @import 'foundation-icons'; +@import 'base/*'; @import '*'; +@import 'pages/*'; +@import '../web/all'; ofn-modal { display: block; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/darkswarm/all_split2.css b/app/assets/stylesheets/darkswarm/all_split2.css deleted file mode 100644 index e231ca5d02e..00000000000 --- a/app/assets/stylesheets/darkswarm/all_split2.css +++ /dev/null @@ -1,3 +0,0 @@ -/* - *= require 'darkswarm/all' - */ diff --git a/app/assets/stylesheets/darkswarm/animations.scss b/app/assets/stylesheets/darkswarm/animations.scss index 7601da02dc8..b3848777ba1 100644 --- a/app/assets/stylesheets/darkswarm/animations.scss +++ b/app/assets/stylesheets/darkswarm/animations.scss @@ -123,9 +123,12 @@ -moz-transition: -moz-transform 0.2s ease-out; -o-transition: -o-transform 0.2s ease-out; transition: transform 0.2s ease-out; - -webkit-transform: translate(0, -25%); - -ms-transform: translate(0, -25%); - transform: translate(0, -25%); + + @media only screen and (min-width: 641px) { + -webkit-transform: translate(0, -5%); + -ms-transform: translate(0, -5%); + transform: translate(0, -5%); + } } .reveal-modal.in { diff --git a/app/assets/stylesheets/darkswarm/base/colors.css.scss b/app/assets/stylesheets/darkswarm/base/colors.css.scss new file mode 100644 index 00000000000..4b36b9cb9b8 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/base/colors.css.scss @@ -0,0 +1,4 @@ +$modal-background-color: #efefef; +$modal-content-background-color: #fff; +$modal-alert-link-color: #fff; +$modal-alert-link-hover-color: rgba(255, 255, 255, .7); diff --git a/app/assets/stylesheets/darkswarm/modal-login.css.scss b/app/assets/stylesheets/darkswarm/modal-login.css.scss deleted file mode 100644 index 80687c645f4..00000000000 --- a/app/assets/stylesheets/darkswarm/modal-login.css.scss +++ /dev/null @@ -1,25 +0,0 @@ -// Styling for login modal to style tabs -.reveal-modal.login-modal { - border-bottom-color: #efefef; -} - -.login-modal { - background: #efefef; - - .tabs-content { - background: #fff; - padding-top: 10px; - } - - .alert-box { - a { - color: white; - text-decoration: underline; - - &:hover { - color: rgba(255, 255, 255, 0.7); - text-decoration: underline; - } - } - } -} diff --git a/app/assets/stylesheets/darkswarm/modals.css.scss b/app/assets/stylesheets/darkswarm/modals.css.scss index 538a5622edb..734d388acf2 100644 --- a/app/assets/stylesheets/darkswarm/modals.css.scss +++ b/app/assets/stylesheets/darkswarm/modals.css.scss @@ -20,16 +20,16 @@ dialog // Small - when modal IS full screen @media only screen and (max-width: 640px) { - height: 500px; + left: 0; + max-height: 100%; position: absolute !important; top: 0; - left: 0; } // Medium and up - when modal IS NOT full screen @media only screen and (min-width: 641px) { - top: 10%; - max-height: 100%; + max-height: 90%; + top: 5%; } } diff --git a/app/assets/stylesheets/darkswarm/pages/login_modal.css.scss b/app/assets/stylesheets/darkswarm/pages/login_modal.css.scss new file mode 100644 index 00000000000..3a9d3854410 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/pages/login_modal.css.scss @@ -0,0 +1,25 @@ +@import '../base/colors'; + +// Styling for login modal to style tabs +.reveal-modal.login-modal { + border-bottom-color: $modal-background-color; +} + +.login-modal { + background: $modal-background-color; + + .tabs-content { + background: $modal-content-background-color; + padding-top: 10px; + } + + .alert-box a { + color: $modal-alert-link-color; + text-decoration: underline; + + &:hover { + color: $modal-alert-link-hover-color; + text-decoration: underline; + } + } +} diff --git a/app/assets/stylesheets/store/all.css b/app/assets/stylesheets/store/all.css deleted file mode 100644 index ea25eaeefcb..00000000000 --- a/app/assets/stylesheets/store/all.css +++ /dev/null @@ -1,12 +0,0 @@ -/* - * This is a manifest file that'll automatically include all the stylesheets available in this directory - * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at - * the top of the compiled file, but it's generally better to create a new file per style scope. - * - - *= require store/spree_frontend - *= require store/spree_auth - - *= require_self - *= require_tree . -*/ diff --git a/app/controllers/admin/contents_controller.rb b/app/controllers/admin/contents_controller.rb index 9eac6f279ad..26a6493f958 100644 --- a/app/controllers/admin/contents_controller.rb +++ b/app/controllers/admin/contents_controller.rb @@ -1,21 +1,15 @@ module Admin class ContentsController < Spree::Admin::BaseController def edit - @preference_sections = [{name: I18n.t('admin.contents.edit.header'), preferences: [:logo, :logo_mobile, :logo_mobile_svg]}, - {name: I18n.t('admin.contents.edit.home_page'), preferences: [:home_hero, :home_show_stats]}, - {name: I18n.t('admin.contents.edit.producer_signup_page'), preferences: [:producer_signup_pricing_table_html, :producer_signup_case_studies_html, :producer_signup_detail_html]}, - {name: I18n.t('admin.contents.edit.hub_signup_page'), preferences: [:hub_signup_pricing_table_html, :hub_signup_case_studies_html, :hub_signup_detail_html]}, - {name: I18n.t('admin.contents.edit.group_signup_page'), preferences: [:group_signup_pricing_table_html, :group_signup_case_studies_html, :group_signup_detail_html]}, - {name: I18n.t('admin.contents.edit.main_links'), preferences: [:menu_1, :menu_1_icon_name, :menu_2, :menu_2_icon_name, :menu_3, :menu_3_icon_name, :menu_4, :menu_4_icon_name, :menu_5, :menu_5_icon_name, :menu_6, :menu_6_icon_name, :menu_7, :menu_7_icon_name]}, - {name: I18n.t('admin.contents.edit.footer_and_external_links'), preferences: [:footer_logo, - :footer_facebook_url, :footer_twitter_url, :footer_instagram_url, :footer_linkedin_url, :footer_googleplus_url, :footer_pinterest_url, - :footer_email, :community_forum_url, :footer_links_md, :footer_about_url]}] + @preference_sections = preference_sections.map do |preference_section| + { name: preference_section.name, preferences: preference_section.preferences } + end end def update params.each do |name, value| if ContentConfig.has_preference?(name) || ContentConfig.has_attachment?(name) - ContentConfig.send("#{name}=", value) + ContentConfig.public_send("#{name}=", value) end end @@ -26,5 +20,20 @@ def update redirect_to main_app.edit_admin_content_path end + + private + + def preference_sections + [ + PreferenceSections::HeaderSection.new, + PreferenceSections::HomePageSection.new, + PreferenceSections::ProducerSignupPageSection.new, + PreferenceSections::HubSignupPageSection.new, + PreferenceSections::GroupSignupPageSection.new, + PreferenceSections::MainLinksSection.new, + PreferenceSections::FooterAndExternalLinksSection.new, + PreferenceSections::UserGuideSection.new + ] + end end end diff --git a/app/controllers/admin/inventory_items_controller.rb b/app/controllers/admin/inventory_items_controller.rb index 432b9551349..6fd382d0d5d 100644 --- a/app/controllers/admin/inventory_items_controller.rb +++ b/app/controllers/admin/inventory_items_controller.rb @@ -19,7 +19,7 @@ class InventoryItemsController < ResourceController # we can authorise #create using an object with required attributes def build_resource if parent_data.present? - parent.send(controller_name).build + parent.public_send(controller_name).build else model_class.new(params[object_name]) # This line changed end diff --git a/app/controllers/admin/product_import_controller.rb b/app/controllers/admin/product_import_controller.rb index 38ea79a97db..e8ae8951e90 100644 --- a/app/controllers/admin/product_import_controller.rb +++ b/app/controllers/admin/product_import_controller.rb @@ -14,6 +14,7 @@ def import @filepath = save_uploaded_file(params[:file]) @importer = ProductImport::ProductImporter.new(File.new(@filepath), spree_current_user, params[:settings]) @original_filename = params[:file].try(:original_filename) + @non_updatable_fields = ProductImport::EntryValidator.non_updatable_fields check_file_errors @importer check_spreadsheet_has_data @importer @@ -53,7 +54,7 @@ def process_data(method) @importer = ProductImport::ProductImporter.new(File.new(params[:filepath]), spree_current_user, start: params[:start], end: params[:end], settings: params[:settings]) begin - @importer.send("#{method}_entries") + @importer.public_send("#{method}_entries") rescue StandardError => e render json: e.message, response: 500 return false diff --git a/app/controllers/admin/resource_controller.rb b/app/controllers/admin/resource_controller.rb index 66151f6f75b..19a98dc3373 100644 --- a/app/controllers/admin/resource_controller.rb +++ b/app/controllers/admin/resource_controller.rb @@ -15,18 +15,18 @@ def new_object_url(options = {}) def edit_object_url(object, options = {}) if parent_data.present? - main_app.send "edit_admin_#{model_name}_#{object_name}_url", parent, object, options + main_app.public_send "edit_admin_#{model_name}_#{object_name}_url", parent, object, options else - main_app.send "edit_admin_#{object_name}_url", object, options + main_app.public_send "edit_admin_#{object_name}_url", object, options end end def object_url(object = nil, options = {}) target = object ? object : @object if parent_data.present? - main_app.send "admin_#{model_name}_#{object_name}_url", parent, target, options + main_app.public_send "admin_#{model_name}_#{object_name}_url", parent, target, options else - main_app.send "admin_#{object_name}_url", target, options + main_app.public_send "admin_#{object_name}_url", target, options end end diff --git a/app/controllers/admin/subscription_line_items_controller.rb b/app/controllers/admin/subscription_line_items_controller.rb index 5011129c5f6..d981b71fe1f 100644 --- a/app/controllers/admin/subscription_line_items_controller.rb +++ b/app/controllers/admin/subscription_line_items_controller.rb @@ -1,5 +1,6 @@ require 'open_food_network/permissions' require 'open_food_network/order_cycle_permissions' +require 'open_food_network/scope_variant_to_hub' module Admin class SubscriptionLineItemsController < ResourceController diff --git a/app/controllers/angular_templates_controller.rb b/app/controllers/angular_templates_controller.rb deleted file mode 100644 index 44d870cc58e..00000000000 --- a/app/controllers/angular_templates_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AngularTemplatesController < ApplicationController - def show - render params[:id].to_s, layout: nil - end -end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index f25c47417d2..a7f96d01cf6 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -9,5 +9,9 @@ class BaseController < Spree::Api::BaseController include ActionController::UrlFor include Rails.application.routes.url_helpers use_renderers :json + + def respond_with_conflict(json_hash) + render json: json_hash, status: :conflict + end end end diff --git a/app/controllers/api/cookies_consent_controller.rb b/app/controllers/api/cookies_consent_controller.rb deleted file mode 100644 index 89016c20661..00000000000 --- a/app/controllers/api/cookies_consent_controller.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Api - class CookiesConsentController < BaseController - include ActionController::Cookies - respond_to :json - - def show - render json: { cookies_consent: cookies_consent.exists? } - end - - def create - cookies_consent.set - show - end - - def destroy - cookies_consent.destroy - show - end - - private - - def cookies_consent - @cookies_consent ||= CookiesConsent.new(cookies, request.host) - end - end -end diff --git a/app/controllers/api/enterprise_attachment_controller.rb b/app/controllers/api/enterprise_attachment_controller.rb new file mode 100644 index 00000000000..083d6946be0 --- /dev/null +++ b/app/controllers/api/enterprise_attachment_controller.rb @@ -0,0 +1,37 @@ +module Api + class EnterpriseAttachmentController < BaseController + class MissingImplementationError < StandardError; end + class UnknownEnterpriseAuthorizationActionError < StandardError; end + + before_filter :load_enterprise + + respond_to :json + + def destroy + return respond_with_conflict(error: destroy_attachment_does_not_exist_error_message) unless @enterprise.public_send("#{attachment_name}?") + + @enterprise.update_attributes!(attachment_name => nil) + render json: @enterprise, serializer: Admin::EnterpriseSerializer, spree_current_user: spree_current_user + end + + protected + + def attachment_name + raise MissingImplementationError, "Method attachment_name should be defined" + end + + def enterprise_authorize_action + raise MissingImplementationError, "Method enterprise_authorize_action should be defined" + end + + def load_enterprise + @enterprise = Enterprise.find_by_permalink(params[:enterprise_id].to_s) + raise UnknownEnterpriseAuthorizationActionError if enterprise_authorize_action.blank? + authorize!(enterprise_authorize_action, @enterprise) + end + + def destroy_attachment_does_not_exist_error_message + I18n.t("api.enterprise_#{attachment_name}.destroy_attachment_does_not_exist") + end + end +end diff --git a/app/controllers/api/logos_controller.rb b/app/controllers/api/logos_controller.rb new file mode 100644 index 00000000000..f3e7934724e --- /dev/null +++ b/app/controllers/api/logos_controller.rb @@ -0,0 +1,16 @@ +module Api + class LogosController < EnterpriseAttachmentController + private + + def attachment_name + :logo + end + + def enterprise_authorize_action + case action_name.to_sym + when :destroy + :remove_logo + end + end + end +end diff --git a/app/controllers/api/promo_images_controller.rb b/app/controllers/api/promo_images_controller.rb new file mode 100644 index 00000000000..0e79ebdb938 --- /dev/null +++ b/app/controllers/api/promo_images_controller.rb @@ -0,0 +1,16 @@ +module Api + class PromoImagesController < EnterpriseAttachmentController + private + + def attachment_name + :promo_image + end + + def enterprise_authorize_action + case action_name.to_sym + when :destroy + :remove_promo_image + end + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 65d1fb1f33f..4b0cae80494 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,6 @@ class ApplicationController < ActionController::Base before_filter :set_cache_headers # Issue #1213, prevent cart emptying via cache when using back button include EnterprisesHelper - helper CssSplitter::ApplicationHelper def redirect_to(options = {}, response_status = {}) ::Rails.logger.error("Redirected by #{caller(1).first rescue "unknown"}") diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 2b2a639b87e..00af18d701e 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -77,7 +77,7 @@ def reset_distributor(order, distributor) def reset_user_and_customer(order) order.associate_user!(spree_current_user) if order.user.blank? || order.email.blank? - order.send(:associate_customer) if order.customer.nil? # Only associates existing customers + order.__send__(:associate_customer) if order.customer.nil? # Only associates existing customers end def reset_order_cycle(order, distributor) diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 8cac75eca2f..4160bc1690f 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -44,7 +44,7 @@ def changeable_orders_alert private def filtered_json(products_json) - if applicator.send(:rules).any? + if applicator.rules.any? filter(products_json) else products_json diff --git a/app/controllers/spree/admin/base_controller_decorator.rb b/app/controllers/spree/admin/base_controller_decorator.rb index fb0e54baa80..60887f2ce5f 100644 --- a/app/controllers/spree/admin/base_controller_decorator.rb +++ b/app/controllers/spree/admin/base_controller_decorator.rb @@ -28,7 +28,11 @@ def authorize_admin record = self.class.to_s.sub("Controller", "").underscore.split('/').last.singularize.to_sym end authorize! :admin, record - authorize! action, record + authorize! resource_authorize_action, record + end + + def resource_authorize_action + action end # This is in Spree::Core::ControllerHelpers::Auth diff --git a/app/controllers/spree/admin/general_settings_controller_decorator.rb b/app/controllers/spree/admin/general_settings_controller_decorator.rb index 603f74bf632..80cdb8fa9eb 100644 --- a/app/controllers/spree/admin/general_settings_controller_decorator.rb +++ b/app/controllers/spree/admin/general_settings_controller_decorator.rb @@ -10,6 +10,6 @@ def edit @preferences_general << :bugherd_api_key end end - GeneralSettingsController.send(:prepend, GeneralSettingsEditPreferences) + GeneralSettingsController.prepend(GeneralSettingsEditPreferences) end end diff --git a/app/controllers/spree/admin/line_items_controller_decorator.rb b/app/controllers/spree/admin/line_items_controller_decorator.rb index 7210a6abb10..3ef85234df4 100644 --- a/app/controllers/spree/admin/line_items_controller_decorator.rb +++ b/app/controllers/spree/admin/line_items_controller_decorator.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + Spree::Admin::LineItemsController.class_eval do prepend_before_filter :load_order, except: :index around_filter :apply_enterprise_fees_with_lock, only: :update diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index 49f017930d0..88110da52f5 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -61,7 +61,15 @@ def index respond_with(@orders) do |format| format.html format.json do - render_as_json @orders + render json: { + orders: ActiveModel::ArraySerializer.new(@orders, each_serializer: Api::Admin::OrderSerializer), + pagination: { + results: @orders.total_count, + pages: @orders.num_pages.to_i, + page: params[:page].to_i, + per_page: params[:per_page].to_i + } + } end end end @@ -102,16 +110,14 @@ def update_distribution_charge def orders if json_request? @search = OpenFoodNetwork::Permissions.new(spree_current_user).editable_orders.ransack(params[:q]) - @search.result.reorder('id ASC') else @search = Spree::Order.accessible_by(current_ability, :index).ransack(params[:q]) # Replaced this search to filter orders to only show those distributed by current user (or all for admin user) - @search.result.includes([:user, :shipments, :payments]). - distributed_by_user(spree_current_user). - page(params[:page]). - per(params[:per_page] || Spree::Config[:orders_per_page]) + @search.result.includes([:user, :shipments, :payments]).distributed_by_user(spree_current_user) end + + @search.result.page(params[:page]).per(params[:per_page] || Spree::Config[:orders_per_page]) end def require_distributor_abn diff --git a/app/controllers/spree/admin/payment_methods_controller_decorator.rb b/app/controllers/spree/admin/payment_methods_controller_decorator.rb index bded58fe19f..b7268276e74 100644 --- a/app/controllers/spree/admin/payment_methods_controller_decorator.rb +++ b/app/controllers/spree/admin/payment_methods_controller_decorator.rb @@ -10,7 +10,7 @@ module Admin # Only show payment methods that user has access to and sort by distributor name # ! Redundant code copied from Spree::Admin::ResourceController with modifications marked def collection - return parent.send(controller_name) if parent_data.present? + return parent.public_send(controller_name) if parent_data.present? collection = if model_class.respond_to?(:accessible_by) && !current_ability.has_block?(params[:action], model_class) diff --git a/app/controllers/spree/admin/payments_controller_decorator.rb b/app/controllers/spree/admin/payments_controller_decorator.rb index 92faeb093d1..ccbdef25d57 100644 --- a/app/controllers/spree/admin/payments_controller_decorator.rb +++ b/app/controllers/spree/admin/payments_controller_decorator.rb @@ -10,7 +10,7 @@ def fire # Because we have a transition method also called void, we do this to avoid conflicts. event = "void_transaction" if event == "void" - if @payment.send("#{event}!") + if @payment.public_send("#{event}!") flash[:success] = t(:payment_updated) else flash[:error] = t(:cannot_perform_operation) diff --git a/app/controllers/spree/admin/resource_controller_decorator.rb b/app/controllers/spree/admin/resource_controller_decorator.rb index cb789d7330b..19293545ec3 100644 --- a/app/controllers/spree/admin/resource_controller_decorator.rb +++ b/app/controllers/spree/admin/resource_controller_decorator.rb @@ -13,4 +13,4 @@ def load_resource end end -Spree::Admin::ResourceController.send(:prepend, AuthorizeOnLoadResource) +Spree::Admin::ResourceController.prepend(AuthorizeOnLoadResource) diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index a3548523916..1ad8853bb07 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -1,10 +1,12 @@ require 'spree/core/controller_helpers/order_decorator' +require 'spree/core/controller_helpers/auth_decorator' Spree::OrdersController.class_eval do before_filter :update_distribution, only: :update before_filter :filter_order_params, only: :update before_filter :enable_embedded_shopfront + prepend_before_filter :require_order_authentication, only: :show prepend_before_filter :require_order_cycle, only: :edit prepend_before_filter :require_distributor_chosen, only: :edit before_filter :check_hub_ready_for_checkout, only: :edit @@ -128,6 +130,13 @@ def cancel private + def require_order_authentication + return if session[:access_token] || params[:token] || spree_current_user + + flash[:error] = I18n.t("spree.orders.edit.login_to_view_order") + require_login_then_redirect_to request.env['PATH_INFO'] + end + def order_to_update return @order_to_update if defined? @order_to_update return @order_to_update = current_order unless params[:id] diff --git a/app/controllers/user_registrations_controller.rb b/app/controllers/user_registrations_controller.rb index b7001ab78b1..cc43badbb81 100644 --- a/app/controllers/user_registrations_controller.rb +++ b/app/controllers/user_registrations_controller.rb @@ -1,4 +1,8 @@ +require 'open_food_network/error_logger' + class UserRegistrationsController < Spree::UserRegistrationsController + I18N_SCOPE = 'devise.user_registrations.spree_user'.freeze + before_filter :set_checkout_redirect, only: :create # POST /resource/sign_up @@ -19,14 +23,23 @@ def create end end else - clean_up_passwords(resource) - respond_to do |format| - format.html do - render :new - end - format.js do - render json: @user.errors, status: :unauthorized - end + render_error(@user.errors) + end + rescue StandardError => error + OpenFoodNetwork::ErrorLogger.notify(error) + render_error(message: I18n.t('unknown_error', scope: I18N_SCOPE)) + end + + private + + def render_error(errors = {}) + clean_up_passwords(resource) + respond_to do |format| + format.html do + render :new + end + format.js do + render json: errors, status: :unauthorized end end end diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 1f1ed295503..7d87395bc61 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -7,10 +7,6 @@ def ng_fields_for(record_name, *args, &block) end def ng_text_field(method, options = {}) - # @object_name --> "enterprise_fee_set" - # @fields_for_record_name --> :collection - # @object.send(@fields_for_record_name).first.class.to_s.underscore --> enterprise_fee - value = "{{ #{angular_model(method)} }}" options.reverse_merge!({'id' => angular_id(method)}) @@ -46,6 +42,6 @@ def angular_id(method) end def angular_model(method) - "#{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method}" + "#{@object.public_send(@fields_for_record_name).first.class.to_s.underscore}.#{method}" end end diff --git a/app/helpers/angular_form_helper.rb b/app/helpers/angular_form_helper.rb index 0687f621880..2528e8bc7ef 100644 --- a/app/helpers/angular_form_helper.rb +++ b/app/helpers/angular_form_helper.rb @@ -11,7 +11,7 @@ def ng_options_for_select(container, angular_field=nil) def ng_options_from_collection_for_select(collection, value_method, text_method, angular_field) options = collection.map do |element| - [element.send(text_method), element.send(value_method)] + [element.public_send(text_method), element.public_send(value_method)] end ng_options_for_select(options, angular_field) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bec9d181c91..f61a11727cb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -15,7 +15,7 @@ def ng_form_for(name, *args, &block) # spree.foo_path in any view rendered from non-spree-namespaced controllers. def method_missing(method, *args, &block) if (method.to_s.end_with?('_path') || method.to_s.end_with?('_url')) && spree.respond_to?(method) - spree.send(method, *args) + spree.public_send(method, *args) else super end diff --git a/app/helpers/cookies_policy_helper.rb b/app/helpers/cookies_policy_helper.rb deleted file mode 100644 index 2afd28d9db9..00000000000 --- a/app/helpers/cookies_policy_helper.rb +++ /dev/null @@ -1,8 +0,0 @@ -module CookiesPolicyHelper - def render_cookie_entry(cookie_name, cookie_desc, cookie_domain = nil) - render partial: 'cookies_policy_entry', - locals: { cookie_name: cookie_name, - cookie_desc: cookie_desc, - cookie_domain: cookie_domain } - end -end diff --git a/app/helpers/footer_links_helper.rb b/app/helpers/footer_links_helper.rb new file mode 100644 index 00000000000..71e8683da0b --- /dev/null +++ b/app/helpers/footer_links_helper.rb @@ -0,0 +1,11 @@ +require 'web/cookies_consent' + +module FooterLinksHelper + def cookies_policy_link + link_to( t( '.footer_data_cookies_policy' ), '', 'cookies-policy-modal' => true, 'cookies-banner' => !Web::CookiesConsent.new(cookies, request.host).exists? && Spree::Config.cookies_consent_banner_toggle) + end + + def privacy_policy_link + link_to( t( '.footer_data_privacy_policy' ), Spree::Config.privacy_policy_url, target: '_blank' ) + end +end diff --git a/app/mailers/spree/user_mailer_decorator.rb b/app/mailers/spree/user_mailer_decorator.rb index 3921d3c6480..dced31e7fd3 100644 --- a/app/mailers/spree/user_mailer_decorator.rb +++ b/app/mailers/spree/user_mailer_decorator.rb @@ -13,8 +13,14 @@ def confirmation_instructions(user, _opts) @contact = ContentConfig.footer_email subject = t('spree.user_mailer.confirmation_instructions.subject') - mail(to: user.email, + mail(to: confirmation_email_address, from: from_address, subject: subject) end + + private + + def confirmation_email_address + @user.pending_reconfirmation? ? @user.unconfirmed_email : @user.email + end end diff --git a/app/models/column_preference.rb b/app/models/column_preference.rb index 8def9bde307..f6bba869f7e 100644 --- a/app/models/column_preference.rb +++ b/app/models/column_preference.rb @@ -20,7 +20,7 @@ class ColumnPreference < ActiveRecord::Base def self.for(user, action_name) stored_preferences = where(user_id: user.id, action_name: action_name) - default_preferences = send("#{action_name}_columns") + default_preferences = __send__("#{action_name}_columns") filter(default_preferences, user, action_name) default_preferences.each_with_object([]) do |(column_name, default_attributes), preferences| stored_preference = stored_preferences.find_by_column_name(column_name) @@ -37,7 +37,7 @@ def self.for(user, action_name) private def self.valid_columns_for(action_name) - send("#{action_name}_columns").keys.map(&:to_s) + __send__("#{action_name}_columns").keys.map(&:to_s) end def self.known_actions diff --git a/app/models/content_configuration.rb b/app/models/content_configuration.rb index 19e1d07f9ed..3e1e2b0939d 100644 --- a/app/models/content_configuration.rb +++ b/app/models/content_configuration.rb @@ -69,4 +69,7 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration EOS preference :footer_about_url, :string, default: "http://www.openfoodnetwork.org/ofn-local/open-food-network-australia/" + + #User Guide + preference :user_guide_link, :string, default: 'https://guide.openfoodnetwork.org/' end diff --git a/app/models/model_set.rb b/app/models/model_set.rb index cb75f929ab9..0236a007764 100644 --- a/app/models/model_set.rb +++ b/app/models/model_set.rb @@ -12,7 +12,7 @@ def initialize(klass, collection, attributes={}, reject_if=nil, delete_if=nil) @collection = attributes[:collection] if attributes[:collection] attributes.each do |name, value| - send("#{name}=", value) + public_send("#{name}=", value) end end @@ -30,8 +30,11 @@ def collection_attributes=(collection_attributes) def errors errors = ActiveModel::Errors.new self - full_messages = @collection.map { |ef| ef.errors.full_messages }.flatten - full_messages.each { |fm| errors.add(:base, fm) } + full_messages = @collection + .map { |model| model.errors.full_messages } + .flatten + + full_messages.each { |message| errors.add(:base, message) } errors end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 4d8f443a681..ac9b7d8cb80 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + class OrderCycle < ActiveRecord::Base belongs_to :coordinator, :class_name => 'Enterprise' @@ -121,7 +123,7 @@ def self.earliest_closing_times def clone! oc = self.dup - oc.name = "COPY OF #{oc.name}" + oc.name = I18n.t("models.order_cycle.cloned_order_cycle_name", order_cycle: oc.name) oc.orders_open_at = oc.orders_close_at = nil oc.coordinator_fee_ids = self.coordinator_fee_ids oc.preferred_product_selection_from_coordinator_inventory_only = self.preferred_product_selection_from_coordinator_inventory_only diff --git a/app/models/order_updater.rb b/app/models/order_updater.rb index c798da087b1..a38270f9888 100644 --- a/app/models/order_updater.rb +++ b/app/models/order_updater.rb @@ -9,27 +9,13 @@ class OrderUpdater < SimpleDelegator # https://github.com/spree/spree/commit/38b8456183d11fc1e00e395e7c9154c76ef65b85 # https://github.com/spree/spree/commit/7b264acff7824f5b3dc6651c106631d8f30b147a def update_payment_state - last_state = order.payment_state - if payments.present? && payments.valid.empty? - order.payment_state = 'failed' - elsif order.state == 'canceled' && order.payment_total.zero? - order.payment_state = 'void' - else - # This part added so that we don't need to override order.outstanding_balance - balance = order.outstanding_balance - balance = -1 * order.payment_total if canceled_and_paid_for? - order.payment_state = 'balance_due' if balance > 0 - order.payment_state = 'credit_owed' if balance < 0 - order.payment_state = 'paid' if balance.zero? - - # Original logic - # order.payment_state = 'balance_due' if order.outstanding_balance > 0 - # order.payment_state = 'credit_owed' if order.outstanding_balance < 0 - # order.payment_state = 'paid' if !order.outstanding_balance? + last_payment_state = order.payment_state + + order.payment_state = infer_payment_state + track_payment_state_change(last_payment_state) + + order.payment_state end - order.state_changed('payment') if last_state != order.payment_state - order.payment_state - end def before_save_hook shipping_address_from_distributor @@ -37,10 +23,61 @@ def before_save_hook private + def infer_payment_state + if failed_payments? + 'failed' + elsif canceled_and_not_paid_for? + 'void' + else + infer_payment_state_from_balance + end + end + + def infer_payment_state_from_balance + # This part added so that we don't need to override + # order.outstanding_balance + balance = order.outstanding_balance + balance = -1 * order.payment_total if canceled_and_paid_for? + + infer_state(balance) + end + + def infer_state(balance) + if balance > 0 + 'balance_due' + elsif balance < 0 + 'credit_owed' + elsif balance.zero? + 'paid' + end + end + + # Tracks the state transition through a state_change for this order. It + # does so until the last state is reached. That is, when the infered next + # state is the same as the order has now. + # + # @param last_payment_state [String] + def track_payment_state_change(last_payment_state) + return if last_payment_state == order.payment_state + order.state_changed('payment') + end + # Taken from order.outstanding_balance in Spree 2.4 # See: https://github.com/spree/spree/commit/7b264acff7824f5b3dc6651c106631d8f30b147a def canceled_and_paid_for? - order.canceled? && order.payments.present? && !order.payments.completed.empty? + order.canceled? && paid? + end + + def canceled_and_not_paid_for? + order.state == 'canceled' && order.payment_total.zero? + end + + def paid? + payments.present? && !payments.completed.empty? + end + + def failed_payments? + payments.present? && payments.valid.empty? end # Sets the distributor's address as shipping address of the order for those diff --git a/app/models/preference_sections/footer_and_external_links_section.rb b/app/models/preference_sections/footer_and_external_links_section.rb new file mode 100644 index 00000000000..cf9d926553a --- /dev/null +++ b/app/models/preference_sections/footer_and_external_links_section.rb @@ -0,0 +1,23 @@ +module PreferenceSections + class FooterAndExternalLinksSection + def name + I18n.t('admin.contents.edit.footer_and_external_links') + end + + def preferences + [ + :footer_logo, + :footer_facebook_url, + :footer_twitter_url, + :footer_instagram_url, + :footer_linkedin_url, + :footer_googleplus_url, + :footer_pinterest_url, + :footer_email, + :community_forum_url, + :footer_links_md, + :footer_about_url + ] + end + end +end diff --git a/app/models/preference_sections/group_signup_page_section.rb b/app/models/preference_sections/group_signup_page_section.rb new file mode 100644 index 00000000000..b28570fcd6a --- /dev/null +++ b/app/models/preference_sections/group_signup_page_section.rb @@ -0,0 +1,15 @@ +module PreferenceSections + class GroupSignupPageSection + def name + I18n.t('admin.contents.edit.group_signup_page') + end + + def preferences + [ + :group_signup_pricing_table_html, + :group_signup_case_studies_html, + :group_signup_detail_html + ] + end + end +end diff --git a/app/models/preference_sections/header_section.rb b/app/models/preference_sections/header_section.rb new file mode 100644 index 00000000000..87e09d6e032 --- /dev/null +++ b/app/models/preference_sections/header_section.rb @@ -0,0 +1,15 @@ +module PreferenceSections + class HeaderSection + def name + I18n.t('admin.contents.edit.header') + end + + def preferences + [ + :logo, + :logo_mobile, + :logo_mobile_svg + ] + end + end +end diff --git a/app/models/preference_sections/home_page_section.rb b/app/models/preference_sections/home_page_section.rb new file mode 100644 index 00000000000..05e58c55238 --- /dev/null +++ b/app/models/preference_sections/home_page_section.rb @@ -0,0 +1,14 @@ +module PreferenceSections + class HomePageSection + def name + I18n.t('admin.contents.edit.home_page') + end + + def preferences + [ + :home_hero, + :home_show_stats + ] + end + end +end diff --git a/app/models/preference_sections/hub_signup_page_section.rb b/app/models/preference_sections/hub_signup_page_section.rb new file mode 100644 index 00000000000..3c80e3ebff3 --- /dev/null +++ b/app/models/preference_sections/hub_signup_page_section.rb @@ -0,0 +1,15 @@ +module PreferenceSections + class HubSignupPageSection + def name + I18n.t('admin.contents.edit.hub_signup_page') + end + + def preferences + [ + :hub_signup_pricing_table_html, + :hub_signup_case_studies_html, + :hub_signup_detail_html + ] + end + end +end diff --git a/app/models/preference_sections/main_links_section.rb b/app/models/preference_sections/main_links_section.rb new file mode 100644 index 00000000000..b23833650f3 --- /dev/null +++ b/app/models/preference_sections/main_links_section.rb @@ -0,0 +1,26 @@ +module PreferenceSections + class MainLinksSection + def name + I18n.t('admin.contents.edit.main_links') + end + + def preferences + [ + :menu_1, + :menu_1_icon_name, + :menu_2, + :menu_2_icon_name, + :menu_3, + :menu_3_icon_name, + :menu_4, + :menu_4_icon_name, + :menu_5, + :menu_5_icon_name, + :menu_6, + :menu_6_icon_name, + :menu_7, + :menu_7_icon_name + ] + end + end +end diff --git a/app/models/preference_sections/producer_signup_page_section.rb b/app/models/preference_sections/producer_signup_page_section.rb new file mode 100644 index 00000000000..bf96894c531 --- /dev/null +++ b/app/models/preference_sections/producer_signup_page_section.rb @@ -0,0 +1,15 @@ +module PreferenceSections + class ProducerSignupPageSection + def name + I18n.t('admin.contents.edit.producer_signup_page') + end + + def preferences + [ + :producer_signup_pricing_table_html, + :producer_signup_case_studies_html, + :producer_signup_detail_html + ] + end + end +end diff --git a/app/models/preference_sections/user_guide_section.rb b/app/models/preference_sections/user_guide_section.rb new file mode 100644 index 00000000000..4c1323959f6 --- /dev/null +++ b/app/models/preference_sections/user_guide_section.rb @@ -0,0 +1,11 @@ +module PreferenceSections + class UserGuideSection + def name + I18n.t('admin.contents.edit.user_guide') + end + + def preferences + [:user_guide_link] + end + end +end diff --git a/app/models/product_import/entry_processor.rb b/app/models/product_import/entry_processor.rb index 97f9cbe55cc..22bae539077 100644 --- a/app/models/product_import/entry_processor.rb +++ b/app/models/product_import/entry_processor.rb @@ -4,12 +4,14 @@ module ProductImport class EntryProcessor - attr_reader :inventory_created, :inventory_updated, :products_created, :variants_created, :variants_updated, :products_reset_count, :supplier_products, :total_supplier_products + attr_reader :inventory_created, :inventory_updated, :products_created, + :variants_created, :variants_updated, :supplier_products, + :total_supplier_products, :products_reset_count def initialize(importer, validator, import_settings, spreadsheet_data, editable_enterprises, import_time, updated_ids) @importer = importer @validator = validator - @import_settings = import_settings + @settings = Settings.new(import_settings) @spreadsheet_data = spreadsheet_data @editable_enterprises = editable_enterprises @import_time = import_time @@ -43,7 +45,7 @@ def count_existing_items next unless supplier_id && permission_by_id?(supplier_id) products_count = - if importing_into_inventory? + if settings.importing_into_inventory? VariantOverride.where('variant_overrides.hub_id IN (?)', supplier_id).count else Spree::Variant. @@ -60,45 +62,39 @@ def count_existing_items end def reset_absent_items - # For selected enterprises; set stock to zero for all products/inventory - # that were not listed in the newly uploaded spreadsheet - return unless data_for_stock_reset? - suppliers_to_reset_products = [] - suppliers_to_reset_inventories = [] + return unless settings.data_for_stock_reset? && settings.reset_all_absent? - settings = @import_settings[:settings] + @products_reset_count = reset_absent.call + end - @import_settings[:enterprises_to_reset].each do |enterprise_id| - suppliers_to_reset_products.push Integer(enterprise_id) if settings['reset_all_absent'] && permission_by_id?(enterprise_id) && !importing_into_inventory? - suppliers_to_reset_inventories.push Integer(enterprise_id) if settings['reset_all_absent'] && permission_by_id?(enterprise_id) && importing_into_inventory? - end + def reset_absent + @reset_absent ||= ResetAbsent.new(self, settings, reset_stock_strategy) + end - unless suppliers_to_reset_inventories.empty? - @products_reset_count += VariantOverride. - where('variant_overrides.hub_id IN (?) - AND variant_overrides.id NOT IN (?)', suppliers_to_reset_inventories, @import_settings[:updated_ids]). - update_all(count_on_hand: 0) + def reset_stock_strategy_factory + if settings.importing_into_inventory? + InventoryResetStrategy + else + ProductsResetStrategy end + end - return if suppliers_to_reset_products.empty? - - @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', suppliers_to_reset_products, @import_settings[:updated_ids]). - update_all(count_on_hand: 0) + def reset_stock_strategy + @reset_stock_strategy ||= reset_stock_strategy_factory + .new(settings.updated_ids) end def total_saved_count @products_created + @variants_created + @variants_updated + @inventory_created + @inventory_updated end + def permission_by_id?(supplier_id) + @editable_enterprises.value?(Integer(supplier_id)) + end + private - def data_for_stock_reset? - @import_settings[:settings] && @import_settings[:updated_ids] && @import_settings[:enterprises_to_reset] - end + attr_reader :settings def save_to_inventory(entry) save_new_inventory_item entry if entry.validates_as? 'new_inventory_item' @@ -115,12 +111,18 @@ def save_to_product_list(entry) return unless entry.validates_as? 'existing_variant' - save_variant entry + begin + save_variant entry + rescue ActiveRecord::StaleObjectError + entry.product_object.reload + save_variant entry + end + @variants_updated += 1 end def import_into_inventory?(entry) - entry.supplier_id && @import_settings[:settings]['import_into'] == 'inventories' + entry.supplier_id && settings.importing_into_inventory? end def save_new_inventory_item(entry) @@ -195,16 +197,16 @@ def assign_defaults(object, entry) # Assigns a default value for a specified field e.g. category='Vegetables', setting this value # either for all entries (overwrite_all), or only for those entries where the field was blank # in the spreadsheet (overwrite_empty), depending on selected import settings - return unless @import_settings.key?(:settings) && @import_settings[:settings][entry.supplier_id.to_s] && @import_settings[:settings][entry.supplier_id.to_s]['defaults'] + return unless settings.defaults(entry) - @import_settings[:settings][entry.supplier_id.to_s]['defaults'].each do |attribute, setting| + settings.defaults(entry).each do |attribute, setting| next unless setting['active'] case setting['mode'] when 'overwrite_all' object.assign_attributes(attribute => setting['value']) when 'overwrite_empty' - if object.send(attribute).blank? || ((attribute == 'on_hand' || attribute == 'count_on_hand') && entry.on_hand_nil) + if object.public_send(attribute).blank? || ((attribute == 'on_hand' || attribute == 'count_on_hand') && entry.on_hand_nil) object.assign_attributes(attribute => setting['value']) end end @@ -233,13 +235,5 @@ def ensure_variant_updated(product, entry) variant.import_date = @import_time variant.save end - - def permission_by_id?(supplier_id) - @editable_enterprises.value?(Integer(supplier_id)) - end - - def importing_into_inventory? - @import_settings[:settings] && @import_settings[:settings]['import_into'] == 'inventories' - end end end diff --git a/app/models/product_import/entry_validator.rb b/app/models/product_import/entry_validator.rb index 4f48c7745c3..700c1e43082 100644 --- a/app/models/product_import/entry_validator.rb +++ b/app/models/product_import/entry_validator.rb @@ -14,6 +14,17 @@ def initialize(current_user, import_time, spreadsheet_data, editable_enterprises @import_settings = import_settings end + def self.non_updatable_fields + { + category: :primary_taxon_id, + description: :description, + unit_type: :variant_unit_scale, + variant_unit_name: :variant_unit_name, + tax_category: :tax_category_id, + shipping_category: :shipping_category_id + } + end + def validate_all(entries) entries.each do |entry| supplier_validation(entry) @@ -124,16 +135,15 @@ def producer_validation(entry) end def inventory_validation(entry) - # Checks a potential inventory item corresponds to a valid variant - match = Spree::Product.where(supplier_id: entry.producer_id, name: entry.name, deleted_at: nil).first + products = Spree::Product.where(supplier_id: entry.producer_id, name: entry.name, deleted_at: nil) - if match.nil? + if products.empty? mark_as_invalid(entry, attribute: 'name', error: I18n.t('admin.product_import.model.no_product')) return end - match.variants.each do |existing_variant| - unit_scale = match.variant_unit_scale + products.flat_map(&:variants).each do |existing_variant| + unit_scale = existing_variant.product.variant_unit_scale unscaled_units = entry.unscaled_units || 0 entry.unit_value = unscaled_units * unit_scale @@ -169,31 +179,29 @@ def tax_and_shipping_validation(entry, type, category, index) return if category.blank? if index.key? category - entry.send("#{type}_category_id=", index[category]) + entry.public_send("#{type}_category_id=", index[category]) else mark_as_invalid(entry, attribute: "#{type}_category", error: I18n.t('admin.product_import.model.not_found')) end end def product_validation(entry) - # Find product with matching supplier and name - match = Spree::Product.where(supplier_id: entry.supplier_id, name: entry.name, deleted_at: nil).first + products = Spree::Product.where(supplier_id: entry.supplier_id, name: entry.name, deleted_at: nil) - # If no matching product was found, create a new product - if match.nil? + if products.empty? 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| + products.each { |product| product_field_errors(entry, product) } + + products.flat_map(&:variants).each do |existing_variant| if entry_matches_existing_variant?(entry, existing_variant) && existing_variant.deleted_at.nil? return mark_as_existing_variant(entry, existing_variant) end end - # Otherwise, a variant with sufficiently matching attributes doesn't exist; create a new one - mark_as_new_variant(entry, match.id) + mark_as_new_variant(entry, products.first.id) end def mark_as_new_product(entry) @@ -220,6 +228,21 @@ def mark_as_existing_variant(entry, existing_variant) end end + def product_field_errors(entry, existing_product) + EntryValidator.non_updatable_fields.each do |display_name, attribute| + next if attributes_match?(attribute, existing_product, entry) || attributes_blank?(attribute, existing_product, entry) + mark_as_invalid(entry, attribute: display_name, error: I18n.t('admin.product_import.model.not_updatable')) + end + end + + def attributes_match?(attribute, existing_product, entry) + existing_product.public_send(attribute) == entry.public_send(attribute) + end + + def attributes_blank?(attribute, existing_product, entry) + existing_product.public_send(attribute).blank? && entry.public_send(attribute).blank? + end + def permission_by_name?(supplier_name) @editable_enterprises.key?(supplier_name) end diff --git a/app/models/product_import/inventory_reset_strategy.rb b/app/models/product_import/inventory_reset_strategy.rb new file mode 100644 index 00000000000..c22cb6218a0 --- /dev/null +++ b/app/models/product_import/inventory_reset_strategy.rb @@ -0,0 +1,28 @@ +module ProductImport + class InventoryResetStrategy + def initialize(excluded_items_ids) + @excluded_items_ids = excluded_items_ids + end + + def reset(supplier_ids) + @supplier_ids = supplier_ids + + if supplier_ids.present? + relation.update_all(count_on_hand: 0) + else + 0 + end + end + + private + + attr_reader :excluded_items_ids, :supplier_ids + + def relation + relation = VariantOverride.where(hub_id: supplier_ids) + return relation if excluded_items_ids.blank? + + relation.where('id NOT IN (?)', excluded_items_ids) + end + end +end diff --git a/app/models/product_import/product_importer.rb b/app/models/product_import/product_importer.rb index 99362b8c1b4..6cabcc9e2f8 100644 --- a/app/models/product_import/product_importer.rb +++ b/app/models/product_import/product_importer.rb @@ -57,6 +57,13 @@ def item_count @sheet ? @sheet.last_row - 1 : 0 end + def product_field_errors? + @entries.each do |entry| + return true if entry.errors.messages.values.include? [I18n.t('admin.product_import.model.not_updatable')] + end + false + end + def reset_counts # Return indexed data about existing product count, reset count, and updates count per supplier @reset_counts.each do |supplier_id, values| diff --git a/app/models/product_import/products_reset_strategy.rb b/app/models/product_import/products_reset_strategy.rb new file mode 100644 index 00000000000..80dd6a448ca --- /dev/null +++ b/app/models/product_import/products_reset_strategy.rb @@ -0,0 +1,34 @@ +module ProductImport + class ProductsResetStrategy + def initialize(excluded_items_ids) + @excluded_items_ids = excluded_items_ids + end + + def reset(supplier_ids) + @supplier_ids = supplier_ids + + if supplier_ids.present? + relation.update_all(count_on_hand: 0) + else + 0 + end + end + + private + + attr_reader :excluded_items_ids, :supplier_ids + + def relation + relation = Spree::Variant + .joins(:product) + .where( + spree_products: { supplier_id: supplier_ids }, + spree_variants: { is_master: false, deleted_at: nil } + ) + + return relation if excluded_items_ids.blank? + + relation.where('spree_variants.id NOT IN (?)', excluded_items_ids) + end + end +end diff --git a/app/models/product_import/reset_absent.rb b/app/models/product_import/reset_absent.rb new file mode 100644 index 00000000000..549ea1e7deb --- /dev/null +++ b/app/models/product_import/reset_absent.rb @@ -0,0 +1,31 @@ +module ProductImport + class ResetAbsent + def initialize(entry_processor, settings, reset_stock_strategy) + @entry_processor = entry_processor + @settings = settings + @reset_stock_strategy = reset_stock_strategy + end + + # For selected enterprises; set stock to zero for all products/inventory + # that were not listed in the newly uploaded spreadsheet + # + # @return [Integer] number of items affected by the reset + def call + reset_stock_strategy.reset(authorized_enterprises) + end + + private + + attr_reader :settings, :reset_stock_strategy, :entry_processor + + # Returns the enterprises that have permissions to be reset + # + # @return [Array] array of Enterprise ids + def authorized_enterprises + settings.enterprises_to_reset.map do |enterprise_id| + next unless entry_processor.permission_by_id?(enterprise_id) + enterprise_id.to_i + end + end + end +end diff --git a/app/models/product_import/settings.rb b/app/models/product_import/settings.rb new file mode 100644 index 00000000000..4a0df965cbf --- /dev/null +++ b/app/models/product_import/settings.rb @@ -0,0 +1,37 @@ +module ProductImport + class Settings + def initialize(import_settings) + @import_settings = import_settings + end + + def defaults(entry) + @import_settings.key?(:settings) && + settings[entry.supplier_id.to_s] && + settings[entry.supplier_id.to_s]['defaults'] + end + + def settings + @import_settings[:settings] + end + + def updated_ids + @import_settings[:updated_ids] + end + + def enterprises_to_reset + @import_settings[:enterprises_to_reset] + end + + def importing_into_inventory? + settings && settings['import_into'] == 'inventories' + end + + def reset_all_absent? + settings['reset_all_absent'] + end + + def data_for_stock_reset? + !!(settings && updated_ids && enterprises_to_reset) + end + end +end diff --git a/app/models/product_import/spreadsheet_entry.rb b/app/models/product_import/spreadsheet_entry.rb index 8b3e2d54f0e..fed45a6645b 100644 --- a/app/models/product_import/spreadsheet_entry.rb +++ b/app/models/product_import/spreadsheet_entry.rb @@ -71,7 +71,7 @@ def assign_units(attrs) units.converted_attributes.each do |attr, value| if respond_to?("#{attr}=") - send("#{attr}=", value) unless non_product_attributes.include?(attr) + public_send("#{attr}=", value) unless non_product_attributes.include?(attr) end end end diff --git a/app/models/proxy_order.rb b/app/models/proxy_order.rb index 8c983faa91e..bcfa1b1cef8 100644 --- a/app/models/proxy_order.rb +++ b/app/models/proxy_order.rb @@ -19,7 +19,7 @@ class ProxyOrder < ActiveRecord::Base def state # NOTE: the order is important here %w(canceled paused pending cart).each do |state| - return state if send("#{state}?") + return state if __send__("#{state}?") end order.state end @@ -32,7 +32,7 @@ def cancel return false unless order_cycle.orders_close_at.andand > Time.zone.now transaction do update_column(:canceled_at, Time.zone.now) - order.send('cancel') if order + order.cancel if order true end end @@ -41,7 +41,7 @@ def resume return false unless order_cycle.orders_close_at.andand > Time.zone.now transaction do update_column(:canceled_at, nil) - order.send('resume') if order + order.resume if order true end end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index a30dc22be7c..bd38794b939 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -97,7 +97,7 @@ def add_enterprise_management_abilities(user) end can [:admin, :index, :create], Enterprise - can [:read, :edit, :update, :bulk_update, :resend_confirmation], Enterprise do |enterprise| + can [:read, :edit, :update, :remove_logo, :remove_promo_image, :bulk_update, :resend_confirmation], Enterprise do |enterprise| OpenFoodNetwork::Permissions.new(user).editable_enterprises.include? enterprise end can [:welcome, :register], Enterprise do |enterprise| diff --git a/app/models/spree/calculator/default_tax_decorator.rb b/app/models/spree/calculator/default_tax_decorator.rb index 45149b6d4de..a70bb4f8ba6 100644 --- a/app/models/spree/calculator/default_tax_decorator.rb +++ b/app/models/spree/calculator/default_tax_decorator.rb @@ -18,15 +18,15 @@ def compute_order(order) # Added this block, finds relevant fees for each line_item, calculates the tax on them, and returns the total tax per_item_fees_total = order.line_items.sum do |line_item| - calculator.send(:per_item_enterprise_fee_applicators_for, line_item.variant) + calculator.per_item_enterprise_fee_applicators_for(line_item.variant) .select { |applicator| (!applicator.enterprise_fee.inherits_tax_category && applicator.enterprise_fee.tax_category == rate.tax_category) || - (applicator.enterprise_fee.inherits_tax_category && line_item.product.tax_category == rate.tax_category) + (applicator.enterprise_fee.inherits_tax_category && line_item.product.tax_category == rate.tax_category) } .sum { |applicator| applicator.enterprise_fee.compute_amount(line_item) } end # Added this block, finds relevant fees for whole order, calculates the tax on them, and returns the total tax - per_order_fees_total = calculator.send(:per_order_enterprise_fee_applicators_for, order) + per_order_fees_total = calculator.per_order_enterprise_fee_applicators_for(order) .select { |applicator| applicator.enterprise_fee.tax_category == rate.tax_category } .sum { |applicator| applicator.enterprise_fee.compute_amount(order) } diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index 7af42821f6c..b606d058da4 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -1,3 +1,4 @@ +require 'open_food_network/scope_variant_to_hub' require 'open_food_network/variant_and_line_item_naming' Spree::LineItem.class_eval do diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 92cdece3307..948cfc494ab 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -33,6 +33,12 @@ # See: https://guides.spreecommerce.org/developer/checkout.html#modifying-the-checkout-flow remove_checkout_step :confirm + state_machine.after_transition to: :payment, do: :charge_shipping_and_payment_fees! + + state_machine.event :restart_checkout do + transition to: :cart, unless: :completed? + end + # -- Scopes scope :managed_by, lambda { |user| if user.has_spree_role?('admin') @@ -390,17 +396,13 @@ def update_adjustment!(adjustment) adjustment.state = state end - # object_params sets the payment amount to the order total, but it does this before - # the shipping method is set. This results in the customer not being charged for their - # order's shipping. To fix this, we refresh the payment amount here. + # object_params sets the payment amount to the order total, but it does this + # before the shipping method is set. This results in the customer not being + # charged for their order's shipping. To fix this, we refresh the payment + # amount here. def charge_shipping_and_payment_fees! update_totals return unless payments.any? payments.first.update_attribute :amount, total end end - -Spree::Order.state_machine.after_transition to: :payment, do: :charge_shipping_and_payment_fees! -Spree::Order.state_machine.event :restart_checkout do - transition :to => :cart, unless: :completed? -end diff --git a/app/models/spree/preferences/file_configuration.rb b/app/models/spree/preferences/file_configuration.rb index 02c5ae3646c..f918f52af5d 100644 --- a/app/models/spree/preferences/file_configuration.rb +++ b/app/models/spree/preferences/file_configuration.rb @@ -15,7 +15,7 @@ def self.preference(name, type, *args) def get_preference(key) if !has_preference?(key) && has_attachment?(key) - send key + public_send key else super key end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 64e5c746774..e61f06ee379 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -177,10 +177,7 @@ def variants_distributed_by(order_cycle, distributor) # Get the most recent import_date of a product's variants def import_date - variants.map do |variant| - next if variant.import_date.blank? - variant.import_date - end.sort.last + variants.map(&:import_date).compact.max end # Build a product distribution for each distributor diff --git a/app/models/spree/product_set.rb b/app/models/spree/product_set.rb index 1a73ab00fe6..d8716b821f7 100644 --- a/app/models/spree/product_set.rb +++ b/app/models/spree/product_set.rb @@ -3,21 +3,67 @@ def initialize(attributes={}) super(Spree::Product, [], attributes, proc { |attrs| attrs[:product_id].blank? }) end - # A separate method of updating products was required due to an issue with the way Rails' assign_attributes and updates_attributes behave when delegated attributes of a nested - # object are updated via the parent object (ie. price of variants). Updating such attributes by themselves did not work using: - # product.update_attributes( { variants_attributes: [ { id: y, price: xx.x } ] } ) - # and so an explicit call to update attributes on each individual variant was required. ie: - # variant.update_attributes( { price: xx.x } ) + # A separate method of updating products was required due to an issue with + # the way Rails' assign_attributes and updates_attributes behave when + # delegated attributes of a nested object are updated via the parent object + # (ie. price of variants). Updating such attributes by themselves did not + # work using: + # + # product.update_attributes(variants_attributes: [{ id: y, price: xx.x }]) + # + # and so an explicit call to update attributes on each individual variant was + # required. ie: + # + # variant.update_attributes( { price: xx.x } ) + # def update_attributes(attributes) - attributes[:taxon_ids] = attributes[:taxon_ids].split(',') if attributes[:taxon_ids].present? - e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? } - if e.nil? + if attributes[:taxon_ids].present? + attributes[:taxon_ids] = attributes[:taxon_ids].split(',') + end + + found_model = @collection.find do |model| + model.id.to_s == attributes[:id].to_s && model.persisted? + end + + if found_model.nil? @klass.new(attributes).save unless @reject_if.andand.call(attributes) else - ( attributes.except(:id, :variants_attributes, :master_attributes).present? ? e.update_attributes(attributes.except(:id, :variants_attributes, :master_attributes)) : true) and - (attributes[:variants_attributes] ? update_variants_attributes(e, attributes[:variants_attributes]) : true ) and - (attributes[:master_attributes] ? update_variant(e, attributes[:master_attributes]) : true ) + update_product_only_attributes(found_model, attributes) && + update_product_variants(found_model, attributes) && + update_product_master(found_model, attributes) + end + end + + def update_product_only_attributes(product, attributes) + variant_related_attrs = [:id, :variants_attributes, :master_attributes] + product_related_attrs = attributes.except(*variant_related_attrs) + + return true if product_related_attrs.blank? + + product.assign_attributes(product_related_attrs) + + product.variants.each do |variant| + validate_presence_of_unit_value(product, variant) end + + product.save if errors.empty? + end + + def validate_presence_of_unit_value(product, variant) + return unless %w(weight volume).include?(product.variant_unit) + return if variant.unit_value.present? + + product.errors.add(:unit_value, "can't be blank") + end + + def update_product_variants(product, attributes) + return true unless attributes[:variants_attributes] + update_variants_attributes(product, attributes[:variants_attributes]) + end + + def update_product_master(product, attributes) + return true unless attributes[:master_attributes] + update_variant(product, attributes[:master_attributes]) end def update_variants_attributes(product, variants_attributes) @@ -27,16 +73,20 @@ def update_variants_attributes(product, variants_attributes) end def update_variant(product, variant_attributes) - e = product.variants_including_master.detect { |e| e.id.to_s == variant_attributes[:id].to_s && !e.id.nil? } - if e.present? - e.update_attributes(variant_attributes.except(:id)) + found_variant = product.variants_including_master.find do |variant| + variant.id.to_s == variant_attributes[:id].to_s && variant.persisted? + end + + if found_variant.present? + found_variant.update_attributes(variant_attributes.except(:id)) else - product.variants.create variant_attributes + product.variants.create(variant_attributes) end end def collection_attributes=(attributes) - @collection = Spree::Product.where( :id => attributes.each_value.map{ |p| p[:id] } ) + @collection = Spree::Product + .where(id: attributes.each_value.map { |product| product[:id] }) @collection_hash = attributes end diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index cf375e7e65d..dec0309d2b7 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -24,8 +24,6 @@ # We use the same options as Spree and add :confirmable devise :confirmable, reconfirmable: true - handle_asynchronously :send_confirmation_instructions - handle_asynchronously :send_on_create_confirmation_instructions # TODO: Later versions of devise have a dedicated after_confirmation callback, so use that after_update :welcome_after_confirm, if: lambda { confirmation_token_changed? && confirmation_token.nil? } diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 41529e39490..b45be2bdfe3 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -20,11 +20,13 @@ attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name, :import_date accepts_nested_attributes_for :images - validates_presence_of :unit_value, - if: -> v { %w(weight volume).include? v.product.andand.variant_unit } + validates :unit_value, presence: true, if: -> (variant) { + %w(weight volume).include?(variant.product.andand.variant_unit) + } - validates_presence_of :unit_description, - if: -> v { v.product.andand.variant_unit.present? && v.unit_value.nil? } + validates :unit_description, presence: true, if: -> (variant) { + variant.product.andand.variant_unit.present? && variant.unit_value.nil? + } before_validation :update_weight_from_unit_value, if: -> v { v.product.present? } after_save :update_units diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 165b063ae0f..f217bcb0ec2 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -51,7 +51,7 @@ def paused? def state # NOTE: the order is important here %w(canceled paused pending ended).each do |state| - return state if send("#{state}?") + return state if __send__("#{state}?") end "active" end diff --git a/app/overrides/add_capture_order_shortcut.rb b/app/overrides/add_capture_order_shortcut.rb deleted file mode 100644 index db6947cf08d..00000000000 --- a/app/overrides/add_capture_order_shortcut.rb +++ /dev/null @@ -1,11 +0,0 @@ -Deface::Override.new(:virtual_path => "spree/admin/orders/index", - :name => "add_capture_order_shortcut", - :insert_bottom => "[data-hook='admin_orders_index_row_actions']", - :partial => 'spree/admin/orders/capture' - ) -# And align actions column (not spree standard, but looks better IMO) -Deface::Override.new(:virtual_path => "spree/admin/orders/index", - :name => "add_capture_order_shortcut_align", - :set_attributes => "[data-hook='admin_orders_index_row_actions']", - :attributes => {:class => "actions", :style => "text-align:left;"} #removes 'align-center' class - ) diff --git a/app/overrides/add_orders_admin_sub_menu.rb b/app/overrides/add_orders_admin_sub_menu.rb deleted file mode 100644 index ff261454216..00000000000 --- a/app/overrides/add_orders_admin_sub_menu.rb +++ /dev/null @@ -1,4 +0,0 @@ -Deface::Override.new(:virtual_path => "spree/admin/orders/index", - :name => "add_orders_admin_sub_menu", - :insert_before => "code[erb-silent]:contains('content_for :table_filter_title do')", - :text => "<%= render :partial => 'spree/admin/shared/order_sub_menu' %>") diff --git a/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface b/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface deleted file mode 100644 index 7068d94d2d4..00000000000 --- a/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface +++ /dev/null @@ -1,13 +0,0 @@ -/ insert_before "div.clearfix" - -.field-block.alpha.eight.columns - = label_tag nil, t(:distributors) - = select_tag("q[distributor_id_in]", - options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]), - {class: "select2 fullwidth", multiple: true}) - -.field-block.alpha.eight.columns - = label_tag nil, t(:order_cycles) - = select_tag("q[order_cycle_id_in]", - options_for_select(OrderCycle.managed_by(spree_current_user).where('order_cycles.orders_close_at is not null').order('order_cycles.orders_close_at DESC').map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]), - {class: "select2 fullwidth", multiple: true}) diff --git a/app/overrides/spree/admin/orders/index/add_distributor_td.html.haml.deface b/app/overrides/spree/admin/orders/index/add_distributor_td.html.haml.deface deleted file mode 100644 index 5d9b7ab0833..00000000000 --- a/app/overrides/spree/admin/orders/index/add_distributor_td.html.haml.deface +++ /dev/null @@ -1,4 +0,0 @@ -/ insert_top "[data-hook='admin_orders_index_rows']" - -%td.align-center - = order.distributor.andand.name diff --git a/app/overrides/spree/admin/orders/index/add_distributor_th.html.haml.deface b/app/overrides/spree/admin/orders/index/add_distributor_th.html.haml.deface deleted file mode 100644 index bba71ef434f..00000000000 --- a/app/overrides/spree/admin/orders/index/add_distributor_th.html.haml.deface +++ /dev/null @@ -1,4 +0,0 @@ -/ insert_top "[data-hook='admin_orders_index_headers']" - -%th - = t(:products_distributor) diff --git a/app/overrides/spree/admin/orders/index/add_ship_shortcut.html.haml.deface b/app/overrides/spree/admin/orders/index/add_ship_shortcut.html.haml.deface deleted file mode 100644 index a0ac8a72663..00000000000 --- a/app/overrides/spree/admin/orders/index/add_ship_shortcut.html.haml.deface +++ /dev/null @@ -1,6 +0,0 @@ -/ insert_bottom "[data-hook='admin_orders_index_row_actions']" --# See also: app/overrides/add_capture_order_shortcut.rb - -- if order.ready_to_ship? - - # copied from backend/app/views/spree/admin/payments/_list.html.erb - = link_to_with_icon "icon-road", t('admin.orders.index.ship'), fire_admin_order_url(order, :e => 'ship'), :method => :put, :no_text => true, :data => {:action => 'ship', :confirm => t(:are_you_sure)} diff --git a/app/overrides/spree/admin/orders/index/add_special_instructions.html.haml.deface b/app/overrides/spree/admin/orders/index/add_special_instructions.html.haml.deface deleted file mode 100644 index 1711343f3cb..00000000000 --- a/app/overrides/spree/admin/orders/index/add_special_instructions.html.haml.deface +++ /dev/null @@ -1,6 +0,0 @@ -/ insert_bottom "[data-hook='admin_orders_index_rows'] td:nth-child(3)" - -- if order.special_instructions.present? - %br - %span{class: "icon-warning-sign", "ofn-with-tip" => order.special_instructions} - notes diff --git a/app/overrides/spree/admin/orders/index/rearrange_cols.html.haml.deface b/app/overrides/spree/admin/orders/index/rearrange_cols.html.haml.deface deleted file mode 100644 index 39344b460e8..00000000000 --- a/app/overrides/spree/admin/orders/index/rearrange_cols.html.haml.deface +++ /dev/null @@ -1,5 +0,0 @@ -/ replace_contents "table#listing_orders colgroup" --# See also: add_capture_order_shortcut, admin/orders/index/add_distributor_*to_admin_orders - -%col{style: "width: 10%"} --# There are 8 other columns, but they seem to sort themselves out :) diff --git a/app/overrides/spree/admin/orders/index/set_ng_app.deface b/app/overrides/spree/admin/orders/index/set_ng_app.deface deleted file mode 100644 index 9ca071be11b..00000000000 --- a/app/overrides/spree/admin/orders/index/set_ng_app.deface +++ /dev/null @@ -1,2 +0,0 @@ -add_to_attributes "table#listing_orders" -attributes "ng-app" => "ofn.admin" diff --git a/app/overrides/spree/admin/product_properties/index/add_producer_properties_warning_and_table.html.haml.deface b/app/overrides/spree/admin/product_properties/index/add_producer_properties_warning_and_table.html.haml.deface index c31a74b9e45..149f72015af 100644 --- a/app/overrides/spree/admin/product_properties/index/add_producer_properties_warning_and_table.html.haml.deface +++ b/app/overrides/spree/admin/product_properties/index/add_producer_properties_warning_and_table.html.haml.deface @@ -1,7 +1,7 @@ / insert_after 'table.index.sortable' =f.check_box :inherits_properties -=f.label :inherits_properties, "Inherit properties from #{@product.supplier.name}? (unless overridden above)" +=f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name) %br %br diff --git a/app/serializers/api/admin/enterprise_fee_serializer.rb b/app/serializers/api/admin/enterprise_fee_serializer.rb index 96c279d1c07..7bf2ea4e144 100644 --- a/app/serializers/api/admin/enterprise_fee_serializer.rb +++ b/app/serializers/api/admin/enterprise_fee_serializer.rb @@ -15,7 +15,7 @@ def calculator_settings result = nil - options[:controller].send(:with_format, :html) do + options[:controller].__send__(:with_format, :html) do result = options[:controller].render_to_string :partial => 'admin/enterprise_fees/calculator_settings', :locals => {:enterprise_fee => object} end diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb index e29954992f5..348bb75461a 100644 --- a/app/serializers/api/admin/enterprise_serializer.rb +++ b/app/serializers/api/admin/enterprise_serializer.rb @@ -5,11 +5,20 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer attributes :preferred_product_selection_from_inventory_only attributes :owner, :contact, :users, :tag_groups, :default_tag_group attributes :require_login, :allow_guest_orders, :allow_order_changes + attributes :logo, :promo_image has_one :owner, serializer: Api::Admin::UserSerializer has_many :users, serializer: Api::Admin::UserSerializer has_one :address, serializer: Api::AddressSerializer + def logo + attachment_urls(object.logo, [:thumb, :small, :medium]) + end + + def promo_image + attachment_urls(object.promo_image, [:thumb, :medium, :large]) + end + def tag_groups object.tag_rules.prioritised.reject(&:is_default).each_with_object([]) do |tag_rule, tag_groups| tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(",").map{ |t| { text: t } }) @@ -33,4 +42,24 @@ def find_match(tag_groups, tags) end return { tags: tags, rules: [] } end + + private + + # Returns a hash of URLs for specified versions of an attachment. + # + # Example: + # + # attachment_urls(object.logo, [:thumb, :small, :medium]) + # # { + # # thumb: LOGO_THUMB_URL, + # # small: LOGO_SMALL_URL, + # # medium: LOGO_MEDIUM_URL + # # } + def attachment_urls(attachment, versions) + return unless attachment.exists? + + versions.each_with_object({}) do |version, urls| + urls[version] = attachment.url(version) + end + end end diff --git a/app/serializers/api/admin/order_serializer.rb b/app/serializers/api/admin/order_serializer.rb index 6f22ba1e946..e3207b30fd9 100644 --- a/app/serializers/api/admin/order_serializer.rb +++ b/app/serializers/api/admin/order_serializer.rb @@ -1,5 +1,8 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer - attributes :id, :number, :full_name, :email, :phone, :completed_at + attributes :id, :number, :full_name, :email, :phone, :completed_at, :display_total + attributes :show_path, :edit_path, :state, :payment_state, :shipment_state + attributes :payments_path, :shipments_path, :ship_path, :ready_to_ship, :created_at + attributes :distributor_name, :special_instructions, :payment_capture_path has_one :distributor, serializer: Api::Admin::IdSerializer has_one :order_cycle, serializer: Api::Admin::IdSerializer @@ -8,6 +11,48 @@ def full_name object.billing_address.nil? ? "" : ( object.billing_address.full_name || "" ) end + def distributor_name + object.distributor.andand.name + end + + def show_path + return '' unless object.id + spree_routes_helper.admin_order_path(object) + end + + def edit_path + return '' unless object.id + spree_routes_helper.edit_admin_order_path(object) + end + + def payments_path + return '' unless object.payment_state + spree_routes_helper.admin_order_payments_path(object) + end + + def shipments_path + return '' unless object.shipment_state + spree_routes_helper.admin_order_shipments_path(object) + end + + def ship_path + spree_routes_helper.fire_admin_order_path(object, e: 'ship') + end + + def payment_capture_path + pending_payment = object.pending_payments.first + return '' unless object.payment_required? && pending_payment + spree_routes_helper.fire_admin_order_payment_path(object, pending_payment.id, e: 'capture') + end + + def ready_to_ship + object.ready_to_ship? + end + + def display_total + object.display_total.to_html + end + def email object.email || "" end @@ -16,7 +61,17 @@ def phone object.billing_address.nil? ? "a" : ( object.billing_address.phone || "" ) end + def created_at + object.created_at.blank? ? "" : I18n.l(object.created_at, format: '%B %d, %Y') + end + def completed_at - object.completed_at.blank? ? "" : object.completed_at.strftime("%F %T") + object.completed_at.blank? ? "" : I18n.l(object.completed_at, format: '%B %d, %Y') + end + + private + + def spree_routes_helper + Spree::Core::Engine.routes_url_helpers end end diff --git a/app/services/cookies_consent.rb b/app/services/cookies_consent.rb deleted file mode 100644 index 64c4dfa083a..00000000000 --- a/app/services/cookies_consent.rb +++ /dev/null @@ -1,29 +0,0 @@ -class CookiesConsent - COOKIE_NAME = 'cookies_consent'.freeze - - def initialize(cookies, domain) - @cookies = cookies - @domain = domain - end - - def exists? - cookies.key?(COOKIE_NAME) - end - - def destroy - cookies.delete(COOKIE_NAME, domain: domain) - end - - def set - cookies[COOKIE_NAME] = { - value: COOKIE_NAME, - expires: 1.year.from_now, - domain: domain, - httponly: true - } - end - - private - - attr_reader :cookies, :domain -end diff --git a/app/services/order_factory.rb b/app/services/order_factory.rb index fdef7b6b667..68e7224158b 100644 --- a/app/services/order_factory.rb +++ b/app/services/order_factory.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + # Builds orders based on a set of attributes # There are some idiosyncracies in the order creation process, # and it is nice to have them dealt with in one place. diff --git a/app/services/order_syncer.rb b/app/services/order_syncer.rb index c7b7c871957..de7aa44790a 100644 --- a/app/services/order_syncer.rb +++ b/app/services/order_syncer.rb @@ -83,7 +83,7 @@ def relevant_address_attrs def addresses_match?(order_address, subscription_address) relevant_address_attrs.all? do |attr| - order_address[attr] == subscription_address.send("#{attr}_was") || + order_address[attr] == subscription_address.public_send("#{attr}_was") || order_address[attr] == subscription_address[attr] end end @@ -101,7 +101,7 @@ def ship_address_updatable?(order) # address on the order matches the shop's address def force_ship_address_required?(order) return false unless shipping_method.require_ship_address? - distributor_address = order.send(:address_from_distributor) + distributor_address = order.__send__(:address_from_distributor) relevant_address_attrs.all? do |attr| order.ship_address[attr] == distributor_address[attr] end diff --git a/app/services/subscription_estimator.rb b/app/services/subscription_estimator.rb index 5abe3880292..8e69e310ac9 100644 --- a/app/services/subscription_estimator.rb +++ b/app/services/subscription_estimator.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + # Responsible for estimating prices and fees for subscriptions # Used by SubscriptionForm as part of the create/update process # The values calculated here are intended to be persisted in the db diff --git a/app/views/admin/enterprise_groups/_form_address.html.haml b/app/views/admin/enterprise_groups/_form_address.html.haml index 2b8add53923..1e93c5f7f9d 100644 --- a/app/views/admin/enterprise_groups/_form_address.html.haml +++ b/app/views/admin/enterprise_groups/_form_address.html.haml @@ -5,7 +5,7 @@ .alpha.three.columns = af.label :phone .omega.eight.columns - = af.text_field :phone, { placeholder: t(:admin_entreprise_groups_contact_phone_placeholder)} + = af.text_field :phone, { placeholder: t(:admin_enterprise_groups_contact_phone_placeholder)} .row .alpha.three.columns = f.label :email @@ -15,7 +15,7 @@ .three.columns.alpha = af.label :address1 .eight.columns.omega - = af.text_field :address1, { placeholder: t(:admin_entreprise_groups_contact_address1_placeholder)} + = af.text_field :address1, { placeholder: t(:admin_enterprise_groups_contact_address1_placeholder)} .row .alpha.three.columns = af.label :address2 @@ -23,17 +23,17 @@ = af.text_field :address2 .row .three.columns.alpha - = af.label :city, t(:admin_entreprise_groups_contact_city) + = af.label :city, t(:admin_enterprise_groups_contact_city) \/ - = af.label :zipcode, t(:admin_entreprise_groups_contact_zipcode) + = af.label :zipcode, t(:admin_enterprise_groups_contact_zipcode) .four.columns - = af.text_field :city, { placeholder: t(:admin_entreprise_groups_contact_city_placeholder)} + = af.text_field :city, { placeholder: t(:admin_enterprise_groups_contact_city_placeholder)} .four.columns.omega - = af.text_field :zipcode, { placeholder: t(:admin_entreprise_groups_contact_zipcode_placeholder)} + = af.text_field :zipcode, { placeholder: t(:admin_enterprise_groups_contact_zipcode_placeholder)} .row .three.columns.alpha - = af.label :state_id, t(:admin_entreprise_groups_contact_state_id) - = af.label :country_id, t(:admin_entreprise_groups_contact_country_id) + = af.label :state_id, t(:admin_enterprise_groups_contact_state_id) + = af.label :country_id, t(:admin_enterprise_groups_contact_country_id) .four.columns = af.collection_select :state_id, af.object.country.states, :id, :name, {}, :class => "select2 fullwidth" .four.columns.omega diff --git a/app/views/admin/enterprise_groups/_form_images.html.haml b/app/views/admin/enterprise_groups/_form_images.html.haml index 7add7c914a8..aa31913a201 100644 --- a/app/views/admin/enterprise_groups/_form_images.html.haml +++ b/app/views/admin/enterprise_groups/_form_images.html.haml @@ -2,16 +2,16 @@ %legend {{menu.selected.label}} .row .alpha.three.columns - = f.label :logo, 'ofn-with-tip' => t('admin_entreprise_groups_data_powertip_logo') - %div{'ofn-with-tip' => t('admin_entreprise_groups_data_powertip_logo')} + = f.label :logo, 'ofn-with-tip' => t('admin_enterprise_groups_data_powertip_logo') + %div{'ofn-with-tip' => t('admin_enterprise_groups_data_powertip_logo')} %a= t 'admin.whats_this' .omega.eight.columns = image_tag @object.logo.url if @object.logo.present? = f.file_field :logo .row .alpha.three.columns - = f.label :promo_image, 'ofn-with-tip' => t(:admin_entreprise_groups_data_powertip_promo_image) - %div{'ofn-with-tip' => t('admin_entreprise_groups_data_powertip_promo_image')} + = f.label :promo_image, 'ofn-with-tip' => t(:admin_enterprise_groups_data_powertip_promo_image) + %div{'ofn-with-tip' => t('admin_enterprise_groups_data_powertip_promo_image')} %a= t 'admin.whats_this' .omega.eight.columns = image_tag @object.promo_image.url if @object.promo_image.present? diff --git a/app/views/admin/enterprise_groups/_form_primary_details.html.haml b/app/views/admin/enterprise_groups/_form_primary_details.html.haml index f003963b4e0..ad40764fac8 100644 --- a/app/views/admin/enterprise_groups/_form_primary_details.html.haml +++ b/app/views/admin/enterprise_groups/_form_primary_details.html.haml @@ -11,12 +11,12 @@ = f.text_field :description = f.field_container :on_front_page do - = f.label :on_front_page, t(:admin_entreprise_groups_on_front_page) + = f.label :on_front_page, t(:admin_enterprise_groups_on_front_page) %br/ = f.check_box :on_front_page = f.field_container :enterprise_ids do - = f.label :enterprise_ids, t(:admin_entreprise_groups_entreprise) + = f.label :enterprise_ids, t(:admin_enterprise_groups_enterprise) %br/ = f.collection_select :enterprise_ids, @enterprises, :id, :name, {}, {class: "select2 fullwidth", multiple: true} diff --git a/app/views/admin/enterprise_groups/_form_users.html.haml b/app/views/admin/enterprise_groups/_form_users.html.haml index 22ac7921ca9..f96212064eb 100644 --- a/app/views/admin/enterprise_groups/_form_users.html.haml +++ b/app/views/admin/enterprise_groups/_form_users.html.haml @@ -2,8 +2,8 @@ %legend {{menu.selected.label}} .row .three.columns.alpha - =f.label :owner_id, t(:admin_entreprise_groups_owner) - .with-tip{'data-powertip' => t(:admin_entreprise_groups_data_powertip)} + =f.label :owner_id, t(:admin_enterprise_groups_owner) + .with-tip{'data-powertip' => t(:admin_enterprise_groups_data_powertip)} %a = t 'admin.whats_this' .eight.columns.omega diff --git a/app/views/admin/enterprise_groups/_form_web.html.haml b/app/views/admin/enterprise_groups/_form_web.html.haml index e0de2f71599..ad155ac95c8 100644 --- a/app/views/admin/enterprise_groups/_form_web.html.haml +++ b/app/views/admin/enterprise_groups/_form_web.html.haml @@ -4,7 +4,7 @@ .alpha.three.columns = f.label :website .omega.eight.columns - = f.text_field :website, { placeholder: t(:admin_entreprise_groups_web_website_placeholder)} + = f.text_field :website, { placeholder: t(:admin_enterprise_groups_web_website_placeholder)} .row .alpha.three.columns = f.label :facebook, 'Facebook' @@ -24,4 +24,4 @@ .alpha.three.columns = f.label :twitter .omega.eight.columns - = f.text_field :twitter, { placeholder: t(:admin_entreprise_groups_web_twitter) } + = f.text_field :twitter, { placeholder: t(:admin_enterprise_groups_web_twitter) } diff --git a/app/views/admin/enterprise_groups/index.html.haml b/app/views/admin/enterprise_groups/index.html.haml index 2540c649800..93d7ee81ba2 100644 --- a/app/views/admin/enterprise_groups/index.html.haml +++ b/app/views/admin/enterprise_groups/index.html.haml @@ -1,5 +1,5 @@ = content_for :page_title do - = t 'admin_entreprise_groups' + = t 'admin_enterprise_groups' - if admin_user? = content_for :page_actions do @@ -9,14 +9,14 @@ %thead %tr %th - = t 'admin_entreprise_groups_name' + = t 'admin_enterprise_groups_name' - if spree_current_user.admin? %th - = t 'admin_entreprise_groups_owner' + = t 'admin_enterprise_groups_owner' %th - = t 'admin_entreprise_groups_on_front_page' + = t 'admin_enterprise_groups_on_front_page' %th - = t 'admin_entreprise_groups_entreprise' + = t 'admin_enterprise_groups_enterprise' %th.actions %tbody diff --git a/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml b/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml index 05e6505868c..c1c6ccbc4bd 100644 --- a/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml +++ b/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml @@ -1,7 +1,7 @@ %tr{"ng-repeat" => "enterprise_relationship in EnterpriseRelationships.enterprise_relationships | keywords:query"} %td {{ enterprise_relationship.parent_name }} %td - = t 'admin_entreprise_relationships_permits' + = t 'admin_enterprise_relationships_permits' %td {{ enterprise_relationship.child_name }} %td %ul diff --git a/app/views/admin/enterprise_relationships/_form.html.haml b/app/views/admin/enterprise_relationships/_form.html.haml index 4d68d61e1b8..958ce530748 100644 --- a/app/views/admin/enterprise_relationships/_form.html.haml +++ b/app/views/admin/enterprise_relationships/_form.html.haml @@ -3,17 +3,17 @@ %select.select2.fullwidth{id: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"} %td - = t 'admin_entreprise_relationships_permits' + = t 'admin_enterprise_relationships_permits' %td %select.select2.fullwidth{id: "enterprise_relationship_child_id", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.all_enterprises"} %td %label %input{type: "checkbox", ng: {checked: "allPermissionsChecked()", click: "checkAllPermissions()"}} - = t 'admin_entreprise_relationships_everything' + = t 'admin_enterprise_relationships_everything' %div{"ng-repeat" => "permission in EnterpriseRelationships.all_permissions"} %label %input{type: "checkbox", "ng-model" => "permissions[permission]"} to {{ EnterpriseRelationships.permission_presentation(permission) }} %td.actions - %input{type: "button", value: t(:admin_entreprise_relationships_button_create), "ng-click" => "create()"} + %input{type: "button", value: t(:admin_enterprise_relationships_button_create), "ng-click" => "create()"} .errors {{ EnterpriseRelationships.create_errors }} diff --git a/app/views/admin/enterprise_relationships/_search_input.html.haml b/app/views/admin/enterprise_relationships/_search_input.html.haml index 350089dc4fd..a85f201b919 100644 --- a/app/views/admin/enterprise_relationships/_search_input.html.haml +++ b/app/views/admin/enterprise_relationships/_search_input.html.haml @@ -1,4 +1,4 @@ -%input.search{"ng-model" => "query", "placeholder" => t(:admin_entreprise_relationships_seach_placeholder)} +%input.search{"ng-model" => "query", "placeholder" => t(:admin_enterprise_relationships_seach_placeholder)} %label{ng: {repeat: "permission in EnterpriseRelationships.all_permissions"}} %input{type: "checkbox", ng: {click: "$parent.query = toggleKeyword($parent.query, permission)"}} diff --git a/app/views/admin/enterprise_relationships/index.html.haml b/app/views/admin/enterprise_relationships/index.html.haml index bdff595f09e..c9a12531f04 100644 --- a/app/views/admin/enterprise_relationships/index.html.haml +++ b/app/views/admin/enterprise_relationships/index.html.haml @@ -1,5 +1,5 @@ - content_for :page_title do - = t 'admin_entreprise_relationships' + = t 'admin_enterprise_relationships' = render 'admin/shared/enterprises_sub_menu' diff --git a/app/views/admin/enterprises/form/_images.html.haml b/app/views/admin/enterprises/form/_images.html.haml index 97d31dabcc6..0f48fce3225 100644 --- a/app/views/admin/enterprises/form/_images.html.haml +++ b/app/views/admin/enterprises/form/_images.html.haml @@ -1,12 +1,15 @@ -.row +.row.page-admin-enterprises-form__logo-field-group.image-field-group .alpha.three.columns = f.label :logo %br 100 x 100 pixels .omega.eight.columns - = image_tag @object.logo(:medium) if @object.logo.present? + %img{ class: 'image-field-group__preview-image', ng: { src: '{{ Enterprise.logo.medium }}', if: 'Enterprise.logo' } } = f.file_field :logo -.row + %a.button.red{ href: '', ng: {click: 'removeLogo()', if: 'Enterprise.logo'} } + = t('admin.enterprises.remove_logo.remove') + +.row.page-admin-enterprises-form__promo-image-field-group.image-field-group .alpha.three.columns = f.label :promo_image, 'ofn-with-tip' => t('.promo_image_placeholder') %br/ @@ -17,5 +20,7 @@ = t('.promo_image_note3') .omega.eight.columns - = image_tag @object.promo_image(:large) if @object.promo_image.present? + %img{ class: 'image-field-group__preview-image', ng: { src: '{{ Enterprise.promo_image.large }}', if: 'Enterprise.promo_image' } } = f.file_field :promo_image + %a.button.red{ href: '', ng: {click: 'removePromoImage()', if: 'Enterprise.promo_image'} } + = t('admin.enterprises.remove_promo_image.remove') diff --git a/app/views/admin/order_cycles/_filters.html.haml b/app/views/admin/order_cycles/_filters.html.haml index 4f0df004553..7cb735dd8a5 100644 --- a/app/views/admin/order_cycles/_filters.html.haml +++ b/app/views/admin/order_cycles/_filters.html.haml @@ -2,16 +2,16 @@ .filter.four.columns.alpha %label{ :for => 'query' }=t('admin.quick_search') %br - %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query' }, placeholder: "Search by Order Cycle name..." } + %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query' }, placeholder: t(".search_by_order_cycle_name") } .filter_select.four.columns %label{ :for => 'involving_filter' }=t('.involving') %br - %input.ofn-select2.fullwidth{ :id => 'involving_filter', type: 'number', blank: "{id: 0, name: 'Any Enterprise'}", data: 'enterprises', ng: { model: 'involvingFilter' } } + %input.ofn-select2.fullwidth{ id: 'involving_filter', type: 'number', blank: "{id: 0, name: '#{j t(".any_enterprise")}'}", data: 'enterprises', ng: { model: 'involvingFilter' } } - if subscriptions_enabled? .filter_select.four.columns %label{ :for => 'schedule_filter' }=t('admin.order_cycles.index.schedule') %br - %input.ofn-select2.fullwidth{ :id => 'schedule_filter', type: 'number', blank: "{id: 0, name: 'Any Schedule'}", data: 'schedules', ng: { model: 'scheduleFilter' } } + %input.ofn-select2.fullwidth{ id: 'schedule_filter', type: 'number', blank: "{id: 0, name: '#{j t(".any_schedule")}'}", data: 'schedules', ng: { model: 'scheduleFilter' } } .two.columns   - else .six.columns   diff --git a/app/views/admin/product_import/_entries_table.html.haml b/app/views/admin/product_import/_entries_table.html.haml index 8ad958b1c2f..6b0dcfdf6ed 100644 --- a/app/views/admin/product_import/_entries_table.html.haml +++ b/app/views/admin/product_import/_entries_table.html.haml @@ -7,7 +7,7 @@ %th= heading %tr{ng: {repeat: "(line_number, entry) in (entries | entriesFilterValid:'#{entries}')"}} %td - %i{ng: {class: "{'fa fa-warning warning': (count(entry.errors) > 0), 'fa fa-check-circle success': (count(entry.errors) == 0)}"}} + %i{ng: {class: "{'fa fa-warning error': (count(entry.errors) > 0), 'fa fa-check-circle success': (count(entry.errors) == 0)}"}} %td {{line_number}} %td{ng: {repeat: "(attribute, value) in entry.attributes", class: "{'invalid': attribute_invalid(attribute, line_number)}"}} diff --git a/app/views/admin/product_import/_import_review.html.haml b/app/views/admin/product_import/_import_review.html.haml index 9015c31bb8f..90d8de9577a 100644 --- a/app/views/admin/product_import/_import_review.html.haml +++ b/app/views/admin/product_import/_import_review.html.haml @@ -3,6 +3,12 @@ %div{ng: {controller: 'ImportFeedbackCtrl'}} + - if @importer.product_field_errors? + .alert-box.warning + = t('.not_updatable_tip') + %em= @non_updatable_fields.keys.join(', ') + "." + = t('.fields_ignored') + %div.panel-section{ng: {controller: 'DropdownPanelsCtrl'}} %div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"all"))}'}} %div.header-caret @@ -21,7 +27,7 @@ %div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"invalid"))}'}} %div.header-caret %i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"invalid")) == 0'}} - %div.header-icon.warning + %div.header-icon.error %i.fa.fa-warning %div.header-count %strong.invalid-count diff --git a/app/views/admin/shared/_angular_pagination.html.haml b/app/views/admin/shared/_angular_pagination.html.haml new file mode 100644 index 00000000000..53a28af885d --- /dev/null +++ b/app/views/admin/shared/_angular_pagination.html.haml @@ -0,0 +1,17 @@ +.pagination + %button{'ng-click' => 'changePage(1)', 'ng-class' => "{'disabled': pagination.page == 1}", 'ng-disabled' => "pagination.page == 1"} + = "«".html_safe + = t(:first) + %button{'ng-click' => 'changePage((pagination.page)-1)', 'ng-class' => "{'disabled': pagination.page == 1}", 'ng-disabled' => "pagination.page == 1"} + = t(:previous) + %span{'ng-show' => 'pagination.page > 3'} + = "…".html_safe + %button{'ng-repeat' => 'i in [].constructor(pagination.pages) track by $index', 'ng-show' =>'($index+1 > pagination.page-3 || (pagination.page > pagination.pages-2 && $index+1 > pagination.pages-5)) && ($index+1 < pagination.page+3 || (pagination.page < 3 && $index+1 < 6))', 'ng-class' => "{'active': pagination.page == $index+1}", 'ng-click' => 'changePage($index+1)', 'ng-disabled' => "pagination.page == $index+1"} + {{$index+1}} + %span{'ng-show' => 'pagination.page < pagination.pages-2'} + = "…".html_safe + %button{'ng-click' => 'changePage((pagination.page)+1)', 'ng-class' => "{'disabled': pagination.page == pagination.pages}", 'ng-disabled' => "pagination.page == pagination.pages"} + = t(:next) + %button{'ng-click' => 'changePage(pagination.pages)', 'ng-class' => "{'disabled': pagination.page == pagination.pages}", 'ng-disabled' => "pagination.page == pagination.pages"} + = t(:last) + = "»".html_safe diff --git a/app/views/admin/shared/_user_guide_link.html.haml b/app/views/admin/shared/_user_guide_link.html.haml index cd1b0dba864..26aafc8bc75 100644 --- a/app/views/admin/shared/_user_guide_link.html.haml +++ b/app/views/admin/shared/_user_guide_link.html.haml @@ -1 +1 @@ -= button_link_to t('.user_guide'), "http://www.openfoodnetwork.org/platform/user-guide/", icon: 'icon-external-link', target: '_blank' += button_link_to t('.user_guide'), ContentConfig.user_guide_link, icon: 'icon-external-link', target: '_blank' diff --git a/app/views/enterprise_mailer/welcome.html.haml b/app/views/enterprise_mailer/welcome.html.haml index ab804b268e6..1d68f481165 100644 --- a/app/views/enterprise_mailer/welcome.html.haml +++ b/app/views/enterprise_mailer/welcome.html.haml @@ -6,7 +6,7 @@ = "#{t(:email_registered)} #{ Spree::Config.site_name }!" %p - = t :email_userguide_html, link: link_to('Open Food Network User Guide', 'http://www.openfoodnetwork.org/platform/user-guide/') + = t :email_userguide_html, link: link_to('Open Food Network User Guide', ContentConfig.user_guide_link) %p = t :email_admin_html, link: link_to('Admin Panel', spree.admin_url) diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 2b796ad0cc9..79d78c67ea0 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -17,8 +17,9 @@ = yield :scripts %script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"} %script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry#{ ENV['GOOGLE_MAPS_API_KEY'] ? '&key=' + ENV['GOOGLE_MAPS_API_KEY'] : ''} "} - = split_stylesheet_link_tag "darkswarm/all" + = stylesheet_link_tag "darkswarm/all" = javascript_include_tag "darkswarm/all" + = javascript_include_tag "web/all" = render "layouts/i18n_script" = render "layouts/bugherd_script" diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml index 9c7126c999b..4142a8fbec1 100644 --- a/app/views/shared/_footer.html.haml +++ b/app/views/shared/_footer.html.haml @@ -140,8 +140,6 @@ = t '.footer_legal_text_html', {content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)' )} %p.text-small %div - - cookies_policy_link = link_to( t( '.footer_data_cookies_policy' ), '', 'cookies-policy-modal' => true, 'cookies-banner' => !CookiesConsent.new(cookies, request.host).exists? && Spree::Config.cookies_consent_banner_toggle) - - privacy_policy_link = link_to( t( '.footer_data_privacy_policy' ), Spree::Config.privacy_policy_url, :target => '_blank' ) - if Spree::Config.privacy_policy_url.present? = t '.footer_data_text_with_privacy_policy_html', {cookies_policy: cookies_policy_link.html_safe, privacy_policy: privacy_policy_link.html_safe } - else diff --git a/app/views/spree/admin/orders/_capture.html.haml b/app/views/spree/admin/orders/_capture.html.haml deleted file mode 100644 index b1267badbb1..00000000000 --- a/app/views/spree/admin/orders/_capture.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -- # Get the payment in 'checkout' state if any, and show capture button -- if order.payments.present? - - payment = order.payments.select{|p| p if p.state == 'checkout'}.first - - if !payment.nil? - - payment.actions.grep(/^capture$/).each do |action| - - # copied from backend/app/views/spree/admin/payments/_list.html.erb - = link_to_with_icon "icon-#{action}", t('admin.orders.index.capture'), fire_admin_order_payment_path(order, payment, :e => action), :method => :put, :no_text => true, :data => {:action => action} diff --git a/app/views/spree/admin/orders/_filters.html.haml b/app/views/spree/admin/orders/_filters.html.haml new file mode 100644 index 00000000000..5ebea2a4cd7 --- /dev/null +++ b/app/views/spree/admin/orders/_filters.html.haml @@ -0,0 +1,51 @@ +%div{"data-hook" => "admin_orders_index_search"} + = search_form_for [:admin, @search], html: { name: "orders_form", "ng-submit" => "fetchResults()"} do |f| + .field-block.alpha.four.columns + .date-range-filter.field + = label_tag nil, t(:date_range) + .date-range-fields + = f.text_field :created_at_gt, class: 'datepicker', datepicker: 'q.created_at_gt', 'ng-model' => 'q.created_at_gt', :value => params[:q][:created_at_gt], :placeholder => t(:start) + %span.range-divider + %i.icon-arrow-right + = f.text_field :created_at_lt, class: 'datepicker', datepicker: 'q.created_at_lt', 'ng-model' => 'q.created_at_lt', :value => params[:q][:created_at_lt], :placeholder => t(:stop) + .field + = label_tag nil, t(:status) + = f.select :state_eq, Spree::Order.state_machines[:state].states.collect {|s| [t("order_state.#{s.name}"), s.value]}, {:include_blank => true}, :class => 'select2', 'ng-model' => 'q.state_eq' + .four.columns + .field + = label_tag nil, t(:order_number) + = f.text_field :number_cont, 'ng-model' => 'q.number_cont' + .field + = label_tag nil, t(:email) + = f.email_field :email_cont, 'ng-model' => 'q.email_cont' + .four.columns + .field + = label_tag nil, t(:first_name_begins_with) + = f.text_field :bill_address_firstname_start, :size => 25, 'ng-model' => 'q.bill_address_firstname_start' + .field + = label_tag nil, t(:last_name_begins_with) + = f.text_field :bill_address_lastname_start, :size => 25, 'ng-model' => 'q.bill_address_lastname_start' + .omega.four.columns + .field.checkbox + %label + = f.check_box :completed_at_not_null, {:checked => @show_only_completed, 'ng-model' => 'q.completed_at_not_null'}, '1', '' + = t(:show_only_complete_orders) + .field.checkbox + %label + = f.check_box :inventory_units_shipment_id_null, {'ng-model' => 'q.inventory_units_shipment_id_null'}, '1', '0' + = t(:show_only_unfulfilled_orders) + .field-block.alpha.eight.columns + = label_tag nil, t(:distributors) + = select_tag("q[distributor_id_in]", + options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]), + {class: "select2 fullwidth", multiple: true, 'ng-model' => 'q.distributor_id_in'}) + .field-block.omega.eight.columns + = label_tag nil, t(:order_cycles) + = select_tag("q[order_cycle_id_in]", + options_for_select(OrderCycle.managed_by(spree_current_user).where('order_cycles.orders_close_at is not null').order('order_cycles.orders_close_at DESC').map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]), + {class: "select2 fullwidth", multiple: true, 'ng-model' => 'q.order_cycle_id_in'}) + .clearfix + .actions.filter-actions + %div + %a.button.icon-search{'ng-click' => 'fetchResults()'} + = t(:filter_results) diff --git a/app/views/spree/admin/orders/_per_page_controls.html.haml b/app/views/spree/admin/orders/_per_page_controls.html.haml new file mode 100644 index 00000000000..87e6c50dba7 --- /dev/null +++ b/app/views/spree/admin/orders/_per_page_controls.html.haml @@ -0,0 +1,6 @@ +.per-page{'ng-show' => '!RequestMonitor.loading && orders.length > 0'} + %input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'} + + %span.per-page-feedback + {{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }} + {{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + orders.length} }} diff --git a/app/views/spree/admin/orders/_sortable_header.html.haml b/app/views/spree/admin/orders/_sortable_header.html.haml new file mode 100644 index 00000000000..97d24165c04 --- /dev/null +++ b/app/views/spree/admin/orders/_sortable_header.html.haml @@ -0,0 +1,4 @@ +%a{'ng-click' => "sortOptions.toggle('#{column_name}')"} + = t(column_name.to_s, scope: 'activerecord.attributes.spree/order') + %span{'ng-show' => "sorting == '#{column_name} asc'"}= "▲".html_safe + %span{'ng-show' => "sorting == '#{column_name} desc'"}= "▼".html_safe \ No newline at end of file diff --git a/app/views/spree/admin/orders/edit.html.haml b/app/views/spree/admin/orders/edit.html.haml index f53cca42ef4..f897065224e 100644 --- a/app/views/spree/admin/orders/edit.html.haml +++ b/app/views/spree/admin/orders/edit.html.haml @@ -13,7 +13,7 @@ %div{"data-hook" => "admin_order_edit_header"} = render 'spree/shared/error_messages', target: @order -%div{"ng-app" => "admin.orders", "ng-controller" => "ordersCtrl", "ofn-distributor-id" => @order.distributor_id, "ofn-order-cycle-id" => @order.order_cycle_id} +%div{"ng-app" => "admin.orders", "ng-controller" => "orderCtrl", "ofn-distributor-id" => @order.distributor_id, "ofn-order-cycle-id" => @order.order_cycle_id} = render 'add_product' %div{"data-hook" => "admin_order_edit_form"} diff --git a/app/views/spree/admin/orders/index.html.haml b/app/views/spree/admin/orders/index.html.haml new file mode 100644 index 00000000000..1dc35e308aa --- /dev/null +++ b/app/views/spree/admin/orders/index.html.haml @@ -0,0 +1,89 @@ +- content_for :page_title do + = t('.listing_orders') + +- content_for :page_actions do + %li + = button_link_to t('.new_order'), new_admin_order_url, icon: 'icon-plus', id: 'admin_new_order' + += render partial: 'spree/admin/shared/order_sub_menu' + +- content_for :app_wrapper_attrs do + = "ng-app='admin.orders' ng-controller='ordersCtrl'" + +- content_for :table_filter_title do + = t(:search) + +- content_for :table_filter do + = render partial: 'filters' + +.row + = render partial: 'per_page_controls' + +%table#listing_orders.index.responsive{width: "100%", 'ng-init' => 'initialise()', 'ng-show' => "!RequestMonitor.loading && orders.length > 0" } + %colgroup + %col{style: "width: 10%"} + %thead + %tr + %th + = t(:products_distributor) + - if @show_only_completed + %th + %a{'ng-click' => "sortOptions.toggle('completed_at')"} + = t(:completed_at, scope: 'activerecord.attributes.spree/order') + %span{'ng-show' => "sorting == 'completed_at asc'"}= "▲".html_safe + %span{'ng-show' => "sorting == 'completed_at desc' || sorting === undefined"}= "▼".html_safe + - else + %th + = render partial: 'sortable_header', locals: {column_name: 'created_at'} + - ['number', 'state', 'payment_state', 'shipment_state', 'email', 'total'].each do |column_name| + %th + = render partial: 'sortable_header', locals: {column_name: column_name} + %th.actions + %tbody + %tr{ng: {repeat: 'order in orders track by $index', class: {even: "'even'", odd: "'odd'"}}, 'ng-class' => "'state-{{order.state}}'"} + %td.align-center + {{order.distributor_name}} + %td.align-center + = @show_only_completed ? '{{order.completed_at}}' : '{{order.created_at}}' + %td + %a{'ng-href' => '{{order.show_path}}'} + {{order.number}} + %div{'ng-if' => 'order.special_instructions'} + %br + %span.icon-warning-sign{'ofn-with-tip' => "{{order.special_instructions}}"} + = t('.note') + %td.align-center + %span.state{'ng-class' => 'order.state'} + {{'order_state.' + order.state | t}} + %td.align-center + %span.state{'ng-class' => 'order.payment_state', 'ng-if' => 'order.payment_state'} + %a{'ng-href' => '{{order.payments_path}}' } + {{'payment_states.' + order.payment_state | t}} + %td.align-center + %span.state{'ng-class' => 'order.shipment_state', 'ng-if' => 'order.shipment_state'} + %a{'ng-href' => '{{order.shipments_path}}' } + {{'shipment_states.' + order.shipment_state | t}} + %td + = mail_to "{{order.email}}" + %td.align-center + %span{'ng-bind-html' => 'order.display_total'} + %td.actions + %a.icon_link.with-tip.icon-edit.no-text{'ng-href' => '{{order.edit_path}}', 'data-action' => 'edit', 'ofn-with-tip' => t('.edit')} + %div{'ng-if' => 'order.ready_to_ship'} + %a.icon-road.icon_link.with-tip.no-text{'ng-href' => '{{order.ship_path}}', 'data-action' => 'ship', 'data-confirm' => t(:are_you_sure), 'data-method' => 'put', rel: 'nofollow', 'ofn-with-tip' => t('.ship')} + %div{'ng-if' => 'order.payment_capture_path'} + %a.icon-capture.icon_link.no-text{'ng-href' => '{{order.payment_capture_path}}', 'data-action' => 'capture', 'data-method' => 'put', rel: 'nofollow', 'ofn-with-tip' => t('.capture')} + +.orders-loading{'ng-show' => 'RequestMonitor.loading'} + .row + .small-12.columns.fullwidth.text-center + %img.spinner{ src: "/assets/spinning-circles.svg" } + .row + .small-12.columns.fullwidth.text-center + %span= t('.loading') + +%div{'ng-show' => "!RequestMonitor.loading && orders.length > 0" } + = render partial: 'admin/shared/angular_pagination' + +.no-objects-found{'ng-show' => "!RequestMonitor.loading && orders.length == 0"} + = t('.no_orders_found') diff --git a/app/views/spree/admin/orders/new.html.haml b/app/views/spree/admin/orders/new.html.haml index 24190f3190d..b653439230d 100644 --- a/app/views/spree/admin/orders/new.html.haml +++ b/app/views/spree/admin/orders/new.html.haml @@ -14,7 +14,7 @@ %div{"data-hook" => "admin_order_new_header"} = render 'spree/shared/error_messages', :target => @order -%div{"ng-app" => "admin.orders", "ng-controller" => "ordersCtrl"} +%div{"ng-app" => "admin.orders", "ng-controller" => "orderCtrl"} %div{"ng-show" => "distributionChosen()"} = render 'add_product' diff --git a/app/views/spree/admin/orders/set_distribution.html.haml b/app/views/spree/admin/orders/set_distribution.html.haml index 51c7b97e8db..89d036b93a1 100644 --- a/app/views/spree/admin/orders/set_distribution.html.haml +++ b/app/views/spree/admin/orders/set_distribution.html.haml @@ -14,7 +14,7 @@ %div{"data-hook" => "admin_order_new_header"} = render 'spree/shared/error_messages', :target => @order -%div{"ng-app" => "admin.orders", "ng-controller" => "ordersCtrl"} +%div{"ng-app" => "admin.orders", "ng-controller" => "orderCtrl"} = form_for @order, url: admin_order_url(@order), method: :put do |f| = render 'spree/admin/orders/_form/distribution_fields' -# This param passed to stop validation error in next page due to no line items in order yet: diff --git a/app/views/spree/admin/payments/_form.html.erb b/app/views/spree/admin/payments/_form.html.erb index e46b2febcda..e075d1288a7 100644 --- a/app/views/spree/admin/payments/_form.html.erb +++ b/app/views/spree/admin/payments/_form.html.erb @@ -1,5 +1,5 @@ <%= admin_inject_json "admin.payments", "currentOrderNumber", @order.number %> -<%= admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer %> +<%= admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer, current_order: @order %>
    diff --git a/app/views/spree/admin/reports/users_and_enterprises.html.haml b/app/views/spree/admin/reports/users_and_enterprises.html.haml index d7a521911f4..4c74a79e0de 100644 --- a/app/views/spree/admin/reports/users_and_enterprises.html.haml +++ b/app/views/spree/admin/reports/users_and_enterprises.html.haml @@ -1,6 +1,6 @@ = form_tag spree.users_and_enterprises_admin_reports_url do |f| .row - .alpha.two.columns= label_tag nil, t(:report_entreprises) + .alpha.two.columns= label_tag nil, t(:report_enterprises) .omega.fourteen.columns= select_tag(:enterprise_id_in, options_from_collection_for_select(Enterprise.all, :id, :name, params[:enterprise_id_in].andand.split(",")), {class: "select2 fullwidth", multiple: true}) .row diff --git a/app/views/spree/layouts/admin/_login_nav.html.haml b/app/views/spree/layouts/admin/_login_nav.html.haml index 088ac023771..816eab7bda8 100644 --- a/app/views/spree/layouts/admin/_login_nav.html.haml +++ b/app/views/spree/layouts/admin/_login_nav.html.haml @@ -11,4 +11,4 @@ = link_to t(:logout), spree.logout_path %li{"data-hook" => "store-frontend-link"} %i.icon-external-link - = link_to t(:store), spree.root_path, :target => '_blank' + = link_to t(".header.store"), spree.root_path, target: "_blank" diff --git a/app/views/spree/users/_authorised_shops.html.haml b/app/views/spree/users/_authorised_shops.html.haml index 692c953f12a..a1cdd58a91f 100644 --- a/app/views/spree/users/_authorised_shops.html.haml +++ b/app/views/spree/users/_authorised_shops.html.haml @@ -1,7 +1,7 @@ %table %tr - %th= t(:shop_title) - %th= t(:allow_charges?) + %th= t(".shop_name") + %th= t(".allow_charges?") %tr.customer{ id: "customer{{ customer.id }}", ng: { repeat: "customer in customers" } } %td.shop{ ng: { bind: 'shopsByID[customer.enterprise_id].name' } } %td.allow_charges diff --git a/config/application.rb b/config/application.rb index ea3539556b2..a872fdfe401 100644 --- a/config/application.rb +++ b/config/application.rb @@ -141,11 +141,11 @@ class Application < Rails::Application # Instead, they must be explicitly included below # http://stackoverflow.com/questions/8012434/what-is-the-purpose-of-config-assets-precompile config.assets.initialize_on_precompile = true - config.assets.precompile += ['store/all.css', 'store/all.js', 'store/shop_front.js', 'iehack.js'] + config.assets.precompile += ['iehack.js'] config.assets.precompile += ['admin/all.css', 'admin/*.js', 'admin/**/*.js'] - config.assets.precompile += ['darkswarm/all.css', 'darkswarm/all_split2.css', 'darkswarm/all.js'] + config.assets.precompile += ['web/all.css', 'web/all.js'] + config.assets.precompile += ['darkswarm/all.css', 'darkswarm/all.js'] config.assets.precompile += ['mail/all.css'] - config.assets.precompile += ['search/all.css', 'search/*.js'] config.assets.precompile += ['shared/*'] config.assets.precompile += ['qz/*'] diff --git a/config/application.yml.example b/config/application.yml.example index 980d3f9b26f..27710fbbc46 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -4,6 +4,9 @@ # Minimum 30 but usually 128 characters. To obtain run 'rake secret', or faster, 'openssl rand -hex 128' SECRET_TOKEN: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# Time zone must match the operating system time zone in order to pass all tests. +# This string is the key for the MAPPING hash constant in ActiveSupport::TimeZone. +# Documentation including the hash: https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html TIMEZONE: Melbourne # Default country for dropdowns etc. See for codes: http://en.wikipedia.org/wiki/ISO_3166-1 DEFAULT_COUNTRY_CODE: AU diff --git a/config/database.yml b/config/database.yml index 7f55b6c499b..d313dd74e4e 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,7 +10,7 @@ development: test: adapter: postgresql encoding: unicode - database: open_food_network_test<%= ENV['TEST_ENV_NUMBER'] %> + database: open_food_network_test pool: 5 host: localhost username: ofn diff --git a/config/locales/de_DE.yml b/config/locales/de_DE.yml index 4f7616a9398..c3bf6a6e687 100644 --- a/config/locales/de_DE.yml +++ b/config/locales/de_DE.yml @@ -19,7 +19,7 @@ de_DE: email: taken: "Es gibt bereits ein Konto für diese E-Mail-Adresse. Bitte versuchen Sie sich einzuloggen oder setzen Sie Ihr Passwort zurück." spree/order: - no_card: Es gibt keine autorisierten Kreditkarten, auf die zugegriffen werden kann. + no_card: Es sind keine belastbaren Karten verfügbar. order_cycle: attributes: orders_close_at: @@ -31,7 +31,7 @@ de_DE: attributes: subscription_line_items: at_least_one_product: "^ Bitte fügen Sie mindestens ein Produkt hinzu" - not_available: "^ %{name} ist im ausgewählten Plan nicht verfügbar" + not_available: "^ %{name} ist im ausgewählten Zeitplan nicht verfügbar" ends_at: after_begins_at: "Muss nach \"beginnt um\" sein " customer: @@ -40,8 +40,8 @@ de_DE: not_coordinated_by_shop: "wird nicht von %{shop} koordiniert" payment_method: not_available_to_shop: "ist nicht verfügbar für %{shop}" - invalid_type: "muss eine Bar- oder Stripe-Methode sein" - charges_not_allowed: "Kreditkartebehebungen von diesem Kunden sind nicht gestattet." + invalid_type: "muss Bar oder Stripe sein" + charges_not_allowed: "Der Kunde hat keine Belastungen erlaubt. " no_default_card: "^ Für diesen Kunden ist keine Standardkarte verfügbar" shipping_method: not_available_to_shop: "ist nicht verfügbar für %{shop}" @@ -55,6 +55,7 @@ de_DE: user_registrations: spree_user: signed_up_but_unconfirmed: "Eine Nachricht mit einem Bestätigungslink wurde an Ihre E-Mail-Adresse gesendet. Bitte öffnen Sie den Link, um Ihr Konto zu aktivieren." + unknown_error: "Beim Erstellen Ihres Kontos ist ein Fehler aufgetreten. Überprüfen Sie Ihre E-Mail-Adresse und versuchen Sie es erneut." failure: invalid: | Ungültige E-Mail-Adresse oder Passwort. @@ -64,6 +65,9 @@ de_DE: user_passwords: spree_user: updated_not_active: "Ihr Passwort wurde zurückgesetzt, aber ihre E-Mail muss noch bestätigt werden." + models: + order_cycle: + cloned_order_cycle_name: "Kopie von %{order_cycle}" enterprise_mailer: confirmation_instructions: subject: "Bitte bestätigen Sie die E-Mail-Adresse für %{enterprise}" @@ -71,9 +75,26 @@ de_DE: subject: "%{enterprise} ist jetzt auf %{sitename}" invite_manager: subject: "%{enterprise} hat Sie eingeladen, ein Manager zu sein" + order_mailer: + cancel_email: + dear_customer: "Sehr geehrter Kunde," + instructions: "Ihre Bestellung wurde storniert. Bitte bewahren Sie diese Stornierungsinformationen für Ihre Unterlagen auf." + order_summary_canceled: "Bestellübersicht [STORNIERT]" + subject: "Stornierung der Bestellung" + subtotal: "Zwischensumme: %{subtotal}" + total: "Bestellsumme: %{total}" producer_mailer: order_cycle: subject: "Bestellungszyklusreport für %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Sehr geehrter Kunde," + instructions: "Ihre Bestellung wurde versandt" + shipment_summary: "Übersicht" + subject: "Versandbenachrichtigung" + thanks: "Danke für Ihren Einkauf." + track_information: "Tracking-Informationen: %{tracking}" + track_link: "Tracking-Link: %{url}" subscription_mailer: placement_summary_email: subject: Eine Zusammenfassung der kürzlich aufgegebenen Abonnementbestellungen @@ -136,6 +157,7 @@ de_DE: free_trial: "kostenloser Versuch" plus_tax: "zuzüglich Umsatzsteuer" min_bill_turnover_desc: "sobald der Umsatz %{mbt_amount} übersteigt" + more: "Mehr" say_no: "Nein" say_yes: "Ja" then: dann @@ -217,13 +239,13 @@ de_DE: image: Bild product: Produkt quantity: Menge - schedule: Plan + schedule: Zeitplan shipping: Versand - shipping_method: Versandart + shipping_method: Lieferart shop: Laden sku: Artikelnummer status_state: Status - tags: Markierwort + tags: Stichwörter variant: Variante weight: Gewicht volume: Volumen @@ -249,18 +271,18 @@ de_DE: viewing: "Zeigt: %{current_view_name}" description: Beschreibung whats_this: Was ist das? - tag_has_rules: "Vorhandene Regeln für dieses Tag: %{num}" + tag_has_rules: "Vorhandene Regeln für dieses Stichwort: %{num}" has_one_rule: "hat eine Regel" has_n_rules: "hat %{num} Regel(n)" unsaved_confirm_leave: "Es gibt ungespeicherte Änderungen auf dieser Seite. Möchten Sie ohne Speichern fortfahren?" unsaved_changes: "Sie haben ungespeicherte Änderungen" accounts_and_billing_settings: method_settings: - default_accounts_payment_method: "Zahlungsmethode für Standardkonten" - default_accounts_shipping_method: "Standardmethode für den Kontoversand" + default_accounts_payment_method: "Zahlungsart für Standardkonten" + default_accounts_shipping_method: "Lieferart für Standardkonten" edit: accounts_and_billing: "Konten und Abrechung" - accounts_administration_distributor: "Kontenadministrator" + accounts_administration_distributor: "Kontenverwaltung-Verteiler" admin_settings: "Einstellungen" update_invoice: "Rechnungen aktualisieren" auto_update_invoices: "Rechnungen automatisch jede Nacht um 1:00 Uhr aktualisieren" @@ -325,11 +347,11 @@ de_DE: enable_receipt_printing?: Optionen zum Drucken von Belegen mit Thermodruckern in der Dropdown-Liste anzeigen? stripe_connect_settings: edit: - title: "Streifen verbinden" - settings: "die Einstellungen" + title: "Stripe Connect" + settings: "Einstellungen" stripe_connect_enabled: Läden erlauben, Zahlungen über Stripe Connect anzunehmen? no_api_key_msg: Für dieses Unternehmen existiert kein Stripe-Konto. - configuration_explanation_html: Detaillierte Anweisungen zur Konfiguration der Stripe Connect-Integration finden Sie unter konsultieren Sie diese Anleitung . + configuration_explanation_html: Detaillierte Anweisungen zur Konfiguration der Stripe Connect-Integration finden Sie unter der Anleitung . status: Status ok: OK instance_secret_key: Instanzgeheimschlüssel @@ -338,7 +360,7 @@ de_DE: charges_enabled: Gebühren aktiviert charges_enabled_warning: "Warnung: Gebühren sind für Ihr Konto nicht aktiviert" auth_fail_error: Der von Ihnen angegebene API-Schlüssel ist ungültig - empty_api_key_error_html: Es wurde kein Stripe-API-Schlüssel bereitgestellt. Um den API-Schlüssel festzulegen, folgen Sie bitte diesen Anweisungen < / a> + empty_api_key_error_html: Es wurde kein Stripe-API-Schlüssel bereitgestellt. Um den API-Schlüssel festzulegen, folgen Sie bitte diesen Anweisungen matomo_settings: edit: title: "Matomo-Einstellungen" @@ -377,10 +399,12 @@ de_DE: header: Header home_page: Homepage producer_signup_page: Hersteller-Anmeldeseite - hub_signup_page: Hub-Anmeldeseite + hub_signup_page: Hubregistrierseite group_signup_page: Gruppen-Anmeldeseite + main_links: Hauptmenü-Links footer_and_external_links: Fußzeile und externe Links your_content: Ihr Inhalt + user_guide: Benutzerhandbuch enterprise_fees: index: title: Unternehmensgebühren @@ -435,6 +459,7 @@ de_DE: conditional_blank: Kann nicht leer sein, wenn Einheitart leer ist no_product: hat keine Produkte in der Datenbank gefunden not_found: nicht in der Datenbank gefunden + not_updatable: kann über den Produktimport nicht auf bestehende Produkte aktualisiert werden blank: kann nicht leer sein products_no_permission: Sie sind nicht berechtigt, Produkte für dieses Unternehmen zu verwalten inventory_no_permission: Sie sind nicht berechtigt, Bestand für diesen Produzenten zu erstellen @@ -443,10 +468,10 @@ de_DE: index: select_file: Wählen Sie eine Tabelle zum Hochladen spreadsheet: Kalkulationstabelle - choose_import_type: Wählen Sie den Importtyp aus + choose_import_type: Wählen Sie die Importart import_into: Importart product_list: Produktliste - inventories: Bestände + inventories: Kataloge import: Importieren upload: Hochladen csv_templates: CSV Vorlage @@ -473,7 +498,7 @@ de_DE: no_permission: Sie sind nicht berechtigt, dieses Unternehmen zu verwalten not_found: Unternehmen konnte nicht in der Datenbank gefunden werden no_name: Kein Name - blank_supplier: Manche Produkte haben einen leeren Lieferantennamen + blank_supplier: Manche Produkte haben einen leeren Anbieternamen reset_absent?: Fehlende Produkte zurücksetzen reset_absent_tip: Den Bestand für alle nicht in der Datei vorhandenen Produkte auf Null setzen overwrite_all: Alles überschreiben @@ -493,6 +518,8 @@ de_DE: inventory_to_reset: Bestehende Katalogeinträge werden auf null Bestand zurückgesetzt line: Zeile item_line: Artikelzeile + import_review: + not_updatable_tip: "Die folgenden Felder können nicht über den Bulk-Import für bestehende Produkte aktualisiert werden:" save_results: final_results: Endgültige Ergebnisse importieren products_created: Produkte erstellt @@ -503,7 +530,7 @@ de_DE: inventory_reset: Bei Katalogeinträgen wurde der Bestand auf null zurückgesetzt all_saved: "Alle Artikel wurden erfolgreich gespeichert" some_saved: "Artikel wurden erfolgreich gespeichert" - save_errors: Fehler speichern + save_errors: Fehler beim Speichern import_again: Eine andere Datei hochladen view_products: Zur Produktseite gehen view_inventory: Zur Katalogseite gehen @@ -523,19 +550,16 @@ de_DE: new_products_alert_message: Es sind %{new_product_count} neue Produkte verfügbar, die in den Katalog aufgenommen werden können currently_empty: Ihr Katalog ist momentan leer. no_matching_products: Es wurden keine passenden Produkte im Katalog gefunden. - no_hidden_products: In diesem Katalog wurden keine Produkte verborgen - no_matching_hidden_products: Keine verborgenen Produkte entsprechen Ihren Suchkriterien + no_hidden_products: In diesem Katalog wurden keine Produkte ausgeblendet + no_matching_hidden_products: Keine ausgeblendeten Produkte entsprechen Ihren Suchkriterien no_new_products: Es sind keine neuen Produkte verfügbar, die zu diesem Katalog hinzugefügt werden können no_matching_new_products: Keine neuen Produkte entsprechen Ihren Suchkriterien inventory_powertip: Dies ist Ihr Produktkatalog. Um Produkte zu Ihrem Katalog hinzuzufügen, wählen Sie "Neue Produkte" aus dem Dropdown-Menü. - hidden_powertip: Diese Produkte wurden in Ihrem Katalog versteckt und können nicht in Ihrem Laden hinzugefügt werden. Sie können auf "Hinzufügen" klicken, um ein Produkt zu Ihrem Katalog hinzuzufügen. - new_powertip: Diese Produkte können Ihrem Katalog hinzugefügt werden. Klicken Sie auf "Hinzufügen", um ein Produkt zu Ihrem Katalog hinzuzufügen, oder auf "Ausblenden", um es aus der Ansicht auszublenden. Sie können Ihre Meinung später immer ändern! + hidden_powertip: Diese Produkte wurden in Ihrem Katalog ausgeblendet und können nicht zu Ihrem Laden hinzugefügt werden. Sie können auf "Hinzufügen" klicken, um ein Produkt zu Ihrem Katalog hinzuzufügen. + new_powertip: Diese Produkte können Ihrem Katalog hinzugefügt werden. Klicken Sie auf "Hinzufügen", um ein Produkt zu Ihrem Katalog hinzuzufügen, oder auf "Verbergen", um es aus der Ansicht auszublenden. Sie können Ihre Meinung später immer ändern! controls: back_to_my_inventory: Zurück zu meinem Katalog orders: - index: - capture: "Erfassung" - ship: "Liefern" invoice_email_sent: 'Rechnungs-E-Mail wurde gesendet' order_email_resent: 'Bestellungs-E-Mail wurde erneut gesendet' bulk_management: @@ -576,10 +600,10 @@ de_DE: desc_long: Über uns desc_long_placeholder: Schreiben Sie etwas über sich. Diese Information wird in Ihrem öffentlichen Profil angezeigt. business_details: - abn: ABN - abn_placeholder: z.B. 99 123 456 789 - acn: ACN - acn_placeholder: z.B. 123 456 789 + abn: USt-IdNr. + abn_placeholder: z.B. DE999999999 + acn: St.-Nr. + acn_placeholder: z.B. 93815/08152 display_invoice_logo: Logo in Rechnungen anzeigen invoice_text: Fügen Sie benutzerdefinierten Text am Ende der Rechnungen hinzu contact: @@ -597,45 +621,45 @@ de_DE: fee_type: Art der Gebühr manage_fees: Unternehmensgebühren verwalten no_fees_yet: Sie haben noch keine Unternehmensgebühren. - create_button: Erstelle jetzt einen + create_button: Erstelle jetzt eine images: logo: Logo promo_image_placeholder: 'Dieses Bild wird in "Über uns" angezeigt' promo_image_note1: 'BITTE BEACHTEN SIE:' - promo_image_note2: Jedes hier hochgeladene Promo-Bild wird auf 1200 x 260 beschnitten. + promo_image_note2: Jedes hier hochgeladene Werbebild wird auf 1200 x 260 beschnitten. promo_image_note3: Das Werbebild wird oben auf der Profilseite eines Unternehmens und in Pop-ups angezeigt. inventory_settings: - text1: Sie können sich dafür entscheiden, Lagerbestände und Preise über Ihre zu verwalten + text1: Sie verwalten optional Ihre Lagerbestände und Preise auch in Ihrem inventory: Katalog text2: > Wenn Sie das Katalog-Tool verwenden, können Sie auswählen, ob neue Produkte, - die von Ihren Lieferanten hinzugefügt wurden, zu Ihrem Katalog hinzugefügt + die von Ihren Anbieter hinzugefügt wurden, zu Ihrem Katalog hinzugefügt werden müssen, bevor sie verkauft werden können. Wenn Sie Ihren Katalog nicht zur Verwaltung Ihrer Produkte verwenden, sollten Sie die folgende - Option auswählen: + "empfohlene" Option auswählen: preferred_product_selection_from_inventory_only_yes: Neue Produkte können in meinem Laden angeboten werden (empfohlen) preferred_product_selection_from_inventory_only_no: Neue Produkte müssen zu meinem Katalog hinzugefügt werden, bevor sie in meinem Laden erscheinen können payment_methods: name: Name applies: Gilt? - manage: Zahlungsmethoden verwalten - not_method_yet: noch keine Zahlungsmethoden - create_button: Neue Zahlungsmethode - create_one_button: Erstelle jetzt einen + manage: Zahlungsarten verwalten + not_method_yet: Sie haben noch keine Zahlungsarten + create_button: Neue Zahlungsart + create_one_button: Erstelle jetzt eine primary_details: name: Name name_placeholder: z.B. Professor Plums biodynamische Trüffel groups: Gruppen groups_tip: Wählen Sie beliebige Gruppen oder Regionen aus, bei denen Sie Mitglied sind. Dies hilft Kunden, Ihr Unternehmen zu finden. groups_placeholder: Beginnen Sie mit der Eingabe, um nach verfügbaren Gruppen zu suchen ... - primary_producer: primärer Erzeuger - primary_producer_tip: Wählen Sie "Produzent", wenn Sie ein Hauptproduzent von Lebensmitteln sind. - producer: Produzent - any: Egal + primary_producer: Erzeuger? + primary_producer_tip: Wählen Sie "Erzeuger", wenn Sie ein Erzeuger von Lebensmitteln sind. + producer: Erzeuger + any: Alle none: Keine own: Eigene sells: vertreibt - sells_tip: "None - Unternehmen verkauft nicht direkt an Kunden.
    Own - Enterprise verkauft eigene Produkte an Kunden.
    Any - Enterprise kann eigene oder andere Unternehmensprodukte verkaufen.
    " + sells_tip: "Keine - Unternehmen verkauft nicht direkt an Kunden.
    Eigene - Unternehmen verkauft eigene Produkte an Kunden.
    Alle - Unternehmen verkauft eigene Produkte und Produkte anderer.
    " visible_in_search: öffentlich sichtbar in der Suche visible_in_search_tip: Bestimmt, ob dieses Unternehmen für Kunden beim Durchsuchen der Website sichtbar ist. visible: öffentlich sichtbar @@ -647,27 +671,27 @@ de_DE: shipping_methods: name: Name applies: Gilt? - manage: Versandmethoden verwalten - create_button: Erstellen Sie eine neue Versandmethode - create_one_button: Erstelle jetzt einen - no_method_yet: Sie haben noch keine Versandmethoden. + manage: Lieferarten verwalten + create_button: Neue Lieferart erstellen + create_one_button: Erstelle jetzt eine + no_method_yet: Sie haben noch keine Lieferarten. shop_preferences: shopfront_requires_login: "Öffentlich sichtbarer Laden?" shopfront_requires_login_tip: "Wählen Sie aus, ob sich Kunden anmelden müssen, um den Laden zu sehen oder ob sie für alle sichtbar sind." - shopfront_requires_login_false: "Öffentlich sichtbar" + shopfront_requires_login_false: "Öffentlich" shopfront_requires_login_true: "Nur für registrierte Nutzer sichtbar" - recommend_require_login: "Wenn Bestellungen nachträglich geändert werden dürfen, dann emfehlen wir Einkauf nur für eingeloggte Nutzer." + recommend_require_login: "Wenn Bestellungen nachträglich geändert werden dürfen, empfehlen wir Einkauf nur für eingeloggte Nutzer." allow_guest_orders: "Gast Einkauf" allow_guest_orders_tip: "Gasteinkauf erlauben oder nur für eingeloggte Nutzer" allow_guest_orders_false: "Einkauf nur für eingeloggte Nutzer" - allow_guest_orders_true: "Gast Einkauf erlauben" + allow_guest_orders_true: "Gasteinkauf erlauben" allow_order_changes: "Bestellungen nachträglich ändern" allow_order_changes_tip: "Kunden erlauben, ihre Bestellung zu ändern, solange der Bestellzyklus offen ist." - allow_order_changes_false: "Platzierte Bestellungen können nicht geändert / storniert werden" + allow_order_changes_false: "Bestellungen können nicht geändert / storniert werden" allow_order_changes_true: "Kunden können Bestellungen ändern oder stornieren, während der Bestellzyklus geöffnet ist" enable_subscriptions: "Abonnements" enable_subscriptions_tip: "Abo-Funktionalität aktivieren?" - enable_subscriptions_false: "Behindert" + enable_subscriptions_false: "deaktiviert" enable_subscriptions_true: "aktiviert" shopfront_message: Laden-Nachricht shopfront_message_placeholder: > @@ -686,41 +710,41 @@ de_DE: social: twitter_placeholder: z.B. @the_prof stripe_connect: - connect_with_stripe: "Verbinde dich mit Streifen" + connect_with_stripe: "Stripe integrieren" stripe_connect_intro: "Um Zahlungen mit Kreditkarte zu akzeptieren, müssen Sie Ihr Stripe-Konto mit dem Open Food Network verbinden. Verwenden Sie den Knopf rechts, um loszulegen." stripe_account_connected: "Stripe-Konto verbunden." disconnect: "Trennen Sie das Konto" confirm_modal: - title: Verbinde dich mit Streifen + title: Stripe integrieren part1: Stripe ist ein Zahlungsverarbeitungsdienst, der es Geschäften im OFN ermöglicht, Kreditkartenzahlungen von Kunden zu akzeptieren. - part2: Um diese Funktion zu verwenden, müssen Sie Ihr Stripe-Konto mit dem OFN verbinden. Wenn Sie unten auf "Ich stimme zu" klicken, wird die Stripe-Website an Sie weitergeleitet, wo Sie ein bestehendes Stripe-Konto verbinden oder ein neues erstellen können. + part2: Um diese Funktion zu verwenden, müssen Sie Ihr Stripe-Konto mit dem OFN verbinden. Klicken Sie "Zustimmen", um auf die Stripe-Website weitergeleitet zu werden, wo Sie ein bestehendes Stripe-Konto verbinden oder ein neues erstellen können. part3: Dadurch kann das Open Food Network Kreditkartenzahlungen von Kunden in Ihrem Namen akzeptieren. Bitte beachten Sie, dass Sie ein eigenes Stripe-Konto unterhalten müssen, die Gebühren für Stripe-Gebühren bezahlen und etwaige Rückbuchungen und Kundenservice selbst vornehmen müssen. - i_agree: Ich stimme zu + i_agree: Zustimmen cancel: Stornieren tag_rules: default_rules: by_default: Standardmäßig no_rules_yet: Es gelten noch keine Standardregeln add_new_button: '+ Fügen Sie eine neue Standardregel hinzu' - no_tags_yet: Für dieses Unternehmen sind noch keine Tags vorhanden - no_rules_yet: Für dieses Tag gelten noch keine Regeln - for_customers_tagged: 'Für Kunden mit dem Tag:' - add_new_rule: '+ Fügen Sie eine neue Regel hinzu' - add_new_tag: '+ Hinzufügen eines neuen Tags' + no_tags_yet: Für dieses Unternehmen sind noch keine Stichwörter vorhanden + no_rules_yet: Für dieses Stichwort gelten noch keine Regeln + for_customers_tagged: 'Für Kunden mit dem Stichwort:' + add_new_rule: '+ Neue Regel hinzufügen' + add_new_tag: '+ Neues Stichwort hinzufügen' users: email_confirmation_notice_html: "E-Mail-Bestätigung steht aus. Wir haben eine Bestätigungs-E-Mail an %{email} gesendet." resend: Erneut senden owner: 'Inhaber' contact: "Kontakt" contact_tip: "Der Manager, der Enterprise-E-Mails für Bestellungen und Benachrichtigungen erhält. Muss eine bestätigte E-Mail-Adresse haben." - owner_tip: Der primäre Benutzer, der für dieses Unternehmen verantwortlich ist. + owner_tip: Der Hauptnutzer, der für dieses Unternehmen verantwortlich ist. notifications: Benachrichtigungen notifications_tip: Benachrichtigungen über Bestellungen werden an diese E-Mail-Adresse gesendet. notifications_placeholder: z.B. lisa@maier.de notifications_note: 'Hinweis: Eine neue E-Mail-Adresse muss möglicherweise vor der Verwendung bestätigt werden' managers: Manager - managers_tip: Die anderen Benutzer mit der Berechtigung, dieses Unternehmen zu verwalten. - invite_manager: "Einladungsmanager" + managers_tip: Andere Benutzer mit der Berechtigung, dieses Unternehmen zu verwalten. + invite_manager: "Manager einladen" invite_manager_tip: "Laden Sie einen nicht registrierten Benutzer ein, sich anzumelden und ein Manager dieses Unternehmens zu werden." add_unregistered_user: "Fügen Sie einen nicht registrierten Benutzer hinzu" email_confirmed: "E-Mail bestätigt" @@ -729,9 +753,9 @@ de_DE: edit_profile: Einstellungen properties: Eigenschaften payment_methods: Zahlungsarten - payment_methods_tip: Dieses Unternehmen hat keine Zahlungsmethoden - shipping_methods: Liefermethoden - shipping_methods_tip: Dieses Unternehmen hat Versandmethoden + payment_methods_tip: Dieses Unternehmen hat keine Zahlungsarten + shipping_methods: Lieferarten + shipping_methods_tip: Dieses Unternehmen hat Lieferarten enterprise_fees: Unternehmensgebühren enterprise_fees_tip: Dieses Unternehmen hat keine Gebühren admin_index: @@ -740,47 +764,58 @@ de_DE: sells: vertreibt visible: Sichtbar? owner: Inhaber - producer: Produzent + producer: Erzeuger change_type_form: producer_profile: Erzeuger Profil connect_ofn: Verbindung über OFN herstellen - always_free: IMMER FREI - producer_description_text: Fügen Sie Ihre Produkte zu Open Food Network hinzu, damit Hubs Ihre Produkte in ihren Geschäften lagern können. + always_free: IMMER KOSTENLOS + producer_description_text: Fügen Sie Ihre Produkte zum Open Food Network hinzu, damit Hubs Ihre Produkte in ihren Läden anbieten können. producer_shop: Erzeugerladen sell_your_produce: Verkaufen Sie Ihre eigenen Produkte producer_shop_description_text: Verkaufen Sie Ihre Produkte direkt an Ihre Kunden durch Ihren eigenen Laden im Open Food Network. producer_shop_description_text2: Ein Erzeugerladen ist nur für Ihre eigenen Produkte bestimmt. Wenn Sie Produkte anderer verkaufen möchten, wählen Sie "Hub". - producer_hub: Produzent Hub - producer_hub_text: Verkaufe Produkte von dir selbst und anderen + producer_hub: Hub + producer_hub_text: Verkaufen Sie eigene Produkte und Produkte anderer producer_hub_description_text: Ihr Unternehmen ist das Rückgrat Ihres lokalen Lebensmittelsystems. Sie können sowohl Ihre eigenen Produkte, als auch Produkte von anderen Unternehmen über Ihren Laden im Open Food Network verkaufen. - profile: Profil nur - get_listing: Holen Sie sich einen Eintrag - profile_description_text: Menschen können Sie im Open Food Network finden und kontaktieren. Ihr Unternehmen wird auf der Karte angezeigt und kann in den Suchergebnissen durchsucht werden. + profile: Nur Profil + get_listing: Erstellen Sie einen Eintrag + profile_description_text: Sie können im Open Food Network gefunden und kontaktiert werden. Ihr Unternehmen wird auf der Karte und in Suchergebnissen angezeigt. hub_shop: Hub - hub_shop_text: Verkaufe Produkte von anderen + hub_shop_text: Verkaufen Sie Produkte anderer hub_shop_description_text: Ihr Unternehmen ist das Rückgrat Ihres lokalen Lebensmittelsystems. Sie aggregieren Produkte von anderen Unternehmen und verkaufen sie über Ihren Laden im Open Food Network. - choose_option: Bitte wähle eine der oben genannten Optionen. + choose_option: Bitte wählen Sie eine der oben aufgeführten Optionen. change_now: Jetzt ändern enterprise_user_index: - loading_enterprises: LADEN VON UNTERNEHMEN + loading_enterprises: UNTERNEHMEN WERDEN GELADEN no_enterprises_found: Keine Unternehmen gefunden. - search_placeholder: Suche mit Name + search_placeholder: Suche nach Name manage: Verwalten manage_link: Einstellungen + producer?: "Erzeuger?" + package: "Paket" + status: "Status" new_form: owner: Inhaber - owner_tip: Der primäre Benutzer, der für dieses Unternehmen verantwortlich ist. - i_am_producer: Ich bin ein Produzent + owner_tip: Der Hauptnutzer, der für dieses Unternehmen verantwortlich ist. + i_am_producer: Ich bin ein Erzeuger contact_name: Kontaktname edit: - editing: 'Die Einstellungen:' + editing: 'Einstellungen:' back_link: Zurück zur Unternehmensliste new: - title: Neue Unternehmung + title: Neues Unternehmen back_link: Zurück zur Unternehmensliste + remove_logo: + remove: "Bild entfernen" + removed_successfully: "Das Logo wurde erfolgreich entfernt" + immediate_removal_warning: "Das Logo wird sofort nach der Bestätigung entfernt." + remove_promo_image: + remove: "Bild entfernen" + removed_successfully: "Werbebild wurde erfolgreich entfernt" + immediate_removal_warning: "Das Werbebild wird sofort nach der Bestätigung entfernt." welcome: - welcome_title: Willkommen im Open Food Netzwerk! - welcome_text: Sie haben erfolgreich eine erstellt + welcome_title: Willkommen im Open Food Network! + welcome_text: 'Erfolgreich erstellt:' next_step: Nächster Schritt choose_starting_point: 'Wählen Sie Ihr Paket:' invite_manager: @@ -790,18 +825,18 @@ de_DE: edit: advanced_settings: Erweiterte Einstellungen update_and_close: Aktualisieren und schließen - choose_products_from: 'Wählen Sie Produkte aus:' + choose_products_from: 'Wählen Sie Produkte von:' exchange_form: pickup_time_tip: Wenn Bestellungen von diesem OC für den Kunden bereit sind - pickup_instructions_placeholder: "Abholungsanweisungen" - pickup_instructions_tip: Diese Anweisungen werden Kunden nach Abschluss einer Bestellung angezeigt + pickup_instructions_placeholder: "Abholungsinformationen" + pickup_instructions_tip: Diese Informationen werden Kunden nach Abschluss einer Bestellung angezeigt pickup_time_placeholder: "Bereit für (dh Datum / Uhrzeit)" - receival_instructions_placeholder: "Empfangsanweisungen" + receival_instructions_placeholder: "Lieferinformation" add_fee: 'Gebühr hinzufügen' selected: 'ausgewählt' add_exchange_form: add_supplier: 'Anbieter hinzufügen' - add_distributor: 'Händler hinzufügen' + add_distributor: 'Verteiler hinzufügen' advanced_settings: title: Erweiterte Einstellungen choose_product_tip: Sie können alle verfügbaren Produkte (sowohl eingehende als auch ausgehende) auf nur diejenigen im Katalog von %{inventory} beschränken. @@ -810,30 +845,34 @@ de_DE: save_reload: Speichern und neu laden coordinator_fees: add: Koordinatorgebühr hinzufügen + filters: + search_by_order_cycle_name: "Suche nach Bestellzyklus ..." + involving: "Betrifft" + any_enterprise: "Alle Unternehmen" + any_schedule: "Alle Zeitpläne" form: incoming: Eingehend - supplier: Zulieferer - receival_details: Empfangsdetails + supplier: Anbieter + receival_details: Lieferinformation fees: Gebühren - outgoing: Ausgehende + outgoing: Ausgehend distributor: Verteiler products: Produkte - tags: Stichworte - add_a_tag: Füge einen Tag hinzu - delivery_details: Abhol- / Lieferdetails + tags: Stichwörter + add_a_tag: Stichwort hinzufügen + delivery_details: Abhol- / Lieferinformationen debug_info: Debug-Informationen index: - involving: Einbeziehen schedule: Zeitplan - schedules: Termine + schedules: Zeitpläne adding_a_new_schedule: Hinzufügen eines neuen Zeitplans updating_a_schedule: Einen Zeitplan aktualisieren - new_schedule: Neue Terminplanung + new_schedule: Neuer Zeitplan create_schedule: Zeitplan erstellen update_schedule: Zeitplan aktualisieren delete_schedule: Zeitplan löschen created_schedule: Zeitplan erstellt - updated_schedule: Aktualisierter Zeitplan + updated_schedule: Zeitplan aktualisiert deleted_schedule: Zeitplan gelöscht schedule_name_placeholder: Zeitplanname name_required_error: Bitte geben Sie einen Namen für diesen Zeitplan ein @@ -844,28 +883,28 @@ de_DE: coordinator: Koordinator orders_close: Bestellungen schließen row: - suppliers: Lieferanten - distributors: Händler + suppliers: Anbieter + distributors: Verteiler variants: Varianten simple_form: - ready_for: Fertig am - ready_for_placeholder: Terminzeit - customer_instructions: Kunden Anweisungen + ready_for: Bereit am + ready_for_placeholder: Datum / Uhrzeit + customer_instructions: Kundeninformation customer_instructions_placeholder: Abhol- oder Lieferscheine products: Produkte fees: Gebühren destroy_errors: - orders_present: Dieser Bestellzyklus wurde von einem Kunden ausgewählt und kann nicht gelöscht werden. Um zu verhindern, dass Kunden darauf zugreifen, schließen Sie es stattdessen. - schedule_present: Dieser Bestellzyklus ist mit einem Zeitplan verknüpft und kann nicht gelöscht werden. Bitte heben Sie die Verknüpfung auf oder löschen Sie den Zeitplan zuerst. + orders_present: Dieser Bestellzyklus wurde von einem Kunden ausgewählt und kann nicht gelöscht werden. Um weitere Verwendung zu verhindern, können Sie ihn stattdessen schließen. + schedule_present: Dieser Bestellzyklus ist mit einem Zeitplan verknüpft und kann nicht gelöscht werden. Bitte heben Sie zuerst die Verknüpfung auf oder löschen Sie den Zeitplan. bulk_update: no_data: Hm, etwas ist schief gelaufen. Keine Bestellzyklusdaten gefunden. date_warning: - msg: Der Bestellvorgang verweist auf (...) noch offene, aber bereits gezeichnete Bestellungen. Dieses Datum zu ändern wird keine der bereits getätigten Bestellungen berühren, sollte wenn möglich aber vermieden werden. Sind Sie sicher, daß Sie fortfahren wollen? - cancel: Abrechen + msg: Dieser Bestellzyklus enthält %{n} offene Abonementbestellungen. Das Ändern des Datums wird bereits erteilte Bestellungen nicht beeinträchtigen, sollte wenn möglich aber vermieden werden. Sind Sie sicher, daß Sie fortfahren wollen? + cancel: Abbrechen proceed: Fortfahren producer_properties: index: - title: Herstellereigenschaften + title: Erzeugereigenschaften proxy_orders: cancel: could_not_cancel_the_order: Die Bestellung konnte nicht storniert werden @@ -876,68 +915,68 @@ de_DE: user_guide: Benutzerhandbuch overview: enterprises_header: - ofn_with_tip: Unternehmen sind Hersteller und / oder Hubs und sind die grundlegende Organisationseinheit innerhalb des Open Food Network. + ofn_with_tip: Unternehmen sind Erzeuger und / oder Hubs und sind die grundlegende Organisationseinheit innerhalb des Open Food Network. enterprises_hubs_tabs: - has_no_payment_methods: "%{enterprise} hat keine Zahlungsmethoden" + has_no_payment_methods: "%{enterprise} hat keine Zahlungsarten" has_no_shipping_methods: "%{enterprise} hat keine Versandarten" has_no_enterprise_fees: "%{enterprise} hat keine Unternehmensgebühren" enterprise_issues: - create_new: Erstelle neu - resend_email: E-Mail zurücksenden - has_no_payment_methods: "%{enterprise} hat derzeit keine Zahlungsmethoden" + create_new: Erstellen + resend_email: E-Mail erneut senden + has_no_payment_methods: "%{enterprise}hat derzeit keine Zahlungsarten" has_no_shipping_methods: "%{enterprise} hat derzeit keine Versandarten" email_confirmation: "E-Mail-Bestätigung steht aus. Wir haben eine Bestätigungs-E-Mail an %{email} gesendet." not_visible: "%{enterprise} ist nicht sichtbar und kann daher nicht auf der Karte oder in Suchen gefunden werden" reports: - hidden: VERSTECKT + hidden: Ausgeblendet unitsize: EINHEITSGRÖSSE - total: GESAMT + total: SUMME total_items: GESAMTANZAHL - supplier_totals: Bestellzyklus Lieferantensummen - supplier_totals_by_distributor: Order Cycle Supplier Gesamtsumme von Distributor - totals_by_supplier: Order Cycle Distributor Summen nach Lieferant - customer_totals: Bestellzyklus Kundensummen + supplier_totals: Anbieter-Gesamtsummen + supplier_totals_by_distributor: Anbieter-Gesamtsummen nach Verteiler + totals_by_supplier: Verteiler-Gesamtsummen nach Anbieter + customer_totals: Kunden-Gesamtsummen all_products: Alle Produkte inventory: Aktueller Bestand - lettuce_share: SalatShare + lettuce_share: LettuceShare mailing_list: Mailingliste addresses: Adressen - payment_methods: Zahlungsmethoden Bericht - delivery: Sendebericht + payment_methods: Zahlungsarten Bericht + delivery: Lieferbericht tax_types: Steuerarten tax_rates: Steuersätze - pack_by_customer: Nach Kunde verpacken - pack_by_supplier: Nach Lieferant verpacken + pack_by_customer: Packliste nach Kunde + pack_by_supplier: Packliste nach Anbieter orders_and_distributors: - name: Bestellungen und Händler - description: Bestellungen mit Händlerdetails + name: Bestellungen und Verteiler + description: Bestellungen mit Verteilerdetails bulk_coop: name: Massen-Co-Op - description: Berichte für Bulk-Co-Op-Bestellungen + description: Berichte für Massen-Co-Op-Bestellungen payments: name: Zahlungsberichte description: Berichte für Zahlungen orders_and_fulfillment: - name: Bestellungen & Erfüllungsberichte + name: Bestellungs- & Erfüllungsberichte customers: name: Kunden products_and_inventory: name: Produkte und Katalog sales_total: - name: Verkäufe insgesamt + name: Gesamtumsatz description: Gesamtumsatz für alle Bestellungen users_and_enterprises: name: Benutzer und Unternehmen description: Unternehmenseigentum & Status order_cycle_management: - name: Auftragszyklus-Management + name: Bestellzyklus-Verwaltung sales_tax: - name: Mehrwertsteuer + name: Steuern xero_invoices: name: Xero Rechnungen description: Rechnungen für den Import in Xero packing: - name: Verpackungsberichte + name: Packberichte subscriptions: subscriptions: Abonnements new: Neues Abonnement @@ -945,25 +984,25 @@ de_DE: index: please_select_a_shop: Bitte wählen Sie einen Laden edit_subscription: Abonnement bearbeiten - pause_subscription: Abo pausieren - unpause_subscription: Abonnement aufheben + pause_subscription: Abonement pausieren + unpause_subscription: Abonnement fortsetzen cancel_subscription: Abonnement beenden setup_explanation: - just_a_few_more_steps: 'Noch ein paar Schritte bevor Sie beginnen können:' + just_a_few_more_steps: 'Nur noch ein paar Schritte bevor Sie beginnen können:' enable_subscriptions: "Aktivieren Sie Abonnements für mindestens einen Ihrer Läden" enable_subscriptions_step_1_html: 1. Gehen Sie zur Seite %{enterprises_link}, suchen Sie Ihren Laden und klicken Sie auf "Verwalten" enable_subscriptions_step_2: 2. Aktivieren Sie unter "Ladeneinstellungen" die Option Abonnements - set_up_shipping_and_payment_methods_html: Richten Sie die Methoden %{shipping_link} und %{payment_link} ein - set_up_shipping_and_payment_methods_note_html: Beachten Sie, dass nur Bar- und Stripe-Zahlungsmethoden für Abonnements verwendet werden dürfen - ensure_at_least_one_customer_html: Stellen Sie sicher, dass mindestens eine %{customer_link} vorhanden ist + set_up_shipping_and_payment_methods_html: Erstellen Sie %{shipping_link}und %{payment_link} + set_up_shipping_and_payment_methods_note_html: Beachten Sie, dass nur Bar- und Stripe-Zahlungsarten für Abonnements verwendet werden dürfen + ensure_at_least_one_customer_html: Stellen Sie sicher, dass mindestens ein %{customer_link} vorhanden ist create_at_least_one_schedule: Erstellen Sie mindestens einen Zeitplan create_at_least_one_schedule_step_1_html: 1. Gehen Sie auf die Seite %{order_cycles_link} create_at_least_one_schedule_step_2: 2. Erstellen Sie einen Bestellzyklus, falls Sie dies noch nicht getan haben create_at_least_one_schedule_step_3: 3. Klicken Sie auf "+ Neuer Zeitplan" und füllen Sie das Formular aus once_you_are_done_you_can_html: Sobald Sie fertig sind, können Sie %{reload_this_page_link} - reload_this_page: lade diese Seite neu + reload_this_page: diese Seite neu laden steps: - details: 1. Grundlegende Details + details: 1. Grundlegendes address: 2. Adresse products: 3. Fügen Sie Produkte hinzu review: 4. Überprüfen und speichern @@ -974,64 +1013,96 @@ de_DE: details: details: Einzelheiten invalid_error: Hoppla! Bitte füllen Sie alle erforderlichen Felder aus ... - allowed_payment_method_types_tip: Zurzeit können nur Bar- und Stripe-Zahlungsmethoden verwendet werden + allowed_payment_method_types_tip: Zurzeit können nur Bar- und Stripe-Zahlungsarten verwendet werden credit_card: Kreditkarte - charges_not_allowed: Aufträge dieses Kunden/dieser Kundin sind nicht gestattet. - no_default_card: Der Kunde/die Kundin hat keine Karte, die aufgeladen werden kann. - card_ok: Der Kunde/die Kundin hat eine Karte, die aufgeladen werden kann. + charges_not_allowed: Der Kunde hat keine Belastungen erlaubt. + no_default_card: Für den Kunden ist keine belastbare Karte vorhanden. + card_ok: Eine belastbare Karte ist für den Kunden vorhanden. loading_flash: - loading: LADEN VON ABONNEMENTS + loading: ABONNEMENTS WERDEN GELADEN review: details: Einzelheiten address: Adresse products: Produkte - product_already_in_order: Dieses Produkt wurde bereits zur Bestellung hinzugefügt. Bitte bearbeiten Sie die Menge direkt. + product_already_in_order: Dieses Produkt wurde bereits zur Bestellung hinzugefügt. Bitte änderns Sie stattdessen die Menge. orders: number: Nummer confirm_edit: Sind Sie sicher, dass Sie diese Bestellung bearbeiten möchten? Dies kann die automatische Synchronisierung von Änderungen am Abonnement in Zukunft erschweren. - confirm_cancel_msg: Sind Sie sicher, dass Sie dieses Abonnement kündigen möchten? Diese Aktion kann nicht rückgängig gemacht werden. + confirm_cancel_msg: Sind Sie sicher, dass Sie dieses Abonnement kündigen möchten? Dieser Vorgang kann nicht rückgängig gemacht werden. cancel_failure_msg: 'Entschuldigung, Stornierung fehlgeschlagen!' confirm_pause_msg: Möchten Sie dieses Abonnement wirklich pausieren? - pause_failure_msg: 'Entschuldigung, die Pause ist fehlgeschlagen!' - confirm_unpause_msg: Möchten Sie dieses Abonnement wirklich wieder aktivieren? - unpause_failure_msg: 'Entschuldigung, fehlgeschlagen!' - confirm_cancel_open_orders_msg: "Einige Bestellungen für dieses Abonnement sind derzeit geöffnet. Der Kunde wurde bereits darüber informiert, dass die Bestellung aufgegeben wird. Möchten Sie diese Bestellung (en) stornieren oder behalten?" - resume_canceled_orders_msg: "Einige Bestellungen für dieses Abonnement können jetzt fortgesetzt werden. Sie können sie aus dem Dropdown-Menü für Bestellungen wiederherstellen." - yes_cancel_them: Annulliere sie - no_keep_them: Behalte sie + pause_failure_msg: 'Entschuldigung, Pausieren fehlgeschlagen!' + confirm_unpause_msg: Möchten Sie dieses Abonnement wirklich fortsetzen? + unpause_failure_msg: 'Entschuldigung, Fortsetzung fehlgeschlagen!' + confirm_cancel_open_orders_msg: "Einige Bestellungen für dieses Abonnement sind derzeit offen. Der Kunde wurde bereits informiert, dass die Bestellung aufgegeben wird. Möchten Sie diese Bestellung(en) stornieren oder erhalten?" + resume_canceled_orders_msg: "Manche Bestellungen für dieses Abonnement können jetzt fortgesetzt werden. Sie können sie aus dem Bestellungen-Dropdown-Menü wiederherstellen." + yes_cancel_them: Stornieren + no_keep_them: Behalten yes_i_am_sure: Ja, ich bin mir sicher - order_update_issues_msg: Einige Bestellungen konnten nicht automatisch aktualisiert werden, dies liegt wahrscheinlich daran, dass sie manuell bearbeitet wurden. Bitte überprüfen Sie die unten aufgeführten Punkte und nehmen Sie gegebenenfalls Anpassungen an einzelnen Bestellungen vor. + order_update_issues_msg: Einige Bestellungen konnten nicht automatisch aktualisiert werden. Dies liegt wahrscheinlich daran, dass sie manuell bearbeitet wurden. Bitte überprüfen Sie die unten aufgeführten Punkte und nehmen Sie gegebenenfalls Anpassungen an einzelnen Bestellungen vor. no_results: no_subscriptions: Noch keine Abonnements - why_dont_you_add_one: Warum fügst du keinen hinzu? :) + why_dont_you_add_one: Vielleicht welche hinzufügen? :) no_matching_subscriptions: Keine passenden Abonnements gefunden schedules: destroy: - associated_subscriptions_error: Dieser Zeitplan kann nicht gelöscht werden, da ihm Subskriptionen zugeordnet sind + associated_subscriptions_error: Dieser Zeitplan kann nicht gelöscht werden, da ihm Abonnements zugeordnet sind. controllers: enterprises: stripe_connect_cancelled: "Die Verbindung zu Stripe wurde abgebrochen" stripe_connect_success: "Stripe-Konto erfolgreich verbunden" stripe_connect_fail: Die Verbindung Ihres Stripe-Kontos ist fehlgeschlagen stripe_connect_settings: - resource: Streifenverbindungskonfiguration + resource: Konfiguration für Stripe Connect + api: + enterprise_logo: + destroy_attachment_does_not_exist: "Logo existiert nicht" + enterprise_promo_image: + destroy_attachment_does_not_exist: "Webebild existiert nicht" checkout: already_ordered: - cart: "Wagen" - message_html: "Sie haben bereits eine Bestellung für diesen Bestellzyklus. Überprüfen Sie die %{cart}, um die Artikel zu sehen, die Sie zuvor bestellt haben. Sie können Artikel auch stornieren, solange der Bestellzyklus geöffnet ist." + cart: "Warenkorb" + message_html: "Sie haben bereits eine Bestellung für diesen Bestellzyklus. Überprüfen Sie den %{cart}, um die Artikel zu sehen, die Sie zuvor bestellt haben. Sie können Artikel auch stornieren, solange der Bestellzyklus geöffnet ist." shops: hubs: show_closed_shops: "Geschlossene Läden anzeigen" hide_closed_shops: "Geschlossene Läden ausblenden." - show_on_map: "Zeige alle auf der Karte" + show_on_map: "Alle auf der Karte zeigen" shared: menu: cart: - checkout: "Checke jetzt aus" - already_ordered_products: "Bereits in dieser Reihenfolge bestellt" + checkout: "Zur Kasse" + already_ordered_products: "Bereits in diesem Bestellzyklus bestellt" register_call: selling_on_ofn: "Interesse am Open Food Network?" register: "Hier anmelden" + footer: + footer_global_headline: "OFN Global" + footer_global_home: "Startseite" + footer_global_news: "Aktuelles" + footer_global_about: "Über Uns" + footer_global_contact: "Kontakt" + footer_sites_headline: "OFN Webseiten" + footer_sites_developer: "Entwickler" + footer_sites_community: "Community" + footer_sites_userguide: "Benutzerhandbuch" + footer_secure: "Sicher und vertrauenswürdig." + footer_secure_text: "Open Food Network verwendet überall SSL-Verschlüsselung (2048 Bit RSA), um Ihre Einkaufs- und Zahlungsinformationen geheim zu halten. Unsere Server speichern Ihre Kreditkartendetails nicht und Zahlungen werden von PCI-konformen Dienstleistern verarbeitet." + footer_contact_headline: "In Verbindung bleiben" + footer_contact_email: "Schreiben Sie uns eine E-Mail" + footer_nav_headline: "Navigieren" + footer_join_headline: "Mitmachen" + footer_join_body: "Erstellen Sie einen Eintrag, einen Laden oder ein Gruppenverzeichnis im Open Food Network." + footer_join_cta: "Erzähl mir mehr!" + footer_legal_call: "Lesen Sie unsere" + footer_legal_tos: "AGB" + footer_legal_visit: "Finden Sie uns auf" + footer_legal_text_html: "Open Food Network ist eine kostenlose Open-Source Software-Plattform. Unser Inhalt ist mit %{content_license} und unser Code mit %{code_license} lizenziert." + footer_data_text_with_privacy_policy_html: "Wir passen auf Ihre Daten auf. Siehe unsere %{privacy_policy} und %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "Wir passen auf Ihre Daten auf. Siehe unsere %{cookies_policy}" + footer_data_privacy_policy: "Datenschutz-Bestimmungen" + footer_data_cookies_policy: "Cookie-Richtlinien" + footer_skylight_dashboard_html: Leistungsdaten sind in der %{dashboard} verfügbar. shop: messages: login: "Anmeldung" @@ -1039,50 +1110,64 @@ de_DE: contact: "Kontakt" require_customer_login: "Dieser Laden ist nur für Kunden." require_login_html: "Bitte %{login}, wenn Sie bereits ein Konto haben. Andernfalls, %{register}, um Kunde zu werden." - require_customer_html: "Bitte %{contact} %{enterprise}, um Kunde zu werden." + require_customer_html: "Bitte kontaktieren Sie %{enterprise}, um Kunde zu werden." card_could_not_be_updated: Die Karte konnte nicht aktualisiert werden card_could_not_be_saved: Karte konnte nicht gespeichert werden spree_gateway_error_flash_for_checkout: "Bei den Zahlungsinformationen ist ein Problem aufgetreten: %{error}" invoice_billing_address: "Rechnungsadresse:" - invoice_column_tax: "GST" + invoice_column_tax: "Umsatzsteuer" invoice_column_price: "Preis" invoice_column_item: "Artikel" invoice_column_qty: "Menge" invoice_column_unit_price_with_taxes: "Stückpreis (inkl. Steuern)" invoice_column_unit_price_without_taxes: "Stückpreis (zzgl. Steuern)" invoice_column_price_with_taxes: "Gesamtpreis (inkl. Steuern)" - invoice_column_price_without_taxes: "Gesamtpreis (ohne Steuern)" + invoice_column_price_without_taxes: "Gesamtpreis (zzgl. Steuern)" invoice_column_tax_rate: "Steuersatz" - invoice_tax_total: "Gesamtkosten:" + invoice_tax_total: "Umsatzsteuersumme:" tax_invoice: "Steuerrechnung" - tax_total: "Gesamtsteuer (%{rate}):" + tax_total: "Steuersumme (%{rate}):" total_excl_tax: "Summe (ohne Steuern):" total_incl_tax: "Gesamt (Inkl. Steuern):" - abn: "ABN:" - acn: "ACN:" + abn: "USt-IdNr." + acn: "St.-Nr." invoice_issued_on: "Rechnung ausgestellt am:" order_number: "Rechnungsnummer:" date_of_transaction: "Datum der Transaktion:" ticket_column_qty: "Menge" ticket_column_item: "Artikel" - ticket_column_unit_price: "Einzelpreis" + ticket_column_unit_price: "Stückpreis" ticket_column_total_price: "Gesamtpreis" + menu_1_title: "Läden" + menu_1_url: "/ läden" + menu_2_title: "Karte" + menu_2_url: "/karte" + menu_3_title: "Erzeuger" + menu_3_url: "/ erzeuger" + menu_4_title: "Gruppen" + menu_4_url: "/ gruppen" + menu_5_title: "Über Uns" + menu_5_url: "http://www.openfoodnetwork.org/" + menu_6_title: "Verbinden" + menu_6_url: "https://openfoodnetwork.org/au/connect/" + menu_7_title: "Mehr Erfahren" + menu_7_url: "https://openfoodnetwork.org/au/learn/" logo: "Logo (640x130)" - logo_mobile: "Mobiles Logo (75x26)" - logo_mobile_svg: "Mobiles Logo (SVG)" + logo_mobile: "Handylogo (75x26)" + logo_mobile_svg: "Handylogo (SVG)" home_hero: "Heldenbild" home_show_stats: "Zeige Statistiken" footer_logo: "Logo (220x76)" footer_facebook_url: "Facebook URL" - footer_twitter_url: "Twitter-URL" + footer_twitter_url: "Twitter URL" footer_instagram_url: "Instagram URL" - footer_linkedin_url: "LinkedIn-URL" - footer_googleplus_url: "Google Plus-URL" - footer_pinterest_url: "Pinterest-URL" + footer_linkedin_url: "LinkedIn URL" + footer_googleplus_url: "Google Plus URL" + footer_pinterest_url: "Pinterest URL" footer_email: "E-Mail:" footer_links_md: "Links" footer_about_url: "Über URL" - footer_tos_url: "Nutzungsbedingungen URL" + user_guide_link: "Benutzerhandbuch-Link" name: Name first_name: Vorname last_name: Nachname @@ -1093,26 +1178,26 @@ de_DE: address_placeholder: z.B. Gartenstrasse 123 address2: Adresse (Fortsetzung) city: Ort - city_placeholder: z.B. Northcote + city_placeholder: z.B. Nordwestheim postcode: Postleitzahl - postcode_placeholder: z.B. 3070 + postcode_placeholder: z.B. 30701 state: Bundesland country: Land unauthorized: Nicht autorisiert - terms_of_service: "Geschäftsbedingungen" + terms_of_service: "AGB" on_demand: Auf Anfrage none: Keine not_allowed: Nicht erlaubt - no_shipping: keine Versandarten - no_payment: keine Zahlungsmethoden - no_shipping_or_payment: keine Versand- oder Zahlungsmethoden + no_shipping: keine Lieferarten + no_payment: keine Zahlungsarten + no_shipping_or_payment: keine Versand- oder Zahlungsarten unconfirmed: unbestätigt days: Tage label_shop: "Laden" label_shops: "Läden" label_map: "Karte" - label_producer: "Hersteller" - label_producers: "Produzenten" + label_producer: "Erzeuger" + label_producers: "Erzeuger" label_groups: "Gruppen" label_about: "Über" label_connect: "Verbinde" @@ -1120,21 +1205,21 @@ de_DE: label_blog: "Blog" label_support: "Unterstützung" label_shopping: "Einkauf" - label_login: "Login" - label_logout: "Logout" + label_login: "Anmeldung" + label_logout: "Ausloggen" label_signup: "Registrieren" - label_administration: "Administration" - label_admin: "Administrator" + label_administration: "Verwaltung" + label_admin: "Verwalter" label_account: "Konto" label_more: "Mehr anzeigen" label_less: "Weniger anzeigen" - label_notices: "Notizen" + label_notices: "Bemerkungen" cart_items: "Artikel" cart_headline: "Ihr Warenkorb" total: "Total" - cart_updating: "Einkaufswagen aktualisieren..." - cart_empty: "Leerer Einkaufswagen" - cart_edit: "Einkaufswagen bearbeiten" + cart_updating: "Warenkorb aktualisieren..." + cart_empty: "Warenkorb leer" + cart_edit: "Warenkorb bearbeiten" card_number: Kartennummer card_securitycode: "Sicherheitscode" card_expiry_date: Ablaufdatum @@ -1142,44 +1227,63 @@ de_DE: card_expiry_abbreviation: "Exp" new_credit_card: "Neue Kreditkarte" my_credit_cards: Meine Kreditkarten - add_new_credit_card: Fügen Sie eine neue Kreditkarte hinzu + add_new_credit_card: Neue Karte hinzufügen saved_cards: Gespeicherte Karten - add_a_card: Füge eine Karte hinzu + add_a_card: Karte hinzufügen add_card: Karte hinzufügen - you_have_no_saved_cards: Du hast noch keine Karten gespeichert + you_have_no_saved_cards: Sie haben noch keine Karten gespeichert saving_credit_card: Kreditkarte speichern ... card_has_been_removed: "Ihre Karte wurde entfernt (Nummer: %{number})" - card_could_not_be_removed: Entschuldigung, die Karte konnte nicht entfernt werden + card_could_not_be_removed: Die Karte konnte nicht entfernt werden ie_warning_headline: "Ihr Browser ist veraltet :-(" ie_warning_text: "Für das beste Open-Food-Network-Erlebnis empfehlen wir dringend, Ihren Browser zu aktualisieren:" ie_warning_chrome: Chrome herunterladen - ie_warning_firefox: Firefox herunterlade + ie_warning_firefox: Firefox herunterladen ie_warning_ie: Internet Explorer aktualisieren - ie_warning_other: "Können Sie Ihren Browser nicht aktualisieren? Testen Sie Open Food Network auf Ihrem Smartphone :-)" - footer_global_headline: "OFN Global" - footer_global_home: "Zuhause" - footer_global_news: "Nachrichten" - footer_global_about: "Über" - footer_global_contact: "Kontakt" - footer_sites_headline: "OFN Seiten" - footer_sites_developer: "Entwickler" - footer_sites_community: "Community" - footer_sites_userguide: "Benutzerhandbuch" - footer_secure: "Sicher und vertrauenswürdig." - footer_secure_text: "Open Food Network verwendet überall SSL-Verschlüsselung (2048 Bit RSA), um Ihre Einkaufs- und Zahlungsinformationen geheim zu halten. Unsere Server speichern Ihre Kreditkartendetails nicht und Zahlungen werden von PCI-konformen Dienstleistern verarbeitet." - footer_contact_headline: "Den Kontakt halten" - footer_contact_email: "Schreiben Sie uns eine E-Mail" - footer_nav_headline: "Navigieren" - footer_join_headline: "Begleiten Sie uns" - footer_join_body: "Erstellen Sie einen Eintrag, ein Geschäft oder ein Gruppenverzeichnis im Open Food Network." - footer_join_cta: "Erzähl mir mehr!" - footer_legal_call: "Lesen Sie unsere" - footer_legal_tos: "Geschäftsbedingungen" - footer_legal_visit: "Finden Sie uns auf" - footer_legal_text_html: "Open Food Network ist eine freie und Open-Source-Software-Plattform. Unser Inhalt ist mit %{content_license} und unserem Code mit %{code_license} lizenziert." - footer_skylight_dashboard_html: Leistungsdaten sind unter %{dashboard} verfügbar. + ie_warning_other: "Können Sie Ihren Browser nicht aktualisieren? Versuchen Sie Open Food Network auf Ihrem Smartphone :-)" + legal: + cookies_policy: + header: "Wie wir Cookies verwenden" + desc_part_1: "Cookies sind sehr kleine Textdateien, die beim Besuch mancher Websiten auf Ihrem Computer gespeichert werden." + desc_part_2: "In OFN respektieren wir Ihre Privatsphäre. Wir verwenden nur die Cookies, die notwendig sind, um Ihnen den Service des Online-Kaufs / -Verkaufs von Lebensmitteln zu bieten. Wir verkaufen keine Ihrer Daten. Wir könnten Ihnen in Zukunft vorschlagen, einige Ihrer Daten zu teilen, um neue Commons-Dienste zu entwickeln, die für das Ökosystem nützlich sein könnten (wie Logistikdienstleistungen für kurze Nahrungsmittelsysteme), aber wir sind noch nicht dort, und wir werden es nicht ohne Ihr tun Genehmigung :-)" + desc_part_3: "Wir verwenden Cookies hauptsächlich, um sich daran zu erinnern, wer Sie sind, wenn Sie sich bei dem Dienst anmelden oder sich die Artikel merken können, die Sie in Ihren Warenkorb legen, auch wenn Sie nicht eingeloggt sind \"Cookies akzeptieren\" nehmen wir an, dass Sie uns die Speicherung von Cookies erlauben, die für das Funktionieren der Website notwendig sind. Hier ist die Liste der von uns verwendeten Cookies!" + essential_cookies: "Essentielle Kekse" + essential_cookies_desc: "Die folgenden Cookies sind für den Betrieb unserer Website unbedingt erforderlich." + essential_cookies_note: "Die meisten Cookies enthalten nur einen eindeutigen Bezeichner, aber keine anderen Daten. Ihre E-Mail-Adresse und Ihr Kennwort sind zum Beispiel niemals darin enthalten und werden nie veröffentlicht." + cookie_domain: "Einstellen nach:" + cookie_session_desc: "Wird verwendet, damit die Website sich zwischen den Seitenbesuchen an die Benutzer erinnern kann, z. B. an Artikel in Ihrem Einkaufswagen." + cookie_consent_desc: "Wird verwendet, um den Status der Benutzerzustimmung zum Speichern von Cookies beizubehalten" + cookie_remember_me_desc: "Wird verwendet, wenn der Benutzer die Website aufgefordert hat, sich an ihn zu erinnern. Dieser Cookie wird nach 12 Tagen automatisch gelöscht. Wenn Sie als Benutzer möchten, dass dieser Cookie gelöscht wird, müssen Sie sich nur abmelden. Wenn Sie nicht möchten, dass der Cookie auf Ihrem Computer installiert wird, sollten Sie das Kontrollkästchen \"An mich erinnern\" nicht aktivieren, wenn Sie sich anmelden." + cookie_openstreemap_desc: "Wird von unserem freundlichen Open-Source-Mapping-Anbieter (OpenStreetMap) verwendet, um sicherzustellen, dass während eines bestimmten Zeitraums nicht zu viele Anfragen eingehen, um den Missbrauch ihrer Dienste zu verhindern." + cookie_stripe_desc: "Daten gesammelt von unserem Zahlungsabwickler Stripe für die Betrugserkennung https://stripe.com/cookies-policy/legal. Nicht alle Geschäfte verwenden Stripe als Zahlungsmethode, aber es ist eine gute Vorgehensweise, sie auf alle Seiten anzuwenden. Stripe erstellt wahrscheinlich ein Bild davon, welche unserer Seiten normalerweise mit ihrer API interagieren und merkt, wenn etwas Ungewöhnliches passiert. Das Festlegen des Stripe-Cookies hat also eine breitere Funktion als die Bereitstellung einer Zahlungsmethode für einen Benutzer. Das Entfernen könnte die Sicherheit des Dienstes selbst beeinträchtigen. Sie können mehr über Stripe erfahren und dessen Datenschutzrichtlinie unter https://stripe.com/privacy lesen." + statistics_cookies: "Statistik-Cookies" + statistics_cookies_desc: "Die folgenden Punkte sind nicht unbedingt erforderlich, helfen Ihnen jedoch, die beste Benutzererfahrung zu bieten, indem wir das Benutzerverhalten analysieren, die am häufigsten verwendeten Funktionen identifizieren oder nicht verwenden, Probleme mit der Benutzerfreundlichkeit verstehen usw." + statistics_cookies_analytics_desc_html: "Zur Erfassung und Analyse von Daten zur Nutzung der Plattform verwenden wir Google Analytics, da es sich um den Standarddienst handelt, der mit Spree (der E-Commerce-Open-Source-Software, auf der wir aufgebaut haben) verbunden ist. Unsere Vision ist jedoch der Wechsel zu Matomo (ex Piwik, ein Open-Source-Analysetool, das der DSGVO entspricht und Ihre Privatsphäre schützt), sobald wir können." + statistics_cookies_matomo_desc_html: "Um Daten zur Nutzung der Plattform zu erfassen und zu analysieren, verwenden wir Matomo (ex Piwik), ein Open-Source-Analysetool, das der DSGVO-Richtlinie entspricht schützt Ihre Privatsphäre." + statistics_cookies_matomo_optout: "Möchten Sie Matomo Analytics deaktivieren? Wir sammeln keine persönlichen Daten und Matomo hilft uns, unseren Service zu verbessern, aber wir respektieren Ihre Wahl :-)" + cookie_analytics_utma_desc: "Wird zur Unterscheidung von Benutzern und Sitzungen verwendet. Der Cookie wird erstellt, wenn die JavaScript-Bibliothek ausgeführt wird und keine vorhandenen __utma-Cookies vorhanden sind. Der Cookie wird jedes Mal aktualisiert, wenn Daten an Google Analytics gesendet werden." + cookie_analytics_utmt_desc: "Wird zum Drosseln der Anforderungsrate verwendet." + cookie_analytics_utmb_desc: "Wird verwendet, um neue Sitzungen / Besuche zu bestimmen. Der Cookie wird erstellt, wenn die JavaScript-Bibliothek ausgeführt wird und keine vorhandenen __utmb-Cookies vorhanden sind. Der Cookie wird jedes Mal aktualisiert, wenn Daten an Google Analytics gesendet werden." + cookie_analytics_utmc_desc: "Wird nicht in ga.js verwendet Stellen Sie die Interoperabilität mit urchin.js ein. In der Vergangenheit wurde dieser Cookie in Verbindung mit dem __utmb-Cookie verwendet, um festzustellen, ob sich der Benutzer in einer neuen Sitzung / einem neuen Besuch befand." + cookie_analytics_utmz_desc: "Speichert die Zugriffsquelle oder Kampagne, die erläutert, wie der Nutzer Ihre Website erreicht hat. Der Cookie wird erstellt, wenn die JavaScript-Bibliothek ausgeführt wird, und wird jedes Mal aktualisiert, wenn Daten an Google Analytics gesendet werden." + cookie_matomo_basics_desc: "Matomo First Party Cookies zum Sammeln von Statistiken." + cookie_matomo_heatmap_desc: "Matomo Heatmap & Session Aufnahme-Cookie." + cookie_matomo_ignore_desc: "Cookie verwendet, um Benutzer von der Verfolgung auszuschließen." + disabling_cookies_header: "Warnung zum Deaktivieren von Cookies" + disabling_cookies_desc: "Als Nutzer können Sie Cookies von Open Food Network oder einer anderen Website immer zulassen, blockieren oder löschen, wann immer Sie die Einstellungskontrolle Ihres Browsers nutzen möchten. Jeder Browser hat einen anderen Benutzer. Hier sind die Links:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" + disabling_cookies_note: "Beachten Sie jedoch, dass die Website nicht funktioniert, wenn Sie die essentiellen Cookies löschen oder ändern, die von Open Food Network verwendet werden. Sie können also nichts zum Warenkorb hinzufügen, auch nicht zur Kasse gehen." + cookies_banner: + cookies_usage: "Diese Website verwendet Cookies, um Ihre Navigation reibungslos und sicher zu gestalten und um zu verstehen, wie Sie sie verwenden, um die von uns angebotenen Funktionen zu verbessern." + cookies_definition: "Cookies sind sehr kleine Textdateien, die beim Besuch einiger Websites auf Ihrem Computer gespeichert werden." + cookies_desc: "Wir verwenden nur die Cookies, die notwendig sind, um Ihnen den Service des Online-Kaufs / -Verkaufs von Lebensmitteln zu bieten. Wir verkaufen keine Ihrer Daten. Wir verwenden Cookies hauptsächlich, um sich daran zu erinnern, wer Sie sind, wenn Sie sich bei dem Dienst anmelden oder sich die Artikel merken können, die Sie in Ihren Warenkorb legen, auch wenn Sie nicht eingeloggt sind \"Cookies akzeptieren\" nehmen wir an, dass Sie uns die Speicherung von Cookies erlauben, die für das Funktionieren der Website notwendig sind." + cookies_policy_link_desc: "Wenn Sie mehr erfahren möchten, besuchen Sie unsere" + cookies_policy_link: "Hinweise zu Cookies" + cookies_accept_button: "Cookies akzeptieren" home_shop: Jetzt einkaufen - brandstory_headline: "Essen, ohne eigene Rechtspersönlichkeit." brandstory_intro: "Manchmal ist der beste Weg, das System zu reparieren, ein neues zu starten ..." brandstory_part1: "Wir beginnen von Grund auf. Mit Bauern und Züchtern, die bereit sind, ihre Geschichten stolz und wahrhaftig zu erzählen. Mit Händlern, die bereit sind, Menschen mit Produkten fair und ehrlich zu verbinden. Mit Käufern, die glauben, dass bessere wöchentliche Einkaufsentscheidungen die Welt ernsthaft verändern können." brandstory_part2: "Dann brauchen wir einen Weg, um es real zu machen. Ein Weg, jeden zu stärken, der Lebensmittel anbaut, verkauft und kauft. Ein Weg, um alle Geschichten zu erzählen, um die gesamte Logistik zu bewältigen. Eine Möglichkeit, Transaktionen jeden Tag in Transformation umzuwandeln." @@ -1189,39 +1293,39 @@ de_DE: brandstory_part6: "Wir alle lieben das Essen. Jetzt können wir unser Nahrungssystem auch lieben." learn_body: "Erkunden Sie Modelle, Geschichten und Ressourcen, um Sie bei der Entwicklung Ihres Fair-Food-Geschäfts oder Ihrer Organisation zu unterstützen. Finden Sie Schulungen, Veranstaltungen und andere Möglichkeiten, um von Gleichgesinnten zu lernen." learn_cta: "Lass dich inspirieren" - connect_body: "Durchsuchen Sie unsere vollständigen Verzeichnisse von Herstellern, Hubs und Gruppen, um Fair-Food-Händler in Ihrer Nähe zu finden. Listen Sie Ihr Unternehmen oder Ihre Organisation auf dem OFN auf, damit Käufer Sie finden können. Treten Sie der Community bei, um Rat zu bekommen und Probleme gemeinsam zu lösen." + connect_body: "Durchsuchen Sie unsere vollständigen Verzeichnisse von Erzeugern, Hubs und Gruppen, um Fair-Food-Händler in Ihrer Nähe zu finden. Listen Sie Ihr Unternehmen oder Ihre Organisation auf dem OFN auf, damit Käufer Sie finden können. Treten Sie der Community bei, um Rat zu bekommen und Probleme gemeinsam zu lösen." connect_cta: "Erkunden" system_headline: "Einkaufen - so funktioniert es." system_step1: "1. Suche" system_step1_text: "Durchsuchen Sie unsere verschiedenen, unabhängigen Geschäfte nach saisonalem, lokalem Essen. Suche nach Gegend und Lebensmittelkategorie, oder ob Sie Lieferung oder Abholung bevorzugen." system_step2: "2. Laden" - system_step2_text: "Transformieren Sie Ihre Transaktionen mit erschwinglichen lokalen Lebensmitteln von verschiedenen Herstellern und Hubs. Kenne die Geschichten hinter deinem Essen und den Menschen, die es schaffen!" + system_step2_text: "Transformieren Sie Ihre Transaktionen mit erschwinglichen lokalen Lebensmitteln von verschiedenen Erzeugern und Hubs. Erfahren Sie die Geschichten hinter Ihrem Essen und den Menschen, die es erzeugen." system_step3: "3. Abholung / Lieferung" - system_step3_text: "Erhalten Sie eine Lieferung oder besuchen Sie Ihren Hersteller oder Hub für eine persönlichere Verbindung mit Ihrem Essen. Lebensmitteleinkauf so vielfältig wie die Natur es beabsichtigt hat." + system_step3_text: "Erhalten Sie eine Lieferung oder besuchen Sie Ihren Hersteller oder Ihr Hub für eine persönlichere Verbindung mit Ihrem Essen. Lebensmitteleinkauf so vielfältig wie die Natur es beabsichtigt hat." cta_headline: "Einkaufen, das die Welt verbessert." cta_label: "Ich bin bereit" stats_headline: "Wir schaffen ein neues Ernährungssystem." stats_producers: "Nahrungsmittelproduzenten" stats_shops: "Lebensmittelgeschäfte" stats_shoppers: "Lebensmittelkäufer" - stats_orders: "Essen Bestellungen" - checkout_title: Auschecken - checkout_now: Checke jetzt aus - checkout_order_ready: Bestellung bereit für - checkout_hide: Verstecke + stats_orders: "Nahrungsmittelbestellungen" + checkout_title: Kasse + checkout_now: Zur Kasse + checkout_order_ready: Bestellung bereit am + checkout_hide: Ausblenden checkout_expand: Erweitern - checkout_headline: "Ok, bereit zur Kasse?" - checkout_as_guest: "Kasse als Gast" - checkout_details: "Deine Details" + checkout_headline: "Ok, jetzt zur Kasse?" + checkout_as_guest: "Als Gast zur Kasse" + checkout_details: "Ihre Details" checkout_billing: "Rechnungsinfo" checkout_default_bill_address: "Als Standard-Rechnungsadresse speichern" checkout_shipping: Versandinformation checkout_default_ship_address: "Als Standardversandadresse speichern" - checkout_method_free: Frei + checkout_method_free: ?? checkout_address_same: Lieferadresse wie Rechnungsadresse? - checkout_ready_for: "Bereit für:" - checkout_instructions: "Irgendwelche Kommentare oder spezielle Anweisungen?" - checkout_payment: Bezahlung + checkout_ready_for: "Bereit am:" + checkout_instructions: "Kommentare oder spezielle Anweisungen?" + checkout_payment: Zahlung checkout_send: Jetzt bestellen checkout_your_order: Ihre Bestellung checkout_cart_total: Warenkorb insgesamt @@ -1232,23 +1336,23 @@ de_DE: order_paid: BEZAHLT order_not_paid: NICHT BEZAHLT order_total: Gesamtbestellung - order_payment: "Bezahlen über:" + order_payment: "Bezahlen per:" order_billing_address: Rechnungsadresse order_delivery_on: Lieferung am order_delivery_address: Lieferadresse order_delivery_time: Lieferzeit - order_special_instructions: "Deine Noten:" + order_special_instructions: "Deine Notizen:" order_pickup_time: abholbereit - order_pickup_instructions: Sammlung Anweisungen - order_produce: Produzieren + order_pickup_instructions: Abholinformationen + order_produce: Produkte order_total_price: Total order_includes_tax: (inkl. Steuern) - order_payment_paypal_successful: Ihre Zahlung via PayPal wurde erfolgreich abgewickelt. + order_payment_paypal_successful: Ihre Zahlung mit PayPal wurde erfolgreich abgewickelt. order_hub_info: Hub-Info order_back_to_store: Zurück zum Laden order_back_to_cart: Zurück zum Warenkorb bom_tip: "Verwenden Sie diese Seite, um Produktmengen über mehrere Bestellungen hinweg zu ändern. Produkte können bei Bedarf auch komplett aus Bestellungen entfernt werden." - unsaved_changes_warning: "Nicht gespeicherte Änderungen sind vorhanden und gehen verloren, wenn Sie fortfahren." + unsaved_changes_warning: "Sie haben nicht gespeicherte Änderungen, die beim Fortfahren verloren gehen." unsaved_changes_error: "Felder mit roten Rahmen enthalten Fehler." products: "Produkte" products_in: "in %{oc}" @@ -1259,8 +1363,8 @@ de_DE: email_registered: "ist jetzt Teil von" email_userguide_html: "Das Benutzerhandbuch mit ausführlicher Unterstützung für die Einrichtung Ihres Producer oder Hub finden Sie hier: %{link}" email_admin_html: "Sie können Ihr Konto verwalten, indem Sie sich bei %{link} anmelden oder indem Sie oben rechts auf der Startseite auf das Zahnrad klicken und Administration auswählen." - email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Sie werden ermutigt mitzumachen. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum wird prägen, was als nächstes passiert. %{link}" - join_community: "Trete der Community bei" + email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Reden Sie doch mit. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum prägt, was als nächstes passiert. %{link}" + join_community: "Treten Sie der Community bei" email_confirmation_activate_account: "Bevor wir Ihr neues Konto aktivieren können, müssen wir Ihre E-Mail-Adresse bestätigen." email_confirmation_greeting: "Hallo, %{contact}!" email_confirmation_profile_created: "Ein Profil für %{name} wurde erfolgreich erstellt! Um Ihr Profil zu aktivieren, müssen wir diese E-Mail-Adresse bestätigen." @@ -1270,9 +1374,9 @@ de_DE: email_confirmation_notice_unexpected: "Sie haben diese Nachricht erhalten, weil Sie sich unter %{sitename} angemeldet haben oder von einer Person eingeladen wurden, die Sie wahrscheinlich kennen. Wenn Sie nicht verstehen, warum Sie diese E-Mail erhalten, schreiben Sie bitte an %{contact}." email_social: "Verbinde dich mit uns:" email_contact: "Schreiben Sie uns eine E-Mail:" - email_signoff: "Prost," + email_signoff: "Danke" email_signature: "%{sitename} Team" - email_confirm_customer_greeting: "Hallo %[name]," + email_confirm_customer_greeting: "Hallo %{name}," email_confirm_customer_intro_html: "Vielen Dank für ihren Einkauf bei %{distributor} !" email_confirm_customer_number_html: "Bestellbestätigung # %{number} " email_confirm_customer_details_html: "Hier sind Ihre Bestelldetails von %{distributor} :" @@ -1289,7 +1393,7 @@ de_DE: email_payment_paid: BEZAHLT email_payment_not_paid: NICHT BEZAHLT email_payment_summary: Zahlungsübersicht - email_payment_method: "Bezahlen über:" + email_payment_method: "Bezahlen per:" email_so_placement_intro_html: "Sie haben eine neue Bestellung mit %{distributor} " email_so_placement_details_html: "Hier sind die Details Ihrer Bestellung für %{distributor} :" email_so_placement_changes: "Leider waren nicht alle von Ihnen angeforderten Produkte verfügbar. Die von Ihnen angeforderten Originalmengen sind unten durchgestrichen." @@ -1298,6 +1402,7 @@ de_DE: email_so_edit_true_html: "Sie können Änderungen vornehmen , bis Bestellungen auf %{orders_close_at} schließen." email_so_edit_false_html: "Sie können jederzeit Details zu dieser Bestellung anzeigen ." email_so_contact_distributor_html: "Wenn Sie Fragen haben, können Sie sich mit %{distributor} über %{email} in Verbindung setzen." + email_so_contact_distributor_to_change_order_html: "Diese Bestellung wurde automatisch für Sie erstellt. Sie können Änderungen vornehmen, bis Bestellungen auf %{orders_close_at} geschlossen werden, indem Sie %{distributor} über %{email} kontaktieren." email_so_confirmation_intro_html: "Ihre Bestellung mit %{distributor} ist jetzt bestätigt" email_so_confirmation_explainer_html: "Diese Bestellung wurde automatisch für Sie aufgegeben und ist nun abgeschlossen." email_so_confirmation_details_html: "Hier finden Sie alles, was Sie über Ihre Bestellung wissen müssen: %{distributor} :" @@ -1341,11 +1446,11 @@ de_DE: shopping_contact_web: "Kontakt" shopping_contact_social: "Folgen" shopping_groups_part_of: "ist ein Teil von:" - shopping_producers_of_hub: "%{hub} Produzenten:" - enterprises_next_closing: "Nächster Auftrag schließt" + shopping_producers_of_hub: "Erzeuger bei%{hub}" + enterprises_next_closing: "Nächster Bestellschluß" enterprises_ready_for: "Fertig am" - enterprises_choose: "Wählen Sie, wann Sie Ihre Bestellung wünschen:" - maps_open: "Öffnen" + enterprises_choose: "Wählen Sie, wann Sie Ihre Bestellung wollen:" + maps_open: "Offen" maps_closed: "Geschlossen" hubs_buy: "Suche nach:" hubs_shopping_here: "Hier einkaufen" @@ -1408,7 +1513,7 @@ de_DE: groups_signup_motivation2: Deshalb sind wir jeden Tag aufgestanden. Wir sind eine globale Non-Profit-Organisation, die auf Open-Source-Code basiert. Wir spielen fair. Sie können uns immer vertrauen. groups_signup_motivation3: Wir wissen, dass Sie große Ideen haben und wir helfen wollen. Wir teilen unser Wissen, Netzwerke und Ressourcen. Wir wissen, dass Isolation keine Veränderung verursacht, also werden wir mit Ihnen zusammenarbeiten. groups_signup_motivation4: Wir treffen dich, wo du bist. - groups_signup_motivation5: Sie könnten eine Allianz von Food-Hubs, Produzenten oder Distributoren, einer Industrieorganisation oder einer lokalen Regierung sein. + groups_signup_motivation5: Sie könnten eine Allianz von Hubs, Produzenten oder Vertailern sein, eine Industrieorganisation oder eine Lokalbehörde. groups_signup_motivation6: Was auch immer Ihre Rolle in Ihrer lokalen Nahrungsmittelbewegung ist, wir sind bereit zu helfen. Wie auch immer Sie sich fragen, wie Open Food Network in Ihrem Teil der Welt aussehen würde oder wird, lassen Sie uns das Gespräch beginnen. groups_signup_motivation7: Wir machen Nahrungsmittelbewegungen sinnvoller. groups_signup_motivation8: Sie müssen Ihre Netzwerke aktivieren und aktivieren, wir bieten eine Plattform für Konversation und Aktion. Sie brauchen echtes Engagement. Wir helfen, alle Akteure, alle Beteiligten, alle Sektoren zu erreichen. @@ -1419,8 +1524,8 @@ de_DE: groups_signup_contact_text: "Kontaktieren Sie uns, um herauszufinden, was OFN für Sie tun kann:" groups_signup_detail: "Hier ist das Detail." login_invalid: "Ungültige E-Mail-Adresse oder ungültiges Passwort" - modal_hubs: "Essen Hubs" - modal_hubs_abstract: Unsere Food-Hubs sind der Kontaktpunkt zwischen Ihnen und den Menschen, die Ihr Essen zubereiten! + modal_hubs: "Lebensmittel-Hubs" + modal_hubs_abstract: Unsere Hubs sind der Kontaktpunkt zwischen Ihnen und den Menschen, die Ihre Lebensmittel herstellen! modal_hubs_content1: Sie können nach einem geeigneten Hub nach Standort oder Namen suchen. Einige Hubs haben mehrere Punkte, an denen Sie Ihre Einkäufe abholen können, und einige bieten auch Lieferoptionen. Jeder Food-Hub ist eine Verkaufsstelle mit eigenständigem Geschäftsbetrieb und Logistik - so sind Unterschiede zwischen den Hubs zu erwarten. modal_hubs_content2: Sie können nicht bei mehr als einem Hub gleichzeitig einkaufen. modal_groups: "Gruppen / Regionen" @@ -1467,7 +1572,7 @@ de_DE: sell_headline: "Steigen Sie in das Open Food Netzwerk ein!" sell_motivation: "Stellen Sie Ihr schönes Essen vor." sell_producers: "Produzenten" - sell_hubs: "Naben" + sell_hubs: "Hubs" sell_groups: "Gruppen" sell_producers_detail: "Richten Sie in wenigen Minuten ein Profil für Ihr Unternehmen auf dem OFN ein. Sie können Ihr Profil jederzeit auf einen Online-Shop aktualisieren und Ihre Produkte direkt an Kunden verkaufen." sell_hubs_detail: "Richten Sie im OFN ein Profil für Ihr Lebensmittelunternehmen oder Ihre Organisation ein. Sie können Ihr Profil jederzeit auf einen Multi-Producer-Shop upgraden." @@ -1479,8 +1584,8 @@ de_DE: shops_title: Läden shops_headline: Einkaufen, verwandelt. shops_text: Nahrung wächst in Zyklen, Bauern ernten in Zyklen und wir bestellen Nahrung in Zyklen. Wenn Sie feststellen, dass ein Bestellzyklus geschlossen ist, schauen Sie bald wieder vorbei. - shops_signup_title: Melde dich als Hub an - shops_signup_headline: Food-Hubs, unbegrenzt. + shops_signup_title: Als Hub registrieren + shops_signup_headline: Lebensmittel-Hubs, unbegrenzt. shops_signup_motivation: Was auch immer Ihr Modell ist, wir unterstützen Sie. Wie auch immer Sie sich ändern, wir sind bei Ihnen. Wir sind gemeinnützig, unabhängig und Open-Source. Wir sind die Software-Partner, von denen Sie schon immer geträumt haben. shops_signup_action: Jetzt beitreten shops_signup_pricing: Unternehmenskonten @@ -1494,13 +1599,13 @@ de_DE: orders_edit_headline: Ihr Warenkorb orders_edit_time: Bestellung bereit für orders_edit_continue: Mit dem Einkauf fortfahren - orders_edit_checkout: Auschecken + orders_edit_checkout: Kasse orders_form_empty_cart: "Einkaufskorb leeren" orders_form_subtotal: Zwischensumme erzeugen orders_form_admin: Admin & Handhabung orders_form_total: Total orders_oc_expired_headline: Bestellungen wurden für diesen Bestellzyklus geschlossen - orders_oc_expired_text: "Entschuldigung, Bestellungen für diesen Bestellzyklus haben %{time} geschlossen! Bitte kontaktieren Sie direkt Ihren Hub, um zu sehen, ob sie verspätete Bestellungen annehmen können." + orders_oc_expired_text: "Entschuldigung, Bestellungen für diesen Bestellzyklus wurden vor %{time} geschlossen! Bitte kontaktieren Sie Ihr Hub direkt, um zu sehen, ob sie verspätete Bestellungen annehmen können." orders_oc_expired_text_others_html: "Entschuldigung, Bestellungen für diesen Bestellzyklus haben %{time} geschlossen! Wenden Sie sich direkt an Ihren Hub, um zu sehen, ob er verspätete Bestellungen annehmen kann %{link} ." orders_oc_expired_text_link: "oder sehen Sie die anderen Bestellzyklen an diesem Hub" orders_oc_expired_email: "Email:" @@ -1559,6 +1664,7 @@ de_DE: error_number: "muss nummer sein" error_email: "muss eine E-Mail-Adresse sein" error_not_found_in_database: "%{name} nicht in Datenbank gefunden" + error_not_primary_producer: "%{name} ist nicht als Producer aktiviert" error_no_permission_for_enterprise: "\"%{name}\": Sie sind nicht berechtigt, Produkte für dieses Unternehmen zu verwalten" item_handling_fees: "Artikel Bearbeitungsgebühren (in den Gesamtsummen enthalten)" january: "Januar" @@ -1684,9 +1790,9 @@ de_DE: enterprise_long_desc_placeholder: "Dies ist Ihre Chance, die Geschichte Ihres Unternehmens zu erzählen - was macht Sie anders und wundervoll? Wir empfehlen, Ihre Beschreibung auf unter 600 Zeichen oder 150 Wörter zu beschränken." enterprise_long_desc_length: "%{num} Zeichen / bis zu 600 empfohlen" enterprise_limit: Unternehmensgrenze - enterprise_abn: "ABN" + enterprise_abn: "USt-IdNr." enterprise_abn_placeholder: "z.B. 99 123 456 789" - enterprise_acn: "ACN" + enterprise_acn: "St.-Nr." enterprise_acn_placeholder: "z.B. 123 456 789" enterprise_tax_required: "Sie müssen eine Auswahl treffen." enterprise_final_step: "Letzter Schritt!" @@ -1750,7 +1856,7 @@ de_DE: registration_detail_state_error: "Staat erforderlich" registration_detail_country: "Land:" registration_detail_country_error: "Bitte wähle ein Land" - shipping_method_destroy_error: "Diese Versandmethode kann nicht gelöscht werden, da sie in einer Bestellung referenziert wird: %{number}." + shipping_method_destroy_error: "Diese Lieferart kann nicht gelöscht werden, da sie in einer Bestellung verwendet wird: %{number}." accounts_and_billing_task_already_running_error: "Eine Aufgabe wird bereits ausgeführt. Bitte warten Sie, bis sie abgeschlossen ist" accounts_and_billing_start_task_notice: "Aufgabe in Warteschlange gestellt" fees: "Gebühren" @@ -1785,37 +1891,37 @@ de_DE: you_have_no_orders_yet: "Du hast noch keine Bestellungen" running_balance: "Laufendes Gleichgewicht" outstanding_balance: "Offener Betrag" - admin_entreprise_relationships: "Unternehmensberechtigungen" - admin_entreprise_relationships_everything: "Alles" - admin_entreprise_relationships_permits: "Genehmigungen" - admin_entreprise_relationships_seach_placeholder: "Suche" - admin_entreprise_relationships_button_create: "Neu" - admin_entreprise_groups: "Unternehmensgruppen" - admin_entreprise_groups_name: "Name" - admin_entreprise_groups_owner: "Inhaber" - admin_entreprise_groups_on_front_page: "Auf der ersten Seite?" - admin_entreprise_groups_entreprise: "Unternehmen" - admin_entreprise_groups_data_powertip: "Der primäre Benutzer, der für diese Gruppe verantwortlich ist." - admin_entreprise_groups_data_powertip_logo: "Dies ist das Logo für die Gruppe" - admin_entreprise_groups_data_powertip_promo_image: "Dieses Bild wird oben im Gruppenprofil angezeigt" - admin_entreprise_groups_contact: "Kontakt" - admin_entreprise_groups_contact_phone_placeholder: "z.B. 98 7654 3210" - admin_entreprise_groups_contact_address1_placeholder: "z.B. Gartenweg 12" - admin_entreprise_groups_contact_city: "Vorort" - admin_entreprise_groups_contact_city_placeholder: "z.B. Northcote" - admin_entreprise_groups_contact_zipcode: "Postleitzahl" - admin_entreprise_groups_contact_zipcode_placeholder: "z.B. 3070" - admin_entreprise_groups_contact_state_id: "Bundesland" - admin_entreprise_groups_contact_country_id: "Land" - admin_entreprise_groups_web: "Webressourcen" - admin_entreprise_groups_web_twitter: "z.B. @the_prof" - admin_entreprise_groups_web_website_placeholder: "z.B. www.truffles.com" + admin_enterprise_relationships: "Unternehmensberechtigungen" + admin_enterprise_relationships_everything: "Alles" + admin_enterprise_relationships_permits: "Genehmigungen" + admin_enterprise_relationships_seach_placeholder: "Suche" + admin_enterprise_relationships_button_create: "Neu" + admin_enterprise_groups: "Unternehmensgruppen" + admin_enterprise_groups_name: "Name" + admin_enterprise_groups_owner: "Inhaber" + admin_enterprise_groups_on_front_page: "Auf der ersten Seite?" + admin_enterprise_groups_enterprise: "Unternehmen" + admin_enterprise_groups_data_powertip: "Der primäre Benutzer, der für diese Gruppe verantwortlich ist." + admin_enterprise_groups_data_powertip_logo: "Dies ist das Logo für die Gruppe" + admin_enterprise_groups_data_powertip_promo_image: "Dieses Bild wird oben im Gruppenprofil angezeigt" + admin_enterprise_groups_contact: "Kontakt" + admin_enterprise_groups_contact_phone_placeholder: "z.B. 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "z.B. Gartenstrasse 123" + admin_enterprise_groups_contact_city: "Vorort" + admin_enterprise_groups_contact_city_placeholder: "z.B. Nordwestheim" + admin_enterprise_groups_contact_zipcode: "Postleitzahl" + admin_enterprise_groups_contact_zipcode_placeholder: "z.B. 30701" + admin_enterprise_groups_contact_state_id: "Status" + admin_enterprise_groups_contact_country_id: "Land" + admin_enterprise_groups_web: "Webressourcen" + admin_enterprise_groups_web_twitter: "z.B. @the_prof" + admin_enterprise_groups_web_website_placeholder: "z.B. www.truffles.com" admin_order_cycles: "Admin-Bestellzyklen" open: "Öffnen" close: "Abschließen" create: "Neu" search: "Suche" - supplier: "Zulieferer" + supplier: "Anbieter" product_name: "Produktname" product_description: "Produktbeschreibung" units: "Einheitsgröße" @@ -1824,7 +1930,7 @@ de_DE: enterprise_fees: "Unternehmensgebühren" process_my_order: "Verarbeite meine Bestellung" delivery_instructions: Lieferanleitungen - delivery_method: Zustellungsmethode + delivery_method: Lieferart fee_type: "Art der Gebühr" tax_category: "Steuerkategorie" calculator: "Rechner" @@ -1864,7 +1970,7 @@ de_DE: spree_admin_overview_enterprises_footer: "VERWALTEN SIE MEINE UNTERNEHMEN" spree_admin_enterprises_hubs_name: "Name" spree_admin_enterprises_create_new: "neu erstellen" - spree_admin_enterprises_shipping_methods: "Lieferoptionen" + spree_admin_enterprises_shipping_methods: "Lieferarten" spree_admin_enterprises_fees: "Unternehmensgebühren" spree_admin_enterprises_none_create_a_new_enterprise: "ERSTELLEN SIE EIN NEUES UNTERNEHMEN" spree_admin_enterprises_none_text: "Sie haben noch keine Unternehmen" @@ -1879,10 +1985,10 @@ de_DE: spree_admin_unit_value: Einheitswert spree_admin_unit_description: Einheit Beschreibung spree_admin_variant_unit: Varianteneinheit - spree_admin_variant_unit_scale: Variationseinheitsskala - spree_admin_supplier: Lieferant + spree_admin_variant_unit_scale: Einheitsmaß der Variante + spree_admin_supplier: Anbieter spree_admin_product_category: Produktkategorie - spree_admin_variant_unit_name: Name der Varianteinheit + spree_admin_variant_unit_name: Einheit der Variante change_package: "Paket ändern" spree_admin_single_enterprise_hint: "Tipp: Um es Ihnen zu ermöglichen, Sie zu finden, aktivieren Sie Ihre Sichtbarkeit unter" spree_admin_eg_pickup_from_school: "z.B. \"Abholung von der Grundschule\"" @@ -1909,13 +2015,12 @@ de_DE: edit_profile_details_etc: "Ändern Sie Ihre Profilbeschreibung, Bilder usw." order_cycle: "Bestellungszyklus" order_cycles: "Bestellrunden" + enterprise_relationships: "Unternehmensberechtigungen" remove_tax: "Steuer entfernen" - enterprise_terms_of_service: "Unternehmens-Nutzungsbedingungen" - enterprises_require_tos: "Unternehmen müssen die Nutzungsbedingungen akzeptieren" - enterprise_tos_link: "Links zum Unternehmens-Servicebedingungen" + enterprise_tos_link: "Link zu den AGB des Unternehmens" enterprise_tos_message: "Wir wollen mit Menschen zusammenarbeiten, die unsere Ziele und Werte teilen. Als solche bitten wir neue Unternehmen, uns zuzustimmen" - enterprise_tos_link_text: "Nutzungsbedingungen." - enterprise_tos_agree: "Ich stimme den oben genannten Nutzungsbedingungen zu" + enterprise_tos_link_text: "AGB." + enterprise_tos_agree: "Ich stimme den oben genannten Geschäftsbedingungen zu" tax_settings: "Steuereinstellungen" products_require_tax_category: "Produkte benötigen eine Steuerkategorie" admin_shared_address_1: "Adresse" @@ -1932,7 +2037,7 @@ de_DE: hub_sidebar_red: "rot" shop_trial_in_progress: "Ihr Laden-Versuch läuft in %{days} ab." report_customers_distributor: "Verteiler" - report_customers_supplier: "Zulieferer" + report_customers_supplier: "Anbieter" report_customers_cycle: "Bestellungszyklus" report_customers_type: "Berichtsart" report_customers_csv: "Download als CSV" @@ -1946,7 +2051,7 @@ de_DE: report_payment_totals: 'Zahlungssummen' report_all: 'alle' report_order_cycle: "Bestellzyklus:" - report_entreprises: "Unternehmen:" + report_enterprises: "Unternehmen:" report_users: "Benutzer:" report_tax_rates: Steuersätze report_tax_types: Steuerarten @@ -1970,32 +2075,32 @@ de_DE: report_header_paid: Bezahlt? report_header_delivery: Lieferung? report_header_shipping: Lieferung - report_header_shipping_method: Liefermethode + report_header_shipping_method: Lieferart report_header_shipping_instructions: Versand-Anweisungen - report_header_ship_street: Schiffsstraße - report_header_ship_street_2: Schiffsstraße 2 - report_header_ship_city: Schiffsstadt - report_header_ship_postcode: Postleitzahl senden - report_header_ship_state: Schiffsstaat - report_header_billing_street: Rechnungsstraße - report_header_billing_street_2: Rechnungsstraße 2 - report_header_billing_street_3: Rechnungsstraße 3 - report_header_billing_street_4: Rechnungsstraße 4 - report_header_billing_city: Abrechnungsstadt - report_header_billing_postcode: Rechnungs Postleitzahl - report_header_billing_state: Abrechnungs Zustand + report_header_ship_street: Liefer-Straße + report_header_ship_street_2: Liefer-Straße 2 + report_header_ship_city: Liefer-Stadt + report_header_ship_postcode: Liefer-Postleitzahl + report_header_ship_state: Liefer-Bundesland + report_header_billing_street: Rechnung-Straße + report_header_billing_street_2: Rechnung-Straße 2 + report_header_billing_street_3: Rechnung-Straße 3 + report_header_billing_street_4: Rechnung-Straße 4 + report_header_billing_city: Rechnung-Stadt + report_header_billing_postcode: Rechnung-Postleitzahl + report_header_billing_state: Rechnung-Bundesland report_header_incoming_transport: Eingehender Transport - report_header_special_instructions: spezielle Anweisungen + report_header_special_instructions: Besondere Anweisungen report_header_order_number: Bestellnummer report_header_date: Datum report_header_confirmation_date: Bestätigungsdatum - report_header_tags: Stichworte - report_header_items: Artikel - report_header_items_total: "Artikel insgesamt %{currency_symbol}" + report_header_tags: Stichwörter + report_header_items: Produkte + report_header_items_total: "Produkte insgesamt %{currency_symbol}" report_header_taxable_items_total: "Steuerpflichtige Posten insgesamt (%{currency_symbol})" report_header_sales_tax: "Umsatzsteuer (%{currency_symbol})" report_header_delivery_charge: "Liefergebühr (%{currency_symbol})" - report_header_tax_on_delivery: "Versandkosten (%{currency_symbol})" + report_header_tax_on_delivery: "Steuer auf Versandkosten (%{currency_symbol})" report_header_tax_on_fees: "Steuer auf Gebühren (%{currency_symbol})" report_header_total_tax: "Gesamtsteuer (%{currency_symbol})" report_header_enterprise: Unternehmen @@ -2012,11 +2117,11 @@ de_DE: report_header_unallocated: Nicht zugewiesen report_header_max_quantity_excess: Max Menge Überschuss report_header_taxons: Taxonen - report_header_supplier: Zulieferer + report_header_supplier: Anbieter report_header_producer: Erzeuger - report_header_producer_suburb: Produzent Vorort + report_header_producer_suburb: Erzeuger Vorort report_header_unit: Einheit - report_header_group_buy_unit_quantity: Gruppe Kaufeinheitsmenge + report_header_group_buy_unit_quantity: Gruppen-Kaufeinheitsmenge report_header_cost: Kosten report_header_shipping_cost: Versandkosten report_header_curr_cost_per_unit: Curr. Kosten pro Einheit @@ -2027,25 +2132,25 @@ de_DE: report_header_price: Preis report_header_unit_size: Einheitsgröße report_header_distributor: Verteiler - report_header_distributor_address: Händleradresse - report_header_distributor_city: Händlerstadt - report_header_distributor_postcode: Postleitzahl des Vertriebspartners + report_header_distributor_address: Verteileradresse + report_header_distributor_city: Verteilerstadt + report_header_distributor_postcode: Verteilerpostleitzahl report_header_delivery_address: Lieferadresse report_header_delivery_postcode: Liefer-Postleitzahl report_header_bulk_unit_size: Bulk-Einheitsgröße report_header_weight: Gewicht report_header_sum_total: Gesamtsumme report_header_date_of_order: Datum der Bestellung - report_header_amount_owing: Geschuldeten Betrag + report_header_amount_owing: Offener Betrag report_header_amount_paid: Bezahlter Betrag report_header_units_required: Benötigte Einheiten report_header_remainder: Rest - report_header_order_date: Auftragsdatum - report_header_order_id: Auftragsnummer + report_header_order_date: Bestelldatum + report_header_order_id: Bestellnummer report_header_item_name: Artikelname - report_header_temp_controlled_items: Temp Kontrollierte Artikel? + report_header_temp_controlled_items: Temperaturkontrollierte Artikel? report_header_customer_name: Kundenname - report_header_customer_email: Kunden-eMail + report_header_customer_email: Kunden-E-Mail report_header_customer_phone: Kundentelefon report_header_customer_city: Kundenstadt report_header_payment_state: Zahlungsstatus @@ -2053,17 +2158,17 @@ de_DE: report_header_item_price: "Artikel (%{currency})" report_header_item_fees_price: "Artikel + Gebühren (%{currency})" report_header_admin_handling_fees: "Verwaltung und Handhabung (%{currency})" - report_header_ship_price: "Schiff (%{currency})" - report_header_pay_fee_price: "Gebühren (%{currency})" + report_header_ship_price: "Liefer (%{currency})" + report_header_pay_fee_price: "Gebühren bezahlen (%{currency})" report_header_total_price: "Gesamt (%{currency})" - report_header_product_total_price: "Produkt gesamt (%{currency})" + report_header_product_total_price: "Produkte gesamt (%{currency})" report_header_shipping_total_price: "Versand Gesamt (%{currency})" - report_header_outstanding_balance_price: "Guthaben (%{currency})" + report_header_outstanding_balance_price: "Saldo (%{currency})" report_header_eft_price: "EFT (%{currency})" report_header_paypal_price: "PayPal (%{currency})" report_header_sku: Artikelnummer - report_header_amount: Menge - report_header_balance: Gesamt + report_header_amount: Betrag + report_header_balance: Saldo report_header_total_cost: "Gesamtkosten" report_header_total_ordered: Insgesamt bestellt report_header_total_max: Gesamt max @@ -2071,11 +2176,11 @@ de_DE: report_header_sum_max_total: "Summe Max. Summe" report_header_total_excl_vat: "Summe exkl. Steuern (%{currency_symbol})" report_header_total_incl_vat: "Summe inkl. Steuern (%{currency_symbol})" - report_header_temp_controlled: TempControlled? - report_header_is_producer: Hersteller? + report_header_temp_controlled: Temperaturkontrolliert? + report_header_is_producer: Erzeuger? report_header_not_confirmed: Nicht bestätigt - report_header_gst_on_income: GST auf Einkommen - report_header_gst_free_income: GST Freies Einkommen + report_header_gst_on_income: Umsatzsteuer auf Einkommen + report_header_gst_free_income: Unbesteuertes Einkommen report_header_total_untaxable_produce: Total unversteuerbares Produkt (keine Steuer) report_header_total_taxable_produce: Gesamtsteuerpflichtiges Produkt (inklusive Steuern) report_header_total_untaxable_fees: Summe nicht steuerpflichtiger Gebühren (keine Steuern) @@ -2115,11 +2220,11 @@ de_DE: business_details: "Geschäftsdetails" properties: "Eigenschaften" shipping: "Versand" - shipping_methods: "Lieferoptionen" + shipping_methods: "Lieferart" payment_methods: "Zahlungsarten" payment_method_fee: "Transaktionsgebühr" inventory_settings: "Katalogeinstellungen" - tag_rules: "Tag-Regeln" + tag_rules: "Stichwort-Regeln" shop_preferences: "Ladeneinstellungen" enterprise_fee_whole_order: Ganze Bestellung enterprise_fee_by: "%{type} Gebühr von %{role} %{enterprise_name}" @@ -2131,7 +2236,7 @@ de_DE: content_configuration_pricing_table: "(TODO: Preistabelle)" content_configuration_case_studies: "(TODO: Fallstudien)" content_configuration_detail: "(Todo: Detail)" - enterprise_name_error: "wurde bereits genommen. Wenn dies Ihr Unternehmen ist und Sie die Eigentumsrechte beanspruchen möchten oder wenn Sie mit diesem Unternehmen handeln möchten, wenden Sie sich bitte an den aktuellen Manager dieses Profils unter %{email}." + enterprise_name_error: "wurde bereits vergeben. Wenn dies Ihr Unternehmen ist und Sie die Eigentumsrechte beanspruchen möchten oder wenn Sie mit diesem Unternehmen handeln möchten, wenden Sie sich bitte an den aktuellen Manager dieses Profils unter %{email}." enterprise_owner_error: "^ %{email} darf keine weiteren Unternehmen besitzen (Limit ist %{enterprise_limit})." enterprise_role_uniqueness_error: "^ Diese Rolle ist bereits vorhanden." inventory_item_visibility_error: muss wahr oder falsch sein @@ -2145,11 +2250,11 @@ de_DE: adjustments_tax_rate_error: "^ Bitte überprüfen Sie, ob der Steuersatz für diese Anpassung korrekt ist." active_distributors_not_ready_for_checkout_message_singular: >- Das Hub %{distributor_names} ist in einem aktiven Bestellzyklus aufgeführt, - hat jedoch keine gültigen Versand- und Zahlungsmethoden. Bis Sie diese einrichten, + hat jedoch keine gültigen Versand- und Zahlungsarten. Bis Sie diese einrichten, können Kunden nicht an diesem Hub einkaufen. active_distributors_not_ready_for_checkout_message_plural: >- Die Hubs %{distributor_names} sind in einem aktiven Bestellzyklus aufgeführt, - haben jedoch keine gültigen Versand- und Zahlungsmethoden. Bis Sie diese einrichten, + haben jedoch keine gültigen Versand- und Zahlungsarten. Bis Sie diese einrichten, können Kunden nicht an diesen Hubs einkaufen. enterprise_fees_update_notice: Ihre Unternehmensgebühren wurden aktualisiert. enterprise_fees_destroy_error: "Diese Unternehmensgebühr kann nicht gelöscht werden, da sie von einer Produktverteilung referenziert wird: %{id} - %{name}." @@ -2200,22 +2305,21 @@ de_DE: invite: "Einladen" invite_title: "Laden Sie einen nicht registrierten Benutzer ein" tag_rule_help: - title: Tag-Regeln + title: Stichwort-Regeln overview: Überblick overview_text: > - Mit Tag-Regeln können Sie beschreiben, welche Elemente für welche Kunden - sichtbar sind oder nicht. Artikel können Versandmethoden, Zahlungsmethoden, - Produkte und Bestellzyklen sein. + Mit StichwortßRegeln können Sie beschreiben, welche Elemente für welche + Kunden sichtbar sind. Elemente können Versandarten, Zahlungsarten, Produkte + und Bestellzyklen sein. by_default_rules: "\"Standardmäßig ...\" Regeln" by_default_rules_text: > - Mit Standardregeln können Sie Elemente ausblenden, sodass sie standardmäßig + Mit Standardregeln können Sie Elemente verbergen, sodass sie standardmäßig nicht sichtbar sind. Dieses Verhalten kann dann durch nicht standardmäßige - Regeln für Kunden mit bestimmten Tags überschrieben werden. - customer_tagged_rules: "'Kunden getagged ...' Regeln" + Regeln für Kunden mit bestimmten Stichwörtern überschrieben werden. + customer_tagged_rules: "'Kunden mit Stichwort ...' Regeln" customer_tagged_rules_text: > - Durch das Erstellen von Regeln für ein bestimmtes Kundentag können Sie - das Standardverhalten (ob Elemente anzeigen oder ausblenden) für Kunden - mit dem angegebenen Tag überschreiben. + Durch das Erstellen von Regeln für ein bestimmtes Stichwort können Sie + die Standardregeln für bestimmte Kunden überschreiben. panels: save: SPEICHERN saved: GERETTET @@ -2247,9 +2351,8 @@ de_DE: Ihr Unternehmen wird nicht vollständig aktiviert, bis ein Paket aus den Optionen auf der linken Seite ausgewählt wird. choose_package_text2: > - Klicken Sie auf eine Option, um detailliertere Informationen zu jedem - Paket zu erhalten, und drücken Sie die rote SAVE-Taste, wenn Sie fertig - sind! + Klicken Sie auf ein Paket, um weitere Informationen zu erhalten. Klicken + Sie SPEICHERN, wenn Sie fertig sind! profile_only: Profil nur profile_only_cost: "KOSTEN: IMMER KOSTENLOS" profile_only_text1: > @@ -2318,6 +2421,9 @@ de_DE: select_rule_type: "Wählen Sie einen Regelart aus:" resend_user_email_confirmation: resend: "Erneut senden" + sending: "Erneut senden..." + done: "Erneut senden ✓" + failed: "Erneut senden fehlgeschlagen ✗" out_of_stock: reduced_stock_available: Reduzierter Bestand verfügbar out_of_stock_text: > @@ -2327,7 +2433,7 @@ de_DE: only_n_remainging: "Jetzt hat nur noch %{num} übrig." variant_overrides: inventory_products: "Katalogprodukte" - hidden_products: "Versteckte Produkte" + hidden_products: "Ausgeblendete Produkte" new_products: "Neue Produkte" reset_stock_levels: Zurücksetzen der Bestandswerte auf Standardwerte changes_to: Änderungen an @@ -2340,10 +2446,10 @@ de_DE: changing_on_hand_stock: Änderung der Lagerbestände ... stock_reset: Aktien werden auf Standardwerte zurückgesetzt. tag_rules: - show_hide_variants: 'Produktvarianten im Laden anzeigen oder verbergen' - show_hide_shipping: 'Versandarten im Shop anzeigen' - show_hide_payment: 'Zahlungsmethoden im Shop anzeigen' - show_hide_order_cycles: 'Bestellzyklen in meinem Laden anzeigen order verbergen' + show_hide_variants: 'Produktvarianten im Laden anzeigen?' + show_hide_shipping: 'Lieferarten an der Kasse anzeigen?' + show_hide_payment: 'Zahlungsarten an der Kasse anzeigen?' + show_hide_order_cycles: 'Bestellzyklen in meinem Laden anzeigen?' visible: SICHTBAR not_visible: UNSICHTBAR services: @@ -2361,7 +2467,9 @@ de_DE: Dadurch wird der Lagerbestand für alle Produkte auf Null gesetzt Unternehmen, die in der hochgeladenen Datei nicht vorhanden sind. order_cycles: + create_failure: "Fehler beim Erstellen des Bestellzyklus" update_success: 'Ihr Bestellzyklus wurde aktualisiert.' + update_failure: "Fehler beim Aktualisieren des Bestellzyklus" no_distributors: In diesem Bestellzyklus gibt es keine Distributoren. Dieser Bestellzyklus ist für Kunden erst sichtbar, wenn Sie einen hinzufügen. Möchten Sie diesen Bestellzyklus weiterhin speichern? enterprises: producer: "Erzeuger" @@ -2382,8 +2490,20 @@ de_DE: my_account: "Mein Konto" date: "Datum" time: "Zeit" + layouts: + admin: + header: + store: Geschäft admin: + product_properties: + index: + inherits_properties_checkbox_hint: "Vererben Eigenschaften von %{supplier}? (außer oben aufgehoben)" orders: + index: + capture: "Erfassung" + ship: "Liefern" + edit: "Bearbeiten" + next: "Weiter" invoice: issued_on: Ausgegeben am tax_invoice: Steuerrechnung @@ -2421,15 +2541,19 @@ de_DE: payments: source_forms: stripe: - no_payment_via_admin_backend: Das Erstellen von Stripe-basierten Zahlungen aus dem Admin-Backend ist derzeit nicht möglich + error_saving_payment: Fehler beim Speichern der Zahlung + submitting_payment: Zahlung wird gesendet ... products: new: title: 'Neues Produkt' unit_name_placeholder: 'z.B. Trauben' index: + header: + title: Massenbearbeitung von Produkten indicators: title: LADE PRODUKTE no_products: "Bisher sind keine Produkte gewählt worden. Warum fügen Sie nicht einige hinzu?" + no_results: "Entschuldigung, keine Ergebnisse stimmen überein" products_head: name: Name unit: Einheit @@ -2439,6 +2563,10 @@ de_DE: inherits_properties?: Vererbt Eigenschaften available_on: Verfügbar am av_on: "Verfüg. am" + import_date: "Importdatum" + products_variant: + variant_has_n_overrides: "Diese Variante hat %{n} Überschreibungen" + new_variant: "Neue Variante" product_name: Produktname primary_taxon_form: product_category: Produktkategorie @@ -2451,13 +2579,25 @@ de_DE: table: select_and_search: "Wählen Sie Filter und klicken Sie auf SEARCH, um auf Ihre Daten zuzugreifen." bulk_coop: - bulk_coop_supplier_report: 'Bulk Coop - Summen nach Lieferant' + bulk_coop_supplier_report: 'Massen-Kooperative - Summen nach Anbieter' bulk_coop_allocation: 'Massenkoop - Zuteilung' bulk_coop_packing_sheets: 'Massenkoop - Verpackungsblätter' bulk_coop_customer_payments: 'Massenkoop - Kundenzahlungen' + users: + email_confirmation: + confirmation_pending: "E-Mail-Bestätigung steht aus. Wir haben eine Bestätigungs-E-Mail an %{address} gesendet." variants: autocomplete: producer_name: Produzent + general_settings: + edit: + legal_settings: "Rechtliche Einstellungen" + cookies_consent_banner_toggle: "Zeigen Sie das Zustimmungsbanner für Cookies" + privacy_policy_url: "Datenschutz URL" + enterprises_require_tos: "Unternehmen müssen die AGB akzeptieren" + cookies_policy_matomo_section: "Zeigen Sie den Matomo-Abschnitt auf der Cookie-Richtlinienseite an" + cookies_policy_ga_section: "Google Analytics-Abschnitt auf der Cookie-Richtlinienseite anzeigen" + footer_tos_url: "AGB URL" checkout: payment: stripe: @@ -2471,6 +2611,8 @@ de_DE: js_format: 'JJ-MM-TT' inventory: Katalog orders: + edit: + login_to_view_order: "Bitte loggen Sie sich ein, um Ihre Bestellung anzuzeigen." bought: item: "Bereits in dieser Reihenfolge bestellt" order_mailer: @@ -2566,6 +2708,9 @@ de_DE: cards: authorised_shops: Bevollmächtigte Läden authorised_shops_popover: Dies ist die Liste der Shops, die Ihre Standardkreditkarte für eventuell vorhandene Abonnements (dh wiederkehrende Bestellungen) belasten dürfen. Ihre Kartendetails werden sicher aufbewahrt und nicht an Ladenbesitzer weitergegeben. Sie werden immer benachrichtigt, wenn Sie belastet werden. - saved_cards_popover: Dies ist die Liste der Karten, die Sie für die spätere Verwendung gespeichert haben. Ihr "Standard" wird automatisch beim Abschließen einer Bestellung ausgewählt und kann von allen Geschäften belastet werden, die Sie dazu berechtigt haben (siehe rechts). + saved_cards_popover: Dies ist die Liste der Karten, die Sie für spätere Verwendung gespeichert haben. Ihr "Standard" wird automatisch beim Abschließen einer Bestellung ausgewählt und kann von allen Geschäften belastet werden, die Sie dazu berechtigt haben (siehe rechts). + authorised_shops: + shop_name: "Laden Name" + allow_charges?: "Gebühren erlauben?" localized_number: invalid_format: hat ein ungültiges Format. Bitte gebe eine Nummer ein. diff --git a/config/locales/en.yml b/config/locales/en.yml index d3e93749d31..be7e43f7b5a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -79,6 +79,7 @@ en: user_registrations: spree_user: signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." + unknown_error: "Something went wrong while creating your account. Check your email address and try again." failure: invalid: | Invalid email or password. @@ -88,6 +89,11 @@ en: user_passwords: spree_user: updated_not_active: "Your password has been reset, but your email has not been confirmed yet." + + models: + order_cycle: + cloned_order_cycle_name: "COPY OF %{order_cycle}" + enterprise_mailer: confirmation_instructions: subject: "Please confirm the email address for %{enterprise}" @@ -95,9 +101,26 @@ en: subject: "%{enterprise} is now on %{sitename}" invite_manager: subject: "%{enterprise} has invited you to be a manager" + order_mailer: + cancel_email: + dear_customer: "Dear Customer," + instructions: "Your order has been CANCELED. Please retain this cancellation information for your records." + order_summary_canceled: "Order Summary [CANCELED]" + subject: "Cancellation of Order" + subtotal: "Subtotal: %{subtotal}" + total: "Order Total: %{total}" producer_mailer: order_cycle: subject: "Order cycle report for %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Dear Customer," + instructions: "Your order has been shipped" + shipment_summary: "Shipment Summary" + subject: "Shipment Notification" + thanks: "Thank you for your business." + track_information: "Tracking Information: %{tracking}" + track_link: "Tracking Link: %{url}" subscription_mailer: placement_summary_email: subject: A summary of recently placed subscription orders @@ -162,6 +185,7 @@ en: free_trial: "free trial" plus_tax: "plus GST" min_bill_turnover_desc: "once turnover exceeds %{mbt_amount}" + more: "More" say_no: "No" say_yes: "Yes" then: then @@ -431,6 +455,7 @@ en: main_links: Main Menu Links footer_and_external_links: Footer and External Links your_content: Your content + user_guide: User Guide enterprise_fees: index: @@ -490,6 +515,7 @@ en: conditional_blank: can't be blank if unit_type is blank no_product: did not match any products in the database not_found: not found in database + not_updatable: cannot be updated on existing products via product import blank: can't be blank products_no_permission: you do not have permission to manage products for this enterprise inventory_no_permission: you do not have permission to create inventory for this producer @@ -548,6 +574,11 @@ en: inventory_to_reset: Existing inventory items will have their stock reset to zero line: Line item_line: Item line + import_review: + not_updatable_tip: "The following fields cannot be updated via bulk import for existing products:" + fields_ignored: These fields will be ignored when the imported products are saved. + entries_table: + not_updatable: This field is not updatable via bulk import on existing products save_results: final_results: Import final results products_created: Products created @@ -589,9 +620,6 @@ en: controls: back_to_my_inventory: Back to my inventory orders: - index: - capture: "Capture" - ship: "Ship" invoice_email_sent: 'Invoice email has been sent' order_email_resent: 'Order email has been resent' bulk_management: @@ -821,6 +849,9 @@ en: search_placeholder: Search By Name manage: Manage manage_link: Settings + producer?: "Producer?" + package: "Package" + status: "Status" new_form: owner: Owner owner_tip: The primary user responsible for this enterprise. @@ -832,6 +863,14 @@ en: new: title: New Enterprise back_link: Back to enterprises list + remove_logo: + remove: "Remove Image" + removed_successfully: "Logo removed successfully" + immediate_removal_warning: "The logo will be removed immediately after you confirm." + remove_promo_image: + remove: "Remove Image" + removed_successfully: "Promo image removed successfully" + immediate_removal_warning: "The promo image will be removed immediately after you confirm." welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -864,6 +903,11 @@ en: save_reload: Save and Reload Page coordinator_fees: add: Add coordinator fee + filters: + search_by_order_cycle_name: "Search by Order Cycle name..." + involving: "Involving" + any_enterprise: "Any Enterprise" + any_schedule: "Any Schedule" form: incoming: Incoming supplier: Supplier @@ -878,7 +922,6 @@ en: delivery_details: Pickup / Delivery details debug_info: Debug information index: - involving: Involving schedule: Schedule schedules: Schedules adding_a_new_schedule: Adding A New Schedule @@ -1078,6 +1121,14 @@ en: stripe_connect_settings: resource: Stripe Connect configuration +# API +# + api: + enterprise_logo: + destroy_attachment_does_not_exist: "Logo does not exist" + enterprise_promo_image: + destroy_attachment_does_not_exist: "Promo image does not exist" + # Frontend views # # These keys are referenced relatively like `t('.message')` in @@ -1200,6 +1251,8 @@ en: footer_links_md: "Links" footer_about_url: "About URL" + user_guide_link: "User Guide Link" + name: Name first_name: First Name last_name: Last Name @@ -1299,6 +1352,7 @@ en: statistics_cookies_desc: "The following are not strictly necessary, but help to provide you with the best user experience by allowing us to analyse user behaviour, identify which features you use most, or don’t use, understand user experience issues, etc." statistics_cookies_analytics_desc_html: "To collect and analyse platform usage data, we use Google Analytics, as it was the default service connected with Spree (the e-commerce open source software that we built on) but our vision is to switch to Matomo (ex Piwik, open source analytics tool that is GDPR compliant and protects your privacy) as soon as we can." statistics_cookies_matomo_desc_html: "To collect and analyse platform usage data, we use Matomo (ex Piwik), an open source analytics tool that is GDPR compliant and protects your privacy." + statistics_cookies_matomo_optout: "Do you want to opt-out of Matomo analytics? We don’t collect any personal data, and Matomo helps us to improve our service, but we respect your choice :-)" cookie_analytics_utma_desc: "Used to distinguish users and sessions. The cookie is created when the javascript library executes and no existing __utma cookies exists. The cookie is updated every time data is sent to Google Analytics." cookie_analytics_utmt_desc: "Used to throttle request rate." cookie_analytics_utmb_desc: "Used to determine new sessions/visits. The cookie is created when the javascript library executes and no existing __utmb cookies exists. The cookie is updated every time data is sent to Google Analytics." @@ -1994,31 +2048,31 @@ See the %{link} to find out more about %{sitename}'s features and to start using you_have_no_orders_yet: "You have no orders yet" running_balance: "Running balance" outstanding_balance: "Outstanding balance" - admin_entreprise_relationships: "Enterprise Permissions" - admin_entreprise_relationships_everything: "Everything" - admin_entreprise_relationships_permits: "permits" - admin_entreprise_relationships_seach_placeholder: "Search" - admin_entreprise_relationships_button_create: "Create" - admin_entreprise_groups: "Enterprise Groups" - admin_entreprise_groups_name: "Name" - admin_entreprise_groups_owner: "Owner" - admin_entreprise_groups_on_front_page: "On front page ?" - admin_entreprise_groups_entreprise: "Enterprises" - admin_entreprise_groups_data_powertip: "The primary user responsible for this group." - admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" - admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" - admin_entreprise_groups_contact: "Contact" - admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" - admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street" - admin_entreprise_groups_contact_city: "Suburb" - admin_entreprise_groups_contact_city_placeholder: "eg. Northcote" - admin_entreprise_groups_contact_zipcode: "Postcode" - admin_entreprise_groups_contact_zipcode_placeholder: "eg. 3070" - admin_entreprise_groups_contact_state_id: "State" - admin_entreprise_groups_contact_country_id: "Country" - admin_entreprise_groups_web: "Web Resources" - admin_entreprise_groups_web_twitter: "eg. @the_prof" - admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.com" + admin_enterprise_relationships: "Enterprise Permissions" + admin_enterprise_relationships_everything: "Everything" + admin_enterprise_relationships_permits: "permits" + admin_enterprise_relationships_seach_placeholder: "Search" + admin_enterprise_relationships_button_create: "Create" + admin_enterprise_groups: "Enterprise Groups" + admin_enterprise_groups_name: "Name" + admin_enterprise_groups_owner: "Owner" + admin_enterprise_groups_on_front_page: "On front page ?" + admin_enterprise_groups_enterprise: "Enterprises" + admin_enterprise_groups_data_powertip: "The primary user responsible for this group." + admin_enterprise_groups_data_powertip_logo: "This is the logo for the group" + admin_enterprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" + admin_enterprise_groups_contact: "Contact" + admin_enterprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "eg. 123 High Street" + admin_enterprise_groups_contact_city: "Suburb" + admin_enterprise_groups_contact_city_placeholder: "eg. Northcote" + admin_enterprise_groups_contact_zipcode: "Postcode" + admin_enterprise_groups_contact_zipcode_placeholder: "eg. 3070" + admin_enterprise_groups_contact_state_id: "State" + admin_enterprise_groups_contact_country_id: "Country" + admin_enterprise_groups_web: "Web Resources" + admin_enterprise_groups_web_twitter: "eg. @the_prof" + admin_enterprise_groups_web_website_placeholder: "eg. www.truffles.com" admin_order_cycles: "Admin Order Cycles" open: "Open" close: "Close" @@ -2156,7 +2210,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_payment_totals: 'Payment Totals' report_all: 'all' report_order_cycle: "Order Cycle: " - report_entreprises: "Enterprises: " + report_enterprises: "Enterprises: " report_users: "Users: " report_tax_rates: Tax rates report_tax_types: Tax types @@ -2517,6 +2571,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using resolve: Resolve new_tag_rule_dialog: select_rule_type: "Select a rule type:" + orders: + index: + per_page: "%{results} per page" resend_user_email_confirmation: resend: "Resend" sending: "Resend..." @@ -2565,7 +2622,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' + update_failure: "Failed to update order cycle" no_distributors: There are no distributors in this order cycle. This order cycle will not be visible to customers until you add one. Would you like to continue saving this order cycle?' enterprises: producer: "Producer" @@ -2589,8 +2648,30 @@ See the %{link} to find out more about %{sitename}'s features and to start using my_account: "My account" date: "Date" time: "Time" + layouts: + admin: + header: + store: Store admin: + product_properties: + index: + inherits_properties_checkbox_hint: "Inherit properties from %{supplier}? (unless overridden above)" orders: + index: + listing_orders: "Listing Orders" + new_order: "New Order" + capture: "Capture" + ship: "Ship" + edit: "Edit" + note: "Note" + first: "First" + last: "Last" + previous: "Previous" + next: "Next" + loading: "Loading" + no_orders_found: "No Orders Found" + results_found: "%{number} Results found." + viewing: "Viewing %{start} to %{end}." invoice: issued_on: Issued on tax_invoice: TAX INVOICE @@ -2699,6 +2780,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using inventory: Inventory zipcode: Postcode orders: + edit: + login_to_view_order: "Please log in to view your order." bought: item: "Already ordered in this order cycle" shipment_states: @@ -2814,5 +2897,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using authorised_shops: Authorised Shops authorised_shops_popover: This is the list of shops which are permitted to charge your default credit card for any subscriptions (ie. repeating orders) you may have. Your card details will be kept secure and will not be shared with shop owners. You will always be notified when you are charged. saved_cards_popover: This is the list of cards you have opted to save for later use. Your 'default' will be selected automatically when you checkout an order, and can be charged by any shops you have allowed to do so (see right). + authorised_shops: + shop_name: "Shop Name" + allow_charges?: "Allow Charges?" localized_number: invalid_format: has an invalid format. Please enter a number. diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index d50003b2094..14021d13981 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -19,7 +19,7 @@ en_GB: email: taken: "There's already an account for this email. Please login or reset your password." spree/order: - no_card: There are no valid credit cards available + no_card: There are no authorised credit cards available to charge order_cycle: attributes: orders_close_at: @@ -41,11 +41,10 @@ en_GB: payment_method: not_available_to_shop: "is not available to %{shop}" invalid_type: "must be a Cash or Stripe method" + charges_not_allowed: "^Credit card charges are not allowed by this customer" + no_default_card: "^No default card available for this customer" shipping_method: not_available_to_shop: "is not available to %{shop}" - credit_card: - not_available: "is not available" - blank: "is required" devise: confirmations: send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." @@ -137,6 +136,7 @@ en_GB: free_trial: "free trial" plus_tax: "plus VAT" min_bill_turnover_desc: "once turnover exceeds %{mbt_amount}" + more: "More" say_no: "No" say_yes: "Yes" then: then @@ -160,6 +160,7 @@ en_GB: distributors: Distributors distribution: Distribution bulk_order_management: Bulk Order Management + enterprises: Enterprises enterprise_groups: Groups reports: Reports variant_overrides: Inventory @@ -310,6 +311,42 @@ en_GB: 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 Monthly Bill (Incl. Tax)" total_monthly_bill_incl_tax_tip: "The example total monthly bill with tax included, given the settings and the turnover provided." + cache_settings: + show: + title: Caching + distributor: Distributor + order_cycle: Order Cycle + status: Status + diff: Diff + error: Error + invoice_settings: + edit: + title: Invoice Settings + invoice_style2?: Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax) + enable_receipt_printing?: Show options for printing receipts using thermal printers in order dropdown? + stripe_connect_settings: + edit: + title: "Stripe Connect" + settings: "Settings" + stripe_connect_enabled: Enable shops to accept payments using Stripe Connect? + no_api_key_msg: No Stripe account exists for this enterprise. + configuration_explanation_html: For detailed instructions on configuring the Stripe Connect integration, please consult this guide. + status: Status + ok: Ok + instance_secret_key: Instance Secret Key + account_id: Account ID + business_name: Business Name + charges_enabled: Charges Enabled + charges_enabled_warning: "Warning: Charges are not enabled for your account" + auth_fail_error: The API key you provided is invalid + empty_api_key_error_html: No Stripe API key has been provided. To set your API key, please follow these instructions + matomo_settings: + edit: + title: "Matomo Settings" + matomo_url: "Matomo URL" + matomo_site_id: "Matomo Site ID" + info_html: "Matomo is a Web and Mobile Analytics. You can either host Matomo on-premises or use a cloud-hosted service. See matomo.org for more information." + config_instructions_html: "Here you can configure the OFN Matomo integration. The Matomo URL below should point to the Matomo instance where the user tracking information will be sent to; if it is left empty, Matomo user tracking will be disabled. The Site ID field is not mandatory but useful if you are tracking more than one website on a single Matomo instance; it can be found on the Matomo instance console." customers: index: add_customer: "Add Customer" @@ -332,16 +369,9 @@ en_GB: update_address: 'Update Address' confirm_delete: 'Sure to delete?' search_by_email: "Search by email/code..." + guest_label: 'Guest checkout' destroy: has_associated_orders: 'Delete failed: customer has associated orders with his shop' - cache_settings: - show: - title: Caching - distributor: Distributor - order_cycle: Order Cycle - status: Status - diff: Diff - error: Error contents: edit: title: Content @@ -350,8 +380,10 @@ en_GB: producer_signup_page: Producer signup page hub_signup_page: Hub signup page group_signup_page: Group signup page + main_links: Main Menu Links footer_and_external_links: Footer and External Links your_content: Your content + user_guide: User Guide enterprise_fees: index: title: Enterprise Fees @@ -414,20 +446,29 @@ en_GB: index: select_file: Select a spreadsheet to upload spreadsheet: Spreadsheet - import_into: "Import into:" + choose_import_type: Select import type + import_into: Import type product_list: Product list inventories: Inventories import: Import upload: Upload + csv_templates: CSV Templates + product_list_template: Download Product List template + inventory_template: Download Inventory template + category_values: Available Category Values + product_categories: Product Categories + tax_categories: Tax Categories + shipping_categories: Shipping Categories import: review: Review - proceed: Proceed + import: Import save: Save results: Results save_imported: Save imported products no_valid_entries: No valid entries found none_to_save: There are no entries that can be saved - some_invalid_entries: Imported file contains some invalid entries + some_invalid_entries: Imported file contains invalid entries + fix_before_import: Please fix these errors and try importing the file again save_valid?: Save valid entries for now and discard the others? no_errors: No errors detected! save_all_imported?: Save all imported products? @@ -436,7 +477,8 @@ en_GB: not_found: enterprise could not be found in database no_name: No name blank_supplier: some products have blank supplier name - reset_absent?: Reset absent products? + reset_absent?: Reset absent products + reset_absent_tip: Set stock to zero for all exiting products not present in the file overwrite_all: Overwrite all overwrite_empty: Overwrite if empty default_stock: Set stock level @@ -454,7 +496,7 @@ en_GB: inventory_to_reset: Existing inventory items will have their stock reset to zero line: Line item_line: Item line - save: + save_results: final_results: Import final results products_created: Products created products_updated: Products updated @@ -465,8 +507,9 @@ en_GB: all_saved: "All items saved successfully" some_saved: "items saved successfully" save_errors: Save errors - view_products: View Products - view_inventory: View Inventory + import_again: Upload Another File + view_products: Go To Products Page + view_inventory: Go To Inventory Page variant_overrides: loading_flash: loading_inventory: LOADING INVENTORY @@ -684,7 +727,7 @@ en_GB: email_confirmed: "Email confirmed" email_not_confirmed: "Email not confirmed" actions: - edit_profile: Edit Profile + edit_profile: Settings properties: Properties payment_methods: Payment Methods payment_methods_tip: This enterprise has no payment methods @@ -724,13 +767,17 @@ en_GB: no_enterprises_found: No enterprises found. search_placeholder: Search By Name manage: Manage + manage_link: Settings + producer?: "Producer?" + package: "Package" + status: "Status" new_form: owner: Owner owner_tip: The primary user responsible for this enterprise. i_am_producer: I am a Producer contact_name: Contact Name edit: - editing: 'Editing:' + editing: 'Settings:' back_link: Back to enterprises list new: title: New Enterprise @@ -767,6 +814,8 @@ en_GB: save_reload: Save and Reload Page coordinator_fees: add: Add coordinator fee + filters: + involving: "Involving" form: incoming: Incoming supplier: Supplier @@ -780,7 +829,6 @@ en_GB: delivery_details: Pickup / Delivery details debug_info: Debug information index: - involving: Involving schedule: Schedule schedules: Schedules adding_a_new_schedule: Adding A New Schedule @@ -812,6 +860,7 @@ en_GB: products: Products fees: Fees destroy_errors: + orders_present: That order cycle has been selected by a customer and cannot be deleted. To prevent customers from accessing it, please close it instead. schedule_present: That order cycle is linked to a schedule and cannot be deleted. Please unlink or delete the schedule first. bulk_update: no_data: Hm, something went wrong. No order cycle data found. @@ -830,11 +879,6 @@ en_GB: shared: user_guide_link: user_guide: User Guide - invoice_settings: - edit: - title: Invoice Settings - invoice_style2?: Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax) - enable_receipt_printing?: Show options for printing receipts using thermal printers in order dropdown? overview: enterprises_header: ofn_with_tip: Enterprises are Producers and/or Hubs and are the basic unit of organisation within the Open Food Network. @@ -907,6 +951,7 @@ en_GB: please_select_a_shop: Please select a shop edit_subscription: Edit Subscription pause_subscription: Pause Subscription + unpause_subscription: Unpause Subscription cancel_subscription: Cancel Subscription setup_explanation: just_a_few_more_steps: 'Just a few more steps before you can begin:' @@ -924,14 +969,21 @@ en_GB: reload_this_page: reload this page steps: details: 1. Basic Details + address: 2. Address products: 3. Add Products review: 4. Review & Save + subscription_line_items: + this_is_an_estimate: | + The displayed prices are only an estimate and calculated at the time the subscription is changed. + If you change prices or fees, orders will be updated, but the subscription will still display the old values. details: details: Details invalid_error: Oops! Please fill in all of the required fields... allowed_payment_method_types_tip: Only Cash and Stripe payment methods may be used at the moment credit_card: Credit Card - no_cards_available: No cards available + charges_not_allowed: Charges are not allowed by this customer + no_default_card: Customer has no cards available to charge + card_ok: Customer has a card available to charge loading_flash: loading: LOADING SUBSCRIPTIONS review: @@ -961,22 +1013,6 @@ en_GB: schedules: destroy: associated_subscriptions_error: This schedule cannot be deleted because it has associated subscriptions - stripe_connect_settings: - edit: - title: "Stripe Connect" - settings: "Settings" - stripe_connect_enabled: Enable shops to accept payments using Stripe Connect? - no_api_key_msg: No Stripe account exists for this enterprise. - configuration_explanation_html: For detailed instructions on configuring the Stripe Connect integration, please consult this guide. - status: Status - ok: Ok - instance_secret_key: Instance Secret Key - account_id: Account ID - business_name: Business Name - charges_enabled: Charges Enabled - charges_enabled_warning: "Warning: Charges are not enabled for your account" - auth_fail_error: The API key you provided is invalid - empty_api_key_error_html: No Stripe API key has been provided. To set your API key, please follow these instructions controllers: enterprises: stripe_connect_cancelled: "Connection to Stripe has been cancelled" @@ -1001,6 +1037,33 @@ en_GB: register_call: selling_on_ofn: "Interested in selling through the Open Food Network?" register: "Register here" + footer: + footer_global_headline: "OFN Global" + footer_global_home: "Home" + footer_global_news: "News" + footer_global_about: "About" + footer_global_contact: "Contact" + footer_sites_headline: "OFN Sites" + footer_sites_developer: "Developer" + footer_sites_community: "Community" + footer_sites_userguide: "User Guide" + footer_secure: "Secure and trusted." + footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." + footer_contact_headline: "Keep in touch" + footer_contact_email: "Email us" + footer_nav_headline: "Navigate" + footer_join_headline: "Join us" + footer_join_body: "Create a listing, shop or group directory on the Open Food Network." + footer_join_cta: "Tell me more!" + footer_legal_call: "Read our" + footer_legal_tos: "Terms and conditions" + footer_legal_visit: "Find us on" + footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." + footer_data_text_with_privacy_policy_html: "We take good care of your data. See our %{privacy_policy} and %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "We take good care of your data. See our %{cookies_policy}" + footer_data_privacy_policy: "privacy policy" + footer_data_cookies_policy: "cookies policy" + footer_skylight_dashboard_html: Performance data is available on %{dashboard}. shop: messages: login: "login" @@ -1036,6 +1099,18 @@ en_GB: ticket_column_item: "Item" ticket_column_unit_price: "Unit Price" ticket_column_total_price: "Total Price" + menu_1_title: "Shops" + menu_1_url: "/shops" + menu_2_title: "Map" + menu_2_url: "/map" + menu_3_title: "Services" + menu_3_url: "https://about.openfoodnetwork.org.uk/services" + menu_5_title: "About" + menu_5_url: "https://about.openfoodnetwork.org.uk" + menu_6_title: "Blog" + menu_6_url: "https://about.openfoodnetwork.org.uk/blog" + menu_7_title: "Support" + menu_7_url: "https://about.openfoodnetwork.org.uk/support" logo: "Logo (640x130)" logo_mobile: "Mobile logo (75x26)" logo_mobile_svg: "Mobile logo (SVG)" @@ -1051,7 +1126,6 @@ en_GB: footer_email: "Email" footer_links_md: "Links" footer_about_url: "About URL" - footer_tos_url: "Terms of Service URL" name: Name first_name: First Name last_name: Last Name @@ -1125,31 +1199,50 @@ en_GB: ie_warning_firefox: Download Firefox ie_warning_ie: Upgrade Internet Explorer ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)" - footer_global_headline: "OFN Global" - footer_global_home: "Home" - footer_global_news: "News" - footer_global_about: "About" - footer_global_contact: "Contact" - footer_sites_headline: "OFN Sites" - footer_sites_developer: "Developer" - footer_sites_community: "Community" - footer_sites_userguide: "User Guide" - footer_secure: "Secure and trusted." - footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." - footer_contact_headline: "Keep in touch" - footer_contact_email: "Email us" - footer_nav_headline: "Navigate" - footer_join_headline: "Join us" - footer_join_body: "Create a listing, shop or group directory on the Open Food Network." - footer_join_cta: "Tell me more!" - footer_legal_call: "Read our" - footer_legal_tos: "Terms and conditions" - footer_legal_visit: "Find us on" - footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." - footer_skylight_dashboard_html: Performance data is available on %{dashboard}. + legal: + cookies_policy: + header: "How We Use Cookies" + desc_part_1: "Cookies are very small text files that are stored on your computer when you visit some websites." + desc_part_2: "In OFN we are fully respectful of your privacy. We use only the cookies that are necessary for delivering you the service of selling/buying food online. We don’t sell any of your data. We might in the future propose you to share some of your data to build new commons services that could be useful for the ecosystem (like logistics services for short food systems) but we are not yet there, and we won’t do it without your authorization :-)" + desc_part_3: "We use cookies mainly to remember who you are if you 'log in' to the service, or to be able to remember the items you put in your cart even if you are not logged in. If you keep navigating on the website without clicking on “Accept cookies”, we assume you are giving us consent to store the cookies that are essential for the functioning of the website. Here is the list of cookies we use!" + essential_cookies: "Essential Cookies" + essential_cookies_desc: "The following cookies are strictly necessary for the operation of our website." + essential_cookies_note: "Most cookies only contain a unique identifier, but no other data, so your email address and password for instance are never contained in them and never exposed." + cookie_domain: "Set By:" + cookie_session_desc: "Used to allow the website to remember users between page visits, for example, remember items in your cart." + cookie_consent_desc: "Used to maintain status of user consent to store cookies" + cookie_remember_me_desc: "Used if the user has requested the website to remember him. This cookie is automatically deleted after 12 days. If as a user you want that cookie to be deleted, you only need to logout. If you don’t want that cookie to be installed on your computer you shouldn’t check the “remember me” checkbox when logging in." + cookie_openstreemap_desc: "Used by our friendly open source mapping provider (OpenStreetMap) to ensure that it does not receive too many requests during a given time period, to prevent abuse of their services." + cookie_stripe_desc: "Data collected by our payment processor Stripe for fraud detection https://stripe.com/cookies-policy/legal. Not all shops use Stripe as a payment method but it is a good practice to prevent fraud to apply it to all pages. Stripe probably build a picture of which of our pages usually interact with their API and then flag anything unusual. So setting the Stripe cookie has a broader function than simply the provision of a payment method to a user. Removing it could affect the security of the service itself. You can learn more about Stripe and read its privacy policy at https://stripe.com/privacy." + statistics_cookies: "Statistics Cookies" + statistics_cookies_desc: "The following are not strictly necessary, but help to provide you with the best user experience by allowing us to analyse user behaviour, identify which features you use most, or don’t use, understand user experience issues, etc." + statistics_cookies_analytics_desc_html: "To collect and analyse platform usage data, we use Google Analytics." + statistics_cookies_matomo_desc_html: "To collect and analyse platform usage data, we use Matomo (ex Piwik), an open source analytics tool that is GDPR compliant and protects your privacy." + cookie_analytics_utma_desc: "Used to distinguish users and sessions. The cookie is created when the javascript library executes and no existing __utma cookies exists. The cookie is updated every time data is sent to Google Analytics." + cookie_analytics_utmt_desc: "Used to throttle request rate." + cookie_analytics_utmb_desc: "Used to determine new sessions/visits. The cookie is created when the javascript library executes and no existing __utmb cookies exists. The cookie is updated every time data is sent to Google Analytics." + cookie_analytics_utmc_desc: "Not used in ga.js. Set for interoperability with urchin.js. Historically, this cookie operated in conjunction with the __utmb cookie to determine whether the user was in a new session/visit." + cookie_analytics_utmz_desc: "Stores the traffic source or campaign that explains how the user reached your site. The cookie is created when the javascript library executes and is updated every time data is sent to Google Analytics." + cookie_matomo_basics_desc: "Matomo first party cookies to collect statistics." + cookie_matomo_heatmap_desc: "Matomo Heatmap & Session Recording cookie." + cookie_matomo_ignore_desc: "Cookie used to exclude user from being tracked." + disabling_cookies_header: "Warning on disabling cookies" + disabling_cookies_desc: "As a user you can always allow, block or delete Open Food Network’s or any other website cookies whenever you want to through your browser’s setting control. Each browser has a different operative. Here are the links:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" + disabling_cookies_note: "But be aware that if you delete or modify the essential cookies used by Open Food Network, the website won’t work, you will not be able to add anything to your cart neither to checkout for instance." + cookies_banner: + cookies_usage: "This site uses cookies in order to make your navigation frictionless and secure, and to help us understand how you use it in order to improve the features we offer." + cookies_definition: "Cookies are very small text files that are stored on your computer when you visit some websites." + cookies_desc: "We use only the cookies that are necessary for delivering you the service of selling/buying food online. We don’t sell any of your data. We use cookies mainly to remember who you are if you ‘log in’ to the service, or to be able to remember the items you put in your cart even if you are not logged in. If you keep navigating on the website without clicking on “Accept cookies”, we assume you are giving us consent to store the cookies that are essential for the functioning of the website." + cookies_policy_link_desc: "If you want to learn more, check our" + cookies_policy_link: "cookies policy" + cookies_accept_button: "Accept Cookies" home_shop: Shop Now brandstory_headline: "Re-imagining Local Food" - brandstory_intro: "Online tools for buying, selling & distributing local food" + brandstory_intro: "A learning community & tools to support local food systems to thrive" brandstory_part1: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world." brandstory_part2: "Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day." brandstory_part3: "So we build an online marketplace that levels the playing field. It’s transparent, so it creates real relationships. It’s open source, so it’s owned by everyone. It scales to regions and nations, so people start versions across the world." @@ -1236,6 +1329,7 @@ en_GB: email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile." email_confirmation_link_label: "Confirm this email address »" email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise. See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store." + email_confirmation_notice_unexpected: "You received this message because you signed up on %{sitename}, or were invited to sign up by someone you probably know. If you don't understand why you are receiving this email, please write to %{contact}." email_social: "Connect with Us:" email_contact: "Email us:" email_signoff: "Cheers," @@ -1266,6 +1360,7 @@ en_GB: email_so_edit_true_html: "You can make changes until orders close on %{orders_close_at}." email_so_edit_false_html: "You can view details of this order at any time." email_so_contact_distributor_html: "If you have any questions you can contact %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "This order was automatically created for you. You can make changes until orders close on %{orders_close_at} by contacting %{distributor} via %{email}." email_so_confirmation_intro_html: "Your order with %{distributor} is now confirmed" email_so_confirmation_explainer_html: "This order was automatically placed for you, and it has now been finalised." email_so_confirmation_details_html: "Here's everything you need to know about your order from %{distributor}:" @@ -1527,6 +1622,7 @@ en_GB: error_number: "must be number" error_email: "must be email address" error_not_found_in_database: "%{name} not found in database" + error_not_primary_producer: "%{name} is not enabled as a producer" error_no_permission_for_enterprise: "\"%{name}\": you do not have permission to manage products for this enterprise" item_handling_fees: "Item Handling Fees (included in item totals)" january: "January" @@ -1558,6 +1654,17 @@ en_GB: reset_password: "Reset password" who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" update_and_recalculate_fees: "Update And Recalculate Fees" + registration: + steps: + type: + headline: "Last step to add %{enterprise}!" + question: "Are you a producer?" + yes_producer: "Yes, I'm a producer" + no_producer: "No, I'm not a producer" + producer_field_error: "Please choose one. Are you are producer?" + yes_producer_help: "Producers make tasty things to eat and/or drink. You're a producer if you grow, rear, brew, bake, ferment ... etc." + no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." + create_profile: "Create profile" enterprise: registration: modal: @@ -1596,13 +1703,6 @@ en_GB: phone_field_placeholder: 'eg. 07123123123' type: title: 'Type' - headline: "Last step to add %{enterprise}!" - question: "Are you a producer?" - yes_producer: "Yes, I'm a producer" - no_producer: "No, I'm not a producer" - producer_field_error: "Please choose one. Are you are producer?" - yes_producer_help: "Producers make tasty things to eat and/or drink. You're a producer if you grow, rear, brew, bake, ferment ... etc." - no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." about: title: 'About' images: @@ -1641,6 +1741,7 @@ en_GB: enterprise_about_headline: "Nice one!" enterprise_about_message: "Now let's flesh out the details about" enterprise_success: "Success! %{enterprise} added to the Open Food Network " + enterprise_registration_exit_message: "If you exit this wizard at any stage, you can continue to create your profile by going to the admin interface." enterprise_description: "Short Description" enterprise_description_placeholder: "A short sentence describing your enterprise" enterprise_long_desc: "Long Description" @@ -1690,7 +1791,6 @@ en_GB: registration_type_error: "Please choose one. Are you are producer?" registration_type_producer_help: "Producers make things to eat and/or drink. You're a producer if you might grow it, raise it, brew it, bake it or ferment it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." - create_profile: "Create profile" registration_images_headline: "Thanks!" registration_images_description: "Let's upload some pictures so your profile looks great! :)" registration_detail_headline: "Let's get started" @@ -1749,31 +1849,31 @@ en_GB: you_have_no_orders_yet: "You have no orders yet" running_balance: "Running balance" outstanding_balance: "Outstanding balance" - admin_entreprise_relationships: "Enterprise Relationships" - admin_entreprise_relationships_everything: "Everything" - admin_entreprise_relationships_permits: "permits" - admin_entreprise_relationships_seach_placeholder: "Search" - admin_entreprise_relationships_button_create: "Create" - admin_entreprise_groups: "Enterprise Groups" - admin_entreprise_groups_name: "Name" - admin_entreprise_groups_owner: "Owner" - admin_entreprise_groups_on_front_page: "On front page?" - admin_entreprise_groups_entreprise: "Enterprises" - admin_entreprise_groups_data_powertip: "The primary user responsible for this group." - admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" - admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" - admin_entreprise_groups_contact: "Contact" - admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" - admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street" - admin_entreprise_groups_contact_city: "Suburb" - admin_entreprise_groups_contact_city_placeholder: "eg. Camden" - admin_entreprise_groups_contact_zipcode: "Postcode" - admin_entreprise_groups_contact_zipcode_placeholder: "eg. NW1 0AA" - admin_entreprise_groups_contact_state_id: "County" - admin_entreprise_groups_contact_country_id: "Country" - admin_entreprise_groups_web: "Web Resources" - admin_entreprise_groups_web_twitter: "eg. @openfoodnetuk" - admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.co.uk" + admin_enterprise_relationships: "Enterprise Permissions" + admin_enterprise_relationships_everything: "Everything" + admin_enterprise_relationships_permits: "permits" + admin_enterprise_relationships_seach_placeholder: "Search" + admin_enterprise_relationships_button_create: "Create" + admin_enterprise_groups: "Enterprise Groups" + admin_enterprise_groups_name: "Name" + admin_enterprise_groups_owner: "Owner" + admin_enterprise_groups_on_front_page: "On front page?" + admin_enterprise_groups_enterprise: "Enterprises" + admin_enterprise_groups_data_powertip: "The primary user responsible for this group." + admin_enterprise_groups_data_powertip_logo: "This is the logo for the group" + admin_enterprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" + admin_enterprise_groups_contact: "Contact" + admin_enterprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "eg. 123 High Street" + admin_enterprise_groups_contact_city: "Suburb" + admin_enterprise_groups_contact_city_placeholder: "eg. Newcastle" + admin_enterprise_groups_contact_zipcode: "Postcode" + admin_enterprise_groups_contact_zipcode_placeholder: "eg. 3070" + admin_enterprise_groups_contact_state_id: "County" + admin_enterprise_groups_contact_country_id: "Country" + admin_enterprise_groups_web: "Web Resources" + admin_enterprise_groups_web_twitter: "eg. @the_prof" + admin_enterprise_groups_web_website_placeholder: "eg. www.truffles.co.uk" admin_order_cycles: "Admin Order Cycles" open: "Open" close: "Close" @@ -1873,11 +1973,8 @@ en_GB: edit_profile_details_etc: "Change your profile description, images, etc." order_cycle: "Order Cycle" order_cycles: "Order Cycles" - enterprises: "Enterprises" - enterprise_relationships: "Enterprise relationships" + enterprise_relationships: "Enterprise permissions" remove_tax: "Remove tax" - enterprise_terms_of_service: "Enterprise Terms of Service" - enterprises_require_tos: "Enterprises must accept Terms of Service" enterprise_tos_link: "Enterprise Terms of Service link" enterprise_tos_message: "We want to work with people that share our aims and values. As such we ask new enterprises to agree to our " enterprise_tos_link_text: "Terms of Service." @@ -1912,7 +2009,7 @@ en_GB: report_payment_totals: 'Payment Totals' report_all: 'all' report_order_cycle: "Order Cycle:" - report_entreprises: "Enterprises:" + report_enterprises: "Enterprises:" report_users: "Users:" report_tax_rates: Tax rates report_tax_types: Tax types @@ -2277,6 +2374,11 @@ en_GB: resolve: Resolve new_tag_rule_dialog: select_rule_type: "Select a rule type:" + resend_user_email_confirmation: + resend: "Resend" + sending: "Resend..." + done: "Resend done ✓" + failed: "Resend failed ✗" out_of_stock: reduced_stock_available: Reduced stock available out_of_stock_text: > @@ -2380,7 +2482,8 @@ en_GB: payments: source_forms: stripe: - no_payment_via_admin_backend: Creating Stripe-based payments from the admin backend is not possible at this time + error_saving_payment: Error saving payment + submitting_payment: Submitting payment... products: new: title: 'New Product' @@ -2421,12 +2524,21 @@ en_GB: bulk_coop_allocation: 'Bulk Co-op - Allocation' bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets' bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' - shared: - configuration_menu: - stripe_connect: Stripe Connect + users: + email_confirmation: + confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}." variants: autocomplete: producer_name: Producer + general_settings: + edit: + legal_settings: "Legal Settings" + cookies_consent_banner_toggle: "Display cookies consent banner" + privacy_policy_url: "Privacy Policy URL" + enterprises_require_tos: "Enterprises must accept Terms of Service" + cookies_policy_matomo_section: "Display Matomo section on cookies policy page" + cookies_policy_ga_section: "Display Google Analytics section on cookies policy page" + footer_tos_url: "Terms of Service URL" checkout: payment: stripe: @@ -2494,6 +2606,8 @@ en_GB: issue_text: | If the above URL does not work try copying and pasting it into your browser. If you continue to have problems please feel free to contact us. + confirmation_instructions: + subject: Please confirm your OFN account weight: Weight (per kg) zipcode: Postcode users: @@ -2505,6 +2619,7 @@ en_GB: cards: Credit Cards transactions: Transactions settings: Account Settings + unconfirmed_email: "Pending email confirmation for: %{unconfirmed_email}. Your email address will be updated once the new email is confirmed." orders: open_orders: Open Orders past_orders: Past Orders @@ -2528,5 +2643,12 @@ en_GB: total: Total paid?: Paid? view: View + saved_cards: + default?: Default? + delete?: Delete? + cards: + authorised_shops: Authorised Shops + authorised_shops_popover: This is the list of shops which are permitted to charge your default credit card for any subscriptions (ie. repeating orders) you may have. Your card details will be kept secure and will not be shared with shop owners. You will always be notified when you are charged. + saved_cards_popover: This is the list of cards you have opted to save for later use. Your 'default' will be selected automatically when you checkout an order, and can be charged by any shops you have allowed to do so (see right). localized_number: invalid_format: has an invalid format. Please enter a number. diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index 0da03df1c60..d1f41a8849e 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -19,7 +19,7 @@ en_US: email: taken: "There's already an account registered for this email. Please login to reset your password." spree/order: - no_card: There are no valid credit cards available + no_card: There are no authorized credit cards available to charge order_cycle: attributes: orders_close_at: @@ -41,15 +41,14 @@ en_US: payment_method: not_available_to_shop: "is not available to %{shop}" invalid_type: "must be a Cash or Stripe method" + charges_not_allowed: "^Credit card charges are not allowed by this customer" + no_default_card: "^No default card available for this customer" shipping_method: not_available_to_shop: "is not available to %{shop}" - credit_card: - not_available: "is not available" - blank: "is required" devise: confirmations: send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." - failed_to_send: "An error occurred whilst sending your confirmation email." + failed_to_send: "An error occurred while sending your confirmation email." resend_confirmation_email: "Resend confirmation email." confirmed: "Thanks for confirming your email! You can now log in." not_confirmed: "Your email address could not be confirmed. Perhaps you have already completed this step?" @@ -61,6 +60,13 @@ en_US: Invalid email or password. Were you a guest last time? Perhaps you need to create an account or reset your password. unconfirmed: "You have to confirm your account before continuing." + already_registered: "This email address is already registered. Please log in to continue, or go back and use another email address." + user_passwords: + spree_user: + updated_not_active: "Your password has been reset, but your email has not been confirmed yet." + models: + order_cycle: + cloned_order_cycle_name: "COPY OF %{order_cycle}" enterprise_mailer: confirmation_instructions: subject: "Please confirm the email address for %{enterprise}" @@ -68,9 +74,26 @@ en_US: subject: "%{enterprise}is now on %{sitename}" invite_manager: subject: "%{enterprise} has invited you to be a manager" + order_mailer: + cancel_email: + dear_customer: "Dear Customer," + instructions: "Your order has been CANCELED. Please retain this cancellation information for your records." + order_summary_canceled: "Order Summary [CANCELED]" + subject: "Cancellation of Order" + subtotal: "Subtotal: %{subtotal}" + total: "Order Total: %{total}" producer_mailer: order_cycle: subject: "Order cycle report for %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Dear Customer," + instructions: "Your order has been shipped" + shipment_summary: "Shipment Summary" + subject: "Shipment Notification" + thanks: "Thank you for your business." + track_information: "Tracking Information: %{tracking}" + track_link: "Tracking Link: %{url}" subscription_mailer: placement_summary_email: subject: A summary of recently placed subscription orders @@ -133,6 +156,7 @@ en_US: free_trial: "free trial" plus_tax: "plus tax" min_bill_turnover_desc: "once turnover exceeds %{mbt_amount}" + more: "More" say_no: "No" say_yes: "Yes" then: then @@ -156,6 +180,7 @@ en_US: distributors: Distributors distribution: Distribution bulk_order_management: Bulk Order Management + enterprises: Enterprises enterprise_groups: Groups reports: Reports variant_overrides: Inventory @@ -233,6 +258,9 @@ en_US: form_invalid: "Form contains missing or invalid fields" clear_filters: Clear Filters clear: Clear + save: Save + cancel: Cancel + back: Back show_more: Show more show_n_more: Show %{num} more choose: "Choose..." @@ -303,6 +331,42 @@ en_US: 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 Monthly Bill (Incl. Tax)" total_monthly_bill_incl_tax_tip: "The example total monthly bill with tax included, given the settings and the turnover provided." + cache_settings: + show: + title: Caching + distributor: Distributor + order_cycle: Order Cycle + status: Status + diff: Diff + error: Error + invoice_settings: + edit: + title: Invoice Settings + invoice_style2?: Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax) + enable_receipt_printing?: Show options for printing receipts using thermal printers in order dropdown? + stripe_connect_settings: + edit: + title: "Stripe Connect" + settings: "Settings" + stripe_connect_enabled: Enable shops to accept payments using Stripe Connect? + no_api_key_msg: No Stripe account exists for this enterprise. + configuration_explanation_html: For detailed instructions on configuring the Stripe Connect integration, please consult this guide. + status: Status + ok: Ok + instance_secret_key: Instance Secret Key + account_id: Account ID + business_name: Business Name + charges_enabled: Charges Enabled + charges_enabled_warning: "Warning: Charges are not enabled for your account" + auth_fail_error: The API key you provided is invalid + empty_api_key_error_html: No Stripe API key has been provided. To set your API key, please follow these instructions + matomo_settings: + edit: + title: "Matomo Settings" + matomo_url: "Matomo URL" + matomo_site_id: "Matomo Site ID" + info_html: "Matomo is a Web and Mobile Analytics. You can either host Matomo locally or use a cloud-hosted service. See matomo.org for more information." + config_instructions_html: "Here you can configure the OFN Matomo integration. The Matomo URL below should point to the Matomo instance where the user tracking information will be sent to; if it is left empty, Matomo user tracking will be disabled. The Site ID field is not mandatory but useful if you are tracking more than one website on a single Matomo instance; it can be found on the Matomo instance console." customers: index: add_customer: "Add Customer" @@ -325,16 +389,9 @@ en_US: update_address: 'Update Address' confirm_delete: 'Sure to delete?' search_by_email: "Search by email/code..." + guest_label: 'Guest checkout' destroy: has_associated_orders: 'Delete failed: customer has associated orders with his shop' - cache_settings: - show: - title: Caching - distributor: Distributor - order_cycle: Order Cycle - status: Status - diff: Diff - error: Error contents: edit: title: Content @@ -343,8 +400,10 @@ en_US: producer_signup_page: Producer signup page hub_signup_page: Hub signup page group_signup_page: Group signup page + main_links: Main Menu Links footer_and_external_links: Footer and External Links your_content: Your content + user_guide: User Guide enterprise_fees: index: title: Enterprise Fees @@ -372,6 +431,7 @@ en_US: inherits_properties?: Inherits Properties? available_on: Available On av_on: "Av. On" + import_date: Imported upload_an_image: Upload an image product_search_keywords: Product Search Keywords product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword. @@ -386,6 +446,88 @@ en_US: product_distributions: "Product Distributions" group_buy_options: "Group Buy Options" back_to_products_list: "Back to products list" + product_import: + title: Product Import + file_not_found: File not found or could not be opened + no_data: No data found in spreadsheet + confirm_reset: "This will set stock level to zero on all products for this \n enterprise that are not present in the uploaded file" + model: + no_file: "error: no file uploaded" + could_not_process: "could not process file: invalid filetype" + incorrect_value: incorrect value + conditional_blank: can't be blank if unit_type is blank + no_product: did not match any products in the database + not_found: not found in database + blank: can't be blank + products_no_permission: you do not have permission to manage products for this enterprise + inventory_no_permission: you do not have permission to create inventory for this producer + none_saved: did not save any products successfully + line: Line + index: + select_file: Select a spreadsheet to upload + spreadsheet: Spreadsheet + choose_import_type: Select import type + import_into: Import type + product_list: Product list + inventories: Inventories + import: Import + upload: Upload + csv_templates: CSV Templates + product_list_template: Download Product List template + inventory_template: Download Inventory template + category_values: Available Category Values + product_categories: Product Categories + tax_categories: Tax Categories + shipping_categories: Shipping Categories + import: + review: Review + import: Import + save: Save + results: Results + save_imported: Save imported products + no_valid_entries: No valid entries found + none_to_save: There are no entries that can be saved + some_invalid_entries: Imported file contains invalid entries + fix_before_import: Please fix these errors and try importing the file again + save_valid?: Save valid entries for now and discard the others? + no_errors: No errors detected! + save_all_imported?: Save all imported products? + options_and_defaults: Import options and defaults + no_permission: you do not have permission to manage this enterprise + not_found: enterprise could not be found in database + no_name: No name + blank_supplier: some products have blank supplier name + reset_absent?: Reset absent products + overwrite_all: Overwrite all + overwrite_empty: Overwrite if empty + default_stock: Set stock level + default_tax_cat: Set tax category + default_shipping_cat: Set shipping category + default_available_date: Set available date + validation_overview: Import validation overview + entries_with_errors: Items contain errors and will not be imported + products_to_create: Products will be created + products_to_update: Products will be updated + inventory_to_create: Inventory items will be created + inventory_to_update: Inventory items will be updated + products_to_reset: Existing products will have their stock reset to zero + inventory_to_reset: Existing inventory items will have their stock reset to zero + line: Line + item_line: Item line + save_results: + final_results: Import final results + products_created: Products created + products_updated: Products updated + inventory_created: Inventory items created + inventory_updated: Inventory items updated + products_reset: Products had stock level reset to zero + inventory_reset: Inventory items had stock level reset to zero + all_saved: "All items saved successfully" + some_saved: "items saved successfully" + save_errors: Save errors + import_again: Upload Another File + view_products: Go To Products Page + view_inventory: Go To Inventory Page variant_overrides: loading_flash: loading_inventory: LOADING INVENTORY @@ -396,6 +538,7 @@ en_US: inherit?: Inherit? add: Add hide: Hide + import_date: Imported select_a_shop: Select A Shop review_now: Review Now new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. @@ -602,7 +745,7 @@ en_US: email_confirmed: "Email confirmed" email_not_confirmed: "Email not confirmed" actions: - edit_profile: Edit Profile + edit_profile: Settings properties: Properties payment_methods: Payment Methods payment_methods_tip: This enterprise has no payment methods @@ -642,22 +785,34 @@ en_US: no_enterprises_found: No enterprises found. search_placeholder: Search By Name manage: Manage + manage_link: Settings + producer?: "Producer?" + package: "Package" + status: "Status" new_form: owner: Owner owner_tip: The primary user responsible for this enterprise. i_am_producer: I am a Producer contact_name: Contact Name edit: - editing: 'Editing:' + editing: 'Settings:' back_link: Back to enterprises list new: title: New Enterprise back_link: Back to enterprises list + remove_logo: + remove: "Remove Image" + removed_successfully: "Logo removed successfully" + immediate_removal_warning: "The logo will be removed immediately after you confirm." + remove_promo_image: + remove: "Remove Image" + removed_successfully: "Promo image removed successfully" + immediate_removal_warning: "The promo image will be removed immediately after you confirm." welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a next_step: Next step - choose_starting_point: 'Choose your starting point:' + choose_starting_point: 'Choose your package:' invite_manager: user_already_exists: "User already exists" error: "Something went wrong" @@ -685,6 +840,11 @@ en_US: save_reload: Save and Reload Page coordinator_fees: add: Add coordinator fee + filters: + search_by_order_cycle_name: "Search by Order Cycle name..." + involving: "Involving" + any_enterprise: "Any Enterprise" + any_schedule: "Any Schedule" form: incoming: Incoming supplier: Supplier @@ -698,7 +858,6 @@ en_US: delivery_details: Pickup / Delivery details debug_info: Debug information index: - involving: Involving schedule: Schedule schedules: Schedules adding_a_new_schedule: Adding A New Schedule @@ -717,7 +876,7 @@ en_US: name: Name orders_open: Orders open at coordinator: Coordinator - order_closes: Orders close + orders_close: Orders close row: suppliers: suppliers distributors: distributors @@ -734,6 +893,10 @@ en_US: schedule_present: That order cycle is linked to a schedule and cannot be deleted. Please unlink or delete the schedule first. bulk_update: no_data: Hm, something went wrong. No order cycle data found. + date_warning: + msg: This order cycle is linked to %{n} open subscription orders. Changing this date now will not affect any orders which have already been placed, but should be avoided if possible. Are you sure you want to proceed? + cancel: Cancel + proceed: Proceed producer_properties: index: title: Producer Properties @@ -745,11 +908,6 @@ en_US: shared: user_guide_link: user_guide: User Guide - invoice_settings: - edit: - title: Invoice Settings - invoice_style2?: Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax) - enable_receipt_printing?: Show options for printing receipts using thermal printers in order dropdown? overview: enterprises_header: ofn_with_tip: Enterprises are Producers and/or Hubs and are the basic unit of organization within the Open Food Network. @@ -843,12 +1001,18 @@ en_US: address: 2. Address products: 3. Add Products review: 4. Review & Save + subscription_line_items: + this_is_an_estimate: | + The displayed prices are only an estimate and calculated at the time the subscription is changed. + If you change prices or fees, orders will be updated, but the subscription will still display the old values. details: details: Details invalid_error: Oops! Please fill in all of the required fields... allowed_payment_method_types_tip: Only Cash and Stripe payment methods may be used at the moment credit_card: Credit Card - no_cards_available: No cards available + charges_not_allowed: Charges are not allowed by this customer + no_default_card: Customer has no cards available to charge + card_ok: Customer has a card available to charge loading_flash: loading: LOADING SUBSCRIPTIONS review: @@ -878,22 +1042,6 @@ en_US: schedules: destroy: associated_subscriptions_error: This schedule cannot be deleted because it has associated subscriptions - stripe_connect_settings: - edit: - title: "Stripe Connect" - settings: "Settings" - stripe_connect_enabled: Enable shops to accept payments using Stripe Connect? - no_api_key_msg: No Stripe account exists for this enterprise. - configuration_explanation_html: For detailed instructions on configuring the Stripe Connect integration, please consult this guide. - status: Status - ok: Ok - instance_secret_key: Instance Secret Key - account_id: Account ID - business_name: Business Name - charges_enabled: Charges Enabled - charges_enabled_warning: "Warning: Charges are not enabled for your account" - auth_fail_error: The API key you provided is invalid - empty_api_key_error_html: No Stripe API key has been provided. To set your API key, please follow these instructions controllers: enterprises: stripe_connect_cancelled: "Connection to Stripe has been cancelled" @@ -901,6 +1049,11 @@ en_US: stripe_connect_fail: Sorry, the connection of your Stripe account failed stripe_connect_settings: resource: Stripe Connect configuration + api: + enterprise_logo: + destroy_attachment_does_not_exist: "Logo does not exist" + enterprise_promo_image: + destroy_attachment_does_not_exist: "Promo image does not exist" checkout: already_ordered: cart: "cart" @@ -918,6 +1071,33 @@ en_US: register_call: selling_on_ofn: "Interested in selling through the Open Food Network?" register: "Register here" + footer: + footer_global_headline: "OFN Global" + footer_global_home: "Home" + footer_global_news: "News" + footer_global_about: "About" + footer_global_contact: "Contact" + footer_sites_headline: "OFN Sites" + footer_sites_developer: "Developer" + footer_sites_community: "Community" + footer_sites_userguide: "User Guide" + footer_secure: "Secure and trusted." + footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." + footer_contact_headline: "Keep in touch" + footer_contact_email: "Email us" + footer_nav_headline: "Navigate" + footer_join_headline: "Join us" + footer_join_body: "Create a listing, shop or group directory on the Open Food Network." + footer_join_cta: "Tell me more!" + footer_legal_call: "Read our" + footer_legal_tos: "Terms and conditions" + footer_legal_visit: "Find us on" + footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." + footer_data_text_with_privacy_policy_html: "We take good care of your data. See our %{privacy_policy} and %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "We take good care of your data. See our %{cookies_policy}" + footer_data_privacy_policy: "privacy policy" + footer_data_cookies_policy: "cookies policy" + footer_skylight_dashboard_html: Performance data is available on %{dashboard}. shop: messages: login: "login" @@ -926,6 +1106,7 @@ en_US: require_customer_login: "This shop is for customers only." require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer." require_customer_html: "Please %{contact} %{enterprise} to become a customer." + card_could_not_be_updated: Card could not be updated card_could_not_be_saved: card could not be saved spree_gateway_error_flash_for_checkout: "There was a problem with your payment information: %{error}" invoice_billing_address: "Billing address:" @@ -952,6 +1133,20 @@ en_US: ticket_column_item: "Item" ticket_column_unit_price: "Unit Price" ticket_column_total_price: "Total Price" + menu_1_title: "Stores" + menu_1_url: "/shops" + menu_2_title: "Map" + menu_2_url: "/map" + menu_3_title: "Producers" + menu_3_url: "/producers" + menu_4_title: "Groups" + menu_4_url: "/groups" + menu_5_title: "About" + menu_5_url: "http://www.openfoodnetwork.org/" + menu_6_title: "Connect" + menu_6_url: "https://openfoodnetwork.org/au/connect/" + menu_7_title: "Learn" + menu_7_url: "https://openfoodnetwork.org/au/learn/" logo: "Logo (640x130)" logo_mobile: "Mobile logo (75x26)" logo_mobile_svg: "Mobile logo (SVG)" @@ -967,7 +1162,7 @@ en_US: footer_email: "Email" footer_links_md: "Links" footer_about_url: "About URL" - footer_tos_url: "Terms of Service URL" + user_guide_link: "User Guide Link" name: Name first_name: First name last_name: Last name @@ -1041,27 +1236,48 @@ en_US: ie_warning_firefox: Download Firefox ie_warning_ie: Upgrade Internet Explorer ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)" - footer_global_headline: "OFN Global" - footer_global_home: "Home" - footer_global_news: "News" - footer_global_about: "About" - footer_global_contact: "Contact" - footer_sites_headline: "OFN Sites" - footer_sites_developer: "Developer" - footer_sites_community: "Community" - footer_sites_userguide: "User Guide" - footer_secure: "Secure and trusted." - footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." - footer_contact_headline: "Keep in touch" - footer_contact_email: "Email us" - footer_nav_headline: "Navigate" - footer_join_headline: "Join us" - footer_join_body: "Create a listing, shop or group directory on the Open Food Network." - footer_join_cta: "Tell me more!" - footer_legal_call: "Read our" - footer_legal_tos: "Terms and conditions" - footer_legal_visit: "Find us on" - footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." + legal: + cookies_policy: + header: "How We Use Cookies" + desc_part_1: "Cookies are very small text files that are stored on your computer when you visit some websites." + desc_part_2: "In OFN we are fully respectful of your privacy. We use only the cookies that are necessary for delivering you the service of selling/buying food online. We don’t sell any of your data. We might in the future propose you to share some of your data to build new commons services that could be useful for the ecosystem (like logistics services for short food systems) but we are not yet there, and we won’t do it without your authorization :-)" + desc_part_3: "We use cookies mainly to remember who you are if you 'log in' to the service, or to be able to remember the items you put in your cart even if you are not logged in. If you keep navigating on the website without clicking on “Accept cookies”, we assume you are giving us consent to store the cookies that are essential for the functioning of the website. Here is the list of cookies we use!" + essential_cookies: "Essential Cookies" + essential_cookies_desc: "The following cookies are strictly necessary for the operation of our website." + essential_cookies_note: "Most cookies only contain a unique identifier, but no other data, so your email address and password for instance are never contained in them and never exposed." + cookie_domain: "Set By:" + cookie_session_desc: "Used to allow the website to remember users between page visits, for example, remember items in your cart." + cookie_consent_desc: "Used to maintain status of user consent to store cookies" + cookie_remember_me_desc: "Used if the user has requested the website to remember him. This cookie is automatically deleted after 12 days. If as a user you want that cookie to be deleted, you only need to logout. If you don’t want that cookie to be installed on your computer you shouldn’t check the “remember me” checkbox when logging in." + cookie_openstreemap_desc: "Used by our friendly open source mapping provider (OpenStreetMap) to ensure that it does not receive too many requests during a given time period, to prevent abuse of their services." + cookie_stripe_desc: "Data collected by our payment processor Stripe for fraud detection https://stripe.com/cookies-policy/legal. Not all shops use Stripe as a payment method but it is a good practice to prevent fraud to apply it to all pages. Stripe probably build a picture of which of our pages usually interact with their API and then flag anything unusual. So setting the Stripe cookie has a broader function than simply the provision of a payment method to a user. Removing it could affect the security of the service itself. You can learn more about Stripe and read its privacy policy at https://stripe.com/privacy." + statistics_cookies: "Statistics Cookies" + statistics_cookies_desc: "The following are not strictly necessary, but help to provide you with the best user experience by allowing us to analyse user behavior, identify which features you use most, or don’t use, understand user experience issues, etc." + statistics_cookies_analytics_desc_html: "To collect and analyze platform usage data, we use Google Analytics, as it was the default service connected with Spree (the e-commerce open source software that we built on) but our vision is to switch to Matomo (ex Piwik, open source analytics tool that is GDPR compliant and protects your privacy) as soon as we can." + statistics_cookies_matomo_desc_html: "To collect and analyze platform usage data, we use Matomo (ex Piwik), an open source analytics tool that is GDPR compliant and protects your privacy." + statistics_cookies_matomo_optout: "Do you want to opt-out of Matomo analytics? We don’t collect any personal data, and Matomo helps us to improve our service, but we respect your choice :-)" + cookie_analytics_utma_desc: "Used to distinguish users and sessions. The cookie is created when the javascript library executes and no existing __utma cookies exists. The cookie is updated every time data is sent to Google Analytics." + cookie_analytics_utmt_desc: "Used to throttle request rate." + cookie_analytics_utmb_desc: "Used to determine new sessions/visits. The cookie is created when the javascript library executes and no existing __utmb cookies exists. The cookie is updated every time data is sent to Google Analytics." + cookie_analytics_utmc_desc: "Not used in ga.js. Set for interoperability with urchin.js. Historically, this cookie operated in conjunction with the __utmb cookie to determine whether the user was in a new session/visit." + cookie_analytics_utmz_desc: "Stores the traffic source or campaign that explains how the user reached your site. The cookie is created when the javascript library executes and is updated every time data is sent to Google Analytics." + cookie_matomo_basics_desc: "Matomo first party cookies to collect statistics." + cookie_matomo_heatmap_desc: "Matomo Heatmap & Session Recording cookie." + cookie_matomo_ignore_desc: "Cookie used to exclude user from being tracked." + disabling_cookies_header: "Warning on disabling cookies" + disabling_cookies_desc: "As a user you can always allow, block or delete Open Food Network’s or any other website cookies whenever you want to through your browser’s setting control. Each browser has a different operative. Here are the links:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" + disabling_cookies_note: "But be aware that if you delete or modify the essential cookies used by Open Food Network, the website won’t work, you will not be able to add anything to your cart or to check out, for instance." + cookies_banner: + cookies_usage: "This site uses cookies in order to make your navigation frictionless and secure, and to help us understand how you use it in order to improve the features we offer." + cookies_definition: "Cookies are very small text files that are stored on your computer when you visit some websites." + cookies_desc: "We use only the cookies that are necessary for delivering you the service of selling/buying food online. We don’t sell any of your data. We use cookies mainly to remember who you are if you ‘log in’ to the service, or to be able to remember the items you put in your cart even if you are not logged in. If you keep navigating on the website without clicking on “Accept cookies”, we assume you are giving us consent to store the cookies that are essential for the functioning of the website." + cookies_policy_link_desc: "If you want to learn more, check our" + cookies_policy_link: "cookies policy" + cookies_accept_button: "Accept Cookies" home_shop: Shop Now brandstory_headline: "Food, unincorporated." brandstory_intro: "Sometimes the best way to fix the system is to start a new one…" @@ -1151,6 +1367,7 @@ en_US: email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile." email_confirmation_link_label: "Confirm this email address »" email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise. See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store." + email_confirmation_notice_unexpected: "You received this message because you signed up on %{sitename}, or were invited to sign up by someone you probably know. If you don't understand why you are receiving this email, please write to %{contact}." email_social: "Connect with Us:" email_contact: "Email us:" email_signoff: "Cheers," @@ -1181,6 +1398,7 @@ en_US: email_so_edit_true_html: "You can make changes until orders close on %{orders_close_at}." email_so_edit_false_html: "You can view details of this order at any time." email_so_contact_distributor_html: "If you have any questions you can contact %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "This order was automatically created for you. You can make changes until orders close on %{orders_close_at} by contacting %{distributor} via %{email}." email_so_confirmation_intro_html: "Your order with %{distributor} is now confirmed" email_so_confirmation_explainer_html: "This order was automatically placed for you, and it has now been finalised." email_so_confirmation_details_html: "Here's everything you need to know about your order from %{distributor}:" @@ -1246,6 +1464,9 @@ en_US: hubs_intro: Shop in your local area hubs_distance: Closest to hubs_distance_filter: "Show me shops near %{location}" + shop_changeable_orders_alert_html: + one: Your order with %{shop} / %{order} is open for review. You can make changes until %{oc_close}. + other: You have %{count} orders with %{shop} currently open for review. You can make changes until %{oc_close}. orders_changeable_orders_alert_html: This order has been confirmed, but you can make changes until %{oc_close}. products_clear_all: Clear all products_showing: "Showing:" @@ -1393,6 +1614,9 @@ en_US: orders_your_order_has_been_cancelled: "Your order has been cancelled" orders_could_not_cancel: "Sorry, the order could not be cancelled" orders_cannot_remove_the_final_item: "Cannot remove the final item from an order, please cancel the order instead." + orders_bought_items_notice: + one: "An additional item is already confirmed for this order cycle" + other: "%{count} additional items already confirmed for this order cycle" orders_bought_edit_button: Edit confirmed items orders_bought_already_confirmed: "* already confirmed" orders_confirm_cancel: Are you sure you want to cancel this order? @@ -1436,6 +1660,7 @@ en_US: error_number: "must be a number" error_email: "must be email address" error_not_found_in_database: "%{name} not found in database" + error_not_primary_producer: "%{name} is not enabled as a producer" error_no_permission_for_enterprise: "\"%{name}\": you do not have permission to manage products for this enterprise" item_handling_fees: "Item Handling Fees (included in item totals)" january: "January" @@ -1451,6 +1676,7 @@ en_US: november: "November" december: "December" email_not_found: "Email address not found" + email_unconfirmed: "You must confirm your email address before you can reset your password." email_required: "You must provide an email address" logging_in: "Hold on a moment, we're logging you in" signup_email: "Your email" @@ -1466,6 +1692,17 @@ en_US: reset_password: "Reset password" who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" update_and_recalculate_fees: "Update And Recalculate Fees" + registration: + steps: + type: + headline: "Last step to add %{enterprise}!" + question: "Are you a producer?" + yes_producer: "Yes, I'm a producer" + no_producer: "No, I'm not a producer" + producer_field_error: "Please choose one. Are you are producer?" + yes_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mold it." + no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." + create_profile: "Create profile" enterprise: registration: modal: @@ -1504,13 +1741,6 @@ en_US: phone_field_placeholder: 'eg. (123)456-7891' type: title: 'Type' - headline: "Last step to add %{enterprise}!" - question: "Are you a producer?" - yes_producer: "Yes, I'm a producer" - no_producer: "No, I'm not a producer" - producer_field_error: "Please choose one. Are you are producer?" - yes_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mold it." - no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." about: title: 'About' images: @@ -1549,6 +1779,7 @@ en_US: enterprise_about_headline: "Nice one!" enterprise_about_message: "Now let's flesh out the details about" enterprise_success: "Success! %{enterprise} added to the Open Food Network " + enterprise_registration_exit_message: "If you exit this wizard at any stage, you can continue to create your profile by going to the admin interface." enterprise_description: "Short Description" enterprise_description_placeholder: "A short sentence describing your enterprise" enterprise_long_desc: "Long Description" @@ -1598,7 +1829,6 @@ en_US: registration_type_error: "Please choose one. Are you are producer?" registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." - create_profile: "Create profile" registration_images_headline: "Thanks!" registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" registration_detail_headline: "Let's get started" @@ -1657,31 +1887,31 @@ en_US: you_have_no_orders_yet: "You have no orders yet" running_balance: "Running balance" outstanding_balance: "Outstanding balance" - admin_entreprise_relationships: "Enterprise Relationships" - admin_entreprise_relationships_everything: "Everything" - admin_entreprise_relationships_permits: "permits" - admin_entreprise_relationships_seach_placeholder: "Search" - admin_entreprise_relationships_button_create: "Create" - admin_entreprise_groups: "Enterprise Groups" - admin_entreprise_groups_name: "Name" - admin_entreprise_groups_owner: "Owner" - admin_entreprise_groups_on_front_page: "On front page?" - admin_entreprise_groups_entreprise: "Enterprises" - admin_entreprise_groups_data_powertip: "The primary user responsible for this group." - admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" - admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" - admin_entreprise_groups_contact: "Contact" - admin_entreprise_groups_contact_phone_placeholder: "eg. (123)435-7891" - admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street" - admin_entreprise_groups_contact_city: "City" - admin_entreprise_groups_contact_city_placeholder: "City or Town" - admin_entreprise_groups_contact_zipcode: "Zip Code" - admin_entreprise_groups_contact_zipcode_placeholder: "eg. 24098" - admin_entreprise_groups_contact_state_id: "State" - admin_entreprise_groups_contact_country_id: "Country" - admin_entreprise_groups_web: "Web Resources" - admin_entreprise_groups_web_twitter: "eg. @openfoodnetuk" - admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.com" + admin_enterprise_relationships: "Enterprise Permissions" + admin_enterprise_relationships_everything: "Everything" + admin_enterprise_relationships_permits: "permits" + admin_enterprise_relationships_seach_placeholder: "Search" + admin_enterprise_relationships_button_create: "Create" + admin_enterprise_groups: "Enterprise Groups" + admin_enterprise_groups_name: "Name" + admin_enterprise_groups_owner: "Owner" + admin_enterprise_groups_on_front_page: "On front page?" + admin_enterprise_groups_enterprise: "Enterprises" + admin_enterprise_groups_data_powertip: "The primary user responsible for this group." + admin_enterprise_groups_data_powertip_logo: "This is the logo for the group" + admin_enterprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" + admin_enterprise_groups_contact: "Contact" + admin_enterprise_groups_contact_phone_placeholder: "eg. (123)435-7891" + admin_enterprise_groups_contact_address1_placeholder: "eg. 123 High Street" + admin_enterprise_groups_contact_city: "City" + admin_enterprise_groups_contact_city_placeholder: "City or Town" + admin_enterprise_groups_contact_zipcode: "Zipcode" + admin_enterprise_groups_contact_zipcode_placeholder: "eg. 24098" + admin_enterprise_groups_contact_state_id: "State" + admin_enterprise_groups_contact_country_id: "Country" + admin_enterprise_groups_web: "Web Resources" + admin_enterprise_groups_web_twitter: "eg. @the_prof" + admin_enterprise_groups_web_website_placeholder: "eg. www.truffles.com" admin_order_cycles: "Admin Order Cycles" open: "Open" close: "Close" @@ -1740,12 +1970,7 @@ en_US: spree_admin_enterprises_fees: "Enterprise Fees" spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE" spree_admin_enterprises_none_text: "You don't have any enterprises yet" - spree_admin_enterprises_producers_name: "Name" - spree_admin_enterprises_producers_total_products: "Total Products" - spree_admin_enterprises_producers_active_products: "Active Products" - spree_admin_enterprises_producers_order_cycles: "Products in OCs" spree_admin_enterprises_tabs_hubs: "HUBS" - spree_admin_enterprises_tabs_producers: "PRODUCERS" spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" spree_admin_enterprises_any_active_products_text: "You don't have any active products." spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" @@ -1786,11 +2011,8 @@ en_US: edit_profile_details_etc: "Change your profile description, images, etc." order_cycle: "Order Cycle" order_cycles: "Order Cycles" - enterprises: "Enterprises" - enterprise_relationships: "Enterprise relationships" + enterprise_relationships: "Enterprise permissions" remove_tax: "Remove tax" - enterprise_terms_of_service: "Enterprise Terms of Service" - enterprises_require_tos: "Enterprises must accept Terms of Service" enterprise_tos_link: "Enterprise Terms of Service link" enterprise_tos_message: "We want to work with people that share our aims and values. As such we ask new enterprises to agree to our " enterprise_tos_link_text: "Terms of Service." @@ -1825,7 +2047,7 @@ en_US: report_payment_totals: 'Payment Totals' report_all: 'all' report_order_cycle: "Order Cycle:" - report_entreprises: "Enterprises:" + report_enterprises: "Enterprises:" report_users: "Users:" report_tax_rates: Tax rates report_tax_types: Tax types @@ -2010,7 +2232,7 @@ en_US: content_configuration_pricing_table: "(TODO: Pricing table)" content_configuration_case_studies: "(TODO: Case studies)" content_configuration_detail: "(TODO: Detail)" - enterprise_name_error: "has already been taken. If this is your enterprise and you would like to claim ownership, please contact the current manager of this profile at %{email}." + enterprise_name_error: "has already been taken. If this is your enterprise and you would like to claim ownership, or if you would like to trade with this enterprise please contact the current manager of this profile at %{email}." enterprise_owner_error: "^%{email} is not permitted to own any more enterprises (limit is %{enterprise_limit})." enterprise_role_uniqueness_error: "^That role is already present." inventory_item_visibility_error: must be true or false @@ -2045,6 +2267,7 @@ en_US: order_cycles_no_permission_to_coordinate_error: "None of your enterprises have permission to coordinate an order cycle" order_cycles_no_permission_to_create_error: "You don't have permission to create an order cycle coordinated by that enterprise" back_to_orders_list: "Back to order list" + no_orders_found: "No Orders Found" order_information: "Order Information" date_completed: "Date Completed" amount: "Amount" @@ -2069,6 +2292,7 @@ en_US: choose: Choose resolve_errors: Please resolve the following errors more_items: "+ %{count} More" + default_card_updated: Default Card Updated admin: enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it." modals: @@ -2188,6 +2412,11 @@ en_US: resolve: Resolve new_tag_rule_dialog: select_rule_type: "Select a rule type:" + resend_user_email_confirmation: + resend: "Resend" + sending: "Resend..." + done: "Resend done ✓" + failed: "Resend failed ✗" out_of_stock: reduced_stock_available: Reduced stock available out_of_stock_text: > @@ -2231,7 +2460,9 @@ en_US: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' + update_failure: "Failed to update order cycle" no_distributors: There are no distributors in this order cycle. This order cycle will not be visible to customers until you add one. Would you like to continue saving this order cycle?' enterprises: producer: "Producer" @@ -2252,7 +2483,14 @@ en_US: my_account: "My account" date: "Date" time: "Time" + layouts: + admin: + header: + store: Store admin: + product_properties: + index: + inherits_properties_checkbox_hint: "Inherit properties from %{supplier}? (unless overridden above)" orders: invoice: issued_on: Issued on @@ -2291,7 +2529,8 @@ en_US: payments: source_forms: stripe: - no_payment_via_admin_backend: Creating Stripe-based payments from the admin backend is not possible at this time + error_saving_payment: Error saving payment + submitting_payment: Submitting payment... products: new: title: 'New Product' @@ -2324,17 +2563,28 @@ en_US: display_as: display_as: Display As reports: + table: + select_and_search: "Select filters and click on SEARCH to access your data." bulk_coop: bulk_coop_supplier_report: 'Bulk Co-op - Totals by Supplier' bulk_coop_allocation: 'Bulk Co-op - Allocation' bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets' bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' - shared: - configuration_menu: - stripe_connect: Stripe Connect + users: + email_confirmation: + confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}." variants: autocomplete: producer_name: Producer + general_settings: + edit: + legal_settings: "Legal Settings" + cookies_consent_banner_toggle: "Display cookies consent banner" + privacy_policy_url: "Privacy Policy URL" + enterprises_require_tos: "Enterprises must accept Terms of Service" + cookies_policy_matomo_section: "Display Matomo section on cookies policy page" + cookies_policy_ga_section: "Display Google Analytics section on cookies policy page" + footer_tos_url: "Terms of Service URL" checkout: payment: stripe: diff --git a/config/locales/es.yml b/config/locales/es.yml index e990f79e557..198942497a8 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -19,7 +19,7 @@ es: email: taken: "Ya existe una cuenta con este email. Inicie sesión o restablezca tu contraseña." spree/order: - no_card: No hay tarjetas de crédito válidas disponibles + no_card: No hay tarjetas de crédito autorizadas disponibles para cargar order_cycle: attributes: orders_close_at: @@ -41,11 +41,10 @@ es: payment_method: not_available_to_shop: "no está disponible para %{shop}" invalid_type: "El método debe ser Cash o Stripe" + charges_not_allowed: "^ Los cargos de la tarjeta de crédito no estan permitidos para esta consumidora" + no_default_card: "^ Ninguna tarjeta predeterminada disponible para esta consumidora" shipping_method: not_available_to_shop: "no está disponible para %{shop}" - credit_card: - not_available: "no está disponible" - blank: "Requerido" devise: confirmations: send_instructions: "Recibirás un correo electrónico con instrucciones sobre cómo confirmar su cuenta en unos minutos." @@ -56,11 +55,19 @@ es: user_registrations: spree_user: signed_up_but_unconfirmed: "Se ha enviado un mensaje con un enlace de confirmación a tu dirección de correo electrónico. Abre el enlace para activar tu cuenta." + unknown_error: "Algo salió mal al crear tu cuenta. Comprueba tu dirección de correo electrónico y vuelve a intentarlo." failure: invalid: | Correo o contraseña inválidos. ¿Has sido invitada? Tal vez necesites crear una cuenta o recuperar tu contraseña. unconfirmed: "Debes confirmar tu cuenta antes de continuar." + already_registered: "Esta dirección de correo electrónico ya está registrada. Inicie sesión para continuar, o vuelva atrás y use otra dirección de correo electrónico." + user_passwords: + spree_user: + updated_not_active: "Su contraseña ha sido restablecida, pero su correo electrónico aún no ha sido confirmado." + models: + order_cycle: + cloned_order_cycle_name: "COPIA DE %{order_cycle}" enterprise_mailer: confirmation_instructions: subject: "Confirma la dirección de correo electrónico de %{enterprise}" @@ -68,9 +75,26 @@ es: subject: "%{enterprise} está ahora en %{sitename}" invite_manager: subject: "%{enterprise} te ha invitado a ser administrador" + order_mailer: + cancel_email: + dear_customer: "Estimada consumidora," + instructions: "Tu pedido ha sido CANCELADO. Guarda esta información de cancelación para tus registros." + order_summary_canceled: "Resumen del pedido [CANCELADO]" + subject: "Cancelación del pedido" + subtotal: "Subtotal: %{subtotal}" + total: "Total del pedido: %{total}" producer_mailer: order_cycle: subject: "Informe Ciclo de Pedido para %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Estimada consumidora," + instructions: "Tu pedido ha sido enviado" + shipment_summary: "Resumen de envío" + subject: "Notificación de envío" + thanks: "Gracias por hacer negocios." + track_information: "Información de seguimiento: %{tracking}" + track_link: "Enlace de seguimiento: %{url}" subscription_mailer: placement_summary_email: subject: Un resumen los pedidos de suscripción recientes @@ -133,6 +157,7 @@ es: free_trial: "Prueba gratuita" plus_tax: "más Impuestos" min_bill_turnover_desc: "Una vez que el volumen de negocio supere %{mbt_amount}" + more: "Más" say_no: "No" say_yes: "Si" then: Entonces @@ -156,6 +181,7 @@ es: distributors: Distribuidores distribution: Distribución bulk_order_management: Gestión de pedidos en bloque + enterprises: Organizaciones enterprise_groups: Redes reports: Informes variant_overrides: Inventario @@ -233,6 +259,9 @@ es: form_invalid: "El formulario contiene campos vacíos o inválidos" clear_filters: Limpiar filtros clear: Limpiar + save: Guardar + cancel: Cancelar + back: Atrás show_more: Mostrar más show_n_more: Mostrar %{num} más choose: "Escoger..." @@ -303,6 +332,42 @@ es: included_tax_tip: "El impuesto total incluido en la factura mensual de ejemplo, dada la configuración y la facturación proporcionada." total_monthly_bill_incl_tax: "Factura mensual total (incl. impuesto)" total_monthly_bill_incl_tax_tip: "El ejemplo de factura mensual total con impuestos incluidos, dada la configuración y la facturación proporcionada." + cache_settings: + show: + title: Almacenando + distributor: Distribuidor + order_cycle: Ciclo de Pedido + status: Estado + diff: Diff + error: Error + invoice_settings: + edit: + title: Configuración de Factura + invoice_style2?: Utiliza el modelo de factura alternativo que incluye el desglose fiscal total por tipo de interés y tasa de impuestos por artículo (todavía no es adecuado para países que muestran los precios sin impuestos) + enable_receipt_printing?: ¿Mostrar opciones para imprimir recibos usando impresoras térmicas en el desplegable del pedido? + stripe_connect_settings: + edit: + title: "Stripe Connect" + settings: "Configuración" + stripe_connect_enabled: ¿Permitir a las tiendas aceptar pagos mediante Stripe Connect? + no_api_key_msg: No existe una cuenta Stripe para esta organización. + configuration_explanation_html: Para obtener instrucciones detalladas sobre cómo configurar la integración con Stripe Connect, consulte esta guía . + status: Estado + ok: Ok + instance_secret_key: Clave secreta de instancia + account_id: Account ID + business_name: Nombre de la Organización + charges_enabled: Cargos habilitados + charges_enabled_warning: "Advertencia: los cargos no están habilitados para su cuenta" + auth_fail_error: La clave API que proporcionó no es válida. + empty_api_key_error_html: No se ha proporcionado ninguna clave API Stripe. Para configurar su clave API, siga estas instrucciones + matomo_settings: + edit: + title: "Configuración de Matomo" + matomo_url: "URL de Matomo" + matomo_site_id: "ID de sitio de Matomo" + info_html: "Matomo es un analizador web y móvil. Puede alojar Matomo en sus servidores o utilizar un servicio alojado en la nube. Consulte matomo.org para obtener más información." + config_instructions_html: "Aquí puede configurar la integración de OFN Matomo. La siguiente URL de Matomo debe apuntar a la instancia de Matomo a la que se enviará la información de seguimiento del usuario; si se deja vacío, el seguimiento del usuario Matomo se desactivará. El campo ID del sitio no es obligatorio, pero es útil si está rastreando más de un sitio web en una sola instancia de Matomo; se puede encontrar en la consola de la instancia de Matomo." customers: index: add_customer: "Añadir Consumidor" @@ -325,16 +390,9 @@ es: update_address: 'Actualizar Dirección' confirm_delete: '¿Confirmas que quieres borrar?' search_by_email: "Buscar por email/código" + guest_label: 'Hacer pedido como invitado' destroy: has_associated_orders: 'Se ha producido un error en la eliminación: la consumidora tiene pedidos asociados en su tienda.' - cache_settings: - show: - title: Almacenando - distributor: Distribuidor - order_cycle: Ciclo de Pedido - status: Estado - diff: Diff - error: Error contents: edit: title: Contenido @@ -343,8 +401,10 @@ es: producer_signup_page: Página de registro del productor hub_signup_page: Página de registro del Grupo group_signup_page: Página de registro de grupo + main_links: Enlaces al menú principal footer_and_external_links: Pie de página y enlaces externos your_content: Tu contenido + user_guide: Manual de Usuario enterprise_fees: index: title: Comisiones de la Organización @@ -372,6 +432,7 @@ es: inherits_properties?: ¿Hereda propiedades? available_on: Disponible en av_on: "Av. En" + import_date: Importado upload_an_image: Subir una imagen product_search_keywords: Palabras clave de búsqueda de productos product_search_tip: Escriba palabras para ayudar a buscar sus productos en las tiendas. Use espacio para separar cada palabra clave. @@ -386,6 +447,96 @@ es: product_distributions: "Distribuciones de productos" group_buy_options: "Opciones de compra grupales" back_to_products_list: "Volver a la lista de productos" + product_import: + title: Importación de productos + file_not_found: Archivo no encontrado o no se pudo abrir + no_data: No se encontraron datos en la hoja de cálculo + confirm_reset: "Esto establecerá el nivel de stock en cero en todos los productos para esta\n organizacion que no están presentes en el archivo cargado" + model: + no_file: "Error: no se ha subido ningún archivo" + could_not_process: "No se pudo procesar el archivo: tipo de archivo inválido" + incorrect_value: valor incorrecto + conditional_blank: no puede estar en blanco si unit_type está en blanco + no_product: no coincide con ningún producto en la base de datos + not_found: no encontrado en la base de datos + not_updatable: No se puede actualizar sobre productos existentes a través de la importación de productos + blank: no puede estar vacío + products_no_permission: no tienes permiso para administrar productos para esta organización + inventory_no_permission: no tienes permiso para crear inventario para esta productora + none_saved: No se guardó ningún producto con éxito + line: Línea + index: + select_file: Selecciona una hoja de cálculo para subir + spreadsheet: Hoja de cálculo + choose_import_type: Seleccionar tipo de importación + import_into: Tipo de importación + product_list: Lista de productos + inventories: Inventarios + import: Importar + upload: Subir + csv_templates: Plantillas CSV + product_list_template: Descargar la plantilla de lista de productos + inventory_template: Descargar plantilla de inventario + category_values: Valores de categoría disponibles + product_categories: Categorías de Producto + tax_categories: Categorías de impuestos + shipping_categories: Categorías de envío + import: + review: Revisión + import: Importar + save: Guardar + results: Resultados + save_imported: Guardar productos importados + no_valid_entries: No se encontraron entradas válidas + none_to_save: No hay entradas que se puedan guardar + some_invalid_entries: El archivo importado contiene entradas no válidas + fix_before_import: Corrija estos errores e intente importar el archivo nuevamente + save_valid?: ¿Guardar entradas válidas por ahora y descartar las demás? + no_errors: ¡No se detectaron errores! + save_all_imported?: Guardar todos los productos importados? + options_and_defaults: Opciones de importación y valores predeterminados + no_permission: no tienes permiso para administrar esta organización + not_found: no se pudo encontrar la organización en la base de datos + no_name: Sin nombre + blank_supplier: algunos productos tienen un nombre de proveedor en blanco + reset_absent?: Restablecer productos ausentes + reset_absent_tip: Establezca el stock en cero para todos los productos existentes que no estén presentes en el archivo. + overwrite_all: Sobrescribir todo + overwrite_empty: Sobrescribir si está vacío + default_stock: Establecer nivel de existencias + default_tax_cat: Establecer categoría de impuestos + default_shipping_cat: Establecer categoría de envío + default_available_date: Establecer fecha disponible + validation_overview: Resumen de validación de importación + entries_found: Entradas encontradas en el archivo importado + entries_with_errors: Los artículos contienen errores y no se importarán + products_to_create: Los productos se crearán + products_to_update: Los productos se actualizarán + inventory_to_create: Se crearán Artículos de inventario + inventory_to_update: Los artículos de inventario se actualizarán + products_to_reset: Los productos existentes tendrán su stock restablecido a cero + inventory_to_reset: Los artículos de inventario existentes tendrán su stock restablecido a cero + line: Línea + item_line: Línea del artículo + import_review: + not_updatable_tip: "Los siguientes campos no se pueden actualizar mediante la importación masiva de productos existentes:" + fields_ignored: Estos campos se ignorarán cuando se guarden los productos importados. + entries_table: + not_updatable: Este campo no es actualizable mediante importación masiva en productos existentes + save_results: + final_results: Importar resultados finales + products_created: Productos creados + products_updated: Productos actualizados + inventory_created: Artículos de inventario creados + inventory_updated: Artículos de inventario actualizados + products_reset: Los productos tenían nivel de stock restablecido a cero + inventory_reset: Los artículos de inventario tenían el nivel de stock restablecido a cero + all_saved: "Todos los artículos guardados con éxito" + some_saved: "Artículos guardados con éxito" + save_errors: Guardar errores + import_again: Subir otro archivo + view_products: Ir a la página de productos + view_inventory: Ir a la página de inventario variant_overrides: loading_flash: loading_inventory: CARGANDO INVENTARIO @@ -396,6 +547,7 @@ es: inherit?: ¿Heredado? add: Añadir hide: Ocultar + import_date: Importado select_a_shop: Selecciona una Tienda review_now: Revisar ahora new_products_alert_message: Hay %{new_product_count} nuevos productos disponibles para añadir a tu inventario. @@ -411,9 +563,6 @@ es: controls: back_to_my_inventory: Volver a mi inventario orders: - index: - capture: "Captura" - ship: "Envío" invoice_email_sent: 'Se ha enviado correo electrónico con la factura.' order_email_resent: 'El correo electrónico del pedido se ha reenviado' bulk_management: @@ -604,7 +753,7 @@ es: email_confirmed: "Correo electrónico confirmado" email_not_confirmed: "Correo electrónico no confirmado" actions: - edit_profile: Editar Perfil + edit_profile: Configuración properties: Propiedades payment_methods: Métodos de Pago payment_methods_tip: Esta organización no tiene métodos de pago @@ -644,22 +793,34 @@ es: no_enterprises_found: No se encuentran organizaciones. search_placeholder: Buscar por Nombre manage: Gestionar + manage_link: Configuración + producer?: "¿Productora?" + package: "Perfil" + status: "Estado" new_form: owner: Propietaria owner_tip: La principal usaría responsable para esta organización. i_am_producer: Soy una Productora contact_name: Nombre de Contacto edit: - editing: 'Editando:' + editing: 'Configuración:' back_link: Volver a la lista de organizaciones new: title: Nueva Organización back_link: Volver a la lista de organizaciones + remove_logo: + remove: "Eliminar la imagen" + removed_successfully: "Logotipo eliminado con éxito" + immediate_removal_warning: "El logotipo se eliminará inmediatamente después de confirmar." + remove_promo_image: + remove: "Eliminar la imagen" + removed_successfully: "Imagen promocional eliminada con éxito" + immediate_removal_warning: "La imagen promocional se eliminará inmediatamente después de confirmar." welcome: welcome_title: Bienvenida a Open Food Network! welcome_text: Has creado correctamente un next_step: Siguiente paso - choose_starting_point: 'Elige tu punto de partida:' + choose_starting_point: 'Selecciona tu perfil:' invite_manager: user_already_exists: "El usuario ya existe" error: "Algo salió mal" @@ -687,6 +848,11 @@ es: save_reload: Guardar y recargar la página coordinator_fees: add: Añadir comisión para el coordinador + filters: + search_by_order_cycle_name: "Buscar por nombre del Ciclo de Pedido..." + involving: "Involucrando" + any_enterprise: "Cualquier organización" + any_schedule: "Cualquier horario" form: incoming: Entrante supplier: Proveedora @@ -700,7 +866,6 @@ es: delivery_details: Detalles de Recogida / Entrega debug_info: Información de Debug index: - involving: Involucrando schedule: Horario schedules: Horarios adding_a_new_schedule: Agregar un nuevo horario @@ -719,7 +884,7 @@ es: name: Nombre orders_open: Pedidos abiertos a coordinator: Coordinadora - order_closes: Cierre de Pedidos + orders_close: Cierre de Pedidos row: suppliers: proveedoras distributors: distribuidoras @@ -736,6 +901,10 @@ es: schedule_present: Ese ciclo de pedido está vinculado a un horario y no puede ser eliminado. Desvincula o elimina el calendario primero. bulk_update: no_data: Hm, algo salió mal. No se encontraron datos de ciclo de pedido. + date_warning: + msg: Este ciclo de pedido está vinculado a %{n}pedidos de suscripción abiertos. Cambiar esta fecha ahora no afectará ningun pedido que ya se haya realizado pero se debe evitar si es posible. ¿Estás seguro que deseas continuar? + cancel: Cancelar + proceed: Proceder producer_properties: index: title: Propiedades de la Productora @@ -747,11 +916,6 @@ es: shared: user_guide_link: user_guide: Manual de Usuario - invoice_settings: - edit: - title: Configuración de Factura - invoice_style2?: Utiliza el modelo de factura alternativo que incluye el desglose fiscal total por tipo de interés y tasa de impuestos por artículo (todavía no es adecuado para países que muestran los precios sin impuestos) - enable_receipt_printing?: ¿Mostrar opciones para imprimir recibos usando impresoras térmicas en el desplegable del pedido? overview: enterprises_header: ofn_with_tip: Las Organizaciones son Productoras y/o Grupos y son la unidad básica de organización dentro de la Open Food Network. @@ -845,12 +1009,18 @@ es: address: 2. Dirección products: 3. Agregue productos review: 4. Revisar y guardar + subscription_line_items: + this_is_an_estimate: | + Los precios mostrados son solo una estimación y se calculan en el momento en que se cambia la suscripción. + Si cambias precios o tarifas, los pedidos se actualizarán pero la suscripción seguirá mostrando los valores anteriores. details: details: Detalles invalid_error: Ups! Por favor complete todos los campos requeridos ... allowed_payment_method_types_tip: Solo se pueden usar métodos de pago en efectivo y Stripe en este momento credit_card: Tarjeta de crédito - no_cards_available: No hay tarjetas disponibles + charges_not_allowed: Los cargos no están permitidos para esta consumidora + no_default_card: La consumidora no tiene tarjetas disponibles para cargar + card_ok: La consumidora tiene una tarjeta disponible para cargar loading_flash: loading: CARGANDO SUSCRIPCIONES review: @@ -880,22 +1050,6 @@ es: schedules: destroy: associated_subscriptions_error: Este horario no se puede eliminar porque tiene suscripciones asociadas - stripe_connect_settings: - edit: - title: "Stripe Connect" - settings: "Configuración" - stripe_connect_enabled: ¿Permitir a las tiendas aceptar pagos mediante Stripe Connect? - no_api_key_msg: No existe una cuenta Stripe para esta organización. - configuration_explanation_html: Para obtener instrucciones detalladas sobre cómo configurar la integración con Stripe Connect, consulte esta guía . - status: Estado - ok: Ok - instance_secret_key: Clave secreta de instancia - account_id: Account ID - business_name: Nombre de la Organización - charges_enabled: Cargos habilitados - charges_enabled_warning: "Advertencia: los cargos no están habilitados para su cuenta" - auth_fail_error: La clave API que proporcionó no es válida. - empty_api_key_error_html: No se ha proporcionado ninguna clave API Stripe. Para configurar su clave API, siga estas instrucciones controllers: enterprises: stripe_connect_cancelled: "Se ha cancelado la conexión a Stripe" @@ -903,6 +1057,11 @@ es: stripe_connect_fail: Lo sentimos, la conexión de tu cuenta Stripe ha fallado stripe_connect_settings: resource: Configuración de Stripe Connect + api: + enterprise_logo: + destroy_attachment_does_not_exist: "El logotipo no existe" + enterprise_promo_image: + destroy_attachment_does_not_exist: "La imagen promocional no existe" checkout: already_ordered: cart: "carrito" @@ -920,6 +1079,33 @@ es: register_call: selling_on_ofn: "¿Estás interesada en entrar en Open Food Network?" register: "Regístrate aquí" + footer: + footer_global_headline: "OFN Global" + footer_global_home: "Inicio" + footer_global_news: "Noticas" + footer_global_about: "Acerca de" + footer_global_contact: "Contacto" + footer_sites_headline: "Sitios OFN" + footer_sites_developer: "Desarrollador" + footer_sites_community: "Comunidad" + footer_sites_userguide: "Manual de Usuario" + footer_secure: "Seguro y de confianza." + footer_secure_text: "Open Food Network usa cifrado SSL (RSA de 2048 bit) en toda su plataforma para mantener privada la información de compras y pagos. Nuestros servidores no almacenan los detalles de tarjetas de créditos y los pagos son procesados por servicios que cumplen con PCI." + footer_contact_headline: "Mantenerse en contacto" + footer_contact_email: "Envíenos un correo electrónico" + footer_nav_headline: "Navegar" + footer_join_headline: "Unirse" + footer_join_body: "Crea un listado, una tienda o un directorio en Open Food Network." + footer_join_cta: "Cuentame más!" + footer_legal_call: "Leer nuestros" + footer_legal_tos: "Terminos y condiciones" + footer_legal_visit: "Encuéntrenos en" + footer_legal_text_html: "Open Food Network es una plataforma libre y de código abierto. Nuestro contenido tiene una licencia %{content_license} y nuestro código %{code_license}." + footer_data_text_with_privacy_policy_html: "Cuidamos bien tus datos. Consulta nuestra %{privacy_policy} y %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "Cuidamos bien tus datos. Consulta nuestra %{cookies_policy}" + footer_data_privacy_policy: "política de privacidad" + footer_data_cookies_policy: "política de cookies" + footer_skylight_dashboard_html: Los datos de rendimiento están disponibles en %{dashboard}. shop: messages: login: "login" @@ -928,6 +1114,7 @@ es: require_customer_login: "Esta tienda es solo para consumidores registrados." require_login_html: "Haz %{login} si ya tienes una cuenta. De lo contrario realiza un %{register}y contacta con la organización para que acepten tu solicitud." require_customer_html: "%{contact} %{enterprise} para convertirte en miembro." + card_could_not_be_updated: La tarjeta no se pudo actualizar card_could_not_be_saved: la tarjeta no se pudo guardar spree_gateway_error_flash_for_checkout: "Hubo un problema con tu información de pago: %{error}" invoice_billing_address: "Dirección de facturación:" @@ -954,6 +1141,18 @@ es: ticket_column_item: "Artículo" ticket_column_unit_price: "Precio por unidad" ticket_column_total_price: "Precio total" + menu_1_title: "Tiendas" + menu_1_url: "/tiendas" + menu_2_title: "Mapa" + menu_2_url: "/mapa" + menu_3_title: "Productoras" + menu_3_url: "/productoras" + menu_4_title: "Redes" + menu_4_url: "/redes" + menu_5_title: "Acerca de" + menu_5_url: "http://katuma.org/" + menu_6_title: "Conectar" + menu_7_title: "Aprender" logo: "Logo (640x130)" logo_mobile: "Logo para móvil (75x26)" logo_mobile_svg: "Logo para móvil (SVG)" @@ -969,7 +1168,7 @@ es: footer_email: "Correo electrónico" footer_links_md: "Enlaces" footer_about_url: "URL acerca de" - footer_tos_url: "URL de términos y servicios" + user_guide_link: "Enlace de la Guía de usuario" name: Nombre first_name: Nombre last_name: Apellido @@ -1043,27 +1242,44 @@ es: ie_warning_firefox: Descargar Firefox ie_warning_ie: Actualizar Internet Explorer ie_warning_other: "¿No puede actualizar su navegador? Pruebe Open Food Network en su teléfono :-)" - footer_global_headline: "OFN Global" - footer_global_home: "Inicio" - footer_global_news: "Noticas" - footer_global_about: "Acerca de" - footer_global_contact: "Contacto" - footer_sites_headline: "Sitios OFN" - footer_sites_developer: "Desarrollador" - footer_sites_community: "Comunidad" - footer_sites_userguide: "Guía de usuario" - footer_secure: "Seguro y de confianza." - footer_secure_text: "Open Food Network usa cifrado SSL (RSA de 2048 bit) en toda su plataforma para mantener privada la información de compras y pagos. Nuestros servidores no almacenan los detalles de tarjetas de créditos y los pagos son procesados por servicios que cumplen con PCI." - footer_contact_headline: "Mantenerse en contacto" - footer_contact_email: "Envíenos un correo" - footer_nav_headline: "Navegar" - footer_join_headline: "Unirse" - footer_join_body: "Crea un listado, una tienda o un directorio en Open Food Network." - footer_join_cta: "Cuentame más!" - footer_legal_call: "Leer nuestros" - footer_legal_tos: "Terminos y condiciones" - footer_legal_visit: "Encuéntrenos en" - footer_legal_text_html: "Open Food Network es una plataforma libre y de código abierto. Nuestro contenido tiene una licencia %{content_license} y nuestro código %{code_license}." + legal: + cookies_policy: + header: "Cómo utilizamos las cookies" + desc_part_1: "Las cookies son archivos de texto muy pequeños que se almacenan en tu computadora cuando visitas algunos sitios web." + desc_part_2: "En OFN somos respetuosos con tu privacidad. Utilizamos solo las cookies que son necesarias para ofrecerte el servicio de compraventa de alimentos en línea. No vendemos ninguno de tus datos. Es posible que en el futuro te propongamos que compartas alguno de tus datos para crear nuevos servicios comunes que podrían ser útiles para el ecosistema (como los servicios logísticos para sistemas alimentarios cortos), pero aún no hemos llegado a ese punto y no lo haremos sin tu autorización :-)" + desc_part_3: "Usamos cookies principalmente para recordar quién eres si 'inicias sesión' o para poder recordar los artículos que colocas en tu carrito, incluso si no has iniciado sesión. Si continúas navegando en el sitio web sin hacer clic en \"Aceptar cookies\" suponemos que nos das tu consentimiento para almacenar las cookies que son esenciales para el funcionamiento del sitio web. ¡Aquí está la lista de cookies que usamos!" + essential_cookies: "Cookies esenciales" + essential_cookies_desc: "Las siguientes cookies son estrictamente necesarias para el funcionamiento de nuestro sitio web." + essential_cookies_note: "La mayoría de las cookies solo contienen un identificador único, pero no otros datos, por lo que su dirección de correo electrónico y contraseña, por ejemplo, nunca se incluyen ni se exponen." + cookie_domain: "Establecido por:" + cookie_session_desc: "Se usa para permitir que el sitio web recuerde a los usuarios entre las visitas a la página, por ejemplo, recordar los artículos en tu carrito." + cookie_consent_desc: "Se usa para mantener el estado del consentimiento del usuario para almacenar cookies" + cookie_remember_me_desc: "Se usa si el usuario ha solicitado que el sitio web lo recuerde. Esta cookie se elimina automáticamente después de 12 días. Si como usuario deseas que se elimine esa cookie, solo necesitas desconectarte. Si no deseas que la cookie se instale en tu computadora no debes marcar la casilla \"recordarme\" al iniciar sesión." + cookie_openstreemap_desc: "Utilizado por nuestro amigo proveedor de mapeo de código abierto (OpenStreetMap) para garantizar que no recibas demasiadas solicitudes durante un período de tiempo determinado, para evitar el abuso de sus servicios." + cookie_stripe_desc: "Datos recopilados por nuestro procesador de pagos Stripe para detectar fraudes https://stripe.com/cookies-policy/legal. No todas las tiendas usan Stripe como método de pago pero es una buena práctica evitar fraude aplicarlo a todas las páginas. Stripe probablemente crea una imagen de cuáles de nuestras páginas generalmente interactúan con su API y luego marca cualquier cosa inusual. Por lo tanto configurar la cookie Stripe tiene una función más amplia que la simple provisión de un método de pago a un usuario. Eliminarla podría afectar la seguridad del servicio en sí. Puede obtener más información acerca de Stripe y leer su política de privacidad en https://stripe.com/privacy." + statistics_cookies: "Cookies de estadísticas" + statistics_cookies_desc: "Las siguientes no son estrictamente necesarias, pero ayudan a proporcionarle una mejor experiencia de usuario al permitirnos analizar el comportamiento del usuario, identificar qué funciones usa más o no, comprender los problemas de la experiencia del usuario, etc." + statistics_cookies_analytics_desc_html: "Para recopilar y analizar los datos de uso de la plataforma utilizamos Google Analytics, ya que era el servicio predeterminado conectado con Spree (el software de código abierto de comercio electrónico en el que creamos) pero nuestra visión es cambiar a Matomo (ex Piwik, herramienta analítica de código abierto que cumple con GDPR y protege tu privacidad) tan pronto como podamos." + statistics_cookies_matomo_desc_html: "Para recopilar y analizar los datos de uso de la plataforma, utilizamos Matomo (ex Piwik), una herramienta analítica de código abierto que cumple con GDPR y protege tu privacidad" + statistics_cookies_matomo_optout: "¿Deseas excluirte de Matomo Analytics? No recopilamos ningún dato personal y Matomo nos ayuda a mejorar nuestro servicio, pero respetamos tu elección :-)" + cookie_analytics_utma_desc: "Se usa para distinguir usuarios y sesiones. La cookie se crea cuando la biblioteca javascript se ejecuta y no existe ninguna cookie __utma existente. La cookie se actualiza cada vez que se envían datos a Google Analytics." + cookie_analytics_utmt_desc: "Se usa para acelerar la tasa de solicitud." + cookie_analytics_utmb_desc: "Se utiliza para determinar nuevas sesiones / visitas. La cookie se crea cuando la librería javascript se ejecuta y no existe ninguna cookie __utmb existente. La cookie se actualiza cada vez que los datos se envían a Google Analytics." + cookie_analytics_utmc_desc: "No utilizado en ga.js. Establecer para la interoperabilidad con urchin.js. Históricamente, esta cookie funcionó junto con la cookie __utmb para determinar si el usuario estaba en una nueva sesión / visita." + cookie_analytics_utmz_desc: "Almacena la fuente de tráfico o la campaña que explica cómo el usuario llegó a su sitio. La cookie se crea cuando se ejecuta la librería javascript y se actualiza cada vez que se envían datos a Google Analytics." + cookie_matomo_basics_desc: "Matomo cookies de origen para recopilar estadísticas." + cookie_matomo_heatmap_desc: "Matomo Heatmap y sesión de grabación de cookies." + cookie_matomo_ignore_desc: "Cookie utilizada para excluir al usuario de ser rastreado." + disabling_cookies_header: "Advertencia sobre la desactivación de cookies" + disabling_cookies_desc: "Como usuario, siempre puede permitir, bloquear o eliminar las cookies de Open Food Network o cualquier otra página web cuando lo desee a través del control de configuración de su navegador. Cada navegador tiene una operativa diferente. Aquí están los enlaces:" + disabling_cookies_note: "Pero tenga en cuenta que si elimina o modifica las cookies esenciales utilizadas por Open Food Network, el sitio web no funcionará, no podrá agregar nada a su carrito ni realizar pedidos, por ejemplo." + cookies_banner: + cookies_usage: "Este sitio utiliza cookies para que su navegación sea fluida y segura, y para ayudarnos a comprender cómo lo usa para mejorar las funciones que ofrecemos." + cookies_definition: "Las cookies son archivos de texto muy pequeños que se almacenan en tu ordenador cuando visitas algunos sitios web." + cookies_desc: "Utilizamos solo las cookies que son necesarias para ofrecerle el servicio de venta / compra de alimentos en línea. No vendemos ninguno de sus datos. Utilizamos cookies principalmente para recordar quién es usted si 'inicia sesión' en el servicio, o para poder recordar los artículos que puso en su carrito, incluso si no ha iniciado sesión. Si continúa navegando en el sitio web sin hacer clic en \"Aceptar cookies\", asumimos que nos da su consentimiento para almacenar las cookies que son esenciales para el funcionamiento del sitio web." + cookies_policy_link_desc: "Si desea obtener más información, consulte nuestro" + cookies_policy_link: "política de cookies" + cookies_accept_button: "Aceptar cookies" home_shop: Comprar ahora brandstory_headline: "Consume con valores." brandstory_intro: "A veces la mejor forma de arreglar el sistema es empezar uno nuevo…" @@ -1153,6 +1369,7 @@ es: email_confirmation_click_link: "Por favor haga clic en el enlace de abajo para confirmar el correo electrónico y continuar configurando su perfil." email_confirmation_link_label: "Confirmar este correo electrónico »" email_confirmation_help_html: "Después de confirmar el correo electrónico puedes acceder a tu cuenta de administración para esta organización. Visita %{link} para encontrar más información sobre las características de %{sitename} y empieza a usar tu perfil o tienda online." + email_confirmation_notice_unexpected: "Recibes este mensaje porque te has registrado en %{sitename} o has sido invitado a inscribirte por alguien que probablemente conozcas. Si no entiendes por qué estás recibiendo este correo electrónico, escribe a %{contact}." email_social: "Conecte con nosotros:" email_contact: "Envíenos un correo electrónico:" email_signoff: "Saludos," @@ -1183,6 +1400,7 @@ es: email_so_edit_true_html: "Puede realizar cambios hasta que los pedidos se cierren en %{orders_close_at}." email_so_edit_false_html: "Puede ver detalles de este pedido en cualquier momento." email_so_contact_distributor_html: "Si tiene alguna pregunta, puede contactar %{distributor} a través de %{email}." + email_so_contact_distributor_to_change_order_html: "Has creado este pedido automáticamente. Puedes realizar cambios hasta que los pedidos se cierren en %{orders_close_at} contactando %{distributor} a través de %{email}." email_so_confirmation_intro_html: "Tu pedido con %{distributor} ahora está confirmado" email_so_confirmation_explainer_html: "Este pedido fue colocado automáticamente para usted, y ahora ha sido finalizado." email_so_confirmation_details_html: "Aquí encontrará todo lo que necesita saber sobre su pedido en %{distributor} :" @@ -1444,6 +1662,7 @@ es: error_number: "debe ser un número" error_email: "debe ser una dirección de correo electrónico" error_not_found_in_database: "%{name} no se encuentra en la base de datos" + error_not_primary_producer: "%{name} no está habilitado como productora" error_no_permission_for_enterprise: "\"%{name}\": no tiene permiso para administrar productos para esta organización" item_handling_fees: "Tarifa de manejo de artículo (incluída en el total de artículos)" january: "Enero" @@ -1475,6 +1694,17 @@ es: reset_password: "Restaurar contraseña" who_is_managing_enterprise: "¿Quién es responsable de administrar %{enterprise}?" update_and_recalculate_fees: "Actualizar y recalcular tarifas" + registration: + steps: + type: + headline: "Último paso para añadir %{enterprise}!" + question: "¿Eres una productora?" + yes_producer: "Si soy una productora" + no_producer: "No, no soy una productora" + producer_field_error: "Por favor elige uno. ¿Eres una productora?" + yes_producer_help: "Las productoras hacen cosas deliciosas para comer y/o beber. Eres una productora si lo cultivas, lo haces crecer, lo preparas, lo horneas, lo fermentas, lo ordeñas, ..." + no_producer_help: "Si no eres una productora, probablemente conozcas a alguien que venda o distribuya comida. También podrías convertirte en un grupo de consumo u otro tipo de organización." + create_profile: "Crear Perfil" enterprise: registration: modal: @@ -1513,13 +1743,6 @@ es: phone_field_placeholder: 'p.ej. 651 01 20 54' type: title: 'Tipo' - headline: "Último paso para añadir %{enterprise}!" - question: "¿Eres una productora?" - yes_producer: "Si soy una productora" - no_producer: "No, no soy una productora" - producer_field_error: "Por favor elige uno. ¿Eres una productora?" - yes_producer_help: "Las productoras hacen cosas deliciosas para comer y/o beber. Eres una productora si lo cultivas, lo haces crecer, lo preparas, lo horneas, lo fermentas, lo ordeñas, ..." - no_producer_help: "Si no eres una productora, probablemente conozcas a alguien que venda o distribuya comida. También podrías convertirte en un grupo de consumo u otro tipo de organización." about: title: 'Acerca de' images: @@ -1558,6 +1781,7 @@ es: enterprise_about_headline: "¡Seguimos!" enterprise_about_message: "Ahora vamos a profundizar en los detalles acerca de" enterprise_success: "¡Felicidades! %{enterprise} se agregó a Open Food Network " + enterprise_registration_exit_message: "Si sales de este asistente en cualquier etapa puedes continuar creando tu perfil yendo a la interfaz de administración." enterprise_description: "Descripción corta" enterprise_description_placeholder: "Una frase corta que describa tu organización" enterprise_long_desc: "Descripción larga" @@ -1607,7 +1831,6 @@ es: registration_type_error: "Escoja una. ¿Es una productora?" registration_type_producer_help: "Las productoras hacen cosas deliciosas para comer y/o beber. Eres una productora si lo cultivas, lo haces crecer, lo preparas, lo horneas, lo fermentas, lo ordeñas, ..." registration_type_no_producer_help: "Si no eres una productora, probablemente conozcas a alguien que venda o distribuya comida. También podrías convertirte en un grupo de consumo u otro tipo de organización." - create_profile: "Crear Perfil" registration_images_headline: "¡Ya casi lo tenemos!" registration_images_description: "¡Sube algunas fotografías así el perfil se verá mucho mejor! :)" registration_detail_headline: "Empecemos..." @@ -1666,31 +1889,31 @@ es: you_have_no_orders_yet: "No tienes pedidos todavía" running_balance: "Saldo actual" outstanding_balance: "Saldo extraordinario" - admin_entreprise_relationships: "Relaciones de la Organización" - admin_entreprise_relationships_everything: "Marcar todos" - admin_entreprise_relationships_permits: "Permite" - admin_entreprise_relationships_seach_placeholder: "Buscar" - admin_entreprise_relationships_button_create: "Crear" - admin_entreprise_groups: "Redes de organizaciones" - admin_entreprise_groups_name: "Nombre" - admin_entreprise_groups_owner: "Propietaria" - admin_entreprise_groups_on_front_page: "¿En la página principal?" - admin_entreprise_groups_entreprise: "Organizaciones" - admin_entreprise_groups_data_powertip: "El principal usuario responsable de este grupo." - admin_entreprise_groups_data_powertip_logo: "Este es el logo del grupo" - admin_entreprise_groups_data_powertip_promo_image: "Esta imagen se mostrará en la cabecera del perfil del Grupo" - admin_entreprise_groups_contact: "Conacto" - admin_entreprise_groups_contact_phone_placeholder: "ej. 98 7654 3210" - admin_entreprise_groups_contact_address1_placeholder: "ej. C/Torrent de l'Olla" - admin_entreprise_groups_contact_city: "Barrio" - admin_entreprise_groups_contact_city_placeholder: "ej. Barcelona" - admin_entreprise_groups_contact_zipcode: "Código Postal" - admin_entreprise_groups_contact_zipcode_placeholder: "ej. 08025" - admin_entreprise_groups_contact_state_id: "Región" - admin_entreprise_groups_contact_country_id: "País" - admin_entreprise_groups_web: "Recursos Web" - admin_entreprise_groups_web_twitter: "ej. the_prof" - admin_entreprise_groups_web_website_placeholder: "ej. www.truffles.com" + admin_enterprise_relationships: "Permisos de la organización" + admin_enterprise_relationships_everything: "Marcar todos" + admin_enterprise_relationships_permits: "Permite" + admin_enterprise_relationships_seach_placeholder: "Buscar" + admin_enterprise_relationships_button_create: "Crear" + admin_enterprise_groups: "Redes de organizaciones" + admin_enterprise_groups_name: "Nombre" + admin_enterprise_groups_owner: "Propietaria" + admin_enterprise_groups_on_front_page: "¿En la página principal?" + admin_enterprise_groups_enterprise: "Organizaciones" + admin_enterprise_groups_data_powertip: "El principal usuario responsable de este grupo." + admin_enterprise_groups_data_powertip_logo: "Este es el logo del grupo" + admin_enterprise_groups_data_powertip_promo_image: "Esta imagen se mostrará en la cabecera del perfil del Grupo" + admin_enterprise_groups_contact: "Contacto" + admin_enterprise_groups_contact_phone_placeholder: "ej. 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "ej. Carrer Torrent de l'Olla" + admin_enterprise_groups_contact_city: "Barrio" + admin_enterprise_groups_contact_city_placeholder: "ej. Barcelona" + admin_enterprise_groups_contact_zipcode: "Código Postal" + admin_enterprise_groups_contact_zipcode_placeholder: "ej. 08025" + admin_enterprise_groups_contact_state_id: "Provincia" + admin_enterprise_groups_contact_country_id: "País" + admin_enterprise_groups_web: "Recursos Web" + admin_enterprise_groups_web_twitter: "ej: the_prof" + admin_enterprise_groups_web_website_placeholder: "ej. www.truffles.com" admin_order_cycles: "Ciclos de Pedidos del Admin" open: "Abierta" close: "Cerrar" @@ -1749,12 +1972,7 @@ es: spree_admin_enterprises_fees: "Comisiones de la Organización" spree_admin_enterprises_none_create_a_new_enterprise: "CREAR NUEVA ORGANIZACIÓN" spree_admin_enterprises_none_text: "No tienes ninguna organización" - spree_admin_enterprises_producers_name: "Nombre" - spree_admin_enterprises_producers_total_products: "Total de Productos" - spree_admin_enterprises_producers_active_products: "Productos Activos" - spree_admin_enterprises_producers_order_cycles: "Productos en OCs" spree_admin_enterprises_tabs_hubs: "HUBS" - spree_admin_enterprises_tabs_producers: "PRODUCTORAS" spree_admin_enterprises_producers_manage_products: "GESTIONAR PRODUCTOS" spree_admin_enterprises_any_active_products_text: "No tienes ningún producto activo" spree_admin_enterprises_create_new_product: "CREAR UN NUEVO PRODUCTO" @@ -1795,11 +2013,8 @@ es: edit_profile_details_etc: "Cambia tu descripción, imágenes, etc." order_cycle: "Ciclo de Pedido" order_cycles: "Ciclos de Pedidos" - enterprises: "Organizaciones" - enterprise_relationships: "Relaciones entre organizaciones" + enterprise_relationships: "Permisos de la organización" remove_tax: "Eliminar impuesto" - enterprise_terms_of_service: "Términos del Servicio de la Organización" - enterprises_require_tos: "Las organizaciones deben aceptar los Términos del Servicio" enterprise_tos_link: "Enlace a los Términos del Servicio de la Organización" enterprise_tos_message: "Queremos trabajar con personas que compartan nuestros objetivos y valores. Por ello, pedimos a las nuevas organizaciones que acepten" enterprise_tos_link_text: "Términos del Servicio." @@ -1834,7 +2049,7 @@ es: report_payment_totals: 'Pagos Totales' report_all: 'Todo' report_order_cycle: "Ciclo de Pedido:" - report_entreprises: "Organizaciones:" + report_enterprises: "Organizaciones:" report_users: "Usuarias:" report_tax_rates: Porcentajes de los Impuestos report_tax_types: Tipos de Impuestos @@ -2019,7 +2234,7 @@ es: content_configuration_pricing_table: "(TODO: tabla de precios)" content_configuration_case_studies: "(TODO: Casos de Estudio)" content_configuration_detail: "(TODO: Detalle)" - enterprise_name_error: "ya se ha utilizado. Si se trata de tu organización y deseas reclamar la propiedad, ponte en contacto con el administrador actual de este perfil en %{email}." + enterprise_name_error: "ya está en uso. Si esta es su organización y le gustaría reclamar la propiedad, o si desea negociar con esta organización, comuníquese con el gestor actual de este perfil al %{email}." enterprise_owner_error: "^ %{email} no está autorizado a tener más organizaciones (el límite es %{enterprise_limit})." enterprise_role_uniqueness_error: "^Este rol ya está presente." inventory_item_visibility_error: Debe ser verdadero o falso @@ -2054,6 +2269,7 @@ es: order_cycles_no_permission_to_coordinate_error: "Ninguna de tus organizaciones tiene permiso para coordinar un ciclo de pedido" order_cycles_no_permission_to_create_error: "No tienes permiso para crear un ciclo de pedido coordinado por esta empresa." back_to_orders_list: "Volver a la lista de pedidos" + no_orders_found: "No se encontraron pedidos" order_information: "información del pedido" date_completed: "Fecha de finalización" amount: "Cantidad" @@ -2078,6 +2294,7 @@ es: choose: Escoger resolve_errors: Resuelve los siguientes errores more_items: "+ %{count} Más" + default_card_updated: Tarjeta predeterminada actualizada admin: enterprise_limit_reached: "Has alcanzado el límite estándar de organizaciones por cuenta. Escriba a %{contact_email} si necesita aumentarlo." modals: @@ -2201,6 +2418,11 @@ es: resolve: Resolver new_tag_rule_dialog: select_rule_type: "Selecciona un tipo de regla:" + resend_user_email_confirmation: + resend: "Reenviar" + sending: "Reenviar..." + done: "Reenvio hecho ✓" + failed: "Reenvio fallido ✗" out_of_stock: reduced_stock_available: Stock reducido disponible out_of_stock_text: > @@ -2244,7 +2466,9 @@ es: Esto establecerá el nivel de stock a cero en todos los productos para este organización no está presente en el archivo subido. order_cycles: + create_failure: "Error al crear el ciclo de pedido" update_success: 'Se ha actualizado su ciclo de pedido.' + update_failure: "Error al actualizar el ciclo de pedido" no_distributors: No hay distribuidores en este ciclo de pedido. Este ciclo de pedido no será visible para las consumidoras hasta que agregues uno. ¿Deseas continuar guardando este ciclo de pedido? ' enterprises: producer: "Productora" @@ -2265,8 +2489,28 @@ es: my_account: "Mi cuenta" date: "Fecha" time: "Hora" + layouts: + admin: + header: + store: Almacenar admin: + product_properties: + index: + inherits_properties_checkbox_hint: "¿Heredar propiedades desde %{supplier}? (a menos que sea anulado arriba)" orders: + index: + listing_orders: "Pedidos de listado" + new_order: "Nuevo pedido" + capture: "Captura" + ship: "Envío" + edit: "Editar" + note: "Nota" + first: "primero" + last: "Último" + previous: "Anterior" + next: "Siguiente" + loading: "Cargando" + no_orders_found: "No se encontraron pedidos" invoice: issued_on: Emitido el tax_invoice: FACTURA DE IMPUESTOS @@ -2304,7 +2548,8 @@ es: payments: source_forms: stripe: - no_payment_via_admin_backend: La creación de pagos basados ​​en Stripe desde el administrador no es posible en este momento. + error_saving_payment: Error al guardar el pago + submitting_payment: Enviando pago... products: new: title: 'Nuevo producto' @@ -2321,10 +2566,11 @@ es: unit: Unidad display_as: Mostrar como category: Categoría - tax_category: Categoría del impuesto - inherits_properties?: Hereda propiedades? + tax_category: Categoría de impuestos + inherits_properties?: ¿Hereda propiedades? available_on: Disponible en - av_on: "Disp. en" + av_on: "Av. En" + import_date: "Fecha de importación" products_variant: variant_has_n_overrides: "Esta variante tiene %{n} override(s)" new_variant: "Nueva variante" @@ -2337,17 +2583,28 @@ es: display_as: display_as: Mostrar como reports: + table: + select_and_search: "Selecciona filtros y haz clic en BUSCAR para acceder a tus datos." bulk_coop: bulk_coop_supplier_report: 'Bulk Co-op - Totales por Proveedor' bulk_coop_allocation: 'Bulk Co-op - Asignación' bulk_coop_packing_sheets: 'Bulk Co-op - Hojas de Empaquetado' bulk_coop_customer_payments: 'Bulk Co-op - Pagos de las Consumidoras' - shared: - configuration_menu: - stripe_connect: Conectar con Stripe + users: + email_confirmation: + confirmation_pending: "La confirmación por correo electrónico está pendiente. Hemos enviado un correo electrónico de confirmación a %{address}." variants: autocomplete: producer_name: Productora + general_settings: + edit: + legal_settings: "Configuraciones legales" + cookies_consent_banner_toggle: "Mostrar el banner de consentimiento de cookies" + privacy_policy_url: "Vínculo con la Política de privacidad" + enterprises_require_tos: "Las organizaciones deben aceptar los Términos del Servicio" + cookies_policy_matomo_section: "Mostrar la sección de Matomo en la página de política de cookies" + cookies_policy_ga_section: "Mostrar la sección de Google Analytics en la página de la política de cookies" + footer_tos_url: "URL de términos y servicios" checkout: payment: stripe: @@ -2361,6 +2618,8 @@ es: js_format: 'yy-mm-dd' inventory: Inventario orders: + edit: + login_to_view_order: "Por favor inicie sesión para ver su pedido." bought: item: "Pedido en este ciclo de pedido" order_mailer: @@ -2452,5 +2711,9 @@ es: total: Total paid?: ¿Pagado? view: Ver + saved_cards: + delete?: ¿Borrar? + cards: + authorised_shops: Tiendas autorizadas localized_number: invalid_format: tiene un formato invalido. Por favor introduzca un numero. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 81b828e7e97..e7980fab899 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -64,6 +64,9 @@ fr: user_passwords: spree_user: updated_not_active: "Votre mot de passe a bien été réinitialisé, mais votre email n'a pas encore été confirmé." + models: + order_cycle: + cloned_order_cycle_name: "Copie de %{order_cycle}" enterprise_mailer: confirmation_instructions: subject: "Confirmez l'adresse email pour %{enterprise}" @@ -71,9 +74,26 @@ fr: subject: "%{enterprise} est maintenant sur %{sitename}" invite_manager: subject: "%{enterprise} vous a invité comme manager" + order_mailer: + cancel_email: + dear_customer: "Cher Acheteur," + instructions: "Votre commande a été ANNULEE. Veuillez en prendre note et conserver pour preuve si besoin cette confirmation." + order_summary_canceled: "Résumé de la commande [ANNULEE]" + subject: "Annulation de Commande" + subtotal: "Sous-total : %{subtotal}" + total: "Total Commande : %{total}" producer_mailer: order_cycle: subject: "Rapport de cycle de vente pour %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Cher Acheteur," + instructions: "Votre commande a été expédiée" + shipment_summary: "Résumé de l'envoi" + subject: "Notification d'expédition" + thanks: "Merci pour votre commande." + track_information: "Informations de suivi : %{tracking}" + track_link: "Lien de suivi : %{url}" subscription_mailer: placement_summary_email: subject: Un résumé des dernières commandes récurrentes passées @@ -136,6 +156,7 @@ fr: free_trial: "Utilisation contre contribution libre" plus_tax: "plus TVA" min_bill_turnover_desc: "Quand le chiffre d'affaire dépasse %{mbt_amount}" + more: "Plus" say_no: "Non" say_yes: "Oui" then: puis @@ -197,12 +218,12 @@ fr: actions: create_and_add_another: "Créer et ajouter nouveau" admin: - begins_at: Commence à + begins_at: Commence begins_on: Commence le customer: Acheteur date: Date email: Email - ends_at: Termine à + ends_at: Termine ends_on: Termine le name: Nom on_hand: En stock @@ -379,8 +400,10 @@ fr: producer_signup_page: Page d'inscription Producteur hub_signup_page: Page d'inscription Hub group_signup_page: Page d'inscription Groupe + main_links: Liens du menu principal footer_and_external_links: Pied de page et Liens Externes your_content: Votre contenu + user_guide: Guide utilisateur enterprise_fees: index: title: Marges et Commissions @@ -667,8 +690,8 @@ fr: allow_order_changes_true: "Les acheteurs peuvent modifier / valider leurs commandes tant que le cycle de vente est ouvert" enable_subscriptions: "Abonnements" enable_subscriptions_tip: "Activer la fonction abonnements?" - enable_subscriptions_false: "Désactivée" - enable_subscriptions_true: "Activée" + enable_subscriptions_false: "Désactivé" + enable_subscriptions_true: "Activé" shopfront_message: Message d'accueil boutique ouverte shopfront_message_placeholder: > Vous pouvez ici expliquer à vos acheteurs comment votre boutique fonctionne. @@ -766,6 +789,9 @@ fr: search_placeholder: Recherche par nom manage: Gérer manage_link: Paramètres + producer?: "Producteur ?" + package: "Pack" + status: "Statut" new_form: owner: Manager principal owner_tip: Le manager principal est l'individu qui porte la responsabilité principale de l'entreprise dans le contexte de l'utilisation d'Open Food France. @@ -777,6 +803,14 @@ fr: new: title: Nouvelle entreprise back_link: Revenir à la liste des entreprises + remove_logo: + remove: "Supprimer l'image" + removed_successfully: "Logo supprimé avec succès" + immediate_removal_warning: "Le logo sera supprimé juste après votre confirmation." + remove_promo_image: + remove: "Supprimer l'image" + removed_successfully: "Bannière supprimée avec succès" + immediate_removal_warning: "La bannière sera supprimée juste après votre confirmation." welcome: welcome_title: Bienvenue sur Open Food France ! welcome_text: 'Vous avez créé avec succès ' @@ -809,6 +843,9 @@ fr: save_reload: Sauvegarder et rafraichir la page coordinator_fees: add: Ajouter commission coordinateur + filters: + search_by_order_cycle_name: "Recherche par nom de Cycle de Vente..." + involving: "Concernant" form: incoming: Produits entrants (pouvant être mis en vente par les hubs) supplier: Fournisseur @@ -822,7 +859,6 @@ fr: delivery_details: Précisions retrait / livraison debug_info: Informations de débogage index: - involving: Concernant schedule: Rythme d'abonnement schedules: Rythmes d'abonnement adding_a_new_schedule: Ajouter un nouveau rythme d'abonnement @@ -1054,7 +1090,7 @@ fr: footer_legal_visit: "Nous trouver sur" footer_legal_text_html: "Open Food Network est une plateforme logicielle open source, libre et gratuite. Nos données sont protégées sous licence %{content_license} et notre code sous %{code_license}." footer_data_text_with_privacy_policy_html: "Nous prenons soin de vos données. Voir notre %{privacy_policy} et %{cookies_policy}." - footer_data_text_without_privacy_policy_html: "Nous prenons soin de vos données. Voire notre %{cookies_policy}." + footer_data_text_without_privacy_policy_html: "Nous prenons soin de vos données. Voir notre %{cookies_policy}." footer_data_privacy_policy: "politique de confidentialité" footer_data_cookies_policy: "politique de cookies" footer_skylight_dashboard_html: Les informations de performance sont disponibles sur %{dashboard}. @@ -1093,6 +1129,20 @@ fr: ticket_column_item: "Produit" ticket_column_unit_price: "Prix unitaire" ticket_column_total_price: "Prix total" + menu_1_title: "Boutiques" + menu_1_url: "/shops" + menu_2_title: "Carte" + menu_2_url: "/map" + menu_3_title: "Producteurs" + menu_3_url: "/producers" + menu_4_title: "Groupes" + menu_4_url: "/groups" + menu_5_title: "A propos" + menu_5_url: "https://apropos.openfoodfrance.org/" + menu_6_title: "Blog" + menu_6_url: "https://apropos.openfoodfrance.org/blog/" + menu_7_title: "Support" + menu_7_url: "https://apropos.openfoodfrance.org/support/" logo: "Logo (640x130)" logo_mobile: "Logo smartphone (75x26)" logo_mobile_svg: "Logo smartphone (SVG)" @@ -1190,18 +1240,16 @@ fr: essential_cookies: "Cookies essentiels" essential_cookies_desc: "Les cookies suivants sont nécessaires au fonctionnement du site openfoodfrance.org." essential_cookies_note: "Les cookies contiennent un identifiant unique, mais pas d'autres données. Vos emails et mots de passe par exemple ne sont jamais exposés dans les cookies!" - cookie_name: "Nom du cookie" - cookie_domain: "Déposé par" - cookie_desc: "Description" + cookie_domain: "Déposé par!" cookie_session_desc: "Utilisé pour garder en mémoire l'utilisateur d'une page à l'autre lors de la navigation sur le site, ou pour se souvenir des produits dans le panier." cookie_consent_desc: "Utilisé pour se souvenir du consentement de l'utilisation à l'utilisation de cookies." cookie_remember_me_desc: "Utilisé si l'utilisateur a cliqué sur \"se souvenir de moi\" (pour ne pas avoir à se reconnecter à chaque fois). Ce cookie est automatiquement supprimé après 12 jours. Si l'utilisateur souhaite supprimer ce cookie, il n'a qu'à se déconnecter. Si l'utilisateur ne souhaite pas que ce cookie soit installé sur son terminal, il suffit de ne pas cocher la case \"se souvenir de moi\" au moment de la connexion." cookie_openstreemap_desc: "Utilisé par le logiciel de cartographie open source et ami Open Street Map (qui permet l'affichage de la carte sur Open Food France) pour assurer qu'il ne reçoit pas trop de requêtes sur un laps de temps déterminé, et éviter ainsi les risques d'abus de leurs services." cookie_stripe_desc: "Utilisé par le terminal de payement en ligne Stripe (proposé aux utilisateurs d'Open Food France) https://stripe.com/fr/cookies-policy/legal. Même si toutes les boutiques n'utilisent pas Stripe, c'est une bonne pratique en matière de sécurité d'appliquer ce cookie sur toutes les pages vues. Stripe construit probablement une image des pages qui ont un quelconque lien avec l'API connectant Open Food France à leur système de paiement pour détecter les comportements anormaux pouvant suggérer un risque de fraude. Donc ce cookie a un rôle qui va au-delà de la simple fourniture d'un système de paiement. Le supprimer pourrait affecter la sécurité du service. Pour en savoir plus sur la politique de confidentialité de Stripe: https://stripe.com/fr/privacy." statistics_cookies: "Cookies d'analyse de navigation" - statistics_cookies_desc: "Ces cookies ne sont pas obligatoires, mais nous permettent de mieux comprendre votre usage de la plateforme, les endroits où vous bloquez, les fonctionnalités qui semble vous manquer, ou que vous n'utilisez jamais, afin de fournir le service le plus adapté possible aux besoins des utilisateurs." - statistics_cookies_analytics_desc: "Nous utilisons Google Analytics, pas vraiment par choix, mais simplement parce que c'était l'outil d'analyse connecté par défaut via Spree, le logiciel e-commerce open source sur lequel nous avons construit. Mais nous espérons pouvoir rapidement migrer vers Matomo (anciennement Piwik), outil d'analyse open source engagé sur le respect de la vie privée des utilisateurs." - statistics_cookies_matomo_desc: "Nous utilisons Matomo (anciennement Piwik), outil d'analyse open source engagé sur le respect de la vie privée des utilisateurs." + statistics_cookies_desc: "Ces cookies ne sont pas obligatoires, mais nous permettent de mieux comprendre votre usage de la plateforme, les endroits où vous bloquez, les fonctionnalités qui semblent vous manquer, ou que vous n'utilisez jamais, afin de fournir le service le plus adapté possible aux besoins des utilisateurs." + statistics_cookies_analytics_desc_html: "Pour analyser les données concernant votre usage de la plateforme, nous utilisons Google Analytics, pas vraiment par choix, mais simplement parce que c'était l'outil d'analyse connecté par défaut via Spree, le logiciel e-commerce open source sur lequel nous avons construit. Mais nous espérons pouvoir rapidement migrer vers Matomo (anciennement Piwik), outil d'analyse open source compatible RGPD et engagé sur le respect de la vie privée des utilisateurs." + statistics_cookies_matomo_desc_html: "Pour analyser les données concernant votre usage de la plateforme, nous utilisons Matomo(anciennement Piwik), outil d'analyse open source compatible RGPD et engagé sur le respect de la vie privée des utilisateurs." cookie_analytics_utma_desc: "Utilisé pour distinguer les utilisateurs et les sessions. Ce cookie est installé quand la librairie Javascript s'exécute et qu'aucun cookie __utma n'existe déjà. Le cookie est mis à jour à chaque fois que des données sont envoyées à Google Analytics." cookie_analytics_utmt_desc: "Utilisé pour limiter le taux de requêtes." cookie_analytics_utmb_desc: "Utilisé pour distinguer les nouvelles sessions/visites. Ce cookie est installé quand la librairie Javascript s'exécute et qu'aucun cookie __utmb n'existe déjà. Le cookie est mis à jour à chaque fois que des données sont envoyées à Google Analytics. " @@ -1215,9 +1263,15 @@ fr: disabling_cookies_firefox_link: "https://support.mozilla.org/fr/kb/activer-desactiver-cookies-preferences" disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647?hl=fr" disabling_cookies_ie_link: "https://support.microsoft.com/fr-fr/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/fr-ww/cookies/" + disabling_cookies_note: "Mais gardez bien en tête que si vous supprimez ou modifiez un des cookies essentiels utilisés par Open Food France, le site ne fonctionnera pas correctement, vous ne pourrez pas ajouter des produits à votre panier ni finaliser votre commande par exemple." cookies_banner: + cookies_usage: "Ce site utilise des cookies pour rendre votre navigation fluide et sécurisée, et nous aider à comprendre l'usage que vous faites de la plateforme afin d'améliorer les fonctionnalités offertes." cookies_definition: "Les cookies sont de tout petits fichiers texte qui sont stockés sur votre ordinateur quand vous naviguez sur certains sites web." + cookies_desc: "Nous n'utilisons que les cookies nécessaires pour vous offrir un service de vente/achat de produits alimentaires en ligne performant. Nous ne vendons aucune de vos données. Nous utilisons ces cookies principalement pour vous permettre de rester connecté(e) après votre première connexion, ou encore pour mémoriser les produits dans votre panier lorsque vous n'êtes pas connecté(e). Si vous naviguez sur le site sans cliquer sur \"Accepter les cookies\", cela vaut consentement à l'utilisation des cookies nécessaires au bon fonctionnement du site." + cookies_policy_link_desc: "Si vous voulez en savoir plus, consultez notre" cookies_policy_link: "politique de cookies" + cookies_accept_button: "Accepter les cookies" home_shop: Faire mes courses brandstory_headline: "Des aliments porteurs de sens." brandstory_intro: "Parfois, le meilleur moyen de réparer le système, c'est d'en inventer un autre..." @@ -1338,6 +1392,7 @@ fr: email_so_edit_true_html: "Vous pouvez effectuer des modifications jusqu'à la fermeture de la période de commande le %{orders_close_at}." email_so_edit_false_html: "Vous pouvez consulter les détails de cette commande à tout moment." email_so_contact_distributor_html: "Pour toute question contactez %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "Cette commande a été automatiquement créée en votre nom. Vous pouvez effectuer des modifications sur cette commande jusqu'à fermeture de la période de commande le %{orders_close_at} en contactant %{distributor} à %{email}." email_so_confirmation_intro_html: "Votre commande auprès de %{distributor} est maintenant confirmée" email_so_confirmation_explainer_html: "Cette commande a été automatiquement passée pour vous dans le cadre de votre abonnement, et a maintenant été confirmée." email_so_confirmation_details_html: "Voici les détails concernant cette commande auprès de %{distributor}:" @@ -1599,6 +1654,7 @@ fr: error_number: "saisir un nombre" error_email: "saisir une adresse email" error_not_found_in_database: "%{name} n'existe pas" + error_not_primary_producer: "%{name}n'est pas enregistré comme \"producteur\"" error_no_permission_for_enterprise: "\"%{name}\" : vous n'avez pas les droits requis pour gérer les produits de cette entreprise" item_handling_fees: "Frais logistiques (inclus dans le prix affiché)" january: "Janvier" @@ -1825,31 +1881,31 @@ fr: you_have_no_orders_yet: "Vous n'avez pas encore de commande" running_balance: "Solde courant" outstanding_balance: "Solde restant" - admin_entreprise_relationships: "Permissions Inter-entreprises" - admin_entreprise_relationships_everything: "Tout" - admin_entreprise_relationships_permits: "autorise" - admin_entreprise_relationships_seach_placeholder: "Chercher" - admin_entreprise_relationships_button_create: "Créer" - admin_entreprise_groups: "Groupes d'entreprises" - admin_entreprise_groups_name: "Nom" - admin_entreprise_groups_owner: "Manager principal" - admin_entreprise_groups_on_front_page: "Sur la page d'accueil?" - admin_entreprise_groups_entreprise: "Entreprises" - admin_entreprise_groups_data_powertip: "Le manager principal en charge de ce groupe." - admin_entreprise_groups_data_powertip_logo: "Il s'agit du logo du groupe" - admin_entreprise_groups_data_powertip_promo_image: "Cette image est affichée en haut du profil Groupe." - admin_entreprise_groups_contact: "Contact" - admin_entreprise_groups_contact_phone_placeholder: "ex: 06 13 24 35 46" - admin_entreprise_groups_contact_address1_placeholder: "ex: 24 rue de la croix verte" - admin_entreprise_groups_contact_city: "Ville" - admin_entreprise_groups_contact_city_placeholder: "ex: Bordeaux" - admin_entreprise_groups_contact_zipcode: "Code postal" - admin_entreprise_groups_contact_zipcode_placeholder: "ex: 14120" - admin_entreprise_groups_contact_state_id: "Département" - admin_entreprise_groups_contact_country_id: "Pays" - admin_entreprise_groups_web: "Liens web" - admin_entreprise_groups_web_twitter: "ex: @OpenFoodNet_fr" - admin_entreprise_groups_web_website_placeholder: "ex: www.monepicerieenligne.fr" + admin_enterprise_relationships: "Permissions Inter-entreprises" + admin_enterprise_relationships_everything: "Tout" + admin_enterprise_relationships_permits: "autorise" + admin_enterprise_relationships_seach_placeholder: "Rechercher" + admin_enterprise_relationships_button_create: "Créer" + admin_enterprise_groups: "Groupes d'entreprises" + admin_enterprise_groups_name: "Produit/Variante" + admin_enterprise_groups_owner: "Manager principal" + admin_enterprise_groups_on_front_page: "Sur la page d'accueil?" + admin_enterprise_groups_enterprise: "Entreprises" + admin_enterprise_groups_data_powertip: "Le manager principal en charge de ce groupe." + admin_enterprise_groups_data_powertip_logo: "Il s'agit du logo du groupe" + admin_enterprise_groups_data_powertip_promo_image: "Cette image est affichée en haut du profil Groupe." + admin_enterprise_groups_contact: "Contact" + admin_enterprise_groups_contact_phone_placeholder: "ex: 06 13 24 35 46" + admin_enterprise_groups_contact_address1_placeholder: "ex: 24 rue de la croix verte" + admin_enterprise_groups_contact_city: "Ville" + admin_enterprise_groups_contact_city_placeholder: "ex: Nantes" + admin_enterprise_groups_contact_zipcode: "Code postal" + admin_enterprise_groups_contact_zipcode_placeholder: "ex: 44000" + admin_enterprise_groups_contact_state_id: "Département" + admin_enterprise_groups_contact_country_id: "Pays" + admin_enterprise_groups_web: "Liens web" + admin_enterprise_groups_web_twitter: "ex: @OpenFoodNet_fr" + admin_enterprise_groups_web_website_placeholder: "ex: www.maferme.fr" admin_order_cycles: "Gérer les cycles de vente" open: "Ouvre" close: "Ferme" @@ -1985,7 +2041,7 @@ fr: report_payment_totals: 'Total des paiements' report_all: 'tous' report_order_cycle: "Cycle de vente:" - report_entreprises: "Entreprises:" + report_enterprises: "Entreprises:" report_users: "Managers:" report_tax_rates: TVA par taux report_tax_types: TVA par type de produit/service @@ -2469,7 +2525,8 @@ fr: payments: source_forms: stripe: - no_payment_via_admin_backend: La création de paiements via Stripe depuis le back office d'administration n'est pas possible pour le moment + error_saving_payment: Erreur à l'enregistrement du paiement + submitting_payment: Envoi du paiement... products: new: title: 'Nouveau Produit' @@ -2518,7 +2575,12 @@ fr: producer_name: Producteur general_settings: edit: + legal_settings: "Configuration légales" + cookies_consent_banner_toggle: "Afficher la bannière de consentement à l'utilisation des cookies" + privacy_policy_url: "URL de la politique de confidentialité" enterprises_require_tos: "Les entreprises doivent accepter les Conditions Générales d'Utilisation" + cookies_policy_matomo_section: "Afficher la section Matomo sur la politique de cookies" + cookies_policy_ga_section: "Afficher la section Google Analytics sur la politique de cookies" footer_tos_url: "Conditions d'utilisation URL" checkout: payment: diff --git a/config/locales/fr_CA.yml b/config/locales/fr_CA.yml index 1c300036356..521ce832418 100644 --- a/config/locales/fr_CA.yml +++ b/config/locales/fr_CA.yml @@ -65,6 +65,9 @@ fr_CA: user_passwords: spree_user: updated_not_active: "Votre mot de passe a été mis à jour, mais votre email n'a pas encore été confirmé." + models: + order_cycle: + cloned_order_cycle_name: "Copie de %{order_cycle}" enterprise_mailer: confirmation_instructions: subject: "Confirmez l'adresse email pour %{enterprise}" @@ -72,9 +75,26 @@ fr_CA: subject: "%{enterprise} est maintenant sur %{sitename}" invite_manager: subject: "%{enterprise} vous a invité comme manager" + order_mailer: + cancel_email: + dear_customer: "Cher Acheteur," + instructions: "Votre commande a été ANNULEE. Veuillez en prendre note et conserver pour preuve si besoin cette confirmation." + order_summary_canceled: "Résumé de la commande [ANNULEE]" + subject: "Annulation de Commande" + subtotal: "Sous-total : %{subtotal}" + total: "Total Commande : %{total}" producer_mailer: order_cycle: subject: "Rapport de cycle de vente pour %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Cher Acheteur," + instructions: "Votre commande a été expédiée" + shipment_summary: "Résumé de l'envoi" + subject: "Notification d'expédition" + thanks: "Merci pour votre commande." + track_information: "Informations de suivi :%{tracking}" + track_link: "Lien de suivi :%{url}" subscription_mailer: placement_summary_email: subject: Un résumé des dernières commandes récemment passées @@ -137,6 +157,7 @@ fr_CA: free_trial: "essai gratuit" plus_tax: "plus taxe" min_bill_turnover_desc: "Quand le chiffre d'affaire dépasse %{mbt_amount}" + more: "Plus" say_no: "Non" say_yes: "Oui" then: puis @@ -380,8 +401,10 @@ fr_CA: producer_signup_page: Page d'inscription Producteur hub_signup_page: Page d'inscription Hub group_signup_page: Page d'inscription Groupe + main_links: Liens du menu principal footer_and_external_links: Pied de page et Liens Externes your_content: Votre contenu + user_guide: Guide utilisateur enterprise_fees: index: title: Marges et Commissions @@ -455,6 +478,7 @@ fr_CA: inventory_template: Télécharger le modèle pour import dans catalogue boutique category_values: Valeurs disponibles pour les catégories product_categories: Catégorie Produit + tax_categories: Taxe applicable shipping_categories: Condition de transport import: review: Vérifier @@ -766,6 +790,9 @@ fr_CA: search_placeholder: Recherche par nom manage: Gérer manage_link: Paramètres + producer?: "Producteur ?" + package: "Pack" + status: "Statut" new_form: owner: Gérant owner_tip: L'utilisateur principal est l'individu qui porte la responsabilité principale de l'entreprise dans le contexte de l'utilisation d'Open Food Network. @@ -777,6 +804,14 @@ fr_CA: new: title: Nouvelle entreprise back_link: Revenir à la liste des entreprises + remove_logo: + remove: "Supprimer l'image" + removed_successfully: "Logo supprimé avec succès" + immediate_removal_warning: "Le logo sera supprimé juste après votre confirmation." + remove_promo_image: + remove: "Supprimer l'image" + removed_successfully: "Bannière supprimée avec succès" + immediate_removal_warning: "La bannière sera supprimée juste après votre confirmation." welcome: welcome_title: Bienvenue sur Open Food Network ! welcome_text: 'Vous avez créé avec succès ' @@ -809,6 +844,11 @@ fr_CA: save_reload: Sauvegarder et rafraichir la page coordinator_fees: add: Ajouter commission coordinateur + filters: + search_by_order_cycle_name: "Recherche par nom de Cycle de Vente..." + involving: "Concernant" + any_enterprise: "Toutes les entreprises" + any_schedule: "Tous" form: incoming: Produits entrants (pouvant être mis en vente par les hubs) supplier: Fournisseur @@ -822,7 +862,6 @@ fr_CA: delivery_details: Précisions retrait / livraison debug_info: Informations de débogage index: - involving: Concernant schedule: Rythme d'abonnement schedules: Rythmes d'abonnement adding_a_new_schedule: Ajouter un nouveau rythme d'abonnement @@ -1013,6 +1052,11 @@ fr_CA: stripe_connect_fail: Désolé, la connexion de votre compte Stripe a échoué :-( stripe_connect_settings: resource: Configuration de Stripe Connect + api: + enterprise_logo: + destroy_attachment_does_not_exist: "Aucun logo trouvé " + enterprise_promo_image: + destroy_attachment_does_not_exist: "Aucune bannière trouvée " checkout: already_ordered: cart: "panier" @@ -1092,6 +1136,18 @@ fr_CA: ticket_column_item: "Produit" ticket_column_unit_price: "Prix unitaire" ticket_column_total_price: "Prix total" + menu_1_title: "Boutiques" + menu_1_url: "/shops" + menu_2_title: "Carte" + menu_2_url: "/map" + menu_3_title: "Producteurs" + menu_3_url: "/producers" + menu_4_title: "Groupes" + menu_4_url: "/groups" + menu_5_title: "A propos" + menu_5_url: "http://www.openfoodnetwork.org" + menu_6_title: "Se connecter" + menu_7_title: "Apprendre" logo: "Logo (640x130)" logo_mobile: "Logo smartphone (75x26)" logo_mobile_svg: "Logo smartphone (SVG)" @@ -1107,6 +1163,7 @@ fr_CA: footer_email: "Email" footer_links_md: "Liens" footer_about_url: "A propos URL" + user_guide_link: "Lien vers le guide utilisateur" name: Nom first_name: Prénom last_name: Nom de famille @@ -1189,18 +1246,17 @@ fr_CA: essential_cookies: "Cookies essentiels" essential_cookies_desc: "Les cookies suivants sont nécessaires au fonctionnement du site openfoodfrance.org." essential_cookies_note: "Les cookies contiennent un identifiant unique, mais pas d'autres données. Vos emails et mots de passe par exemple ne sont jamais exposés dans les cookies!" - cookie_name: "Nom du cookie" - cookie_domain: "Déposé par" - cookie_desc: "Description" + cookie_domain: "Déposé par!" cookie_session_desc: "Utilisé pour garder en mémoire l'utilisateur d'une page à l'autre lors de la navigation sur le site, ou pour se souvenir des produits dans le panier." cookie_consent_desc: "Utilisé pour se souvenir du consentement de l'utilisation à l'utilisation de cookies." cookie_remember_me_desc: "Utilisé si l'utilisateur a cliqué sur \"se souvenir de moi\" (pour ne pas avoir à se reconnecter à chaque fois). Ce cookie est automatiquement supprimé après 12 jours. Si l'utilisateur souhaite supprimer ce cookie, il n'a qu'à se déconnecter. Si l'utilisateur ne souhaite pas que ce cookie soit installé sur son terminal, il suffit de ne pas cocher la case \"se souvenir de moi\" au moment de la connexion." cookie_openstreemap_desc: "Utilisé par le logiciel de cartographie open source pour assurer qu'il ne reçoit pas trop de requêtes sur un laps de temps déterminé, et éviter ainsi les risques d'abus de leurs services." cookie_stripe_desc: "Utilisé par le terminal de payement en ligne Stripe (proposé aux utilisateurs d'Open Food France) https://stripe.com/fr/cookies-policy/legal. Même si toutes les boutiques n'utilisent pas Stripe, c'est une bonne pratique en matière de sécurité d'appliquer ce cookie sur toutes les pages vues. Stripe construit probablement une image des pages qui ont un quelconque lien avec l'API connectant Open Food France à leur système de paiement pour détecter les comportements anormaux pouvant suggérer un risque de fraude. Donc ce cookie a un rôle qui va au-delà de la simple fourniture d'un système de paiement. Le supprimer pourrait affecter la sécurité du service. Pour en savoir plus sur la politique de confidentialité de Stripe: https://stripe.com/fr/privacy." statistics_cookies: "Cookies d'analyse de navigation" - statistics_cookies_desc: "Ces cookies ne sont pas obligatoires, mais nous permettent de mieux comprendre votre usage de la plateforme, les endroits où vous bloquez, les fonctionnalités qui semble vous manquer, ou que vous n'utilisez jamais, afin de fournir le service le plus adapté possible aux besoins des utilisateurs." - statistics_cookies_analytics_desc: "Nous utilisons Google Analytics, pas vraiment par choix, mais simplement parce que c'était l'outil d'analyse connecté par défaut via Spree, le logiciel e-commerce open source sur lequel nous avons construit. Mais nous espérons pouvoir rapidement migrer vers Matomo (anciennement Piwik), outil d'analyse open source engagé sur le respect de la vie privée des utilisateurs." - statistics_cookies_matomo_desc: "Nous utilisons Matomo (anciennement Piwik), outil d'analyse open source engagé sur le respect de la vie privée des utilisateurs." + statistics_cookies_desc: "Ces cookies ne sont pas obligatoires, mais nous permettent de mieux comprendre votre usage de la plateforme, les endroits où vous bloquez, les fonctionnalités qui semblent vous manquer, ou que vous n'utilisez jamais, afin de fournir le service le plus adapté possible aux besoins des utilisateurs." + statistics_cookies_analytics_desc_html: "Pour analyser les données concernant votre usage de la plateforme, nous utilisons Google Analytics, pas vraiment par choix, mais simplement parce que c'était l'outil d'analyse connecté par défaut via Spree, le logiciel e-commerce open source sur lequel nous avons construit. Mais nous espérons pouvoir rapidement migrer vers Matomo (ex anciennement Piwik), outil d'analyse open source compatible RGPD et engagé sur le respect de la vie privée des utilisateurs." + statistics_cookies_matomo_desc_html: "Pour analyser les données concernant votre usage de la plateforme, nous utilisons Matomo (anciennement Piwik), outil d'analyse open source compatible RGPD et engagé sur le respect de la vie privée des utilisateurs." + statistics_cookies_matomo_optout: "Vous ne voulez pas que vos données soient analysées par Matomo ? Nous ne collectons aucune donnée personnelle, et Matomo nous aide à améliorer le service que nous vous offrons, mais nous respectons votre choix :-)" cookie_analytics_utma_desc: "Utilisé pour distinguer les utilisateurs et les sessions. Ce cookie est installé quand la librairie Javascript s'exécute et qu'aucun cookie __utma n'existe déjà. Le cookie est mis à jour à chaque fois que des données sont envoyées à Google Analytics." cookie_analytics_utmt_desc: "Utilisé pour limiter le taux de requêtes." cookie_analytics_utmb_desc: "Utilisé pour distinguer les nouvelles sessions/visites. Ce cookie est installé quand la librairie Javascript s'exécute et qu'aucun cookie __utmb n'existe déjà. Le cookie est mis à jour à chaque fois que des données sont envoyées à Google Analytics. " @@ -1210,12 +1266,19 @@ fr_CA: cookie_matomo_heatmap_desc: "Utilisé par Matomo pour enregistrer les sessions et \"cartes thermiques\" (représentations graphiques des données)" cookie_matomo_ignore_desc: "Cookie utilisé pour se souvenir qu'un utilisateur a souhaité explicitement que sa navigation ne soit pas analysée par Matomo, et exclure cet utilisateur du suivi du site." disabling_cookies_header: "Mises en garde sur la désactivation des cookies" + disabling_cookies_desc: "En tant qu'utilisateur, vous pouvez toujours autoriser, bloquer ou supprimer tous les cookies utilisés par Open Food Network Canada ou tout autre site web via les paramètres de votre navigateur. Chaque navigateur a un chemin spécifique pour effectuer cette désactivation:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" disabling_cookies_note: "Mais gardez bien en tête que si vous supprimez ou modifiez un des cookies essentiels utilisés par Open Food France, le site ne fonctionnera pas correctement, vous ne pourrez pas ajouter des produits à votre panier ni finaliser votre commande par exemple." cookies_banner: cookies_usage: "Ce site utilise des cookies pour rendre votre navigation fluide et sécurisée, et nous aider à comprendre l'usage que vous faites de la plateforme afin d'améliorer les fonctionnalités offertes." cookies_definition: "Les cookies sont de tout petits fichiers texte qui sont stockés sur votre ordinateur quand vous naviguez sur certains sites web." cookies_desc: "Nous n'utilisons que les cookies nécessaires pour vous offrir un service de vente/achat de produits alimentaires en ligne performant. Nous ne vendons aucune de vos données. Nous utilisons ces cookies principalement pour vous permettre de rester connecté(e) après votre première connexion, ou encore pour mémoriser les produits dans votre panier lorsque vous n'êtes pas connecté(e). Si vous naviguez sur le site sans cliquer sur \"Accepter les cookies\", cela vaut consentement à l'utilisation des cookies nécessaires au bon fonctionnement du site." + cookies_policy_link_desc: "Si vous voulez en savoir plus, consultez notre" cookies_policy_link: "politique de cookies" + cookies_accept_button: "Accepter les cookies" home_shop: Faire mes courses brandstory_headline: "Des aliments porteurs de sens." brandstory_intro: "Parfois, le meilleur moyen de réparer le système, c'est d'en inventer un autre..." @@ -1336,6 +1399,7 @@ fr_CA: email_so_edit_true_html: "Vous pouvez effectuer des modifications jusqu'à la fermeture de la période de commande le %{orders_close_at}." email_so_edit_false_html: "Vous pouvez consulter les détails de cette commande à tout moment." email_so_contact_distributor_html: "Pour toute question contactez %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "Cette commande a été automatiquement créée en votre nom. Vous pouvez effectuer des modifications sur cette commande jusqu'à fermeture de la période de commande le%{orders_close_at} en contactant%{distributor} à%{email} ." email_so_confirmation_intro_html: "Votre commande auprès de %{distributor} est maintenant confirmée" email_so_confirmation_explainer_html: "Cette commande a été automatiquement passée pour vous dans le cadre de votre abonnement, et a maintenant été confirmée." email_so_confirmation_details_html: "Voici les détails concernant cette commande auprès de %{distributor}:" @@ -1597,6 +1661,7 @@ fr_CA: error_number: "saisir un nombre" error_email: "saisir une adresse email" error_not_found_in_database: "%{name} n'a pas été trouvé dans la base de donnée" + error_not_primary_producer: "%{name}n'est pas enregistré comme \"producteur\"" error_no_permission_for_enterprise: "\"%{name}\" : vous n'avez pas les droits requis pour gérer les produits de cette entreprise" item_handling_fees: "Frais logistiques (inclus dans le prix affiché)" january: "Janvier" @@ -1823,31 +1888,31 @@ fr_CA: you_have_no_orders_yet: "Vous n'avez pas encore de commande" running_balance: "Solde courant" outstanding_balance: "Solde restant" - admin_entreprise_relationships: "Permissions Inter-entreprises" - admin_entreprise_relationships_everything: "Tout" - admin_entreprise_relationships_permits: "autorise" - admin_entreprise_relationships_seach_placeholder: "Chercher" - admin_entreprise_relationships_button_create: "Créer" - admin_entreprise_groups: "Groupes d'entreprises" - admin_entreprise_groups_name: "Nom" - admin_entreprise_groups_owner: "Gérant" - admin_entreprise_groups_on_front_page: "Sur la page d'accueil?" - admin_entreprise_groups_entreprise: "Entreprises" - admin_entreprise_groups_data_powertip: "L'utilisateur principal en charge de ce groupe." - admin_entreprise_groups_data_powertip_logo: "Il s'agit du logo du groupe" - admin_entreprise_groups_data_powertip_promo_image: "Cette image est affichée en haut du profil Groupe." - admin_entreprise_groups_contact: "Contact" - admin_entreprise_groups_contact_phone_placeholder: "ex: 98 7654 3210" - admin_entreprise_groups_contact_address1_placeholder: "ex: 24 rue de la croix verte" - admin_entreprise_groups_contact_city: "Ville" - admin_entreprise_groups_contact_city_placeholder: "ex: Bordeaux" - admin_entreprise_groups_contact_zipcode: "Code postal" - admin_entreprise_groups_contact_zipcode_placeholder: "ex: 14120" - admin_entreprise_groups_contact_state_id: "Département" - admin_entreprise_groups_contact_country_id: "Pays" - admin_entreprise_groups_web: "Liens web" - admin_entreprise_groups_web_twitter: "ex: @OpenFoodNet_fr" - admin_entreprise_groups_web_website_placeholder: "ex: www.monepicerieenligne.fr" + admin_enterprise_relationships: "Permissions Inter-entreprises" + admin_enterprise_relationships_everything: "Tout" + admin_enterprise_relationships_permits: "autorise" + admin_enterprise_relationships_seach_placeholder: "Chercher" + admin_enterprise_relationships_button_create: "Créer" + admin_enterprise_groups: "Groupes d'entreprises" + admin_enterprise_groups_name: "Nom" + admin_enterprise_groups_owner: "Gérant" + admin_enterprise_groups_on_front_page: "Sur la page d'accueil?" + admin_enterprise_groups_enterprise: "Entreprises" + admin_enterprise_groups_data_powertip: "L'utilisateur principal en charge de ce groupe." + admin_enterprise_groups_data_powertip_logo: "Il s'agit du logo du groupe" + admin_enterprise_groups_data_powertip_promo_image: "Cette image est affichée en haut du profil Groupe." + admin_enterprise_groups_contact: "Contact" + admin_enterprise_groups_contact_phone_placeholder: "ex: 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "ex: 24 rue de la croix verte" + admin_enterprise_groups_contact_city: "Ville" + admin_enterprise_groups_contact_city_placeholder: "ex: Bordeaux" + admin_enterprise_groups_contact_zipcode: "Code postal" + admin_enterprise_groups_contact_zipcode_placeholder: "ex: 44000" + admin_enterprise_groups_contact_state_id: "Département" + admin_enterprise_groups_contact_country_id: "Pays" + admin_enterprise_groups_web: "Liens web" + admin_enterprise_groups_web_twitter: "ex: @OpenFoodNet_fr" + admin_enterprise_groups_web_website_placeholder: "ex: www.maferme.fr" admin_order_cycles: "Gérer les cycles de vente" open: "Ouvre" close: "Ferme" @@ -1983,7 +2048,7 @@ fr_CA: report_payment_totals: 'Total des paiements' report_all: 'tous' report_order_cycle: "Cycle de vente:" - report_entreprises: "Entreprises:" + report_enterprises: "Entreprises:" report_users: "Utilisateurs" report_tax_rates: Taux de taxes report_tax_types: Taux de taxes par type de produit/service @@ -2464,10 +2529,6 @@ fr_CA: account_id: Identifiant Compte business_name: Nom de l'entreprise charges_enabled: Frais activés - payments: - source_forms: - stripe: - no_payment_via_admin_backend: La création de paiements via Stripe depuis le back office d'administration n'est pas possible pour le moment products: new: title: 'Nouveau Produit' @@ -2516,6 +2577,9 @@ fr_CA: producer_name: Producteur general_settings: edit: + legal_settings: "Configuration légales" + cookies_consent_banner_toggle: "Afficher la bannière de consentement à l'utilisation des cookies" + privacy_policy_url: "URL de la politique de confidentialité" enterprises_require_tos: "Les entreprises doivent accepter les Conditions Générales d'Utilisation" cookies_policy_matomo_section: "Afficher la section Matomo sur la politique de cookies" cookies_policy_ga_section: "Afficher la section Google Analytics sur la politique de cookies" diff --git a/config/locales/it.yml b/config/locales/it.yml index fe81ff7f31c..9d46f3e7771 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1,25 +1,109 @@ it: + language_name: "Inglese" activerecord: attributes: spree/order: payment_state: Stato del pagamento shipment_state: Stato della spedizione + completed_at: Completo al + number: Numero + email: Mail consumatore + spree/payment: + amount: Quantità + order_cycle: + orders_close_at: Data chiusura errors: models: spree/user: attributes: email: taken: "Esiste già un account con questa email. Ti preghiamo di effettuare il login o impostare una nuova password." + order_cycle: + attributes: + orders_close_at: + after_orders_open_at: deve essere dopo la data di apertura + activemodel: + errors: + models: + subscription_validator: + attributes: + subscription_line_items: + at_least_one_product: "^Aggiungi almeno un prodotto" + not_available: "^%{name} non è disponibile nel programma selezionato" + ends_at: + after_begins_at: "deve essere dopo l'inizio il" + customer: + does_not_belong_to_shop: "non appartiene a %{shop}" + schedule: + not_coordinated_by_shop: "non è coordinato da %{shop}" + payment_method: + not_available_to_shop: "non è disponibile al %{shop}" + shipping_method: + not_available_to_shop: "non è disponibile al %{shop}" devise: + confirmations: + send_instructions: "Riceverai entro qualche minuto un'email con le istruzioni utili a confermare il tuo account." + failed_to_send: "C'è stato un errore nell'invio della tua mail di conferma." + resend_confirmation_email: "Re-invia mail di conferma" + confirmed: "Grazie per aver confermato la tua mail! Ora puoi effettuare il log in." + not_confirmed: "Il tuo indirizzo email non può essere confermato. Forse avevi già completato questo passaggio?" + user_registrations: + spree_user: + signed_up_but_unconfirmed: "Abbiamo inviato un link di conferma al tuo indirizzo email. Per favore apri il link per attivare il tuo account." failure: invalid: | Email o password non valida. La volta scorsa eri ospite? Forse devi creare un account o resettare la tua password. + unconfirmed: "Devi confermare il tuo account prima di continuare." + already_registered: "Questo indirizzo email è già registrato. Per favore effettua il log in per continuare, otorna indietro ed utilizza un altro indirizzo email." + user_passwords: + spree_user: + updated_not_active: "La tua password è stata resettata, ma la tua email non è ancora stata confermata." enterprise_mailer: confirmation_instructions: subject: "Per favore conferma l'indirizzo email per %{enterprise}" welcome: subject: "%{enterprise} è ora su %{sitename}" + invite_manager: + subject: "%{enterprise} ti a invitato ad essere un referente" + producer_mailer: + order_cycle: + subject: "Resoconto ciclo di richieste per %{producer}" + subscription_mailer: + placement_summary_email: + subject: Un riassunto delle gentili richieste recenti + greeting: "Ciao %{name}," + intro: "Qui sotto un riassunto delle gentili richieste che sono state confermate per %{shop}." + confirmation_summary_email: + subject: Un riassunto delle gentile richieste recentemente confermate + greeting: "Ciao %{name}," + intro: "Qui sotto un riassunto delle gentili richieste che hai appena confermato per %{shop}." + summary_overview: + total: Un totale di %{count} abbonamenti sono stati contrassegnati per l'elaborazione automatica.. + success_zero: Di questi, nessuno è stato modificato con successo + success_some: Di questi, %{count} sono stati elaborati con successo. + success_all: Tutti sono stati elaborati con successo. + issues: Qui sotto i dettagli dei problemi incontrati. + summary_detail: + no_message_provided: 'Nessun messaggio di errore ' + changes: + title: Scorte insufficienti (%{count} gentili richieste) + explainer: Queste gentili richieste sono state elaborate, ma la quantità richiesta di alcuni prodotti non è disponibile + empty: + title: Nessuna scorta (%{count} gentili richieste) + explainer: Queste gentili richieste non sono state confermate perchè alcuni prodotti richiesti non sono disponibili. + complete: + title: Già elaborate (%{count} gentili richieste) + explainer: Queste gentili richieste sono già segnate come complete, e non sono quindi state elaborate. + processing: + title: Errore incontrato (%{count} gentili richieste) + explainer: L'elaborazione automatica di questi ordini è fallita a causa di un errore. L'errore è stato segnalato dove possibile. + failed_payment: + title: Pagamento non riuscito (%{count} gentili richieste) + explainer: L'elaborazione automatica del pagamento per queste gentili richieste non è riuscito a causa di un errore. L'errore è stato segnalato dove possibile. + other: + title: Altro errore (%{count} gentili richieste) + explainer: L'elaborazione automatica di queste gentili richieste non è riuscita per una ragione sconosciuta. Questo non dovrebbe accadere, ti preghiamo di contattarci se visualizzi questo messaggio. home: "OFN" title: Open Food Network welcome_to: 'Benvenuto a' @@ -29,6 +113,7 @@ it: charges_sales_tax: Addebiti l'IVA? print_invoice: "Stampa fattura" print_ticket: "Stampa Biglietto" + select_ticket_printer: "Seleziona la stampante per i biglietti" send_invoice: "Manda fattura" resend_confirmation: "Rimanda conferma" view_order: "Vedi l'ordine" @@ -46,51 +131,112 @@ it: free_trial: "Prova gratuita" plus_tax: "più IVA" min_bill_turnover_desc: "dopo the il ricambio ha superato il %{mbt_amount}" + more: "Di più" say_no: "No" say_yes: "Sì" then: poi + ongoing: Attivo + bill_address: Indirizzo di fatturazione + ship_address: Indirizzo consegna sort_order_cycles_on_shopfront_by: "Ordina cicli d'ordine in vetrina per" + required_fields: I campi obbligatori sono contrassegnati con un asterisco select_continue: Seleziona e continua remove: Rimuovi or: o + collapse_all: Riduci tutto + expand_all: Espandi tutto + loading: Caricamento... show_more: Mostra di più show_all: Mostra tutti show_all_with_more: "Mostra tutti (ancora %{num})" cancel: Annulla edit: Modifica + clone: Duplica + distributors: Distributori + distribution: Distribuzione + bulk_order_management: Gestione richieste all'ingrosso + enterprises: Aziende + enterprise_groups: Gruppi + reports: Resoconti + variant_overrides: Inventario all: Tutti + current: Attuali available: Disponibile + dashboard: Pannello di controllo + undefined: non definito + unused: non in uso + admin_and_handling: Admin + profile: Profilo + supplier_only: Solo per i fornitori weight: Peso + volume: Volume + items: Prodotti + summary: Sommario + detailed: Dettaglio + updated: Aggiornato 'yes': "Sì" 'no': "No" + y: 'SI' + n: 'NO' blocked_cookies_alert: "Il tuo browser sembra stia bloccando i cookies necessari ad usare questa pagina del negozio. Clicca sotto per consentire i cookies e ricaricare la pagina." + allow_cookies: "Accetta i cookies" notes: Note error: Eorrore + processing_payment: Elaborazione pagamento... + show_only_unfulfilled_orders: Mostra solo le gentili richieste insoddisfatte + filter_results: Filtra i risultati + quantity: Quantità + pick_up: Ritiro + copy: Copia + actions: + create_and_add_another: "Crea e aggiungi un altro" admin: + begins_at: Inizia a + begins_on: Inizia da + customer: Consumatore date: Data email: Email + ends_at: Termina a + ends_on: Termina da name: Nome on_hand: Disponibile on_demand: A richiesta on_demand?: A richiesta? order_cycle: Ciclo d'ordine + payment: Pagamento + payment_method: Metodo di pagamento phone: Telefono price: Prezzo producer: Produttore + image: Immagine product: Prodotto quantity: Quantità + schedule: Programma + shipping: Spedizione + shipping_method: Metodo di consegna shop: Negozio sku: articolo gestito a magazzino + status_state: Stato tags: Tag variant: Variante weight: Peso + volume: Volume + items: Prodotti select_all: Seleziona tutto quick_search: Ricerca veloce clear_all: Cancella tutto start_date: "Data di inizio" end_date: "Data di fine" + form_invalid: "Modulo contenente campi mancanti o non validi" clear_filters: Cancella filtri clear: Pulisci + save: Salva + cancel: Annulla + back: Indietro + show_more: Mostra di più + show_n_more: Mostra %{num} di più + choose: "Scegli..." + please_select: Seleziona... columns: Colonne actions: Azioni viewing: "Vista: %{current_view_name}" @@ -100,9 +246,36 @@ it: has_one_rule: "ha una regola" has_n_rules: "ha %{num} regole" unsaved_confirm_leave: "Ci sono cambiamenti non salvati in questa pagina. Continuare senza salvare?" + unsaved_changes: "Hai modifiche non salvate" accounts_and_billing_settings: edit: admin_settings: "Impostazioni" + update_invoice: "Aggiorna fatture" + auto_update_invoices: "Auto-aggiorna le fatture all'1:00 di notte" + shopfront_settings: + embedded_shopfront_settings: "Impostazioni di vetrina incorporate" + enable_embedded_shopfronts: "Disabilita Vetrine incorporata" + number_localization: + enable_localized_number: "Usa la logica internazionale di separazione migliaia/decimali" + business_model_configuration: + edit: + business_model_configuration: "Modello di business" + business_model_configuration_tip: "Configura la tariffa mensile per i negozi per l'utilizzo di Open Food Network" + bill_calculation_settings: "Impostazioni di calcolo pagamento" + bill_calculation_settings_tip: "Modifica la tariffa mensile per l'utilizzo di OFN" + cache_settings: + show: + distributor: Distributore + order_cycle: Ciclo d'ordine + status: Stato + error: Errore + invoice_settings: + edit: + title: Impostazioni fatturazione + stripe_connect_settings: + edit: + settings: "Impostazioni" + status: Stato customers: index: add_customer: "Aggiungi cliente" @@ -122,12 +295,136 @@ it: select_country: 'Seleziona il paese' select_state: 'Selezione la provincia' edit: 'Modifica' - cache_settings: - show: - error: Eorrore + update_address: 'Aggiorna indirizzo' + confirm_delete: 'Sei sicuro di cancellare?' + search_by_email: "Cerca per email/codice..." + guest_label: 'Check-out ospite' + destroy: + has_associated_orders: 'Cancellazione non riuscita: l''utente ha ordini associati al suo negozio' + contents: + edit: + title: Contenuto + header: Intestazione + home_page: Prima pagina + producer_signup_page: Pagina accesso produttore + hub_signup_page: Pagina accesso hub + group_signup_page: Pagina accesso gruppo + main_links: Link del menu principale + footer_and_external_links: Footer e link esterni + your_content: I tuoi contenuti + user_guide: Guida per gli utenti + enterprise_fees: + index: + title: Tariffe azienda + enterprise: Azienda + fee_type: Tipo di tariffa + name: Nome + tax_category: Categoria d'imposta + calculator: Calcolatore + enterprise_groups: + index: + new_button: Nuovo gruppo di aziende products: + index: + unit: Unità + display_as: Visualizza come + category: Categoria + tax_category: Categoria di imposta + inherits_properties?: Eredita proprietà? + available_on: Disponibile il + av_on: "Disp. il" + import_date: Importato + upload_an_image: Carica un'immagine + product_search_keywords: Cerca prodotto per parole chiave + product_search_tip: Digita le parole che possono aiutare a trovare i tuoi prodotti nei negozi. Usa lo spazio per separare ciascuna parola chiave. + SEO_keywords: Parole chiave + seo_tip: Digita le parole che possono aiutare a trovare i tuoi prodotti nel web. Usa lo spazio per separare ciascuna parola chiave. Search: Cerca + product_distributions: "Distribuzione prodotti" + group_buy_options: "Opzioni Acquisti di gruppo" + back_to_products_list: "Indietro alla lista dei prodotti" + product_import: + title: Importa prodotto + file_not_found: Documento non trovato o non disponibile + no_data: Nessun dato trovato nel foglio elettronico + confirm_reset: "Questo imposterà a zero il livello di scorta di tutti i prodotti per questa \n azienda che non sono presenti nel documento aggiornato" + model: + no_file: "errore: nessun documento caricato" + could_not_process: "non è stato possibile elaborare il documento: tipo di documento non valido" + incorrect_value: valore scorretto + no_product: Non corrisponde a nessun prodotto nel database + not_found: non trovato nel database + blank: non può essere lasciato vuoto + products_no_permission: non sei abilitato a gestire i prodotti per questa azienda + inventory_no_permission: non sei abilitato a creare l'inventario per questo produttore + none_saved: Nessun prodotto salvato con successo + line: Linea + index: + select_file: Seleziona un foglio di calcolo da caricare + spreadsheet: Foglio di calcolo + choose_import_type: Seleziona modalità di importazione + import_into: ipo di importo + product_list: Lista prodotti + inventories: ventari + import: Importazione + upload: Carica + csv_templates: Documenti CSV + product_list_template: Scarica modello Listino Prodotti + inventory_template: Scarica modello Inventario + category_values: Valori di categoria disponibili + product_categories: Categorie Prodotti + tax_categories: Categorie imposte + shipping_categories: Categorie Spedizioni + import: + review: Revisione + import: Importazione + save: Salva + results: Risultati + save_imported: Salva i prodotti importati + no_valid_entries: Nessun elemento valido trovato + none_to_save: Nessun elemento può essere salvato + some_invalid_entries: Il file importato contiene voci non valide + fix_before_import: Per favore, correggi questi errori e prova ad importare nuovamente il documento + save_valid?: Salva le voci valide per adesso ed elimina le altre? + no_errors: Nessun errore trovato! + save_all_imported?: Vuoi salvare tutti i prodotti importati? + options_and_defaults: Importa opzioni e impostazioni standard + no_permission: Non hai il permesso di gestire questa attività + not_found: l'azienda non è stata trovata nel database + no_name: Nessun nome + blank_supplier: Alcuni prodotti non hanno il nome del produttore + reset_absent?: Elimina i prodotti assenti + reset_absent_tip: Imposta la scorta a zero per tutti i prodotti esistenti non presenti in questo file + overwrite_all: Sovrascrivi tutto + overwrite_empty: Sovrascrivi se vuoto + default_stock: Imposta il livello di scorta + default_tax_cat: Imposta la categoria d'imposta + default_shipping_cat: Imposta la categoria di spedizione + default_available_date: Imposta data disponibile + entries_with_errors: Alcuni elementi contengono errori e non verranno importati + products_to_create: Saranno creati nuovi prodotti + products_to_update: Alcuni prodotti saranno aggiornati + inventory_to_create: Saranno creati elementi dell'inventario + inventory_to_update: Elementi dell'inventario saranno aggiornati + products_to_reset: La scorta di prodotti esistenti sarà impostata a zero + inventory_to_reset: La scorta di elementi dell'inventario esistenti sarà portata a zero + line: Riga + item_line: Riga elemento + save_results: + final_results: Importa i risultati finali + products_created: Prodotti creati + products_updated: Prodotti aggiornati + inventory_created: Elementi d'inventario creati + inventory_updated: Elementi d'inventario aggiornati + all_saved: "Tutti gli elementi sono stati salvati con successo" + some_saved: "Elementi salvati con successo" + save_errors: Salva errori + import_again: Carica un altro file + view_products: Vai alla Pagina dei Prodotti + view_inventory: Vai alla Pagina dell'Inventario variant_overrides: + loading_flash: + loading_inventory: Caricamento Inventario index: title: Inventario description: Usa questa paginaper gestire gli inventari delle tue aziende. I dettagli dei prodotti impostati qui sovrascriveranno quelli impostati sulla pagina 'Prodotti' @@ -135,6 +432,7 @@ it: inherit?: Eredita? add: Aggiungi hide: Nascondi + import_date: Importato select_a_shop: Seleziona un negozio review_now: Rivedi adesso new_products_alert_message: Ci sono %{new_product_count} nuovi prodotti disponibili da aggiungere all'inventario. @@ -147,7 +445,13 @@ it: inventory_powertip: Questo è il tuo inventario dei prodotti. Per aggiungere prodotti all'inventario, seleziona 'Nuovi prodotti' dall'elenco hidden_powertip: Questi prodotti sono stati nascosti dal tuo inventario e non potranno essere aggiunti al tuo negozio. Puoi cliccare 'Aggiungi' per aggiungere un prodotto al tuo inventario. new_powertip: Questi prodotto possono essere aggiunti al tuo inventario. Clicca 'Aggiungi' per aggiungere un prodotto al tuo inventario o 'Nascondi' per nasconderlo dalla vista. Puoi sempre cambiare idea più tardi! + controls: + back_to_my_inventory: Indietro al mio inventario orders: + index: + ship: "Spedizione" + invoice_email_sent: 'La mail con la fattura è stata inviata' + order_email_resent: 'La mail con la gentile richiesta è stata re-inviata' bulk_management: tip: "Usa questa pagina per modificare le quantità su ordini multipli. I prodotti possono anche essere rimossi del tutto dagli ordini, se necessario." shared: "Risorsa condivisa?" @@ -168,31 +472,427 @@ it: max_fulfilled_units: "Massime unità riempite" order_error: "Qualche errore deve essere risolto prima che tu possa aggiornare gli ordini.\nOgni campo con i bordi rossi contiene errori." variants_without_unit_value: "ATTENZIONE: Alcune varianti non hanno il valore dell'unità" + select_variant: "Seleziona una variante" enterprise: select_outgoing_oc_products_from: Seleziona prodotti OC in uscita da enterprises: index: + title: Aziende + new_enterprise: Nuova Azienda producer?: "Produttore?" package: Imballaggio status: Stato manage: Gestisci form: + about_us: + desc_short: Breve descrizione + desc_short_placeholder: Raccontaci la tua attività in una o due frasi + desc_long: Chi siamo + desc_long_placeholder: Racconta di te ai consumatori. Questa informazione comparirà nel tuo profilo pubblico. + business_details: + display_invoice_logo: Mostra logo nelle fatture + contact: + name: Nome + email_address: 'Pubblica Indirizzo mail ' + email_address_tip: "Questo indirizzo mail sarà visualizzato nel tuo profilo pubblico" + phone: Telefono + website: Sito web + enterprise_fees: + name: Nome + fee_type: Tipo di tariffa + manage_fees: Gestisci tariffe azienda + no_fees_yet: Non hai ancora nessuna tariffa aziendale + create_button: Creane una ora + images: + logo: Logo + promo_image_placeholder: 'Questa immagine sarà visibile in "Chi Siamo"' + promo_image_note1: 'Per favore nota:' + promo_image_note2: Ogni immagine caricata sarà ritagliata a 1200 x 260 + promo_image_note3: L'immagine promo sarà visualizzata in cima alla pagina del profilo azienda e nei pop-up. + inventory_settings: + text1: Puoi optare per la gestione dei livelli di scorta e dei prezzi attraverso il tuo + inventory: inventario + text2: > + Se stai usando lo strumento inventario, puoi selezionare se i nuovi + prodotti aggiunti dai tuoi fornitori devono essere aggiunti al tuo inventario + prima di poter essere messi in scaorta. Se non utilizzi l'inventario + per gestire i tuoi prodotti dovresti selezionare a seguente opzione + "suggerita": + preferred_product_selection_from_inventory_only_yes: I nuovi prodotti possono essere inseriti nella mia vetrina (suggerita) + preferred_product_selection_from_inventory_only_no: I nuovi prodotti devono essere aggiunti al mio inventario prima di poter essere inseriti nella mia vetrina + payment_methods: + name: Nome + applies: Applica? + manage: Gestisci metodi di pagamento + not_method_yet: Non hai ancora nessun metodo di pagamento. + create_button: Crea un nuovo metodo di pagamento + create_one_button: Crea uno ora + primary_details: + name: Nome + groups: Gruppi + groups_tip: Seleziona i gruppi o le regioni di cui sei membro. Questo aiuterà i consumatori a trovare la tua azienda. + groups_placeholder: 'Inizia a digitare per trovare i gruppi ' + primary_producer: Produttore primario? + primary_producer_tip: Seleziona "Produttore" se sei un produttore primario di cibo + producer: Produttore + any: Proprio e altrui + none: Nessuno + own: Proprio + sells: Vende + sells_tip: "Nessuno - L'azienda non vende direttamente ai consumatori.
    Proprio - L'azienda vende i propri prodotti ai consumatori.
    Proprio e altrui - L'azienda può vendere prodotti propri o di altre aziende.
    " + visible_in_search: Visibile nella ricerca? + visible_in_search_tip: Determina se questa azienda sarà visibile ai consumatori quando cercano nel sito. + visible: Visibile + not_visible: Non visibile + permalink: Permalink (nessuno spazio) + permalink_tip: "Questo permalink serve a creare l'url al tuo negozio: %{link}nome-del-tuo-negozio/negozio" + link_to_front: Link alla tua vetrina + link_to_front_tip: Un link diretto alla tua vetrina su Open Food Network + shipping_methods: + name: Nome + applies: Applica? + manage: Gestisci metodi di spedizione + create_button: Crea un nuovo metodo di consegna + create_one_button: Crea uno ora + no_method_yet: Non hai ancora un metodo di consegna + shop_preferences: + shopfront_requires_login: "Vetrina visibile pubblicamente?" + shopfront_requires_login_tip: "Scegli se i consumatori devono essere registrati per poter vedere la vetrina o se è visibile a tutti" + shopfront_requires_login_false: "Pubblica" + shopfront_requires_login_true: "Visibile solo agli utenti registrati" + recommend_require_login: "Consigliamo di richiedere la registrazione quando si dà la possibilità di modificare gli ordini." + allow_guest_orders: "Gentili richieste ospiti" + allow_guest_orders_tip: "Permetti l'acquisto come ospite o richiedi di registrarsi" + allow_guest_orders_false: "Richiedi il login per ordinare" + allow_guest_orders_true: "Permetti acquisto come ospite" + allow_order_changes: "Modifica gentili richieste" + allow_order_changes_tip: "Permetti ai consumatori di modificare le proprie gentili richieste finché il ciclo di richieste è aperto." + allow_order_changes_false: "Le gentili richieste confermate non possono essere modificate / annullate" + allow_order_changes_true: "Gli utenti possono modificare / annullare le gentili richieste mentre il ciclo di richieste è aperto" + enable_subscriptions: "Sottoscrizioni" + enable_subscriptions_tip: "Abilita la funzionalità \"sottoscrizioni\"?" + enable_subscriptions_false: "Disabilitata" + enable_subscriptions_true: "Abilitata" + shopfront_message: Messaggio vetrina + shopfront_message_placeholder: > + Una spiegazione opzionale che dettaglia il funzionamento del tuo negozio, + che sarà visualizzata sopra al listino dei prodotti nella pagina del + tuo negozio. + shopfront_closed_message: Messaggio Chiusura Vetrina + shopfront_category_ordering: Categorie disponibili in Vetrina + open_date: Data apertura + close_date: Data chiusura stripe_connect: confirm_modal: cancel: Annulla users: + email_confirmation_notice_html: "Email di conferma in sospeso. Abbiamo inviato una mail di conferma a %{email}." + resend: Invia di nuovo + owner: 'Proprietario' contact: "Contatto" + contact_tip: "Il referente che riceverà le mail dell'azienda per le gentili richieste e le notifiche. Deve essere un indirizzo mail confermato." + owner_tip: L'utente responsabile per questa azienda. + notifications: Notifiche + notifications_tip: Le notifiche riguardanti gli ordini saranno inviate a questo indirizzo mail + notifications_note: 'Nota: se inserisci un nuovo indirizzo mail, ti sarà richiesto di confermarlo prima dell''uso.' + managers: Referenti + managers_tip: Altri utenti abilitati a gestire questa azienda. + invite_manager: "Invita referente" + invite_manager_tip: "Invita un utente non registrato a registrarsi e diventare referente per questa azienda." + add_unregistered_user: "Aggiungi un utente non registrato" + email_confirmed: "Email confermata" + email_not_confirmed: "Email non confermata" + actions: + edit_profile: Impostazioni + properties: Proprietà + payment_methods: Metodi di pagamento + payment_methods_tip: Questa azienda non ha metodi di pagamento + shipping_methods: Metodi di spedizione + shipping_methods_tip: Questa azienda ha metodi di spedizione + enterprise_fees: Tariffe azienda + enterprise_fees_tip: Questa azienda non ha tariffe aziendali + admin_index: + name: Nome + role: Ruolo + sells: Vende + visible: Visibile? + owner: Proprietario + producer: Produttore + change_type_form: + producer_profile: Profilo produttore + connect_ofn: Connetti attraverso OFN + always_free: SEMPRE LIBERO + producer_description_text: Aggiungi i tuoi prodotti, permettendo agli hubs di inserire i tuoi prodotti nei loro negozi. + producer_shop: Negozio produttore + sell_your_produce: Vendi i tuoi prodotti + producer_shop_description_text: Vendi i tuoi prodotti direttamente ai consumatori tramite la tua propria vetrina su OFN + producer_shop_description_text2: Un Negozio produttore è solo per i tuoi prodotti. Se vuoi vendere prodotti altrui, seleziona "Hub produttore". + producer_hub: Hub produttore + producer_hub_text: Vendi prodotti tuoi e di altri + producer_hub_description_text: La tua azienda è un pilastro del nostro sistema di cibo locale. Puoi vendere i tuoi prodotti, ma anche prodotti di tuoi produttori fidati attraverso la tua vetrina di Open Food Network. + profile: Solo Profilo + get_listing: Ottieni un listino + profile_description_text: Le persone ti possono trovare e contattare su OFN. La tua azienda sarà visibile sulla mappa e potrà essere trovata nelle ricerche degli utenti. + hub_shop: Isola logistica + hub_shop_text: Vendi prodotti di altri + choose_option: Per favore seleziona una delle opzioni. + change_now: Modifica ora + enterprise_user_index: + loading_enterprises: Caricamento aziende + no_enterprises_found: Nessuna azienda trovata + search_placeholder: Cerca per Nome + manage: Gestisci + manage_link: Impostazioni + producer?: "Produttore?" + package: "Imballaggio" + status: "Stato" + new_form: + owner: Proprietario + owner_tip: L'utente responsabile per questa azienda. + i_am_producer: Sono un produttore + contact_name: Nome contatto + edit: + editing: 'Impostazioni:' + back_link: Indietro alla lista delle aziende + new: + title: Nuova Azienda + back_link: Indietro alla lista delle aziende + welcome: + welcome_title: Benvenuto su Open Food Network! + welcome_text: Hai creato con successo una + next_step: Prossimo passo + choose_starting_point: 'Scegli il tuo imballaggio:' + invite_manager: + user_already_exists: "L'Utente esiste già" + error: "Qualcosa è andato storto" + order_cycles: + edit: + advanced_settings: Impostazioni avanzate + update_and_close: Aggiorna e chiudi + choose_products_from: 'Scegli i prodotti da:' + exchange_form: + pickup_instructions_placeholder: "Istruzioni per la consegna" + pickup_instructions_tip: Queste istruzioni saranno visibili agli utenti dopo che hanno completato una gentile richiesta + pickup_time_placeholder: "Pronto per (es. Data / Ora)" + receival_instructions_placeholder: "Istruzioni per il ritiro" + add_fee: 'Aggiungi tariffa' + selected: 'selezionato' + add_exchange_form: + add_supplier: 'Aggiungi fornitore' + add_distributor: 'Aggiungi distributore' + advanced_settings: + title: Impostazioni avanzate + choose_product_tip: Puoi decidere di restringere tutti i prodotti disponibili (sia in entrata che in uscita) ai soli prodotti nell'inventario di %{inventory}. + preferred_product_selection_from_coordinator_inventory_only_here: Solo inventario del coordinatore + preferred_product_selection_from_coordinator_inventory_only_all: Tutti i prodotti disponibili + save_reload: Salva e ricarica la pagina + coordinator_fees: + add: Aggiungi un ricarico per il coordinamento + form: + supplier: Fornitore + receival_details: Dettagli ritiro + fees: Tariffe + outgoing: In uscita + distributor: Distributore + products: Prodotti + tags: Tag + add_a_tag: Aggiungi una tag + delivery_details: Dettagli per il ritiro / consegna + debug_info: Messa a punto informazioni + index: + schedule: Programma + schedules: Programma + adding_a_new_schedule: Aggiungi un nuovo programma + updating_a_schedule: Aggiorna un programma + new_schedule: Nuovo programma + create_schedule: Crea programma + update_schedule: Aggiorna programma + delete_schedule: Annulla Programma + created_schedule: Programma creato + updated_schedule: Programma aggiornato + deleted_schedule: Programma annullato + schedule_name_placeholder: Nome programma + name_required_error: Inserisci un nome per questo programma + no_order_cycles_error: Per favore seleziona almeno un ciclo di richieste (drag and drop) + name_and_timing_form: + name: Nome + orders_open: Gentili richieste aperte a + coordinator: Referente + orders_close: Gentili richieste chiuse + row: + suppliers: fornitori + distributors: distributori + variants: varianti + simple_form: + ready_for: Pronto per + ready_for_placeholder: Data / Ora + customer_instructions: Istruzioni consumatori + customer_instructions_placeholder: Note per ritiro / consegna + products: Prodotti + fees: Tariffe + destroy_errors: + orders_present: Questo ciclo di richieste è stato selezionato da un consumatore e non può essere cancellato. Per evitare altri accessi, chiudi il ciclo. + schedule_present: Questo ciclo di richieste è connesso a un programma e non può essere cancellato. Puoi eliminare il link o cancellare il programma prima. + bulk_update: + no_data: mmm, qualcosa è andato storto. Nessun dato per ciclo di richieste trovato. + date_warning: + msg: 'Questo ciclo di richieste è connesso a %{n} gentili richieste aperte. Modificare questo dato ora non modificherà le gentili richieste che sono già state confermate, ma dovrebbe essere evitato per quanto possibile. Sei sicura/o di voler procedere? ' + cancel: Annulla + proceed: Procedi + producer_properties: + index: + title: Proprietà produttore + proxy_orders: + cancel: + could_not_cancel_the_order: Non è possibile annullare la gentile richiesta + resume: + could_not_resume_the_order: Non è possibile riprendere la gentile richiesta + shared: + user_guide_link: + user_guide: Guida per gli utenti + overview: + enterprises_header: + ofn_with_tip: Le aziende sono produttori e/o isole logistiche e sono le unità base dell'organizzazione di OFN + enterprises_hubs_tabs: + has_no_payment_methods: "%{enterprise} non ha metodi di pagamento" + has_no_shipping_methods: "%{enterprise} non ha metodi di consegna" + has_no_enterprise_fees: "%{enterprise} non ha tariffe aziendali" + enterprise_issues: + create_new: Crea Nuovo + resend_email: Invia di nuovo Email + has_no_payment_methods: "%{enterprise} attualmente non ha metodi di pagamento" + has_no_shipping_methods: "%{enterprise} attualmente non ha metodi di consegna" + email_confirmation: "Email di conferma in sospeso. Abbiamo inviato una mail di conferma a %{email}." + not_visible: "%{enterprise} non sono visibili e quindi non possono essere trovati nella mappa o nelle ricerche" + reports: + hidden: NASCOSTO + unitsize: Dimensione unità + total: TOTALE + total_items: TOTALE ARTICOLI + supplier_totals: Totali Ciclo di richieste fornitori + supplier_totals_by_distributor: Totali Ciclo di richieste fornitori per distributore + totals_by_supplier: Totali Ciclo di richieste fornitori per fornitore + customer_totals: Totali ciclo di richieste consumatori + all_products: Tutti i prodotti + inventory: Inventario (in mano) + mailing_list: Mailing List + addresses: Indirizzi + payment_methods: Rapporto Metodi di pagamento + delivery: Rapporto Consegne + tax_types: Tipologia tariffe + pack_by_customer: Smistato dai consumatori + pack_by_supplier: Smistato dai fornitori + orders_and_distributors: + name: Gentili richieste e distributori + description: Gentili richieste con i dettagli del distributore + payments: + name: Resoconti pagamenti + description: Rapporto per i pagamenti + orders_and_fulfillment: + name: Gentili richieste e resoconti di soddifazione + customers: + name: Consumatori + products_and_inventory: + name: Prodotti e inventario + sales_total: + name: Totale vendite + description: Totale vendite per tutti gli ordini + users_and_enterprises: + name: Utenti e aziende + description: Proprietà e status aziende + order_cycle_management: + name: Gestione Ciclo di Richieste + sales_tax: + name: Imposta di vendita + packing: + name: Resoconti smistamento/imballaggio subscriptions: + subscriptions: Abbonamenti + new: Nuovo Abbonamento + create: Crea Abbonamento + index: + please_select_a_shop: Seleziona un negozio + edit_subscription: Modifica Abbonamento + pause_subscription: Metti in pausa l'abbonamento + unpause_subscription: Riprendi abbonamento + cancel_subscription: Annulla abbonamento + setup_explanation: + just_a_few_more_steps: 'Ancora pochi passi prima di cominciare:' + enable_subscriptions: "Ativa abbonamento ad almeno uno dei tuoi negozi" + enable_subscriptions_step_1_html: 1. Vai alla pagina %{enterprises_link}, trova il tuo negozio e clicca "Gestisci" + enable_subscriptions_step_2: 2. In "Preferenze Negozio", attiva l'opzione Abbonamenti + set_up_shipping_and_payment_methods_html: 'Imposta i metodi %{shipping_link} e %{payment_link} ' + set_up_shipping_and_payment_methods_note_html: Nota con gli abbonamenti può
    essere utilizzato solo il metodo Contanti + ensure_at_least_one_customer_html: 'Assicurati che esista almeno un %{customer_link} ' + create_at_least_one_schedule: Crea almeno un programma + create_at_least_one_schedule_step_1_html: '1. Vai alla pagina %{order_cycles_link} ' + create_at_least_one_schedule_step_2: 2. Crea un Ciclo di Richieste se non l'hai già fatto + create_at_least_one_schedule_step_3: '3. Clicca su"+ Nuovo programma" e compila il modulo ' + once_you_are_done_you_can_html: Quando hai fatto, puoi %{reload_this_page_link} + reload_this_page: Ricarica questa pagina + steps: + details: 1. Dettagli base + address: 2. Indirizzo + products: 3. Aggiungi prodotti + review: 4. Controlla e salva + subscription_line_items: + this_is_an_estimate: | + I prezzi visualizzati sono solo una stima e saranno calcolati nel momento in cui l'abbonamento verrà cambiato. + Se cambi i prezzi o le tariffe, le gentili richieste saranno aggiornate, ma l'abbonamento visualizzerà ancora i valori precedenti + details: + details: Dettagli + invalid_error: Oops! Per favore compila i campi obbligatori... + allowed_payment_method_types_tip: Al momento può essere utilizzato solo il metodo di pagamento Contanti + loading_flash: + loading: CARICAMENTO ABBONAMENTI review: + details: Dettagli address: Indirizzo products: Prodotti - stripe_connect_settings: - edit: - settings: "Impostazioni" + product_already_in_order: Questo prodotto è già stato aggiunto alla gentile richiesta. Per favore modifica direttamente la quantità. + orders: + number: Numero + confirm_edit: Sei sicura/o di voler modificare questa gentile richiesta? Facendolo potrebbe essere più difficile in futuro sincronizzare le modifiche all'abbonamento + confirm_cancel_msg: Sei sicura/o di voler eliminare questo abbonamento? Quest'azione non potrà essere annullata. + cancel_failure_msg: 'Ci dispiace, eliminazione non riuscita!' + confirm_pause_msg: Sei sicura/o di voler mettere in pausa questo abbonamento? + confirm_unpause_msg: Sei sicura/o di voler riprendere questo abbonamento? + unpause_failure_msg: 'Ci dispiace, ripresa non riuscita!' + checkout: + already_ordered: + cart: "carrello" + shops: + hubs: + show_closed_shops: "Mostra i negozi chiusi" shared: + menu: + cart: + already_ordered_products: "Già richiesto in questo ciclo di richieste" register_call: selling_on_ofn: "Interessato ad entrare in Open Food Network?" register: "Registrati qui" + footer: + footer_global_headline: "OFN Globale" + footer_global_home: "Home" + footer_global_news: "News" + footer_global_about: "About" + footer_global_contact: "Contatto" + footer_sites_headline: "Siti OFN" + footer_sites_developer: "Sviluppatore" + footer_sites_community: "Community" + footer_sites_userguide: "Guida utente" + footer_secure: "Sicuro e affidabile." + footer_secure_text: "Open Food Network usa la codifica SSL (2048 bit RSA) ovunque per mantenere private le tue informazioni sulla spesa e i pagamenti. I nostri server non memorizzano i dettagli della tua carta di credito e i pagamenti sono processati da servizi PCI-conformi." + footer_contact_headline: "Tieniti in contatto" + footer_contact_email: "Scrivici" + footer_nav_headline: "Naviga" + footer_join_headline: "Unisciti a noi" + footer_join_body: "Crea una lista, un negozio o un gruppo su Open Food Network." + footer_join_cta: "Dimmi di più!" + footer_legal_call: "Leggi i nostri" + footer_legal_tos: "Termini e condizioni" + footer_legal_visit: "Trovaci su" + footer_legal_text_html: "Open Food Network è un software gratuito e open source. Il nostro contenuto è distribuito con licenza %{content_license} e il nostro codice con licenza %{code_license}." shop: messages: login: "fai il login" @@ -205,6 +905,13 @@ it: invoice_column_price: "Prezzo" invoice_column_item: "Articolo" invoice_column_qty: "Qtà." + menu_1_title: "Negozi" + menu_2_title: "Mappa" + menu_3_title: "Produttori" + menu_4_title: "Gruppi" + menu_5_title: "About" + menu_6_title: "Connetti" + menu_7_title: "Impara" logo: "Logo (640x130)" logo_mobile: "Mobile logo (75x26)" logo_mobile_svg: "Mobile logo (SVG)" @@ -220,7 +927,6 @@ it: footer_email: "Email" footer_links_md: "Collegamenti" footer_about_url: "URL About" - footer_tos_url: "URL Termini di Servizio" name: Nome first_name: Nome last_name: Cognome @@ -237,13 +943,17 @@ it: terms_of_service: "Termini di servizio" on_demand: A richiesta none: Nessuno + label_shop: "Negozio" label_shops: "Negozi" label_map: "Mappa" + label_producer: "Produttore" label_producers: "Produttori" label_groups: "Gruppi" label_about: "About" label_connect: "Connetti" label_learn: "Impara" + label_blog: "Blog" + label_support: "Aiuto" label_shopping: "Acquisto" label_login: "Login" label_logout: "Logout" @@ -268,27 +978,6 @@ it: ie_warning_firefox: Scarica Firefox ie_warning_ie: Aggiorna Internet Explorer ie_warning_other: "Non puoi aggiornare il browser? Prova Open Food Network sul tuo smartphone :-)" - footer_global_headline: "OFN Globale" - footer_global_home: "Home" - footer_global_news: "News" - footer_global_about: "About" - footer_global_contact: "Contatta" - footer_sites_headline: "Siti OFN" - footer_sites_developer: "Sviluppatore" - footer_sites_community: "Community" - footer_sites_userguide: "Guida utente" - footer_secure: "Sicuro e affidabile." - footer_secure_text: "Open Food Network usa la codifica SSL (2048 bit RSA) ovunque per mantenere private le tue informazioni sulla spesa e i pagamenti. I nostri server non memorizzano i dettagli della tua carta di credito e i pagamenti sono processati da servizi PCI-conformi." - footer_contact_headline: "Tieniti in contatto" - footer_contact_email: "Scrivici" - footer_nav_headline: "Naviga" - footer_join_headline: "Unisciti a noi" - footer_join_body: "Crea una lista, un negozio o un gruppo su Open Food Network." - footer_join_cta: "Dimmi di più!" - footer_legal_call: "Leggi i nostri" - footer_legal_tos: "Termini e condizioni" - footer_legal_visit: "Trovaci su" - footer_legal_text_html: "Open Food Network è un software gratuito e open source. Il nostro contenuto è distribuito con licenza %{content_license} e il nostro codice con licenza %{code_license}." home_shop: Compra ora brandstory_headline: "Cibo, libero." brandstory_intro: "A volte il modo migliore di correggere il sistema è crearne uno nuovo..." @@ -391,6 +1080,20 @@ it: email_payment_not_paid: NON PAGATO email_payment_summary: Riassunto del pagamento email_payment_method: "Pagamento via:" + email_so_placement_intro_html: "Hai una nuova gentile richiesta di %{distributor}" + email_so_placement_details_html: "Ecco i dettagli della gentile richiesta per %{distributor}:" + email_so_placement_changes: "Purtroppo alcuni prodotti richiesti non sono disponibili. Le quantità originali richieste sono barrate qui sotto." + email_so_placement_explainer_html: "Questa gentile richiesta è stata elaborata automaticamente per te." + email_so_edit_true_html: "Puoi apportare modifiche finchè la gentile richiesta chiuderà, il %{orders_close_at}." + email_so_edit_false_html: "Puoi visualizzare i dettagli di questa gentile richiesta in qualsiasi momento." + email_so_contact_distributor_html: "Per qualsiasi domanda, puoi contattare %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "Questa gentile richiesta è stata creata automaticamente per te. Puoi modificarla fino alla data di chiusura prestabilita, il %{orders_close_at} contattando %{distributor} via %{email}." + email_so_confirmation_intro_html: "La tua gentile richiesta per %{distributor} è ora confermata" + email_so_confirmation_explainer_html: "Questo ordine p stato confermato automaticamente per te, e ora è stato ultimato." + email_so_confirmation_details_html: "Ecco tutto ciò che è necessario sapere riguardo la tua gentile richiesta da %{distributor}:" + email_so_empty_intro_html: "Abbiamo cercato di confermare una nuova gentile richiesta con %{distributor}, ma abbiamo avuto dei problemi..." + email_so_empty_explainer_html: "Purtroppo nessuno dei prodotti richiesti è disponibile, quindi nessuna gentile richiesta è stata confermata. Qui sotto le quantità originali richieste sono state barrate." + email_so_empty_details_html: "Ecco i dettagli della gentile richiesta non confermata per %{distributor}:" email_shipping_delivery_details: Dettagli della consegna email_shipping_delivery_time: "Consegna il" email_shipping_delivery_address: "Indirizzo di consegna" @@ -400,9 +1103,17 @@ it: email_special_instructions: "Tue note:" email_signup_greeting: Ciao! email_signup_welcome: "Benvenuto a %{sitename}!" + email_signup_confirmed_email: "Grazie di aver confermato la tua mail." + email_signup_shop_html: "Puoi effettuare il log in qui: %{link}." email_signup_text: "Grazie per esserti unito alla rete. Se sei un cliente, non vediamo l'ora di introdurti a molti produttori fantastici, distributori di cibo spettacolari e cibo delizioso! Se sei un produttore o un'impresa del cibo, siamo entusiasti di averti come parte della rete." email_signup_help_html: "Accettiamo volentieri tutte le tue domane e i tuoi suggerimenti: puoi usare il bottone Invia Feedback sul sito o scriverci a %{email}" - producer_mail_greeting: "Caro" + invite_email: + greeting: "Ciao!" + invited_to_manage: "Sei stata/o invitata/o a gestire %{enterprise} su %{instance}." + confirm_your_email: "Dovresti aver ricevuto o ricevere a breve un'email con un link di conferma, Non potrai accedere al profilo di %{enterprise} finché non avrai confermato la tua mail." + set_a_password: "Ti sarà richiesto di impostare una password prima di poter gestire la pagina dell'azienda." + mistakenly_sent: "Non sai perché hai ricevuto questa mail? Per favore contatta %{owner_email} per maggiori informazioni." + producer_mail_greeting: "Cara/o" producer_mail_text_before: "Adesso abbiamo tutti gli ordini dei clienti per la prossima consegna." producer_mail_order_text: "Ecco il sommario degli ordini per i tuoi prodotti:" producer_mail_delivery_instructions: "Istruzioni per la consegna o il ritiro in magazzino:" @@ -422,6 +1133,7 @@ it: enterprises_ready_for: "Pronto per" enterprises_choose: "Scegli quando vuoi il tuo ordine:" maps_open: "Aperto" + maps_closed: "Chiuso" hubs_buy: "Acquista per:" hubs_shopping_here: "Compra qui" hubs_orders_closed: "Ordini chiusi" @@ -433,10 +1145,15 @@ it: hubs_filter_by: "Filtra per" hubs_filter_type: "Tipo" hubs_filter_delivery: "Consegna" + hubs_filter_property: "Proprietà" hubs_matches: "Intendevi?" hubs_intro: Fai la spesa nella tua zona hubs_distance: Più vicino a hubs_distance_filter: "Mostrami negozi vicino a %{location}" + shop_changeable_orders_alert_html: + one: La tua gentile richiesta con %{shop} / %{order} è aperta. Puoi apportare modifiche fino a%{oc_close}. + other: Hai %{count} gentili richieste con %{shop} attualmente aperte. Puoi effettuare modifiche fino a %{oc_close}. + orders_changeable_orders_alert_html: Questa gentile richiesta è stata confermata, ma puoi effettuare modifiche fino a %{oc_close}. products_clear_all: Cancella tutto products_showing: "Mostra:" products_with: con @@ -558,6 +1275,7 @@ it: shops_signup_help: Siamo pronti ad aiutare. shops_signup_help_text: Ti serve un ritorno migliore. Ti servono nuovi compratori e partner logistici. Ti serve che la tua storia sia raccontata attraverso l'ingrosso, il dettaglio e la tavola della cucina. shops_signup_detail: Ecco il dettaglio. + orders: Gentili richieste orders_fees: Commissioni... orders_edit_title: Carrello orders_edit_headline: Il tuo carrello @@ -566,6 +1284,7 @@ it: orders_edit_checkout: Paga orders_form_empty_cart: "Carrello vuoto" orders_form_subtotal: Calcola il subtotale + orders_form_admin: Admin orders_form_total: Totale orders_oc_expired_headline: Gli ordini sono chiusi per questo ciclo di ordini orders_oc_expired_text: "Spiacenti, gli ordini per questo ciclo son chiusi %{time} da! Per favore contatta direttamente il tuo hub per sapere se accettano ordini tardivi." @@ -575,6 +1294,17 @@ it: orders_oc_expired_phone: "Telefono:" orders_show_title: Conferma dell'ordine orders_show_time: Ordine pronto + orders_show_order_number: "Gentile richiesta #%{number}" + orders_show_cancelled: Annullato + orders_show_confirmed: Confermato + orders_your_order_has_been_cancelled: "La tua gentile richiesta è stata annullata" + orders_could_not_cancel: "Ci dispiace, la gentile richiesta non ha potuto essere annullata" + orders_cannot_remove_the_final_item: "Non si può rimuovere l'articolo definitivo da una gentile richiesta, per favore annulla piuttosto la gentile richiesta." + orders_bought_items_notice: + one: "Un articolo aggiuntivo è già stato confermato per questo ciclo di richieste" + other: "%{count} articoli aggiuntivi già confermati in questo ciclo di richieste" + orders_bought_already_confirmed: "* già confermato" + orders_confirm_cancel: Sei sicura/o di voler annullare questa gentile richiesta? products_cart_distributor_choice: "Distributore per il tuo ordine:" products_cart_distributor_change: "Il tuo distributore per questo ordine sarà sostituito da %{name} se aggiungi questo prodotto al tuo carrello." products_cart_distributor_is: "Il tuo distributore per quest'ordine è %{name}." @@ -600,6 +1330,7 @@ it: failed_to_create_enterprise: "La creazione della tua azienda non è andata a buon fine." failed_to_create_enterprise_unknown: "La creazione della tua azienda non è andata a buon fine.\nPer favore assicurati che tutti i campi siano completamente compilati." failed_to_update_enterprise_unknown: "Errore nell'aggiornamento della tua impresa.\nPer favore assicurati che tutti i campi siano completamente riempiti." + enterprise_confirm_delete_message: "Verrà eliminato anche il %{product} che l'azienda fornisce. Sei sicura/o di voler continuare?" order_not_saved_yet: "Il tuo ordine non è stato ancora salvato. Dacci qualche secondo per finire!" filter_by: "Filtra per" hide_filters: "Nascondi filtri" @@ -612,6 +1343,9 @@ it: error_required: "non può essere lasciato vuoto" error_number: "deve essere un numero" error_email: "deve essere un indirizzo email" + error_not_found_in_database: "%{name} non trovato nel database" + error_not_primary_producer: "%{name} non è abilitato come produttore" + error_no_permission_for_enterprise: "\"%{name}\": non sei abilitato a gestire prodotti per questa azienda" item_handling_fees: "Contributo per il trasporto dell'articolo (incluso nel totale dell'articolo)" january: "Gennaio" february: "Febbraio" @@ -626,6 +1360,7 @@ it: november: "Novembre" december: "Dicembre" email_not_found: "Indirizzo email non trovato" + email_unconfirmed: "Devi confermare il tuo indirizzo email prima di resettare la tua password." email_required: "Devi fornire un indirizzo email" logging_in: "Aspetta un attimo, ti stiamo facendo accedere" signup_email: "La tua email" @@ -640,7 +1375,58 @@ it: password_reset_sent: "Ti abbiamo mandato una email con le istruzioni per resettare la password." reset_password: "Resetta la password" who_is_managing_enterprise: "Chi è responsabile per la gestione di %{enterprise}?" + update_and_recalculate_fees: "Aggiorna e ricalcola tariffe" + registration: + steps: + type: + headline: "Ultimo passo per aggiungere %{enterprise}!" + question: "Sei un produttore?" + yes_producer: "Sì, sono un produttore" + no_producer: "No, non sono un produttore" + producer_field_error: "Per favore scegline uno. Sei un produttore?" + yes_producer_help: "I produttori fanno cose buone da mangiare e/o bere. Sei un produttore se le coltivi, le allevi, le infondi, le cucini, le fai fermentare, le mungi o le modelli." + no_producer_help: "Se non sei un produttore, probabilmente sei qualcuno che vende e distribuisce cibo. potresti essere un hub, una cooperativa, un gruppo d'acquisto, un rivenditore al dettaglio o all'ingrosso, o altro." + create_profile: "Crea profilo" + enterprise: + registration: + modal: + steps: + details: + title: 'Dettagli' + headline: "Iniziamo!" + enterprise: "Bene! Innanzitutto abbiamo bisogno di sapere qualcosa in più sulla tua azienda:" + producer: "Bene! Innanzitutto abbiamo bisogno di sapere qualcosa in più sulla tua azienda:" + enterprise_name_field: "Nome Azienda:" + producer_name_field: "Nome Azienda:" + producer_name_field_error: "Per favore scegli un nome univoco per la tua azienda" + address1_field: "Indirizzo rigo 1:" + address1_field_error: "Per favore inserisci un indirizzo" + address2_field: "Indirizzo rigo 2:" + suburb_field: "Comune" + suburb_field_error: "Per favore inserisci un comune" + postcode_field: "CAP:" + postcode_field_error: "CAP obbligatorio" + state_field: "Provincia:" + state_field_error: "Provincia obbligatoria" + country_field: "Nazione" + country_field_error: "Seleziona una nazione" + contact: + title: 'Contatto' + contact_field: 'Contatto principale' + contact_field_placeholder: 'Nome contatto' + contact_field_required: "E' necessario inserire un contatto principale." + email_field: 'Indirizzo email' + phone_field: 'Numero di telefono' + type: + title: 'Tipo' + about: + title: 'Descrizione' + images: + title: 'Immagini' + social: + title: 'Social' enterprise_contact: "Contatto principale" + enterprise_contact_placeholder: "Nome contatto" enterprise_contact_required: "Devi inserire un contatto principale" enterprise_email_address: "Indirizzo email" enterprise_phone: "Numero telefonico" @@ -674,6 +1460,7 @@ it: enterprise_long_desc: "Descrizione lunga" enterprise_long_desc_placeholder: "Questa è la tua opportunità per raccontare la storia della tua azienda - cosa ti rende differente e fantastico? Ti suggeriamo di mantenere la descrizione sotto i 600 caratteri o le 150 parole." enterprise_long_desc_length: "%{num} caratteri / fino a 600 raccomandati" + enterprise_limit: Limite azienda enterprise_abn: "Partita IVA" enterprise_abn_placeholder: "es. 99 123 456 789" enterprise_acn: "Codice fiscale" @@ -709,6 +1496,7 @@ it: registration_finished_thanks: "Grazie per aver riempito i dettagli per %{enterprise}." registration_finished_login: "Puoi cambiare o aggiornare la tua azienda ad ogni passo accedendo a Open Food Network e andando su Amministrazione." registration_finished_action: "Open Food Network home" + registration_contact_name: 'Nome contatto' registration_type_headline: "Ultimo passo per aggiungere %{enterprise}!" registration_type_question: "Sei un produttore?" registration_type_producer: "Sì, sono un produttore" @@ -716,7 +1504,6 @@ it: registration_type_error: "Per favore scegline uno. Sei un produttore?" registration_type_producer_help: "I produttori fanno cose buone da mangiare e/o bere. Sei un produttore se le coltivi, le allevi, le infondi, le cucini, le fai fermentare, le mungi o le modelli." registration_type_no_producer_help: "Se non sei un produttore, probabilmente sei qualcuno che vende e distribuisce cibo. potresti essere un hub, una cooperativa, un gruppo d'acquisto, un rivenditore al dettaglio o all'ingrosso, o altro." - create_profile: "Crea profilo" registration_images_headline: "Grazie!" registration_images_description: "Carichiamo qualche immagine per rendere più bello il tuo profilo! :)" registration_detail_headline: "Cominciamo" @@ -740,6 +1527,9 @@ it: registration_detail_state_error: "Lo Stato è obbligatorio" registration_detail_country: "Stato:" registration_detail_country_error: "Seleziona un paese" + shipping_method_destroy_error: "Questo metodo di consegna non può essere annullato perchè è collegato ad una gentile richiesta: %{number}." + accounts_and_billing_task_already_running_error: "Un'attività è già in esecuzione, per favore attendi " + accounts_and_billing_start_task_notice: "Attività in coda" fees: "Commissioni" item_cost: "Costo dell'articolo" bulk: "Volume" @@ -767,51 +1557,67 @@ it: credit: "Credito" Paid: "Pagato" Ready: "Pronto" + ok: OK + not_visible: non visibile you_have_no_orders_yet: "Non hai ordini al momento" running_balance: "Bilancio corrente" outstanding_balance: "Insoluto" - admin_entreprise_relationships: "Relazioni dell'azienda" - admin_entreprise_relationships_everything: "Tutto" - admin_entreprise_relationships_permits: "permessi" - admin_entreprise_relationships_seach_placeholder: "Cerca" - admin_entreprise_relationships_button_create: "Crea" - admin_entreprise_groups: "Gruppi dell'azienda" - admin_entreprise_groups_name: "Nome" - admin_entreprise_groups_owner: "Proprietario" - admin_entreprise_groups_on_front_page: "Sulla vetrina?" - admin_entreprise_groups_entreprise: "Aziende" - admin_entreprise_groups_data_powertip: "L'utente principale responsabile per questo gruppo." - admin_entreprise_groups_data_powertip_logo: "Questo è il logo per il gruppo" - admin_entreprise_groups_data_powertip_promo_image: "Questa immagine è mostrata in cima al profilo del Gruppo" - admin_entreprise_groups_contact: "Contatto" - admin_entreprise_groups_contact_phone_placeholder: "p.es. 01 123 4567" - admin_entreprise_groups_contact_address1_placeholder: "p.es. Viale Mazzini 1" - admin_entreprise_groups_contact_city: "Località" - admin_entreprise_groups_contact_city_placeholder: "p.es. Sesto" - admin_entreprise_groups_contact_zipcode: "CAP:" - admin_entreprise_groups_contact_zipcode_placeholder: "p.es. 20100" - admin_entreprise_groups_contact_state_id: "Stato" - admin_entreprise_groups_contact_country_id: "Stato" - admin_entreprise_groups_web: "Risorse web" - admin_entreprise_groups_web_twitter: "p.es. @az_agr" - admin_entreprise_groups_web_website_placeholder: "p.es. www.cascina.it" + admin_enterprise_relationships: "Permessi Azienda" + admin_enterprise_relationships_everything: "Tutto" + admin_enterprise_relationships_permits: "permessi" + admin_enterprise_relationships_seach_placeholder: "Cerca" + admin_enterprise_relationships_button_create: "Crea" + admin_enterprise_groups: "Gruppi dell'azienda" + admin_enterprise_groups_name: "Nome" + admin_enterprise_groups_owner: "Proprietario" + admin_enterprise_groups_on_front_page: "Sulla vetrina?" + admin_enterprise_groups_enterprise: "Aziende" + admin_enterprise_groups_data_powertip: "L'utente principale responsabile per questo gruppo." + admin_enterprise_groups_data_powertip_logo: "Questo è il logo per il gruppo" + admin_enterprise_groups_data_powertip_promo_image: "Questa immagine è mostrata in cima al profilo del Gruppo" + admin_enterprise_groups_contact: "Contatto" + admin_enterprise_groups_contact_phone_placeholder: "p.es. 01 123 4567" + admin_enterprise_groups_contact_address1_placeholder: "p.es. Viale Mazzini 1" + admin_enterprise_groups_contact_city: "Località" + admin_enterprise_groups_contact_city_placeholder: "p.es. Sesto" + admin_enterprise_groups_contact_zipcode: "CAP" + admin_enterprise_groups_contact_zipcode_placeholder: "p.es. 20100" + admin_enterprise_groups_contact_state_id: "Stato" + admin_enterprise_groups_contact_country_id: "Stato" + admin_enterprise_groups_web: "Risorse web" + admin_enterprise_groups_web_twitter: "p.es. @az_agr" + admin_enterprise_groups_web_website_placeholder: "p.es. www.cascina.it" admin_order_cycles: "Amministrazione Cicli d'ordine" open: "Aperto" close: "Chiuso" + create: "Crea" + search: "Cerca" supplier: "Fornitore" + product_name: "Nome Prodotto" + product_description: "Descrizione prodotto" + units: "Unità di misura" coordinator: "Coordinatore" distributor: "Distributore" enterprise_fees: "Contributi dell'azienda" + process_my_order: "Elabora la mia gentile richiesta" + delivery_instructions: Istruzioni di consegna + delivery_method: Metodo di consegna fee_type: "Tipo di contributo" tax_category: "Categoria di imposta" calculator: "Calcolatore" calculator_values: "Valori del calcolatore" flat_percent_per_item: "Percentuale (per prodotto)" - new_order_cycles: "Nuovi cicli d'ordine" + flat_rate_per_item: "Tariffa fissa (per articolo)" + flat_rate_per_order: "Tariffa fissa (per gentile richiesta)" + flexible_rate: "Tariffa flessibile" + new_order_cycles: "Nuovi cicli di richieste" + new_order_cycle: "Nuovo ciclo di richieste" select_a_coordinator_for_your_order_cycle: "Seleziona un coordinatore per il tuo ciclo di ordini" + notify_producers: 'Avvisa produttori' edit_order_cycle: "Modifica il ciclo d'ordine" roles: "Ruoli" update: "Aggiorna" + delete: Annulla add_producer_property: "Aggiungi proprietà produttori" in_progress: "In elaborazione" started_at: "Iniziato alle" @@ -828,6 +1634,8 @@ it: price: "Prezzo" on_hand: "A disposizione" save_changes: "Salva Modifiche" + order_saved: "Gentile richiesta salvata" + no_products: Nessun prodotto spree_admin_overview_enterprises_header: "Le Mie Aziende" spree_admin_overview_enterprises_footer: "GESTICI MIE AZIENDE" spree_admin_enterprises_hubs_name: "Nome" @@ -836,20 +1644,26 @@ it: spree_admin_enterprises_fees: "Contributi dell'azienda" spree_admin_enterprises_none_create_a_new_enterprise: "CREA NUOVA AZIENDA" spree_admin_enterprises_none_text: "Non hai nessua azienda al momento" - spree_admin_enterprises_producers_name: "Nome" - spree_admin_enterprises_producers_total_products: "Totale prodotti" - spree_admin_enterprises_producers_active_products: "Prodotti attivi" - spree_admin_enterprises_producers_order_cycles: "Prodotti in OC" spree_admin_enterprises_tabs_hubs: "HUBS" - spree_admin_enterprises_tabs_producers: "PRODUTTORI" spree_admin_enterprises_producers_manage_products: "GESTISCI PRODOTTI" spree_admin_enterprises_any_active_products_text: "Non hai prodotti attivi." spree_admin_enterprises_create_new_product: "CREA UN NUOVO PRODOTTO" spree_admin_single_enterprise_alert_mail_confirmation: "Conferma l'indirizzo email per" spree_admin_single_enterprise_alert_mail_sent: "Abbiamo spedito una mail a" spree_admin_overview_action_required: "Azione richiesta" + spree_admin_overview_check_your_inbox: "Controlla le tua email per ulteriori istruzioni. Grazie!" + spree_admin_unit_value: Unità di valore + spree_admin_unit_description: Descrizione Unità + spree_admin_supplier: Fornitore + spree_admin_product_category: Categoria prodotto change_package: "Cambia pacchetto" spree_admin_single_enterprise_hint: "Suggerimento: Per permettere alle persone di trovarti, abilita la tua visibilità sotto" + spree_order_availability_error: "Il distributore o il ciclo di richiesta non possono rifornire i prodotti nel tuo carrello" + spree_order_populator_error: "Il distributore o il ciclo di richieste non hanno disponibilità di alcuni prodotti nel tuo carrello. Per favore scegline un altro." + spree_order_populator_availability_error: "Questo prodotto non è disponibile dal distributore o nel ciclo di richieste selezionati." + spree_distributors_error: "Seleziona almeno un hub" + spree_user_enterprise_limit_error: "^%{email}non può gestire altre aziende (il limite è %{enterprise_limit})." + spree_variant_product_error: deve avere almeno una variante your_profil_live: "Il tuo profilo è attivo" on_ofn_map: "sulla mappa di Open Food Network" see: "Vedi" @@ -862,8 +1676,14 @@ it: manage_products: "Gestisci prodotti" edit_profile_details: "Modifica i dettagli del profilo" edit_profile_details_etc: "Cambia la descrizione del tuo profilo, le immagini, ecc." - order_cycle: "Ciclo d'ordine" + order_cycle: "Ciclo di richieste" + order_cycles: "Cicli di richieste" + enterprise_relationships: "Permessi azienda" remove_tax: "Rimuovi tassa" + enterprise_tos_link: "link Termini di Servizio Aziende" + enterprise_tos_message: "Abbiamo bisogno di lavorare con persone che condividono i nostri scopi e i nostri valori. Per questo chiediamo alle nuove aziende di sottoscrivere i nostri" + enterprise_tos_link_text: "Termini di Servizio" + enterprise_tos_agree: "Accetto i Termini di Servizio" tax_settings: "Impostazione contributi" products_require_tax_category: "i prodotto richiedono la categoria di tassa" admin_shared_address_1: "Indirizzo" @@ -881,7 +1701,7 @@ it: shop_trial_in_progress: "Il tuo negozio di prova scade tra %{days}." report_customers_distributor: "Distributore" report_customers_supplier: "Fornitore" - report_customers_cycle: "Ciclo d'ordine" + report_customers_cycle: "Ciclo di richieste" report_customers_type: "Tipo di rapporto" report_customers_csv: "Scarica come csv" report_producers: "Produttori:" @@ -894,9 +1714,100 @@ it: report_payment_totals: 'Totale pagamenti' report_all: 'tutti' report_order_cycle: "Ciclo d'ordine:" - report_entreprises: "Aziende:" + report_enterprises: "Aziende:" report_users: "Utenti:" + report_tax_rates: Tariffe + report_tax_types: Tipo tariffe + report_header_order_cycle: Ciclo di richieste + report_header_user: Utente + report_header_email: Email + report_header_status: Stato + report_header_comments: ommenti + report_header_first_name: Nome + report_header_last_name: Cognome + report_header_phone: Telefono + report_header_suburb: Comune + report_header_address: Indirizzo + report_header_billing_address: Indirizzo di fatturazione + report_header_hub: Hub + report_header_hub_address: Indirizzo hub + report_header_hub_code: Codice hub + report_header_code: Codice + report_header_paid: Pagato? + report_header_delivery: Consegna? + report_header_shipping: Spedizione + report_header_shipping_method: Metodo di consegna + report_header_shipping_instructions: Istruzioni consegna + report_header_special_instructions: Istruzioni speciali + report_header_order_number: Numero gentile richiesta + report_header_date: Data + report_header_confirmation_date: Data conferma + report_header_tags: Tag + report_header_items: Prodotti + report_header_items_total: "Totale articoli %{currency_symbol}" + report_header_taxable_items_total: "Totale Articoli con tariffe (%{currency_symbol})" + report_header_sales_tax: "Tariffa vendite (%{currency_symbol})" + report_header_delivery_charge: "Tariffa consegna (%{currency_symbol})" + report_header_enterprise: Azienda + report_header_customer: Consumatore + report_header_customer_code: Codice cliente + report_header_product: Prodotto + report_header_product_properties: Proprietà prodotto + report_header_quantity: Quantità + report_header_max_quantity: Quantità massima + report_header_total_available: Totale disponibile + report_header_supplier: Fornitore + report_header_producer: Produttore + report_header_producer_suburb: Comune produttore + report_header_unit: Unità + report_header_cost: Costo + report_header_shipping_cost: Costo consegna + report_header_total_shipping_cost: Totale costo consegna + report_header_payment_method: Metodo di pagamento + report_header_sells: Vende + report_header_visible: Visibile + report_header_price: Prezzo + report_header_unit_size: Unità di misura + report_header_distributor: Distributore + report_header_distributor_address: Indirizzo distributore + report_header_distributor_city: Città distributore + report_header_distributor_postcode: CAP distributore + report_header_delivery_address: Indirizzo consegna + report_header_delivery_postcode: CAP consegna report_header_weight: Peso + report_header_sum_total: Somma Tolale + report_header_date_of_order: Data Richiesta + report_header_amount_owing: Quantità disponibile + report_header_amount_paid: Importo pagato + report_header_units_required: Unità richieste + report_header_remainder: Pro-memoria + report_header_order_date: Data richiesta + report_header_order_id: ID richiesta + report_header_item_name: Nome articolo + report_header_customer_name: Nome Consumatore + report_header_customer_email: Email consumatore + report_header_customer_phone: Telefono Consumatore + report_header_customer_city: Città consumatore + report_header_payment_state: Stato Pagamento + report_header_payment_type: Tipo Pagamento + report_header_item_price: "Articolo (%{currency})" + report_header_item_fees_price: "Articolo + maggiorazioni (%{currency})" + report_header_admin_handling_fees: "Admin (%{currency})" + report_header_ship_price: "Consegna (%{currency})" + report_header_pay_fee_price: "Tassa pagamento (%{currency})" + report_header_total_price: "Totale (%{currency})" + report_header_product_total_price: "Totale Prodotto (%{currency})" + report_header_shipping_total_price: "Totale consegna (%{currency})" + report_header_amount: Quantità + report_header_total_cost: "Costi totali" + report_header_total_ordered: Totale richiesto + report_header_total_max: Totale max + report_header_total_units: Unità totali + report_header_sum_max_total: "Somma totale max" + report_header_total_excl_vat: "Totale imponibile (%{currency_symbol})" + report_header_total_incl_vat: "Total IVA incl. (%{currency_symbol})" + report_header_is_producer: Produttore? + report_header_not_confirmed: Non confermato initial_invoice_number: "Numero di fattura iniziale:" invoice_date: "Data fattura:" due_date: "Scadenza:" @@ -915,6 +1826,8 @@ it: products_unsaved: "Modifiche a %{n} prodotti rimangono non salvate." is_already_manager: "è già un gestore!" no_change_to_save: "Nessuna modifica da salvare" + user_invited: "%{email} è stata/o invitata/o a gestire questa azienda" + add_manager: "Aggiungi un utente esistente" users: "Utenti" about: "About" images: "Immagini" @@ -925,36 +1838,277 @@ it: social: "Social" business_details: "Dettagli aziendali" properties: "Proprietà" + shipping: "Spedizione" shipping_methods: "Metodi di Spedizione" payment_methods: "Metodi di pagamento" payment_method_fee: "Imposta di transizione" inventory_settings: "Impostazioni dell'inventario" tag_rules: "Regole dei tag" shop_preferences: "Preferenze di acquisto" + enterprise_fee_whole_order: Ordine intero + enterprise_fee_by: "%{type} tariffa da %{role} %{enterprise_name}" validation_msg_relationship_already_established: "^Questa relazione è già stabilita." validation_msg_at_least_one_hub: "^Almeno un hub deve essere selezionato" validation_msg_product_category_cant_be_blank: "^Categoria di prodotto non può essere vuoto" validation_msg_tax_category_cant_be_blank: "^Categoria di tassa non può essere vuoto" validation_msg_is_associated_with_an_exising_customer: "è associato con un cliente esistente" + enterprise_name_error: "è già stato utilizzato. Se questo è il nome della tua azienda e vorresti reclamarne la proprietà, o se vuoi contattare questa azienda, puoi contattare l'attuale referente di questo profilo a %{email}." + enterprise_owner_error: "^%{email}non può gestire altre aziende (il limite è %{enterprise_limit})." + enterprise_role_uniqueness_error: "^Questo ruolo è già presente." + inventory_item_visibility_error: deve essere vero o falso + product_importer_file_error: "errore: nessun documento caricato" + product_importer_spreadsheet_error: "non è stato possibile elaborare il documento: tipo di documento non valido" + product_importer_products_save_error: Nessun prodotto salvato con successo + product_import_file_not_found_notice: 'Documento non trovato o non disponibile' + product_import_no_data_in_spreadsheet_notice: 'Nessun dato trovato nel foglio elettronico' + order_choosing_hub_notice: Il tuo hub è stato selezionato + order_cycle_selecting_notice: Il tuo ciclo di richieste è stato selezionato + active_distributors_not_ready_for_checkout_message_singular: >- + L'hub %{distributor_names} figura in un ciclo di richieste attivo, ma non ha + metodi di consegna e di pagamento validi. Finché non li imposti, i consumatori + non potranno acquistare da questo hub. + active_distributors_not_ready_for_checkout_message_plural: >- + Gli hub %{distributor_names}figurano in un ciclo di richieste attivo, ma non + hanno metodi di consegna e di pagamento validi. Finché non li imposti, i consumatori + non potranno acquistare da questi hub. + enterprise_fees_update_notice: Le tariffe della tua azienda sono state aggiornate. + enterprise_fees_destroy_error: "Questa tariffa dell'azienda non può essere annullata perchè è collegata ad una distribuzione: %{id} - %{name}." + enterprise_register_package_error: "Per favore seleziona un pacchetto" + enterprise_register_error: "Non abbiamo potuto completare la registrazione per %{enterprise}" + enterprise_register_success_notice: "Congratulazioni! L registrazione di %{enterprise} è completa!" + enterprise_bulk_update_success_notice: "Aziende aggiornate con successo" + enterprise_bulk_update_error: 'Aggiornamento fallito' + order_cycles_create_notice: 'Il tuo ciclo di richieste è stato creato.' + order_cycles_update_notice: 'Il tuo ciclo di richieste è stato aggiornato' + order_cycles_bulk_update_notice: 'I cicli di richieste sono stati aggiornati.' + order_cycles_clone_notice: "Il tuo ciclo di richieste %{name} è stato duplicato." + order_cycles_email_to_producers_notice: 'Le email da inviare ai produttori sono state messe in coda per l''invio.' + order_cycles_no_permission_to_coordinate_error: "Nessuna delle tue aziende ha il permesso di coordinare un ciclo di richieste" + order_cycles_no_permission_to_create_error: "Non hai il permesso di creare un ciclo di richieste coordinato da questa azienda" + back_to_orders_list: "Indietro alla lista delle gentili richieste" + no_orders_found: "Nessuna gentile richiesta trovata" + order_information: "Informazioni Gentile Richiesta" + amount: "Quantità" + state_names: + ready: Pronto + pending: In sospeso + shipped: Spedito js: + saving: 'Salvataggio...' + changes_saved: 'Modifiche salvate.' + save_changes_first: Salva prima le modifiche. + all_changes_saved: Tutte le modifiche sono state salvate + unsaved_changes: Hai modifiche non salvate + all_changes_saved_successfully: Tutte le modifiche sono state salvate con successo + oh_no: "Oh no! non siamo riusciti a salvare le tue modifiche" + unauthorized: "Non sei autorizzata/o ad accedere a questa pagina." error: Eorrore + unavailable: Non disponibile + profile: Profilo + hub: Hub + shop: Negozio + choose: Scegli + resolve_errors: 'Per favore risolvi i seguenti errori:' + more_items: "+ %{count} ancora" admin: + enterprise_limit_reached: "Hai raggiunto il limite standard di aziende per account. Scrivi a %{contact_email} se hai bisogno di aumentarlo." + modals: + got_it: Capito + close: "Chiuso" + invite: "Invita" + invite_title: "Invita un utente non registrato" + tag_rule_help: + title: Regole Tag + overview: Panoramica + overview_text: > + Le regole per le tag forniscono un modo per definire quali elementi + sono visibili, o a quali utenti. Gli elementi possono essere: metodi + di consegna, metodi di pagamento, prodotti e cicli di richieste. + by_default_rules: "Regole predefinite" + by_default_rules_text: > + Le regole predefinite ti permettono di nascondere gli elementi affinché + non siano visibili. Puoi renderli visibili assegnando regole non predefinite + ad utenti con tag particolari + customer_tagged_rules: "Regole \"Utenti taggati...\"" + customer_tagged_rules_text: > + Creando regole relative ad una specifica tag per clienti, puoi superare + il comportamento predefinito (che prevede elementi visibili o nascosti) + per clienti con le tag specificate. panels: + save: SALVA + saved: SALVATO + saving: SALVATAGGIO + enterprise_package: + hub_profile: Profilo Hub + hub_shop: Isola logistica + profile_only: Solo Profilo + producer_shop: Negozio produttore + producer_hub: Hub produttore + get_listing: Ottieni un listino + always_free: SEMPRE LIBERO + sell_produce_others: Vendi prodotti di altri + sell_own_produce: Vendi i tuoi prodotti + sell_both: Vendi prodotti tuoi e di altri + enterprise_producer: + producer: Produttore + producer_desc: Produttori di cibo + non_producer_desc: Tutti gli altri tipi di Aziende alimentari + non_producer_example: es. Botteghe, Food Coop, GAS enterprise_status: + status_title: "%{name} è impostato e pronto a partire!" description: Descrizione + resolve: Risolvi + new_tag_rule_dialog: + select_rule_type: "Seleziona un tipo di regola:" + resend_user_email_confirmation: + resend: "Invia nuovamente" + sending: "Invia di nuovo..." + done: "Reinvio fatto ✓" + failed: "Re-invio fallito ✗" + out_of_stock: + reduced_stock_available: Quantità disponibile ridotta + out_of_stock_text: > + Mentre stavi acquistando, le quantità disponibili per uno o più prodotti + nel tuo carrello sono diminuite. Ecco cosa è cambiato: + now_out_of_stock: non è al momento disponibile + only_n_remainging: "attualmente solo %{num} rimasto." + variant_overrides: + inventory_products: "Inventario Prodotti" + hidden_products: "Prodotti Nascosti" + new_products: "Nuovi Prodotti" + reset_stock_levels: Resetta le quantità disponibili alla quantità predefinita + changes_to: Cambia in + no_authorisation: "Non abbiamo l'autorizzazione per salvare queste modifiche. " + some_trouble: "Abbiamo avuto problemi durante il salvataggio: %{errors}" + stock_reset: Quantità resettate alle predefinite. + tag_rules: + show_hide_variants: 'Mostra o nascondi varianti nella mia vetrina' + show_hide_shipping: 'Mostra o nascondi metodi di consegna al checkout' + show_hide_payment: 'Mostra o nascondi metodi di pagamento al checkout' + show_hide_order_cycles: 'Mostra o nascondi cicli di richieste nella mia vetrina' + visible: VISIBILE + not_visible: NON VISIBILE + services: + unsaved_changes_message: 'Ci sono modifiche non salvate: salva ora o ignora?' + save: SALVA + ignore: IGNORA + add_to_order_cycle: "aggiungi al ciclo di richiesta" + manage_products: "gestisci prodotti" + edit_profile: "modifica profilo" + add_products_to_inventory: "aggiungi prodotti all'inventario" + resources: + could_not_delete_customer: 'Non è possibile annullare utente' + order_cycles: + update_success: 'Il tuo ciclo di richieste è stato aggiornato' + enterprises: + producer: "Produttore" + non_producer: "Non-produttore" + customers: + select_shop: 'Seleziona prima un negozio' + could_not_create: Ci dispiace! Non è possibile creare + subscriptions: + closes: chiude + closed: chiuso + close_date_not_set: Data di chiusura non impostata + producers: + signup: + start_free_profile: "Inizia con un profilo gratuito e migliora quando sei pronto!" spree: + email: Email + account_updated: "Account aggiornato!" + my_account: "Il mio account" + date: "Data" + time: "Ora" + admin: + orders: + invoice: + code: Codice + from: Da + to: A + form: + distribution_fields: + title: Distribuzione + distributor: "Distributore:" + order_cycle: "Ciclo di richieste:" + overview: + order_cycles: + order_cycles: "Cicli di richiesteCicli di richieste" + order_cycles_tip: "I cicli di richieste determinano dove e quando i tuoi prodotti sono disponibili per i consumatori." + you_have_active: + zero: "Non hai nessun ciclo di richieste attivo." + one: "Hai un ciclo di richieste attivo." + other: "Hai %{count} cicli di richieste attivi." + manage_order_cycles: "GESTISCI CICLI DI RICHIESTE" + payment_methods: + stripe_connect: + enterprise_select_placeholder: Scegli... + status: Stato + products: + index: + products_head: + name: Nome + unit: Unità + display_as: Visualizza come + category: Categoria + tax_category: Categoria di imposta + inherits_properties?: Eredita proprietà? + available_on: Disponibile il + av_on: "Disp. il" + products_variant: + new_variant: "Nuova variante" + product_name: Nome Prodotto + primary_taxon_form: + product_category: Categoria prodotto + display_as: + display_as: Visualizza come + reports: + table: + select_and_search: "Seleziona i filtri e clicca su CERCA per accedere ai tuoi dati" + users: + email_confirmation: + confirmation_pending: "Email di conferma in sospeso. Abbiamo inviato una mail di conferma a %{address}." + variants: + autocomplete: + producer_name: Produttore + general_settings: + edit: + legal_settings: "Impostazioni Legali" + enterprises_require_tos: "Le aziende devono accettare i Termini di Servizio" + footer_tos_url: "URL Termini di Servizio" + checkout: + payment: + stripe: + choose_one: Scegli uno + date_picker: + js_format: 'aa-mm-gg' + inventory: Inventario + orders: + bought: + item: "Già richiesto in questo ciclo di richieste" + order_mailer: + invoice_email: + hi: "Ciao %{name}" order_state: address: indirizzo adjustments: aggiustamenti awaiting_return: restituzione attesa + canceled: annullato cart: carrello complete: completo confirm: conferma delivery: consegna + paused: in pausa payment: pagamento + pending: in sospeso resumed: ripreso returned: restituito skrill: skrill + subscription_state: + active: attivo + pending: in sospeso + ended: finito + paused: in pausa + canceled: annullato payment_states: balance_due: saldo completed: completato @@ -972,7 +2126,55 @@ it: pending: in pendenza ready: pronto shipped: spedito + user_mailer: + reset_password_instructions: + request_sent_text: | + E' stata fatta una richiesta per resettare la tua password + Se non hai fatto questa richiesta, ignora semplicemente questa email. + link_text: > + Se hai fatto questa richiesta, clicca il link seguente: + issue_text: | + Se l'URL qui sopra non funziona, prova a copiarlo e incollarlo nel tuo browser. + Se continui ad avere problemi, contattaci. + confirmation_instructions: + subject: Per favore conferma il tuo account OFN + weight: Peso (kg) + zipcode: CAP users: + form: + account_settings: Impostazioni account + show: + tabs: + orders: Gentili richieste + settings: Impostazioni account + unconfirmed_email: "Email di conferma in sospeso per: %{unconfirmed_email}. Il tuo indirizzo email sarà aggiornato quando la nuova email sarà confermata. " + orders: + open_orders: Gentili richieste aperte + past_orders: Gentili richieste passate + transactions: + transaction_history: Storico Transazioni open_orders: + order: Gentile Richiesta + shop: Negozio + changes_allowed_until: Modifiche permesse fino al + items: Prodotti + total: Totale edit: Modifica cancel: Annulla + closed: Chiuso + until: Fino + past_orders: + order: Gentile Richiesta + shop: Negozio + completed_at: Completo al + items: Prodotti + total: Totale + paid?: Pagato? + view: Vista + saved_cards: + default?: Predefinito? + delete?: Elimina? + cards: + authorised_shops: Negozi autorizzati + localized_number: + invalid_format: 'Formato non valido: inserire un numero.' diff --git a/config/locales/nb.yml b/config/locales/nb.yml index 5cb20bd7bbc..51d7efc2c53 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -64,6 +64,9 @@ nb: user_passwords: spree_user: updated_not_active: "Ditt passord har blitt tilbakestilt, men epostadressen din er ikke bekreftet enda." + models: + order_cycle: + cloned_order_cycle_name: "KOPI AV %{order_cycle}" enterprise_mailer: confirmation_instructions: subject: "Vennligst bekreft epostadressen til %{enterprise}" @@ -71,9 +74,26 @@ nb: subject: "%{enterprise} er nå på %{sitename}" invite_manager: subject: "%{enterprise} har invitert deg til å være en administrator" + order_mailer: + cancel_email: + dear_customer: "Kjære Kunde," + instructions: "Din bestilling har blitt KANSELLERT. Vennligst behold denne kanselleringsinformasjonen som referanse." + order_summary_canceled: "Bestillingssammendrag [KANSELLERT]" + subject: "Kansellering av bestilling" + subtotal: "Subtotal: %{subtotal}" + total: "Ordre Totalt: %{total}" producer_mailer: order_cycle: subject: "Bestillingsrunderapport for %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Kjære Kunde," + instructions: "Din bestilling har blitt sendt" + shipment_summary: "Leveringssammendrag" + subject: "Leveringsvarsling" + thanks: "Takk for handelen." + track_information: "Sporingsinformasjon: %{tracking}" + track_link: "Sporingslink: %{url}" subscription_mailer: placement_summary_email: subject: Et sammendrag av nylig bestilte abonnementsbestillinger @@ -136,6 +156,7 @@ nb: free_trial: "gratis prøveperiode" plus_tax: "pluss MVA" min_bill_turnover_desc: "når omsettning overstiger %{mbt_amount}" + more: "Mer" say_no: "Nei" say_yes: "Ja" then: vil @@ -379,8 +400,10 @@ nb: producer_signup_page: Produsent registreringsside hub_signup_page: Hub-registreringsside group_signup_page: Grupperegistreringsside + main_links: 'Lenker Hovedmeny ' footer_and_external_links: Footer og eksterne lenker your_content: Ditt innhold + user_guide: Brukermanual enterprise_fees: index: title: Bedriftsavgifter @@ -765,6 +788,9 @@ nb: search_placeholder: Søk på navn manage: Administrer manage_link: Innstillinger + producer?: "Produsent?" + package: "Pakke" + status: "Status" new_form: owner: Eier owner_tip: Primærbrukeren ansvarlig for denne bedriften. @@ -776,6 +802,14 @@ nb: new: title: Ny Bedrift back_link: Tilbake til bedriftsliste + remove_logo: + remove: "Fjern Bilde" + removed_successfully: "Logo fjernet vellykket" + immediate_removal_warning: "Logoen vil bli fjernet umiddelbart etter at du har bekreftet." + remove_promo_image: + remove: "Fjern Bilde" + removed_successfully: "Promobilde fjernet vellykket" + immediate_removal_warning: "Promo-bildet vil bli fjernet umiddelbart etter at du har bekreftet." welcome: welcome_title: Velkommen til Open Food Network! welcome_text: Du har opprettet en @@ -808,6 +842,11 @@ nb: save_reload: Lagre og last siden på nytt coordinator_fees: add: Legg til koordinatoravgift + filters: + search_by_order_cycle_name: "Søk etter navn på bestillingsrunde..." + involving: "Involverer" + any_enterprise: "Enhver Bedrift" + any_schedule: "Enhver Tidsplan" form: incoming: Innkommende supplier: Leverandør @@ -821,7 +860,6 @@ nb: delivery_details: Hente-/Leveringsdetaljer debug_info: Debuginformasjon index: - involving: Involverer schedule: Tidsplan schedules: Tidsplaner adding_a_new_schedule: Legge til en ny tidsplan @@ -1030,6 +1068,33 @@ nb: register_call: selling_on_ofn: "Interessert i å bli med i Open Food Network?" register: "Registrer her" + footer: + footer_global_headline: "OFN Globalt" + footer_global_home: "Hjem" + footer_global_news: "Nyheter" + footer_global_about: "Om" + footer_global_contact: "Kontakt" + footer_sites_headline: "OFN nettsteder" + footer_sites_developer: "Utvikler" + footer_sites_community: "Forum" + footer_sites_userguide: "Brukermanual" + footer_secure: "Sikker og klarert." + footer_secure_text: "Open Food Network bruker SSL-kryptering (2048 bit RSA) overalt for å holde handlingen og betalingen din privat. Våre servere lagrer ikke kortopplysninger og betalinger behandles av PCI-kompatible tjenester." + footer_contact_headline: "Hold kontakten" + footer_contact_email: "Send oss epost" + footer_nav_headline: "Naviger" + footer_join_headline: "Bli med" + footer_join_body: "Opprette en profil, butikk eller gruppe på Open Food Network." + footer_join_cta: "Fortell meg mer!" + footer_legal_call: "Les våre" + footer_legal_tos: "Vilkår og betingelser" + footer_legal_visit: "Finn oss på" + footer_legal_text_html: "Open Food Network er en plattform med fri og åpen kildekode. Vårt innhold er lisensiert med %{content_license} og vår kode med %{code_license}." + footer_data_text_with_privacy_policy_html: "Vi tar godt vare på dine data. Se vår %{privacy_policy} og %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "Vi tar godt vare på dine data. Se vår %{cookies_policy}" + footer_data_privacy_policy: "personvernpolicy" + footer_data_cookies_policy: "policy informasjonskapsler" + footer_skylight_dashboard_html: Ytelsesdata er tilgjengelig på %{dashboard}. shop: messages: login: "innlogging" @@ -1065,6 +1130,20 @@ nb: ticket_column_item: "Vare" ticket_column_unit_price: "Enhetspris" ticket_column_total_price: "Totalpris" + menu_1_title: "Butikker" + menu_1_url: "/shops" + menu_2_title: "Kart" + menu_2_url: "/map" + menu_3_title: "Produsenter" + menu_3_url: "/producers" + menu_4_title: "Grupper" + menu_4_url: "/groups" + menu_5_title: "Om" + menu_5_url: "https://openfoodnetwork.org/ofn-local/open-food-network-scandinavia/" + menu_6_title: "Koble til" + menu_6_url: " " + menu_7_title: "Lære" + menu_7_url: " " logo: "Logo (640x130)" logo_mobile: "Mobil logo (75x26)" logo_mobile_svg: "Mobil logo (SVG)" @@ -1080,7 +1159,6 @@ nb: footer_email: "Epost" footer_links_md: "Linker" footer_about_url: "Om URL" - footer_tos_url: "Vilkår URL" name: Navn first_name: Fornavn last_name: Etternavn @@ -1154,28 +1232,47 @@ nb: ie_warning_firefox: Last ned Firefox ie_warning_ie: Oppgrader Internet Explorer ie_warning_other: "Kan ikke oppgradere nettleseren din? Prøv Open Food Network på smart-telefonen din :-)" - footer_global_headline: "OFN Globalt" - footer_global_home: "Hjem" - footer_global_news: "Nyheter" - footer_global_about: "Om" - footer_global_contact: "Kontakt" - footer_sites_headline: "OFN nettsteder" - footer_sites_developer: "Utvikler" - footer_sites_community: "Forum" - footer_sites_userguide: "Brukerhåndbok" - footer_secure: "Sikker og klarert." - footer_secure_text: "Open Food Network bruker SSL-kryptering (2048 bit RSA) overalt for å holde handlingen og betalingen din privat. Våre servere lagrer ikke kortopplysninger og betalinger behandles av PCI-kompatible tjenester." - footer_contact_headline: "Hold kontakten" - footer_contact_email: "Send oss en epost" - footer_nav_headline: "Naviger" - footer_join_headline: "Bli med" - footer_join_body: "Opprette en profil, butikk eller gruppe på Open Food Network." - footer_join_cta: "Fortell meg mer!" - footer_legal_call: "Les våre" - footer_legal_tos: "Vilkår og betingelser" - footer_legal_visit: "Finn oss på" - footer_legal_text_html: "Open Food Network er en plattform med fri og åpen kildekode. Vårt innhold er lisensiert med %{content_license} og vår kode med %{code_license}." - footer_skylight_dashboard_html: Ytelsesdata er tilgjengelig på %{dashboard}. + legal: + cookies_policy: + header: "Hvordan vi bruker informasjonskapsler" + desc_part_1: "Informasjonskapsler er svært små tekstfiler som er lagret på datamaskinen din når du besøker noen nettsteder." + desc_part_2: "I OFN respekterer vi fullt ut ditt privatliv. Vi bruker bare informasjonskapsler som er nødvendige for å levere deg tjenesten til å selge/kjøpe mat på nettet. Vi selger ikke noen av dataene dine. Vi kan i fremtiden foreslå at du deler noen av dataene dine for å bygge nye commons-tjenester som kan være nyttige for økosystemet (som logistikk-tjenester for kortmatssystemer), men vi er ikke der ennå, og vi vil ikke gjøre det uten din godkjenning :-)" + desc_part_3: "Vi bruker informasjonskapsler hovedsakelig for å huske hvem du er hvis du logger inn på tjenesten, eller for å kunne huske elementene du legger inn i handlekurven din selv om du ikke er logget inn. Hvis du fortsetter å navigere på nettstedet uten å klikke på \"Godta cookies\", antar vi at du gir oss samtykke til å lagre informasjonskapslene som er avgjørende for nettstedet. Her er listen over informasjonskapsler vi bruker!" + essential_cookies: "Viktige informasjonskapsler" + essential_cookies_desc: "Følgende informasjonskapsler er strengt nødvendige for driften av nettstedet vårt." + essential_cookies_note: "De fleste informasjonskapsler inneholder bare en unik identifikator, men ingen andre data, slik at epostadressen og passordet ditt for eksempel aldri er inneholdt i dem og aldri blir eksponert." + cookie_domain: "Satt av:" + cookie_session_desc: "Brukes til å tillate nettstedet å huske brukere mellom sidebesøk, for eksempel, husk elementer i handlekurven din." + cookie_consent_desc: "Brukes til å opprettholde status for brukerens samtykke til å lagre informasjonskapsler" + cookie_remember_me_desc: "Brukes hvis brukeren har bedt nettsiden om å huske ham. Denne informasjonskapsel slettes automatisk etter 12 dager. Hvis du som bruker ønsker at cookien skal slettes, trenger du bare å logge ut. Hvis du ikke vil at cookien skal installeres på datamaskinen, bør du ikke merke avkrysningsruten «husk meg» når du logger inn." + cookie_openstreemap_desc: "Brukes av vår vennlige open source kartleverandør (OpenStreetMap) for å sikre at den ikke mottar for mange forespørsler i en gitt tidsperiode for å forhindre misbruk av sine tjenester." + cookie_stripe_desc: "Data samlet inn av betalingsprosessoren vår Stripe for svindeloppdagelse https://stripe.com/cookies-policy/legal. Ikke alle butikker bruker Stripe som betalingsmetode, men det er en god praksis å forhindre at svindel gjelder for alle sider. Stripe bygger sannsynligvis et bilde av hvilke av våre sider som til vanlig samhandler med API-en og deretter flagge alt uvanlig. Så å sette Stripe-cookien har en bredere funksjon enn bare å levere en betalingsmetode til en bruker. Fjerning av det kan påvirke sikkerheten til selve tjenesten. Du kan lære mer om Stripe og lese retningslinjene for personvern på https://stripe.com/privacy." + statistics_cookies: "Statistikkkapsler" + statistics_cookies_desc: "Følgende er ikke strengt nødvendige, men hjelper deg med å gi deg den beste brukeropplevelsen ved å tillate oss å analysere brukeradferd, identifisere hvilke funksjoner du bruker mest, eller ikke bruker, forstå brukeropplevelsesproblemer osv." + statistics_cookies_analytics_desc_html: "For å samle og analysere plattformbruksdata bruker vi Google Analytics, da det var standardtjenesten som var koblet til Spree (ehandel open source programvare som vi bygde på), men visjonen vår er å bytte til Matomo (ex Piwik, open source analyseverktøy som er GDPR-kompatibelt og beskytter ditt privatliv) så snart vi kan." + statistics_cookies_matomo_desc_html: "For å samle og analysere plattformbruksdata bruker vi Matomo (ex Piwik), et åpen kildekodeanalyseverktøy som er kompatibelt med GDPR og beskytter personvernet ditt." + cookie_analytics_utma_desc: "Brukes til å skille mellom brukere og økter. Kapselen er opprettet når javascriptbiblioteket utføres, og ingen eksisterende __utma-informasjonskapsler eksisterer. Cookien oppdateres hver gang data sendes til Google Analytics." + cookie_analytics_utmt_desc: "Brukes til pådragsforespørselsrate." + cookie_analytics_utmb_desc: "Brukes til å bestemme nye økter/besøk. Kapselen blir opprettet når javascriptbiblioteket kjøres, og ingen eksisterende __utmb-cookies eksisterer. Cookien oppdateres hver gang data sendes til Google Analytics." + cookie_analytics_utmc_desc: "Ikke brukt i ga.js. Satt for interoperabilitet med urchin.js. Historisk kjørte denne informasjonskapselen sammen med __utmb-cookien for å avgjøre om brukeren var i en ny økt/besøk." + cookie_analytics_utmz_desc: "Lagrer trafikkilden eller kampanjen som forklarer hvordan brukeren nådde nettstedet ditt. Kapselen blir opprettet når javascriptbiblioteket utføres og oppdateres hver gang data sendes til Google Analytics." + cookie_matomo_basics_desc: "Matomo førstehånds kapsler for å samle statistikk." + cookie_matomo_heatmap_desc: "Matomo Heatmap & Session opptakskapsel." + cookie_matomo_ignore_desc: "Kapsel brukes til å utelukke at bruker blir sporet." + disabling_cookies_header: "Advarsel om deaktivering av informasjonskapsler" + disabling_cookies_desc: "Som bruker kan du alltid tillate, blokkere eller slette Open Food Network eller andre nettsiders kapsler når du vil gjennom nettleserens innstillingskontroll. Hver nettleser har sin egne måte å operere på. Her er linkene:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" + disabling_cookies_note: "Men vær oppmerksom på at hvis du sletter eller endrer de essensielle informasjonskapslene som brukes av Open Food Network, vil nettstedet ikke fungere, du vil ikke kunne legge til noe i handlekurven din eller for eksempel for å sjekke ut." + cookies_banner: + cookies_usage: "Dette nettstedet bruker informasjonskapsler for å gjøre navigasjonen friksjonsfri og sikker, og for å hjelpe oss å forstå hvordan du bruker den for å forbedre funksjonene vi tilbyr." + cookies_definition: "Informasjonskapsler er svært små tekstfiler som er lagret på datamaskinen din når du besøker noen nettsteder." + cookies_desc: "Vi bruker bare informasjonskapsler som er nødvendige for å levere deg tjenesten til å selge/kjøpe mat på nettet. Vi selger ikke noen av dataene dine. Vi bruker informasjonskapsler hovedsakelig for å huske hvem du er hvis du logger inn på tjenesten, eller for å kunne huske elementene du legger inn i handlekurven din selv om du ikke er logget inn. Hvis du fortsetter å navigere på nettstedet uten å klikke på \"Godta cookies\", antar vi at du gir oss samtykke til å lagre informasjonskapslene som er avgjørende for nettstedet." + cookies_policy_link_desc: "Hvis du vil lære mer, sjekk vår" + cookies_policy_link: "policy om informasjonskapsler" + cookies_accept_button: "Godta Informasjonskapsler" home_shop: Handle nå brandstory_headline: "Food, unincorporated." brandstory_intro: "Noen ganger er det best å fikse systemet ved å starte et nytt..." @@ -1296,6 +1393,7 @@ nb: email_so_edit_true_html: "Du kan gjøre endringer til bestillinger lukkes på %{orders_close_at}." email_so_edit_false_html: "Du kan se detaljer på denne bestillingen når som helst." email_so_contact_distributor_html: "Hvis du har spørsmål, kan du kontakte %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "Denne bestillingen ble automatisk opprettet for deg. Du kan gjøre endringer til bestillinger stenger på %{orders_close_at} ved å kontakte %{distributor} via %{email}." email_so_confirmation_intro_html: "Bestillingen din med %{distributor} er nå bekreftet" email_so_confirmation_explainer_html: "Denne bestillingen ble automatisk lagt inn for deg, og den er nå fullført." email_so_confirmation_details_html: "Her er alt du trenger å vite om bestillingen din fra %{distributor} :" @@ -1557,6 +1655,7 @@ nb: error_number: "må være tall" error_email: "må være epostadresse" error_not_found_in_database: "%{name} ikke funnet i databasen" + error_not_primary_producer: "%{name} er ikke aktivert som produsent" error_no_permission_for_enterprise: "\"%{name}\": du har ikke rettigheter til å administrere produkter for denne bedriften" item_handling_fees: "Håndteringsavgifter for varen (inkludert i varens totaler)" january: "januar" @@ -1675,6 +1774,7 @@ nb: enterprise_about_headline: "Bra!" enterprise_about_message: "Nå la oss finne ut detaljene om" enterprise_success: "Suksess! %{enterprise} lagt til Open Food Network" + enterprise_registration_exit_message: "Hvis du avslutter denne veiviseren på et hvilket som helst tidspunkt, kan du fortsette å opprette profilen din ved å gå til administrasjonsgrensesnittet." enterprise_description: "Kort beskrivelse" enterprise_description_placeholder: "En kort setning som beskriver virksomheten din" enterprise_long_desc: "Lang beskrivelse" @@ -1782,33 +1882,34 @@ nb: you_have_no_orders_yet: "Du har ingen bestilinger enda" running_balance: "Løpende balanse" outstanding_balance: "Utestående balanse" - admin_entreprise_relationships_everything: "Alt" - admin_entreprise_relationships_permits: "tillater" - admin_entreprise_relationships_seach_placeholder: "Søk" - admin_entreprise_relationships_button_create: "Opprett" - admin_entreprise_groups: "Bedriftsgrupper" - admin_entreprise_groups_name: "Navn" - admin_entreprise_groups_owner: "Eier" - admin_entreprise_groups_on_front_page: "På forsiden?" - admin_entreprise_groups_entreprise: "Bedrifter" - admin_entreprise_groups_data_powertip: "Hovedbrukeren ansvarlig for denne gruppen." - admin_entreprise_groups_data_powertip_logo: "Dette er logoen for gruppen" - admin_entreprise_groups_data_powertip_promo_image: "Dette bildet vises på toppen av Gruppens profil" - admin_entreprise_groups_contact: "Kontakt" - admin_entreprise_groups_contact_phone_placeholder: "eks: 987 123 654" - admin_entreprise_groups_contact_address1_placeholder: "eks: Gårdsvei 12" - admin_entreprise_groups_contact_city: "Område" - admin_entreprise_groups_contact_city_placeholder: "f.eks. Nesodden" - admin_entreprise_groups_contact_zipcode: "Postnummer" - admin_entreprise_groups_contact_zipcode_placeholder: "f.eks. 1450" - admin_entreprise_groups_contact_state_id: "Fylke" - admin_entreprise_groups_contact_country_id: "Land" - admin_entreprise_groups_web: "Nettressurser" - admin_entreprise_groups_web_twitter: "f.eks. @alt_lokalt" - admin_entreprise_groups_web_website_placeholder: "f.eks. www.truffles.com" + admin_enterprise_relationships: "Bedriftsrettigheter" + admin_enterprise_relationships_everything: "Alt" + admin_enterprise_relationships_permits: "tillater" + admin_enterprise_relationships_seach_placeholder: "Søk" + admin_enterprise_relationships_button_create: "Opprett" + admin_enterprise_groups: "Bedriftsgrupper" + admin_enterprise_groups_name: "Navn" + admin_enterprise_groups_owner: "Eier" + admin_enterprise_groups_on_front_page: "På forsiden?" + admin_enterprise_groups_enterprise: "Bedrifter" + admin_enterprise_groups_data_powertip: "Hovedbrukeren ansvarlig for denne gruppen." + admin_enterprise_groups_data_powertip_logo: "Dette er logoen for gruppen" + admin_enterprise_groups_data_powertip_promo_image: "Dette bildet vises på toppen av Gruppens profil" + admin_enterprise_groups_contact: "Kontakt" + admin_enterprise_groups_contact_phone_placeholder: "f.eks. 987 123 654" + admin_enterprise_groups_contact_address1_placeholder: "f.eks. 123 High Street" + admin_enterprise_groups_contact_city: "Område" + admin_enterprise_groups_contact_city_placeholder: "f.eks. Northcote" + admin_enterprise_groups_contact_zipcode: "Postnummer" + admin_enterprise_groups_contact_zipcode_placeholder: "f.eks. 3070" + admin_enterprise_groups_contact_state_id: "Tilstand" + admin_enterprise_groups_contact_country_id: "Land" + admin_enterprise_groups_web: "Nettressurser" + admin_enterprise_groups_web_twitter: "f.eks. @the_prof" + admin_enterprise_groups_web_website_placeholder: "f.eks. www.truffles.com" admin_order_cycles: "Administrasjon Bestillingsrunder" open: "Åpne" - close: "Steng" + close: "Lukk" create: "Opprett" search: "Søk" supplier: "Leverandør" @@ -1905,9 +2006,8 @@ nb: edit_profile_details_etc: "Endre din profilbeskrivelse, bilder, osv." order_cycle: "Bestillingsrunde" order_cycles: "Bestillingsrunder" + enterprise_relationships: "Bedriftsrettigheter" 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." @@ -1942,7 +2042,7 @@ nb: report_payment_totals: 'Betalingstotaler' report_all: 'alle' report_order_cycle: "Bestillingsrunde:" - report_entreprises: "Bedrifter:" + report_enterprises: "Bedrifter:" report_users: "Brukere:" report_tax_rates: Avgiftsrater report_tax_types: Avgiftstyper @@ -2309,6 +2409,9 @@ nb: select_rule_type: "Velg en regeltype:" resend_user_email_confirmation: resend: "Send på nytt" + sending: "Send på nytt ..." + done: "Sending på nytt ferdig ✓" + failed: "Sending på nytt mislyktes ✗" out_of_stock: reduced_stock_available: Redusert lager tilgjengelig out_of_stock_text: > @@ -2411,7 +2514,8 @@ nb: payments: source_forms: stripe: - no_payment_via_admin_backend: Å skape Stripe-baserte betalinger fra admin-backend er ikke mulig for øyeblikket + error_saving_payment: Feil ved lagring av betaling + submitting_payment: Sender inn betaling... products: new: title: 'Nytt produkt' @@ -2445,14 +2549,28 @@ nb: display_as: display_as: Vis som reports: + table: + select_and_search: "Velg filtre og klikk på SØK for å få tilgang til dataene dine." bulk_coop: bulk_coop_supplier_report: 'Bulk Co-op - Totaler etter Leverandør' bulk_coop_allocation: 'Bulk Co-op - Allokering' bulk_coop_packing_sheets: 'Bulk Co-op - Pakkseddel' bulk_coop_customer_payments: 'Bulk Co-op - Kunde Betalinger' + users: + email_confirmation: + confirmation_pending: "Epostbekreftelse venter. Vi har sendt en bekreftelses-epost til %{address}." variants: autocomplete: producer_name: Produsent + general_settings: + edit: + legal_settings: "Juridiske innstillinger" + cookies_consent_banner_toggle: "Vis informasjonskapsler samtykkebanner" + privacy_policy_url: "Personvernspolicy URL" + enterprises_require_tos: "Bedrifter må godta Tjenestevilkår" + cookies_policy_matomo_section: "Vis Matomo-delen på informasjonskapsler-siden" + cookies_policy_ga_section: "Vis Google Analytics-delen om informasjonskapsler på policy-siden om informasjonskapsler" + footer_tos_url: "Vilkår URL" checkout: payment: stripe: @@ -2560,5 +2678,9 @@ nb: saved_cards: default?: Standard? delete?: Slett? + cards: + authorised_shops: Autoriserte Butikker + authorised_shops_popover: Dette er listen over butikker som har lov til å belaste ditt standard kredittkort for eventuelle abonnementer (dvs. gjentatte ordre) du måtte ha. Kortinformasjonen din vil bli holdt sikker og vil ikke bli delt med butikkeiere. Du vil alltid bli varslet når du blir belastet. + saved_cards_popover: Dette er listen over kort du har valgt å lagre for senere bruk. Din standard vil bli valgt automatisk når du gjør en bestilling, og kan belastes av butikker du har gitt lov til å gjøre det (se høyre). localized_number: invalid_format: har et ugyldig format. Vennligst skriv inn et nummer. diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 9dea0d380d0..f3872876080 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -18,8 +18,6 @@ pt: attributes: email: taken: "Já existe uma conta associada a este email. Por favor faça login ou defina uma nova palavra-passe." - spree/order: - no_card: Não há cartões de crédito válidos disponíveis order_cycle: attributes: orders_close_at: @@ -43,9 +41,6 @@ pt: invalid_type: "tem de ser em dinheiro ou método Stripe" shipping_method: not_available_to_shop: "não está disponível para %{shop}" - credit_card: - not_available: "não está disponível" - blank: "é obrigatório" devise: confirmations: send_instructions: "Daqui a uns minutos irá receber um email com instruções sobre como confirmar a sua conta." @@ -137,6 +132,7 @@ pt: free_trial: "experimentar sem pagar" plus_tax: "mais IVA" min_bill_turnover_desc: "assim que o volume de negócios exceder %{mbt_amount}" + more: "Mais" say_no: "Não" say_yes: "Sim" then: então @@ -160,6 +156,7 @@ pt: distributors: Distribuidores distribution: Distribuição bulk_order_management: Gestão de Encomendas por Atacado + enterprises: Organizações enterprise_groups: Grupos reports: Relatórios variant_overrides: Inventário @@ -310,6 +307,35 @@ pt: included_tax_tip: "A taxa total incluída no exemplo de conta mensal, dadas as definições e o volume de negócios indicado." total_monthly_bill_incl_tax: "Total da Conta Mensal (taxa incluída)" total_monthly_bill_incl_tax_tip: "O exemplo de conta mensal total incluindo taxas, dadas as definições e o volume de negócios indicado." + cache_settings: + show: + title: A carregar + distributor: Distribuidor + order_cycle: Ciclo de Encomendas + status: Status + diff: Diff + error: Erro + invoice_settings: + edit: + title: Configuração de Faturas + invoice_style2?: Use o modelo alternativo de fatura que inclui o total de impostos dividido por taxa e taxa de imposto por item (ainda não disponível para países que exibem preços sem taxas) + enable_receipt_printing?: Mostrar opções para imprimir recibos usando impressoras térmicas no selector de encomendas? + stripe_connect_settings: + edit: + title: "Ligar ao Stripe" + settings: "Definições" + stripe_connect_enabled: Permitir às lojas que aceitem pagamentos usando a ligação ao Stripe? + no_api_key_msg: Não existem contas Stripe associadas a esta organização. + configuration_explanation_html: Para instruções detalhadas sobre como configurar a integração com Stripe Connect, por favor consulte este guia. + status: Estado + ok: Ok + instance_secret_key: Chave Secreta da Instância + account_id: ID de Conta + business_name: Nome do Negócio + charges_enabled: Taxas activas + charges_enabled_warning: "Aviso: As taxas não estão activas para a sua conta" + auth_fail_error: A chave da API que indicou não é válida + empty_api_key_error_html: Não foi fornecida nenhuma chave de API Stripe. Para definir a sua chave de API, por favor siga estas instruções customers: index: add_customer: "Adicionar Consumidor/a" @@ -334,14 +360,6 @@ pt: search_by_email: "Pesquisar por e-mail/código" destroy: has_associated_orders: 'Não foi possível apagar: o/a consumidor/a tem encomendas associadas a esta loja.' - cache_settings: - show: - title: A carregar - distributor: Distribuidor - order_cycle: Ciclo de Encomendas - status: Status - diff: Diff - error: Erro contents: edit: title: Conteúdo @@ -352,6 +370,7 @@ pt: group_signup_page: Página de registo de Grupo footer_and_external_links: Rodapé e Ligações Externas your_content: O seu conteúdo + user_guide: Manual do Utilizador enterprise_fees: index: title: Taxas de Organização @@ -414,20 +433,18 @@ pt: index: select_file: Selecione uma folha de cálculo para carregar spreadsheet: Folha de cálculo - import_into: "Importar para:" product_list: Lista de produtos inventories: Inventários import: Importar upload: Carregar import: review: Rever - proceed: Continuar + import: Importar save: Guardar results: Resultados save_imported: Guardar produtos importados no_valid_entries: Não foram encontradas entradas válidas none_to_save: Não foram encontradas entradas que possam ser guardadas - some_invalid_entries: Ficheiro importado contém algumas entradas inválidas save_valid?: Guardar entradas válidas e descartar as outras? no_errors: Nenhum erro detectado. save_all_imported?: Guardar todos os produtos importados? @@ -436,7 +453,6 @@ pt: not_found: organização não encontrada no_name: Sem nome blank_supplier: alguns produtos têm o nome do fornecedor vazio - reset_absent?: Restabelecer produtos ausentes? overwrite_all: Substituir todos overwrite_empty: Substituir se vazio default_stock: Definir nível de stock @@ -454,7 +470,7 @@ pt: inventory_to_reset: Itens de inventário existentes terão o nível de stock restabelecido a zero line: Linha item_line: Linha de item - save: + save_results: final_results: Importar resultados finais products_created: Produtos criados products_updated: Produtos actualizados @@ -463,6 +479,8 @@ pt: products_reset: Os produtos tiveram o nível de stock restabelecido a zero inventory_reset: Itens de inventário tiveram o nível de stock restabelecido a zero all_saved: "Todos os itens guardados com sucesso" + some_saved: "itens guardados com sucesso" + save_errors: Erros a guardar variant_overrides: loading_flash: loading_inventory: A CARREGAR INVENTÁRIO... @@ -681,7 +699,7 @@ pt: email_confirmed: "Email confirmado" email_not_confirmed: "Email não confirmado" actions: - edit_profile: Editar Perfil + edit_profile: Definições properties: Propriedades payment_methods: Métodos de pagamento payment_methods_tip: Esta organização não tem formas de pagamento definidas @@ -721,13 +739,16 @@ pt: no_enterprises_found: Nenhuma organização encontrada. search_placeholder: Procurar por nome manage: Gerir + manage_link: Definições + producer?: "Produtor/a?" + package: "Embalagem" + status: "Estado" new_form: owner: Proprietário owner_tip: O utilizador principal responsável por esta organização. i_am_producer: Sou um produtor contact_name: Nome do contacto edit: - editing: 'Edição:' back_link: Voltar à lista de organizações new: title: Nova Organização @@ -764,6 +785,8 @@ pt: save_reload: Guardar e recarregar página coordinator_fees: add: Adicionar taxa de coordenador + filters: + involving: "Envolvendo" form: incoming: Entrada supplier: Fornecedor @@ -777,7 +800,6 @@ pt: delivery_details: Detalhes de entrega/levantamento debug_info: Informação de depuração index: - involving: Envolvendo schedule: Horário schedules: Horários adding_a_new_schedule: Adicionar um novo Horário @@ -813,6 +835,9 @@ pt: schedule_present: Esse ciclo de encomendas está ligado a um horário e não pode ser apagado. Por favor elimine a ligação ou apague primeiro o horário. bulk_update: no_data: Hmmm, algo correu mal. Não foram encontrados dados do ciclo de encomendas. + date_warning: + cancel: Cancelar + proceed: Continuar producer_properties: index: title: Propriedades do Produtor @@ -824,11 +849,6 @@ pt: shared: user_guide_link: user_guide: Manual do Utilizador - invoice_settings: - edit: - title: Configuração de Faturas - invoice_style2?: Use o modelo alternativo de fatura que inclui o total de impostos dividido por taxa e taxa de imposto por item (ainda não disponível para países que exibem preços sem taxas) - enable_receipt_printing?: Mostrar opções para imprimir recibos usando impressoras térmicas no selector de encomendas? overview: enterprises_header: ofn_with_tip: As Organizações são Produtores e/ou Hubs e representam a unidade básica de organização dentro da Open Food Network. @@ -927,7 +947,6 @@ pt: invalid_error: Ooops! Por favor preencha todos os campos obrigatórios... allowed_payment_method_types_tip: De momento, só podem ser usados métodos de pagamento em Dinheiro ou Stripe credit_card: Cartão de Crédito - no_cards_available: Não existem cartões disponíveis loading_flash: loading: A CARREGAR SUBSCRIÇÕES review: @@ -957,22 +976,6 @@ pt: schedules: destroy: associated_subscriptions_error: Este horário não pode ser eliminado porque tem subscrições associadas - stripe_connect_settings: - edit: - title: "Ligar ao Stripe" - settings: "Definições" - stripe_connect_enabled: Permitir às lojas que aceitem pagamentos usando a ligação ao Stripe? - no_api_key_msg: Não existem contas Stripe associadas a esta organização. - configuration_explanation_html: Para instruções detalhadas sobre como configurar a integração com Stripe Connect, por favor consulte este guia. - status: Estado - ok: Ok - instance_secret_key: Chave Secreta da Instância - account_id: ID de Conta - business_name: Nome do Negócio - charges_enabled: Taxas activas - charges_enabled_warning: "Aviso: As taxas não estão activas para a sua conta" - auth_fail_error: A chave da API que indicou não é válida - empty_api_key_error_html: Não foi fornecida nenhuma chave de API Stripe. Para definir a sua chave de API, por favor siga estas instruções controllers: enterprises: stripe_connect_cancelled: "A ligação ao Stripe foi cancelada" @@ -997,6 +1000,29 @@ pt: register_call: selling_on_ofn: "Tem interesse em participar na Open Food Network?" register: "Registe-se aqui" + footer: + footer_global_headline: "OFN Global" + footer_global_home: "Início" + footer_global_news: "Notícias" + footer_global_about: "Sobre" + footer_global_contact: "Contacto" + footer_sites_headline: "Páginas OFN" + footer_sites_developer: "Desenvolvimento" + footer_sites_community: "Comunidade" + footer_sites_userguide: "Manual do Utilizador" + footer_secure: "Seguro e de confiança." + footer_secure_text: "A Open Food Network utiliza a criptografia SSL (2048 bit RSA) para manter as suas informações em segurança. Os nossos servidores não guardam os detalhes do seu cartão de crédito e os pagamentos são processados por serviços compatíveis com PCI." + footer_contact_headline: "Ficamos em contacto" + footer_contact_email: "Envie-nos um email" + footer_nav_headline: "Navegar" + footer_join_headline: "Junte-se a nós" + footer_join_body: "Crie uma lista de ofertas, uma loja ou um grupo de consumo na Open Food Network" + footer_join_cta: "Quero saber mais!" + footer_legal_call: "Leia os nossos" + footer_legal_tos: "Termos e condições" + footer_legal_visit: "Encontre-nos no" + footer_legal_text_html: "A Open Food Network é uma plataforma livre e de código aberto. O nosso conteúdo tem uma licença %{content_license} e o nosso código %{code_license}." + footer_skylight_dashboard_html: Dados de performance disponível em %{dashboard}. shop: messages: login: "Entrar" @@ -1005,6 +1031,7 @@ pt: require_customer_login: "Essa loja é somente para clientes." require_login_html: "Por favor %{login} se já tem uma conta. Caso contrário, %{register} para se tornar consumidor." require_customer_html: "Por favor %{contact} a %{enterprise} para se tornar consumidor/a. " + card_could_not_be_updated: O cartão não pode ser actualizado card_could_not_be_saved: o cartão não pode ser guardado spree_gateway_error_flash_for_checkout: "Houve um problema com a sua informação de pagamento: %{error}" invoice_billing_address: "Morada de faturação:" @@ -1031,6 +1058,14 @@ pt: ticket_column_item: "Item" ticket_column_unit_price: "Preço Unitário" ticket_column_total_price: "Preço Total" + menu_1_title: "Lojas" + menu_1_url: "/shops" + menu_2_title: "Mapa" + menu_3_title: "Produtores" + menu_4_title: "Grupos" + menu_5_title: "Sobre" + menu_6_title: "Conectar" + menu_7_title: "Aprender" logo: "Logo (640x130)" logo_mobile: "logo mobile (75x26)" logo_mobile_svg: "Logo mobile (svg)" @@ -1046,7 +1081,6 @@ pt: footer_email: "Email" footer_links_md: "Links" footer_about_url: "URL Sobre" - footer_tos_url: "URL dos Termos de Serviço" name: Nome first_name: Primeiro Nome last_name: Último Nome @@ -1120,27 +1154,6 @@ pt: ie_warning_firefox: Descarregar Firefox ie_warning_ie: Actualizar Internet Explorer ie_warning_other: "Não consegue actualizar o navegador? Tente aceder à OFN pelo smartphone :-)" - footer_global_headline: "OFN Global" - footer_global_home: "Início" - footer_global_news: "Notícias" - footer_global_about: "Sobre" - footer_global_contact: "Contacto" - footer_sites_headline: "Páginas OFN" - footer_sites_developer: "Desenvolvimento" - footer_sites_community: "Comunidade" - footer_sites_userguide: "Manual do Utilizador" - footer_secure: "Seguro e de confiança." - footer_secure_text: "A Open Food Network utiliza a criptografia SSL (2048 bit RSA) para manter as suas informações em segurança. Os nossos servidores não guardam os detalhes do seu cartão de crédito e os pagamentos são processados por serviços compatíveis com PCI." - footer_contact_headline: "Ficamos em contacto" - footer_contact_email: "Envie-nos um email" - footer_nav_headline: "Navegar" - footer_join_headline: "Junte-se a nós" - footer_join_body: "Crie uma lista de ofertas, uma loja ou um grupo de consumo na Open Food Network" - footer_join_cta: "Quero saber mais!" - footer_legal_call: "Leia os nossos" - footer_legal_tos: "Termos e condições" - footer_legal_visit: "Encontre-nos no" - footer_legal_text_html: "A Open Food Network é uma plataforma livre e de código aberto. O nosso conteúdo tem uma licença %{content_license} e o nosso código %{code_license}." home_shop: Ir às compras brandstory_headline: "Para quem consome com princípios" brandstory_intro: "Às vezes a melhor forma de consertar o sistema é construir um novo..." @@ -1552,6 +1565,17 @@ pt: reset_password: "Redefinir palavra-passe" who_is_managing_enterprise: "Quem é responsável por gerir %{enterprise}? " update_and_recalculate_fees: "Actualizar e Recalcular Taxas" + registration: + steps: + type: + headline: "Último passo para adicionar %{enterprise}!" + question: "É produtor/a?" + yes_producer: "Sim, sou produtor/a" + no_producer: "Não, não sou produtor/a" + producer_field_error: "Por favor escolha uma opção. É produtor/a?" + yes_producer_help: "Produtores/as são quem faz coisas deliciosas para comer e/ou beber. É produtor/a se planta, cria, fermenta, amassa, munge ou molda algo." + no_producer_help: "Se não é produtor/a, é provavelmente alguém que vende e distribui alimentos. Pode ser uma cooperativa, um grupo de consumo, um distribuidor, um retalhista, ou outro." + create_profile: "Criar perfil" enterprise: registration: modal: @@ -1590,13 +1614,6 @@ pt: phone_field_placeholder: 'ex: 97 1234 5678' type: title: 'Tipo' - headline: "Último passo para adicionar %{enterprise}!" - question: "É produtor/a?" - yes_producer: "Sim, sou produtor/a" - no_producer: "Não, não sou produtor/a" - producer_field_error: "Por favor escolha uma opção. É produtor/a?" - yes_producer_help: "Produtores/as são quem faz coisas deliciosas para comer e/ou beber. É produtor/a se planta, cria, fermenta, amassa, munge ou molda algo." - no_producer_help: "Se não é produtor/a, é provavelmente alguém que vende e distribui alimentos. Pode ser uma cooperativa, um grupo de consumo, um distribuidor, um retalhista, ou outro." about: title: 'Sobre' images: @@ -1684,7 +1701,6 @@ pt: registration_type_error: "Por favor escolha uma opção. É produtor/a?" registration_type_producer_help: "Produtores/as são quem faz coisas deliciosas para comer e/ou beber. É produtor/a se planta, cria, fermenta, amassa, munge ou molda algo." registration_type_no_producer_help: "Se não é produtor/a, é provavelmente alguém que vende e distribui alimentos. Pode ser uma cooperativa, um grupo de consumo, um distribuidor, um retalhista, ou outro." - create_profile: "Criar perfil" registration_images_headline: "Obrigado!" registration_images_description: "Vamos adicionar umas boas imagens para o seu perfil ficar impecável!" registration_detail_headline: "Vamos Começar" @@ -1743,31 +1759,30 @@ pt: you_have_no_orders_yet: "Ainda não tem encomendas" running_balance: "Saldo corrente" outstanding_balance: "Saldo pendente" - admin_entreprise_relationships: "Relações da Organização" - admin_entreprise_relationships_everything: "Tudo" - admin_entreprise_relationships_permits: "permite" - admin_entreprise_relationships_seach_placeholder: "Procurar" - admin_entreprise_relationships_button_create: "Criar" - admin_entreprise_groups: "Grupos da Organização" - admin_entreprise_groups_name: "Nome" - admin_entreprise_groups_owner: "Proprietário" - admin_entreprise_groups_on_front_page: "Na página inicial?" - admin_entreprise_groups_entreprise: "Organizações" - admin_entreprise_groups_data_powertip: "O utilizador responsável por este grupo." - admin_entreprise_groups_data_powertip_logo: "Esse é o logo do grupo" - admin_entreprise_groups_data_powertip_promo_image: "Esta imagem aparecerá no topo do perfil do Grupo" - admin_entreprise_groups_contact: "Contacto" - admin_entreprise_groups_contact_phone_placeholder: "ex: 987654321" - admin_entreprise_groups_contact_address1_placeholder: "ex: Rua Alta, 123" - admin_entreprise_groups_contact_city: "Localidade" - admin_entreprise_groups_contact_city_placeholder: "ex. Famalicão" - admin_entreprise_groups_contact_zipcode: "Código Postal " - admin_entreprise_groups_contact_zipcode_placeholder: "ex: 4000-125" - admin_entreprise_groups_contact_state_id: "Região" - admin_entreprise_groups_contact_country_id: "País" - admin_entreprise_groups_web: "Recursos Web" - admin_entreprise_groups_web_twitter: "ex. @nome_perfil" - admin_entreprise_groups_web_website_placeholder: "ex. www.cogumelos.pt" + admin_enterprise_relationships_everything: "Tudo" + admin_enterprise_relationships_permits: "permite" + admin_enterprise_relationships_seach_placeholder: "Procurar" + admin_enterprise_relationships_button_create: "Criar" + admin_enterprise_groups: "Grupos da Organização" + admin_enterprise_groups_name: "Nome" + admin_enterprise_groups_owner: "Proprietário" + admin_enterprise_groups_on_front_page: "Na página inicial?" + admin_enterprise_groups_enterprise: "Organizações" + admin_enterprise_groups_data_powertip: "O utilizador responsável por este grupo." + admin_enterprise_groups_data_powertip_logo: "Esse é o logo do grupo" + admin_enterprise_groups_data_powertip_promo_image: "Esta imagem aparecerá no topo do perfil do Grupo" + admin_enterprise_groups_contact: "Contacto" + admin_enterprise_groups_contact_phone_placeholder: "ex: 987654321" + admin_enterprise_groups_contact_address1_placeholder: "ex: Rua Alta, 123" + admin_enterprise_groups_contact_city: "Localidade" + admin_enterprise_groups_contact_city_placeholder: "ex. Famalicão" + admin_enterprise_groups_contact_zipcode: "Código postal" + admin_enterprise_groups_contact_zipcode_placeholder: "ex: 4000-125" + admin_enterprise_groups_contact_state_id: "Região" + admin_enterprise_groups_contact_country_id: "País" + admin_enterprise_groups_web: "Recursos Web" + admin_enterprise_groups_web_twitter: "ex: @o_prof" + admin_enterprise_groups_web_website_placeholder: "ex: www.cogumelos.pt" admin_order_cycles: "Ciclos de Encomendas do Administrador" open: "Aberto" close: "Fechado" @@ -1867,11 +1882,7 @@ pt: edit_profile_details_etc: "Modificar o seu perfil: descrição, imagem, etc." order_cycle: "Ciclo de Encomendas" order_cycles: "Ciclos de Encomendas" - enterprises: "Organizações" - enterprise_relationships: "Relações da Organização" remove_tax: "Remover imposto" - enterprise_terms_of_service: "Termos de Serviço da Organização" - enterprises_require_tos: "As organizações têm de aceitar os Termos de Serviço" enterprise_tos_link: "Ligação para Termos de Serviço da Organização" enterprise_tos_message: "Queremos trabalhar com pessoas que partilham os nossos objectivos e valores. Por isso pedimos às organizações novas que concordem com os nossos" enterprise_tos_link_text: "Termos de Serviço." @@ -1906,7 +1917,7 @@ pt: report_payment_totals: 'Totais dos Pagamanetos' report_all: 'todos' report_order_cycle: "Ciclo de Encomendas:" - report_entreprises: "Organizações:" + report_enterprises: "Organizações:" report_users: "Utilizadores:" report_tax_rates: Taxas de imposto report_tax_types: Tipos de imposto @@ -2126,6 +2137,7 @@ pt: order_cycles_no_permission_to_coordinate_error: "Nenhuma das suas organizações tem permissão para coordenar um ciclo de encomendas." order_cycles_no_permission_to_create_error: "Não tem permissão para criar um ciclo de encomendas coordenado por essa organização." back_to_orders_list: "Voltar à lista de encomendas" + no_orders_found: "Nenhuma encomenda encontrada" order_information: "Informação da Encomenda" date_completed: "Data de Conclusão" amount: "Quantia" @@ -2150,6 +2162,7 @@ pt: choose: Escolha resolve_errors: Por favor resolva os seguintes erros more_items: "+ %{count} Mais" + default_card_updated: Cartão por defeito actualizado admin: enterprise_limit_reached: "Atingiu o número limite de organizações por conta. Escreva para %{contact_email} se precisar de aumentá-lo." modals: @@ -2271,6 +2284,8 @@ pt: resolve: Resolver new_tag_rule_dialog: select_rule_type: "Selecionar um tipo de regra:" + resend_user_email_confirmation: + resend: "Reenviar" out_of_stock: reduced_stock_available: Stock reduzido disponível out_of_stock_text: > @@ -2370,10 +2385,6 @@ pt: account_id: ID de Conta business_name: Nome do Negócio charges_enabled: Taxas activas - payments: - source_forms: - stripe: - no_payment_via_admin_backend: Neste momento não é possível criar pagamentos baseados em Stripe a partir do painel de administrador products: new: title: 'Novo Produto' @@ -2394,6 +2405,7 @@ pt: inherits_properties?: Herda Propriedades? available_on: Disponível em av_on: "Disp. em" + import_date: "Data de Importação" products_variant: variant_has_n_overrides: "Esta variante tem %{n}substituição(ões)" new_variant: "Nova variante" @@ -2411,12 +2423,13 @@ pt: bulk_coop_allocation: 'Cooperativa por Atacado - Alocação' bulk_coop_packing_sheets: 'Cooperativa por Atacado - Folhas de Empacotamento' bulk_coop_customer_payments: 'Cooperativa por Atacado - Pagamentos do Consumidor' - shared: - configuration_menu: - stripe_connect: Ligar ao Stripe variants: autocomplete: producer_name: Produtor + general_settings: + edit: + enterprises_require_tos: "As organizações têm de aceitar os Termos de Serviço" + footer_tos_url: "URL dos Termos de Serviço" checkout: payment: stripe: @@ -2521,6 +2534,7 @@ pt: paid?: Pago? view: Ver saved_cards: + default?: Por defeito? delete?: Apagar? localized_number: invalid_format: tem um formato inválido. Por favor introduza um número. diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 6b8ff19728c..742aabdc171 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1,4 +1,5 @@ sv: + language_name: "English" activerecord: attributes: spree/order: @@ -69,6 +70,7 @@ sv: distributors: Distributörer distribution: Distribution bulk_order_management: Hantera flera beställningar + enterprises: Företag enterprise_groups: Grupper reports: Rapporter variant_overrides: Lager @@ -123,6 +125,8 @@ sv: form_invalid: "Formuläret saknar data eller har innehåller ogiltiga fält" clear_filters: Rensa filterinställningar clear: Rensa + cancel: Avbryt + back: Backa columns: Kolumner actions: Handlingar viewing: "Tittar på: %{current_view_name}" @@ -186,6 +190,19 @@ sv: 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: "Totalt månadsfaktura (inkl moms)" total_monthly_bill_incl_tax_tip: "Exemplets totala månadsfaktura med skatt inkluderad enligt de inställningar och den omsättning som var angiven." + cache_settings: + show: + title: Inkomst + distributor: Distributör + order_cycle: Beställningsomgång + status: Status + diff: Differens + error: Fel + 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? ' customers: index: add_customer: "Lägg till kund" @@ -210,14 +227,6 @@ sv: search_by_email: "Sök via e-post/kod..." destroy: has_associated_orders: 'Radering misslyckades: kunden har associerade order med sin butik' - cache_settings: - show: - title: Inkomst - distributor: Distributör - order_cycle: Beställningsomgång - status: Status - diff: Differens - error: Fel contents: edit: title: Innehåll @@ -228,6 +237,7 @@ sv: group_signup_page: Registreringssida för grupper footer_and_external_links: Sidfot och externa länkar your_content: Ditt innehåll + user_guide: Användarinstruktion enterprise_fees: index: title: Företagsavgifter @@ -247,12 +257,28 @@ sv: manages: hanterar products: unit_name_placeholder: 't.ex. klasar' + index: + unit: Enhet + display_as: Visa som + category: Kategori + tax_category: Skattekategori + inherits_properties?: Ärva egenskaper? + available_on: 'Tillgänglig ' + av_on: "Md. På" Search: Sök 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." + product_import: + file_not_found: Filen hittades inte eller kunde inte öppnas + no_data: Ingen data hittades i kalkylbladet + model: + no_file: "Fel: ingen fil laddades upp" + could_not_process: "kunde inte hantera filen: ogiltig filtyp" + blank: kan inte vara blank + none_saved: sparade inte några produkter variant_overrides: loading_flash: loading_inventory: LADDNING AV INVENTARIELISTA @@ -442,7 +468,7 @@ sv: managers: Chefer managers_tip: Andra användare med tillstånd att leda detta företag. actions: - edit_profile: Redigera profilen + edit_profile: Inställningar properties: Egenskaper payment_methods: Betalningssätt payment_methods_tip: Detta företag har inga betalningssätt @@ -482,13 +508,16 @@ sv: no_enterprises_found: Inget företag kunde hittas. search_placeholder: Sök på namn manage: Administrera + manage_link: Inställningar + producer?: "Producent?" + package: "Paket" + status: "Status" 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 @@ -497,7 +526,6 @@ sv: 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: edit: advanced_settings: Avancerade inställningar @@ -530,7 +558,7 @@ sv: name: Namn orders_open: Order öppna till coordinator: Koordinator - order_closes: Order stänger + orders_close: Order stänger row: suppliers: leverantörer distributors: distributörer @@ -542,17 +570,14 @@ sv: customer_instructions_placeholder: Hämtnings eller leverans meddelanden products: Produkter fees: Avgifter + date_warning: + cancel: Avbryt 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? ' overview: enterprises_header: ofn_with_tip: Företag är Producenter och/eller Hubbar och är den grundläggande organisationsenheten inom Open Food Network. @@ -638,6 +663,28 @@ sv: register_call: selling_on_ofn: "Intresserad av att gå med i Open Food Network?" register: "Registrera dig här" + footer: + footer_global_headline: "OFN Globalt" + footer_global_home: "Hem" + footer_global_news: "Nyheter" + footer_global_about: "Om oss" + footer_global_contact: "Kontakt" + footer_sites_headline: "OFN webbsidor" + footer_sites_developer: "Utvecklare" + footer_sites_community: "Gemenskap" + footer_sites_userguide: "Användarinstruktion" + footer_secure: "Säkert och anförtrott." + footer_secure_text: "Open Food Network använder SSL-kryptering (2048 bitars RSA) överallt för att hålla din beställnings- och betalningsinformation privat. Våra servrar lagrar inte dina kreditkortsdetaljer och betalningar processeras av PCI-beskedliga tjänster." + footer_contact_headline: "Håll kontakten" + footer_contact_email: "Mejla oss" + footer_nav_headline: "Navigera" + footer_join_headline: "Gå med oss" + footer_join_body: "Skapa en lista, butik eller gruppförteckning på Open Food Network." + footer_join_cta: "Berätta mer!" + footer_legal_call: "Läs vår" + footer_legal_tos: "Användarvillkor" + footer_legal_visit: "Hitta oss på" + footer_legal_text_html: "Open Food Network är en avgiftsfri plattform för öppen källkod. Vårt innehåll har licens med %{content_license} och vår kod med %{code_license}." shop: messages: login: "Logga in" @@ -670,6 +717,13 @@ sv: ticket_column_item: "Vara" ticket_column_unit_price: "Styckpris" ticket_column_total_price: "Summa" + menu_1_title: "Butiker" + menu_2_title: "Karta" + menu_3_title: "Producenter " + menu_4_title: "Grupper" + menu_5_title: "Om oss" + menu_6_title: "Anslut" + menu_7_title: "Lär mer" logo: "Logotyp (640x130)" logo_mobile: "Mobil logotyp (75x26)" logo_mobile_svg: "Mobil logotyp (SVG)" @@ -685,7 +739,6 @@ sv: footer_email: "epost" footer_links_md: "Webblänkar " footer_about_url: "Om oss URL" - footer_tos_url: "Användarvillkor URL" name: Namn first_name: 'Förnamn ' last_name: 'Efternamn ' @@ -741,27 +794,6 @@ sv: ie_warning_firefox: Hämta Firefox ie_warning_ie: Uppdatera din Internet Explorer webbläsare ie_warning_other: "Kan du inte uppdatera din webbläsare? Pröva Open Food Network på din smartphone :-)" - footer_global_headline: "OFN Globalt" - footer_global_home: "Hem" - footer_global_news: "Nyheter" - footer_global_about: "Om oss" - footer_global_contact: "Kontakt" - footer_sites_headline: "OFN webbsidor" - footer_sites_developer: "Utvecklare" - footer_sites_community: "Gemenskap" - footer_sites_userguide: "Användarguide" - footer_secure: "Säkert och anförtrott." - footer_secure_text: "Open Food Network använder SSL-kryptering (2048 bitars RSA) överallt för att hålla din beställnings- och betalningsinformation privat. Våra servrar lagrar inte dina kreditkortsdetaljer och betalningar processeras av PCI-beskedliga tjänster." - footer_contact_headline: "Håll kontakten" - footer_contact_email: "Mejla oss" - footer_nav_headline: "Navigera" - footer_join_headline: "Gå med oss" - footer_join_body: "Skapa en lista, butik eller gruppförteckning på Open Food Network." - footer_join_cta: "Berätta mer!" - footer_legal_call: "Läs vår" - footer_legal_tos: "Användarvillkor" - footer_legal_visit: "Hitta oss på" - footer_legal_text_html: "Open Food Network är en avgiftsfri plattform för öppen källkod. Vårt innehåll har licens med %{content_license} och vår kod med %{code_license}." home_shop: Handla nu brandstory_headline: "Mat, icke-inkorporerad." brandstory_intro: "Ibland är det bästa sättet att fixa till systemet att starta ett nytt..." @@ -1142,6 +1174,17 @@ sv: password_reset_sent: "Ett e-postmeddelande med instruktioner hur lösenordet återställs har översänts!" reset_password: "Återställ lösenordet" who_is_managing_enterprise: "Vem är ansvarig hos %{enterprise}?" + registration: + steps: + type: + headline: "Sista steget för att lägga till %{enterprise}!" + question: "Är du en producent?" + yes_producer: "Ja, jag är en procucdent" + no_producer: "Nej, jag är inte en producent" + producer_field_error: "Var vänlig välj en. Är du en producent?" + yes_producer_help: "Producenter gör smakfulla rätter att äta och/eller dricka. Du är en producent om du odlar det, föder upp det, brygger det, bakar det behandlar det, förädlar det eller formar det." + no_producer_help: "Om du inte ät en producent är du antagligen någon som säljer och distribuerar mat. Du borde bli ett matställe, kooperation, inköpsgrupp, detaljhandlare,grossist eller annat. " + create_profile: "Skapa en profil" enterprise_contact: "Kontaktperson" enterprise_contact_required: "Du måste ange en kontaktperson." enterprise_email_address: "E-postadress" @@ -1219,7 +1262,6 @@ sv: registration_type_error: "Var vänlig välj en. Är du en producent?" registration_type_producer_help: "Producenter gör smakfulla rätter att äta och/eller dricka. Du är en producent om du odlar det, föder upp det, brygger det, bakar det behandlar det, förädlar det eller formar det." registration_type_no_producer_help: "Om du inte ät en producent är du antagligen någon som säljer och distribuerar mat. Du borde bli ett matställe, kooperation, inköpsgrupp, detaljhandlare,grossist eller annat. " - create_profile: "Skapa en profil" registration_images_headline: "Tack!" registration_images_description: "Låt oss ladda några säljande bilder så att din profil ser lockande ut! :)" registration_detail_headline: "Låt oss börja" @@ -1278,31 +1320,30 @@ sv: you_have_no_orders_yet: "Du har inga order ännu" running_balance: "Saldo" outstanding_balance: "Utestående behållning" - admin_entreprise_relationships: "Företagssamband" - admin_entreprise_relationships_everything: "Allting" - admin_entreprise_relationships_permits: "tillåtelse" - admin_entreprise_relationships_seach_placeholder: "Sök" - admin_entreprise_relationships_button_create: "Skapa" - admin_entreprise_groups: "Företagsgrpper" - admin_entreprise_groups_name: "Namn" - admin_entreprise_groups_owner: "Ägare" - admin_entreprise_groups_on_front_page: "På första sidan?" - admin_entreprise_groups_entreprise: "Företag" - admin_entreprise_groups_data_powertip: "Huvudansvarig för denna grupp." - 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: "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: "t.ex. 3070" - admin_entreprise_groups_contact_state_id: "Region" - admin_entreprise_groups_contact_country_id: "Land" - admin_entreprise_groups_web: "Webb resurser" - admin_entreprise_groups_web_twitter: "ex @the_prof" - admin_entreprise_groups_web_website_placeholder: "ex www.truffles.com" + admin_enterprise_relationships_everything: "Allting" + admin_enterprise_relationships_permits: "tillåtelse" + admin_enterprise_relationships_seach_placeholder: "Sök" + admin_enterprise_relationships_button_create: "Skapa" + admin_enterprise_groups: "Företagsgrpper" + admin_enterprise_groups_name: "Namn" + admin_enterprise_groups_owner: "Ägare" + admin_enterprise_groups_on_front_page: "På första sidan?" + admin_enterprise_groups_enterprise: "Företag" + admin_enterprise_groups_data_powertip: "Huvudansvarig för denna grupp." + admin_enterprise_groups_data_powertip_logo: "Detta är logotypen för gruppen" + admin_enterprise_groups_data_powertip_promo_image: "Denna bild visas överst på gruppens profil" + admin_enterprise_groups_contact: "Kontakt" + admin_enterprise_groups_contact_phone_placeholder: "ex 123 456 789" + admin_enterprise_groups_contact_address1_placeholder: "eg. 123 High Street" + admin_enterprise_groups_contact_city: "Stadsdel" + admin_enterprise_groups_contact_city_placeholder: "eg. Northcote" + admin_enterprise_groups_contact_zipcode: "Postkod" + admin_enterprise_groups_contact_zipcode_placeholder: "eg. 3070" + admin_enterprise_groups_contact_state_id: "Region" + admin_enterprise_groups_contact_country_id: "Land" + admin_enterprise_groups_web: "Webb resurser" + admin_enterprise_groups_web_twitter: "t.ex. @the_prof" + admin_enterprise_groups_web_website_placeholder: "ex www.shop.com" admin_order_cycles: "Administratörens order cykel" open: "Öppen" close: "Stängd" @@ -1352,12 +1393,7 @@ sv: spree_admin_enterprises_fees: "Företagsavgifter" spree_admin_enterprises_none_create_a_new_enterprise: "SKAPA ETT NYTT FÖRETAG" spree_admin_enterprises_none_text: "Du har inget företag ännu" - spree_admin_enterprises_producers_name: "Namn" - spree_admin_enterprises_producers_total_products: "Summa produkter" - spree_admin_enterprises_producers_active_products: "Aktiva produkter" - spree_admin_enterprises_producers_order_cycles: "Produkter i beställningsrunda" spree_admin_enterprises_tabs_hubs: "MATSTÄLLEN" - spree_admin_enterprises_tabs_producers: "PRODUCENTER" spree_admin_enterprises_producers_manage_products: "HANTERA PRODUKTER" spree_admin_enterprises_any_active_products_text: "Du har inga aktiva produkter." spree_admin_enterprises_create_new_product: "SKAPA EN NY PRODUKT" @@ -1396,10 +1432,7 @@ sv: edit_profile_details_etc: "Ändra beskrivningen av din profil, bilder, etc" order_cycle: "Ordercykel" order_cycles: "Beställningsomgångar" - enterprises: "Företag" 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" @@ -1434,7 +1467,7 @@ sv: report_payment_totals: 'Betalningstotaler' report_all: 'alla' report_order_cycle: "Beställningsrundor" - report_entreprises: "Företag:" + report_enterprises: "Företag:" report_users: "Användare:" report_tax_rates: Skattesatser report_tax_types: Typer av skatt @@ -1616,7 +1649,6 @@ sv: content_configuration_pricing_table: "(TODO: Pristabell)" content_configuration_case_studies: "(TODO: Case studies)" content_configuration_detail: "(TODO: Detalj)" - enterprise_name_error: "är redan registrerad. Om det här är ditt företag och du vill göra anspråk på den, vänligen kontakta nuvarande administratör för den här profilen på %{email}." enterprise_owner_error: "^ %{email} har inte rätt att äga några fler företag (gränsen är %{enterprise_limit})." enterprise_role_uniqueness_error: "^ Den rollen existerar redan." inventory_item_visibility_error: Måste vara 'true' eller 'false' @@ -1778,6 +1810,8 @@ sv: resolve: Besluta new_tag_rule_dialog: select_rule_type: "Välj en regeltyp:" + resend_user_email_confirmation: + resend: "Återsänd" out_of_stock: reduced_stock_available: Minska tillgängligt lager out_of_stock_text: > @@ -1862,6 +1896,15 @@ sv: 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" + products_head: + name: Namn + unit: Enhet + display_as: Visa som + category: Kategori + tax_category: Skattekategori + inherits_properties?: Ärva egenskaper? + available_on: 'Tillgänglig ' + av_on: "Md. På" reports: bulk_coop: bulk_coop_supplier_report: 'Bulk Co-op - Totaler per leverantör' @@ -1871,6 +1914,10 @@ sv: variants: autocomplete: producer_name: Producent + general_settings: + edit: + enterprises_require_tos: "Företag måste acceptera servicevillkoren" + footer_tos_url: "Användarvillkor URL" date_picker: format: '%Y-%m-%d' js_format: 'åå-mm-dd' diff --git a/config/routes.rb b/config/routes.rb index 574140bd5b1..bc3fc303f0f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,14 +88,16 @@ get '/:id/shop', to: 'enterprises#shop', as: 'enterprise_shop' get "/enterprises/:permalink", to: redirect("/") # Legacy enterprise URL - get "/angular-templates/:id", to: "angular_templates#show", constraints: { name: %r{[\/\w\.]+} } - namespace :api do resources :enterprises do post :update_image, on: :member get :managed, on: :collection get :accessible, on: :collection + + resource :logo, only: [:destroy] + resource :promo_image, only: [:destroy] end + resources :order_cycles do get :managed, on: :collection get :accessible, on: :collection @@ -105,10 +107,6 @@ get :job_queue end - scope '/cookies' do - resource :consent, only: [:show, :create, :destroy], :controller => "cookies_consent" - end - resources :customers, only: [:index, :update] post '/product_images/:product_id', to: 'product_images#update_product_image' @@ -116,6 +114,9 @@ get 'sitemap.xml', to: 'sitemap#index', defaults: { format: 'xml' } + # Mount Web engine routes + mount Web::Engine, :at => '/' + # Mount Spree's routes mount Spree::Core::Engine, :at => '/' end diff --git a/db/migrate/20180919102548_remove_shipping_method_name_from_spree_line_items.rb b/db/migrate/20180919102548_remove_shipping_method_name_from_spree_line_items.rb new file mode 100644 index 00000000000..eaae250a181 --- /dev/null +++ b/db/migrate/20180919102548_remove_shipping_method_name_from_spree_line_items.rb @@ -0,0 +1,9 @@ +class RemoveShippingMethodNameFromSpreeLineItems < ActiveRecord::Migration + def up + remove_column :spree_line_items, :shipping_method_name + end + + def down + add_column :spree_line_items, :shipping_method_name, :string + end +end diff --git a/db/migrate/20181010093850_fix_variants_missing_unit_value.rb b/db/migrate/20181010093850_fix_variants_missing_unit_value.rb new file mode 100644 index 00000000000..c4226d0dc22 --- /dev/null +++ b/db/migrate/20181010093850_fix_variants_missing_unit_value.rb @@ -0,0 +1,49 @@ +# Fixes variants whose product.variant_unit is 'weight' and miss a unit_value, +# showing 1 unit of the specified weight. That is, if the user chose Kg, it'll +# display 1 as unit. +class FixVariantsMissingUnitValue < ActiveRecord::Migration + HUMAN_UNIT_VALUE = 1 + + def up + logger.info "Fixing variants missing unit_value...\n" + + variants_missing_unit_value.find_each do |variant| + logger.info "Processing variant #{variant.id}..." + + fix_unit_value(variant) + end + + logger.info "Done!" + end + + def down + end + + private + + def variants_missing_unit_value + Spree::Variant + .joins(:product) + .readonly(false) + .where( + spree_products: { variant_unit: 'weight' }, + spree_variants: { unit_value: nil } + ) + end + + def fix_unit_value(variant) + variant.unit_value = HUMAN_UNIT_VALUE * variant.product.variant_unit_scale + + if variant.save + logger.info "Successfully fixed variant #{variant.id}" + else + logger.info "Failed fixing variant #{variant.id}" + end + + logger.info "" + end + + def logger + @logger ||= Logger.new('log/migrate.log') + end +end diff --git a/db/schema.rb b/db/schema.rb index 13f7aea1452..e786ec5708e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20180910155506) do +ActiveRecord::Schema.define(:version => 20181010093850) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false @@ -527,14 +527,13 @@ create_table "spree_line_items", :force => true do |t| t.integer "order_id" t.integer "variant_id" - t.integer "quantity", :null => false - t.decimal "price", :precision => 8, :scale => 2, :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.integer "quantity", :null => false + t.decimal "price", :precision => 8, :scale => 2, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "max_quantity" t.string "currency" t.decimal "distribution_fee", :precision => 10, :scale => 2 - t.string "shipping_method_name" t.decimal "final_weight_volume", :precision => 10, :scale => 2 t.decimal "cost_price", :precision => 8, :scale => 2 t.integer "tax_category_id" diff --git a/db/seeds.rb b/db/seeds.rb index 9561e036e21..17e5bf91b7f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2,6 +2,24 @@ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). require 'yaml' +def set_mail_configuration + MailConfiguration.entries= { + enable_mail_delivery: true, + mail_host: ENV.fetch('MAIL_HOST'), + mail_domain: ENV.fetch('MAIL_DOMAIN'), + mail_port: ENV.fetch('MAIL_PORT'), + mail_auth_type: 'login', + smtp_username: ENV.fetch('SMTP_USERNAME'), + smtp_password: ENV.fetch('SMTP_PASSWORD'), + secure_connection_type: ENV.fetch('MAIL_SECURE_CONNECTION', 'None'), + mails_from: ENV.fetch('MAILS_FROM', "no-reply@#{ENV.fetch('MAIL_DOMAIN')}"), + mail_bcc: ENV.fetch('MAIL_BCC', ''), + intercept_email: '' + } +end +# We need mail_configuration to create a user account, because it sends a confirmation email. +set_mail_configuration + # -- Spree unless Spree::Country.find_by_iso(ENV['DEFAULT_COUNTRY_CODE']) puts "[db:seed] Seeding Spree" @@ -30,23 +48,5 @@ end end -def set_mail_configuration - MailConfiguration.entries= { - enable_mail_delivery: true, - mail_host: ENV.fetch('MAIL_HOST'), - mail_domain: ENV.fetch('MAIL_DOMAIN'), - mail_port: ENV.fetch('MAIL_PORT'), - mail_auth_type: 'login', - smtp_username: ENV.fetch('SMTP_USERNAME'), - smtp_password: ENV.fetch('SMTP_PASSWORD'), - secure_connection_type: ENV.fetch('MAIL_SECURE_CONNECTION', 'None'), - mails_from: ENV.fetch('MAILS_FROM', "no-reply@#{ENV.fetch('MAIL_DOMAIN')}"), - mail_bcc: ENV.fetch('MAIL_BCC', ''), - intercept_email: '' - } -end - -set_mail_configuration - spree_user = Spree::User.first spree_user && spree_user.confirm! diff --git a/engines/web/README.md b/engines/web/README.md new file mode 100644 index 00000000000..87945856ef2 --- /dev/null +++ b/engines/web/README.md @@ -0,0 +1,5 @@ +# Web + +This is the rails engine for the Web domain. + +See our wiki for [more info about domains and engines in OFN](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Tech-Doc:-How-OFN-is-organized-in-Domains-using-Rails-Engines). diff --git a/engines/web/app/assets/javascripts/web/all.js b/engines/web/app/assets/javascripts/web/all.js new file mode 100644 index 00000000000..15ebed9422b --- /dev/null +++ b/engines/web/app/assets/javascripts/web/all.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require_tree . diff --git a/app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_controller.js.coffee b/engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_controller.js.coffee similarity index 100% rename from app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_controller.js.coffee rename to engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_controller.js.coffee diff --git a/app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_directive.js.coffee b/engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_directive.js.coffee similarity index 56% rename from app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_directive.js.coffee rename to engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_directive.js.coffee index 0599646cb79..a00354774c8 100644 --- a/app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_directive.js.coffee +++ b/engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_directive.js.coffee @@ -1,6 +1,7 @@ -Darkswarm.directive 'cookiesBanner', (CookiesBannerService) -> +Darkswarm.directive 'cookiesBanner', (CookiesBannerService, CookiesPolicyModalService) -> restrict: 'A' link: (scope, elm, attr)-> return if not attr.cookiesBanner? || attr.cookiesBanner == 'false' CookiesBannerService.enable() + return if CookiesPolicyModalService.isEnabled() CookiesBannerService.open() diff --git a/app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_service.js.coffee b/engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_service.js.coffee similarity index 86% rename from app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_service.js.coffee rename to engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_service.js.coffee index 139552a2149..c9d2911f73f 100644 --- a/app/assets/javascripts/darkswarm/cookies_banner/cookies_banner_service.js.coffee +++ b/engines/web/app/assets/javascripts/web/cookies_banner/cookies_banner_service.js.coffee @@ -4,7 +4,7 @@ Darkswarm.factory "CookiesBannerService", (Navigation, $modal, $location, Redire modalMessage: null isEnabled: false - open: (path, template = 'darkswarm/cookies_banner/cookies_banner.html') => + open: (path, template = 'angular-templates/cookies_banner.html') => return unless @isEnabled @modalInstance = $modal.open templateUrl: template diff --git a/app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_controller.js.coffee b/engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_controller.js.coffee similarity index 100% rename from app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_controller.js.coffee rename to engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_controller.js.coffee diff --git a/app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_directive.js.coffee b/engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_directive.js.coffee similarity index 100% rename from app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_directive.js.coffee rename to engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_directive.js.coffee diff --git a/app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_service.js.coffee b/engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_service.js.coffee similarity index 69% rename from app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_service.js.coffee rename to engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_service.js.coffee index 0f91645cfd8..a988b1eb0fa 100644 --- a/app/assets/javascripts/darkswarm/cookies_policy/cookies_policy_modal_service.js.coffee +++ b/engines/web/app/assets/javascripts/web/cookies_policy/cookies_policy_modal_service.js.coffee @@ -5,7 +5,7 @@ Darkswarm.factory "CookiesPolicyModalService", (Navigation, $modal, $location, C modalMessage: null constructor: -> - if $location.path() is @defaultPath || location.pathname is @defaultPath + if @isEnabled() @open '' open: (path = false, template = 'angular-templates/cookies_policy.html') => @@ -13,18 +13,16 @@ Darkswarm.factory "CookiesPolicyModalService", (Navigation, $modal, $location, C templateUrl: template windowClass: "cookies-policy-modal medium" - @closeCookiesBanner() - @onCloseReOpenCookiesBanner() + CookiesBannerService.close() + @onCloseOpenCookiesBanner() selectedPath = path || @defaultPath Navigation.navigate selectedPath - closeCookiesBanner: => - setTimeout -> - CookiesBannerService.close() - , 200 - - onCloseReOpenCookiesBanner: => + onCloseOpenCookiesBanner: => @modalInstance.result.then( -> CookiesBannerService.open(), -> CookiesBannerService.open() ) + + isEnabled: => + $location.path() is @defaultPath || location.pathname is @defaultPath diff --git a/engines/web/app/assets/javascripts/web/web.js b/engines/web/app/assets/javascripts/web/web.js new file mode 100644 index 00000000000..dee720facdc --- /dev/null +++ b/engines/web/app/assets/javascripts/web/web.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/engines/web/app/assets/stylesheets/web/all.css.scss b/engines/web/app/assets/stylesheets/web/all.css.scss new file mode 100644 index 00000000000..53fb36eef51 --- /dev/null +++ b/engines/web/app/assets/stylesheets/web/all.css.scss @@ -0,0 +1,2 @@ +@import 'web/pages/cookies_banner'; +@import 'web/pages/cookies_policy_modal'; diff --git a/app/assets/stylesheets/darkswarm/cookies_banner.css.scss b/engines/web/app/assets/stylesheets/web/pages/cookies_banner.css.scss similarity index 94% rename from app/assets/stylesheets/darkswarm/cookies_banner.css.scss rename to engines/web/app/assets/stylesheets/web/pages/cookies_banner.css.scss index 723b41eb1fa..a655c513a80 100644 --- a/app/assets/stylesheets/darkswarm/cookies_banner.css.scss +++ b/engines/web/app/assets/stylesheets/web/pages/cookies_banner.css.scss @@ -1,4 +1,4 @@ -@import 'branding'; +@import 'darkswarm/branding'; .cookies-banner { background: $dark-grey; diff --git a/app/assets/stylesheets/darkswarm/cookies_policy_modal.css.scss b/engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.css.scss similarity index 84% rename from app/assets/stylesheets/darkswarm/cookies_policy_modal.css.scss rename to engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.css.scss index 029cf1f2119..490fe6e529c 100644 --- a/app/assets/stylesheets/darkswarm/cookies_policy_modal.css.scss +++ b/engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.css.scss @@ -1,4 +1,4 @@ -@import 'branding'; +@import 'darkswarm/branding'; .cookies-policy-modal { background: $disabled-light; @@ -16,6 +16,11 @@ } } + iframe { + border: 0; + width: 100%; + } + &.fade { -ms-transform: translate(0, 0) !important; -webkit-transform: translate(0, 0) !important; diff --git a/engines/web/app/controllers/web/angular_templates_controller.rb b/engines/web/app/controllers/web/angular_templates_controller.rb new file mode 100644 index 00000000000..d8670a46643 --- /dev/null +++ b/engines/web/app/controllers/web/angular_templates_controller.rb @@ -0,0 +1,9 @@ +module Web + class AngularTemplatesController < ApplicationController + helper Web::Engine.helpers + + def show + render params[:id].to_s, layout: nil + end + end +end diff --git a/engines/web/app/controllers/web/api/cookies_consent_controller.rb b/engines/web/app/controllers/web/api/cookies_consent_controller.rb new file mode 100644 index 00000000000..e0ea13bafec --- /dev/null +++ b/engines/web/app/controllers/web/api/cookies_consent_controller.rb @@ -0,0 +1,30 @@ +require_dependency 'web/cookies_consent' + +module Web + module Api + class CookiesConsentController < BaseController + include ActionController::Cookies + respond_to :json + + def show + render json: { cookies_consent: cookies_consent.exists? } + end + + def create + cookies_consent.set + show + end + + def destroy + cookies_consent.destroy + show + end + + private + + def cookies_consent + @cookies_consent ||= Web::CookiesConsent.new(cookies, request.host) + end + end + end +end diff --git a/engines/web/app/controllers/web/application_controller.rb b/engines/web/app/controllers/web/application_controller.rb new file mode 100644 index 00000000000..410b0ae531b --- /dev/null +++ b/engines/web/app/controllers/web/application_controller.rb @@ -0,0 +1,5 @@ +module Web + class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + end +end diff --git a/engines/web/app/helpers/web/cookies_policy_helper.rb b/engines/web/app/helpers/web/cookies_policy_helper.rb new file mode 100644 index 00000000000..0274b2d6c7d --- /dev/null +++ b/engines/web/app/helpers/web/cookies_policy_helper.rb @@ -0,0 +1,23 @@ +module Web + module CookiesPolicyHelper + def render_cookie_entry(cookie_name, cookie_desc, cookie_domain = nil) + render partial: 'cookies_policy_entry', + locals: { cookie_name: cookie_name, + cookie_desc: cookie_desc, + cookie_domain: cookie_domain } + end + + def matomo_iframe_src + "#{Spree::Config.matomo_url}"\ + "/index.php?module=CoreAdminHome&action=optOut"\ + "&language=#{locale_language}"\ + "&backgroundColor=&fontColor=222222&fontSize=16px&fontFamily=%22Roboto%22%2C%20Arial%2C%20sans-serif" + end + + # removes country from locale if needed + # for example, both locales en and en_GB return language en + def locale_language + I18n.locale[0..1] + end + end +end diff --git a/app/views/angular_templates/_cookies_policy_entry.html.haml b/engines/web/app/views/web/angular_templates/_cookies_policy_entry.html.haml similarity index 100% rename from app/views/angular_templates/_cookies_policy_entry.html.haml rename to engines/web/app/views/web/angular_templates/_cookies_policy_entry.html.haml diff --git a/app/assets/javascripts/darkswarm/cookies_banner/cookies_banner.html.haml b/engines/web/app/views/web/angular_templates/cookies_banner.html.haml similarity index 100% rename from app/assets/javascripts/darkswarm/cookies_banner/cookies_banner.html.haml rename to engines/web/app/views/web/angular_templates/cookies_banner.html.haml diff --git a/app/views/angular_templates/cookies_policy.html.haml b/engines/web/app/views/web/angular_templates/cookies_policy.html.haml similarity index 94% rename from app/views/angular_templates/cookies_policy.html.haml rename to engines/web/app/views/web/angular_templates/cookies_policy.html.haml index e43d1ded9ed..982ba89d53e 100644 --- a/app/views/angular_templates/cookies_policy.html.haml +++ b/engines/web/app/views/web/angular_templates/cookies_policy.html.haml @@ -69,6 +69,12 @@ = render_cookie_entry( "_pk_hsr, _pk_cvar, _pk_id and _pk_ses", t( "legal.cookies_policy.cookie_matomo_heatmap_desc" ) ) = render_cookie_entry( "piwik_ignore, _pk_cvar, _pk_id and _pk_ses", t( "legal.cookies_policy.cookie_matomo_ignore_desc" ) ) + - if Spree::Config.cookies_policy_matomo_section && Spree::Config.matomo_url.present? + %p + = t 'legal.cookies_policy.statistics_cookies_matomo_optout' + %p + %iframe{ src: matomo_iframe_src } + %h2 = t 'legal.cookies_policy.disabling_cookies_header' %p diff --git a/engines/web/config/routes.rb b/engines/web/config/routes.rb new file mode 100644 index 00000000000..121f3bdd9a0 --- /dev/null +++ b/engines/web/config/routes.rb @@ -0,0 +1,9 @@ +Web::Engine.routes.draw do + namespace :api do + scope '/cookies' do + resource :consent, only: [:show, :create, :destroy], controller: "cookies_consent" + end + end + + get "/angular-templates/:id", to: "angular_templates#show", constraints: { name: %r{[\/\w\.]+} } +end diff --git a/engines/web/lib/web.rb b/engines/web/lib/web.rb new file mode 100644 index 00000000000..613b7630143 --- /dev/null +++ b/engines/web/lib/web.rb @@ -0,0 +1,4 @@ +require "web/engine" + +module Web +end diff --git a/engines/web/lib/web/cookies_consent.rb b/engines/web/lib/web/cookies_consent.rb new file mode 100644 index 00000000000..64f6c0c825e --- /dev/null +++ b/engines/web/lib/web/cookies_consent.rb @@ -0,0 +1,31 @@ +module Web + class CookiesConsent + COOKIE_NAME = 'cookies_consent'.freeze + + def initialize(cookies, domain) + @cookies = cookies + @domain = domain + end + + def exists? + cookies.key?(COOKIE_NAME) + end + + def destroy + cookies.delete(COOKIE_NAME, domain: domain) + end + + def set + cookies[COOKIE_NAME] = { + value: COOKIE_NAME, + expires: 1.year.from_now, + domain: domain, + httponly: true + } + end + + private + + attr_reader :cookies, :domain + end +end diff --git a/engines/web/lib/web/engine.rb b/engines/web/lib/web/engine.rb new file mode 100644 index 00000000000..5285d6726b3 --- /dev/null +++ b/engines/web/lib/web/engine.rb @@ -0,0 +1,5 @@ +module Web + class Engine < ::Rails::Engine + isolate_namespace Web + end +end diff --git a/engines/web/lib/web/version.rb b/engines/web/lib/web/version.rb new file mode 100644 index 00000000000..ae51029b5c3 --- /dev/null +++ b/engines/web/lib/web/version.rb @@ -0,0 +1,3 @@ +module Web + VERSION = "0.0.1".freeze +end diff --git a/engines/web/spec/helpers/cookies_policy_helper_spec.rb b/engines/web/spec/helpers/cookies_policy_helper_spec.rb new file mode 100644 index 00000000000..b4d7f7fdf3f --- /dev/null +++ b/engines/web/spec/helpers/cookies_policy_helper_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +module Web + describe CookiesPolicyHelper, type: :helper do + # keeps global state unchanged + around do |example| + original_locale = I18n.locale + original_matomo_url = Spree::Config.matomo_url + example.run + Spree::Config.matomo_url = original_matomo_url + I18n.locale = original_locale + end + + describe "matomo optout iframe src" do + describe "when matomo url is set" do + before do + Spree::Config.matomo_url = "http://matomo.org/" + end + + scenario "includes the matomo URL" do + expect(helper.matomo_iframe_src).to include Spree::Config.matomo_url + end + + scenario "is not equal to the matomo URL" do + expect(helper.matomo_iframe_src).to_not eq Spree::Config.matomo_url + end + end + + scenario "is not nil, when matomo url is nil" do + Spree::Config.matomo_url = nil + expect(helper.matomo_iframe_src).to_not eq nil + end + end + + describe "language from locale" do + scenario "when locale is the language" do + I18n.locale = "en" + expect(helper.locale_language).to eq "en" + end + + scenario "is empty when locale is empty" do + I18n.locale = "" + expect(helper.locale_language).to be_empty + end + + scenario "is only the language, when locale includes country" do + I18n.locale = "en_GB" + expect(helper.locale_language).to eq "en" + end + end + end +end diff --git a/engines/web/spec/spec_helper.rb b/engines/web/spec/spec_helper.rb new file mode 100644 index 00000000000..3306063166c --- /dev/null +++ b/engines/web/spec/spec_helper.rb @@ -0,0 +1,8 @@ +ENV["RAILS_ENV"] = "test" + +require File.expand_path("dummy/config/environment.rb", __dir__) +require "rails/test_help" + +Rails.backtrace_cleaner.remove_silencers! + +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } diff --git a/engines/web/web.gemspec b/engines/web/web.gemspec new file mode 100644 index 00000000000..f44ee68909e --- /dev/null +++ b/engines/web/web.gemspec @@ -0,0 +1,13 @@ +$LOAD_PATH.push File.expand_path('lib', __dir__) + +require "web/version" + +Gem::Specification.new do |s| + s.name = "web" + s.version = Web::VERSION + s.authors = ["developers@ofn"] + s.summary = "Web domain of the OFN solution." + + s.files = Dir["{app,config,db,lib}/**/*"] + ["LICENSE.txt", "Rakefile", "README.rdoc"] + s.test_files = Dir["test/**/*"] +end diff --git a/lib/discourse/single_sign_on.rb b/lib/discourse/single_sign_on.rb index edc1acfa79c..2e39f292a85 100644 --- a/lib/discourse/single_sign_on.rb +++ b/lib/discourse/single_sign_on.rb @@ -42,7 +42,7 @@ def self.parse(payload, sso_secret = nil) if BOOLS.include? k val = ["true", "false"].include?(val) ? val == "true" : nil end - sso.send("#{k}=", val) + sso.public_send("#{k}=", val) end decoded_hash.each do |k,v| @@ -87,9 +87,9 @@ def payload def unsigned_payload payload = {} ACCESSORS.each do |k| - next if (val = send k) == nil + next if (val = public_send k) == nil - payload[k] = val + payload[k] = val end if @custom_fields diff --git a/lib/open_food_network/address_finder.rb b/lib/open_food_network/address_finder.rb index 49d3242667d..fdccecf1cba 100644 --- a/lib/open_food_network/address_finder.rb +++ b/lib/open_food_network/address_finder.rb @@ -12,7 +12,7 @@ def initialize(*args) args.each do |arg| type = types[arg.class] next unless type - send("#{type}=", arg) + public_send("#{type}=", arg) end end @@ -24,16 +24,6 @@ def ship_address customer_preferred_ship_address || user_preferred_ship_address || fallback_ship_address end - private - - def types - { - String => "email", - Customer => "customer", - Spree::User => "user" - } - end - def email=(arg) @email ||= arg end @@ -46,6 +36,16 @@ def user=(arg) @user ||= arg end + private + + def types + { + String => "email", + Customer => "customer", + Spree::User => "user" + } + end + def customer_preferred_bill_address customer.andand.bill_address end diff --git a/lib/open_food_network/enterprise_fee_calculator.rb b/lib/open_food_network/enterprise_fee_calculator.rb index ee3fa7648c6..b6816cc44b2 100644 --- a/lib/open_food_network/enterprise_fee_calculator.rb +++ b/lib/open_food_network/enterprise_fee_calculator.rb @@ -58,6 +58,41 @@ def create_order_adjustments_for(order) end end + def per_item_enterprise_fee_applicators_for(variant) + fees = [] + + return [] unless @order_cycle && @distributor + + @order_cycle.exchanges_carrying(variant, @distributor).each do |exchange| + exchange.enterprise_fees.per_item.each do |enterprise_fee| + fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, exchange.role) + end + end + + @order_cycle.coordinator_fees.per_item.each do |enterprise_fee| + fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, 'coordinator') + end + + fees + end + + def per_order_enterprise_fee_applicators_for(order) + fees = [] + + return fees unless @order_cycle && order.distributor + + @order_cycle.exchanges_supplying(order).each do |exchange| + exchange.enterprise_fees.per_order.each do |enterprise_fee| + fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, exchange.role) + end + end + + @order_cycle.coordinator_fees.per_order.each do |enterprise_fee| + fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, 'coordinator') + end + + fees + end private @@ -104,41 +139,5 @@ def calculate_fee_for(variant, enterprise_fee) line_item = OpenStruct.new variant: variant, quantity: 1, price: variant.price, amount: variant.price enterprise_fee.compute_amount(line_item) end - - def per_item_enterprise_fee_applicators_for(variant) - fees = [] - - return [] unless @order_cycle && @distributor - - @order_cycle.exchanges_carrying(variant, @distributor).each do |exchange| - exchange.enterprise_fees.per_item.each do |enterprise_fee| - fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, exchange.role) - end - end - - @order_cycle.coordinator_fees.per_item.each do |enterprise_fee| - fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, 'coordinator') - end - - fees - end - - def per_order_enterprise_fee_applicators_for(order) - fees = [] - - return fees unless @order_cycle && order.distributor - - @order_cycle.exchanges_supplying(order).each do |exchange| - exchange.enterprise_fees.per_order.each do |enterprise_fee| - fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, exchange.role) - end - end - - @order_cycle.coordinator_fees.per_order.each do |enterprise_fee| - fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, 'coordinator') - end - - fees - end end end diff --git a/lib/open_food_network/error_logger.rb b/lib/open_food_network/error_logger.rb new file mode 100644 index 00000000000..a1199c9e68f --- /dev/null +++ b/lib/open_food_network/error_logger.rb @@ -0,0 +1,13 @@ +# Our error logging API currently wraps Bugsnag. +# It makes us more flexible if we wanted to replace Bugsnag or change logging +# behaviour. +module OpenFoodNetwork + module ErrorLogger + # Tries to escalate the error to a developer. + # If Bugsnag is configured, it will notify it. It would be nice to implement + # some kind of fallback. + def self.notify(error) + Bugsnag.notify(error) + end + end +end diff --git a/lib/open_food_network/paperclippable.rb b/lib/open_food_network/paperclippable.rb index 82451651c8a..580f8507b2e 100644 --- a/lib/open_food_network/paperclippable.rb +++ b/lib/open_food_network/paperclippable.rb @@ -4,18 +4,18 @@ module OpenFoodNetwork module Paperclippable def self.included(base) - base.send :extend, ActiveModel::Naming - base.send :extend, ActiveModel::Callbacks - base.send :include, ActiveModel::Validations - base.send :include, Paperclip::Glue + base.extend(ActiveModel::Naming) + base.extend(ActiveModel::Callbacks) + base.include(ActiveModel::Validations) + base.include(Paperclip::Glue) # Paperclip required callbacks - base.send :define_model_callbacks, :save, only: [:after] - base.send :define_model_callbacks, :commit, only: [:after] - base.send :define_model_callbacks, :destroy, only: [:before, :after] + base.define_model_callbacks(:save, only: [:after]) + base.define_model_callbacks(:commit, only: [:after]) + base.define_model_callbacks(:destroy, only: [:before, :after]) # Initialise an ID - base.send :attr_accessor, :id + base.__send__(:attr_accessor, :id) base.instance_variable_set :@id, 1 end diff --git a/lib/open_food_network/products_and_inventory_report_base.rb b/lib/open_food_network/products_and_inventory_report_base.rb index 121df3221ec..15110047d9e 100644 --- a/lib/open_food_network/products_and_inventory_report_base.rb +++ b/lib/open_food_network/products_and_inventory_report_base.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + module OpenFoodNetwork class ProductsAndInventoryReportBase attr_reader :params diff --git a/lib/open_food_network/products_cache_refreshment.rb b/lib/open_food_network/products_cache_refreshment.rb index 276734570f9..3bcde00f8de 100644 --- a/lib/open_food_network/products_cache_refreshment.rb +++ b/lib/open_food_network/products_cache_refreshment.rb @@ -17,31 +17,26 @@ module OpenFoodNetwork class ProductsCacheRefreshment def self.refresh(distributor, order_cycle) - unless pending_job? distributor, order_cycle - enqueue_job distributor, order_cycle - end + job = refresh_job(distributor, order_cycle) + enqueue_job(job) unless pending_job?(job) end + def self.refresh_job(distributor, order_cycle) + RefreshProductsCacheJob.new(distributor.id, order_cycle.id) + end + private_class_method :refresh_job - private - - def self.pending_job?(distributor, order_cycle) - # To inspect each job, we need to deserialize the payload. - # This is slow, and if it's a problem in practice, we could pre-filter in SQL - # for handlers matching the class name, distributor id and order cycle id. - + def self.pending_job?(job) Delayed::Job. where(locked_at: nil). - map(&:payload_object). - select { |j| - j.class == RefreshProductsCacheJob && - j.distributor_id == distributor.id && - j.order_cycle_id == order_cycle.id - }.any? + where(handler: job.to_yaml). + exists? end + private_class_method :pending_job? - def self.enqueue_job(distributor, order_cycle) - Delayed::Job.enqueue RefreshProductsCacheJob.new(distributor.id, order_cycle.id), priority: 10 + def self.enqueue_job(job) + Delayed::Job.enqueue job, priority: 10 end + private_class_method :enqueue_job end end diff --git a/lib/open_food_network/scope_product_to_hub.rb b/lib/open_food_network/scope_product_to_hub.rb index 98ffbd84ff9..f06eea430b8 100644 --- a/lib/open_food_network/scope_product_to_hub.rb +++ b/lib/open_food_network/scope_product_to_hub.rb @@ -8,7 +8,7 @@ def initialize(hub) end def scope(product) - product.send :extend, OpenFoodNetwork::ScopeProductToHub::ScopeProductToHub + product.extend(OpenFoodNetwork::ScopeProductToHub::ScopeProductToHub) product.instance_variable_set :@hub, @hub product.instance_variable_set :@variant_overrides, @variant_overrides end diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index feb99ae7c33..bc2afa63da0 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -6,7 +6,7 @@ def initialize(hub, variant_overrides = nil) end def scope(variant) - variant.send :extend, OpenFoodNetwork::ScopeVariantToHub::ScopeVariantToHub + variant.extend(OpenFoodNetwork::ScopeVariantToHub::ScopeVariantToHub) variant.instance_variable_set :@hub, @hub variant.instance_variable_set :@variant_override, @variant_overrides[variant] end diff --git a/lib/open_food_network/scope_variants_for_search.rb b/lib/open_food_network/scope_variants_for_search.rb index d04502dd4cf..9befef53044 100644 --- a/lib/open_food_network/scope_variants_for_search.rb +++ b/lib/open_food_network/scope_variants_for_search.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + # Used to return a set of variants which match the criteria provided # A query string is required, which will be match to the name and/or SKU of a product # Further restrictions on the schedule, order_cycle or distributor through which the diff --git a/lib/open_food_network/tag_rule_applicator.rb b/lib/open_food_network/tag_rule_applicator.rb index 5225dfdd867..803b426f693 100644 --- a/lib/open_food_network/tag_rule_applicator.rb +++ b/lib/open_food_network/tag_rule_applicator.rb @@ -24,6 +24,11 @@ def filter!(subject) end end + def rules + return @rules unless @rules.nil? + @rules = rule_class.prioritised.for(enterprise) + end + private def reject?(element) @@ -38,11 +43,6 @@ def reject?(element) false end - def rules - return @rules unless @rules.nil? - @rules = rule_class.prioritised.for(enterprise) - end - def customer_rules return @customer_matched_rules unless @customer_matched_rules.nil? @customer_matched_rules = rules.select{ |rule| customer_tags_match?(rule) } diff --git a/lib/spree/api/testing_support/setup.rb b/lib/spree/api/testing_support/setup.rb index b8ffcd50f9a..db8633e2529 100644 --- a/lib/spree/api/testing_support/setup.rb +++ b/lib/spree/api/testing_support/setup.rb @@ -20,7 +20,9 @@ def sign_in_as_enterprise_user!(enterprises) let!(:current_api_user) do user = create(:user) user.spree_roles = [] - enterprises.each { |e| user.enterprise_roles.create(enterprise: send(e)) } + enterprises.each do |enterprise| + user.enterprise_roles.create(enterprise: public_send(enterprise)) + end user.save! user end diff --git a/lib/spree/core/controller_helpers/auth_decorator.rb b/lib/spree/core/controller_helpers/auth_decorator.rb new file mode 100644 index 00000000000..1c5f9de911d --- /dev/null +++ b/lib/spree/core/controller_helpers/auth_decorator.rb @@ -0,0 +1,5 @@ +Spree::Core::ControllerHelpers::Auth.class_eval do + def require_login_then_redirect_to(url) + redirect_to root_path(anchor: "login?after_login=#{url}") + end +end diff --git a/lib/spree/core/controller_helpers/order_decorator.rb b/lib/spree/core/controller_helpers/order_decorator.rb index dc38e861e64..eb9ad451eb4 100644 --- a/lib/spree/core/controller_helpers/order_decorator.rb +++ b/lib/spree/core/controller_helpers/order_decorator.rb @@ -1,3 +1,5 @@ +require 'open_food_network/scope_variant_to_hub' + Spree::Core::ControllerHelpers::Order.class_eval do def current_order_with_scoped_variants(create_order_if_necessary = false) order = current_order_without_scoped_variants(create_order_if_necessary) diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb index 9b1579228d2..1b8928e8848 100644 --- a/lib/stripe/profile_storer.rb +++ b/lib/stripe/profile_storer.rb @@ -17,7 +17,7 @@ def create_customer_from_token attrs = source_attrs_from(response) @payment.source.update_attributes!(attrs) else - @payment.send(:gateway_error, response.message) + @payment.__send__(:gateway_error, response.message) end end diff --git a/lib/stripe/webhook_handler.rb b/lib/stripe/webhook_handler.rb index 875dd9e52be..231eb78b03d 100644 --- a/lib/stripe/webhook_handler.rb +++ b/lib/stripe/webhook_handler.rb @@ -6,7 +6,7 @@ def initialize(event) def handle return :unknown unless known_event? - send(event_mappings[@event.type]) + __send__(event_mappings[@event.type]) end private diff --git a/lib/tasks/users.rake b/lib/tasks/users.rake deleted file mode 100644 index 0391c2d4aec..00000000000 --- a/lib/tasks/users.rake +++ /dev/null @@ -1,82 +0,0 @@ -require 'csv' - -namespace :openfoodnetwork do - - namespace :dev do - desc 'export users to CSV' - task export_users: :environment do - CSV.open('db/users.csv', 'wb') do |csv| - csv << user_header - users.each do |user| - csv << user_row(user) - end - end - end - - - desc 'import users from CSV' - task import_users: :environment do - ActionMailer::Base.delivery_method = :test - - CSV.foreach('db/users.csv') do |row| - next if row[0] == 'encrypted_password' - - create_user_from row - end - end - - - private - - def users - # Skip some spambot users - Spree::User.all.reject { |u| u.email =~ /example.net/ } - end - - def user_header - ["encrypted_password", "password_salt", "email", "remember_token", "persistence_token", "reset_password_token", "perishable_token", "sign_in_count", "failed_attempts", "last_request_at", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip", "login", "created_at", "updated_at", "authentication_token", "unlock_token", "locked_at", "remember_created_at", "reset_password_sent_at", - - "role_name", - - "ship_address_firstname", "ship_address_lastname", "ship_address_address1", "ship_address_address2", "ship_address_city", "ship_address_zipcode", "ship_address_phone", "ship_address_state", "ship_address_country", "ship_address_created_at", "ship_address_updated_at", "ship_address_company", - - "bill_address_firstname", "bill_address_lastname", "bill_address_address1", "bill_address_address2", "bill_address_city", "bill_address_zipcode", "bill_address_phone", "bill_address_state", "bill_address_country", "bill_address_created_at", "bill_address_updated_at", "bill_address_company",] - end - - def user_row(user) - sa = user.orders.last.andand.ship_address - ba = user.orders.last.andand.bill_address - - [user.encrypted_password, user.password_salt, user.email, user.remember_token, user.persistence_token, user.reset_password_token, user.perishable_token, user.sign_in_count, user.failed_attempts, user.last_request_at, user.current_sign_in_at, user.last_sign_in_at, user.current_sign_in_ip, user.last_sign_in_ip, user.login, user.created_at, user.updated_at, user.authentication_token, user.unlock_token, user.locked_at, user.remember_created_at, user.reset_password_sent_at, - - user.spree_roles.first.andand.name, - - sa.andand.firstname, sa.andand.lastname, sa.andand.address1, sa.andand.address2, sa.andand.city, sa.andand.zipcode, sa.andand.phone, sa.andand.state, sa.andand.country, sa.andand.created_at, sa.andand.updated_at, sa.andand.company, - - ba.andand.firstname, ba.andand.lastname, ba.andand.address1, ba.andand.address2, ba.andand.city, ba.andand.zipcode, ba.andand.phone, ba.andand.state, ba.andand.country, ba.andand.created_at, ba.andand.updated_at, ba.andand.company,] - end - - def create_user_from(row) - user = Spree::User.create!({password: 'changeme123', password_confirmation: 'changeme123', email: row[2], remember_token: row[3], persistence_token: row[4], reset_password_token: row[5], perishable_token: row[6], sign_in_count: row[7], failed_attempts: row[8], last_request_at: row[9], current_sign_in_at: row[10], last_sign_in_at: row[11], current_sign_in_ip: row[12], last_sign_in_ip: row[13], login: row[14], created_at: row[15], updated_at: row[16], authentication_token: row[17], unlock_token: row[18], locked_at: row[19], remember_created_at: row[20], reset_password_sent_at: row[21]}, without_protection: true) - - user.update_column :encrypted_password, row[0] - user.update_column :password_salt, row[1] - - # Safer if we don't make new users into admins - #role = Spree::Role.find_by_name row[24] - #user.spree_roles << role if role - - sa_state = Spree::State.find_by_name row[30] - sa_country = Spree::Country.find_by_name row[31] - sa = Spree::Address.create!({firstname: row[23], lastname: row[24], address1: row[25], address2: row[26], city: row[27], zipcode: row[28], phone: row[29], state: sa_state, country: sa_country, created_at: row[32], updated_at: row[33], company: row[34]}, without_protection: true) - user.update_column :ship_address_id, sa.id - - ba_state = Spree::State.find_by_name row[42] - ba_country = Spree::Country.find_by_name row[43] - ba = Spree::Address.create!({firstname: row[35], lastname: row[36], address1: row[37], address2: row[38], city: row[39], zipcode: row[40], phone: row[41], state: ba_state, country: ba_country, created_at: row[44], updated_at: row[45], company: row[46]}, without_protection: true) - user.update_column :bill_address_id, ba.id - rescue ActiveRecord::RecordInvalid => e - puts "#{row[2]} - #{e.message}" - end - end -end diff --git a/script/ci/includes.sh b/script/ci/includes.sh index bcb56624695..8981b76ee42 100644 --- a/script/ci/includes.sh +++ b/script/ci/includes.sh @@ -1,10 +1,3 @@ -function load_environment { - source /var/lib/jenkins/.rvm/environments/ruby-2.1.5 - if [ ! -f config/application.yml ]; then - ln -s application.yml.example config/application.yml - fi -} - function require_env_vars { for var in "$@"; do eval value=\$$var @@ -66,12 +59,6 @@ function get_ofn_commit { fi } -function checkout_ofn_commit { - OFN_COMMIT=`buildkite-agent meta-data get "openfoodnetwork:git:commit"` - echo "Checking out stored commit $OFN_COMMIT" - git checkout -qf "$OFN_COMMIT" -} - function drop_and_recreate_database { # Adapted from: http://stackoverflow.com/questions/12924466/capistrano-with-postgresql-error-database-is-being-accessed-by-other-users DB=$1 diff --git a/script/ci/run_js_tests.sh b/script/ci/run_js_tests.sh deleted file mode 100755 index cd274eaf47e..00000000000 --- a/script/ci/run_js_tests.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e - -echo "--- Loading environment" -source ./script/ci/includes.sh -load_environment -checkout_ofn_commit - -echo "--- Verifying branch is based on current master" -exit_unless_master_merged - -echo "--- Bundling" -bundle install - -echo "--- Running tests" -./script/karma run diff --git a/script/ci/run_tests.sh b/script/ci/run_tests.sh deleted file mode 100755 index efae0805a62..00000000000 --- a/script/ci/run_tests.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -e - -echo "--- Loading environment" -source ./script/ci/includes.sh -load_environment -checkout_ofn_commit - -echo "--- Verifying branch is based on current master" -exit_unless_master_merged - -echo "--- Bundling" -bundle install - -echo "--- Loading test database" -bundle exec rake db:drop db:create db:schema:load -bundle exec rake parallel:drop parallel:create parallel:load_schema - -echo "--- Running tests" -bundle exec rake parallel:spec diff --git a/spec/controllers/admin/manager_invitations_controller_spec.rb b/spec/controllers/admin/manager_invitations_controller_spec.rb index c5e6bf35f83..6a2623220f8 100644 --- a/spec/controllers/admin/manager_invitations_controller_spec.rb +++ b/spec/controllers/admin/manager_invitations_controller_spec.rb @@ -25,13 +25,14 @@ module Admin context "signing up a new user" do before do + create(:mail_method) controller.stub spree_current_user: admin end it "creates a new user, sends an invitation email, and returns the user id" do expect do spree_post :create, {email: 'un.registered@email.com', enterprise_id: enterprise.id} - end.to enqueue_job Delayed::PerformableMethod + end.to send_confirmation_instructions new_user = Spree::User.find_by_email('un.registered@email.com') @@ -45,6 +46,7 @@ module Admin describe "with enterprise permissions" do context "as user with proper enterprise permissions" do before do + create(:mail_method) controller.stub spree_current_user: enterprise_owner end diff --git a/spec/controllers/api/logos_controller_spec.rb b/spec/controllers/api/logos_controller_spec.rb new file mode 100644 index 00000000000..213ac8bc6e1 --- /dev/null +++ b/spec/controllers/api/logos_controller_spec.rb @@ -0,0 +1,90 @@ +require "spec_helper" + +module Api + describe LogosController, type: :controller do + include AuthenticationWorkflow + + let(:admin_user) { create(:admin_user) } + let(:enterprise_owner) { create(:user) } + let(:enterprise) { create(:enterprise, owner: enterprise_owner ) } + let(:enterprise_manager) { create(:user, enterprise_limit: 10, enterprises: [enterprise]) } + let(:other_enterprise_owner) { create(:user) } + let(:other_enterprise) { create(:enterprise, owner: other_enterprise_owner ) } + let(:other_enterprise_manager) { create(:user, enterprise_limit: 10, enterprises: [other_enterprise]) } + + describe "removing logo" do + image_path = File.open(Rails.root.join("app", "assets", "images", "logo-black.png")) + let(:image) { Rack::Test::UploadedFile.new(image_path, "image/png") } + + let(:enterprise) { create(:enterprise, owner: enterprise_owner, logo: image) } + + before do + allow(controller).to receive(:spree_current_user) { current_user } + end + + context "as manager" do + let(:current_user) { enterprise_manager } + + it "removes logo" do + spree_delete :destroy, enterprise_id: enterprise + + expect(response).to be_success + expect(json_response["id"]).to eq enterprise.id + enterprise.reload + expect(enterprise.logo?).to be false + end + + context "when logo does not exist" do + let(:enterprise) { create(:enterprise, owner: enterprise_owner, logo: nil) } + + it "responds with error" do + spree_delete :destroy, enterprise_id: enterprise + + expect(response.status).to eq(409) + expect(json_response["error"]).to eq I18n.t("api.enterprise_logo.destroy_attachment_does_not_exist") + end + end + end + + context "as owner" do + let(:current_user) { enterprise_owner } + + it "allows removal of logo" do + spree_delete :destroy, enterprise_id: enterprise + expect(response).to be_success + end + end + + context "as super admin" do + let(:current_user) { admin_user } + + it "allows removal of logo" do + spree_delete :destroy, enterprise_id: enterprise + expect(response).to be_success + end + end + + context "as manager of other enterprise" do + let(:current_user) { other_enterprise_manager } + + it "does not allow removal of logo" do + spree_delete :destroy, enterprise_id: enterprise + expect(response.status).to eq(401) + enterprise.reload + expect(enterprise.logo?).to be true + end + end + + context "as owner of other enterprise" do + let(:current_user) { other_enterprise_owner } + + it "does not allow removal of logo" do + spree_delete :destroy, enterprise_id: enterprise + expect(response.status).to eq(401) + enterprise.reload + expect(enterprise.logo?).to be true + end + end + end + end +end diff --git a/spec/controllers/api/promo_images_controller_spec.rb b/spec/controllers/api/promo_images_controller_spec.rb new file mode 100644 index 00000000000..cce6d08f5f6 --- /dev/null +++ b/spec/controllers/api/promo_images_controller_spec.rb @@ -0,0 +1,90 @@ +require "spec_helper" + +module Api + describe PromoImagesController, type: :controller do + include AuthenticationWorkflow + + let(:admin_user) { create(:admin_user) } + let(:enterprise_owner) { create(:user) } + let(:enterprise) { create(:enterprise, owner: enterprise_owner ) } + let(:enterprise_manager) { create(:user, enterprise_limit: 10, enterprises: [enterprise]) } + let(:other_enterprise_owner) { create(:user) } + let(:other_enterprise) { create(:enterprise, owner: other_enterprise_owner ) } + let(:other_enterprise_manager) { create(:user, enterprise_limit: 10, enterprises: [other_enterprise]) } + + describe "removing promo image" do + image_path = File.open(Rails.root.join("app", "assets", "images", "logo-black.png")) + let(:image) { Rack::Test::UploadedFile.new(image_path, "image/png") } + + let(:enterprise) { create(:enterprise, owner: enterprise_owner, promo_image: image) } + + before do + allow(controller).to receive(:spree_current_user) { current_user } + end + + context "as manager" do + let(:current_user) { enterprise_manager } + + it "removes promo image" do + spree_delete :destroy, enterprise_id: enterprise + + expect(response).to be_success + expect(json_response["id"]).to eq enterprise.id + enterprise.reload + expect(enterprise.promo_image?).to be false + end + + context "when promo image does not exist" do + let(:enterprise) { create(:enterprise, owner: enterprise_owner, promo_image: nil) } + + it "responds with error" do + spree_delete :destroy, enterprise_id: enterprise + + expect(response.status).to eq(409) + expect(json_response["error"]).to eq I18n.t("api.enterprise_promo_image.destroy_attachment_does_not_exist") + end + end + end + + context "as owner" do + let(:current_user) { enterprise_owner } + + it "allows removal of promo image" do + spree_delete :destroy, enterprise_id: enterprise + expect(response).to be_success + end + end + + context "as super admin" do + let(:current_user) { admin_user } + + it "allows removal of promo image" do + spree_delete :destroy, enterprise_id: enterprise + expect(response).to be_success + end + end + + context "as manager of other enterprise" do + let(:current_user) { other_enterprise_manager } + + it "does not allow removal of promo image" do + spree_delete :destroy, enterprise_id: enterprise + expect(response.status).to eq(401) + enterprise.reload + expect(enterprise.promo_image?).to be true + end + end + + context "as owner of other enterprise" do + let(:current_user) { other_enterprise_owner } + + it "does not allow removal of promo image" do + spree_delete :destroy, enterprise_id: enterprise + expect(response.status).to eq(401) + enterprise.reload + expect(enterprise.promo_image?).to be true + end + end + end + end +end diff --git a/spec/controllers/spree/admin/orders_controller_spec.rb b/spec/controllers/spree/admin/orders_controller_spec.rb index e81da877851..bc6a7389671 100644 --- a/spec/controllers/spree/admin/orders_controller_spec.rb +++ b/spec/controllers/spree/admin/orders_controller_spec.rb @@ -66,26 +66,27 @@ def self.make_simple_data! end it "retrieves a list of orders with appropriate attributes, including line items with appropriate attributes" do - keys = json_response.first.keys.map{ |key| key.to_sym } + keys = json_response['orders'].first.keys.map{ |key| key.to_sym } order_attributes.all?{ |attr| keys.include? attr }.should == true end - it "sorts orders in ascending id order" do - ids = json_response.map{ |order| order['id'] } - ids[0].should < ids[1] - ids[1].should < ids[2] + it "sorts orders in descending id order" do + ids = json_response['orders'].map{ |order| order['id'] } + ids[0].should > ids[1] + ids[1].should > ids[2] end it "formats completed_at to 'yyyy-mm-dd hh:mm'" do - json_response.map{ |order| order['completed_at'] }.all?{ |a| a.match("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") }.should == true + pp json_response + json_response['orders'].map{ |order| order['completed_at'] }.all?{ |a| a == order1.completed_at.strftime('%B %d, %Y') }.should == true end it "returns distributor object with id key" do - json_response.map{ |order| order['distributor'] }.all?{ |d| d.has_key?('id') }.should == true + json_response['orders'].map{ |order| order['distributor'] }.all?{ |d| d.has_key?('id') }.should == true end it "retrieves the order number" do - json_response.map{ |order| order['number'] }.all?{ |number| number.match("^R\\d{5,10}$") }.should == true + json_response['orders'].map{ |order| order['number'] }.all?{ |number| number.match("^R\\d{5,10}$") }.should == true end end @@ -120,7 +121,7 @@ def self.make_simple_data! end it "retrieves a list of orders" do - keys = json_response.first.keys.map{ |key| key.to_sym } + keys = json_response['orders'].first.keys.map{ |key| key.to_sym } order_attributes.all?{ |attr| keys.include? attr }.should == true end end @@ -132,7 +133,7 @@ def self.make_simple_data! end it "retrieves a list of orders" do - keys = json_response.first.keys.map{ |key| key.to_sym } + keys = json_response['orders'].first.keys.map{ |key| key.to_sym } order_attributes.all?{ |attr| keys.include? attr }.should == true end end diff --git a/spec/controllers/spree/admin/products_controller_spec.rb b/spec/controllers/spree/admin/products_controller_spec.rb index d5b1f861b5f..35fdf054c4a 100644 --- a/spec/controllers/spree/admin/products_controller_spec.rb +++ b/spec/controllers/spree/admin/products_controller_spec.rb @@ -1,22 +1,75 @@ require 'spec_helper' describe Spree::Admin::ProductsController, type: :controller do - describe "updating a product we do not have access to" do - let(:s_managed) { create(:enterprise) } - let(:s_unmanaged) { create(:enterprise) } - let(:p) { create(:simple_product, supplier: s_unmanaged, name: 'Peas') } - - before do - login_as_enterprise_user [s_managed] - spree_post :bulk_update, {"products" => [{"id" => p.id, "name" => "Pine nuts"}]} - end + describe 'bulk_update' do + context "updating a product we do not have access to" do + let(:s_managed) { create(:enterprise) } + let(:s_unmanaged) { create(:enterprise) } + let(:product) do + create(:simple_product, supplier: s_unmanaged, name: 'Peas') + end - it "denies access" do - response.should redirect_to spree.unauthorized_url + before do + login_as_enterprise_user [s_managed] + spree_post :bulk_update, { + "products" => [{"id" => product.id, "name" => "Pine nuts"}] + } + end + + it "denies access" do + response.should redirect_to spree.unauthorized_url + end + + it "does not update any product" do + product.reload.name.should_not == "Pine nuts" + end end - it "does not update any product" do - p.reload.name.should_not == "Pine nuts" + context "when changing a product's variant_unit" do + let(:producer) { create(:enterprise) } + let!(:product) do + create( + :simple_product, + supplier: producer, + variant_unit: 'items', + variant_unit_scale: nil, + variant_unit_name: 'bunches', + unit_value: nil, + unit_description: 'some description' + ) + end + + before { login_as_enterprise_user([producer]) } + + it 'fails' do + spree_post :bulk_update, { + "products" => [ + { + "id" => product.id, + "variant_unit" => "weight", + "variant_unit_scale" => 1 + } + ] + } + + expect(response).to have_http_status(400) + end + + it 'does not redirect to bulk_products' do + spree_post :bulk_update, { + "products" => [ + { + "id" => product.id, + "variant_unit" => "weight", + "variant_unit_scale" => 1 + } + ] + } + + expect(response).not_to redirect_to( + '/api/products/bulk_products?page=1;per_page=500;' + ) + end end end diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index c0fde1ae13e..cdef5ad8a1a 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -5,61 +5,133 @@ let(:order) { create(:order) } let(:order_cycle) { create(:simple_order_cycle) } - it "redirects home when no distributor is selected" do - spree_get :edit - expect(response).to redirect_to root_path - end + describe "viewing an order" do + let(:customer) { create(:customer) } + let(:order) { create(:order_with_credit_payment, customer: customer, distributor: customer.enterprise) } - it "redirects to shop when order is empty" do - allow(controller).to receive(:current_distributor).and_return(distributor) - allow(controller).to receive(:current_order_cycle).and_return(order_cycle) - allow(controller).to receive(:current_order).and_return order - allow(order).to receive_message_chain(:line_items, :empty?).and_return true - allow(order).to receive(:insufficient_stock_lines).and_return [] - session[:access_token] = order.token - spree_get :edit - expect(response).to redirect_to shop_path - end + before do + allow(controller).to receive(:spree_current_user) { current_user } + end - it "redirects to the shop when no order cycle is selected" do - allow(controller).to receive(:current_distributor).and_return(distributor) - spree_get :edit - expect(response).to redirect_to shop_path - end + context "after checking out as an anonymous guest" do + let(:customer) { create(:customer, user: nil) } + let(:current_user) { nil } + + it "loads page" do + spree_get :show, id: order.number, token: order.token + expect(response).to be_success + end + + it "stores order token in session as 'access_token'" do + spree_get :show, id: order.number, token: order.token + expect(session[:access_token]).to eq(order.token) + end + end - it "redirects home with message if hub is not ready for checkout" do - allow(VariantOverride).to receive(:indexed).and_return({}) + context "when returning to order page after checking out as an anonymous guest" do + let(:customer) { create(:customer, user: nil) } + let(:current_user) { nil } - order = subject.current_order(true) - allow(distributor).to receive(:ready_for_checkout?) { false } - allow(order).to receive_messages(distributor: distributor, order_cycle: order_cycle) + before do + session[:access_token] = order.token + end + + it "loads page" do + spree_get :show, id: order.number + expect(response).to be_success + end + end + + context "when logged in as the customer" do + let(:current_user) { order.user } + + it "loads page" do + spree_get :show, id: order.number + expect(response).to be_success + end + end + + context "when logged in as another customer" do + let(:current_user) { create(:user) } + + it "redirects to unauthorized" do + spree_get :show, id: order.number + expect(response.status).to eq(401) + end + end - expect(order).to receive(:empty!) - expect(order).to receive(:set_distribution!).with(nil, nil) + context "when neither checked out as an anonymous guest nor logged in" do + let(:current_user) { nil } - spree_get :edit + before do + request.env["PATH_INFO"] = spree.order_path(order) + end - expect(response).to redirect_to root_url - expect(flash[:info]).to eq("The hub you have selected is temporarily closed for orders. Please try again later.") + it "redirects to unauthorized" do + spree_get :show, id: order.number + expect(response).to redirect_to(root_path(anchor: "login?after_login=#{spree.order_path(order)}")) + expect(flash[:error]).to eq("Please log in to view your order.") + end + end end - describe "when an item has insufficient stock" do - let(:order) { subject.current_order(true) } - let(:oc) { create(:simple_order_cycle, distributors: [d], variants: [variant]) } - let(:d) { create(:distributor_enterprise, shipping_methods: [create(:shipping_method)], payment_methods: [create(:payment_method)]) } - let(:variant) { create(:variant, on_demand: false, on_hand: 5) } - let(:line_item) { order.line_items.last } + describe "viewing cart" do + it "redirects home when no distributor is selected" do + spree_get :edit + expect(response).to redirect_to root_path + end - before do - order.set_distribution! d, oc - order.add_variant variant, 5 - variant.update_attributes! on_hand: 3 + it "redirects to shop when order is empty" do + allow(controller).to receive(:current_distributor).and_return(distributor) + allow(controller).to receive(:current_order_cycle).and_return(order_cycle) + allow(controller).to receive(:current_order).and_return order + allow(order).to receive_message_chain(:line_items, :empty?).and_return true + allow(order).to receive(:insufficient_stock_lines).and_return [] + session[:access_token] = order.token + spree_get :edit + expect(response).to redirect_to shop_path end - it "displays a flash message when we view the cart" do + it "redirects to the shop when no order cycle is selected" do + allow(controller).to receive(:current_distributor).and_return(distributor) spree_get :edit - expect(response.status).to eq 200 - expect(flash[:error]).to eq("An item in your cart has become unavailable.") + expect(response).to redirect_to shop_path + end + + it "redirects home with message if hub is not ready for checkout" do + allow(VariantOverride).to receive(:indexed).and_return({}) + + order = subject.current_order(true) + allow(distributor).to receive(:ready_for_checkout?) { false } + allow(order).to receive_messages(distributor: distributor, order_cycle: order_cycle) + + expect(order).to receive(:empty!) + expect(order).to receive(:set_distribution!).with(nil, nil) + + spree_get :edit + + expect(response).to redirect_to root_url + expect(flash[:info]).to eq("The hub you have selected is temporarily closed for orders. Please try again later.") + end + + describe "when an item has insufficient stock" do + let(:order) { subject.current_order(true) } + let(:oc) { create(:simple_order_cycle, distributors: [d], variants: [variant]) } + let(:d) { create(:distributor_enterprise, shipping_methods: [create(:shipping_method)], payment_methods: [create(:payment_method)]) } + let(:variant) { create(:variant, on_demand: false, on_hand: 5) } + let(:line_item) { order.line_items.last } + + before do + order.set_distribution! d, oc + order.add_variant variant, 5 + variant.update_attributes! on_hand: 3 + end + + it "displays a flash message when we view the cart" do + spree_get :edit + expect(response.status).to eq 200 + expect(flash[:error]).to eq("An item in your cart has become unavailable.") + end end end diff --git a/spec/controllers/user_confirmations_controller_spec.rb b/spec/controllers/user_confirmations_controller_spec.rb index 678e7ccd86f..f8f38883c8b 100644 --- a/spec/controllers/user_confirmations_controller_spec.rb +++ b/spec/controllers/user_confirmations_controller_spec.rb @@ -57,6 +57,8 @@ end context "requesting confirmation instructions to be resent" do + before { create(:mail_method) } + it "redirects the user to login" do spree_post :create, { spree_user: { email: unconfirmed_user.email } } expect(response).to redirect_to login_path @@ -66,8 +68,7 @@ it "sends the confirmation email" do expect do spree_post :create, { spree_user: { email: unconfirmed_user.email } } - end.to enqueue_job Delayed::PerformableMethod - expect(Delayed::Job.last.payload_object.method_name).to eq(:send_confirmation_instructions_without_delay) + end.to send_confirmation_instructions end end end diff --git a/spec/controllers/user_registrations_controller_spec.rb b/spec/controllers/user_registrations_controller_spec.rb index 578ff272142..cb9d2787744 100644 --- a/spec/controllers/user_registrations_controller_spec.rb +++ b/spec/controllers/user_registrations_controller_spec.rb @@ -3,24 +3,48 @@ describe UserRegistrationsController, type: :controller do + before(:all) do + create(:mail_method) + end + before do @request.env["devise.mapping"] = Devise.mappings[:spree_user] end describe "via ajax" do render_views - it "returns errors when registration fails" do + + let(:user_params) do + { + email: "test@test.com", + password: "testy123", + password_confirmation: "testy123" + } + end + + it "returns validation errors" do xhr :post, :create, spree_user: {}, :use_route => :spree - response.status.should == 401 + expect(response.status).to eq(401) + json = JSON.parse(response.body) + expect(json).to eq({"email" => ["can't be blank"], "password" => ["can't be blank"]}) + end + + it "returns error when emailing fails" do + allow(Spree::UserMailer).to receive(:confirmation_instructions).and_raise("Some error") + expect(OpenFoodNetwork::ErrorLogger).to receive(:notify) + + xhr :post, :create, spree_user: user_params, use_route: :spree + + expect(response.status).to eq(401) json = JSON.parse(response.body) - json.should == {"email" => ["can't be blank"], "password" => ["can't be blank"]} + expect(json).to eq({"message" => I18n.t('devise.user_registrations.spree_user.unknown_error')}) end it "returns 200 when registration succeeds" do - xhr :post, :create, spree_user: {email: "test@test.com", password: "testy123", password_confirmation: "testy123"}, :use_route => :spree - response.status.should == 200 + xhr :post, :create, spree_user: user_params, use_route: :spree + expect(response.status).to eq(200) json = JSON.parse(response.body) - json.should == {"email" => "test@test.com"} + expect(json).to eq({"email" => "test@test.com"}) expect(controller.spree_current_user).to be_nil end end @@ -28,8 +52,8 @@ context "when registration fails" do it "renders new" do spree_post :create, spree_user: {} - response.status.should == 200 - response.should render_template "spree/user_registrations/new" + expect(response.status).to eq(200) + expect(response).to render_template "spree/user_registrations/new" end end @@ -37,8 +61,8 @@ context "when referer is not '/checkout'" do it "redirects to root" do spree_post :create, spree_user: {email: "test@test.com", password: "testy123", password_confirmation: "testy123"}, :use_route => :spree - response.should redirect_to root_path - assigns[:user].email.should == "test@test.com" + expect(response).to redirect_to root_path + expect(assigns[:user].email).to eq("test@test.com") end end @@ -47,8 +71,8 @@ it "redirects to checkout" do spree_post :create, spree_user: {email: "test@test.com", password: "testy123", password_confirmation: "testy123"}, :use_route => :spree - response.should redirect_to checkout_path - assigns[:user].email.should == "test@test.com" + expect(response).to redirect_to checkout_path + expect(assigns[:user].email).to eq("test@test.com") end end end diff --git a/spec/factories.rb b/spec/factories.rb index 2476a91be78..1addb4f79ab 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -602,6 +602,16 @@ confirmation_sent_at '1970-01-01 00:00:00' confirmed_at '1970-01-01 00:00:01' + before(:create) do |user, evaluator| + if evaluator.confirmation_sent_at + if evaluator.confirmed_at + user.skip_confirmation! + else + user.skip_confirmation_notification! + end + end + end + after(:create) do |user| user.spree_roles.clear # Remove admin role end diff --git a/spec/features/admin/adjustments_spec.rb b/spec/features/admin/adjustments_spec.rb index a051d28b2b4..992ee7c47c5 100644 --- a/spec/features/admin/adjustments_spec.rb +++ b/spec/features/admin/adjustments_spec.rb @@ -3,7 +3,7 @@ feature %q{ As an administrator I want to manage adjustments on orders -} do +}, js: true do include AuthenticationWorkflow include WebHelper @@ -30,7 +30,7 @@ click_link 'New Adjustment' fill_in 'adjustment_amount', with: 110 fill_in 'adjustment_label', with: 'Late fee' - select 'GST', from: 'tax_rate_id' + select2_select 'GST', from: 'tax_rate_id' click_button 'Continue' # Then I should see the adjustment, with the correct tax @@ -51,11 +51,11 @@ page.find('tr', text: 'Shipping').find('a.icon-edit').click # Then I should see the uneditable included tax and our tax rate as the default - page.should have_field :adjustment_included_tax, with: '10.00', disabled: true - page.should have_select :tax_rate_id, selected: 'GST' + expect(page).to have_field :adjustment_included_tax, with: '10.00', disabled: true + expect(page).to have_select2 :tax_rate_id, selected: 'GST' # When I edit the adjustment, removing the tax - select 'Remove tax', from: :tax_rate_id + select2_select 'Remove tax', from: :tax_rate_id click_button 'Continue' # Then the adjustment tax should be cleared @@ -75,11 +75,11 @@ page.find('tr', text: 'Shipping').find('a.icon-edit').click # Then I should see the uneditable included tax and 'Remove tax' as the default tax rate - page.should have_field :adjustment_included_tax, with: '0.00', disabled: true - page.should have_select :tax_rate_id, selected: [] + expect(page).to have_field :adjustment_included_tax, with: '0.00', disabled: true + expect(page).to have_select2 :tax_rate_id, selected: [] # When I edit the adjustment, setting a tax rate - select 'GST', from: :tax_rate_id + select2_select 'GST', from: :tax_rate_id click_button 'Continue' # Then the adjustment tax should be recalculated diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index c2cb9bea604..486d0860f57 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -54,8 +54,8 @@ it "displays a column for order date" do expect(page).to have_selector "th.date", text: "ORDER DATE", :visible => true - expect(page).to have_selector "td.date", text: o1.completed_at.strftime("%F %T"), :visible => true - expect(page).to have_selector "td.date", text: o2.completed_at.strftime("%F %T"), :visible => true + expect(page).to have_selector "td.date", text: o1.completed_at.strftime('%B %d, %Y'), :visible => true + expect(page).to have_selector "td.date", text: o2.completed_at.strftime('%B %d, %Y'), :visible => true end it "displays a column for producer" do diff --git a/spec/features/admin/content_spec.rb b/spec/features/admin/content_spec.rb index 073ae35e3cf..daf4cb516ac 100644 --- a/spec/features/admin/content_spec.rb +++ b/spec/features/admin/content_spec.rb @@ -18,23 +18,35 @@ fill_in 'footer_twitter_url', with: 'http://twitter.com/me' fill_in 'footer_links_md', with: '[markdown link](/)' click_button 'Update' - page.should have_content 'Your content has been successfully updated!' + expect(page).to have_content 'Your content has been successfully updated!' visit root_path # Then social media icons are only shown if they have a value - page.should_not have_selector 'i.ofn-i_044-facebook' - page.should have_selector 'i.ofn-i_041-twitter' + expect(page).not_to have_selector 'i.ofn-i_044-facebook' + expect(page).to have_selector 'i.ofn-i_041-twitter' # And markdown is rendered - page.should have_link 'markdown link' + expect(page).to have_link 'markdown link' end scenario "uploading logos" do attach_file 'logo', "#{Rails.root}/app/assets/images/logo-white.png" click_button 'Update' - page.should have_content 'Your content has been successfully updated!' + expect(page).to have_content 'Your content has been successfully updated!' - ContentConfig.logo.to_s.should include "logo-white" + expect(ContentConfig.logo.to_s).to include "logo-white" + end + + scenario "setting user guide link" do + fill_in 'user_guide_link', with: 'http://www.openfoodnetwork.org/platform/user-guide/' + click_button 'Update' + + expect(page).to have_content 'Your content has been successfully updated!' + + visit spree.admin_path + + expect(page).to have_link('User Guide', href: 'http://www.openfoodnetwork.org/platform/user-guide/') + expect(find_link('User Guide')[:target]).to eq('_blank') end end diff --git a/spec/features/admin/enterprise_roles_spec.rb b/spec/features/admin/enterprise_roles_spec.rb index 56db2cb8896..a0d075741b3 100644 --- a/spec/features/admin/enterprise_roles_spec.rb +++ b/spec/features/admin/enterprise_roles_spec.rb @@ -137,6 +137,7 @@ end it "can invite unregistered users to be managers" do + create(:mail_method) find('a.button.help-modal').click expect(page).to have_css '#invite-manager-modal' diff --git a/spec/features/admin/enterprises/images_spec.rb b/spec/features/admin/enterprises/images_spec.rb new file mode 100644 index 00000000000..92c4bbafaf3 --- /dev/null +++ b/spec/features/admin/enterprises/images_spec.rb @@ -0,0 +1,104 @@ +require "spec_helper" + +feature "Managing enterprise images" do + include AuthenticationWorkflow + include WebHelper + + context "as an Enterprise user", js: true do + let(:enterprise_user) { create_enterprise_user(enterprise_limit: 1) } + let(:distributor) { create(:distributor_enterprise, name: "First Distributor") } + + before do + enterprise_user.enterprise_roles.build(enterprise: distributor).save! + + quick_login_as enterprise_user + visit edit_admin_enterprise_path(distributor) + end + + describe "images for an enterprise", js: true do + def go_to_images + within(".side_menu") do + click_link "Images" + end + end + + before do + go_to_images + end + + scenario "editing logo" do + # Adding image + attach_file "enterprise[logo]", Rails.root.join("app", "assets", "images", "logo-white.png") + click_button "Update" + + expect(page).to have_content("Enterprise \"#{distributor.name}\" has been successfully updated!") + + go_to_images + within ".page-admin-enterprises-form__logo-field-group" do + expect(page).to have_selector(".image-field-group__preview-image") + expect(html).to include("logo-white.png") + end + + # Replacing image + attach_file "enterprise[logo]", Rails.root.join("app", "assets", "images", "logo-black.png") + click_button "Update" + + expect(page).to have_content("Enterprise \"#{distributor.name}\" has been successfully updated!") + + go_to_images + within ".page-admin-enterprises-form__logo-field-group" do + expect(page).to have_selector(".image-field-group__preview-image") + expect(html).to include("logo-black.png") + end + + # Removing image + within ".page-admin-enterprises-form__logo-field-group" do + click_on "Remove Image" + end + + expect(page).to have_content("Logo removed successfully") + + within ".page-admin-enterprises-form__logo-field-group" do + expect(page).to have_no_selector(".image-field-group__preview-image") + end + end + + scenario "editing promo image" do + # Adding image + attach_file "enterprise[promo_image]", Rails.root.join("app", "assets", "images", "logo-white.png") + click_button "Update" + + expect(page).to have_content("Enterprise \"#{distributor.name}\" has been successfully updated!") + + go_to_images + within ".page-admin-enterprises-form__promo-image-field-group" do + expect(page).to have_selector(".image-field-group__preview-image") + expect(html).to include("logo-white.jpg") + end + + # Replacing image + attach_file "enterprise[promo_image]", Rails.root.join("app", "assets", "images", "logo-black.png") + click_button "Update" + + expect(page).to have_content("Enterprise \"#{distributor.name}\" has been successfully updated!") + + go_to_images + within ".page-admin-enterprises-form__promo-image-field-group" do + expect(page).to have_selector(".image-field-group__preview-image") + expect(html).to include("logo-black.jpg") + end + + # Removing image + within ".page-admin-enterprises-form__promo-image-field-group" do + click_on "Remove Image" + end + + expect(page).to have_content("Promo image removed successfully") + + within ".page-admin-enterprises-form__promo-image-field-group" do + expect(page).to have_no_selector(".image-field-group__preview-image") + end + end + end + end +end diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 3f7e7916129..895b0275e6b 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -413,18 +413,6 @@ end end - scenario "editing images for an enterprise" do - visit admin_enterprises_path - within("tbody#e_#{distributor1.id}") { click_link 'Settings' } - - within(".side_menu") do - click_link "Images" - end - - page.should have_content "LOGO" - page.should have_content "PROMO" - end - scenario "managing producer properties" do create(:property, name: "Certified Organic") visit admin_enterprises_path diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index 276167e7361..4a969614508 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -109,7 +109,7 @@ def new_order_with_distribution(distributor, order_cycle) quick_login_as_admin visit '/admin/orders' uncheck 'Only show complete orders' - click_button 'Filter Results' + page.find('a.icon-search').click click_edit @@ -128,7 +128,7 @@ def new_order_with_distribution(distributor, order_cycle) visit '/admin/orders' page.find('td.actions a.icon-edit').click - expect(page).not_to have_select2_option product.name, from: ".variant_autocomplete", dropdown_css: ".select2-search" + expect(page).not_to have_select2 "add_variant_id", with_options: [product.name] end scenario "can't change distributor or order cycle once order has been finalized" do diff --git a/spec/features/admin/payment_method_spec.rb b/spec/features/admin/payment_method_spec.rb index 9ce7f76a837..e53af5e1bf9 100644 --- a/spec/features/admin/payment_method_spec.rb +++ b/spec/features/admin/payment_method_spec.rb @@ -176,7 +176,6 @@ page.should have_selector 'td', text: 'Two', count: 1 end - pending "shows me only payment methods for the enterprise I select" do pm1 pm2 diff --git a/spec/features/admin/payments_spec.rb b/spec/features/admin/payments_spec.rb new file mode 100644 index 00000000000..0a667e98332 --- /dev/null +++ b/spec/features/admin/payments_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature ' + As an admin + I want to manage payments +' do + include AuthenticationWorkflow + + let(:order) { create(:completed_order_with_fees) } + + scenario "visiting the payment form" do + quick_login_as_admin + + visit spree.new_admin_order_payment_path order + + expect(page).to have_content "New Payment" + end + + context "with sensitive payment fee" do + let(:payment_method) { order.distributor.payment_methods.first } + + before do + # This calculator doesn't handle a `nil` order well. + # That has been useful in finding bugs. ;-) + payment_method.calculator = Spree::Calculator::FlatPercentItemTotal.new + payment_method.save! + end + + scenario "visiting the payment form" do + quick_login_as_admin + + visit spree.new_admin_order_payment_path order + + expect(page).to have_content "New Payment" + end + end +end diff --git a/spec/features/admin/product_import_spec.rb b/spec/features/admin/product_import_spec.rb index 332aca0a386..26a9887be10 100644 --- a/spec/features/admin/product_import_spec.rb +++ b/spec/features/admin/product_import_spec.rb @@ -20,7 +20,7 @@ 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!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans', unit_value: '500', description: '', primary_taxon_id: category.id) } let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts', unit_value: '500') } let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '500') } let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500') } @@ -201,6 +201,44 @@ expect(Spree::Product.find_by_name('Beans').on_hand).to eq 0 end + xit "can save a new product and variant of that product at the same time, add variant to existing product" do + csv_data = CSV.generate do |csv| + csv << ["name", "supplier", "category", "on_hand", "price", "units", "unit_type", "display_name"] + csv << ["Potatoes", "User Enterprise", "Vegetables", "5", "3.50", "500", "g", "Small Bag"] + csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "5.50", "2", "kg", "Big Bag"] + csv << ["Beans", "User Enterprise", "Vegetables", "7", "2.50", "250", "g", nil] + end + File.write('/tmp/test.csv', csv_data) + + visit main_app.admin_product_import_path + attach_file 'file', '/tmp/test.csv' + click_button 'Upload' + + import_data + + expect(page).to have_selector '.item-count', text: "3" + expect(page).to_not have_selector '.invalid-count' + expect(page).to have_selector '.create-count', text: "3" + expect(page).to_not have_selector '.update-count' + expect(page).to_not have_selector '.inv-create-count' + expect(page).to_not have_selector '.inv-update-count' + + save_data + + small_bag = Spree::Variant.find_by_display_name('Small Bag') + expect(small_bag.product.name).to eq 'Potatoes' + expect(small_bag.price).to eq 3.50 + expect(small_bag.on_hand).to eq 5 + + big_bag = Spree::Variant.find_by_display_name('Big Bag') + expect(big_bag.product.name).to eq 'Potatoes' + expect(big_bag.price).to eq 5.50 + expect(big_bag.on_hand).to eq 6 + + expect(big_bag.product.id).to eq small_bag.product.id + end + + xit "can import items into inventory" do csv_data = CSV.generate do |csv| csv << ["name", "supplier", "producer", "category", "on_hand", "price", "units"] diff --git a/spec/features/admin/users_spec.rb b/spec/features/admin/users_spec.rb index 9af920577ac..6b2cbc21777 100644 --- a/spec/features/admin/users_spec.rb +++ b/spec/features/admin/users_spec.rb @@ -4,7 +4,10 @@ include AuthenticationWorkflow context "as super-admin" do - before { quick_login_as_admin } + before do + create(:mail_method) + quick_login_as_admin + end describe "creating a user" do it "shows no confirmation message to start with" do @@ -31,12 +34,11 @@ it "displays success" do visit spree.edit_admin_user_path user - # The `a` element doesn't have an href, so we can't use click_link. - find("a", text: "Resend").click - expect(page).to have_text "Resend done" - - # And it's successful. (testing it here for reduced test time) - expect(Delayed::Job.last.payload_object.method_name).to eq :send_confirmation_instructions_without_delay + expect do + # The `a` element doesn't have an href, so we can't use click_link. + find("a", text: "Resend").click + expect(page).to have_text "Resend done" + end.to send_confirmation_instructions end end end diff --git a/spec/features/consumer/account/settings_spec.rb b/spec/features/consumer/account/settings_spec.rb index 38063563754..64cdff0f05c 100644 --- a/spec/features/consumer/account/settings_spec.rb +++ b/spec/features/consumer/account/settings_spec.rb @@ -7,6 +7,7 @@ let(:user) { create(:user, email: 'old@email.com') } before do + create(:mail_method) quick_login_as user end @@ -17,7 +18,12 @@ expect(page).to have_content I18n.t('spree.users.form.account_settings') fill_in 'user_email', with: 'new@email.com' - click_button I18n.t(:update) + expect do + click_button I18n.t(:update) + end.to send_confirmation_instructions + + sent_mail = ActionMailer::Base.deliveries.last + expect(sent_mail.to).to eq ['new@email.com'] expect(find(".alert-box.success").text.strip).to eq "#{I18n.t(:account_updated)} ×" user.reload diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index b1f8b1be1fa..24a03e4d7e4 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -75,6 +75,7 @@ end scenario "Signing up successfully" do + create(:mail_method) fill_in "Email", with: "test@foo.com" fill_in "Choose a password", with: "test12345" fill_in "Confirm password", with: "test12345" @@ -82,9 +83,7 @@ expect do click_signup_button expect(page).to have_content I18n.t('devise.user_registrations.spree_user.signed_up_but_unconfirmed') - end.to enqueue_job Delayed::PerformableMethod - - expect(Delayed::Job.last.payload_object.method_name).to eq(:send_on_create_confirmation_instructions_without_delay) + end.to send_confirmation_instructions end end diff --git a/spec/features/consumer/confirm_invitation_spec.rb b/spec/features/consumer/confirm_invitation_spec.rb index 9fea517638a..c42c2ff8f04 100644 --- a/spec/features/consumer/confirm_invitation_spec.rb +++ b/spec/features/consumer/confirm_invitation_spec.rb @@ -8,6 +8,7 @@ let(:user) { Spree::User.create(email: email, unconfirmed_email: email, password: "secret") } before do + create(:mail_method) user.reset_password_token = Devise.friendly_token user.reset_password_sent_at = Time.now.utc user.save! diff --git a/spec/features/consumer/cookies_spec.rb b/spec/features/consumer/cookies_spec.rb index c2dbe5a078c..d86b89f4ade 100644 --- a/spec/features/consumer/cookies_spec.rb +++ b/spec/features/consumer/cookies_spec.rb @@ -10,7 +10,7 @@ Spree::Config[:cookies_consent_banner_toggle] = original_banner_toggle end - describe "in the homepage" do + context "in the homepage" do before do Spree::Config[:cookies_consent_banner_toggle] = true visit_root_path_and_wait @@ -46,7 +46,7 @@ end end - describe "in product listing page" do + context "in product listing page" do before do Spree::Config[:cookies_consent_banner_toggle] = true end @@ -57,7 +57,7 @@ end end - describe "disabled in the settings" do + context "disabled in the settings" do scenario "it is not showing" do Spree::Config[:cookies_consent_banner_toggle] = false visit root_path @@ -70,36 +70,57 @@ # keeps config unchanged around do |example| - original_config_value = Spree::Config[:cookies_policy_matomo_section] + original_matomo_config = Spree::Config[:cookies_policy_matomo_section] + original_matomo_url_config = Spree::Config[:matomo_url] example.run - Spree::Config[:cookies_policy_matomo_section] = original_config_value + Spree::Config[:cookies_policy_matomo_section] = original_matomo_config + Spree::Config[:matomo_url] = original_matomo_url_config end - scenario "showing session_id cookies description with correct instance domain" do + scenario "shows session_id cookies description with correct instance domain" do visit '/#/policies/cookies' expect(page).to have_content('_session_id') .and have_content('127.0.0.1') end - describe "with Matomo section configured" do - scenario "shows Matomo cookies details" do + context "without Matomo section configured" do + scenario "does not show Matomo cookies details and does not show Matomo optout text" do + Spree::Config[:cookies_policy_matomo_section] = false + visit_cookies_policy_page + expect(page).to have_no_content matomo_description_text + expect(page).to have_no_content matomo_opt_out_iframe + end + end + + context "with Matomo section configured" do + before do Spree::Config[:cookies_policy_matomo_section] = true - visit '/#/policies/cookies' + end + + scenario "shows Matomo cookies details" do + visit_cookies_policy_page expect(page).to have_content matomo_description_text end - end - describe "without Matomo section configured" do - scenario "does not show Matomo cookies details" do - Spree::Config[:cookies_policy_matomo_section] = false - visit '/#/policies/cookies' - expect(page).to have_no_content matomo_description_text + context "with Matomo integration enabled" do + scenario "shows Matomo optout iframe" do + Spree::Config[:matomo_url] = "https://0000.innocraft.cloud/" + visit_cookies_policy_page + expect(page).to have_content matomo_opt_out_iframe + expect(page).to have_selector("iframe") + end end - end - end - def matomo_description_text - I18n.t('legal.cookies_policy.cookie_matomo_basics_desc') + context "with Matomo integration disabled" do + scenario "does not show Matomo iframe" do + Spree::Config[:cookies_policy_matomo_section] = true + Spree::Config[:matomo_url] = "" + visit_cookies_policy_page + expect(page).to have_no_content matomo_opt_out_iframe + expect(page).to have_no_selector("iframe") + end + end + end end def expect_visible_cookies_policy_page @@ -142,4 +163,16 @@ def close_cookies_policy_page_and_wait find("a.close-reveal-modal").click sleep 2 end + + def visit_cookies_policy_page + visit '/#/policies/cookies' + end + + def matomo_description_text + I18n.t('legal.cookies_policy.cookie_matomo_basics_desc') + end + + def matomo_opt_out_iframe + I18n.t('legal.cookies_policy.statistics_cookies_matomo_optout') + end end diff --git a/spec/features/consumer/shopping/orders_spec.rb b/spec/features/consumer/shopping/orders_spec.rb index e8eb2344bb0..8a2a6368db0 100644 --- a/spec/features/consumer/shopping/orders_spec.rb +++ b/spec/features/consumer/shopping/orders_spec.rb @@ -3,6 +3,84 @@ feature "Order Management", js: true do include AuthenticationWorkflow + describe "viewing a completed order" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:customer) { create(:customer, user: user, enterprise: distributor) } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) } + + let!(:bill_address) { create(:address) } + let!(:ship_address) { create(:address) } + let!(:shipping_method) { create(:free_shipping_method, distributors: [distributor]) } + + let!(:order) do + create(:order_with_credit_payment, + customer: customer, + user: user, + distributor: distributor, + order_cycle: order_cycle + ) + end + + before do + # For some reason, both bill_address and ship_address are not set + # automatically. + # + # Also, assigning the shipping_method to a ShippingMethod instance results + # in a SystemStackError. + order.update_attributes!( + bill_address: bill_address, + ship_address: ship_address, + shipping_method_id: shipping_method.id + ) + end + + context "when checking out as an anonymous guest" do + let(:user) { nil } + + it "allows the user to see the details" do + # Cannot load the page without token + visit spree.order_path(order) + expect(page).to_not be_confirmed_order_page + + # Can load the page with token + visit spree.order_path(order, token: order.token) + expect(page).to be_confirmed_order_page + + # Can load the page even without the token, after loading the page with + # token. + visit spree.order_path(order) + expect(page).to be_confirmed_order_page + end + end + + context "when logged in as the customer" do + let(:user) { create(:user) } + + before do + login_as user + end + + it "allows the user to see order details" do + visit spree.order_path(order) + expect(page).to be_confirmed_order_page + end + end + + context "when not logged in" do + let(:user) { create(:user) } + + it "allows the user to see order details after login" do + # Cannot load the page without signing in + visit spree.order_path(order) + expect(page).to_not be_confirmed_order_page + + # Can load the page after signing in + fill_in_and_submit_login_form user + expect(page).to be_confirmed_order_page + end + end + end + describe "editing a completed order" do let(:address) { create(:address) } let(:user) { create(:user, bill_address: address, ship_address: address) } @@ -88,4 +166,8 @@ end end end + + def be_confirmed_order_page + have_content /Order #\w+ Confirmed NOT PAID/ + end end diff --git a/spec/helpers/map_helper_spec.rb b/spec/helpers/map_helper_spec.rb deleted file mode 100644 index 7c9fbfb1413..00000000000 --- a/spec/helpers/map_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the MapHelper. For example: -# -# describe MapHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end -describe MapHelper, type: :helper do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index eed37bf99ea..37194fc2b56 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -8,7 +8,7 @@ //= require lodash.underscore.js //= require angular-flash.min.js //= require shared/ng-tags-input.min.js -//= require shared/mm-foundation-tpls-0.8.0.min.js +//= require shared/mm-foundation-tpls-0.9.0-20180826174721.min.js //= require textAngular-rangy.min.js //= require textAngular-sanitize.min.js //= require textAngular.min.js diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee index 17ef445846b..475de27e295 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee @@ -4,6 +4,8 @@ describe "enterpriseCtrl", -> enterprise = null PaymentMethods = null ShippingMethods = null + Enterprises = null + StatusMessage = null beforeEach -> module('admin.enterprises') @@ -18,9 +20,11 @@ describe "enterpriseCtrl", -> shippingMethods: "shipping methods" receivesNotifications = 99 - inject ($rootScope, $controller) -> + inject ($rootScope, $controller, _Enterprises_, _StatusMessage_) -> scope = $rootScope - ctrl = $controller 'enterpriseCtrl', {$scope: scope, enterprise: enterprise, EnterprisePaymentMethods: PaymentMethods, EnterpriseShippingMethods: ShippingMethods, receivesNotifications: receivesNotifications} + Enterprises = _Enterprises_ + StatusMessage = _StatusMessage_ + ctrl = $controller "enterpriseCtrl", {$scope: scope, enterprise: enterprise, EnterprisePaymentMethods: PaymentMethods, EnterpriseShippingMethods: ShippingMethods, Enterprises: Enterprises, StatusMessage: StatusMessage, receivesNotifications: receivesNotifications} describe "initialisation", -> it "stores enterprise", -> @@ -32,6 +36,72 @@ describe "enterpriseCtrl", -> it "stores shipping methods", -> expect(scope.ShippingMethods).toBe ShippingMethods.shippingMethods + describe "removing logo", -> + deferred = null + + beforeEach inject ($q) -> + spyOn(scope, "$emit") + deferred = $q.defer() + spyOn(window, "confirm").and.returnValue(true) + spyOn(Enterprises, "removeLogo").and.returnValue(deferred.promise) + spyOn(StatusMessage, "display").and.callThrough() + scope.removeLogo() + + describe "when successful", -> + beforeEach inject ($rootScope) -> + deferred.resolve() + $rootScope.$digest() + + it "emits an 'enterprise:updated' event", -> + expect(scope.$emit).toHaveBeenCalledWith("enterprise:updated", scope.Enterprise) + + it "notifies user of success", -> + expect(StatusMessage.display).toHaveBeenCalledWith("success", "Logo removed successfully") + + describe "when unsuccessful", -> + beforeEach inject ($rootScope) -> + deferred.reject({ data: { error: "Logo does not exist" } }) + $rootScope.$digest() + + it "does not emit an 'enterprise:updated' event", -> + expect(scope.$emit).not.toHaveBeenCalled() + + it "notifies user of failure", -> + expect(StatusMessage.display).toHaveBeenCalledWith("failure", "Logo does not exist") + + describe "removing promo image", -> + deferred = null + + beforeEach inject ($q) -> + spyOn(scope, "$emit") + deferred = $q.defer() + spyOn(window, "confirm").and.returnValue(true) + spyOn(Enterprises, "removePromoImage").and.returnValue(deferred.promise) + spyOn(StatusMessage, "display").and.callThrough() + scope.removePromoImage() + + describe "when successful", -> + beforeEach inject ($rootScope) -> + deferred.resolve() + $rootScope.$digest() + + it "emits an 'enterprise:updated' event", -> + expect(scope.$emit).toHaveBeenCalledWith("enterprise:updated", scope.Enterprise) + + it "notifies user of success", -> + expect(StatusMessage.display).toHaveBeenCalledWith("success", "Promo image removed successfully") + + describe "when unsuccessful", -> + beforeEach inject ($rootScope) -> + deferred.reject({ data: { error: "Promo image does not exist" } }) + $rootScope.$digest() + + it "does not emit an 'enterprise:updated' event", -> + expect(scope.$emit).not.toHaveBeenCalled() + + it "notifies user of failure", -> + expect(StatusMessage.display).toHaveBeenCalledWith("failure", "Promo image does not exist") + describe "adding managers", -> u1 = u2 = u3 = null beforeEach -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee index 8c088b17158..47c669e105c 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee @@ -118,3 +118,73 @@ describe "Enterprises service", -> it "resets the specified value according to the pristine record", -> Enterprises.resetAttribute(enterprise, "name") expect(enterprise.name).toEqual "enterprise1" + + describe "#removeLogo", -> + enterprise = null + + describe "success", -> + resolved = false + + beforeEach -> + enterprise = new EnterpriseResource({ id: 15, permalink: "enterprise1", name: "Enterprise 1", logo: {} }) + $httpBackend.expectDELETE("/api/enterprises/enterprise1/logo.json").respond 200, { id: 15, name: "Enterprise 1"} + Enterprises.removeLogo(enterprise).then( -> resolved = true) + $httpBackend.flush() + + it "updates the pristine copy of the enterprise", -> + expect(Enterprises.pristineByID[15]).not.toBeUndefined() + expect(Enterprises.pristineByID[15]["id"]).toEqual(15) + expect(Enterprises.pristineByID[15]["logo"]).toBeUndefined() + + it "resolves the promise", -> + expect(resolved).toBe(true) + + describe "failure", -> + rejected = false + + beforeEach -> + enterprise = new EnterpriseResource( { id: 15, permalink: "enterprise1", name: "Enterprise 1" } ) + $httpBackend.expectDELETE("/api/enterprises/enterprise1/logo.json").respond 409, { error: "obj" } + Enterprises.removeLogo(enterprise).catch( -> rejected = true) + $httpBackend.flush() + + it "does not update the pristine copy of the enterprise", -> + expect(Enterprises.pristineByID[15]).toBeUndefined() + + it "rejects the promise", -> + expect(rejected).toBe(true) + + describe "#removePromoImage", -> + enterprise = null + + describe "success", -> + resolved = false + + beforeEach -> + enterprise = new EnterpriseResource({ id: 15, permalink: "enterprise1", name: "Enterprise 1", promo_image: {} }) + $httpBackend.expectDELETE("/api/enterprises/enterprise1/promo_image.json").respond 200, { id: 15, name: "Enterprise 1"} + Enterprises.removePromoImage(enterprise).then( -> resolved = true) + $httpBackend.flush() + + it "updates the pristine copy of the enterprise", -> + expect(Enterprises.pristineByID[15]).not.toBeUndefined() + expect(Enterprises.pristineByID[15]["id"]).toEqual(15) + expect(Enterprises.pristineByID[15]["promo_image"]).toBeUndefined() + + it "resolves the promise", -> + expect(resolved).toBe(true) + + describe "failure", -> + rejected = false + + beforeEach -> + enterprise = new EnterpriseResource( { id: 15, permalink: "enterprise1", name: "Enterprise 1" } ) + $httpBackend.expectDELETE("/api/enterprises/enterprise1/promo_image.json").respond 409, { error: "obj" } + Enterprises.removePromoImage(enterprise).catch( -> rejected = true) + $httpBackend.flush() + + it "does not update the pristine copy of the enterprise", -> + expect(Enterprises.pristineByID[15]).toBeUndefined() + + it "rejects the promise", -> + expect(rejected).toBe(true); diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index 7e63ba7284e..1b23dea59de 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -37,7 +37,7 @@ describe "LineItemsCtrl", -> order = { id: 9, order_cycle: { id: 4 }, distributor: { id: 5 }, number: "R123456" } lineItem = { id: 7, quantity: 3, order: { id: 9 }, supplier: { id: 1 } } - httpBackend.expectGET("/admin/orders.json?q%5Bcompleted_at_gteq%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bstate_not_eq%5D=canceled").respond [order] + httpBackend.expectGET("/admin/orders.json?q%5Bcompleted_at_gteq%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bstate_not_eq%5D=canceled").respond {orders: [order], pagination: {page: 1, pages: 1, results: 1}} httpBackend.expectGET("/admin/bulk_line_items.json?q%5Border%5D%5Bcompleted_at_gteq%5D=SomeDate&q%5Border%5D%5Bcompleted_at_lt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_not_null%5D=true&q%5Border%5D%5Bstate_not_eq%5D=canceled").respond [lineItem] httpBackend.expectGET("/admin/enterprises/visible.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor] httpBackend.expectGET("/admin/order_cycles.json?ams_prefix=basic&as=distributor&q%5Borders_close_at_gt%5D=SomeDate").respond [orderCycle] @@ -68,7 +68,7 @@ describe "LineItemsCtrl", -> describe "initialisation", -> it "gets suppliers", -> - expect(scope.suppliers).toDeepEqual [supplier ] + expect(scope.suppliers).toDeepEqual [ supplier ] it "gets distributors", -> expect(scope.distributors).toDeepEqual [ distributor ] diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/order_cycles_controller_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/order_cycles_controller_spec.js.coffee index 4c36884c0a4..c66bc43dfab 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/order_cycles_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/order_cycles_controller_spec.js.coffee @@ -72,3 +72,13 @@ describe "OrderCyclesCtrl", -> it "the RequestMonitor will not longer have a state of loading", -> expect(scope.RequestMonitor.loading).toBe false + + describe "filtering order cycles", -> + it "filters by and resets filter variables", -> + scope.query = "test" + scope.scheduleFilter = 1 + scope.involvingFilter = 1 + scope.resetSelectFilters() + expect(scope.query).toBe "" + expect(scope.scheduleFilter).toBe 0 + expect(scope.involvingFilter).toBe 0 \ No newline at end of file diff --git a/spec/javascripts/unit/admin/orders/controllers/order_controller_spec.js.coffee b/spec/javascripts/unit/admin/orders/controllers/order_controller_spec.js.coffee new file mode 100644 index 00000000000..297eac32f65 --- /dev/null +++ b/spec/javascripts/unit/admin/orders/controllers/order_controller_spec.js.coffee @@ -0,0 +1,40 @@ +describe "orderCtrl", -> + ctrl = null + scope = {} + attrs = {} + shops = [] + orderCycles = [ + {id: 10, name: 'Ten', status: 'open', distributors: [{id: 1, name: 'One'}]} + {id: 20, name: 'Twenty', status: 'closed', distributors: [{id: 2, name: 'Two', status: 'closed'}]} + ] + + beforeEach -> + scope = {} + + module 'admin.orders' + inject ($controller) -> + ctrl = $controller 'orderCtrl', {$scope: scope, $attrs: attrs, shops: shops, orderCycles: orderCycles} + + it "initialises name_and_status", -> + expect(scope.orderCycles[0].name_and_status).toEqual "Ten (open)" + expect(scope.orderCycles[1].name_and_status).toEqual "Twenty (closed)" + + describe "finding valid order cycles for a distributor", -> + order_cycle = {id: 10, distributors: [{id: 1, name: 'One'}]} + + it "returns true when the order cycle includes the distributor", -> + scope.distributor_id = '1' + expect(scope.validOrderCycle(order_cycle, 1, [order_cycle])).toBe true + + it "returns false otherwise", -> + scope.distributor_id = '2' + expect(scope.validOrderCycle(order_cycle, 1, [order_cycle])).toBe false + + describe "checking if a distributor has order cycles", -> + it "returns true when it does", -> + distributor = {id: 1} + expect(scope.distributorHasOrderCycles(distributor)).toBe true + + it "returns false otherwise", -> + distributor = {id: 3} + expect(scope.distributorHasOrderCycles(distributor)).toBe false diff --git a/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee b/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee index 4ceeea277bd..4aed5991407 100644 --- a/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee @@ -1,40 +1,37 @@ describe "ordersCtrl", -> ctrl = null - scope = {} - attrs = {} - shops = [] - orderCycles = [ - {id: 10, name: 'Ten', status: 'open', distributors: [{id: 1, name: 'One'}]} - {id: 20, name: 'Twenty', status: 'closed', distributors: [{id: 2, name: 'Two', status: 'closed'}]} + Orders = null + $scope = null + orders = [ + { id: 8, order_cycle: { id: 4 }, distributor: { id: 5 }, number: "R123456" } + { id: 9, order_cycle: { id: 5 }, distributor: { id: 7 }, number: "R213776" } ] + form = { + q: { + created_at_lt: '' + created_at_gt: '' + completed_at_not_null: true + } + } beforeEach -> - scope = {} - - module('admin.orders') - inject ($controller) -> - ctrl = $controller 'ordersCtrl', {$scope: scope, $attrs: attrs, shops: shops, orderCycles: orderCycles} - - it "initialises name_and_status", -> - expect(scope.orderCycles[0].name_and_status).toEqual "Ten (open)" - expect(scope.orderCycles[1].name_and_status).toEqual "Twenty (closed)" - - describe "finding valid order cycles for a distributor", -> - order_cycle = {id: 10, distributors: [{id: 1, name: 'One'}]} - - it "returns true when the order cycle includes the distributor", -> - scope.distributor_id = '1' - expect(scope.validOrderCycle(order_cycle, 1, [order_cycle])).toBe true - - it "returns false otherwise", -> - scope.distributor_id = '2' - expect(scope.validOrderCycle(order_cycle, 1, [order_cycle])).toBe false - - describe "checking if a distributor has order cycles", -> - it "returns true when it does", -> - distributor = {id: 1} - expect(scope.distributorHasOrderCycles(distributor)).toBe true - - it "returns false otherwise", -> - distributor = {id: 3} - expect(scope.distributorHasOrderCycles(distributor)).toBe false + module 'admin.orders' + inject ($controller, $rootScope, RequestMonitor, SortOptions) -> + $scope = $rootScope.$new() + Orders = + index: jasmine.createSpy('index').and.returnValue(orders) + all: orders + ctrl = $controller 'ordersCtrl', { $scope: $scope, RequestMonitor: RequestMonitor, SortOptions: SortOptions, Orders: Orders } + $scope.q = form.q + + describe "initialising the controller", -> + it "fetches orders", -> + $scope.initialise() + expect(Orders.index).toHaveBeenCalled() + expect($scope.orders).toEqual orders + + describe "using pagination", -> + it "changes the page", -> + $scope.changePage(2) + expect($scope.page).toEqual 2 + expect(Orders.index).toHaveBeenCalled() diff --git a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee index 3d41ded40a6..68bc3beb1f9 100644 --- a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee +++ b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee @@ -18,20 +18,20 @@ describe "Orders service", -> result = response = null beforeEach -> - response = [{ id: 5, name: 'Order 1'}] + response = { orders: [{ id: 5, name: 'Order 1'}], pagination: {page: 1, pages: 1, results: 1} } $httpBackend.expectGET('/admin/orders.json').respond 200, response result = Orders.index() $httpBackend.flush() it "stores returned data in @byID, with ids as keys", -> # OrderResource returns instances of Resource rather than raw objects - expect(Orders.byID).toDeepEqual { 5: response[0] } + expect(Orders.byID).toDeepEqual { 5: response.orders[0] } it "stores returned data in @pristineByID, with ids as keys", -> - expect(Orders.pristineByID).toDeepEqual { 5: response[0] } + expect(Orders.pristineByID).toDeepEqual { 5: response.orders[0] } it "returns an array of orders", -> - expect(result).toDeepEqual response + expect(result).toDeepEqual response.orders describe "#save", -> diff --git a/spec/lib/open_food_network/error_logger_spec.rb b/spec/lib/open_food_network/error_logger_spec.rb new file mode 100644 index 00000000000..f1cd326fa28 --- /dev/null +++ b/spec/lib/open_food_network/error_logger_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' +require 'open_food_network/error_logger' + +module OpenFoodNetwork + describe ErrorLogger do + let(:error) { StandardError.new("Test") } + + it "notifies Bugsnag" do + expect(Bugsnag).to receive(:notify).with(error) + + ErrorLogger.notify(error) + end + end +end diff --git a/spec/lib/open_food_network/products_cache_refreshment_spec.rb b/spec/lib/open_food_network/products_cache_refreshment_spec.rb index f65b81346e7..74998928d5f 100644 --- a/spec/lib/open_food_network/products_cache_refreshment_spec.rb +++ b/spec/lib/open_food_network/products_cache_refreshment_spec.rb @@ -1,3 +1,4 @@ +require 'spec_helper' require 'open_food_network/products_cache_refreshment' module OpenFoodNetwork diff --git a/spec/models/account_invoice_spec.rb b/spec/models/account_invoice_spec.rb deleted file mode 100644 index 0473d1d4550..00000000000 --- a/spec/models/account_invoice_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe AccountInvoice do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/distributor_shipping_method_spec.rb b/spec/models/distributor_shipping_method_spec.rb deleted file mode 100644 index bd8d7d2973c..00000000000 --- a/spec/models/distributor_shipping_method_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe DistributorShippingMethod do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/order_updater_spec.rb b/spec/models/order_updater_spec.rb index 054154aadd6..c71da1ffd92 100644 --- a/spec/models/order_updater_spec.rb +++ b/spec/models/order_updater_spec.rb @@ -4,101 +4,117 @@ let(:order) { build(:order) } let(:order_updater) { described_class.new(Spree::OrderUpdater.new(order)) } - context "#updating_payment_state" do - it "is failed if no valid payments" do - allow(order).to receive_message_chain(:payments, :valid, :empty?) { true } + it "is failed if no valid payments" do + order.stub_chain(:payments, :valid, :empty?).and_return(true) - order_updater.update_payment_state - expect(order.payment_state).to eq('failed') + order_updater.update_payment_state + order.payment_state.should == 'failed' + end + + context "payment total is greater than order total" do + it "is credit_owed" do + order.payment_total = 2 + order.total = 1 + + expect { + order_updater.update_payment_state + }.to change { order.payment_state }.to 'credit_owed' end + end - context "payment total is greater than order total" do - before do - order.payment_total = 2 - order.total = 1 - end + context "order total is greater than payment total" do + it "is credit_owed" do + order.payment_total = 1 + order.total = 2 - it "is credit_owed" do - expect { - order_updater.update_payment_state - }.to change { order.payment_state }.to 'credit_owed' - end + expect { + order_updater.update_payment_state + }.to change { order.payment_state }.to 'balance_due' end + end - context "order total is greater than payment total" do - before do - order.payment_total = 1 - order.total = 2 - end + context "order total equals payment total" do + it "is paid" do + order.payment_total = 30 + order.total = 30 - it "is credit_owed" do + expect { + order_updater.update_payment_state + }.to change { order.payment_state }.to 'paid' + end + end + + context "order is canceled" do + before do + order.state = 'canceled' + end + + context "and is still unpaid" do + it "is void" do + order.payment_total = 0 + order.total = 30 expect { order_updater.update_payment_state - }.to change { order.payment_state }.to 'balance_due' + }.to change { order.payment_state }.to 'void' end end - context "order total equals payment total" do - before do + context "and is paid" do + it "is credit_owed" do order.payment_total = 30 order.total = 30 + order.stub_chain(:payments, :valid, :empty?).and_return(false) + order.stub_chain(:payments, :completed, :empty?).and_return(false) + expect { + order_updater.update_payment_state + }.to change { order.payment_state }.to 'credit_owed' end + end - it "is paid" do + context "and payment is refunded" do + it "is void" do + order.payment_total = 0 + order.total = 30 + order.stub_chain(:payments, :valid, :empty?).and_return(false) + order.stub_chain(:payments, :completed, :empty?).and_return(false) expect { order_updater.update_payment_state - }.to change { order.payment_state }.to 'paid' + }.to change { order.payment_state }.to 'void' end end + end - context "order is canceled" do - before { order.state = 'canceled' } + context 'when the set payment_state does not match the last payment_state' do + before { order.payment_state = 'previous_to_paid' } - context "and is still unpaid" do - before do - order.payment_total = 0 - order.total = 30 - end + context 'and the order is being updated' do + before { allow(order).to receive(:persisted?) { true } } - it "is void" do - expect { - order_updater.update_payment_state - }.to change { order.payment_state }.to 'void' - end + it 'creates a new state_change for the order' do + expect { order_updater.update_payment_state } + .to change { order.state_changes.size }.by(1) end + end - context "and is paid" do - before do - order.payment_total = 30 - order.total = 30 - order.stub_chain(:payments, :valid, :empty?).and_return(false) - order.stub_chain(:payments, :completed, :empty?).and_return(false) - end - - it "is credit_owed" do - expect { - order_updater.update_payment_state - }.to change { order.payment_state }.to 'credit_owed' - end - end + context 'and the order is being created' do + before { allow(order).to receive(:persisted?) { false } } - context "and payment is refunded" do - before do - order.payment_total = 0 - order.total = 30 - order.stub_chain(:payments, :valid, :empty?).and_return(false) - order.stub_chain(:payments, :completed, :empty?).and_return(false) - end - - it "is void" do - expect { - order_updater.update_payment_state - }.to change { order.payment_state }.to 'void' - end + it 'creates a new state_change for the order' do + expect { order_updater.update_payment_state } + .not_to change { order.state_changes.size } end end end + context 'when the set payment_state matches the last payment_state' do + before { order.payment_state = 'paid' } + + it 'does not create any state_change' do + expect { order_updater.update_payment_state } + .not_to change { order.state_changes.size } + end + end + context '#before_save_hook' do let(:distributor) { build(:distributor_enterprise) } let(:order) { build(:order, distributor: distributor) } diff --git a/spec/models/product_import/entry_processor_spec.rb b/spec/models/product_import/entry_processor_spec.rb new file mode 100644 index 00000000000..ab854c5ec4b --- /dev/null +++ b/spec/models/product_import/entry_processor_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' + +describe ProductImport::EntryProcessor do + let(:importer) { double(:importer) } + let(:validator) { double(:validator) } + let(:import_settings) { double(:import_settings) } + let(:spreadsheet_data) { double(:spreadsheet_data) } + let(:editable_enterprises) { double(:editable_enterprises) } + let(:import_time) { double(:import_time) } + let(:updated_ids) { double(:updated_ids) } + + let(:entry_processor) do + described_class.new( + importer, + validator, + import_settings, + spreadsheet_data, + editable_enterprises, + import_time, + updated_ids + ) + end + + describe '#reset_absent_items' do + let(:reset_absent) do + instance_double(ProductImport::ResetAbsent, call: true) + end + + before do + allow(ProductImport::ResetAbsent).to receive(:new) { reset_absent } + allow(ProductImport::Settings).to receive(:new) { settings } + end + + context 'when there is no data' do + let(:settings) do + instance_double( + ProductImport::Settings, + data_for_stock_reset?: false, + reset_all_absent?: true + ) + end + + it 'does not call ResetAbsent' do + entry_processor.reset_absent_items + expect(reset_absent).not_to have_received(:call) + end + end + + context 'when reset_all_absent is not set' do + let(:settings) do + instance_double( + ProductImport::Settings, + data_for_stock_reset?: true, + reset_all_absent?: false + ) + end + + it 'does not call ResetAbsent' do + entry_processor.reset_absent_items + expect(reset_absent).not_to have_received(:call) + end + end + + context 'when there is data and reset_all_absent is set' do + let(:settings) do + instance_double( + ProductImport::Settings, + data_for_stock_reset?: true, + reset_all_absent?: true, + updated_ids: [1] + ) + end + + context 'when importing into inventory' do + let(:reset_stock_strategy) do + instance_double(ProductImport::InventoryResetStrategy) + end + + before do + allow(settings).to receive(:importing_into_inventory?) { true } + + allow(ProductImport::InventoryResetStrategy) + .to receive(:new).with([1]) { reset_stock_strategy } + end + + it 'delegates to ResetAbsent passing the appropriate reset_stock_strategy' do + entry_processor.reset_absent_items + + expect(ProductImport::ResetAbsent) + .to have_received(:new) + .with(entry_processor, settings, reset_stock_strategy) + end + end + + context 'when not importing into inventory' do + let(:reset_stock_strategy) do + instance_double(ProductImport::ProductsResetStrategy) + end + + before do + allow(settings).to receive(:importing_into_inventory?) { false } + + allow(ProductImport::ProductsResetStrategy) + .to receive(:new).with([1]) { reset_stock_strategy } + end + + it 'delegates to ResetAbsent passing the appropriate reset_stock_strategy' do + entry_processor.reset_absent_items + + expect(ProductImport::ResetAbsent) + .to have_received(:new) + .with(entry_processor, settings, reset_stock_strategy) + end + end + end + end + + describe '#products_reset_count' do + let(:settings) do + instance_double( + ProductImport::Settings, + data_for_stock_reset?: true, + reset_all_absent?: true, + importing_into_inventory?: false, + updated_ids: [1] + ) + end + + context 'when reset_absent_items was executed' do + let(:reset_absent) do + instance_double(ProductImport::ResetAbsent, call: 2) + end + + before do + allow(ProductImport::Settings).to receive(:new) { settings } + allow(ProductImport::ResetAbsent).to receive(:new) { reset_absent } + end + + it 'returns the number of items affected by the last reset' do + entry_processor.reset_absent_items + expect(entry_processor.products_reset_count).to eq(2) + end + end + + context 'when ResetAbsent was not called' do + it 'returns 0' do + expect(entry_processor.products_reset_count).to eq(0) + end + end + end +end diff --git a/spec/models/product_import/inventory_reset_strategy_spec.rb b/spec/models/product_import/inventory_reset_strategy_spec.rb new file mode 100644 index 00000000000..011fe9f3043 --- /dev/null +++ b/spec/models/product_import/inventory_reset_strategy_spec.rb @@ -0,0 +1,148 @@ +require 'spec_helper' + +describe ProductImport::InventoryResetStrategy do + let(:inventory_reset) { described_class.new(excluded_items_ids) } + + describe '#reset' do + let(:supplier_ids) { enterprise.id } + let(:enterprise) { variant.product.supplier } + let(:variant) { create(:variant) } + + let!(:variant_override) do + create( + :variant_override, + count_on_hand: 10, + hub: enterprise, + variant: variant + ) + end + + context 'when there are excluded_items_ids' do + let(:excluded_items_ids) { [variant_override.id] } + + context 'and supplier_ids is []' do + let(:supplier_ids) { [] } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + inventory_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + let(:supplier_ids) { nil } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + inventory_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is set' do + it 'does not update the count_on_hand of the excluded items' do + inventory_reset.reset(supplier_ids) + expect(variant_override.reload.count_on_hand).to eq(10) + end + + it 'updates the count_on_hand of the non-excluded items' do + non_excluded_variant_override = create( + :variant_override, + count_on_hand: 3, + hub: enterprise, + variant: variant + ) + inventory_reset.reset(supplier_ids) + expect(non_excluded_variant_override.reload.count_on_hand).to eq(0) + end + end + end + + context 'when there are no excluded_items_ids' do + let(:excluded_items_ids) { [] } + + context 'and supplier_ids is []' do + let(:supplier_ids) { [] } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + inventory_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + let(:supplier_ids) { nil } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + inventory_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is set' do + it 'sets all count_on_hand to 0' do + inventory_reset.reset(supplier_ids) + expect(variant_override.reload.count_on_hand).to eq(0) + end + end + end + + context 'when excluded_items_ids is nil' do + let(:excluded_items_ids) { nil } + + context 'and supplier_ids is []' do + let(:supplier_ids) { [] } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + inventory_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + let(:supplier_ids) { nil } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + inventory_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is set' do + it 'sets all count_on_hand to 0' do + inventory_reset.reset(supplier_ids) + expect(variant_override.reload.count_on_hand).to eq(0) + end + end + end + end +end diff --git a/spec/models/product_import/products_reset_strategy_spec.rb b/spec/models/product_import/products_reset_strategy_spec.rb new file mode 100644 index 00000000000..ba5a33c8d1f --- /dev/null +++ b/spec/models/product_import/products_reset_strategy_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' + +describe ProductImport::ProductsResetStrategy do + let(:products_reset) { described_class.new(excluded_items_ids) } + + describe '#reset' do + let(:supplier_ids) { enterprise.id } + let(:enterprise) { variant.product.supplier } + let(:variant) { create(:variant, count_on_hand: 2) } + + context 'when there are excluded_items_ids' do + let(:excluded_items_ids) { [variant.id] } + + context 'and supplier_ids is []' do + let(:supplier_ids) { [] } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + products_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + let(:supplier_ids) { nil } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + products_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is set' do + it 'does not update the count_on_hand of the excluded items' do + products_reset.reset(supplier_ids) + expect(variant.reload.count_on_hand).to eq(2) + end + + it 'updates the count_on_hand of the non-excluded items' do + non_excluded_variant = create( + :variant, + count_on_hand: 3, + product: variant.product + ) + products_reset.reset(supplier_ids) + expect(non_excluded_variant.reload.count_on_hand).to eq(0) + end + end + end + + context 'when there are no excluded_items_ids' do + let(:excluded_items_ids) { [] } + + context 'and supplier_ids is []' do + let(:supplier_ids) { [] } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + products_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + let(:supplier_ids) { nil } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + products_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + it 'sets all count_on_hand to 0' do + products_reset.reset(supplier_ids) + expect(variant.reload.count_on_hand).to eq(0) + end + end + end + + context 'when excluded_items_ids is nil' do + let(:excluded_items_ids) { nil } + + context 'and supplier_ids is []' do + let(:supplier_ids) { [] } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + products_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + let(:supplier_ids) { nil } + let(:relation) do + instance_double(ActiveRecord::Relation, update_all: true) + end + + before { allow(VariantOverride).to receive(:where) { relation } } + + it 'does not update any DB record' do + products_reset.reset(supplier_ids) + expect(relation).not_to have_received(:update_all) + end + end + + context 'and supplier_ids is nil' do + it 'sets all count_on_hand to 0' do + products_reset.reset(supplier_ids) + expect(variant.reload.count_on_hand).to eq(0) + end + end + end + end +end diff --git a/spec/models/product_import/reset_absent_spec.rb b/spec/models/product_import/reset_absent_spec.rb new file mode 100644 index 00000000000..d642c67338d --- /dev/null +++ b/spec/models/product_import/reset_absent_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +module ProductImport + describe ResetAbsent do + let(:entry_processor) { instance_double(EntryProcessor) } + + let(:reset_absent) do + described_class.new(entry_processor, settings, reset_stock_strategy) + end + + describe '#call' do + context 'when there are no enterprises_to_reset' do + let(:settings) { instance_double(Settings, enterprises_to_reset: []) } + let(:reset_stock_strategy) { instance_double(InventoryResetStrategy) } + + before do + allow(reset_stock_strategy).to receive(:reset).with([]) { 0 } + end + + it 'returns 0' do + expect(reset_absent.call).to eq(0) + end + + it 'calls the strategy' do + reset_absent.call + expect(reset_stock_strategy).to have_received(:reset) + end + end + + context 'when there are enterprises_to_reset' do + let(:enterprise) { instance_double(Enterprise, id: 1) } + + let(:settings) do + instance_double( + Settings, + enterprises_to_reset: [enterprise.id.to_s] + ) + end + + let(:reset_stock_strategy) { instance_double(ProductsResetStrategy) } + + before do + allow(entry_processor) + .to receive(:permission_by_id?).with(enterprise.id.to_s) { true } + + allow(reset_stock_strategy) + .to receive(:reset).with([enterprise.id]) { 2 } + end + + it 'returns the number of products reset' do + expect(reset_absent.call).to eq(2) + end + + it 'resets the products of the specified suppliers' do + reset_absent.call + expect(reset_stock_strategy).to have_received(:reset) + end + end + + context 'when the enterprise has no permission' do + let(:enterprise) { instance_double(Enterprise, id: 1) } + + let(:settings) do + instance_double( + Settings, + enterprises_to_reset: [enterprise.id.to_s] + ) + end + + let(:reset_stock_strategy) { instance_double(InventoryResetStrategy) } + + before do + allow(entry_processor) + .to receive(:permission_by_id?).with(enterprise.id.to_s) { false } + + allow(reset_stock_strategy).to receive(:reset).with([nil]) { 0 } + end + + it 'calls the strategy' do + reset_absent.call + expect(reset_stock_strategy).to have_received(:reset) + end + + it 'returns 0' do + expect(reset_absent.call).to eq(0) + end + end + end + end +end diff --git a/spec/models/product_import/settings_spec.rb b/spec/models/product_import/settings_spec.rb new file mode 100644 index 00000000000..a7f823b2d8e --- /dev/null +++ b/spec/models/product_import/settings_spec.rb @@ -0,0 +1,230 @@ +require 'spec_helper' + +describe ProductImport::Settings do + let(:settings) { described_class.new(import_settings) } + + describe '#defaults' do + let(:entry) { instance_double(ProductImport::SpreadsheetEntry) } + let(:import_settings) { {} } + + context 'when there are no settings' do + it 'returns false' do + expect(settings.defaults(entry)).to be_falsey + end + end + + context 'when there are settings' do + let(:entry) do + instance_double(ProductImport::SpreadsheetEntry, supplier_id: 1) + end + let(:import_settings) { { settings: {} } } + + context 'and there is no data for the specified entry' do + it 'returns a falsey' do + expect(settings.defaults(entry)).to be_falsey + end + end + + context 'and there is data for the specified entry' do + context 'and it has no defaults' do + let(:import_settings) do + { settings: { '1' => { 'foo' => 'bar' } } } + end + + it 'returns a falsey' do + expect(settings.defaults(entry)).to be_falsey + end + end + + context 'and it has defaults' do + let(:import_settings) do + { settings: { '1' => { 'defaults' => 'default value' } } } + end + + it 'returns a truthy' do + expect(settings.defaults(entry)).to eq('default value') + end + end + end + end + end + + describe '#settings' do + context 'when settings are specified' do + let(:import_settings) { { settings: { foo: 'bar' } } } + + it 'returns them' do + expect(settings.settings).to eq(foo: 'bar') + end + end + + context 'when settings are not specified' do + let(:import_settings) { {} } + + it 'returns nil' do + expect(settings.settings).to be_nil + end + end + end + + describe '#updated_ids' do + context 'when updated_ids are specified' do + let(:import_settings) { { updated_ids: [2] } } + + it 'returns them' do + expect(settings.updated_ids).to eq([2]) + end + end + + context 'when updated_ids are not specified' do + let(:import_settings) { {} } + + it 'returns nil' do + expect(settings.updated_ids).to be_nil + end + end + end + + describe '#enterprises_to_reset' do + context 'when enterprises_to_reset are specified' do + let(:import_settings) { { enterprises_to_reset: [2] } } + + it 'returns them' do + expect(settings.enterprises_to_reset).to eq([2]) + end + end + + context 'when enterprises_to_reset are not specified' do + let(:import_settings) { {} } + + it 'returns nil' do + expect(settings.enterprises_to_reset).to be_nil + end + end + end + + describe '#importing_into_inventory?' do + context 'when :settings is specified' do + context 'and import_into is not specified' do + let(:import_settings) { { settings: {} } } + + it 'returns false' do + expect(settings.importing_into_inventory?).to eq(false) + end + end + + context 'and import_into is equal to inventories' do + let(:import_settings) do + { settings: { 'import_into' => 'inventories' } } + end + + it 'returns true' do + expect(settings.importing_into_inventory?).to eq(true) + end + end + + context 'and import_into is not equal to inventories' do + let(:import_settings) do + { settings: { 'import_into' => 'other' } } + end + + it 'returns false' do + expect(settings.importing_into_inventory?).to eq(false) + end + end + end + + context 'when :settings is not specified' do + let(:import_settings) { {} } + + it 'returns falsy' do + expect(settings.importing_into_inventory?).to be_falsy + end + end + end + + describe '#reset_all_absent?' do + context 'when :settings is not specified' do + let(:import_settings) { {} } + + it 'raises' do + expect { settings.reset_all_absent? }.to raise_error(NoMethodError) + end + end + + context 'when reset_all_absent is not set' do + let(:import_settings) do + { settings: {} } + end + + it 'returns nil' do + expect(settings.reset_all_absent?).to be_nil + end + end + + context 'when reset_all_absent is set' do + let(:import_settings) do + { settings: { 'reset_all_absent' => true } } + end + + it 'returns true' do + expect(settings.reset_all_absent?).to eq(true) + end + end + end + + describe '#data_for_stock_reset?' do + context 'when there are no settings' do + let(:import_settings) do + { + updated_ids: [], + enterprises_to_reset: [] + } + end + + it 'returns false' do + expect(settings.data_for_stock_reset?).to eq(false) + end + end + + context 'when there are no updated_ids' do + let(:import_settings) do + { + settings: [], + enterprises_to_reset: [] + } + end + + it 'returns false' do + expect(settings.data_for_stock_reset?).to eq(false) + end + end + + context 'when there are no enterprises_to_reset' do + let(:import_settings) do + { + settings: [], + updated_ids: [] + } + end + + it 'returns false' do + expect(settings.data_for_stock_reset?).to eq(false) + end + end + + context 'when there are settings, updated_ids and enterprises_to_reset' do + let(:import_settings) do + { + settings: { 'something' => true }, + updated_ids: [0], + enterprises_to_reset: [0] + } + end + + it 'returns true' do + expect(settings.data_for_stock_reset?).to eq(true) + end + end + end +end diff --git a/spec/models/product_importer_spec.rb b/spec/models/product_importer_spec.rb index 39315b10322..9986967b896 100644 --- a/spec/models/product_importer_spec.rb +++ b/spec/models/product_importer_spec.rb @@ -16,18 +16,27 @@ let!(:category) { create(:taxon, name: 'Vegetables') } let!(:category2) { create(:taxon, name: 'Cake') } + let!(:category3) { create(:taxon, name: 'Meat') } + let!(:category4) { create(:taxon, name: 'Cereal') } 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!(:product) { create(:simple_product, supplier: enterprise2, name: 'Hypothetical Cake', description: nil, primary_taxon_id: category2.id) } 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', unit_value: '500') } - let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '500') } - let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500') } - let!(:product6) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Beetroot', unit_value: '500', on_demand: true, variant_unit_scale: 1, variant_unit: 'weight') } - let!(:product7) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Tomato', unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight') } + let!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans', unit_value: '500', primary_taxon_id: category.id, description: nil) } + let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts', unit_value: '500', primary_taxon_id: category.id) } + let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '500', primary_taxon_id: category.id) } + let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500', primary_taxon_id: category.id) } + let!(:product6) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Beetroot', unit_value: '500', on_demand: true, variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) } + let!(:product7) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Tomato', unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) } + + let!(:product8) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Oats', description: "", unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category4.id) } + let!(:product9) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Oats', description: "", unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category4.id) } + let!(:variant2) { create(:variant, product_id: product8.id, price: '4.50', on_hand: '100', unit_value: '500', display_name: 'Porridge Oats') } + let!(:variant3) { create(:variant, product_id: product8.id, price: '5.50', on_hand: '100', unit_value: '500', display_name: 'Rolled Oats') } + let!(:variant4) { create(:variant, product_id: product9.id, price: '6.50', on_hand: '100', unit_value: '500', display_name: 'Flaked Oats') } + let!(:variant_override) { create(:variant_override, variant_id: product4.variants.first.id, hub: enterprise2, count_on_hand: 42) } let!(:variant_override2) { create(:variant_override, variant_id: product5.variants.first.id, hub: enterprise, count_on_hand: 96) } @@ -196,7 +205,7 @@ end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise2.id.to_s => {'import_into' => 'product_list'}} + settings = {'import_into' => 'product_list'} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end after { File.delete('/tmp/test-m.csv') } @@ -242,7 +251,7 @@ end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'product_list'}} + settings = {'import_into' => 'product_list'} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end after { File.delete('/tmp/test-m.csv') } @@ -272,6 +281,8 @@ expect(big_bag.product.name).to eq 'Potatoes' expect(big_bag.price).to eq 5.50 expect(big_bag.on_hand).to eq 6 + + expect(big_bag.product.id).to eq small_bag.product.id end end @@ -284,7 +295,7 @@ end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise3.id.to_s => {'import_into' => 'product_list'}} + settings = {'import_into' => 'product_list'} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end after { File.delete('/tmp/test-m.csv') } @@ -317,6 +328,142 @@ end end + describe "updating non-updatable fields on existing products" do + before do + csv_data = CSV.generate do |csv| + csv << ["name", "supplier", "category", "on_hand", "price", "units", "unit_type"] + csv << ["Beetroot", "And Another Enterprise", "Meat", "5", "3.50", "500", "g"] + csv << ["Tomato", "And Another Enterprise", "Vegetables", "6", "5.50", "500", "Kg"] + end + File.write('/tmp/test-m.csv', csv_data) + file = File.new('/tmp/test-m.csv') + settings = {'import_into' => 'product_list'} + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) + end + after { File.delete('/tmp/test-m.csv') } + + it "does not allow updating" do + @importer.validate_entries + entries = JSON.parse(@importer.entries_json) + + expect(filter('valid', entries)).to eq 0 + expect(filter('invalid', entries)).to eq 2 + + @importer.all_entries.each do |entry| + expect(entry.errors.messages.values).to include [I18n.t('admin.product_import.model.not_updatable')] + end + end + end + + describe "when more than one product of the same name already exists with multiple variants each" do + before do + csv_data = CSV.generate do |csv| + csv << ["name", "supplier", "category", "description", "on_hand", "price", "units", "unit_type", "display_name"] + csv << ["Oats", "User Enterprise", "Cereal", "", "50", "3.50", "500", "g", "Rolled Oats"] # Update + csv << ["Oats", "User Enterprise", "Cereal", "", "80", "3.75", "500", "g", "Flaked Oats"] # Update + csv << ["Oats", "User Enterprise", "Cereal", "", "60", "5.50", "500", "g", "Magic Oats"] # Add + csv << ["Oats", "User Enterprise", "Cereal", "", "70", "8.50", "500", "g", "French Oats"] # Add + csv << ["Oats", "User Enterprise", "Cereal", "", "70", "8.50", "500", "g", "Scottish Oats"] # Add + end + File.write('/tmp/test-m.csv', csv_data) + file = File.new('/tmp/test-m.csv') + settings = {'import_into' => 'product_list'} + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) + end + after { File.delete('/tmp/test-m.csv') } + + it "validates entries" do + @importer.validate_entries + entries = JSON.parse(@importer.entries_json) + + expect(filter('valid', entries)).to eq 5 + expect(filter('invalid', entries)).to eq 0 + expect(filter('create_product', entries)).to eq 3 + expect(filter('update_product', entries)).to eq 2 + expect(filter('create_inventory', entries)).to eq 0 + expect(filter('update_inventory', entries)).to eq 0 + end + + it "saves and updates" do + @importer.save_entries + + expect(@importer.products_created_count).to eq 3 + expect(@importer.products_updated_count).to eq 2 + expect(@importer.inventory_created_count).to eq 0 + expect(@importer.inventory_updated_count).to eq 0 + expect(@importer.updated_ids.count).to eq 5 + end + end + + describe "when importer processes create and update across multiple stages" do + before do + csv_data = CSV.generate do |csv| + csv << ["name", "supplier", "category", "on_hand", "price", "units", "unit_type", "display_name"] + csv << ["Bag of Oats", "User Enterprise", "Cereal", "60", "5.50", "500", "g", "Magic Oats"] # Add + csv << ["Bag of Oats", "User Enterprise", "Cereal", "70", "8.50", "500", "g", "French Oats"] # Add + csv << ["Bag of Oats", "User Enterprise", "Cereal", "80", "9.50", "500", "g", "Organic Oats"] # Add + csv << ["Bag of Oats", "User Enterprise", "Cereal", "90", "7.50", "500", "g", "Scottish Oats"] # Add + csv << ["Bag of Oats", "User Enterprise", "Cereal", "30", "6.50", "500", "g", "Breakfast Oats"] # Add + end + File.write('/tmp/test-m.csv', csv_data) + @file = File.new('/tmp/test-m.csv') + @settings = {'import_into' => 'product_list'} + end + after { File.delete('/tmp/test-m.csv') } + + it "processes the validation in stages" do + # Using settings of start: 1, end: 2 to simulate import over multiple stages + @importer = ProductImport::ProductImporter.new(@file, admin, start: 1, end: 3, settings: @settings) + + @importer.validate_entries + entries = JSON.parse(@importer.entries_json) + + expect(filter('valid', entries)).to eq 3 + expect(filter('invalid', entries)).to eq 0 + expect(filter('create_product', entries)).to eq 3 + expect(filter('update_product', entries)).to eq 0 + expect(filter('create_inventory', entries)).to eq 0 + expect(filter('update_inventory', entries)).to eq 0 + + @importer = ProductImport::ProductImporter.new(@file, admin, start: 4, end: 6, settings: @settings) + + @importer.validate_entries + entries = JSON.parse(@importer.entries_json) + + expect(filter('valid', entries)).to eq 2 + expect(filter('invalid', entries)).to eq 0 + expect(filter('create_product', entries)).to eq 2 + expect(filter('update_product', entries)).to eq 0 + expect(filter('create_inventory', entries)).to eq 0 + expect(filter('update_inventory', entries)).to eq 0 + end + + it "processes saving in stages" do + @importer = ProductImport::ProductImporter.new(@file, admin, start: 1, end: 3, settings: @settings) + @importer.save_entries + + expect(@importer.products_created_count).to eq 3 + expect(@importer.products_updated_count).to eq 0 + expect(@importer.inventory_created_count).to eq 0 + expect(@importer.inventory_updated_count).to eq 0 + expect(@importer.updated_ids.count).to eq 3 + + @importer = ProductImport::ProductImporter.new(@file, admin, start: 4, end: 6, settings: @settings) + @importer.save_entries + + expect(@importer.products_created_count).to eq 2 + expect(@importer.products_updated_count).to eq 0 + expect(@importer.inventory_created_count).to eq 0 + expect(@importer.inventory_updated_count).to eq 0 + expect(@importer.updated_ids.count).to eq 2 + + products = Spree::Product.find_all_by_name('Bag of Oats') + + expect(products.count).to eq 1 + expect(products.first.variants.count).to eq 5 + end + end + describe "importing items into inventory" do before do csv_data = CSV.generate do |csv| @@ -482,7 +629,7 @@ @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, updated_ids: updated_ids, enterprises_to_reset: [enterprise.id], settings: settings) @importer.reset_absent(updated_ids) - expect(@importer.products_reset_count).to eq 2 + expect(@importer.products_reset_count).to eq 7 expect(Spree::Product.find_by_name('Carrots').on_hand).to eq 5 # Present in file, added expect(Spree::Product.find_by_name('Beans').on_hand).to eq 6 # Present in file, updated diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 78826656186..dcd290e1950 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -297,11 +297,11 @@ module Spree let!(:er_pd) { create(:enterprise_relationship, parent: d_related, child: d1, permissions_list: [:edit_profile]) } it "should be able to edit enterprises it manages" do - should have_ability([:read, :edit, :update, :bulk_update, :resend_confirmation], for: d1) + should have_ability([:read, :edit, :update, :remove_logo, :remove_promo_image, :bulk_update, :resend_confirmation], for: d1) end it "should be able to edit enterprises it has permission to" do - should have_ability([:read, :edit, :update, :bulk_update, :resend_confirmation], for: d_related) + should have_ability([:read, :edit, :update, :remove_logo, :remove_promo_image, :bulk_update, :resend_confirmation], for: d_related) end it "should be able to manage shipping methods, payment methods and enterprise fees for enterprises it manages" do diff --git a/spec/models/spree/order/checkout_spec.rb b/spec/models/spree/order/checkout_spec.rb new file mode 100644 index 00000000000..a78e7bb3a52 --- /dev/null +++ b/spec/models/spree/order/checkout_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Spree::Order do + describe 'event :restart_checkout' do + context 'when the order is not complete' do + let(:order) { create(:order) } + + before { allow(order).to receive(:completed?) { false } } + + it 'does transition to cart state' do + expect(order.state).to eq('cart') + end + end + + context 'when the order is complete' do + let(:order) { create(:order) } + + before { allow(order).to receive(:completed?) { true } } + + it 'raises' do + expect { order.restart_checkout! } + .to raise_error( + StateMachine::InvalidTransition, + /Cannot transition state via :restart_checkout/ + ) + end + end + end +end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 2b0eb81384f..da26ac3918b 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -815,4 +815,46 @@ end end end + + describe '#restart_checkout!' do + let(:order) { build(:order) } + + context 'when the order is complete' do + before { order.completed_at = Time.zone.now } + + it 'raises' do + expect { order.restart_checkout! } + .to raise_error(StateMachine::InvalidTransition) + end + end + + context 'when the is not complete' do + before { order.completed_at = nil } + + it 'transitions to :cart state' do + order.restart_checkout! + expect(order.state).to eq('cart') + end + end + end + + describe '#charge_shipping_and_payment_fees!' do + let(:order) do + build(:order, shipping_method: build(:shipping_method)) + end + + context 'after transitioning to payment' do + before do + order.state = 'delivery' # payment's previous state + + allow(order).to receive(:payment_required?) { true } + allow(order).to receive(:charge_shipping_and_payment_fees!) + end + + it 'calls charge_shipping_and_payment_fees!' do + order.next + expect(order).to have_received(:charge_shipping_and_payment_fees!) + end + end + end end diff --git a/spec/models/spree/product_set_spec.rb b/spec/models/spree/product_set_spec.rb new file mode 100644 index 00000000000..7f03f594f30 --- /dev/null +++ b/spec/models/spree/product_set_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe Spree::ProductSet do + describe '#save' do + context 'when passing :collection_attributes' do + let(:product_set) do + described_class.new(collection_attributes: collection_hash) + end + + context 'when the product does not exist yet' do + let(:collection_hash) do + { + 0 => { + product_id: 11, + name: 'a product', + price: 2.0, + supplier_id: create(:enterprise).id, + primary_taxon_id: create(:taxon).id, + unit_description: 'description', + variant_unit: 'items', + variant_unit_name: 'bunches' + } + } + end + + it 'creates it with the specified attributes' do + product_set.save + + expect(Spree::Product.last.attributes) + .to include('name' => 'a product') + end + end + + context 'when the product does exist' do + context 'when a different varian_unit is passed' do + let!(:product) do + create( + :simple_product, + variant_unit: 'items', + variant_unit_scale: nil, + variant_unit_name: 'bunches', + unit_value: nil, + unit_description: 'some description' + ) + end + + let(:collection_hash) do + { + 0 => { + id: product.id, + variant_unit: 'weight', + variant_unit_scale: 1 + } + } + end + + it 'does not update the product' do + product_set.save + + expect(product.reload.attributes).to include( + 'variant_unit' => 'items' + ) + end + + it 'adds an error' do + product_set.save + expect(product_set.errors.get(:base)) + .to include("Unit value can't be blank") + end + + it 'returns false' do + expect(product_set.save).to eq(false) + end + end + + context 'when :master_attributes is passed' do + let!(:product) { create(:simple_product) } + let(:collection_hash) { { 0 => { id: product.id } } } + let(:master_attributes) { { sku: '123' } } + + before do + collection_hash[0][:master_attributes] = master_attributes + end + + context 'and the variant does exist' do + let!(:variant) { create(:variant, product: product) } + + before { master_attributes[:id] = variant.id } + + it 'updates the attributes of the master variant' do + product_set.save + expect(variant.reload.sku).to eq('123') + end + end + + context 'and the variant does not exist' do + let(:master_attributes) do + attributes_for(:variant).merge(sku: '123') + end + + it 'creates it with the specified attributes' do + product_set.save + expect(Spree::Variant.last.sku).to eq('123') + end + end + end + end + end + end +end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index f90de704e23..16f045b03ca 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -714,4 +714,45 @@ module Spree end end end + + describe "product import" do + describe "finding the most recent import date of the variants" do + let!(:product) { create(:product) } + + let(:reference_time) { Time.zone.now.beginning_of_day } + + before do + product.reload + end + + context "when the variants do not have an import date" do + let!(:variant_a) { create(:variant, product: product, import_date: nil) } + let!(:variant_b) { create(:variant, product: product, import_date: nil) } + + it "returns nil" do + expect(product.import_date).to be_nil + end + end + + context "when some variants have import date and some do not" do + let!(:variant_a) { create(:variant, product: product, import_date: nil) } + let!(:variant_b) { create(:variant, product: product, import_date: reference_time - 1.hour) } + let!(:variant_c) { create(:variant, product: product, import_date: reference_time - 2.hour) } + + it "returns the most recent import date" do + expect(product.import_date).to eq(variant_b.import_date) + end + end + + context "when all variants have import date" do + let!(:variant_a) { create(:variant, product: product, import_date: reference_time - 2.hour) } + let!(:variant_b) { create(:variant, product: product, import_date: reference_time - 1.hour) } + let!(:variant_c) { create(:variant, product: product, import_date: reference_time - 3.hour) } + + it "returns the most recent import date" do + expect(product.import_date).to eq(variant_b.import_date) + end + end + end + end end diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 3cd4da93af4..ae54a9eca0b 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -72,10 +72,14 @@ context "#create" do it "should send a confirmation email" do + create(:mail_method) + expect do - create(:user, confirmed_at: nil) - end.to enqueue_job Delayed::PerformableMethod - expect(Delayed::Job.last.payload_object.method_name).to eq(:send_on_create_confirmation_instructions_without_delay) + create(:user, email: 'new_user@example.com', confirmation_sent_at: nil, confirmed_at: nil) + end.to send_confirmation_instructions + + sent_mail = ActionMailer::Base.deliveries.last + expect(sent_mail.to).to eq ['new_user@example.com'] end context "with the the same email as existing customers" do @@ -96,6 +100,8 @@ context "confirming email" do it "should send a welcome email" do + create(:mail_method) + expect do create(:user, confirmed_at: nil).confirm! end.to enqueue_job ConfirmSignupJob diff --git a/spec/serializers/admin/enterprise_serializer_spec.rb b/spec/serializers/admin/enterprise_serializer_spec.rb index 0898b7d4a0c..5bf40559d03 100644 --- a/spec/serializers/admin/enterprise_serializer_spec.rb +++ b/spec/serializers/admin/enterprise_serializer_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Api::Admin::EnterpriseSerializer do let(:enterprise) { create(:distributor_enterprise) } @@ -6,4 +6,56 @@ serializer = Api::Admin::EnterpriseSerializer.new enterprise serializer.to_json.should match enterprise.name end + + context "for logo" do + let(:enterprise) { create(:distributor_enterprise, logo: image) } + + context "when there is a logo" do + let(:image) do + image_path = File.open(Rails.root.join("app", "assets", "images", "logo-black.png")) + Rack::Test::UploadedFile.new(image_path, "image/png") + end + + it "includes URLs of image versions" do + serializer = Api::Admin::EnterpriseSerializer.new(enterprise) + expect(serializer.as_json[:logo]).to_not be_blank + expect(serializer.as_json[:logo][:medium]).to match(/logo-black.png/) + end + end + + context "when there is no logo" do + let(:image) { nil } + + it "includes URLs of image versions" do + serializer = Api::Admin::EnterpriseSerializer.new(enterprise) + expect(serializer.as_json[:logo]).to be_blank + end + end + end + + context "for promo image" do + let(:enterprise) { create(:distributor_enterprise, promo_image: image) } + + context "when there is a promo image" do + let(:image) do + image_path = File.open(Rails.root.join("app", "assets", "images", "logo-black.png")) + Rack::Test::UploadedFile.new(image_path, "image/png") + end + + it "includes URLs of image versions" do + serializer = Api::Admin::EnterpriseSerializer.new(enterprise) + expect(serializer.as_json[:promo_image]).to_not be_blank + expect(serializer.as_json[:promo_image][:medium]).to match(/logo-black.jpg/) + end + end + + context "when there is no promo image" do + let(:image) { nil } + + it "includes URLs of image versions" do + serializer = Api::Admin::EnterpriseSerializer.new(enterprise) + expect(serializer.as_json[:promo_image]).to be_nil + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fe9a64de970..0a60d4f807f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,6 @@ +require 'simplecov' +SimpleCov.start 'rails' + require 'rubygems' # Require pry when we're not inside Travis-CI diff --git a/spec/support/matchers/email_confirmation_matchers.rb b/spec/support/matchers/email_confirmation_matchers.rb new file mode 100644 index 00000000000..8599f820e92 --- /dev/null +++ b/spec/support/matchers/email_confirmation_matchers.rb @@ -0,0 +1,12 @@ +RSpec::Matchers.define :send_confirmation_instructions do + match do |event_proc| + expect(&event_proc).to change { ActionMailer::Base.deliveries.count }.by 1 + + message = ActionMailer::Base.deliveries.last + expect(message.subject).to eq "Please confirm your OFN account" + end + + def supports_block_expectations? + true + end +end diff --git a/spec/support/request/authentication_workflow.rb b/spec/support/request/authentication_workflow.rb index 5d3181ea4f5..7645db00125 100644 --- a/spec/support/request/authentication_workflow.rb +++ b/spec/support/request/authentication_workflow.rb @@ -54,9 +54,13 @@ def login_to_consumer_section user.spree_roles << user_role visit spree.login_path - fill_in 'email', :with => 'someone@ofn.org' - fill_in 'password', :with => 'passw0rd' - click_button 'Login' + fill_in_and_submit_login_form user + end + + def fill_in_and_submit_login_form(user) + fill_in "email", with: user.email + fill_in "password", with: user.password + click_button "Login" end end diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 5e1712b1318..71e43599f45 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -168,15 +168,6 @@ def multi_select2_select(value, options) select_select2_result(value) end - # Deprecated: Use have_select2 instead (spec/support/matchers/select2_matchers.rb) - def have_select2_option(value, options) - container = options[:dropdown_css] || ".select2-with-searchbox" - page.execute_script %Q{$('#{options[:from]}').select2('open')} - page.execute_script "$('#{container} input.select2-input').val('#{value}').trigger('keyup-change');" - sleep 1 - have_selector "div.select2-result-label", text: value - end - def open_select2(selector) page.execute_script "jQuery('#{selector}').select2('open');" end diff --git a/spec/support/views/rabl_helper.rb b/spec/support/views/rabl_helper.rb deleted file mode 100644 index 295a2ef07dc..00000000000 --- a/spec/support/views/rabl_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RablHelper - # See https://github.com/nesquena/rabl/issues/231 - # Allows us to test RABL views using URL helpers - class FakeContext - include Singleton - include Rails.application.routes.url_helpers - include Sprockets::Helpers::RailsHelper - include Sprockets::Helpers::IsolatedHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::AssetTagHelper - end -end diff --git a/spec/views/json/producers.json.rabl_spec.rb b/spec/views/json/producers.json.rabl_spec.rb deleted file mode 100644 index 2f5dd633f8f..00000000000 --- a/spec/views/json/producers.json.rabl_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe 'json/_producers.json.rabl' do - let!(:producer) { create(:supplier_enterprise) } - let(:render) { Rabl.render([producer], 'json/producers', view_path: 'app/views', scope: RablHelper::FakeContext.instance) } - - pending "renders a list of producers" do - render.should have_json_type(Array).at_path '' - render.should have_json_type(Object).at_path '0' - end - - pending "renders names" do - render.should be_json_eql(producer.name.to_json).at_path '0/name' - end -end