diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 7ff900d243..acd5c4a60a 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -11,6 +11,7 @@ assignees: '' - [ ] Merge pull requests in the [Ready To Go] column - [ ] Include translations: `script/release/update_locales` + - You need the [Transifex Client] installed on your local dev environement to run the script. - [ ] Increment version number: `git push upstream HEAD:refs/tags/vX.Y.Z` - Major: if server changes are required (eg. provision with ofn-install) - Minor: larger change that is irreversible (eg. migration deleting data) @@ -53,3 +54,4 @@ The full process is described at https://github.com/openfoodfoundation/openfoodn [#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2 [Create issue]: https://github.com/openfoodfoundation/openfoodnetwork/issues/new?assignees=&labels=&projects=&template=release.md&title=Release [#core-devs]: https://openfoodnetwork.slack.com/archives/GK2T38QPJ +[Transifex Client]: https://developers.transifex.com/docs/cli diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03d98ef5e8..70000b33ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: workflow_dispatch: push: - branches-ignore: + branches-ignore: - 'dependabot/**' pull_request: @@ -47,7 +47,7 @@ jobs: - name: Setup redis uses: supercharge/redis-github-action@1.4.0 - with: + with: redis-version: 6 - name: Set up Ruby @@ -81,11 +81,20 @@ jobs: # RSpec split test files by test examples feature - it's optional # https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true - KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}" + KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}" run: | git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3) bin/rake knapsack_pro:rspec + - name: Save SimpleCov file + uses: actions/upload-artifact@v4 + with: + name: simplecov-chunk-controllers-${{ matrix.ci_node_index }} + path: coverage/*.* + retention-days: 2 # doesn't need to be long, because it's the combined results that matter + if-no-files-found: ignore + include-hidden-files: true + models: runs-on: ubuntu-22.04 services: @@ -116,7 +125,7 @@ jobs: - name: Setup redis uses: supercharge/redis-github-action@1.4.0 - with: + with: redis-version: 6 - name: Set up Ruby @@ -141,10 +150,19 @@ jobs: # RSpec split test files by test examples feature - it's optional # https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true - KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}" + KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}" run: | bin/rake knapsack_pro:rspec + - name: Save SimpleCov file + uses: actions/upload-artifact@v4 + with: + name: simplecov-chunk-models-${{ matrix.ci_node_index }} + path: coverage/*.* + retention-days: 2 # doesn't need to be long, because it's the combined results that matter + if-no-files-found: ignore + include-hidden-files: true + system_admin: runs-on: ubuntu-22.04 services: @@ -175,7 +193,7 @@ jobs: - name: Setup redis uses: supercharge/redis-github-action@1.4.0 - with: + with: redis-version: 6 - name: Set up Ruby @@ -209,16 +227,25 @@ jobs: # RSpec split test files by test examples feature - it's optional # https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true - KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}" + KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}" run: | bin/rake knapsack_pro:queue:rspec + - name: Save SimpleCov file + uses: actions/upload-artifact@v4 + with: + name: simplecov-chunk-system-admin-${{ matrix.ci_node_index }} + path: coverage/*.* + retention-days: 2 # doesn't need to be long, because it's the combined results that matter + if-no-files-found: ignore + include-hidden-files: true + - name: Archive failed tests screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: failed-tests-screenshots + name: failed-admin_${{ matrix.ci_node_index }}-tests-screenshots path: tmp/capybara/screenshots/*.png retention-days: 7 if-no-files-found: ignore @@ -247,13 +274,13 @@ jobs: ci_node_total: [12] # Indexes for parallel jobs (starting from zero). # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. - ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] steps: - uses: actions/checkout@v3 - name: Setup redis uses: supercharge/redis-github-action@1.4.0 - with: + with: redis-version: 6 - name: Set up Ruby @@ -287,16 +314,25 @@ jobs: # RSpec split test files by test examples feature - it's optional # https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true - KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}" + KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}" run: | bin/rake knapsack_pro:queue:rspec + - name: Save SimpleCov file + uses: actions/upload-artifact@v4 + with: + name: simplecov-chunk-system-consumer-${{ matrix.ci_node_index }} + path: coverage/*.* + retention-days: 2 # doesn't need to be long, because it's the combined results that matter + if-no-files-found: ignore + include-hidden-files: true + - name: Archive failed tests screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: failed-tests-screenshots + name: failed-consumer_${{ matrix.ci_node_index }}-tests-screenshots path: tmp/capybara/screenshots/*.png retention-days: 7 if-no-files-found: ignore @@ -331,7 +367,7 @@ jobs: - name: Setup redis uses: supercharge/redis-github-action@1.4.0 - with: + with: redis-version: 6 - name: Set up Ruby @@ -371,14 +407,14 @@ jobs: run: | bin/rake knapsack_pro:rspec - - name: Archive failed tests screenshots - if: failure() - uses: actions/upload-artifact@v3 + - name: Save SimpleCov file + uses: actions/upload-artifact@v4 with: - name: failed-tests-screenshots - path: tmp/capybara/screenshots/*.png - retention-days: 7 + name: simplecov-chunk-engines-${{ matrix.ci_node_index }} + path: coverage/*.* + retention-days: 2 # doesn't need to be long, because it's the combined results that matter if-no-files-found: ignore + include-hidden-files: true test_the_rest: runs-on: ubuntu-22.04 @@ -410,7 +446,7 @@ jobs: - name: Setup redis uses: supercharge/redis-github-action@1.4.0 - with: + with: redis-version: 6 - name: Set up Ruby @@ -448,6 +484,15 @@ jobs: run: | bin/rake knapsack_pro:rspec + - name: Save SimpleCov file + uses: actions/upload-artifact@v4 + with: + name: simplecov-chunk-the-rest-${{ matrix.ci_node_index }} + path: coverage/*.* + retention-days: 2 # doesn't need to be long, because it's the combined results that matter + if-no-files-found: ignore + include-hidden-files: true + non_knapsack_jest_karma: runs-on: ubuntu-22.04 services: @@ -485,3 +530,39 @@ jobs: - name: Run jest tests run: yarn jest + + collate_simplecov_results: + runs-on: ubuntu-22.04 + needs: + - controllers + - models + - engines + - system_admin + - system_consumer + - test_the_rest + steps: + - uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + + - name: Download individual results from individual runners + uses: actions/download-artifact@v4 + with: + pattern: simplecov-chunk-* + path: tmp/simplecov + merge-multiple: true + + - name: collate results from each of the workers + run: bundle exec rake 'simplecov:collate_results[tmp/simplecov]' + + - name: Upload collated results + uses: actions/upload-artifact@v4 + with: + name: combined-simplecov-report + path: coverage/**/*.* + retention-days: 7 + if-no-files-found: ignore + include-hidden-files: true diff --git a/.simplecov b/.simplecov index 0094b3ddcd..8cf02c6b54 100755 --- a/.simplecov +++ b/.simplecov @@ -14,4 +14,6 @@ SimpleCov.start 'rails' do add_filter '/log' add_filter '/db' add_filter '/lib/tasks/sample_data/' + + formatter SimpleCov::Formatter::SimpleFormatter end diff --git a/Gemfile b/Gemfile index 94f0e62398..fb414a745a 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,6 @@ gem "image_processing" gem 'activemerchant', '>= 1.78.0' gem 'angular-rails-templates', '>= 0.3.0' -gem 'awesome_nested_set' gem 'ransack', '~> 4.1.0' gem 'responders' gem 'webpacker', '~> 5' @@ -105,6 +104,7 @@ gem 'sidekiq-scheduler' gem "cable_ready" gem "stimulus_reflex" +gem "turbo_power" gem "turbo-rails" gem 'combine_pdf' diff --git a/Gemfile.lock b/Gemfile.lock index ea6f3f1c07..f18294b3bb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -161,8 +161,6 @@ GEM activerecord (>= 3.1.0, < 8) ast (2.4.2) attr_required (1.0.2) - awesome_nested_set (3.6.0) - activerecord (>= 4.0.0, < 7.2) aws-eventstream (1.3.0) aws-partitions (1.929.0) aws-sdk-core (3.196.1) @@ -787,6 +785,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.6.2) + turbo-rails (>= 1.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -837,7 +837,7 @@ GEM websocket-extensions (0.1.5) whenever (1.0.0) chronic (>= 0.6.3) - wicked_pdf (2.6.3) + wicked_pdf (2.8.1) activesupport wkhtmltopdf-binary (0.12.6.7) xml-simple (1.1.8) @@ -863,7 +863,6 @@ DEPENDENCIES angularjs-file-upload-rails (~> 2.4.1) angularjs-rails (= 1.8.0) arel-helpers (~> 2.12) - awesome_nested_set aws-sdk-s3 bigdecimal (= 3.0.2) bootsnap @@ -976,6 +975,7 @@ DEPENDENCIES stripe timecop turbo-rails + turbo_power valid_email2 validates_lengths_from_database vcr diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 6135d8cb5e..a35bf0f3cc 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -10,7 +10,6 @@ //= require jquery.ui.all //= require jquery.powertip //= require jquery.cookie -//= require jquery.jstree/jquery.jstree //= require jquery.vAlign //= require angular //= require angular-resource diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index ab5cb51249..831be6d767 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -19,6 +19,8 @@ angular.module('admin.orderCycles') $scope.submit = ($event, destination) -> $event.preventDefault() + $scope.order_cycle?.trigger_action = $($event.target).data('trigger-action'); + $scope.order_cycle?.confirm = $($event.target).data('confirm'); StatusMessage.display 'progress', t('js.saving') OrderCycle.update(destination, $scope.order_cycle_form) 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 490b0341e9..56d7636b7e 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 @@ -1,7 +1,11 @@ angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules, Dereferencer) -> $scope.RequestMonitor = RequestMonitor $scope.columns = Columns.columns - $scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form) + $scope.saveAll = ($event) -> + trigger_action = $($event.target).data('trigger-action') + confirm = $($event.target).data('confirm') + OrderCycles.saveChanges($scope.order_cycles_form, { trigger_action, confirm }) + $scope.ordersCloseAtLimit = -31 # days $scope.resetSelectFilters = -> diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index fc043afaaa..625d6fb499 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -22,6 +22,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $scope.submit = ($event, destination) -> $event.preventDefault() + $scope.order_cycle?.trigger_action = $($event.target).data('trigger-action'); + $scope.order_cycle?.confirm = $($event.target).data('confirm'); StatusMessage.display 'progress', t('js.saving') OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors() 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 88ad9da1b8..58dc0a8264 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 @@ -161,7 +161,11 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $ StatusMessage.display('failure', t('js.order_cycles.create_failure')) update: (destination, form) -> - oc = new OrderCycleResource({order_cycle: this.dataForSubmit()}) + oc = new OrderCycleResource({ + order_cycle: this.dataForSubmit(), + confirm: this.order_cycle.confirm, + trigger_action: this.order_cycle.trigger_action + }) oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) => form.$setPristine() if form if destination? @@ -171,6 +175,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $ , (response) -> if response.data.errors? StatusMessage.display('failure', response.data.errors[0]) + else if (response.data.trigger_action) + StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action) else StatusMessage.display('failure', t('js.order_cycles.update_failure')) diff --git a/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee b/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee index 38c7c5e020..8eb53433f9 100644 --- a/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee @@ -29,13 +29,13 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy deferred.reject(response) deferred.promise - saveChanges: (form) -> + saveChanges: (form, params = {}) -> changed = {} for id, orderCycle of @byID when not @saved(orderCycle) changed[Object.keys(changed).length] = @changesFor(orderCycle) if Object.keys(changed).length > 0 StatusMessage.display('progress', "Saving...") - OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed } }, (data) => + OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed }, confirm: params['confirm'], trigger_action: params['trigger_action'] }, (data) => for orderCycle in data delete orderCycle.coordinator delete orderCycle.producers @@ -47,8 +47,10 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy , (response) => if response.data.errors? StatusMessage.display('failure', response.data.errors[0]) + else if (response.data.trigger_action) + StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action) else - StatusMessage.display('failure', "Oh no! I was unable to save your changes.") + StatusMessage.display('failure', t('js.order_cycles.bulk_save_error')) saved: (order_cycle) -> @diff(order_cycle).length == 0 diff --git a/app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee b/app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee deleted file mode 100644 index dca01a3aa3..0000000000 --- a/app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee +++ /dev/null @@ -1,21 +0,0 @@ -root = exports ? this - -root.taxon_tree_menu = (obj, context) -> - - base_url = Spree.url(Spree.routes.taxonomy_taxons) - admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons) - edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit"); - - create: - label: " " + Spree.translations.add, - action: (obj) -> context.create(obj) - rename: - label: " " + Spree.translations.rename, - action: (obj) -> context.rename(obj) - remove: - label: " " + Spree.translations.remove, - action: (obj) -> context.remove(obj) - edit: - separator_before: true, - label: " " + Spree.translations.edit, - action: (obj) -> window.location = edit_url.toString() diff --git a/app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee b/app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee deleted file mode 100644 index a5b01868b1..0000000000 --- a/app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee +++ /dev/null @@ -1,139 +0,0 @@ -handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) -> - $.jstree.rollback(last_rollback) - $("#ajax_error").show().html("" + server_error + "
" + taxonomy_tree_error) - -handle_move = (e, data) -> - last_rollback = data.rlbk - position = data.rslt.cp - node = data.rslt.o - new_parent = data.rslt.np - - url = new URL(Spree.routes.admin_taxonomy_taxons) - url.pathname = url.pathname + '/' + node.attr("id") - data = { - _method: "put", - "taxon[position]": position, - "taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined - } - $.ajax - type: "POST", - dataType: "json", - url: url.toString(), - data: data, - error: handle_ajax_error - - true - -handle_create = (e, data) -> - last_rollback = data.rlbk - node = data.rslt.obj - name = data.rslt.name - position = data.rslt.position - new_parent = data.rslt.parent - - data = { - "taxon[name]": name, - "taxon[position]": position - "taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined - } - $.ajax - type: "POST", - dataType: "json", - url: base_url.toString(), - data: data, - error: handle_ajax_error, - success: (data,result) -> - node.attr('id', data.id) - -handle_rename = (e, data) -> - last_rollback = data.rlbk - node = data.rslt.obj - name = data.rslt.new_name - # change the name inside the main input field as well if taxon is the root one - document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree" - - url = new URL(base_url) - url.pathname = url.pathname + '/' + node.attr("id") - - $.ajax - type: "POST", - dataType: "json", - url: url.toString(), - data: {_method: "put", "taxon[name]": name }, - error: handle_ajax_error - -handle_delete = (e, data) -> - last_rollback = data.rlbk - node = data.rslt.obj - delete_url = new URL(base_url) - delete_url.pathname = delete_url.pathname + '/' + node.attr("id") - if confirm(Spree.translations.are_you_sure_delete) - $.ajax - type: "POST", - dataType: "json", - url: delete_url.toString(), - data: {_method: "delete"}, - error: handle_ajax_error - else - $.jstree.rollback(last_rollback) - last_rollback = null - -root = exports ? this -root.setup_taxonomy_tree = (taxonomy_id) -> - if taxonomy_id != undefined - # this is defined within admin/taxonomies/edit - root.base_url = Spree.url(Spree.routes.taxonomy_taxons) - - $.ajax - url: base_url.pathname.replace("/taxons", "/jstree"), - success: (taxonomy) -> - last_rollback = null - - conf = - json_data: - data: taxonomy, - ajax: - url: (e) -> - base_url.pathname + '/' + e.attr('id') + '/jstree' - themes: - theme: "apple", - url: "/assets/jquery.jstree/themes/apple/style.css" - strings: - new_node: new_taxon, - loading: Spree.translations.loading + "..." - crrm: - move: - check_move: (m) -> - position = m.cp - node = m.o - new_parent = m.np - - # no parent or cant drag and drop - if !new_parent || node.attr("rel") == "root" - return false - - # can't drop before root - if new_parent.attr("id") == "taxonomy_tree" && position == 0 - return false - - true - contextmenu: - items: (obj) -> - taxon_tree_menu(obj, this) - plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"] - - $("#taxonomy_tree").jstree(conf) - .bind("move_node.jstree", handle_move) - .bind("remove.jstree", handle_delete) - .bind("create.jstree", handle_create) - .bind("rename.jstree", handle_rename) - .bind "loaded.jstree", -> - $(this).jstree("core").toggle_node($('.jstree-icon').first()) - - $("#taxonomy_tree a").on "dblclick", (e) -> - $("#taxonomy_tree").jstree("rename", this) - - # surpress form submit on enter/return - $(document).keypress (e) -> - if e.keyCode == 13 - e.preventDefault() diff --git a/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee b/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee index 89ebd5b90e..5d90b023dc 100644 --- a/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee +++ b/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee @@ -9,6 +9,7 @@ angular.module("admin.utils") $window.onbeforeunload = @onBeforeUnloadHandler $rootScope.$on "$locationChangeStart", @locationChangeStartHandler + $window.onBeforeUnloadHandler = @onBeforeUnloadHandler # Action for regular browser navigation. onBeforeUnloadHandler: ($event) => diff --git a/app/assets/javascripts/admin/utils/services/status_message.js.coffee b/app/assets/javascripts/admin/utils/services/status_message.js.coffee index 432c6ecf70..8355209fc1 100644 --- a/app/assets/javascripts/admin/utils/services/status_message.js.coffee +++ b/app/assets/javascripts/admin/utils/services/status_message.js.coffee @@ -10,7 +10,9 @@ angular.module("admin.utils").factory "StatusMessage", -> statusMessage: text: "" - style: {} + style: {}, + type: null, + actionName: null invalidMessage: "" @@ -23,11 +25,15 @@ angular.module("admin.utils").factory "StatusMessage", -> active: -> @statusMessage.text != '' - display: (type, text) -> + display: (type, text, actionName = null) -> @statusMessage.text = text + @statusMessage.type = type + @statusMessage.actionName = actionName @statusMessage.style = @types[type].style null clear: -> @statusMessage.text = '' @statusMessage.style = {} + @statusMessage.type = null + @statusMessage.actionName = null diff --git a/app/assets/javascripts/templates/active_selector.html.haml b/app/assets/javascripts/templates/active_selector.html.haml index c7ddfc3577..3cfbe46f0f 100644 --- a/app/assets/javascripts/templates/active_selector.html.haml +++ b/app/assets/javascripts/templates/active_selector.html.haml @@ -1,2 +1,3 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml %li{ "ng-class": "{active: selector.active}" } %a{ tooltip: "{{selector.object.value}}", "tooltip-placement": "bottom", "ng-transclude": true, "ng-class": "{active: selector.active, 'has-tip': selector.object.value}" } diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 2a33901459..e0ab0f437e 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,7 +1,7 @@ #save-bar.animate-show{ "ng-show": 'dirty || persist || StatusMessage.active()' } .container .seven.columns.alpha - %h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style' } + %h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'statusMessage' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" } {{ StatusMessage.statusMessage.text || " " }} %h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" } {{ StatusMessage.invalidMessage || " " }} diff --git a/app/assets/javascripts/templates/filter_selector.html.haml b/app/assets/javascripts/templates/filter_selector.html.haml index 81559e4004..c2f110a1f7 100644 --- a/app/assets/javascripts/templates/filter_selector.html.haml +++ b/app/assets/javascripts/templates/filter_selector.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml %ul %active-selector{ "ng-repeat": "selector in allSelectors", "ng-show": "ifDefined(selector.fits, true)" } %span{"ng-bind" => "::selector.object.name"} diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index 374f1bda4e..5a85554108 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml .row .columns.small-12.medium-6.large-6.product-header %h3{"ng-bind" => "::product.name"} diff --git a/app/assets/stylesheets/mail.scss b/app/assets/stylesheets/mail.scss new file mode 100644 index 0000000000..e35654c0d2 --- /dev/null +++ b/app/assets/stylesheets/mail.scss @@ -0,0 +1 @@ +@import './mail/all.scss'; diff --git a/app/assets/stylesheets/mail/all.scss b/app/assets/stylesheets/mail/all.scss new file mode 100644 index 0000000000..fa8445d726 --- /dev/null +++ b/app/assets/stylesheets/mail/all.scss @@ -0,0 +1,3 @@ +@import '../../../webpacker/css/admin/globals/palette.scss'; +@import 'email'; +@import 'payments_list'; diff --git a/app/webpacker/css/mail/email.scss b/app/assets/stylesheets/mail/email.scss similarity index 100% rename from app/webpacker/css/mail/email.scss rename to app/assets/stylesheets/mail/email.scss diff --git a/app/webpacker/css/mail/payments_list.scss b/app/assets/stylesheets/mail/payments_list.scss similarity index 100% rename from app/webpacker/css/mail/payments_list.scss rename to app/assets/stylesheets/mail/payments_list.scss diff --git a/app/components/admin_tooltip_component.rb b/app/components/admin_tooltip_component.rb new file mode 100644 index 0000000000..f790fffd1d --- /dev/null +++ b/app/components/admin_tooltip_component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AdminTooltipComponent < ViewComponent::Base + def initialize(text:, link_text:, placement: "top", link: "", link_class: "") + @text = text + @link_text = link_text + @placement = placement + @link = link + @link_class = link_class + end +end diff --git a/app/components/admin_tooltip_component/admin_tooltip_component.html.haml b/app/components/admin_tooltip_component/admin_tooltip_component.html.haml new file mode 100644 index 0000000000..d4e7685940 --- /dev/null +++ b/app/components/admin_tooltip_component/admin_tooltip_component.html.haml @@ -0,0 +1,8 @@ +%div{"data-controller": "tooltip", "data-tooltip-placement-value": @placement } + %a{"data-tooltip-target": "element", href: @link, class: @link_class} + = @link_text + .tooltip-container + .tooltip{"data-tooltip-target": "tooltip"} + = sanitize @text + .arrow{"data-tooltip-target": "arrow"} + diff --git a/app/components/modal_component.rb b/app/components/modal_component.rb index 1de8852a8c..f4b279278b 100644 --- a/app/components/modal_component.rb +++ b/app/components/modal_component.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true class ModalComponent < ViewComponent::Base - def initialize(id:, close_button: true, instant: false, modal_class: :small) + def initialize(id:, close_button: true, instant: false, modal_class: :small, **options) @id = id @close_button = close_button @instant = instant @modal_class = modal_class + @options = options + @data_controller = "modal #{@options.delete(:'data-controller')}".squish + @data_action = + "keyup@document->modal#closeIfEscapeKey #{@options.delete(:'data-action')}".squish end private diff --git a/app/components/modal_component/modal_component.html.haml b/app/components/modal_component/modal_component.html.haml index 7be73deca4..76a484c528 100644 --- a/app/components/modal_component/modal_component.html.haml +++ b/app/components/modal_component/modal_component.html.haml @@ -1,4 +1,4 @@ -%div{ id: @id, "data-controller": "modal", "data-action": "keyup@document->modal#closeIfEscapeKey", "data-modal-instant-value": @instant } +%div{ id: @id, "data-controller": @data_controller, "data-action": @data_action, "data-modal-instant-value": @instant, **@options } .reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" } .reveal-modal.fade.modal-component{ "data-modal-target": "modal", class: @modal_class } = content diff --git a/app/components/modal_component/modal_component.scss b/app/components/modal_component/modal_component.scss index 72c0e74088..58bd7e34b9 100644 --- a/app/components/modal_component/modal_component.scss +++ b/app/components/modal_component/modal_component.scss @@ -24,6 +24,19 @@ max-width: 100%; height: auto; } + + .flex-column { + display: flex; + flex-direction: column; + } + + .gap-1 { + gap: 1rem; + } + + .gap-2 { + gap: 2rem; + } } /* prevent arrow on selected admin menu item appearing above modal */ diff --git a/app/components/searchable_dropdown_component.rb b/app/components/searchable_dropdown_component.rb index 9a53155085..c9d1549f72 100644 --- a/app/components/searchable_dropdown_component.rb +++ b/app/components/searchable_dropdown_component.rb @@ -11,7 +11,8 @@ class SearchableDropdownComponent < ViewComponent::Base selected_option:, placeholder_value:, include_blank: false, - aria_label: '' + aria_label: '', + other_attrs: {} ) @f = form @name = name @@ -20,11 +21,13 @@ class SearchableDropdownComponent < ViewComponent::Base @placeholder_value = placeholder_value @include_blank = include_blank @aria_label = aria_label + @other_attrs = other_attrs end private - attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank, :aria_label + attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank, + :aria_label, :other_attrs def classes "fullwidth #{remove_search_plugin? ? 'no-input' : ''}" diff --git a/app/components/searchable_dropdown_component/searchable_dropdown_component.html.haml b/app/components/searchable_dropdown_component/searchable_dropdown_component.html.haml index 10043eaa29..14d8969348 100644 --- a/app/components/searchable_dropdown_component/searchable_dropdown_component.html.haml +++ b/app/components/searchable_dropdown_component/searchable_dropdown_component.html.haml @@ -1 +1 @@ -= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label += f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label, **other_attrs diff --git a/app/components/vertical_ellipsis_menu/component_controller.js b/app/components/vertical_ellipsis_menu/component_controller.js index c783adf170..97b13592bd 100644 --- a/app/components/vertical_ellipsis_menu/component_controller.js +++ b/app/components/vertical_ellipsis_menu/component_controller.js @@ -8,6 +8,10 @@ export default class extends Controller { window.addEventListener("click", this.#hideIfClickedOutside); } + disconnect() { + window.removeEventListener("click", this.#hideIfClickedOutside); + } + toggle() { this.contentTarget.classList.toggle("show"); } diff --git a/app/controllers/admin/dfc_product_imports_controller.rb b/app/controllers/admin/dfc_product_imports_controller.rb index c1076ea775..e85170db2f 100644 --- a/app/controllers/admin/dfc_product_imports_controller.rb +++ b/app/controllers/admin/dfc_product_imports_controller.rb @@ -35,13 +35,7 @@ module Admin private def fetch_catalog(url) - if url =~ /food-data-collaboration/ - fdc_json = FdcRequest.new(spree_current_user).call(url) - fdc_message = JSON.parse(fdc_json) - fdc_message["products"] - else - DfcRequest.new(spree_current_user).call(url) - end + DfcRequest.new(spree_current_user).call(url) end # Most of this code is the same as in the DfcProvider::SuppliedProductsController. diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 35333e7af7..12a3a9fe5c 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -2,6 +2,8 @@ module Admin class OrderCyclesController < Admin::ResourceController + class DateTimeChangeError < StandardError; end + include ::OrderCyclesHelper include PaperTrailLogging @@ -62,9 +64,7 @@ module Admin end def update - @order_cycle_form = OrderCycles::FormService.new(@order_cycle, order_cycle_params, - spree_current_user) - + @order_cycle_form = set_order_cycle_form if @order_cycle_form.save update_nil_subscription_line_items_price_estimate(@order_cycle) respond_to do |format| @@ -77,6 +77,9 @@ module Admin elsif request.format.json? render json: { errors: @order_cycle.errors.full_messages }, status: :unprocessable_entity end + rescue DateTimeChangeError + render json: { trigger_action: params[:trigger_action] }, + status: :unprocessable_entity end def bulk_update @@ -90,6 +93,9 @@ module Admin order_cycle = order_cycle_set.collection.find{ |oc| oc.errors.present? } render json: { errors: order_cycle.errors.full_messages }, status: :unprocessable_entity end + rescue DateTimeChangeError + render json: { trigger_action: params[:trigger_action] }, + status: :unprocessable_entity end def bulk_update_nil_subscription_line_items_price_estimate @@ -235,7 +241,7 @@ module Admin else begin yield - rescue ActiveRecord::InvalidForeignKey + rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError redirect_to main_app.admin_order_cycles_url flash[:error] = I18n.t('admin.order_cycles.destroy_errors.orders_present') end @@ -270,7 +276,10 @@ module Admin end def order_cycle_set - @order_cycle_set ||= Sets::OrderCycleSet.new(@order_cycles, order_cycle_bulk_params) + @order_cycle_set ||= Sets::OrderCycleSet.new( + @order_cycles, { **order_cycle_bulk_params, + confirm_datetime_change: params[:confirm], error_class: DateTimeChangeError } + ) end def require_order_cycle_set_params @@ -294,5 +303,14 @@ module Admin collection_attributes: [:id] + PermittedAttributes::OrderCycle.basic_attributes ).to_h.with_indifferent_access end + + def set_order_cycle_form + OrderCycles::FormService.new( + @order_cycle, order_cycle_params.merge( + { confirm_datetime_change: params[:order_cycle][:confirm], + error_class: DateTimeChangeError } + ), spree_current_user + ) + end end end diff --git a/app/controllers/admin/product_preview_controller.rb b/app/controllers/admin/product_preview_controller.rb new file mode 100644 index 0000000000..8712d5497c --- /dev/null +++ b/app/controllers/admin/product_preview_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Admin + class ProductPreviewController < Spree::Admin::BaseController + def show + @product = Spree::Product.find(params[:id]) + authorize! :show, @product + + respond_with do |format| + format.turbo_stream { + render "admin/products_v3/product_preview", status: :ok + } + end + end + + private + + def model_class + Spree::Product + end + end +end diff --git a/app/controllers/admin/products_v3_controller.rb b/app/controllers/admin/products_v3_controller.rb index 327a4a4be9..1b15277373 100644 --- a/app/controllers/admin/products_v3_controller.rb +++ b/app/controllers/admin/products_v3_controller.rb @@ -11,6 +11,8 @@ module Admin def index fetch_products render "index", locals: { producers:, categories:, tax_category_options:, flash: } + + session[:products_return_to_url] = request.url end def bulk_update @@ -38,6 +40,8 @@ module Admin { id: params[:id] } ).find_product + authorize! :delete, @record + @record.destroyed_by = spree_current_user status = :ok @@ -72,6 +76,8 @@ module Admin def clone @product = Spree::Product.find(params[:id]) + authorize! :clone, @product + status = :ok begin diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 699c326f3f..fe8a1874c1 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -6,7 +6,7 @@ module Admin include ReportsActions helper ReportsHelper - before_action :authorize_report, only: [:show] + before_action :authorize_report, only: [:show, :create] # Define model class for Can? permissions def model_class @@ -20,14 +20,17 @@ module Admin end def show - @report = report_class.new(spree_current_user, params, render: render_data?) - @rendering_options = rendering_options # also stores user preferences + @report = report_class.new(spree_current_user, params, render: false) + @rendering_options = rendering_options - if render_data? - render_in_background - else - show_report - end + show_report + end + + def create + @report = report_class.new(spree_current_user, params, render: true) + update_rendering_options + + render_in_background end private @@ -54,31 +57,15 @@ module Admin @variant_serialized = Api::Admin::VariantSerializer.new(variant) end - def render_data? - request.post? - end - def render_in_background - cable_ready[ScopedChannel.for_id(params[:uuid])] - .inner_html( - selector: "#report-go", - html: helpers.button(t(:go), "report__submit-btn", "submit", disabled: true) - ).inner_html( - selector: "#report-table", - html: render_to_string(partial: "admin/reports/loading") - ).scroll_into_view( - selector: "#report-table", - block: "start" - ).broadcast + @blob = ReportBlob.create_for_upload_later!(report_filename) ReportJob.perform_later( report_class:, user: spree_current_user, params:, format: report_format, - filename: report_filename, + blob: @blob, channel: ScopedChannel.for_id(params[:uuid]), ) - - head :no_content end end end diff --git a/app/controllers/api/v0/taxonomies_controller.rb b/app/controllers/api/v0/taxonomies_controller.rb deleted file mode 100644 index bf6077a9e1..0000000000 --- a/app/controllers/api/v0/taxonomies_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Api - module V0 - class TaxonomiesController < Api::V0::BaseController - respond_to :json - - skip_authorization_check only: :jstree - - def jstree - @taxonomy = Spree::Taxonomy.find(params[:id]) - render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer - end - end - end -end diff --git a/app/controllers/api/v0/taxons_controller.rb b/app/controllers/api/v0/taxons_controller.rb index 75e85f5435..f5f8bf6275 100644 --- a/app/controllers/api/v0/taxons_controller.rb +++ b/app/controllers/api/v0/taxons_controller.rb @@ -5,12 +5,10 @@ module Api class TaxonsController < Api::V0::BaseController respond_to :json - skip_authorization_check only: [:index, :show, :jstree] + skip_authorization_check only: [:index, :show] def index - @taxons = if taxonomy - taxonomy.root.children - elsif params[:ids] + @taxons = if params[:ids] Spree::Taxon.where(id: raw_params[:ids].split(",")) else Spree::Taxon.ransack(raw_params[:q]).result @@ -18,23 +16,9 @@ module Api render json: @taxons, each_serializer: Api::TaxonSerializer end - def jstree - @taxon = taxon - render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer - end - def create authorize! :create, Spree::Taxon @taxon = Spree::Taxon.new(taxon_params) - @taxon.taxonomy_id = params[:taxonomy_id] - taxonomy = Spree::Taxonomy.find_by(id: params[:taxonomy_id]) - - if taxonomy.nil? - @taxon.errors.add(:taxonomy_id, I18n.t(:invalid_taxonomy_id, scope: 'spree.api')) - invalid_resource!(@taxon) && return - end - - @taxon.parent_id = taxonomy.root.id unless params.dig(:taxon, :parent_id) if @taxon.save render json: @taxon, serializer: Api::TaxonSerializer, status: :created @@ -60,20 +44,14 @@ module Api private - def taxonomy - return if params[:taxonomy_id].blank? - - @taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id]) - end - def taxon - @taxon ||= taxonomy.taxons.find(params[:id]) + @taxon = Spree::Taxon.find(params[:id]) end def taxon_params return if params[:taxon].blank? - params.require(:taxon).permit([:name, :parent_id, :position]) + params.require(:taxon).permit([:name, :position]) end end end diff --git a/app/controllers/concerns/reports_actions.rb b/app/controllers/concerns/reports_actions.rb index b19d6be9e9..9cf8095aa7 100644 --- a/app/controllers/concerns/reports_actions.rb +++ b/app/controllers/concerns/reports_actions.rb @@ -88,14 +88,10 @@ module ReportsActions display_header_row: false } end - update_rendering_options - @rendering_options end def update_rendering_options - return unless request.post? - - @rendering_options.update( + rendering_options.update( options: { fields_to_show: params[:fields_to_show], display_summary_row: params[:display_summary_row].present?, diff --git a/app/controllers/spree/admin/base_controller.rb b/app/controllers/spree/admin/base_controller.rb index f926612ef0..0b549a437b 100644 --- a/app/controllers/spree/admin/base_controller.rb +++ b/app/controllers/spree/admin/base_controller.rb @@ -19,15 +19,18 @@ module Spree before_action :authorize_admin before_action :set_locale - before_action :warn_invalid_order_cycles, if: :html_request? + before_action :warn_invalid_order_cycles, if: :page_load_request? # Warn the user when they have an active order cycle with hubs that are not ready # for checkout (ie. does not have valid shipping and payment methods). def warn_invalid_order_cycles - return if flash[:notice].present? + return if flash[:notice].present? || session[:displayed_order_cycle_warning] warning = OrderCycles::WarningService.new(spree_current_user).call - flash[:notice] = warning if warning.present? + return if warning.blank? + + flash.now[:notice] = warning + session[:displayed_order_cycle_warning] = true end protected @@ -81,6 +84,12 @@ module Spree private + def page_load_request? + return false if request.format.include?('turbo') + + html_request? + end + def html_request? request.format.html? end diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index 3ae92a38bd..4580a751f0 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -10,6 +10,7 @@ module Spree include OpenFoodNetwork::SpreeApiKeyLoader include OrderCyclesHelper include EnterprisesHelper + helper ::Admin::ProductsHelper before_action :load_data before_action :load_producers, only: [:index, :new] diff --git a/app/controllers/spree/admin/taxonomies_controller.rb b/app/controllers/spree/admin/taxonomies_controller.rb deleted file mode 100644 index 68f65e000e..0000000000 --- a/app/controllers/spree/admin/taxonomies_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Spree - module Admin - class TaxonomiesController < ::Admin::ResourceController - respond_to :json, only: [:get_children] - - def get_children - @taxons = Taxon.find(params[:parent_id]).children - end - - private - - def location_after_save - if @taxonomy.created_at == @taxonomy.updated_at - spree.edit_admin_taxonomy_url(@taxonomy) - else - spree.admin_taxonomies_url - end - end - - def permitted_resource_params - params.require(:taxonomy).permit(:name) - end - end - end -end diff --git a/app/controllers/spree/admin/taxons_controller.rb b/app/controllers/spree/admin/taxons_controller.rb index 9efc97ed79..e6020eb8b5 100644 --- a/app/controllers/spree/admin/taxons_controller.rb +++ b/app/controllers/spree/admin/taxons_controller.rb @@ -2,122 +2,70 @@ module Spree module Admin - class TaxonsController < Spree::Admin::BaseController - respond_to :html, :json, :js + class TaxonsController < ::Admin::ResourceController + before_action :set_taxon, except: %i[create index new] - def edit - @taxonomy = Taxonomy.find(params[:taxonomy_id]) - @taxon = @taxonomy.taxons.find(params[:id]) - @permalink_part = @taxon.permalink.split("/").last + def index + @taxons = Taxon.order(:name) end + def new + @taxon = Taxon.new + end + + def edit; end + def create - @taxonomy = Taxonomy.find(params[:taxonomy_id]) - @taxon = @taxonomy.taxons.build(params[:taxon]) + @taxon = Spree::Taxon.new(taxon_params) if @taxon.save - respond_with(@taxon) do |format| - format.json { render json: @taxon.to_json } - end + flash[:success] = flash_message_for(@taxon, :successfully_created) + redirect_to edit_admin_taxon_path(@taxon.id) else - flash[:error] = Spree.t('errors.messages.could_not_create_taxon') - respond_with(@taxon) do |format| - format.html do - if redirect_to @taxonomy - spree.edit_admin_taxonomy_url(@taxonomy) - else - spree.admin_taxonomies_url - end - end - end + render :new, status: :unprocessable_entity end end def update - @taxonomy = Taxonomy.find(params[:taxonomy_id]) - @taxon = @taxonomy.taxons.find(params[:id]) - parent_id = params[:taxon][:parent_id] - new_position = params[:taxon][:position] - - if parent_id || new_position # taxon is being moved - new_parent = parent_id.nil? ? @taxon.parent : Taxon.find(parent_id.to_i) - new_position = new_position.nil? ? -1 : new_position.to_i - - # Bellow is a very complicated way of finding where in nested set we - # should actually move the taxon to achieve sane results, - # JS is giving us the desired position, which was awesome for previous setup, - # but now it's quite complicated to find where we should put it as we have - # to differenciate between moving to the same branch, up down and into - # first position. - new_siblings = new_parent.children - if new_position <= 0 && new_siblings.empty? - @taxon.move_to_child_of(new_parent) - elsif new_parent.id != @taxon.parent_id - if new_position.zero? - @taxon.move_to_left_of(new_siblings.first) - else - @taxon.move_to_right_of(new_siblings[new_position - 1]) - end - elsif new_position < new_siblings.index(@taxon) - @taxon.move_to_left_of(new_siblings[new_position]) # we move up - else - @taxon.move_to_right_of(new_siblings[new_position - 1]) # we move down - end - # Reset legacy position, if any extensions still rely on it - new_parent.children.reload.each do |t| - t.update_columns( - position: t.position, - updated_at: Time.zone.now - ) - end - - if parent_id - @taxon.reload - @taxon.set_permalink - @taxon.save! - @update_children = true - end - end - - if params.key? "permalink_part" - parent_permalink = @taxon.permalink.split("/")[0...-1].join("/") - parent_permalink += "/" if parent_permalink.present? - params[:taxon][:permalink] = parent_permalink + params[:permalink_part] - end - # check if we need to rename child taxons if parent name or permalink changes - if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink - @update_children = true - end - if @taxon.update(taxon_params) flash[:success] = flash_message_for(@taxon, :successfully_updated) - end - - # rename child taxons - if @update_children - @taxon.descendants.each do |taxon| - taxon.reload - taxon.set_permalink - taxon.save! - end - end - - respond_with(@taxon) do |format| - format.html { redirect_to spree.edit_admin_taxonomy_url(@taxonomy) } - format.json { render json: @taxon.to_json } + redirect_to edit_admin_taxon_path(@taxon.id) + else + render :edit, status: :unprocessable_entity end end def destroy - @taxon = Taxon.find(params[:id]) - @taxon.destroy - respond_with(@taxon) { |format| format.json { render json: '' } } + status = if @taxon.destroy + flash_message = t('.delete_taxon.success') + status = :ok + else + flash_message = t('.delete_taxon.error') + status = :unprocessable_entity + end + + respond_to do |format| + format.html { + flash[:success] = flash_message if status == :ok + flash[:error] = flash_message if status == :unprocessable_entity + redirect_to admin_taxons_path + } + format.turbo_stream { + flash[:success] = flash_message if status == :ok + flash[:error] = flash_message if status == :unprocessable_entity + render :destroy_taxon, status: + } + end end private + def set_taxon + @taxon = Taxon.find(params[:id]) + end + def taxon_params params.require(:taxon).permit( - :name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id, + :name, :position, :icon, :description, :permalink, :meta_description, :meta_keywords, :meta_title, :dfc_id ) end diff --git a/app/helpers/admin/products_helper.rb b/app/helpers/admin/products_helper.rb index 1edd8d0a28..fe00c22002 100644 --- a/app/helpers/admin/products_helper.rb +++ b/app/helpers/admin/products_helper.rb @@ -10,8 +10,11 @@ module Admin end end - def prepare_new_variant(product) - product.variants.build + def prepare_new_variant(product, producer_options) + # e.g producer_options = [['producer name', id]] + product.variants.build do |new_variant| + new_variant.supplier_id = producer_options.first.second if producer_options.one? + end end def unit_value_with_description(variant) @@ -29,5 +32,19 @@ module Admin [precised_unit_value, variant.unit_description].compact_blank.join(" ") end + + def products_return_to_url(url_filters) + if feature?(:admin_style_v3, spree_current_user) + return session[:products_return_to_url] || admin_products_url + end + + "#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}" + end + + # if user hasn't saved any preferences on products page and there's only one producer; + # we need to hide producer column + def hide_producer_column?(producer_options) + spree_current_user.column_preferences.bulk_edit_product.empty? && producer_options.one? + end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2f470529b2..f7787e35b9 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -61,15 +61,6 @@ module ApplicationHelper classes << shopfront_layout end - def pdf_stylesheet_pack_tag(source) - if running_in_development? - options = { media: "all", host: "#{Webpacker.dev_server.host}:#{Webpacker.dev_server.port}" } - stylesheet_pack_tag(source, **options) - else - wicked_pdf_stylesheet_pack_tag(source) - end - end - def cache_with_locale(key = nil, options = {}, &block) cache(cache_key_with_locale(key, I18n.locale), options) do yield(block) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 63a0797992..0b21a7bb91 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -8,11 +8,13 @@ module InjectionHelper include OrderCyclesHelper def inject_enterprises(enterprises = nil) + enterprises ||= default_enterprise_query + inject_json_array( "enterprises", - enterprises || default_enterprise_query, + enterprises, Api::EnterpriseSerializer, - enterprise_injection_data, + enterprise_injection_data(enterprises.map(&:id)), ) end @@ -57,15 +59,16 @@ module InjectionHelper inject_json_array "enterprises", enterprises_and_relatives, Api::EnterpriseSerializer, - enterprise_injection_data + enterprise_injection_data(enterprises_and_relatives.map(&:id)) end def inject_group_enterprises(group) + enterprises = group.enterprises.activated.visible.all inject_json_array( "enterprises", - group.enterprises.activated.visible.all, + enterprises, Api::EnterpriseSerializer, - enterprise_injection_data, + enterprise_injection_data(enterprises.map(&:id)), ) end @@ -73,7 +76,7 @@ module InjectionHelper inject_json "currentHub", current_distributor, Api::EnterpriseSerializer, - enterprise_injection_data + enterprise_injection_data(current_distributor ? [current_distributor.id] : nil) end def inject_current_order @@ -153,7 +156,9 @@ module InjectionHelper Enterprise.activated.includes(address: [:state, :country]).all end - def enterprise_injection_data - @enterprise_injection_data ||= { data: OpenFoodNetwork::EnterpriseInjectionData.new } + def enterprise_injection_data(enterprise_ids) + { + data: OpenFoodNetwork::EnterpriseInjectionData.new(enterprise_ids) + } end end diff --git a/app/helpers/spree/admin/taxons_helper.rb b/app/helpers/spree/admin/taxons_helper.rb deleted file mode 100644 index e8e4d8d918..0000000000 --- a/app/helpers/spree/admin/taxons_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Spree - module Admin - module TaxonsHelper - def taxon_path(taxon) - taxon.ancestors.reverse.collect(&:name).join( " >> ") - end - end - end -end diff --git a/app/jobs/report_job.rb b/app/jobs/report_job.rb index 3c5aecab25..0367ddd259 100644 --- a/app/jobs/report_job.rb +++ b/app/jobs/report_job.rb @@ -9,12 +9,12 @@ class ReportJob < ApplicationJob NOTIFICATION_TIME = 5.seconds - def perform(report_class:, user:, params:, format:, filename:, channel: nil) + def perform(report_class:, user:, params:, format:, blob:, channel: nil) start_time = Time.zone.now report = report_class.new(user, params, render: true) result = report.render_as(format) - blob = ReportBlob.create!(filename, result) + blob.store(result) execution_time = Time.zone.now - start_time diff --git a/app/models/column_preference.rb b/app/models/column_preference.rb index 3e86f36440..04a35ebbe8 100644 --- a/app/models/column_preference.rb +++ b/app/models/column_preference.rb @@ -15,10 +15,11 @@ class ColumnPreference < ApplicationRecord validates :column_name, presence: true, inclusion: { in: proc { |p| valid_columns_for(p.action_name) } } + scope :bulk_edit_product, -> { where(action_name: 'products_v3_index') } def self.for(user, action_name) stored_preferences = where(user_id: user.id, action_name:) - default_preferences = __send__("#{action_name}_columns") + default_preferences = get_default_preferences(action_name, user) 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:) @@ -36,7 +37,7 @@ class ColumnPreference < ApplicationRecord end def self.valid_columns_for(action_name) - __send__("#{action_name}_columns").keys.map(&:to_s) + get_default_preferences(action_name, Spree::User.new).keys.map(&:to_s) end def self.known_actions @@ -52,4 +53,13 @@ class ColumnPreference < ApplicationRecord default_preferences.delete(:schedules) end + + def self.get_default_preferences(action_name, user) + case action_name + when 'products_v3_index' + products_v3_index_columns(user) + else + __send__("#{action_name}_columns") + end + end end diff --git a/app/models/concerns/stock_settings_override_validation.rb b/app/models/concerns/stock_settings_override_validation.rb index 7040cb50e0..ecc3f2ec3b 100644 --- a/app/models/concerns/stock_settings_override_validation.rb +++ b/app/models/concerns/stock_settings_override_validation.rb @@ -6,14 +6,14 @@ # `count_on_hand` can either be: nil or a number # # This means that a variant override can be in six different stock states -# but only three of them are valid. +# but only four of them are valid. # # | on_demand | count_on_hand | stock_overridden? | use_producer_stock_settings? | valid? | # |-----------|---------------|-------------------|------------------------------|--------| -# | 1 | nil | false | false | true | +# | 1 | nil | true | false | true | # | 0 | x | true | false | true | # | nil | nil | false | true | true | -# | 1 | x | ? | ? | false | +# | 1 | x | true | false | true | # | 0 | nil | ? | ? | false | # | nil | x | ? | ? | false | # @@ -27,7 +27,6 @@ module StockSettingsOverrideValidation def require_compatible_on_demand_and_count_on_hand disallow_count_on_hand_if_using_producer_stock_settings - disallow_count_on_hand_if_on_demand require_count_on_hand_if_limited_stock end @@ -39,14 +38,6 @@ module StockSettingsOverrideValidation errors.add(:count_on_hand, error_message) end - def disallow_count_on_hand_if_on_demand - return unless on_demand? && count_on_hand.present? - - error_message = I18n.t("count_on_hand.on_demand_but_count_on_hand_set", - scope: i18n_scope_for_stock_settings_override_validation_error) - errors.add(:count_on_hand, error_message) - end - def require_count_on_hand_if_limited_stock return unless on_demand == false && count_on_hand.blank? diff --git a/app/models/concerns/variant_stock.rb b/app/models/concerns/variant_stock.rb index f39ab38dc9..2e5023d46c 100644 --- a/app/models/concerns/variant_stock.rb +++ b/app/models/concerns/variant_stock.rb @@ -96,7 +96,7 @@ module VariantStock # Here we depend only on variant.total_on_hand and variant.on_demand. # This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand. def fill_status(quantity) - on_hand = if total_on_hand >= quantity || on_demand + on_hand = if total_on_hand.to_i >= quantity || on_demand quantity else [0, total_on_hand].max @@ -112,8 +112,7 @@ module VariantStock # # This enables us to override this behaviour for variant overrides def move(quantity, originator = nil) - # Don't change variant stock if variant is on_demand or has been deleted - return if on_demand || deleted_at + return if deleted_at raise_error_if_no_stock_item_available diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 16ecf14af8..dfee85fef6 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -1,7 +1,5 @@ # frozen_string_literal: false -require "mini_magick" - class Enterprise < ApplicationRecord SELLS = %w(unspecified none own any).freeze ENTERPRISE_SEARCH_RADIUS = 100 @@ -249,12 +247,6 @@ class Enterprise < ApplicationRecord count(distinct: true) end - # Remove any unsupported HTML. - def long_description - HtmlSanitizer.sanitize_and_enforce_link_target_blank(super) - end - - # Remove any unsupported HTML. def long_description=(html) super(HtmlSanitizer.sanitize_and_enforce_link_target_blank(html)) end @@ -488,7 +480,7 @@ class Enterprise < ApplicationRecord return unless image.variable? image_variant_url_for(image.variant(name)) - rescue ActiveStorage::Error, MiniMagick::Error, ActionView::Template::Error => e + rescue StandardError => e Bugsnag.notify "Enterprise ##{id} #{image.try(:name)} error: #{e.message}" Rails.logger.error(e.message) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 060f75e4f0..f87e850fdd 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -24,6 +24,7 @@ class OrderCycle < ApplicationRecord where incoming: false }, class_name: "Exchange", dependent: :destroy + has_many :orders, class_name: 'Spree::Order', dependent: :restrict_with_exception has_many :suppliers, -> { distinct }, source: :sender, through: :cached_incoming_exchanges has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges has_many :order_cycle_schedules, dependent: :destroy @@ -147,17 +148,20 @@ class OrderCycle < ApplicationRecord # Find the earliest closing times for each distributor in an active order cycle, and return # them in the format {distributor_id => closing_time, ...} - def self.earliest_closing_times - Hash[ - Exchange. - outgoing. - joins(:order_cycle). - merge(OrderCycle.active). - group('exchanges.receiver_id'). - select("exchanges.receiver_id AS receiver_id, - MIN(order_cycles.orders_close_at) AS earliest_close_at"). - map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] } - ] + # + # Optionally, specify some distributor_ids as a parameter to scope the results + def self.earliest_closing_times(distributor_ids = nil) + cycles = Exchange. + outgoing. + joins(:order_cycle). + merge(OrderCycle.active). + group('exchanges.receiver_id') + + cycles = cycles.where(receiver_id: distributor_ids) if distributor_ids.present? + + cycles.pluck("exchanges.receiver_id AS receiver_id", + "MIN(order_cycles.orders_close_at) AS earliest_close_at") + .to_h end def attachable_distributor_payment_methods @@ -313,6 +317,13 @@ class OrderCycle < ApplicationRecord coordinator.sells == 'own' end + def same_datetime_value(attribute, string) + return true if self[attribute].blank? && string.blank? + return false if self[attribute].blank? || string.blank? + + DateTime.parse(string).to_fs(:short) == self[attribute]&.to_fs(:short) + end + private def opening? diff --git a/app/models/report_blob.rb b/app/models/report_blob.rb index b9d3927b68..17fda07005 100644 --- a/app/models/report_blob.rb +++ b/app/models/report_blob.rb @@ -5,7 +5,7 @@ class ReportBlob < ActiveStorage::Blob # AWS S3 limits URL expiry to one week. LIFETIME = 1.week - def self.create!(filename, content) + def self.create_locally!(filename, content) create_and_upload!( io: StringIO.new(content), filename:, @@ -15,11 +15,34 @@ class ReportBlob < ActiveStorage::Blob ) end + def self.create_for_upload_later!(filename) + # ActiveStorage discourages modifying a blob later but we need a blob + # before we know anything about the report file. It enables us to use the + # same blob in the controller to read the result. + create_before_direct_upload!( + filename:, + byte_size: 0, + checksum: "0", + content_type: content_type(filename), + service_name: :local, + ).tap do |blob| + ActiveStorage::PurgeJob.set(wait: LIFETIME).perform_later(blob) + end + end + def self.content_type(filename) MIME::Types.of(filename).first&.to_s || "application/octet-stream" end + def store(content) + io = StringIO.new(content) + upload(io, identify: false) + save! + end + def result + return if checksum == "0" + @result ||= download.force_encoding(Encoding::UTF_8) end diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index 4e2a8601be..34328d5389 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -29,7 +29,6 @@ module Spree can :update, Order do |order, token| order.user == user || (order.token && token == order.token) end - can [:index, :read], Product can [:index, :read], ProductProperty can [:index, :read], Property can :create, Spree::User @@ -39,7 +38,6 @@ module Spree can [:index, :read], StockLocation can [:index, :read], StockMovement can [:index, :read], Taxon - can [:index, :read], Taxonomy can [:index, :read], Variant can [:index, :read], Zone end @@ -244,8 +242,8 @@ module Spree can [:admin, :index], ::Admin::DfcProductImportsController # Reports page - can [:admin, :index, :show], ::Admin::ReportsController - can [:admin, :show, :customers, :orders_and_distributors, :group_buys, :payments, + can [:admin, :index, :show, :create], ::Admin::ReportsController + can [:admin, :show, :create, :customers, :orders_and_distributors, :group_buys, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, :enterprise_fee_summary, :bulk_coop], :report end @@ -325,7 +323,7 @@ module Spree end # Reports page - can [:admin, :index, :show], ::Admin::ReportsController + can [:admin, :index, :show, :create], ::Admin::ReportsController can [:admin, :customers, :group_buys, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices, :enterprise_fee_summary, :bulk_coop], :report diff --git a/app/models/spree/image.rb b/app/models/spree/image.rb index 77a463c301..45859a6b45 100644 --- a/app/models/spree/image.rb +++ b/app/models/spree/image.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "mini_magick" - module Spree class Image < Asset has_one_attached :attachment, service: image_service do |attachment| @@ -35,7 +33,7 @@ module Spree return self.class.default_image_url(size) unless attachment.attached? image_variant_url_for(variant(size)) - rescue ActiveStorage::Error, MiniMagick::Error, ActionView::Template::Error => e + rescue StandardError => e Bugsnag.notify "Product ##{viewable_id} Image ##{id} error: #{e.message}" Rails.logger.error(e.message) diff --git a/app/models/spree/product.rb b/app/models/spree/product.rb index d484b6d123..d4872b3e84 100755 --- a/app/models/spree/product.rb +++ b/app/models/spree/product.rb @@ -263,10 +263,12 @@ module Spree # Format as per WeightsAndMeasures (todo: re-orgnaise maybe after product/variant refactor) def variant_unit_with_scale + # Our code is based upon English based number formatting with a period `.` scale_clean = ActiveSupport::NumberHelper.number_to_rounded(variant_unit_scale, precision: nil, significant: false, - strip_insignificant_zeros: true) + strip_insignificant_zeros: true, + locale: :en) [variant_unit, scale_clean].compact_blank.join("_") end @@ -295,14 +297,8 @@ module Spree # eg clone product. Will raise error if clonning a product with no variant return if variants.first&.valid? - unless Spree::Taxon.find_by(id: primary_taxon_id) - errors.add(:primary_taxon_id, - I18n.t('activerecord.errors.models.spree/product.must_exist')) - end - return if Enterprise.find_by(id: supplier_id) - - errors.add(:supplier_id, - I18n.t('activerecord.errors.models.spree/product.must_exist')) + errors.add(:primary_taxon_id, :blank) unless Spree::Taxon.find_by(id: primary_taxon_id) + errors.add(:supplier_id, :blank) unless Enterprise.find_by(id: supplier_id) end def update_units diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 68f2d19015..204dbc8b73 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -87,16 +87,20 @@ module Spree # Return the services (pickup, delivery) that different distributors provide, in the format: # {distributor_id => {pickup: true, delivery: false}, ...} - def self.services - Hash[ - Spree::ShippingMethod. - joins(:distributor_shipping_methods). - group('distributor_id'). - select("distributor_id"). - select("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup"). - select("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery"). - map { |sm| [sm.distributor_id.to_i, { pickup: sm.pickup, delivery: sm.delivery }] } - ] + # + # Optionally, specify some distributor_ids as a parameter to scope the results + def self.services(distributor_ids = nil) + methods = Spree::ShippingMethod.joins(:distributor_shipping_methods).group('distributor_id') + + if distributor_ids.present? + methods = methods.where(distributor_shipping_methods: { distributor_id: distributor_ids }) + end + + methods. + pluck(Arel.sql("distributor_id"), + Arel.sql("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup"), + Arel.sql("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery")). + to_h { |(distributor_id, pickup, delivery)| [distributor_id.to_i, { pickup:, delivery: }] } end def self.backend diff --git a/app/models/spree/taxon.rb b/app/models/spree/taxon.rb index bc0b85371a..09c772fdf0 100644 --- a/app/models/spree/taxon.rb +++ b/app/models/spree/taxon.rb @@ -2,19 +2,11 @@ module Spree class Taxon < ApplicationRecord - self.belongs_to_required_by_default = false - - acts_as_nested_set dependent: :destroy - - belongs_to :taxonomy, class_name: 'Spree::Taxonomy', touch: true - has_many :variants, class_name: "Spree::Variant", foreign_key: "primary_taxon_id", inverse_of: :primary_taxon, dependent: :restrict_with_error has_many :products, through: :variants, dependent: nil - before_create :set_permalink - validates :name, presence: true # Indicate which filters should be used for this taxon @@ -31,40 +23,21 @@ module Spree end end - def set_permalink - if parent.present? - self.permalink = [parent.permalink, permalink_end].join('/') - elsif permalink.blank? - self.permalink = UrlGenerator.to_url(name) - end - end - - # For #2759 - def to_param - permalink - end - - def pretty_name - ancestor_chain = ancestors.inject("") do |name, ancestor| - name + "#{ancestor.name} -> " - end - ancestor_chain + name.to_s - end - # Find all the taxons of supplied products for each enterprise, indexed by enterprise. # Format: {enterprise_id => [taxon_id, ...]} - def self.supplied_taxons - taxons = {} + # + # Optionally, specify some enterprise_ids to scope the results + def self.supplied_taxons(enterprise_ids = nil) + taxons = Spree::Taxon.joins(variants: :supplier) - Spree::Taxon. - joins(variants: :supplier). - select('spree_taxons.*, enterprises.id AS enterprise_id'). - each do |t| - taxons[t.enterprise_id.to_i] ||= Set.new - taxons[t.enterprise_id.to_i] << t.id - end + taxons = taxons.where(enterprises: { id: enterprise_ids }) if enterprise_ids.present? taxons + .pluck('spree_taxons.id, enterprises.id AS enterprise_id') + .each_with_object({}) do |(taxon_id, enterprise_id), collection| + collection[enterprise_id.to_i] ||= Set.new + collection[enterprise_id.to_i] << taxon_id + end end # Find all the taxons of distributed products for each enterprise, indexed by enterprise. @@ -72,7 +45,9 @@ module Spree # or :current taxons (distributed in an open order cycle). # # Format: {enterprise_id => [taxon_id, ...]} - def self.distributed_taxons(which_taxons = :all) + # + # Optionally, specify some enterprise_ids to scope the results + def self.distributed_taxons(which_taxons = :all, enterprise_ids = nil) ents_and_vars = ExchangeVariant.joins(exchange: :order_cycle).merge(Exchange.outgoing) .select("DISTINCT variant_id, receiver_id AS enterprise_id") @@ -85,18 +60,14 @@ module Spree INNER JOIN (#{ents_and_vars.to_sql}) AS ents_and_vars ON spree_variants.id = ents_and_vars.variant_id") + if enterprise_ids.present? + taxons = taxons.where(ents_and_vars: { enterprise_id: enterprise_ids }) + end + taxons.each_with_object({}) do |t, ts| ts[t.enterprise_id.to_i] ||= Set.new ts[t.enterprise_id.to_i] << t.id end end - - private - - def permalink_end - return UrlGenerator.to_url(name) if permalink.blank? - - permalink.split('/').last - end end end diff --git a/app/models/spree/taxonomy.rb b/app/models/spree/taxonomy.rb deleted file mode 100644 index a8600caa27..0000000000 --- a/app/models/spree/taxonomy.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Spree - class Taxonomy < ApplicationRecord - validates :name, presence: true - - has_many :taxons, dependent: :nullify - has_one :root, -> { where parent_id: nil }, class_name: "Spree::Taxon", dependent: :destroy - - after_save :set_name - - default_scope -> { order("#{table_name}.position") } - - private - - def set_name - if root - root.update_columns( - name:, - updated_at: Time.zone.now - ) - else - self.root = Taxon.create!(taxonomy_id: id, name:) - end - end - end -end diff --git a/app/models/spree/user.rb b/app/models/spree/user.rb index bef7609933..9eae2b5074 100644 --- a/app/models/spree/user.rb +++ b/app/models/spree/user.rb @@ -42,6 +42,7 @@ module Spree has_many :credit_cards, dependent: :destroy has_many :report_rendering_options, class_name: "::ReportRenderingOptions", dependent: :destroy has_many :webhook_endpoints, dependent: :destroy + has_many :column_preferences, dependent: :destroy has_one :oidc_account, dependent: :destroy accepts_nested_attributes_for :enterprise_roles, allow_destroy: true diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index cabc309a93..79a28508f2 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -15,7 +15,9 @@ class VariantOverride < ApplicationRecord # Need to ensure this can be set by the user. validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true validates :price, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true - validates :count_on_hand, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true + validates :count_on_hand, numericality: { + greater_than_or_equal_to: 0, unless: :on_demand? + }, allow_nil: true default_scope { where(permission_revoked_at: nil) } @@ -36,9 +38,8 @@ class VariantOverride < ApplicationRecord end def stock_overridden? - # If count_on_hand is present, it means on_demand is false - # See StockSettingsOverrideValidation for details - count_on_hand.present? + # Testing for not nil because for a boolean `false.present?` is false. + !on_demand.nil? || !count_on_hand.nil? end def use_producer_stock_settings? diff --git a/app/serializers/api/admin/taxon_serializer.rb b/app/serializers/api/admin/taxon_serializer.rb index cb18665f56..1a101d4cdc 100644 --- a/app/serializers/api/admin/taxon_serializer.rb +++ b/app/serializers/api/admin/taxon_serializer.rb @@ -3,7 +3,7 @@ module Api module Admin class TaxonSerializer < ActiveModel::Serializer - attributes :id, :name, :pretty_name + attributes :id, :name end end end diff --git a/app/serializers/api/cached_enterprise_serializer.rb b/app/serializers/api/cached_enterprise_serializer.rb index d9e08a0155..8210a85621 100644 --- a/app/serializers/api/cached_enterprise_serializer.rb +++ b/app/serializers/api/cached_enterprise_serializer.rb @@ -95,9 +95,7 @@ module Api .merge(Exchange.to_enterprise(enterprise)) .select('DISTINCT spree_properties.*') - return properties.merge(OrderCycle.active) if active - - properties + properties.merge(OrderCycle.active) end def distributed_producer_properties @@ -106,16 +104,14 @@ module Api properties = Spree::Property .joins( producer_properties: { - producer: { supplied_products: { variants: { exchanges: :order_cycle } } } + producer: { supplied_variants: { exchanges: :order_cycle } } } ) .merge(Exchange.outgoing) .merge(Exchange.to_enterprise(enterprise)) .select('DISTINCT spree_properties.*') - return properties.merge(OrderCycle.active) if active - - properties + properties.merge(OrderCycle.active) end def active diff --git a/app/serializers/api/taxon_serializer.rb b/app/serializers/api/taxon_serializer.rb index 38eb2bf3dd..fc986a6708 100644 --- a/app/serializers/api/taxon_serializer.rb +++ b/app/serializers/api/taxon_serializer.rb @@ -4,5 +4,5 @@ class Api::TaxonSerializer < ActiveModel::Serializer cached delegate :cache_key, to: :object - attributes :id, :name, :permalink, :pretty_name, :position, :parent_id, :taxonomy_id + attributes :id, :name, :permalink, :position end diff --git a/app/serializers/api/uncached_enterprise_serializer.rb b/app/serializers/api/uncached_enterprise_serializer.rb index d92db46d28..c1d620facd 100644 --- a/app/serializers/api/uncached_enterprise_serializer.rb +++ b/app/serializers/api/uncached_enterprise_serializer.rb @@ -7,7 +7,7 @@ module Api attributes :orders_close_at, :active def orders_close_at - options[:data].earliest_closing_times[object.id] + options[:data].earliest_closing_times[object.id]&.to_time end def active diff --git a/app/services/order_cycles/form_service.rb b/app/services/order_cycles/form_service.rb index 0b2f62d988..16f9f09755 100644 --- a/app/services/order_cycles/form_service.rb +++ b/app/services/order_cycles/form_service.rb @@ -7,6 +7,8 @@ module OrderCycles class FormService def initialize(order_cycle, order_cycle_params, user) @order_cycle = order_cycle + @confirm_datetime_change = order_cycle_params.delete :confirm_datetime_change + @error_class = order_cycle_params.delete :error_class @order_cycle_params = order_cycle_params @specified_params = order_cycle_params.keys @user = user @@ -21,6 +23,9 @@ module OrderCycles end def save + # Check that order cycle datetime values changed if it has existing orders + verify_datetime_change! + schedule_ids = build_schedule_ids order_cycle.assign_attributes(order_cycle_params) return false unless order_cycle.valid? @@ -229,5 +234,16 @@ module OrderCycles DistributorShippingMethod.where(distributor_id: user_distributors_ids) .pluck(:id) end + + def verify_datetime_change! + return unless @confirm_datetime_change + return unless @order_cycle.orders.exists? + return if @order_cycle.same_datetime_value(:orders_open_at, + @order_cycle_params[:orders_open_at]) && + @order_cycle.same_datetime_value(:orders_close_at, + @order_cycle_params[:orders_close_at]) + + raise @error_class + end end end diff --git a/app/services/permitted_attributes/product.rb b/app/services/permitted_attributes/product.rb index f080f2b730..716549724d 100644 --- a/app/services/permitted_attributes/product.rb +++ b/app/services/permitted_attributes/product.rb @@ -9,7 +9,7 @@ module PermittedAttributes :unit_description, :variant_unit_name, :display_as, :sku, :group_buy, :group_buy_unit_size, :taxon_ids, :primary_taxon_id, :tax_category_id, :supplier_id, - :meta_keywords, :notes, :inherits_properties, + :meta_keywords, :notes, :inherits_properties, :shipping_category_id, { product_properties_attributes: [:id, :property_name, :value], variants_attributes: [PermittedAttributes::Variant.attributes], image_attributes: [:attachment] } diff --git a/app/services/sets/order_cycle_set.rb b/app/services/sets/order_cycle_set.rb index 663f684062..9d2e2dfee5 100644 --- a/app/services/sets/order_cycle_set.rb +++ b/app/services/sets/order_cycle_set.rb @@ -3,7 +3,31 @@ module Sets class OrderCycleSet < ModelSet def initialize(collection, attributes = {}) + @confirm_datetime_change = attributes.delete :confirm_datetime_change + @error_class = attributes.delete :error_class + super(OrderCycle, collection, attributes) end + + def process(order_cycle, attributes) + if @confirm_datetime_change && + order_cycle.orders.exists? && + datetime_value_changed(order_cycle, attributes) + raise @error_class + end + + super + end + + private + + def datetime_value_changed(order_cycle, attributes) + # return true if either key is present in params and change in values detected + return true if attributes.key?(:orders_open_at) && + !order_cycle.same_datetime_value(:orders_open_at, attributes[:orders_open_at]) + + attributes.key?(:orders_close_at) && + !order_cycle.same_datetime_value(:orders_close_at, attributes[:orders_close_at]) + end end end diff --git a/app/services/shops_list_service.rb b/app/services/shops_list_service.rb index 0e4d42f99e..e145eedc93 100644 --- a/app/services/shops_list_service.rb +++ b/app/services/shops_list_service.rb @@ -1,12 +1,17 @@ # frozen_string_literal: true class ShopsListService + # shops that are ready for checkout, and have an order cycle that is currently open def open_shops - shops_list.ready_for_checkout.all + shops_list. + ready_for_checkout. + distributors_with_active_order_cycles end + # shops that are either not ready for checkout, or don't have an open order cycle; the inverse of + # #open_shops def closed_shops - shops_list.not_ready_for_checkout.all + shops_list.where.not(id: open_shops.reselect("enterprises.id")) end private diff --git a/app/services/weights_and_measures.rb b/app/services/weights_and_measures.rb index 170dacb208..e285a0baac 100644 --- a/app/services/weights_and_measures.rb +++ b/app/services/weights_and_measures.rb @@ -26,9 +26,15 @@ class WeightsAndMeasures def self.variant_unit_options available_units_sorted.flat_map do |measurement, measurement_info| measurement_info.filter_map do |scale, unit_info| + # Our code is based upon English based number formatting + # Some language locales like +hu+ uses a comma(,) for decimal separator + # While in English, decimal separator is represented by a period. + # e.g. en: 0.001, hu: 0,001 + # Hence the results become "weight_0,001" for hu while or code recognizes "weight_0.001" scale_clean = ActiveSupport::NumberHelper.number_to_rounded(scale, precision: nil, significant: false, - strip_insignificant_zeros: true) + strip_insignificant_zeros: true, + locale: :en) [ "#{I18n.t(measurement)} (#{unit_info['name']})", # Label (eg "Weight (g)") "#{measurement}_#{scale_clean}", # Scale ID (eg "weight_1") diff --git a/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml b/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml new file mode 100644 index 0000000000..8742d80fad --- /dev/null +++ b/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml @@ -0,0 +1,19 @@ +.flex-column-gap-1 + %h6 + = t('.title') + %div{ style: 'font-size: 1rem;' } + = t('.content') +%p.modal-actions.justify-end.gap-1 + - if action == 'simple_update' + %button.button.secondary{ "ng-click": "submit($event, null)", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'save' } } + = t('.proceed') + %button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndNext' } } + = t('.proceed') + %button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndBack' } } + = t('.proceed') + = link_to t('.cancel'), admin_order_cycles_path, id: 'cancel', class: 'button primary', data: { 'order-cycle-form-target': 'cancel' } + - if action == 'bulk_update' + %button.button.secondary{ "ng-click": "saveAll($event)", type: "button", style: "display: none;", data: { action: 'click->modal#close', trigger_action: 'bulk_save' } } + = t('.proceed') + %button.button.primary{ type: "button", 'data-action': 'click->modal#close' } + = t('.cancel') \ No newline at end of file diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index f6f3bc6f49..39e3305b18 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -16,18 +16,25 @@ - ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = admin_inject_order_cycle_instance(@order_cycle) -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| +%div{ data: { controller: 'order-cycle-form' } } + = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + + %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } + %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'save' } } + - if @order_cycle.simple? + %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndBack' } } + - else + %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndNext' } } + %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } + %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } - %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - if @order_cycle.simple? - %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } + = render 'simple_form', f: f - else - %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } - %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } + = render 'form', f: f - - if @order_cycle.simple? - = render 'simple_form', f: f - - else - = render 'form', f: f + %div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } } + %button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' } + = render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do + .content.flex-column.gap-2 + = render 'date_time_warning_modal_content', action: 'simple_update' \ No newline at end of file diff --git a/app/views/admin/order_cycles/index.html.haml b/app/views/admin/order_cycles/index.html.haml index db6c544286..9e593e9245 100644 --- a/app/views/admin/order_cycles/index.html.haml +++ b/app/views/admin/order_cycles/index.html.haml @@ -23,12 +23,20 @@ .thirteen.columns.alpha   %columns-dropdown{ action: "#{controller_name}_#{action_name}" } - %form{ name: 'order_cycles_form' } - %save-bar{ dirty: "order_cycles_form.$dirty", persist: "false" } - %input.red{ type: "button", value: t(:save_changes), "ng-click": "saveAll()", "ng-disabled": "!order_cycles_form.$dirty" } - %table.index#listing_order_cycles{ "ng-show": 'orderCycles.length > 0' } - = render 'admin/order_cycles/header' #, simple_index: simple_index - %tbody - = render 'admin/order_cycles/row' #, simple_index: simple_index - = render 'admin/order_cycles/loading_flash' - = render 'admin/order_cycles/show_more' + %div{ data: { controller: 'order-cycle-form' } } + %form{ name: 'order_cycles_form' } + %save-bar{ dirty: "order_cycles_form.$dirty", persist: "false" } + %input.red{ type: "button", value: t(:save_changes), "ng-click": "saveAll($event)", "ng-disabled": "!order_cycles_form.$dirty", + data: { confirm: "true", 'trigger-action': 'bulk_save' } } + %table.index#listing_order_cycles{ "ng-show": 'orderCycles.length > 0' } + = render 'admin/order_cycles/header' #, simple_index: simple_index + %tbody + = render 'admin/order_cycles/row' #, simple_index: simple_index + = render 'admin/order_cycles/loading_flash' + = render 'admin/order_cycles/show_more' + + %div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } } + %button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' } + = render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do + .content.flex-column.gap-2 + = render 'date_time_warning_modal_content', action: 'bulk_update' diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index f5c4fd8b5d..edd9d406b6 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -25,7 +25,7 @@ -# empty %td.col-on_hand.align-right -# empty -%td.col-on_hand.align-right +%td.col-producer.align-right -# empty %td.col-category.align-left -# empty @@ -40,3 +40,4 @@ "data-modal-link-target-value": "product-delete-modal", "class": "delete", "data-modal-link-modal-dataset-value": {'data-delete-path': admin_product_destroy_path(product)}.to_json } = t('admin.products_page.actions.delete') + = link_to t('admin.products_page.actions.preview'), admin_product_preview_path(product), {"data-turbo-stream": "" } diff --git a/app/views/admin/products_v3/_product_variant_row.html.haml b/app/views/admin/products_v3/_product_variant_row.html.haml index b41e7c56b5..12b6702cba 100644 --- a/app/views/admin/products_v3/_product_variant_row.html.haml +++ b/app/views/admin/products_v3/_product_variant_row.html.haml @@ -11,7 +11,7 @@ %tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': variant.new_record? ? "true" : false } = render partial: 'variant_row', locals: { variant:, f: variant_form, category_options:, tax_category_options:, producer_options: } - = form.fields_for("products][#{product_index}][variants_attributes][NEW_RECORD", prepare_new_variant(product)) do |new_variant_form| + = form.fields_for("products][#{product_index}][variants_attributes][NEW_RECORD", prepare_new_variant(product, producer_options)) do |new_variant_form| %template{ 'data-nested-form-target': "template" } %tr.condensed{ 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': "true" } = render partial: 'variant_row', locals: { variant: new_variant_form.object, f: new_variant_form, category_options:, tax_category_options:, producer_options: } diff --git a/app/views/admin/products_v3/_sort.html.haml b/app/views/admin/products_v3/_sort.html.haml index 4cb5f29ee9..2b25644639 100644 --- a/app/views/admin/products_v3/_sort.html.haml +++ b/app/views/admin/products_v3/_sort.html.haml @@ -1,7 +1,7 @@ #sort %div.pagination-description - if pagy.present? - = t(".pagination.total_html", total: pagy.count, from: pagy.from, to: pagy.to) + = t(".pagination.products_total_html", count: pagy.count, from: pagy.from, to: pagy.to) - if search_term.present? || producer_id.present? || category_id.present? %a{ href: url_for(page: 1), class: "button disruptive", data: { 'turbo-frame': "_self", 'turbo-action': "advance" } } diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index e04f33b00a..71e777ba13 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -13,16 +13,16 @@ = hidden_field_tag :producer_id, @producer_id = hidden_field_tag :category_id, @category_id - %table.products{ 'data-column-preferences-target': "table" } + %table.products{ 'data-column-preferences-target': "table", class: (hide_producer_column?(producer_options) ? 'hide-producer' : '') } %colgroup -# The `min-width` property works in Chrome but not Firefox so is considered progressive enhancement. - %col.col-image{ width:"56px" }= # (image size + padding) + %col.col-image{ width:"44px" }= # (image size + padding) %col.col-name{ style:"min-width: 6em" }= # (grow to fill) %col.col-sku{ width:"8%", style:"min-width: 6em" } %col.col-unit_scale{ width:"8%" } %col.col-unit{ width:"8%" } - %col.col-price{ width:"5%", style:"min-width: 5em" } - %col.col-on_hand{ width:"10%"} + %col.col-price{ width:"10%", style:"min-width: 5em" } + %col.col-on_hand{ width:"8%" } %col.col-producer{ style:"min-width: 6em" }= # (grow to fill) %col.col-category{ width:"8%" } %col.col-tax_category{ width:"8%" } diff --git a/app/views/admin/products_v3/index.html.haml b/app/views/admin/products_v3/index.html.haml index 668a8527a4..fed068a609 100644 --- a/app/views/admin/products_v3/index.html.haml +++ b/app/views/admin/products_v3/index.html.haml @@ -20,3 +20,4 @@ = render partial: 'delete_modal', locals: { object_type: } #modal-component #edit_image_modal + #product-preview-modal-container diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml new file mode 100644 index 0000000000..61935d012c --- /dev/null +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -0,0 +1,119 @@ += turbo_stream.update "product-preview-modal-container" do + = render ModalComponent.new(id: "product-preview-modal", instant: true, modal_class: "big") do + #product-preview{ "data-controller": "tabs" } + %h1 + = t("admin.products_page.product_preview.product_preview") + %dl.admin-tabs + %dd + %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } + = t("admin.products_page.product_preview.shop_tab") + %dd + %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } + = t("admin.products_page.product_preview.product_details_tab") + .tabs-content + .content.active + %div{ data: { "tabs-target": "content" } } + .product-thumb + %a + - if @product.group_buy + %span.product-thumb__bulk-label + = t(".bulk") + = image_tag @product.image&.url(:small) || Spree::Image.default_image_url(:small) + + .summary + .summary-header + %h3 + %a + %span + = @product.name + - if @product.description + .product-description{ "data-controller": "add-blank-to-link" } + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe + + - if @product.variants.first.supplier.visible + %div + .product-producer + = t :products_from + %span + %a + = @product.variants.first.supplier.name + .product-properties.filter-shopfront.property-selectors + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + - if property[:value].present? + = render AdminTooltipComponent.new(text: property[:value], link_text: property[:name], placement: "bottom") + - else + %a + %span + = property[:name] + .shop-variants + - @product.variants.sort { |v1, v2| v1.name_to_display <=> v2.name_to_display }.sort { |v1, v2| v1.unit_value <=> v2.unit_value }.each do |variant| + .variants.row + .small-3.columns.variant-name + - if variant.display_name.present? + .inline + = variant.display_name + .variant-unit + = variant.unit_to_display + .small-4.medium-3.columns.variant-price + = number_to_currency(variant.price) + .unit-price.variant-unit-price + = render AdminTooltipComponent.new(text: t("js.shopfront.unit_price_tooltip"), link_text: "", placement: "top", link_class: "question-mark-icon") + - # TODO use an helper + - unit_price = UnitPrice.new(variant) + - price_per_unit = variant.price / (unit_price.denominator || 1) + = "#{number_to_currency(price_per_unit)} / #{unit_price.unit}".html_safe + + + .medium-3.columns.total-price + %span + = number_to_currency(0.00) + .small-5.medium-3.large-3.columns.variant-quantity-column.text-right + .variant-quantity-inputs + %button.add-variant + = t("js.shopfront.variant.add_to_cart") + + - # TODO can't check the shop preferrence here, display by default ? + - if !variant.on_demand && variant.on_hand <= 3 + .variant-remaining-stock + = t("js.shopfront.variant.remaining_in_stock", quantity: variant.on_hand) + + %div{ data: { "tabs-target": "content" } } + .row + .columns.small-12.medium-6.large-6.product-header + %h3 + = @product.name + %span + %em + = t("products_from") + %span + = @product.variants.first.supplier.name + + %br + + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + - if property[:value].present? + = render AdminTooltipComponent.new(text: property[:value], link_text: property[:name], placement: "bottom") + - else + %a + %span + = property[:name] + + + - if @product.description + .product-description{ 'data-controller': "add-blank-to-link" } + %p.text-small + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe + + .columns.small-12.medium-6.large-6.product-img + - if @product.image + %img{ src: @product.image.url(:large) } + -else + %img.placeholder{ src: Spree::Image.default_image_url(:large) } diff --git a/app/views/admin/reports/_fallback_display.html.haml b/app/views/admin/reports/_fallback_display.html.haml new file mode 100644 index 0000000000..cfac68c742 --- /dev/null +++ b/app/views/admin/reports/_fallback_display.html.haml @@ -0,0 +1,47 @@ +.download.hidden + = link_to t("admin.reports.download.button"), file_url, target: "_blank", class: "button icon icon-file" + +:javascript + (function () { + const tryDownload = function() { + const link = document.querySelector(".download a"); + + // If the report was already rendered via web sockets: + if (link == null) return; + + fetch(link.href).then((response) => { + if (response.ok) { + response.blob().then((blob) => blob.text()).then((text) => { + const loading = document.querySelector(".loading"); + + if (loading == null) return; + + loading.remove(); + document.querySelector("#report-go button").disabled = false; + + if (link.href.endsWith(".html")) { + // This replaces the hidden download button with the report: + link.parentElement.outerHTML = text; + } else { + // Or just show the download button when it's ready: + document.querySelector(".download").classList.remove("hidden") + } + }); + } else { + setTimeout(tryDownload, 2000); + } + }); + } + + /* + A lot of reports are rendered within 250ms. Others take at least + 2.5 seconds. There's a big gap in between. Observed on: + https://openfoodnetwork.org.au/admin/sidekiq/metrics/ReportJob?period=8h + https://openfoodnetwork.org.uk/admin/sidekiq/metrics/ReportJob?period=8h + https://coopcircuits.fr/admin/sidekiq/metrics/ReportJob?period=8h + + But let's leave the timed response to websockets for now and just poll + as a backup mechanism. + */ + setTimeout(tryDownload, 3000); + })(); diff --git a/app/views/admin/reports/create.turbo_stream.haml b/app/views/admin/reports/create.turbo_stream.haml new file mode 100644 index 0000000000..ecfa4f13a0 --- /dev/null +++ b/app/views/admin/reports/create.turbo_stream.haml @@ -0,0 +1,6 @@ += turbo_stream.update "report-go" do + = button t(:go), "report__submit-btn", "submit", disabled: true += turbo_stream.update "report-table" do + = render "admin/reports/loading" + = render "admin/reports/fallback_display", file_url: @blob.expiring_service_url += turbo_stream.scroll_into_view("#report-table", behavior: "smooth") diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index bc0b767f62..038eed9070 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -3,7 +3,7 @@ - content_for :minimal_js, true -= form_for @report.search, { url: url_for, data: { remote: "true" } } do |f| += form_for @report.search, { url: url_for, data: { turbo: "true" } } do |f| = hidden_field_tag "uuid", request.uuid %fieldset.no-border-bottom.print-hidden diff --git a/app/views/admin/shared/_tooltip.html.haml b/app/views/admin/shared/_tooltip.html.haml deleted file mode 100644 index 6e28c08c43..0000000000 --- a/app/views/admin/shared/_tooltip.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div{"data-controller": "tooltip"} - %a{"data-tooltip-target": "element", href: link, class: link_class} - = link_text - .tooltip-container - .tooltip{"data-tooltip-target": "tooltip"} - = sanitize tooltip_text - .arrow{"data-tooltip-target": "arrow"} diff --git a/app/views/admin/shared/_whats_this_tooltip.html.haml b/app/views/admin/shared/_whats_this_tooltip.html.haml index d63fa41768..73d4a21d51 100644 --- a/app/views/admin/shared/_whats_this_tooltip.html.haml +++ b/app/views/admin/shared/_whats_this_tooltip.html.haml @@ -1 +1 @@ -= render partial: 'admin/shared/tooltip', locals: {link_class: "" ,link: nil, link_text: t('admin.whats_this'), tooltip_text: tooltip_text} += render AdminTooltipComponent.new(text: tooltip_text, link_text: t('admin.whats_this'), link: nil) diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml index 0a70687f14..12c654b3d1 100644 --- a/app/views/layouts/mailer.html.haml +++ b/app/views/layouts/mailer.html.haml @@ -5,7 +5,7 @@ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %title = Spree::Config[:site_name] - = stylesheet_pack_tag 'mail' + = stylesheet_link_tag 'mail' %body{:bgcolor => "#FFFFFF" } - unless @hide_ofn_navigation %table.head-wrap{:bgcolor => "#f2f2f2"} diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index b40ea8255b..9d2679abc2 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do %form{action: main_app.cart_path} %products{"ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true } diff --git a/app/views/shop/products/_shop_variant.html.haml b/app/views/shop/products/_shop_variant.html.haml index b6e91c8c2f..7aa1eb9354 100644 --- a/app/views/shop/products/_shop_variant.html.haml +++ b/app/views/shop/products/_shop_variant.html.haml @@ -1,8 +1,9 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do - .small-4.medium-4.large-5.columns.variant-name + .small-3.columns.variant-name .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }} .variant-unit {{ ::variant.unit_to_display }} - .small-3.medium-3.large-2.columns.variant-price + .small-4.medium-3.columns.variant-price %price-breakdown{"price-breakdown" => "_", variant: "variant", "price-breakdown-append-to-body" => "true", "price-breakdown-placement" => "bottom", @@ -16,7 +17,7 @@ key: "'js.shopfront.unit_price_tooltip'"} {{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }} - .medium-2.large-2.columns.total-price + .medium-3.columns.total-price %span{"ng-class" => "{filled: variant.line_item.total_price}"} {{ variant.line_item.total_price | localizeCurrency }} = render partial: "shop/products/shop_variant_no_group_buy" diff --git a/app/views/shop/products/_shop_variant_no_group_buy.html.haml b/app/views/shop/products/_shop_variant_no_group_buy.html.haml index 20fbb09901..b9c7280d43 100644 --- a/app/views/shop/products/_shop_variant_no_group_buy.html.haml +++ b/app/views/shop/products/_shop_variant_no_group_buy.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::!variant.product.group_buy"} diff --git a/app/views/shop/products/_shop_variant_with_group_buy.html.haml b/app/views/shop/products/_shop_variant_with_group_buy.html.haml index 50b4812e5c..07e325a405 100644 --- a/app/views/shop/products/_shop_variant_with_group_buy.html.haml +++ b/app/views/shop/products/_shop_variant_with_group_buy.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::variant.product.group_buy"} diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 8163d6e903..cd3d103a0c 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .product-thumb %a{"ng-click" => "triggerProductModal()"} diff --git a/app/views/spree/admin/orders/_table_row.html.haml b/app/views/spree/admin/orders/_table_row.html.haml index cb0b579391..7cc4aae10f 100644 --- a/app/views/spree/admin/orders/_table_row.html.haml +++ b/app/views/spree/admin/orders/_table_row.html.haml @@ -45,7 +45,7 @@ %div.row-loading-icons - if local_assigns[:success] %i.success.icon-ok-sign{"data-controller": "ephemeral"} - = render partial: 'admin/shared/tooltip', locals: {link_class: "icon_link with-tip icon-edit no-text" ,link: edit_admin_order_path(order), link_text: "", tooltip_text: t('spree.admin.orders.index.edit')} + = render AdminTooltipComponent.new(text: t('spree.admin.orders.index.edit'), link_text: "", link: edit_admin_order_path(order), link_class: "icon_link with-tip icon-edit no-text") - if order.ready_to_ship? %form = render ShipOrderComponent.new(order: order) diff --git a/app/views/spree/admin/orders/invoice.html.haml b/app/views/spree/admin/orders/invoice.html.haml index d1dbc32264..a72e7c1fff 100644 --- a/app/views/spree/admin/orders/invoice.html.haml +++ b/app/views/spree/admin/orders/invoice.html.haml @@ -1,4 +1,4 @@ -= pdf_stylesheet_pack_tag "mail" += wicked_pdf_stylesheet_link_tag "mail" %table{:width => "100%"} %tbody diff --git a/app/views/spree/admin/orders/invoice2.html.haml b/app/views/spree/admin/orders/invoice2.html.haml index c55a1f6637..935e370d7a 100644 --- a/app/views/spree/admin/orders/invoice2.html.haml +++ b/app/views/spree/admin/orders/invoice2.html.haml @@ -1,4 +1,4 @@ -= pdf_stylesheet_pack_tag "mail" += wicked_pdf_stylesheet_link_tag "mail" %table{:width => "100%"} %tbody diff --git a/app/views/spree/admin/orders/invoice4.html.haml b/app/views/spree/admin/orders/invoice4.html.haml index bf7eee3679..f05a463b88 100644 --- a/app/views/spree/admin/orders/invoice4.html.haml +++ b/app/views/spree/admin/orders/invoice4.html.haml @@ -1,4 +1,4 @@ -= pdf_stylesheet_pack_tag "mail" += wicked_pdf_stylesheet_link_tag "mail" %table{:width => "100%"} %tbody diff --git a/app/views/spree/admin/products/_primary_taxon_form.html.haml b/app/views/spree/admin/products/_primary_taxon_form.html.haml index fa4b0b1976..615d415bc1 100644 --- a/app/views/spree/admin/products/_primary_taxon_form.html.haml +++ b/app/views/spree/admin/products/_primary_taxon_form.html.haml @@ -1,6 +1,12 @@ -= f.field_container :primary_taxon do += f.field_container :primary_taxon_id do = f.label :primary_taxon_id, t('.product_category') %span.required * %br - = f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"}) + = render(SearchableDropdownComponent.new(form: f, + name: :primary_taxon_id, + aria_label: t('.product_category'), + options: Spree::Taxon.select(:name, :id).order(:name).pluck(:name, :id), + selected_option: @product.primary_taxon_id, + include_blank: true, + placeholder_value: t('.search_for_categories'))) = f.error_message_on :primary_taxon_id diff --git a/app/views/spree/admin/products/_shipping_category_form.html.haml b/app/views/spree/admin/products/_shipping_category_form.html.haml index 0f3635d698..d7157023ad 100644 --- a/app/views/spree/admin/products/_shipping_category_form.html.haml +++ b/app/views/spree/admin/products/_shipping_category_form.html.haml @@ -1,4 +1,4 @@ -= f.field_container :shipping_categories do += f.field_container :shipping_category_id do = f.label :shipping_category_id, t(:shipping_category) = f.collection_select(:shipping_category_id, Spree::ShippingCategory.all, :id, :name, {:include_blank => false}, {:class => 'select2 fullwidth'}) = f.error_message_on :shipping_category_id diff --git a/app/views/spree/admin/products/edit.html.haml b/app/views/spree/admin/products/edit.html.haml index 6a5ccb4c21..003a35eee6 100644 --- a/app/views/spree/admin/products/edit.html.haml +++ b/app/views/spree/admin/products/edit.html.haml @@ -1,7 +1,7 @@ = admin_inject_available_units - content_for :page_actions do - %li= button_link_to t('admin.products.back_to_products_list'), "#{admin_products_path}#{(@url_filters.empty? ? "" : "#?#{@url_filters.to_query}")}", :icon => 'icon-arrow-left' + %li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url(@url_filters), :icon => 'icon-arrow-left' %li#new_product_link = button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' } @@ -11,9 +11,13 @@ = render :partial => 'spree/shared/error_messages', :locals => { :target => @product } = form_for [:admin, @product], :url => admin_product_path(@product, @url_filters), :method => :put, :html => { :multipart => true } do |f| - %fieldset.no-border-top{'ng-app' => 'admin.products'} + %fieldset.no-border-top{'ng-app': 'admin.products', 'data-turbo': true, 'data-controller': "product-preview"} = render :partial => 'form', :locals => { :f => f } .form-buttons.filter-actions.actions + = link_to t("admin.products_page.actions.preview"), Rails.application.routes.url_helpers.admin_product_preview_path(@product), {"data-turbo-stream": "" , class: "button secondary"} + = button t(:update), 'icon-refresh' - = button_link_to t(:cancel), "#{collection_url}#{(@url_filters.empty? ? "" : "#?#{@url_filters.to_query}")}", icon: 'icon-remove' + = button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove' + + #product-preview-modal-container diff --git a/app/views/spree/admin/products/new.html.haml b/app/views/spree/admin/products/new.html.haml index 70d620ef79..a716215738 100644 --- a/app/views/spree/admin/products/new.html.haml +++ b/app/views/spree/admin/products/new.html.haml @@ -8,10 +8,16 @@ %legend{align: "center"}= t(".new_product") .sixteen.columns.alpha .eight.columns.alpha - = f.field_container :supplier do - = f.label :supplier, t(".supplier") + = f.field_container :supplier_id do + = f.label :supplier_id, t(".supplier") %span.required * - = f.select :supplier_id, options_from_collection_for_select(@producers, :id, :name, @product.supplier_id), { include_blank: t("spree.admin.products.new.supplier_select_placeholder") }, { "data-controller": "tom-select", class: "primary" } + = render(SearchableDropdownComponent.new(form: f, + name: :supplier_id, + aria_label: t('.supplier'), + options: @producers.select(:name, :id).order(:name).pluck(:name, :id), + selected_option: @product.supplier_id, + include_blank: true, + placeholder_value: t('.search_for_suppliers'))) = f.error_message_on :supplier_id .eight.columns.omega = f.field_container :name do @@ -25,8 +31,16 @@ = f.field_container :variant_unit do = f.label :variant_unit, t(".units") %span.required * - %select{id: 'product_variant_unit_with_scale', 'ng-model' => 'product.variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options', "data-controller": "tom-select","data-tom-select-options-value": '{"allowEmptyOption":false}', class: "primary"} - %option{'value' => '', 'ng-hide' => "hasUnit(product)"} + = f.select 'variant_unit', [], + { include_blank: true }, + { id: 'product_variant_unit_with_scale', + name: 'product_variant_unit_with_scale', + 'ng-model' => 'product.variant_unit_with_scale', + 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options', + "data-controller": "tom-select", + "data-tom-select-options-value": '{"allowEmptyOption":false}', + class: "primary", + } %input{ type: 'hidden', 'ng-value': 'product.variant_unit', "ng-init": "product.variant_unit='#{@product.variant_unit}'", name: 'product[variant_unit]' } %input{ type: 'hidden', 'ng-value': 'product.variant_unit_scale', "ng-init": "product.variant_unit_scale='#{@product.variant_unit_scale}'", name: 'product[variant_unit_scale]' } = f.error_message_on :variant_unit @@ -34,16 +48,17 @@ = f.field_container :unit_value do = f.label :unit_value, t(".value"), 'ng-disabled' => "!hasUnit(product)" %span.required * - %input.fullwidth{ id: 'product_unit_value', 'ng-model' => 'product.master.unit_value_with_description', :type => 'text', placeholder: "eg. 2", 'ng-disabled' => "!hasUnit(product)" } + = f.text_field :unit_value, placeholder: "eg. 2", 'ng-model' => 'product.master.unit_value_with_description', class: 'fullwidth', 'ng-disabled' => "!hasUnit(product)" %input{ type: 'hidden', 'ng-value': 'product.master.unit_value', "ng-init": "product.master.unit_value='#{@product.unit_value}'", name: 'product[unit_value]' } %input{ type: 'hidden', 'ng-value': 'product.master.unit_description', "ng-init": "product.master.unit_description='#{@product.unit_description}'", name: 'product[unit_description]' } = f.error_message_on :unit_value = render 'display_as', f: f .six.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" } - = f.field_container :unit_name do - = f.label :product_variant_unit_name, t(".unit_name") + = f.field_container :variant_unit_name do + = f.label :variant_unit_name, t(".unit_name") %span.required * - %input.fullwidth{ id: 'product_variant_unit_name','ng-model' => 'product.variant_unit_name', :name => 'product[variant_unit_name]', :placeholder => t('admin.products.unit_name_placeholder'), :type => 'text' } + = f.text_field :variant_unit_name, :placeholder => t('admin.products.unit_name_placeholder'), 'ng-model' => 'product.variant_unit_name', class: 'fullwidth', 'ng-init': "product.variant_unit_name='#{@product.variant_unit_name}'" + = f.error_message_on :variant_unit_name .sixteen.columns.alpha .eight.columns.alpha = render 'spree/admin/products/primary_taxon_form', f: f diff --git a/app/views/spree/admin/shared/_configuration_menu.html.haml b/app/views/spree/admin/shared/_configuration_menu.html.haml index 215011f6b6..0b0f2c2d80 100644 --- a/app/views/spree/admin/shared/_configuration_menu.html.haml +++ b/app/views/spree/admin/shared/_configuration_menu.html.haml @@ -15,7 +15,7 @@ - if DefaultCountry.id = configurations_sidebar_menu_item Spree.t(:states), admin_country_states_path(DefaultCountry.id) = configurations_sidebar_menu_item Spree.t(:payment_methods), admin_payment_methods_path - = configurations_sidebar_menu_item Spree.t(:taxonomies), admin_taxonomies_path + = configurations_sidebar_menu_item Spree.t(:taxons), admin_taxons_path = configurations_sidebar_menu_item Spree.t(:shipping_methods), admin_shipping_methods_path = configurations_sidebar_menu_item Spree.t(:shipping_categories), admin_shipping_categories_path = configurations_sidebar_menu_item t(:enterprise_fees), main_app.admin_enterprise_fees_path diff --git a/app/views/spree/admin/shared/_tabs.html.haml b/app/views/spree/admin/shared/_tabs.html.haml index f9a9c6613f..3ad1a1d53a 100644 --- a/app/views/spree/admin/shared/_tabs.html.haml +++ b/app/views/spree/admin/shared/_tabs.html.haml @@ -3,7 +3,7 @@ = tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh' = tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path, icon: 'icon-shopping-cart' = tab :reports, url: main_app.admin_reports_path, icon: 'icon-file' -= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxonomies, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path += tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path = tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path = tab :customers, url: main_app.admin_customers_path = tab :enterprise_groups, url: main_app.admin_enterprise_groups_path, label: 'groups' diff --git a/app/views/spree/admin/taxonomies/_form.html.haml b/app/views/spree/admin/taxonomies/_form.html.haml deleted file mode 100644 index ac60030374..0000000000 --- a/app/views/spree/admin/taxonomies/_form.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.field.align-center - = f.field_container :name do - = f.label :name, t("spree.name") - %span.required * - %br/ - = error_message_on :taxonomy, :name - = text_field :taxonomy, :name diff --git a/app/views/spree/admin/taxonomies/_js_head.html.erb b/app/views/spree/admin/taxonomies/_js_head.html.erb deleted file mode 100755 index e95f4bdeff..0000000000 --- a/app/views/spree/admin/taxonomies/_js_head.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<% content_for :head do %> - <%= javascript_tag "var taxonomy_id = #{@taxonomy.id}; - var loading = '#{escape_javascript t("spree.loading")}'; - var new_taxon = '#{escape_javascript t("spree.new_taxon")}'; - var server_error = '#{escape_javascript t("spree.server_error")}'; - var taxonomy_tree_error = '#{escape_javascript t("spree.taxonomy_tree_error")}'; - - $(document).ready(function(){ - setup_taxonomy_tree(taxonomy_id); - }); - " - %> -<% end %> diff --git a/app/views/spree/admin/taxonomies/_list.html.haml b/app/views/spree/admin/taxonomies/_list.html.haml deleted file mode 100644 index 1574e8b718..0000000000 --- a/app/views/spree/admin/taxonomies/_list.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%table#listing_taxonomies.index.sortable{"data-sortable-link" => update_positions_admin_taxonomies_url} - %colgroup - %col{style: "width: 85%"}/ - %col{style: "width: 15%"}/ - %thead - %tr - %th= t("spree.name") - %th.actions - %tbody - - @taxonomies.each do |taxonomy| - - tr_class = cycle('odd', 'even') - - tr_id = spree_dom_id(taxonomy) - %tr{class: tr_class, id: tr_id} - %td - %span.handle - = taxonomy.name - %td.actions - = link_to_edit taxonomy.id, no_text: true - = link_to_delete taxonomy, no_text: true diff --git a/app/views/spree/admin/taxonomies/_taxon.html.haml b/app/views/spree/admin/taxonomies/_taxon.html.haml deleted file mode 100644 index ce8fd21247..0000000000 --- a/app/views/spree/admin/taxonomies/_taxon.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -- if taxon.children.length != 0 - %ul - - taxon.children.each do |child| - %li{id: "#{child.id}", rel: "taxon"} - %a{href: "#"}= child.name - - if child.children.length > 0 - = render partial: 'taxon', locals: { taxon: child } diff --git a/app/views/spree/admin/taxonomies/edit.haml b/app/views/spree/admin/taxonomies/edit.haml deleted file mode 100755 index 2b8065674e..0000000000 --- a/app/views/spree/admin/taxonomies/edit.haml +++ /dev/null @@ -1,27 +0,0 @@ -= render partial: 'spree/admin/shared/configuration_menu' - -= render partial: 'js_head' - -- content_for :page_title do - = t("spree.taxonomy_edit") - -- content_for :page_actions do - %li - = button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left' - -#ajax_error.errorExplanation{style: "display:none;"} -= form_for [:admin, @taxonomy] do |f| - %fieldset.no-border-top - = render partial: 'form', locals: { f: f } - %div - = label_tag nil, t("spree.tree") - %br/ - :javascript - Spree.routes.taxonomy_taxons = "#{main_app.api_v0_taxonomy_taxons_url(@taxonomy)}"; - Spree.routes.admin_taxonomy_taxons = "#{spree.admin_taxonomy_taxons_url(@taxonomy)}"; - #taxonomy_tree.tree - .info= t("spree.taxonomy_tree_instruction") - %br/ - .filter-actions.actions - = button t('spree.actions.update'), 'icon-refresh' - = button_link_to t('spree.actions.cancel'), admin_taxonomies_path, icon: 'icon-remove' diff --git a/app/views/spree/admin/taxonomies/index.html.haml b/app/views/spree/admin/taxonomies/index.html.haml deleted file mode 100644 index 63041138d1..0000000000 --- a/app/views/spree/admin/taxonomies/index.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -= render partial: 'spree/admin/shared/configuration_menu' - -- content_for :page_title do - = t("spree.taxonomies") - -- content_for :page_actions do - %li - = button_link_to t("spree.new_taxonomy"), spree.new_admin_taxonomy_url, icon: 'icon-plus', id: 'admin_new_taxonomy_link' - -#list-taxonomies - = render partial: 'list' diff --git a/app/views/spree/admin/taxonomies/new.html.haml b/app/views/spree/admin/taxonomies/new.html.haml deleted file mode 100644 index a84338ddf1..0000000000 --- a/app/views/spree/admin/taxonomies/new.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -= render partial: 'spree/admin/shared/configuration_menu' - -- content_for :page_title do - = t("spree.new_taxonomy") - -- content_for :page_actions do - %li - = button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left' - -= form_for [:admin, @taxonomy] do |f| - = render partial: 'form', locals: { f: f } - %fieldset.no-border-top - %br/ - .filter-actions.actions - = button t("spree.create"), 'icon-ok' diff --git a/app/views/spree/admin/taxons/_form.html.haml b/app/views/spree/admin/taxons/_form.html.haml index 621b5eadf8..a14f351bae 100644 --- a/app/views/spree/admin/taxons/_form.html.haml +++ b/app/views/spree/admin/taxons/_form.html.haml @@ -6,12 +6,6 @@ %br/ = error_message_on :taxon, :name = text_field :taxon, :name, class: 'fullwidth' - = f.field_container :permalink_part do - = f.label :permalink_part, t(".permalink") - %span.required * - %br/ - = @taxon.permalink.split("/")[0...-1].join("/") + "/" - = text_field_tag :permalink_part, @permalink_part = f.field_container :meta_title do = f.label :meta_title, t(".meta_title") %br/ diff --git a/app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml b/app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml new file mode 100644 index 0000000000..6eb0c41014 --- /dev/null +++ b/app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml @@ -0,0 +1,4 @@ +- unless flash[:error] + = turbo_stream.remove(spree_dom_id(@taxon)) += turbo_stream.append "flashes" do + = render(partial: 'admin/shared/flashes', locals: { flashes: flash }) \ No newline at end of file diff --git a/app/views/spree/admin/taxons/edit.html.haml b/app/views/spree/admin/taxons/edit.html.haml index 1df6e7288f..25c2815d07 100644 --- a/app/views/spree/admin/taxons/edit.html.haml +++ b/app/views/spree/admin/taxons/edit.html.haml @@ -1,16 +1,16 @@ = render partial: 'spree/admin/shared/configuration_menu' - content_for :page_title do - = t("spree.taxonomy_edit") + = t(".title") - content_for :page_actions do %li - = button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left' + = button_link_to t("spree.admin.taxons.back_to_list"), admin_taxons_path, icon: 'icon-arrow-left' -- # Because otherwise the form would attempt to use to_param of @taxon -- form_url = admin_taxonomy_taxon_path(@taxonomy.id, @taxon.id) -= form_for [:admin, @taxonomy, @taxon], method: :put, url: form_url, html: { multipart: true } do |f| += form_with model: @taxon, url: admin_taxon_path(@taxon.id), + data: { turbo: true }, id: "edit-taxon-#{@taxon.id}", + method: :put, html: { multipart: true } do |f| = render partial: 'form', locals: { f: f } .form-buttons = button t('spree.actions.update'), 'icon-refresh' - = button_link_to t('spree.actions.cancel'), edit_admin_taxonomy_url(@taxonomy), icon: "icon-remove" + = button_link_to t('spree.actions.cancel'), admin_taxons_url, icon: "icon-remove" diff --git a/app/views/spree/admin/taxons/index.html.haml b/app/views/spree/admin/taxons/index.html.haml new file mode 100644 index 0000000000..c4f673421e --- /dev/null +++ b/app/views/spree/admin/taxons/index.html.haml @@ -0,0 +1,27 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t(".title") + +- content_for :page_actions do + %li + = button_link_to t(".new_taxon"), spree.new_admin_taxon_url, icon: 'icon-plus', id: 'admin_new_taxon_link' + +%table#listing_taxons.index + %colgroup + %col{style: "width: 85%"}/ + %col{style: "width: 15%"}/ + %thead + %tr + %th= t("spree.name") + %th.actions + %tbody + - @taxons.each do |taxon| + %tr{class: cycle('odd', 'even'), id: spree_dom_id(taxon)} + %td + = taxon.name + %td.actions + = link_to_edit taxon.id, no_text: true + = link_to '', admin_taxon_path(taxon.id), method: :delete, + class: "icon_link with-tip icon-trash no-text", + data: { turbo: true, turbo_method: :delete, turbo_confirm: t(:are_you_sure) } diff --git a/app/views/spree/admin/taxons/new.html.haml b/app/views/spree/admin/taxons/new.html.haml new file mode 100644 index 0000000000..38930fbcdf --- /dev/null +++ b/app/views/spree/admin/taxons/new.html.haml @@ -0,0 +1,14 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t(".title") + +- content_for :page_actions do + %li + = button_link_to t("spree.admin.taxons.back_to_list"), spree.admin_taxons_path, icon: 'icon-arrow-left' + += form_with model: @taxon, url: admin_taxons_path, data: { turbo: true }, html: { multipart: true } do |f| + = render partial: 'form', locals: { f: f } + .form-buttons + = button t('actions.create'), 'icon-ok' + = button_link_to t('actions.cancel'), admin_taxons_url, icon: "icon-remove" diff --git a/app/views/spree/shared/_line_item_name.html.haml b/app/views/spree/shared/_line_item_name.html.haml index 8b1e75de55..4d732055d2 100644 --- a/app/views/spree/shared/_line_item_name.html.haml +++ b/app/views/spree/shared/_line_item_name.html.haml @@ -2,5 +2,5 @@ = "#{line_item.product.name}" - unless line_item.product.name.include? line_item.name_to_display %span= "- #{line_item.name_to_display}" -- if line_item.options_text - = "(#{line_item.options_text})" +- if line_item.unit_to_display + = "(#{line_item.unit_to_display})" diff --git a/app/views/subscription_mailer/_summary_detail.html.haml b/app/views/subscription_mailer/_summary_detail.html.haml index a566b2396d..53706b2b5a 100644 --- a/app/views/subscription_mailer/_summary_detail.html.haml +++ b/app/views/subscription_mailer/_summary_detail.html.haml @@ -6,7 +6,7 @@ - separator = messages.values.any? ? ": " : ", " - orders.each_with_index do |order, i| - %a{ href: order_url(order) }>= order.number + %a{ href: spree.edit_admin_order_url(order) }>= order.number = separator if messages.values.any? || i < orders.count - 1 - if messages.values.any? = messages[order.id] || t(".no_message_provided") diff --git a/app/webpacker/controllers/column_preferences_controller.js b/app/webpacker/controllers/column_preferences_controller.js index 9b55a961dc..65202920f4 100644 --- a/app/webpacker/controllers/column_preferences_controller.js +++ b/app/webpacker/controllers/column_preferences_controller.js @@ -6,7 +6,7 @@ export default class ColumnPreferencesController extends Controller { connect() { this.table = document.querySelector('table[data-column-preferences-target="table"]'); this.cols = Array.from(this.table.querySelectorAll('col')); - this.colSpanCells = this.table.querySelectorAll('th[colspan],td[colspan]'); + this.colSpanCells = Array.from(this.table.querySelectorAll('th[colspan],td[colspan]')); // Initialise data-default-col-span this.colSpanCells.forEach((cell)=> { cell.dataset.defaultColSpan ||= cell.colSpan; @@ -19,6 +19,8 @@ export default class ColumnPreferencesController extends Controller { // On checkbox changed element.addEventListener("change", this.#showHideColumn.bind(this)); } + + this.#observeProductsTableRows(); } // private @@ -30,14 +32,39 @@ export default class ColumnPreferencesController extends Controller { this.table.classList.toggle(`hide-${name}`, !element.checked); // Reset cell colspans - const hiddenColCount = this.checkboxes.filter((checkbox)=> !checkbox.checked).length; for(const cell of this.colSpanCells) { - const span = parseInt(cell.dataset.defaultColSpan, 10) - hiddenColCount; - cell.colSpan = span; + this.#updateColSpanCell(cell); }; } #showHideElement(element, show) { element.style.display = show ? "" : "none"; } + + #observeProductsTableRows(){ + this.productsTableObserver = new MutationObserver((mutations, _observer) => { + const mutationRecord = mutations[0]; + + if(mutationRecord){ + const productRowElement = mutationRecord.addedNodes[0]; + + if(productRowElement){ + const newColSpanCell = productRowElement.querySelector('td[colspan]'); + newColSpanCell.dataset.defaultColSpan ||= newColSpanCell.colSpan; + this.#updateColSpanCell(newColSpanCell); + this.colSpanCells.push(newColSpanCell); + } + } + }); + + this.productsTableObserver.observe(this.table, { childList: true }); + } + + #hiddenColCount(){ + return this.checkboxes.filter((checkbox)=> !checkbox.checked).length; + } + + #updateColSpanCell(cell){ + cell.colSpan = parseInt(cell.dataset.defaultColSpan, 10) - this.#hiddenColCount(); + } } diff --git a/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js b/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js index 3c9e3d7b3d..7648563791 100644 --- a/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js +++ b/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js @@ -12,6 +12,9 @@ export const useOpenAndCloseAsAModal = (controller) => { }.bind(controller), close: function (_event, remove = false) { + // Only execute close if there is an open modal + if (!document.querySelector("body").classList.contains('modal-open')) return; + this.modalTarget.classList.remove("in"); this.backgroundTarget.classList.remove("in"); document.querySelector("body").classList.remove("modal-open"); diff --git a/app/webpacker/controllers/order_cycle_form_controller.js b/app/webpacker/controllers/order_cycle_form_controller.js new file mode 100644 index 0000000000..a92d621dec --- /dev/null +++ b/app/webpacker/controllers/order_cycle_form_controller.js @@ -0,0 +1,55 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ['statusMessage', 'cancel'] + connect() { + this.observer = new MutationObserver(this.updateCallback); + this.observer.observe( + this.statusMessageTarget, + { attributes: true, attributeOldValue: true, attributeFilter: ['data-type'] } + ); + + if (this.hasCancelTarget) { + this.cancelTarget.addEventListener('click', this.removeUnloadEvent) + } + } + + // Callback to trigger warning modal + updateCallback(mutationsList) { + const newDataType = document.getElementById('status-message').getAttribute('data-type'); + const actionName = document.getElementById('status-message').getAttribute('data-action-name'); + if(!actionName) return; + + for(let mutation of mutationsList) { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-type') { + // Only trigger warning modal when notice display (notice) is preceeded by progress display (progress) + if(mutation.oldValue === 'progress' && newDataType === 'notice') { + // Hide all confirmation buttons in warning modal + document.querySelectorAll( + '#linked-order-warning-modal .modal-actions button.secondary' + ).forEach((node) => { + node.style.display = 'none'; + }); + // Show the appropriate confirmation button, open warning modal, and return + document.querySelectorAll( + `#linked-order-warning-modal button[data-trigger-action=${actionName}]` + ).forEach((node) => { + node.style.display = 'block'; + }) + document.querySelector('.warning-modal button.modal-target-trigger').click(); + } + } + } + } + + removeUnloadEvent() { + window.removeEventListener('beforeunload', window.onBeforeUnloadHandler) + } + + disconnect() { + this.observer.disconnect(); + if (this.hasCancelTarget) { + this.cancelTarget.removeEventListener('click', this.removeUnloadEvent) + } + } +} diff --git a/app/webpacker/controllers/trixeditor_controller.js b/app/webpacker/controllers/trixeditor_controller.js index 506086fb2b..e386352361 100644 --- a/app/webpacker/controllers/trixeditor_controller.js +++ b/app/webpacker/controllers/trixeditor_controller.js @@ -2,9 +2,14 @@ import { Controller } from "stimulus"; export default class extends Controller { connect() { - window.addEventListener("trix-change", this.#trixChange); + this.element.addEventListener("trix-change", this.#trixChange); this.#trixInitialize(); - window.addEventListener("trix-initialize", this.#trixInitialize); + this.element.addEventListener("trix-initialize", this.#trixInitialize); + } + + disconnect() { + this.element.removeEventListener("trix-change", this.#trixChange); + this.element.removeEventListener("trix-initialize", this.#trixInitialize); } #trixChange = (event) => { diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index 254b188a78..a63e543f90 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -33,7 +33,6 @@ @import "plugins/flatpickr-customization"; @import "plugins/powertip"; -@import "plugins/jstree"; @import "plugins/select2"; @import "sections/orders"; diff --git a/app/webpacker/css/admin/order_cycles.scss b/app/webpacker/css/admin/order_cycles.scss index ea73266fb8..ace5c65498 100644 --- a/app/webpacker/css/admin/order_cycles.scss +++ b/app/webpacker/css/admin/order_cycles.scss @@ -62,3 +62,7 @@ form.order_cycle { } } } + +#linked-order-warning-modal .reveal-modal{ + width: 28rem; +} \ No newline at end of file diff --git a/app/webpacker/css/admin/plugins/jstree.scss b/app/webpacker/css/admin/plugins/jstree.scss deleted file mode 100644 index b16fd23a1a..0000000000 --- a/app/webpacker/css/admin/plugins/jstree.scss +++ /dev/null @@ -1,131 +0,0 @@ -#taxonomy_tree { - > ul, - .jstree-icon { - background-image: none; - } - - .jstree-icon { - @extend [class^="icon-"], :before; - } - - .jstree-open > .jstree-icon { - @extend .icon-caret-down; - } - .jstree-closed > .jstree-icon { - @extend .icon-caret-right; - } - - li { - background-image: none; - - a { - background-color: very-light($color-3); - border: 1px solid $color-border; - color: $color-body-text; - font-weight: $font-weight-bold; - text-shadow: none; - width: 90%; - height: auto; - line-height: inherit; - padding: 5px 0 5px 10px; - margin-bottom: 10px; - - .jstree-icon { - padding-left: 0px; - @extend .icon-move; - } - } - } -} - -#vakata-dragged.jstree-apple .jstree-invalid, -#vakata-dragged.jstree-apple .jstree-ok, -#jstree-marker { - background-image: none !important; - background-color: transparent !important; - @extend [class^="icon-"], :before; -} -#vakata-dragged.jstree-apple .jstree-invalid { - @extend .icon-remove; - color: $color-5; -} -#vakata-dragged.jstree-apple .jstree-ok { - @extend .icon-ok; - color: $color-2; -} - -#jstree-marker { - @extend .icon-caret-right; - color: $color-body-text !important; - width: 4px !important; -} - -#jstree-marker-line { - @include border-radius($border-radius !important); - height: 0px !important; - margin-left: 5px !important; - margin-top: -2px !important; - border: none !important; - border-bottom: 1px solid $color-body-text !important; - background-color: very-light($color-3) !important; - - -webkit-box-shadow: none !important; - -moz-box-shadow: none !important; - box-shadow: none !important; -} - -#vakata-contextmenu { - background-color: $color-3 !important; - -moz-box-shadow: none !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; - border: none !important; - @include border-radius($border-radius !important); - - &:before { - content: ""; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-bottom: 10px solid $color-3; - top: 0px; - margin-top: -10px; - left: 25px; - z-index: 1; - } - - a { - color: $color-1 !important; - line-height: inherit !important; - padding: 5px 10px !important; - margin: 0 !important; - font-size: 90% !important; - - &:hover { - @include border-radius($border-radius !important); - background-color: $color-2 !important; - border: none !important; - -moz-box-shadow: none !important; - -webkit-box-shadow: none !important; - line-height: inherit !important; - padding: 5px 10px !important; - margin: 0 !important; - } - } - - li:first-child a:hover:before { - content: ""; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-bottom: 10px solid $color-2; - top: 0px; - margin-top: -10px; - left: 25px; - z-index: 1; - } - - li.vakata-separator { - display: none; - } -} diff --git a/app/webpacker/css/admin/products_v3.scss b/app/webpacker/css/admin/products_v3.scss index 32dc4c5cda..912e940608 100644 --- a/app/webpacker/css/admin/products_v3.scss +++ b/app/webpacker/css/admin/products_v3.scss @@ -148,7 +148,7 @@ border-bottom: 2px solid $color-tbl-bg; &.with-image { - padding: 8px; + padding: 4px 2px; } } diff --git a/app/webpacker/css/admin_v3/all.scss b/app/webpacker/css/admin_v3/all.scss index 843204ec1b..a5c673b909 100644 --- a/app/webpacker/css/admin_v3/all.scss +++ b/app/webpacker/css/admin_v3/all.scss @@ -38,7 +38,6 @@ @import "plugins/flatpickr-customization"; // admin_v3 @import "plugins/powertip"; // admin_v3 -@import "../admin/plugins/jstree"; @import "sections/orders"; // admin_v3 @import "../admin/sections/products"; @@ -131,3 +130,5 @@ @import "app/webpacker/css/admin/trix.scss"; @import "terms_of_service_banner"; // admin_v3 + +@import "pages/product_preview"; // admin_v3 diff --git a/app/webpacker/css/admin_v3/components/navigation.scss b/app/webpacker/css/admin_v3/components/navigation.scss index 4ef5d5a06c..70ea3cf8b9 100644 --- a/app/webpacker/css/admin_v3/components/navigation.scss +++ b/app/webpacker/css/admin_v3/components/navigation.scss @@ -1,11 +1,60 @@ // Navigation //--------------------------------------------------- + +@mixin menu-display { + display: flex; + flex-wrap: wrap; +} + +@mixin menu-link { + a { + display: inline-block; + padding: 16px 20px; + color: $color-9 !important; + text-align: center; + position: relative; + font-size: 14px; + font-weight: 600; + + &:hover { + color: $red !important; + + &:after { + content: ""; + position: absolute; + bottom: 0; + left: 20px; + right: 20px; + height: 3px; + background: $red; + } + } + + &.active { + @extend :hover; + } + } +} + .inline-menu { margin: 0; -webkit-margin-before: 0; -webkit-padding-start: 0; } +// tabs +/// use the same styling as #admin-menu via menu-display and menu-link mixins +dl.admin-tabs { + box-shadow: $box-shadow; + @include menu-display; + + dd { + width: auto; + padding: 0; + @include menu-link; + } +} + nav.menu { ul { list-style: none; @@ -95,33 +144,10 @@ nav.menu { } ul { - display: flex; - flex-wrap: wrap; + @include menu-display; li { - a { - display: inline-block; - padding: 16px 20px; - color: $color-9 !important; - text-align: center; - position: relative; - font-size: 14px; - font-weight: 600; - - &:hover { - color: $red !important; - - &:after { - content: ""; - position: absolute; - bottom: 0; - left: 20px; - right: 20px; - height: 3px; - background: $red; - } - } - } + @include menu-link; &.selected a { @extend a, :hover; diff --git a/app/webpacker/css/admin_v3/globals/variables.scss b/app/webpacker/css/admin_v3/globals/variables.scss index 41863422d4..6aa5247dd1 100644 --- a/app/webpacker/css/admin_v3/globals/variables.scss +++ b/app/webpacker/css/admin_v3/globals/variables.scss @@ -40,8 +40,8 @@ $color-tbl-thead-txt: $color-headers !default; $color-tbl-thead-bg: $light-grey !default; $color-tbl-border: $pale-blue !default; $padding-tbl-cell: 12px; -$padding-tbl-cell-condensed: 4px 12px; -$padding-tbl-cell-relaxed: 12px 12px; +$padding-tbl-cell-condensed: 4px 3px; +$padding-tbl-cell-relaxed: 8px 3px; // Button colors $color-btn-bg: $teal !default; diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss new file mode 100644 index 0000000000..4b7f7e3a6a --- /dev/null +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -0,0 +1,176 @@ +@import "../../darkswarm/branding"; +@import "../../darkswarm/mixins"; + +#product-preview { + // The frontend css is base on foundation-sites https://github.com/foundation/foundation-sites + // Below we copied the sections that are relevant to the product preview modal + + // from foundation-sites/scss/foundations/components/_types.scss + h1, + h2, + h3, + h4, + h5, + h6 { + color: #222222; + font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + font-style: normal; + font-weight: normal; + line-height: 1.4; + margin-bottom: 0.5rem; + margin-top: 0.2rem; + text-rendering: optimizeLegibility; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.4; + } + + h3 { + font-size: 1.6875rem; + } + + em, + i { + font-style: italic; + line-height: inherit; + } + + ul, + ol, + dl { + font-family: inherit; + font-size: 1rem; + line-height: 1.6; + list-style-position: outside; + margin-bottom: 1.25rem; + } + + .text-right { + text-align: right !important; + } + + // from foundation-sites/scss/foundations/components/_buttons.scss + button, + .button { + -webkit-appearance: none; + -moz-appearance: none; + border-radius: 0; + border-style: solid; + border-width: 0; + cursor: pointer; + font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + font-weight: normal; + line-height: normal; + margin: 0 0 1.25rem; + position: relative; + text-align: center; + text-decoration: none; + display: inline-block; + padding: 1rem 2rem 1.0625rem 2rem; + font-size: 1rem; + background-color: #008cba; + border-color: #007095; + color: #ffffff; + transition: background-color 300ms ease-out; + } + + // from foundation-sites/scss/foundations/components/_grid.scss + @media only screen and (min-width: 64.0625em) { + .column, + .columns { + position: relative; + padding-left: 0.9375rem; + padding-right: 0.9375rem; + float: left; + } + } + + .column + .column:last-child, + .column + .columns:last-child, + .columns + .column:last-child, + .columns + .columns:last-child { + float: right; + } + + .small-3 { + width: 25%; + } + + @media only screen and (min-width: 40.0625em) { + .medium-3 { + width: 20%; // original value 25% + } + } + + @media only screen and (min-width: 64.0625em) { + .large-3 { + width: 25%; + } + } + + @media only screen and (min-width: 64.0625em) { + .large-6 { + width: 50%; + } + } + + // from foundation-sites/scss/foundations/components/_global.scss + img { + display: inline-block; + vertical-align: middle; + } + + // Import frontend partials + + // Product details + @import "../../darkswarm/shop_partials/typography"; + @import "../../darkswarm/overrides"; + + .row { + margin: 0 auto; + width: 100%; + } + + @import "../../darkswarm/shop_partials/animations"; + @import "../../darkswarm/shop_partials/shop-filters"; + @import "../../darkswarm/shop-modals"; + @import "../../darkswarm/shop_partials/images"; + + // Shop + @import "../../darkswarm/shop_partials/shop-product-thumb"; + @import "../../darkswarm/shop_partials/shop-product-rows"; + @import "../../darkswarm/shop_partials/shop-inputs"; + @import "../../shared/question-mark-icon"; + + button.add-variant { + opacity: 0.5; + } + + .variant-remaining-stock { + opacity: 0.5; + } + + .question-mark-icon { + display: block; + } + + .tooltip { + @include joyride-content; + width: $joyride-width; + text-transform: none; + } + + .arrow { + background-color: $dynamic-blue; + } + + .columns { + margin-left: 0; + } +} diff --git a/app/webpacker/css/darkswarm/_shop-filters.scss b/app/webpacker/css/darkswarm/_shop-filters.scss index e4bceed102..f6de8c8536 100644 --- a/app/webpacker/css/darkswarm/_shop-filters.scss +++ b/app/webpacker/css/darkswarm/_shop-filters.scss @@ -1,102 +1,4 @@ - -@mixin filter-selector($base-clr, $border-clr, $hover-clr) { - &.inline-block, ul.inline-block { - display: inline-block; - } - - li { - display: inline-block; - - @include border-radius(0); - - padding: 0; - margin: 0 0.5rem 0.5rem 0; - - &:hover, &:focus { - background: transparent; - } - - &.active { - box-shadow: none; - } - - a, a.button { - display: block; - - @include border-radius(0.5em); - - border: 1px solid $border-clr; - padding: 0.5em 0.625em; - color: $base-clr; - font-size: 0.75em; - background: white; - margin: 0; - - i { - padding-left: 0.25rem; - } - - render-svg { - &, & svg { - width: 1rem; - height: 1rem; - float: left; - padding-right: 0.25rem; - - path { - @include csstrans; - - fill: $base-clr; - } - } - } - - &:hover, &:focus { - border-color: $hover-clr; - color: $hover-clr; - - render-svg { - svg { - path { - fill: $hover-clr; - } - } - } - } - - &.disabled { - opacity: 0.6; - - &:hover, &:focus { - border-color: $border-clr; - color: $base-clr; - - render-svg { - svg { - path { - fill: $base-clr; - } - } - } - } - } - - &.active, &.active:hover, &.active:focus { - border: 1px solid $base-clr; - background: $base-clr; - color: white; - - render-svg { - svg { - path { - fill: white; - } - } - } - } - } - } -} +@import "shop_partials/shop-filters"; // Alert when search, taxon, filter is triggered @@ -167,23 +69,3 @@ max-height: calc(100vh - #{$topbar-height}); overflow-y: auto; } - -.filter-shopfront { - &.taxon-selectors, &.property-selectors { - background: transparent; - } - - // Shopfront taxons - &.taxon-selectors { - @include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright); - } - - // Shopfront properties - &.property-selectors { - @include filter-selector(#666, #ccc, #777); - } - - ul { - margin: 0; - } -} diff --git a/app/webpacker/css/darkswarm/_shop-inputs.scss b/app/webpacker/css/darkswarm/_shop-inputs.scss index 98f4f7fecf..26c28c2fa9 100644 --- a/app/webpacker/css/darkswarm/_shop-inputs.scss +++ b/app/webpacker/css/darkswarm/_shop-inputs.scss @@ -15,81 +15,7 @@ // // They are not nested so that they can be used in modals. -.variant-quantity-inputs { - height: 2.5rem; - white-space: nowrap; -} - -button.add-variant, button.variant-quantity { - height: 2.5rem; - border-radius: 0; - background-color: $orange-500; - color: white; - // Override foundation button styles: - font-size: 1rem; - margin: 0; - padding: 0; - transition: none; - - &:hover { - background-color: $orange-600; - } - - &[disabled] { - background-color: $grey-400; - - &:hover, &:focus { - background-color: $grey-400; - } - } - &:nth-of-type(1) { - border-bottom-left-radius: 0.25em; - border-top-left-radius: 0.25em; - } - &:nth-last-of-type(1) { - border-top-right-radius: 0.25em; - border-bottom-right-radius: 0.25em; - } -} - -button.add-variant { - min-width: 7rem; - padding: 0 1em; - - &[disabled] { - &:hover, &:focus { - background-color: $orange-500; - } - } -} - -button.variant-quantity { - width: 2.25rem; - - &:nth-of-type(1):not(.bulk-buy):not(.bulk-buy-add) { - border-right: .1em solid $orange-400; - } -} - -.variant-quantity-display, .variant-remaining-stock { - font-size: 0.875em; - margin-top: 0.25em; - text-align: center; - width: 7rem; - display: inline-block; -} - -.variant-quantity-display { - visibility: hidden; - - &.visible { - visibility: visible; - } -} - -.variant-remaining-stock { - color: $red-500; -} +@import "shop_partials/shop-inputs"; button.bulk-buy.variant-quantity { background-color: transparent; diff --git a/app/webpacker/css/darkswarm/_shop-product-rows.scss b/app/webpacker/css/darkswarm/_shop-product-rows.scss index 0ab0a8b1fd..ddf25d6d51 100644 --- a/app/webpacker/css/darkswarm/_shop-product-rows.scss +++ b/app/webpacker/css/darkswarm/_shop-product-rows.scss @@ -1,201 +1,7 @@ .darkswarm { products { product { - // GENERAL LAYOUT - .row { - .columns { - padding-top: 0em; - padding-bottom: 0em; - line-height: 1.1; - } - } - - .shop-variants { - // product-thumb width + 1rem - padding-left: calc(22.222% + 1rem); - - @include breakpoint(phablet) { - padding-left: 0; - clear: left; - } - } - - // ROW VARIANTS - .row.variants { - margin: 0 0 1em 0; - - &.out-of-stock { - opacity: 0.2; - } - - .variant-name, - .total-price { - padding-top: .74em; - } - .variant-price { - padding-top: .65em; - } - - // Variant name - .variant-name { - padding-left: 0; - padding-right: 0; - - @include breakpoint(phablet) { - padding-left: 0.5rem; - } - - & > *:nth-child(n + 2) { - color: $grey-550; - font-size: 0.875rem; - font-style: italic; - line-height: normal; - } - } - - // Variant price - .variant-price { - white-space: nowrap; - @include breakpoint(phablet) { - padding-left: 1rem; - } - } - - .variant-unit-price { - color: $grey-700; - font-size: 0.85rem; - margin-top: 15px; - position: relative; - left: -1px; - } - - // Total price - .total-price { - padding-left: 0rem; - color: $disabled-med; - - .filled { - color: $med-drk-grey; - } - - @include breakpoint(phablet) { - display: none; - } - } - } - - // ROW SUMMARY - .summary { - margin-left: 0; - margin-right: 0; - margin-bottom: 1.25em; - background: #fff; - - .columns { - padding-top: 1em; - padding-bottom: 1em; - line-height: 1; - - @include breakpoint(tablet) { - padding-top: 0.65rem; - padding-bottom: 0.65rem; - } - } - - .summary-header { - // product-thumb width + 1rem - padding-left: calc(22.222% + 1rem); - padding-right: 1rem; - - @include breakpoint(phablet) { - padding-left: calc(33.333% + 1rem); - } - - .product-producer { - color: $grey-550; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - - a { - color: $teal-500; - - &:hover, &:focus, &:active { - color: $teal-600; - text-decoration: underline; - } - } - } - - h3 { - font-size: 1.3rem; - margin-top: 0.75rem; - margin-bottom: 0.6rem; - } - - h3 a { - color: $orange-500; - - &:hover, &:focus, &:active { - color: $orange-600; - text-decoration: underline; - } - } - - .product-description { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 0.75rem; - cursor: pointer; - // Force product description to be on one line - // and truncate with ellipsis - display: -webkit-box; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - overflow: hidden; - // line-clamp is not supported in Safari - // Trick to get overflow: hidden to work in old Safari - line-height: 1rem; - height: 1.75rem; - - > div { - margin-bottom: 1.5rem; // Equivalent to p (trix doesn't use p as separator by default, so emulate div as p to be backward compatible) - } - - @include trix-styles; - } - - .product-properties { - margin: .5em 0; - - li { - margin: 0 0.25rem 0.25rem 0; - - a { - padding: 0.1em 0.625em; - - cursor: auto; - - &.has-tip { - cursor: pointer; - font-weight: normal; - } - &:hover, &:focus { - border-color: #ccc; - } - } - - // Foundation doesn't show the nub on mobile. - // Repeating the style to show it here. - .nub { - border-color: transparent transparent #333333 transparent; - } - } - } - } - } + @import "shop_partials/shop-product-rows"; } } } diff --git a/app/webpacker/css/darkswarm/_shop-product-thumb.scss b/app/webpacker/css/darkswarm/_shop-product-thumb.scss index 9b51d7f881..d218a9c1ad 100644 --- a/app/webpacker/css/darkswarm/_shop-product-thumb.scss +++ b/app/webpacker/css/darkswarm/_shop-product-thumb.scss @@ -1,34 +1,7 @@ .darkswarm { products { product { - .product-thumb { - // Desktop: the product summary is nine columns wide. Use two - // for the image. 100% / 9 * 2 = 22.222% <= 192px - width: calc(22.222%); - float: left; - - // Mobile: the summary has full twelve columns and the image - // should take four of them. 100% / 12 * 4 = 33.333% <= 227px - @include breakpoint(phablet) { - width: calc(33.333%); - } - - // Make this an anchor for the bulk label. - position: relative; - - .product-thumb__bulk-label { - background-color: $grey-700; - color: white; - position: absolute; - right: 0; - top: .8em; - padding: .25em .5em; - } - - &:hover { - filter: brightness(96%); - } - } + @import "shop_partials/shop-product-thumb"; } } } diff --git a/app/webpacker/css/darkswarm/animations.scss b/app/webpacker/css/darkswarm/animations.scss index 9fdb17eec7..bfe0b5b681 100644 --- a/app/webpacker/css/darkswarm/animations.scss +++ b/app/webpacker/css/darkswarm/animations.scss @@ -275,11 +275,4 @@ product.animate-repeat { } } -@mixin csstrans { - -webkit-transition: all 300ms ease; - -moz-transition: all 300ms ease; - -ms-transition: all 300ms ease; - -o-transition: all 300ms ease; - transition: all 300ms ease; - -webkit-transform-style: preserve-3d; -} +@import "shop_partials/animations"; diff --git a/app/webpacker/css/darkswarm/images.scss b/app/webpacker/css/darkswarm/images.scss index 9019971bfb..5ac895be9e 100644 --- a/app/webpacker/css/darkswarm/images.scss +++ b/app/webpacker/css/darkswarm/images.scss @@ -1,23 +1,4 @@ -.product-img { - text-align: center; - - img { - padding: 0.3rem; - - // placeholder for when no product images - &.placeholder { - opacity: 0.35; - - @include breakpoint(desktop) { - display: none; - } - } - - @media only screen and (max-width: 1024px) { - margin: 0 0 0.5rem; - } - } -} +@import "shop_partials/images"; .hero-img { outline: 1px solid $disabled-bright; diff --git a/app/webpacker/css/darkswarm/shop_partials/_animations.scss b/app/webpacker/css/darkswarm/shop_partials/_animations.scss new file mode 100644 index 0000000000..ab06239fc8 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_animations.scss @@ -0,0 +1,8 @@ +@mixin csstrans { + -webkit-transition: all 300ms ease; + -moz-transition: all 300ms ease; + -ms-transition: all 300ms ease; + -o-transition: all 300ms ease; + transition: all 300ms ease; + -webkit-transform-style: preserve-3d; +} diff --git a/app/webpacker/css/darkswarm/shop_partials/_images.scss b/app/webpacker/css/darkswarm/shop_partials/_images.scss new file mode 100644 index 0000000000..a08504547c --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_images.scss @@ -0,0 +1,21 @@ +.product-img { + text-align: center; + + img { + padding: 0.3rem; + + // placeholder for when no product images + &.placeholder { + opacity: 0.35; + + @include breakpoint(desktop) { + display: none; + } + } + + @media only screen and (max-width: 1024px) { + margin: 0 0 0.5rem; + } + } +} + diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss new file mode 100644 index 0000000000..5a05979d7f --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss @@ -0,0 +1,119 @@ +@mixin filter-selector($base-clr, $border-clr, $hover-clr) { + &.inline-block, ul.inline-block { + display: inline-block; + } + + li { + display: inline-block; + + @include border-radius(0); + + padding: 0; + margin: 0 0.5rem 0.5rem 0; + + &:hover, &:focus { + background: transparent; + } + + &.active { + box-shadow: none; + } + + a, a.button { + display: block; + + @include border-radius(0.5em); + + border: 1px solid $border-clr; + padding: 0.5em 0.625em; + color: $base-clr; + font-size: 0.75em; + background: white; + margin: 0; + + i { + padding-left: 0.25rem; + } + + render-svg { + &, & svg { + width: 1rem; + height: 1rem; + float: left; + padding-right: 0.25rem; + + path { + @include csstrans; + + fill: $base-clr; + } + } + } + + &:hover, &:focus { + border-color: $hover-clr; + color: $hover-clr; + + render-svg { + svg { + path { + fill: $hover-clr; + } + } + } + } + + &.disabled { + opacity: 0.6; + + &:hover, &:focus { + border-color: $border-clr; + color: $base-clr; + + render-svg { + svg { + path { + fill: $base-clr; + } + } + } + } + } + + &.active, &.active:hover, &.active:focus { + border: 1px solid $base-clr; + background: $base-clr; + color: white; + + render-svg { + svg { + path { + fill: white; + } + } + } + } + } + } +} + +.filter-shopfront { + &.taxon-selectors, &.property-selectors { + background: transparent; + } + + // Shopfront taxons + &.taxon-selectors { + @include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright); + } + + // Shopfront properties + &.property-selectors { + @include filter-selector(#666, #ccc, #777); + } + + ul { + margin: 0; + } +} + diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss new file mode 100644 index 0000000000..65dbfaca77 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss @@ -0,0 +1,76 @@ +.variant-quantity-inputs { + height: 2.5rem; + white-space: nowrap; +} + +button.add-variant, button.variant-quantity { + height: 2.5rem; + border-radius: 0; + background-color: $orange-500; + color: white; + // Override foundation button styles: + font-size: 1rem; + margin: 0; + padding: 0; + transition: none; + + &:hover { + background-color: $orange-600; + } + + &[disabled] { + background-color: $grey-400; + + &:hover, &:focus { + background-color: $grey-400; + } + } + &:nth-of-type(1) { + border-bottom-left-radius: 0.25em; + border-top-left-radius: 0.25em; + } + &:nth-last-of-type(1) { + border-top-right-radius: 0.25em; + border-bottom-right-radius: 0.25em; + } +} + +button.add-variant { + min-width: 7rem; + padding: 0 1em; + + &[disabled] { + &:hover, &:focus { + background-color: $orange-500; + } + } +} + +button.variant-quantity { + width: 2.25rem; + + &:nth-of-type(1):not(.bulk-buy):not(.bulk-buy-add) { + border-right: .1em solid $orange-400; + } +} + +.variant-quantity-display, .variant-remaining-stock { + font-size: 0.875em; + margin-top: 0.25em; + text-align: center; + width: 7rem; + display: inline-block; +} + +.variant-quantity-display { + visibility: hidden; + + &.visible { + visibility: visible; + } +} + +.variant-remaining-stock { + color: $red-500; +} + diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss new file mode 100644 index 0000000000..1cf62b4263 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss @@ -0,0 +1,195 @@ +// GENERAL LAYOUT +.row { + .columns { + padding-top: 0em; + padding-bottom: 0em; + line-height: 1.1; + } +} + +.shop-variants { + // product-thumb width + 1rem + padding-left: calc(22.222% + 1rem); + + @include breakpoint(phablet) { + padding-left: 0; + clear: left; + } +} + +// ROW VARIANTS +.row.variants { + margin: 0 0 1em 0; + + &.out-of-stock { + opacity: 0.2; + } + + .variant-name, + .total-price { + padding-top: .74em; + } + .variant-price { + padding-top: .65em; + } + + // Variant name + .variant-name { + padding-left: 0; + padding-right: 0; + + @include breakpoint(phablet) { + padding-left: 0.5rem; + } + + & > *:nth-child(n + 2) { + color: $grey-550; + font-size: 0.875rem; + font-style: italic; + line-height: normal; + } + } + + // Variant price + .variant-price { + white-space: nowrap; + @include breakpoint(phablet) { + padding-left: 1rem; + } + } + + .variant-unit-price { + color: $grey-700; + font-size: 0.85rem; + margin-top: 15px; + position: relative; + left: -1px; + } + + // Total price + .total-price { + padding-left: 0rem; + color: $disabled-med; + + .filled { + color: $med-drk-grey; + } + + @include breakpoint(phablet) { + display: none; + } + } +} + +// ROW SUMMARY +.summary { + margin-left: 0; + margin-right: 0; + margin-bottom: 1.25em; + background: #fff; + + .columns { + padding-top: 1em; + padding-bottom: 1em; + line-height: 1; + + @include breakpoint(tablet) { + padding-top: 0.65rem; + padding-bottom: 0.65rem; + } + } + + .summary-header { + // product-thumb width + 1rem + padding-left: calc(22.222% + 1rem); + padding-right: 1rem; + + @include breakpoint(phablet) { + padding-left: calc(33.333% + 1rem); + } + + .product-producer { + color: $grey-550; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + + a { + color: $teal-500; + + &:hover, &:focus, &:active { + color: $teal-600; + text-decoration: underline; + } + } + } + + h3 { + font-size: 1.3rem; + margin-top: 0.75rem; + margin-bottom: 0.6rem; + } + + h3 a { + color: $orange-500; + + &:hover, &:focus, &:active { + color: $orange-600; + text-decoration: underline; + } + } + + .product-description { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 0.75rem; + cursor: pointer; + // Force product description to be on one line + // and truncate with ellipsis + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + // line-clamp is not supported in Safari + // Trick to get overflow: hidden to work in old Safari + line-height: 1rem; + height: 1.75rem; + + > div { + margin-bottom: 1.5rem; // Equivalent to p (trix doesn't use p as separator by default, so emulate div as p to be backward compatible) + } + + @include trix-styles; + } + + .product-properties { + margin: .5em 0; + + li { + margin: 0 0.25rem 0.25rem 0; + + a { + padding: 0.1em 0.625em; + + cursor: auto; + + &.has-tip { + cursor: pointer; + font-weight: normal; + } + &:hover, &:focus { + border-color: #ccc; + } + } + + // Foundation doesn't show the nub on mobile. + // Repeating the style to show it here. + .nub { + border-color: transparent transparent #333333 transparent; + } + } + } + } +} diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss new file mode 100644 index 0000000000..eb88dd7405 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss @@ -0,0 +1,28 @@ +.product-thumb { + // Desktop: the product summary is nine columns wide. Use two + // for the image. 100% / 9 * 2 = 22.222% <= 192px + width: calc(22.222%); + float: left; + + // Mobile: the summary has full twelve columns and the image + // should take four of them. 100% / 12 * 4 = 33.333% <= 227px + @include breakpoint(phablet) { + width: calc(33.333%); + } + + // Make this an anchor for the bulk label. + position: relative; + + .product-thumb__bulk-label { + background-color: $grey-700; + color: white; + position: absolute; + right: 0; + top: .8em; + padding: .25em .5em; + } + + &:hover { + filter: brightness(96%); + } +} diff --git a/app/webpacker/css/darkswarm/shop_partials/_typography.scss b/app/webpacker/css/darkswarm/shop_partials/_typography.scss new file mode 100644 index 0000000000..5160a1c074 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_typography.scss @@ -0,0 +1,31 @@ +@mixin headingFont { + font-family: "Oswald", sans-serif; +} + +// TODO should probably move that to a variables.scss +$body-font: "Roboto", Arial, sans-serif; + +h1, h2, h3, h4, h5, h6 { + @include headingFont; + + padding: 0px; +} + +a { + color: $clr-brick; + + &:hover, &:focus, &:active { + text-decoration: none; + color: $clr-brick-bright; + } +} + +.text-small { + font-size: 0.875rem; + margin-bottom: 0.5rem; + font-family: $body-font; + + &, & * { + font-size: 0.875rem; + } +} diff --git a/app/webpacker/css/darkswarm/split-checkout.scss b/app/webpacker/css/darkswarm/split-checkout.scss index 1fc90b9815..2088f97286 100644 --- a/app/webpacker/css/darkswarm/split-checkout.scss +++ b/app/webpacker/css/darkswarm/split-checkout.scss @@ -347,7 +347,6 @@ margin-top: 40px; .button.primary { - background-color: $clr-turquoise; &:hover { text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); } diff --git a/app/webpacker/css/darkswarm/typography.scss b/app/webpacker/css/darkswarm/typography.scss index 2261036d74..2740288f0c 100644 --- a/app/webpacker/css/darkswarm/typography.scss +++ b/app/webpacker/css/darkswarm/typography.scss @@ -1,14 +1,10 @@ - -@mixin headingFont { - font-family: "Oswald", sans-serif; -} +@import "shop_partials/typography"; @mixin bodyFont { font-family: "Roboto", Arial, sans-serif; } $headingFont: "Oswald"; -$body-font: "Roboto", Arial, sans-serif; body { @include bodyFont; @@ -16,15 +12,6 @@ body { font-weight: 400; } -a { - color: $clr-brick; - - &:hover, &:focus, &:active { - text-decoration: none; - color: $clr-brick-bright; - } -} - .text-vbig { font-size: 2rem; font-weight: 300; @@ -39,16 +26,6 @@ small, .small { font-size: 0.75rem; } -.text-small { - font-size: 0.875rem; - margin-bottom: 0.5rem; - font-family: $body-font; - - &, & * { - font-size: 0.875rem; - } -} - .text-normal { font-weight: 400; font-family: $body-font; @@ -92,12 +69,6 @@ small, .small { border-color: rgba(#ddd, 0.25); } -h1, h2, h3, h4, h5, h6 { - @include headingFont; - - padding: 0px; -} - .inline-header { display: inline-block; margin: 0px; diff --git a/app/webpacker/css/mail/all.scss b/app/webpacker/css/mail/all.scss deleted file mode 100644 index 087d2d1310..0000000000 --- a/app/webpacker/css/mail/all.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../admin/globals/palette.scss'; -@import 'email'; -@import 'payments_list'; diff --git a/app/webpacker/css/shared/question-mark-icon.scss b/app/webpacker/css/shared/question-mark-icon.scss index e8720d207c..932c897844 100644 --- a/app/webpacker/css/shared/question-mark-icon.scss +++ b/app/webpacker/css/shared/question-mark-icon.scss @@ -50,8 +50,19 @@ margin-left: 2px; } +@mixin joyride-content { + background-color: $dynamic-blue; + padding: $padding-small; + border-radius: $radius-small; + color: $white; + width: 100%; + font-size: 0.8rem; +} + +$joyride-width: 16rem; + .joyride-tip-guide.question-mark-tooltip { - width: 16rem; + width: $joyride-width; max-width: 65%; // JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS margin-left: -7.4rem; @@ -70,12 +81,7 @@ } .joyride-content-wrapper { - background-color: $dynamic-blue; - padding: $padding-small; - border-radius: $radius-small; - color: $white; - width: 100%; - font-size: 0.8rem; + @include joyride-content; } .joyride-nub.bottom { diff --git a/app/webpacker/js/turbo.js b/app/webpacker/js/turbo.js index 4451810c93..d8816b4d2a 100644 --- a/app/webpacker/js/turbo.js +++ b/app/webpacker/js/turbo.js @@ -1,5 +1,8 @@ import "@hotwired/turbo"; +import TurboPower from "turbo_power"; +TurboPower.initialize(Turbo.StreamActions); + document.addEventListener("turbo:frame-missing", (event) => { // don't replace frame contents event.preventDefault(); diff --git a/app/webpacker/packs/admin.js b/app/webpacker/packs/admin.js index 9ad33b1a1c..1625ce11bb 100644 --- a/app/webpacker/packs/admin.js +++ b/app/webpacker/packs/admin.js @@ -9,9 +9,6 @@ import "../js/moment"; import bigDecimal from "js-big-decimal"; window.bigDecimal = bigDecimal; -import debounced from "debounced"; -debounced.initialize({ input: { wait: 300 } }); - import Trix from "trix"; document.addEventListener("trix-before-initialize", (event) => { diff --git a/app/webpacker/packs/mail.scss b/app/webpacker/packs/mail.scss deleted file mode 100644 index d4f981e355..0000000000 --- a/app/webpacker/packs/mail.scss +++ /dev/null @@ -1 +0,0 @@ -@import "../css/mail/all.scss"; diff --git a/config/application.rb b/config/application.rb index 09d8fb5abe..a1bda0a7d5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -216,6 +216,7 @@ module Openfoodnetwork config.assets.precompile += ['web/all.js'] config.assets.precompile += ['darkswarm/all.js'] config.assets.precompile += ['shared/*'] + config.assets.precompile += ['mail.scss'] config.assets.precompile += ['*.jpg', '*.jpeg', '*.png', '*.gif' '*.svg'] # Highlight code that triggered database queries in logs. diff --git a/config/initializers/wicked_pdf.rb b/config/initializers/wicked_pdf.rb index d04e36dbf2..1ebccb6cc6 100644 --- a/config/initializers/wicked_pdf.rb +++ b/config/initializers/wicked_pdf.rb @@ -1,20 +1,13 @@ -if Rails.env.test? - Rails.application.reloader.to_prepare do - WickedPdf.config = { - #:wkhtmltopdf => '/usr/local/bin/wkhtmltopdf', - #:layout => "pdf.html", - :page_size => 'A3', - :exe_path => `bundle exec which wkhtmltopdf`.chomp - } - end -else - Rails.application.reloader.to_prepare do - WickedPdf.config = { - #:wkhtmltopdf => '/usr/local/bin/wkhtmltopdf', - #:layout => "pdf.html", - :page_size => 'A4', # default - :exe_path => `bundle exec which wkhtmltopdf`.chomp - } +WickedPdf.configure do |c| + c.exe_path = `bundle exec which wkhtmltopdf`.chomp + + if Rails.env.test? + # Conversion from PDF to text struggles with multi-line text. + # We avoid that by printing on bigger pages. + # https://github.com/openfoodfoundation/openfoodnetwork/pull/9674 + c.page_size = "A3" + else + c.page_size = "A4" end end diff --git a/config/locales/ar.yml b/config/locales/ar.yml index c762b981bb..eabd7e2769 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -84,7 +84,6 @@ ar: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "يجب أن تكون فارغة بسبب استخدام إعدادات المخزون للمنتج" - on_demand_but_count_on_hand_set: "يجب أن تكون فارغة إذا كانت عند الطلب" limited_stock_but_no_count_on_hand: "يجب ان تكون تحديد بسبب فرض مدودية المخزون" messages: blank: "لا يمكن أن تكون فارغة" @@ -483,6 +482,8 @@ ar: remove: إزالة image: edit: تعديل + product_preview: + shop_tab: المتجر adjustments: skipped_changing_canceled_order: "لا يمكنك تغيير الطلب الذي تم إلغاؤه." begins_at: يبدأ عند @@ -686,7 +687,6 @@ ar: variants: infinity: "ما لا نهاية" to_order_tip: "لا تحتوي المواد التي تريد طلبها على مستوى مخزون محدد ، مثل أرغفة الخبز المصنوعة طازجة حسب الطلب." - back_to_products_list: "العودة إلى قائمة المنتجات" editing_product: "تحرير المنتج" tabs: product_details: "تفاصيل المنتج" @@ -1198,10 +1198,8 @@ ar: contact_name: اسم جهة الاتصال edit: editing: 'الإعدادات:' - back_link: العودة إلى قائمة المؤسسات new: title: شركة جديدة - back_link: العودة إلى قائمة المؤسسات welcome: welcome_title: مرحبا بكم في شبكة الغذاء المفتوح الاردن - فلاحة جو ! welcome_text: لقد نجحت في إنشاء @@ -1238,6 +1236,8 @@ ar: choose_products_from: "اختر المنتجات من:" re_notify_producers: إعادة إخطار المنتجين notify_producers_tip: سيؤدي هذا إلى إرسال بريد إلكتروني إلى كل منتج مع قائمة طلباتهم. + date_time_warning_modal_content: + cancel: 'إلغاء' incoming: incoming: "الوارد" supplier: "المورد" @@ -2651,7 +2651,6 @@ ar: spree_admin_single_enterprise_hint: "لمحة: للسماح للأشخاص بالعثور عليك ، قم بتشغيل الرؤية الخاصة بك أسفل" spree_admin_eg_pickup_from_school: "على سبيل المثال. "البيك اب من المدرسة الابتدائية"" spree_admin_eg_collect_your_order: "على سبيل المثال. "يرجى جمع طلبك من 123 Imaginary St، Northcote ، 3070"" - spree_classification_primary_taxon_error: "Taxon %{taxon} هو التصنيف الأساسي %{product} ولا يمكن حذفه" spree_order_availability_error: "لا يمكن لموزع أو دورة طلب توفير المنتجات في سلة التسوق" spree_order_populator_error: "لا يمكن لهذا الموزع أو دورة الطلب توفير جميع المنتجات في سلة التسوق. الرجاء اختيار آخر." spree_order_cycle_error: "الرجاء اختيار دورة الطلب لهذا الطلب." @@ -2964,7 +2963,7 @@ ar: order_cycles_no_permission_to_coordinate_error: "لا تملك أي من مؤسساتك إذنًا لتنسيق دورة الطلب" order_cycles_no_permission_to_create_error: "ليس لديك إذن لإنشاء دورة طلب تنسقها تلك المؤسسة" order_cycle_closed: "تم إغلاق دورة الطلب التي حددتها للتو. حاول مرة اخرى!" - back_to_orders_list: "العودة إلى قائمة الطلب" + back_to_orders_list: "العودة إلى قائمة الطلبات" no_orders_found: "لم يتم العثور على أية طلبات" order_information: "معلومات الطلب" new_payment: "دفعة جديد" @@ -3300,6 +3299,8 @@ ar: سيؤدي ذلك إلى ضبط مستوى المخزون لصفر على جميع المنتجات المؤسسة غير موجودة في الملف الذي تم تحميله. order_cycles: + unsaved_changes: "لم تحفظ التغييرات" + bulk_save_error: "عذرا لا! لم أستطع حفظ تغييراتك." create_failure: "فشل في تكوين دورة الطلب" update_success: 'تم تحديث دورة الطلب.' update_failure: "فشل في تحديث دورة الطلب" @@ -3617,7 +3618,6 @@ ar: more: "أكثر" new_adjustment: "تعديل جديد" new_tax_category: "فئة ضريبية جديدة" - new_taxon: "اصنوفة جديدة" new_user: "مستخدم جديد" no_pending_payments: "لا توجد دفعات معلقة" remove: "إزالة" @@ -3636,8 +3636,6 @@ ar: delivery: "موقعة ومختومة وتم التسليم" start_date: "تاريخ البدء" successfully_removed: "تمت الإزالة بنجاح" - taxonomy_edit: "التصنيف" - tree: "شجرة" updating: "تحديث" your_order_is_empty_add_product: "طلبك فارغ ، يرجى البحث عن المنتج أعلاه وإضافته" add_product: "أضف منتج" @@ -3733,7 +3731,6 @@ ar: tax_rate_amount_explanation: "معدلات الضرائب هي مبلغ عشري للمساعدة في العمليات الحسابية ، (أي إذا كان معدل الضريبة 5٪ ، فأدخل 0.05)" included_in_price: "المدرجة في السعر" show_rate_in_label: "عرض السعر في التسمية" - back_to_tax_rates_list: "العودة إلى قائمة أسعار الضرائب" tax_settings: "الإعدادات الضريبية" zones: "مناطق" new_zone: "منطقة جديدة" @@ -3746,14 +3743,11 @@ ar: iso_name: "اسم شهادة ISO" states_required: "المنطقة مطلوبة" editing_country: "تحرير الدولة" - back_to_countries_list: "العودة إلى قائمة الدول" states: "محافظات" abbreviation: "الاختصار" new_state: "محافظة جديدة" payment_methods: "طرق الدفع" - taxonomies: "التصنيفات" - new_taxonomy: "تصنيف جديد" - back_to_taxonomies_list: "العودة إلى قائمة التصنيفات" + taxons: "فئات المنتجات" shipping_methods: "طرق الشحن" shipping_method: "طريقة الشحن" shipment: "الشحنة" @@ -3910,7 +3904,6 @@ ar: continue: "تابع" new: new_return_authorization: "عودة الترخيص جديد" - back_to_return_authorizations_list: "العودة إلى قائمة عودة الترخيص" continue: "تابع" edit: receive: "استلام" @@ -4218,9 +4211,10 @@ ar: total: "المجموع" billing_address_name: "الاسم" taxons: + index: + title: "فئات المنتجات" form: name: الاسم - permalink: الرابط الثابت description: وصف general_settings: edit: @@ -4458,7 +4452,6 @@ ar: key_cleared: "تم مسح المفتاح" shipment: cannot_ready: "لا يمكن تجهيز الشحنة" - invalid_taxonomy_id: "معرف التصنيف غير صالح." toggle_api_key_view: "إظهار عرض مفتاح API للمستخدم" activerecord: models: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 6e1732d504..dd8bf38bce 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -86,7 +86,6 @@ ca: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "ha d'estar en blanc perquè s'utilitza la configuració d'estoc de la productora" - on_demand_but_count_on_hand_set: "ha d'estar en blanc si és sota demanda" limited_stock_but_no_count_on_hand: "cal especificar-se perquè força existències limitades" messages: blank: "no es pot deixar en blanc" @@ -511,6 +510,8 @@ ca: remove: Eliminar image: edit: Editar + product_preview: + shop_tab: Botiga adjustments: skipped_changing_canceled_order: "No podeu canviar una comanda cancel·lada." begins_at: Comença a @@ -717,7 +718,6 @@ ca: variants: infinity: "Infinit" to_order_tip: "Els articles preparats per encàrrec no tenen un nivell fixat d'existències, com ara pa fet sota comanda." - back_to_products_list: "Torna a la llista de productes" editing_product: "Editant el producte" tabs: product_details: "Detalls del producte" @@ -755,7 +755,6 @@ ca: search: Cerca sort: pagination: - total_html: "%{total} productes trobats pels vostres criteris de cerca. Mostrant de %{from} a %{to} ." per_page: show: Mostra per_page: "%{num} per pàgina" @@ -1253,10 +1252,8 @@ ca: contact_name: Nom de contacte edit: editing: 'Configuració:' - back_link: Tornar a la llista d'organitzacions new: title: Nova organització - back_link: 'Tornar a la llista d''organitzacions ' welcome: welcome_title: Benvingut a Katuma - Open Food Network! welcome_text: Heu creat correctament un @@ -1292,6 +1289,8 @@ ca: save_and_back_to_list: "Desa i torna a la llista" choose_products_from: "Trieu Productes des de:" notify_producers_tip: Això enviarà un correu electrònic a cada productor amb la llista dels seus productes demanats a les comandes. + date_time_warning_modal_content: + cancel: 'Cancel·lar' incoming: incoming: "Entrant" supplier: "Proveïdora" @@ -2656,7 +2655,6 @@ ca: spree_admin_single_enterprise_hint: "Suggeriment: per permetre que la gent us trobi, activeu la vostra visibilitat" spree_admin_eg_pickup_from_school: "p. ex: 'Recollida al local del grup de consum'" spree_admin_eg_collect_your_order: "p. ex: \"Recolliu la vostra comanda al c/Ample, n. 123'" - spree_classification_primary_taxon_error: "El taxó %{taxon} és el principal taxó d'%{product} i no es pot eliminar" spree_order_availability_error: "La distribuïdora o el cicle de comanda no pot subministrar els productes de la vostra cistella" spree_order_populator_error: "Aquesta distribuïdora o cicle de comanda no pot subministrar tots els productes de la vostra cistella. Si us plau trieu-ne d'altres." spree_order_cycle_error: "Seleccioneu un cicle per a aquesta comanda." @@ -2947,7 +2945,7 @@ ca: order_cycles_no_permission_to_coordinate_error: "Cap de les vostres organitzacions té permís per coordinar un cicle de comanda" order_cycles_no_permission_to_create_error: "No teniu permís per crear un cicle de comandes coordinat per aquesta organització" order_cycle_closed: "El cicle de comandes que has triat acaba de tancar. Sisplau prova en un altre moment." - back_to_orders_list: "Torna a la llista de comandes" + back_to_orders_list: "Tornar a la llista de comandes" no_orders_found: "No s'han trobat comandes" order_information: "Informació de la comanda" new_payment: "Nou pagament" @@ -3281,6 +3279,8 @@ ca: Això establirà el nivell d'estoc a zero en tots els productes per aquesta organització que no estan presents al fitxer carregat. order_cycles: + unsaved_changes: "Teniu canvis sense desar" + bulk_save_error: "Ah no! No he pogut desar els canvis." create_failure: "No s'ha pogut crear el cicle de comanda" update_success: 'S''ha actualitzat el cicle de comanda.' update_failure: "No s'ha pogut actualitzar el cicle de comanda" @@ -3494,7 +3494,6 @@ ca: more: "Més" new_adjustment: "Nou ajustament" new_tax_category: "Nova categoria fiscal" - new_taxon: "Nou tàxon" new_user: "Nou usuari" no_pending_payments: "No hi ha pagaments pendents" remove: "Eliminar" @@ -3513,8 +3512,6 @@ ca: delivery: "Signat, segellat, lliurat" start_date: "Data d'inici" successfully_removed: "S'ha suprimit correctament" - taxonomy_edit: "Edició de taxonomia" - tree: "Arbre" updating: "Actualitzant" your_order_is_empty_add_product: "La comanda està buida, si us plau cerca i afegeix un producte a dalt" add_product: "Afegeix un producte" @@ -3611,7 +3608,6 @@ ca: tax_rate_amount_explanation: "Les taxes impositives són un import decimal per ajudar en els càlculs (és a dir, si el tipus impositiu és del 5%, introduïu 0,05)" included_in_price: "Inclòs al preu" show_rate_in_label: "Mostra l'impost a l'etiqueta" - back_to_tax_rates_list: "Tornar a la llista d'impostos" tax_settings: "Configuració fiscal" zones: "Zones" new_zone: "Nova zona" @@ -3624,14 +3620,11 @@ ca: iso_name: "Nom ISO" states_required: "Estats obligatoris" editing_country: "Editar el país" - back_to_countries_list: "Torna a la llista de països" states: "Estats" abbreviation: "Abreviatura" new_state: "Nou estat" payment_methods: "Mètodes de Pagament" - taxonomies: "Taxonomies" - new_taxonomy: "Nova taxonomia" - back_to_taxonomies_list: "Torna a la llista de taxonomies" + taxons: "Tipus de productes" shipping_methods: "Mètodes d'enviament" shipping_method: "Mètode d'enviament" shipment: "Enviament" @@ -3785,7 +3778,6 @@ ca: continue: "Continua" new: new_return_authorization: "Nova autorització de devolució" - back_to_return_authorizations_list: "Tornar a la llista d'autorització" continue: "Continua" edit: receive: "rebre" @@ -4021,6 +4013,7 @@ ca: product_name: Nom del producte primary_taxon_form: product_category: Categoria del producte + search_for_categories: "Cercar categories" group_buy_form: group_buy: "Compra en grup?" bulk_unit_size: Mida de la unitat a granel @@ -4096,9 +4089,10 @@ ca: total: "Total" billing_address_name: "Nom" taxons: + index: + title: "Tipus de productes" form: name: Nom - permalink: Enllaç permanent description: Descripció general_settings: edit: @@ -4326,7 +4320,6 @@ ca: key_cleared: "La clau esborrada" shipment: cannot_ready: "No es pot fer l'enviament." - invalid_taxonomy_id: "Identificador de taxonomia no vàlid." activerecord: models: spree/payment: diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 9b111b2005..24eea9cbe6 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -81,8 +81,6 @@ cy: white_label_logo_link: "Defnyddiwyd dolen i’r logo yn ffrynt y siop" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Mae etifeddu’r categori treth yn golygu bod angen cyfrifiannell fesul eitem." spree/user: attributes: email: @@ -102,7 +100,6 @@ cy: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "rhaid bod yn wag oherwydd defnydd o osodiadau stoc cynhyrchwyr" - on_demand_but_count_on_hand_set: "rhaid iddo fod yn wag os ar alw" limited_stock_but_no_count_on_hand: "rhaid nodi hyn oherwydd gorfodi stoc gyfyngedig" messages: confirmation: "Nid yw’n cyfateb %{attribute}" @@ -470,7 +467,7 @@ cy: password_confirmation: Cadarnhau Cyfrinair reset_password_token: Ailosod y cyfrinair expired: wedi dod i ben, gofynnwch am un newydd - back_to_payments_list: "Yn ôl i'r Rhestr Taliadau" + back_to_payments_list: "Yn ôl at y Rhestr Taliadau" maestro_or_solo_cards: "Cardiau Maestro / Solo" backordered: "Ol-archebwyd" on_hand: "Ar gael" @@ -541,6 +538,8 @@ cy: remove: Dileu image: edit: Golygu + product_preview: + shop_tab: Siop adjustments: skipped_changing_canceled_order: "Nid yw'n bosib newid archeb a ganslwyd" begins_at: Yn dechrau am @@ -747,7 +746,6 @@ cy: variants: infinity: "Anfeidredd" to_order_tip: "Nid oes gan eitemau a gynhyrchir ar gyfer archeb lefel stoc benodol, megis torthau o fara a gynhyrchir yn ffres ar gyfer archeb." - back_to_products_list: "Yn ôl i'r rhestr cynnyrch" editing_product: "Golygu cynnyrch" tabs: product_details: "Manylion Cynnyrch" @@ -785,7 +783,6 @@ cy: search: Chwilio sort: pagination: - total_html: "%{total}Cafwyd hyd i cynnyrch ar gyfer eich meini prawf chwilio. Yn dangos %{from} i %{to}." per_page: show: Dangos per_page: "%{num} fesul tudalen" @@ -1315,10 +1312,8 @@ cy: contact_name: Enw Cyswllt edit: editing: 'Gosodiadau:' - back_link: Yn ôl i'r rhestr mentrau new: title: Menter Newydd - back_link: Yn ôl i'r rhestr mentrau welcome: welcome_title: Croeso i'r Open Food Network! welcome_text: 'Rydych chi wedi llwyddo i greu ' @@ -1355,6 +1350,8 @@ cy: choose_products_from: "Dewis Cynnyrch gan:" re_notify_producers: Ail-hysbysu cynhyrchwyr. notify_producers_tip: Trwy hyn anfonir ebost at bob cynhyrchydd gyda rhestr o'r archebion. + date_time_warning_modal_content: + cancel: 'Canslo' incoming: incoming: "Yn dod i mewn" supplier: "Cyflenwr" @@ -2799,7 +2796,6 @@ cy: spree_admin_single_enterprise_hint: "Awgrym: Er mwyn caniatáu i bobl ddod o hyd i chi, gofalwch fod pobl yn gallu eich gweld" spree_admin_eg_pickup_from_school: "e.e. 'Casglu o'r Ysgol Gynradd'" spree_admin_eg_collect_your_order: "e.e. 'Dylid casglu eich archeb o 123 Ffordd y Don, Pwllheli'" - spree_classification_primary_taxon_error: "Tacson %{taxon} yw prif dacson %{product} ac ni ellir ei ddileu" spree_order_availability_error: "Ni all dosbarthwr neu gylch archebu gyflenwi'r cynnyrch yn eich basged" spree_order_populator_error: "Ni all y dosbarthwr neu'r cylch archebu dan sylw gyflenwi'r holl gynnyrch yn eich basged. Dewiswch un arall." spree_order_cycle_error: "Dewiswch gylch archebu ar gyfer archeb hwn." @@ -3119,7 +3115,7 @@ cy: order_cycles_no_permission_to_coordinate_error: "Nid oes gan unrhyw un o'ch mentrau ganiatâd i gydlynu cylch archebu" order_cycles_no_permission_to_create_error: "Nid oes gennych ganiatâd i greu cylch archebu a gydlynwyd gan y fenter honno" order_cycle_closed: "Mae'r cylch archebu a ddewiswyd newydd gau. Rhowch gynnig arall!" - back_to_orders_list: "Yn ôl i'r rhestr archebu" + back_to_orders_list: "Yn ôl i'r Rhestr Archebion" no_orders_found: "Ni ddaethpwyd o hyd i archebion" order_information: "Gwybodaeth Archebu" new_payment: "Taliad Newydd" @@ -3462,6 +3458,8 @@ cy: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "Mae gennych chi newidiadau heb eu cadw" + bulk_save_error: "O na! Nid oeddyn bosib cadw eich newidiadau." create_failure: "Wedi methu creu cylch archebu" update_success: 'Diweddarwyd eich cylch archebu' update_failure: "Wedi methu diweddaru'r cylch archebu" @@ -3732,7 +3730,6 @@ cy: more: "Mwy" new_adjustment: "Addasiad newydd" new_tax_category: "Categori treth newydd" - new_taxon: "Tacson newydd" new_user: "Defnyddiwr newydd" no_pending_payments: "Dim taliadau yn yr arfaeth" remove: "Dileu" @@ -3751,8 +3748,6 @@ cy: delivery: "Llofnodwyd, cadarnhawyd a chyflenwyd" start_date: "Dyddiad cychwyn" successfully_removed: "Llwyddwyd i ddileu" - taxonomy_edit: "Golygu tacsonomi" - tree: "Coeden" updating: "Yn diweddaru" your_order_is_empty_add_product: "Mae eich archeb yn wag, chwiliwch am ac ychwanegwch gynnyrch uchod" add_product: "Ychwanegu Cynnyrch" @@ -3849,7 +3844,6 @@ cy: tax_rate_amount_explanation: "Mae cyfraddau treth yn swm degol i helpu gyda chyfrifiadau, (h.y. os yw'r gyfradd dreth yn 5% yna nodwch 0.05)" included_in_price: "Wedi'i gynnwys yn y Pris" show_rate_in_label: "Dangos cyfradd yn y label" - back_to_tax_rates_list: "Yn ôl i'r Rhestr Cyfraddau Treth" tax_settings: "Gosodiadau Treth" zones: "Parthau" new_zone: "Parth Newydd" @@ -3862,14 +3856,11 @@ cy: iso_name: "Enw ISO" states_required: "Angen y Siroedd" editing_country: "Yn golygu'r wlad " - back_to_countries_list: "Yn ôl i'r Rhestr Gwledydd" states: "Siroedd" abbreviation: "Talfyriad" new_state: "Sir Newydd" payment_methods: "Dulliau Talu" - taxonomies: "Tacsonomeg" - new_taxonomy: "Tacsonomeg Newydd" - back_to_taxonomies_list: "Yn ôl i'r Rhestr Tacsonomeg" + taxons: "Categorïau Cynnyrch" shipping_methods: "Dulliau Anfon" shipping_method: "Dull Anfon" shipment: "Llwyth" @@ -4029,7 +4020,6 @@ cy: continue: "Parhau" new: new_return_authorization: "Awdurdodi Dychwelyd nwyddau newydd" - back_to_return_authorizations_list: "Yn ôl i'r rhestr awdurdodi dychwelyd nwyddau" continue: "Parhau" edit: receive: "derbyn" @@ -4273,6 +4263,7 @@ cy: product_name: Enw Cynnyrch primary_taxon_form: product_category: Categori Cynnyrch + search_for_categories: "Chwilio am gategorïau" group_buy_form: group_buy: "Prynu fel Grŵp?" bulk_unit_size: Maint uned swmp @@ -4350,13 +4341,15 @@ cy: total: "Cyfanswm" billing_address_name: "Enw" taxons: + index: + title: "Categorïau Cynnyrch" form: name: Enw - permalink: Permalink meta_title: Meta Title meta_description: Meta Description meta_keywords: Meta Keywords description: Disgrifiad + dfc_id: DFC URI general_settings: edit: legal_settings: "Gosodiadau Cyfreithiol" @@ -4593,7 +4586,6 @@ cy: key_cleared: "Cliriwyd yr Allwedd" shipment: cannot_ready: "Methu â pharatoi'r llwyth." - invalid_taxonomy_id: "Tacsonomeg id. annilys" toggle_api_key_view: "Dangos gwedd yr allwedd API ar gyfer y defnyddiwr." activerecord: models: diff --git a/config/locales/de_CH.yml b/config/locales/de_CH.yml index 3606cc8eee..d99c0dd4fa 100644 --- a/config/locales/de_CH.yml +++ b/config/locales/de_CH.yml @@ -72,7 +72,6 @@ de_CH: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "muss leer sein, da die Einstellungen des Produzentenbestands verwendet werden" - on_demand_but_count_on_hand_set: "muss leer sein, wenn unbegrenzt verfügbar oder die Produktion auf Bestellung erfolgt" limited_stock_but_no_count_on_hand: "muss angegeben werden, da nur begrenzte Lagerbestände verfügbar sind" messages: blank: "darf nicht leer sein" @@ -475,6 +474,8 @@ de_CH: remove: Löschen image: edit: Bearbeiten + product_preview: + shop_tab: Laden adjustments: skipped_changing_canceled_order: "Eine stornierte Bestellung kann nicht geändert werden." begins_at: Beginnt @@ -673,7 +674,6 @@ de_CH: variants: infinity: "Unbegrenzt" to_order_tip: "Artikel, die auf Anfrage hergestellt werden, haben keinen festgelegten Lagerbestand." - back_to_products_list: "Zurück zur Produktliste" editing_product: "Produkt bearbeiten" tabs: product_details: "Produktdetails" @@ -1164,10 +1164,8 @@ de_CH: contact_name: Kontaktname edit: editing: 'Einstellungen:' - back_link: Zurück zur Unternehmensliste new: title: Neues Unternehmen - back_link: Zurück zur Unternehmensliste welcome: welcome_title: Willkommen im Open Food Schweiz! welcome_text: 'Erfolgreich erstellt:' @@ -1204,6 +1202,8 @@ de_CH: choose_products_from: "Wählen Sie Produkte von:" re_notify_producers: Produzenten erneut benachrichtigen notify_producers_tip: Benachrichtigen Sie die Produzenten per E-Mail über in diesem Bestellzyklus erhaltene Bestellungen. + date_time_warning_modal_content: + cancel: 'Stornieren' incoming: incoming: "Eingehend" supplier: "Lieferant" @@ -2574,7 +2574,6 @@ de_CH: spree_admin_single_enterprise_hint: "Tipp: Damit andere Nutzer Sie finden können, aktivieren Sie Ihre Sichtbarkeit unter:" spree_admin_eg_pickup_from_school: "z. B. \"Abholung von der Grundschule\"" spree_admin_eg_collect_your_order: "z. B. 'Bitte holen Sie Ihre Bestellung in der Gartenstrasse 123 in 30701 Nordwestheim ab.'" - spree_classification_primary_taxon_error: "Kategorie %{taxon} ist die primäre Kategorie von %{product} und kann nicht gelöscht werden." spree_order_availability_error: "Verteilstelle oder Bestellzyklus kann die Produkte in Ihrem Warenkorb nicht liefern" spree_order_populator_error: "Diese Verteilstelle oder dieser Bestellzyklus kann nicht alle Produkte aus Ihrem Warenkorb liefern. Bitte ändern Sie Ihre Auswahl." spree_order_cycle_error: "Bitte wählen Sie einen Bestellzyklus für diese Bestellung aus." @@ -3210,6 +3209,8 @@ de_CH: Dadurch wird der Lagerbestand für alle Produkte dieses Unternehmens, die in der hochgeladenen Datei nicht vorhanden sind, auf Null gesetzt. order_cycles: + unsaved_changes: "Sie haben nicht gespeicherte Änderungen." + bulk_save_error: "Ihre Änderungen konnten leider nicht gespeichert werden." create_failure: "Fehler beim Erstellen des Bestellzyklus." update_success: 'Ihr Bestellzyklus wurde aktualisiert.' update_failure: "Fehler beim Aktualisieren des Bestellzyklus." @@ -3423,7 +3424,6 @@ de_CH: more: "Mehr" new_adjustment: "Neue Anpassung" new_tax_category: "Neue Steuerkategorie" - new_taxon: "Neue Kategorie" new_user: "Neuer Benutzer" no_pending_payments: "Keine ausstehenden Zahlungen." remove: "Löschen" @@ -3440,8 +3440,6 @@ de_CH: UPS Ground: "UPS Ground" start_date: "Anfangsdatum" successfully_removed: "Erfolgreich gelöscht" - taxonomy_edit: "Kategorien bearbeiten" - tree: "Struktur" updating: "Aktualisierung" your_order_is_empty_add_product: "Ihre Bestellung ist leer. Suchen Sie oben ein Produkt und fügen Sie es hinzu." add_product: "Produkt hinzufügen" @@ -3536,7 +3534,6 @@ de_CH: tax_rate_amount_explanation: "Eingabe als Dezimalbetrag (d. h. bei Steuersatz 5 %, geben Sie 0.05 ein)." included_in_price: "Im Preis enthalten" show_rate_in_label: "Steuersatz im Namen anzeigen" - back_to_tax_rates_list: "Zurück zur Liste der Steuersätze" tax_settings: "Steuereinstellungen" zones: "Zonen" new_zone: "Neue Zone" @@ -3549,14 +3546,11 @@ de_CH: iso_name: "ISO-Name" states_required: "Staaten/Bundesländer/Regionen erforderlich" editing_country: "Land bearbeiten" - back_to_countries_list: "Zurück zur Länderliste" states: "Bundesländer" abbreviation: "Abkürzung" new_state: "Neuer Kanton" payment_methods: "Zahlungsarten" - taxonomies: "Kategorien" - new_taxonomy: "Neue Kategorie" - back_to_taxonomies_list: "Zurück zur Kategorieliste" + taxons: "Produktkategorien" shipping_methods: "Lieferoptionen" shipping_method: "Lieferoption" shipment: "Lieferung" @@ -3710,7 +3704,6 @@ de_CH: continue: "Weiter" new: new_return_authorization: "Neue Retour" - back_to_return_authorizations_list: "Zurück zur Retourenliste" continue: "Weiter" edit: receive: "erhalten" @@ -4002,9 +3995,10 @@ de_CH: total: "Gesamt" billing_address_name: "Name" taxons: + index: + title: "Produktkategorien" form: name: Name - permalink: Permalink description: Beschreibung general_settings: edit: @@ -4223,7 +4217,6 @@ de_CH: key_cleared: "Schlüssel gelöscht" shipment: cannot_ready: "Versand nicht möglich." - invalid_taxonomy_id: "Ungültige Kategorie-ID" activerecord: models: spree/payment: diff --git a/config/locales/de_DE.yml b/config/locales/de_DE.yml index dee8187e4c..03b4024610 100644 --- a/config/locales/de_DE.yml +++ b/config/locales/de_DE.yml @@ -81,8 +81,6 @@ de_DE: white_label_logo_link: "Verlinkungsziel des Logos im Online-Shop" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Um die Steuerkategorie des Produkts zu übernehmen, muss in der Spalte \"Berechnung\" eine Auswahl mit Zusatz \"pro Artikel\" verwendet werden." spree/user: attributes: email: @@ -102,7 +100,6 @@ de_DE: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "muss leer sein, da die Einstellungen des Produzentenbestands verwendet werden" - on_demand_but_count_on_hand_set: "muss leer sein, wenn unbegrenzt verfügbar oder die Produktion auf Bestellung erfolgt" limited_stock_but_no_count_on_hand: "muss angegeben werden, da nur begrenzte Lagerbestände verfügbar sind" messages: confirmation: "stimmt nicht mit %{attribute} überein" @@ -541,6 +538,8 @@ de_DE: remove: Löschen image: edit: Bearbeiten + product_preview: + shop_tab: Produkte adjustments: skipped_changing_canceled_order: "Eine stornierte Bestellung kann nicht geändert werden." begins_at: Beginnt @@ -747,7 +746,6 @@ de_DE: variants: infinity: "Unbegrenzt" to_order_tip: "Artikel, die auf Anfrage hergestellt werden, haben keinen festgelegten Lagerbestand." - back_to_products_list: "Zurück zur Produktliste" editing_product: "Produkt bearbeiten" tabs: product_details: "Produktdetails" @@ -772,7 +770,6 @@ de_DE: search: Suche sort: pagination: - total_html: "%{total} Produkte für Ihre Suchkriterien gefunden. Zeige %{from} bis %{to}." per_page: show: Zeige per_page: "%{num} pro Seite" @@ -1295,10 +1292,8 @@ de_DE: contact_name: Kontaktname edit: editing: 'Einstellungen:' - back_link: Zurück zur Unternehmensliste new: title: Neues Unternehmen - back_link: Zurück zur Unternehmensliste welcome: welcome_title: Willkommen im Open Food Network! welcome_text: 'Erfolgreich erstellt:' @@ -1335,6 +1330,8 @@ de_DE: choose_products_from: "Wählen Sie Produkte von:" re_notify_producers: Produzenten erneut benachrichtigen notify_producers_tip: Benachrichtigen Sie die Produzenten per E-Mail über in diesem Bestellzyklus erhaltene Bestellungen. + date_time_warning_modal_content: + cancel: 'Abbrechen' incoming: incoming: "Eingehend" supplier: "Lieferant" @@ -2779,7 +2776,6 @@ de_DE: spree_admin_single_enterprise_hint: "Tipp: Damit andere Nutzer Sie finden können, aktivieren Sie Ihre Sichtbarkeit unter:" spree_admin_eg_pickup_from_school: "z. B. \"Abholung von der Grundschule\"" spree_admin_eg_collect_your_order: "z. B. 'Bitte holen Sie Ihre Bestellung in der Gartenstraße 123 in 30701 Nordwestheim ab.'" - spree_classification_primary_taxon_error: "Kategorie %{taxon} ist die primäre Kategorie von %{product} und kann nicht gelöscht werden." spree_order_availability_error: "Verteilstelle oder Bestellzyklus kann die Produkte in Ihrem Warenkorb nicht liefern" spree_order_populator_error: "Diese Verteilstelle oder dieser Bestellzyklus kann nicht alle Produkte aus Ihrem Warenkorb liefern. Bitte ändern Sie Ihre Auswahl." spree_order_cycle_error: "Bitte wählen Sie einen Bestellzyklus für diese Bestellung aus." @@ -3450,6 +3446,8 @@ de_DE: Dadurch wird der Lagerbestand für alle Produkte dieses Unternehmens, die in der hochgeladenen Datei nicht vorhanden sind, auf Null gesetzt. order_cycles: + unsaved_changes: "Sie haben nicht gespeicherte Änderungen." + bulk_save_error: "Ihre Änderungen konnten leider nicht gespeichert werden." create_failure: "Fehler beim Erstellen des Bestellzyklus." update_success: 'Ihr Bestellzyklus wurde aktualisiert.' update_failure: "Fehler beim Aktualisieren des Bestellzyklus." @@ -3671,7 +3669,6 @@ de_DE: more: "Mehr" new_adjustment: "Neue Anpassung" new_tax_category: "Neue Steuerkategorie" - new_taxon: "Neue Kategorie" new_user: "Neuer Benutzer" no_pending_payments: "Keine ausstehenden Zahlungen." remove: "Löschen" @@ -3690,8 +3687,6 @@ de_DE: delivery: "Lieferung" start_date: "Anfangsdatum" successfully_removed: "Erfolgreich gelöscht" - taxonomy_edit: "Kategorien bearbeiten" - tree: "Struktur" updating: "Aktualisierung" your_order_is_empty_add_product: "Ihre Bestellung ist leer. Suchen Sie oben ein Produkt und fügen Sie es hinzu." add_product: "Produkt hinzufügen" @@ -3788,7 +3783,6 @@ de_DE: tax_rate_amount_explanation: "Eingabe als Dezimalbetrag (d. h. bei Steuersatz 5 %, geben Sie 0.05 ein)." included_in_price: "Im Preis enthalten" show_rate_in_label: "Steuersatz im Namen anzeigen" - back_to_tax_rates_list: "Zurück zur Liste der Steuersätze" tax_settings: "Steuereinstellungen" zones: "Zonen" new_zone: "Neue Zone" @@ -3801,14 +3795,11 @@ de_DE: iso_name: "ISO-Name" states_required: "Staaten/Bundesländer/Regionen erforderlich" editing_country: "Land bearbeiten" - back_to_countries_list: "Zurück zur Länderliste" states: "Bundesländer" abbreviation: "Abkürzung" new_state: "Neues Bundesland" payment_methods: "Zahlungsarten" - taxonomies: "Kategorien" - new_taxonomy: "Neue Kategorie" - back_to_taxonomies_list: "Zurück zur Kategorieliste" + taxons: "Produktkategorien" shipping_methods: "Lieferoptionen" shipping_method: "Lieferoption" shipment: "Lieferung" @@ -3966,7 +3957,6 @@ de_DE: continue: "Weiter" new: new_return_authorization: "Neue Retour" - back_to_return_authorizations_list: "Zurück zur Retourenliste" continue: "Weiter" edit: receive: "erhalten" @@ -4281,9 +4271,10 @@ de_DE: total: "Gesamt" billing_address_name: "Name" taxons: + index: + title: "Produktkategorien" form: name: Name - permalink: Permalink description: Beschreibung general_settings: edit: @@ -4520,7 +4511,6 @@ de_DE: key_cleared: "Schlüssel gelöscht" shipment: cannot_ready: "Versand nicht möglich." - invalid_taxonomy_id: "Ungültige Kategorie-ID" toggle_api_key_view: "API-Schlüssel dem Benutzer anzeigen" activerecord: models: diff --git a/config/locales/el.yml b/config/locales/el.yml index e8a12eee70..e5b8efc881 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -78,8 +78,6 @@ el: white_label_logo_link: "Σύνδεσμος για το λογότυπο που χρησιμοποιείται στο κατάστημα" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Η απόδοση της κατηγορίας φόρου απαιτεί έναν υπολογιστή ανά αντικείμενο." spree/image: attributes: attachment: @@ -103,7 +101,6 @@ el: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "πρέπει να είναι κενό επειδή χρησιμοποιούνται ρυθμίσεις αποθεμάτων παραγωγού" - on_demand_but_count_on_hand_set: "πρέπει να παραμείνει κενό αν είναι κατόπιν ζήτησης" limited_stock_but_no_count_on_hand: "πρέπει να καθοριστεί επειδή δημιουργεί περιορισμένο απόθεμα" messages: confirmation: "δεν ταιριάζει " @@ -454,7 +451,7 @@ el: admin_and_handling: Διαχείριση profile: Προφίλ supplier_only: Μόνο Προμηθευτής - has_shopfront: Διαθέτει σελίδα καταστήματος + has_shopfront: Ενεργό κατάστημα weight: Βάρος volume: Όγκος items: Αντικείμενα @@ -486,7 +483,7 @@ el: password_confirmation: Επιβεβαίωση κωδικού πρόσβασης reset_password_token: Επαναφορά ένδειξης κωδικού expired: έχει λήξει, παρακαλώ ζητήστε νέο - back_to_payments_list: "Πίσω στην Λίστα Πληρωμών" + back_to_payments_list: "Επιστροφή στη λίστα πληρωμών" maestro_or_solo_cards: "Κάρτα Maestro/Sola" backordered: "Σε εκκρεμότητα" on_hand: "Στο χέρι" @@ -559,6 +556,8 @@ el: remove: Αφαίρεση image: edit: Επεξεργασία + product_preview: + shop_tab: Κατάστημα adjustments: skipped_changing_canceled_order: "Δεν μπορείτε να κάνετε αλλαγή ακυρωμένης παραγγελίας." begins_at: Ξεκινάει από @@ -570,7 +569,7 @@ el: email: Email ends_at: Ολοκληρώνεται σε ends_on: Ολοκληρώνεται στις - name: Όνονα + name: Όνομα first_name: Όνομα last_name: Επίθετο on_hand: Στο χέρι @@ -725,7 +724,7 @@ el: title: "Χρεώσεις επιχείρησης" enterprise: "Επιχείρηση" fee_type: "Κατηγορίες αμοιβών" - name: "Όνονα" + name: "Όνομα" tax_category: "Φορολογική Κατηγορία" calculator: "Υπολογισμός" calculator_values: "Υπολογισμός Αξίας" @@ -773,7 +772,6 @@ el: variants: infinity: "Άπειρο" to_order_tip: "Προιόντα κατά παραγγελία δεν έχουν καθορισμένο αριθμό σε απόθεμα,για παράδειγμα το ψωμί παραδίδεται κατά παραγγελία και φρέσκο." - back_to_products_list: "Πίσω στην λίστα προϊόντων" editing_product: "Επεξεργασία Προϊόντος" tabs: product_details: "Λεπτομέριες Προϊόντος" @@ -811,7 +809,6 @@ el: search: Αναζήτηση sort: pagination: - total_html: "%{total}προϊόντα βρέθηκαν σύμφωνα με τα κριτήρια της αναζήτησής σας. Εμφανίζονται %{from} στα %{to}." per_page: show: Εμφάνιση per_page: "%{num}ανα σελίδα" @@ -875,7 +872,7 @@ el: index: notice: "Ειδοποίηση" beta_notice: "Αυτή η λειτουργία είναι ακόμα σε δοκιμαστική έκδοση beta: ενδέχεται να αντιμετωπίσετε ορισμένα σφάλματα κατά τη χρήση της. Μη διστάσετε να επικοινωνήσετε με την υποστήριξη." - select_file: Επιλέξτε φύλλο εργασίας προς ανέβασμα + select_file: Επιλέξτε αρχείο CSV spreadsheet: Φύλλο Εργασίας choose_import_type: Επιλογή τύπου εισαγωγής import_into: Τύπος εισαγωγής @@ -886,7 +883,7 @@ el: csv_templates: Πρότυπο CSV product_list_template: Λήψη προτύπου λίστας προϊόντων inventory_template: Λήψη προτύπου κατάλογου προϊόντων - category_values: Διαθέσιμες παραλλαγές Κατηγορίας + category_values: Διαθέσιμες κατηγορίες product_categories: Κατηγορίες προϊόντος tax_categories: Κατηγορίες Φορολόγησης shipping_categories: Κατηγορίες Μεταφορικών @@ -913,8 +910,8 @@ el: not_found: δεν ήταν δυνατή η εύρεση της επιχείρησης στη βάση δεδομένων no_name: Δεν υπάρχει όνομα blank_enterprise: ορισμένα προϊόντα δεν έχουν συσχετιστεί με κάποια επιχείρηση - reset_absent?: Επαναφέρετε τα προϊόντα που λείπουν - reset_absent_tip: Ορίστε μηδενικό απόθεμα για όλα τα εξερχόμενα προϊόντα που δεν υπάρχουν στο αρχείο + reset_absent?: Σχετικά με τα υπόλοιπα προϊόντα + reset_absent_tip: Ορίστε μηδενικό απόθεμα για όλα τα προϊόντα που δεν υπάρχουν στο αρχείο overwrite_all: Αντικατάσταση Όλων overwrite_empty: Αντικατάσταση αν είναι κενό default_stock: Ορισμός επιπέδου αποθέματος @@ -1075,7 +1072,7 @@ el: select_state: "Επέλεξε πόλη" contact: legend: "επικοινωνία" - name: Όνονα + name: Όνομα name_placeholder: π.χ Gustav Plum email_address: Δημόσια Διεύθυνση Ηλεκτρονικού Ταχυδρομείου email_address_placeholder: π.χ. inquiries@fresh-food.com @@ -1089,9 +1086,9 @@ el: website_placeholder: π.χ. www.truffles.com enterprise_fees: legend: "Χρεώσεις επιχείρησης" - name: Όνονα + name: Όνομα fee_type: Κατηγορίες αμοιβών - manage_fees: Διαχείρηση Αμοιβών Επιχειρήσεων + manage_fees: Διαχείριση αμοιβών επιχειρήσεων no_fees_yet: Δεν έχετε τέλη επιχείρησης ακόμα. create_button: Δημιούργησε έναν τώρα enterprise_permissions: @@ -1121,7 +1118,7 @@ el: preferred_product_selection_from_inventory_only_no: Νέα προϊόντα πρέπει να προστεθούν στο απόθεμά μου πριν μπορέσουν να τοποθετηθούν στη βιτρίνα μου payment_methods: legend: "Τρόποι πληρωμής" - name: Όνονα + name: Όνομα applies: Ισχύει? manage: Διαχείριση Τρόπων Πληρωμής no_method_yet: Δεν έχετε επιλέξει κανέναν τρόπο πληρωμής. @@ -1129,7 +1126,7 @@ el: create_one_button: Δημιούργησε έναν τώρα primary_details: legend: "Βασικές λεπτομέρειες" - name: Όνονα + name: Όνομα name_placeholder: π.χ. Βιοδυναμικές τρούφες του καθηγητή Plum groups: Ομάδες groups_tip: Επέλεξε κάποια ομάδα ή περιοχή. Αυτό θα βοηθήσει τους καταναλωτές να βρίσκουν την επιχείρησή σου. @@ -1158,7 +1155,7 @@ el: ofn_uid_tip: Το μοναδικό αναγνωριστικό που χρησιμοποιείται για την ταυτοποίηση της επιχείρησης στο Open Food Network. shipping_methods: legend: "Τρόποι αποστολής" - name: "Όνονα" + name: "Όνομα" applies: "Ενεργό;" manage: "Διαχείριση Τρόπων Αποστολής" create_button: "Δημιουργία Νέου Τρόπου Αποστολής" @@ -1357,7 +1354,7 @@ el: loading_enterprises: ΦΌΡΤΩΣΕ ΤΙΣ ΕΠΙΧΕΙΡΉΣΕΙΣ no_enterprises_found: Δεν βρέθηκε επιχείρηση. search_placeholder: Αναζήτηση Με Όνομα - manage: Διαχείρηση + manage: Διαχείριση manage_link: Ρυθμήσεις producer?: "Παραγωγός?" package: "Πακέτο" @@ -1369,10 +1366,8 @@ el: contact_name: Όνομα Επικοινωνίας edit: editing: 'Ρυθμίσεις:' - back_link: Πίσω στην λίστα επιχειρήσεων new: title: Νέα Επιχείρηση - back_link: Πίσω στην λίστα επιχειρήσεων welcome: welcome_title: Καλώς ήρθατε στο Open Food Network welcome_text: επιτυχής δημιουργία @@ -1409,6 +1404,8 @@ el: choose_products_from: "Επιλογή προϊόντων Από:" re_notify_producers: Επαναλάβετε την ειδοποίηση προς τους παραγωγούς notify_producers_tip: Αυτό θα στείλει ένα email σε κάθε παραγωγό με τη λίστα των παραγγελιών τους. + date_time_warning_modal_content: + cancel: 'Ακύρωση' incoming: incoming: "Εισερχόμενο" supplier: "Προμηθευτής" @@ -1476,7 +1473,7 @@ el: add: Προσθήκη κομίστρου συντονιστή filters: search_by_order_cycle_name: "Αναζήτηση με όνομα Κύκλου Παραγγελίας ..." - involving: "Συμμετοχή" + involving: "Αφορά" any_enterprise: "Οποιαδήποτε Επιχείρηση" any_schedule: "Οποιοδήποτε Πρόγραμμα" form: @@ -1497,7 +1494,7 @@ el: new_schedule: Νέο Πρόγραμμα new_schedule_tooltip: Η συχνότητα με την οποία πραγματοποιείται μια συνδρομή name_and_timing_form: - name: Όνονα + name: Όνομα orders_open: Οι παραγγελίες ανοιγούν στις coordinator: Συντονιστής orders_close: Οι παραγγελίες είναι κλείστες @@ -1957,7 +1954,7 @@ el: mobile_menu: cart: "Καλάθι" register_call: - selling_on_ofn: "Ενδιαφέρεσαι να μπεις στο Open Food Network?" + selling_on_ofn: "Ενδιαφέρεσαι να πουλήσεις τα προϊόντα σου μέσω του Open Food Network;" register: "Εγγραφή εδώ" footer: footer_secure: "Ασφαλές και Αξιόπιστο" @@ -2024,7 +2021,7 @@ el: menu_2_url: "/map" menu_3_title: "Παραγωγοι" menu_3_url: "/producers" - menu_4_title: "Ομάδες" + menu_4_title: "Ομαδες" menu_4_url: "/groups" menu_5_title: "Σχετικα" menu_5_url: "https://about.openfoodnetwork.org.au/" @@ -2048,7 +2045,7 @@ el: footer_links_md: "Σύνδεσμοι" footer_about_url: "Σχετικά με τη διεύθυνση URL" user_guide_link: "Σύνδεσμος οδηγού χρήστη" - name: Όνονα + name: Όνομα first_name: Όνομα last_name: Επίθετο email: Email @@ -2333,7 +2330,7 @@ el: enterprises_currently_open: "Οι παραγγελίες είναι προς το παρόν ανοιχτές" enterprises_ready_for: "Έτοιμος για" enterprises_choose: "Επιλέξτε πότε θέλετε την παραγγελία σας:" - maps_open: "Ανοιξε" + maps_open: "Άνοιγμα" maps_closed: "Κλειστό" map_title: "Χάρτης" hubs_buy: "Ψώνια για:" @@ -2583,10 +2580,10 @@ el: registration_checklist: "Τι χρειάζομαι?" registration_time: "5-10 λεπτά" registration_enterprise_address: "Διέυθυνση επιχείρησης" - registration_contact_details: "Κύρια στοιχεία επικοινωνίας" - registration_logo: "Η εικόνα του λογότυπου σας" - registration_promo_image: "Εικόνα τοπίου για το προφίλ σας" - registration_about_us: "Κείμενο "Σχετικά με εμάς"" + registration_contact_details: "Τα στοιχεία επικοινωνίας σας" + registration_logo: "Το λογότυπο σας" + registration_promo_image: "Εικόνα για το προφίλ σας" + registration_about_us: "Ένα κέιμενο σχετικά με την επιχείρηση σας" registration_outcome_headline: "Τι πέρνω?" registration_outcome1_html: "Το προφίλ σου βοηθά τους ανθρώπους να σε βρουν και να επικοινωνήσουν μαζί σου στο Open Food Network." registration_outcome2: "Χρησιμοποιήστε αυτόν τον χώρο για να πείτε την ιστορία της επιχείρησής σας, για να διευκολύνετε τις συνδέσεις με την κοινωνική και διαδικτυακή σας παρουσία." @@ -2773,9 +2770,9 @@ el: admin_enterprise_groups_contact_country_id: "Χώρα" admin_enterprise_groups_web_twitter: "π.χ. @the_prof" admin_enterprise_groups_web_website_placeholder: "π.χ. www.fresh-food-supplier.gr" - admin_order_cycles: "Κύκλοι παραγγελίας διαχειριστή" - open: "Ανοιξε" - close: "Κλείσιμο" + admin_order_cycles: "Κύκλοι παραγγελιών διαχειριστή" + open: "Έναρξη" + close: "Λήξη" create: "Δημιουργία" search: "Αναζήτηση" supplier: "Προμηθευτής" @@ -2805,7 +2802,7 @@ el: flexible_rate: "Ευέλικτο ποσοστό" price_sack: "Price Sack" new_order_cycles: "Κύκλοι νέας παραγγελίας" - new_order_cycle: "Κύκλος νέας παραγγελίας" + new_order_cycle: "Νέος κύκλος παραγγελίας" new_order_cycle_tooltip: "Ανοίξτε το κατάστημα για μια ορισμένη χρονική περίοδο" select_a_coordinator_for_your_order_cycle: "Επιλέξτε έναν συντονιστή για τον κύκλο παραγγελιών σας" notify_producers: 'Ειδοποιήστε τους παραγωγούς' @@ -2832,7 +2829,7 @@ el: no_products: Χωρίς Προϊόντα spree_admin_overview_enterprises_header: "Οι Επιχειρήσεις μου" spree_admin_overview_enterprises_footer: "ΔΙΑΧΕΙΡΙΣΤΕ ΤΙΣ ΕΠΙΧΕΙΡΗΣΕΙΣ ΜΟΥ" - spree_admin_enterprises_hubs_name: "Όνονα" + spree_admin_enterprises_hubs_name: "Όνομα" spree_admin_enterprises_create_new: "ΔΗΜΙΟΥΡΓΗΣΤΕ ΝΕΟ" spree_admin_enterprises_shipping_methods: "Τρόποι αποστολής" spree_admin_enterprises_fees: "Αμοιβή επιχείρησης" @@ -2857,7 +2854,6 @@ el: spree_admin_single_enterprise_hint: "Υπόδειξη: Για να επιτρέψεις στους άλλους να σε βρουν, ενεργοποίησε την ορατότητά σου κάτω" spree_admin_eg_pickup_from_school: "π.χ. \"Παραλαβή από το Δημοτικό Σχολείο\"" spree_admin_eg_collect_your_order: "π.χ. "Παρακαλώ συλλέξτε την παραγγελία σας από 123 Imaginary St, Northcote, 3070"" - spree_classification_primary_taxon_error: "Το Taxon %{taxon} είναι το κύριο ταξινομικό του %{product} και δεν μπορεί να διαγραφεί" spree_order_availability_error: "Ο κύκλος διανομής ή παραγγελίας δεν μπορεί να παρέχει τα προϊόντα στο καλάθι σας" spree_order_populator_error: "Αυτός ο κύκλος διανομέων ή παραγγελιών δεν μπορεί να παρέχει όλα τα προϊόντα στο καλάθι σας. Επιλέξτε άλλο." spree_order_cycle_error: "Επιλέξτε έναν κύκλο παραγγελιών για αυτήν την παραγγελία." @@ -2865,17 +2861,17 @@ el: spree_distributors_error: "Πρέπει να επιλεγεί τουλάχιστον ένας κόμβος" spree_user_enterprise_limit_error: "^%{email} δεν επιτρέπεται να κατέχει άλλες επιχειρήσεις (το όριο είναι %{enterprise_limit})." spree_variant_product_error: πρέπει να έχει τουλάχιστον μία παραλλαγή - your_profil_live: "Το προφίλ σας ζωντανά" + your_profil_live: "Προφίλ καταστήματος" see: "ΕΜΦΑΝΙΣΗ" - live: "ζω" - manage: "Διαχείρηση" + live: " " + manage: "Διαχείριση" resend: "Ξαναστείλτε" add_and_manage_products: "Προσθήκη και διαχείριση προϊόντων" add_and_manage_order_cycles: "Προσθήκη & διαχείριση κύκλων παραγγελίας" manage_order_cycles: "Διαχείριση κύκλων παραγγελίας" - manage_products: "ΔΙΑΧΕΙΡΙΣΗ ΠΡΟΪΟΝΤΩΝ" + manage_products: "Διαχείριση προϊόντων" edit_profile_details: "Επεξεργασία στοιχείων προφίλ" - edit_profile_details_etc: "Αλλάξτε την περιγραφή του προφίλ σας, εικόνες κ.λπ." + edit_profile_details_etc: "Αλλάξτε την περιγραφή του καταστήματός σας, εικόνες κ.λ.π." order_cycle: "Κύκλος Παραγγελειών" enterprise_relationships: "Δικαιώματα επιχείρησης" first_name_begins_with: "Το όνομα αρχίζει με" @@ -2895,7 +2891,7 @@ el: admin_share_state: "Πόλη" hub_sidebar_hubs: "Σημείο Συλλογής " hub_sidebar_none_available: "Κανένα διαθέσιμο" - hub_sidebar_manage: "Διαχείρηση" + hub_sidebar_manage: "Διαχείριση" hub_sidebar_at_least: "Πρέπει να επιλεγεί τουλάχιστον ένας κόμβος" hub_sidebar_blue: "μπλε" hub_sidebar_red: "το κόκκινο" @@ -3512,6 +3508,8 @@ el: Αυτό θα θέσει το επίπεδο αποθέματος σε μηδέν για όλα τα προϊόντα αυτής της επιχείρησης που δεν υπάρχουν στο ανεβασμένο αρχείο. order_cycles: + unsaved_changes: "Έχετε μη αποθηκευμένες αλλαγές" + bulk_save_error: "Ωχ όχι! Δεν μπόρεσα να αποθηκεύσω τις αλλαγές σας." create_failure: "Η δημιουργία κύκλου παραγγελίας απέτυχε" update_success: 'Ο κύκλος παραγγελιών σας ενημερώθηκε.' update_failure: "Η ενημέρωση του κύκλου παραγγελίας απέτυχε" @@ -3687,7 +3685,7 @@ el: import_date: "Ημερομηνία εισαγωγής" delivery: "Διανομή" temperature_controlled: "Ελεγχόμενη θερμοκρασία" - new_product: "Καινουργιο ΠΡΟΪΟΝ" + new_product: "Καινούργιο προϊόν" administration: "Διαχείριση" logged_in_as: "Συνδεδεμένος ως" account: "Λογαριασμός" @@ -3735,7 +3733,6 @@ el: more: "Περισσότερα" new_adjustment: "Νέα προσαρμογή" new_tax_category: "Νέα Φορολογική Κατηγορία" - new_taxon: "Νέο ταξί" new_user: "Νέος χρήστης" no_pending_payments: "Δεν υπάρχουν πληρωμές σε εκκρεμότητα" remove: "Αφαίρεση" @@ -3754,10 +3751,6 @@ el: delivery: "Υπογράφηκε, σφραγίστηκε, παραδόθηκε" start_date: "Ημερομηνία έναρξης" successfully_removed: "Καταργήθηκε επιτυχώς" - taxonomy_edit: "Επεξεργασία ταξινόμησης" - taxonomy_tree_error: "Παρουσιάστηκε σφάλμα κατά την ενημέρωση του δέντρου ταξονομίας. " - taxonomy_tree_instruction: "Κάντε δεξί κλικ σε ένα αντικείμενο για να προσθέσετε, μετονομάσετε, αφαιρέσετε ή επεξεργαστείτε." - tree: "Δέντρο" updating: "Ενημέρωση" your_order_is_empty_add_product: "Η παραγγελία σας είναι κενή, αναζητήστε και προσθέστε ένα προϊόν παραπάνω" add_product: "Προσθήκη Προϊόντος" @@ -3855,7 +3848,6 @@ el: tax_rate_amount_explanation: "Οι φορολογικοί συντελεστές είναι ένα δεκαδικό ποσό για βοήθεια στους υπολογισμούς, (δηλ. Εάν ο συντελεστής φόρου είναι 5%, τότε πληκτρολογήστε 0,05)" included_in_price: "Περιλαμβάνεται στην Τιμή" show_rate_in_label: "Εμφάνιση τιμής στην ετικέτα" - back_to_tax_rates_list: "Επιστροφή στη λίστα φορολογικών συντελεστών" tax_settings: "Ρυθμίσεις φόρου" zones: "Ζώνες" new_zone: "Νέα Ζώνη" @@ -3868,14 +3860,11 @@ el: iso_name: "Όνομα ISO" states_required: "Απαιτούνται κράτη" editing_country: "Χώρα επεξεργασίας" - back_to_countries_list: "Επιστροφή στη λίστα χωρών" states: "Κρατών" abbreviation: "Συντομογραφία" new_state: "Νέα Πολιτεία" payment_methods: "τρόποι πληρωμής" - taxonomies: "Ταξινομίες" - new_taxonomy: "Νέα Ταξινομία" - back_to_taxonomies_list: "Επιστροφή στη λίστα ταξινομιών" + taxons: "Κατηγορίες προϊόντος" shipping_methods: "Τρόποι αποστολής" shipping_method: "Τρόπος Αποστολής" shipment: "Αποστολή" @@ -4035,7 +4024,6 @@ el: continue: "Συνέχεια" new: new_return_authorization: "Νέα εξουσιοδότηση επιστροφής" - back_to_return_authorizations_list: "Πίσω στην επιστροφή λίστας εξουσιοδότησης" continue: "Συνέχεια" edit: receive: "λαμβάνω" @@ -4244,8 +4232,8 @@ el: image_upload_error: "Παρακαλώ ανεβάστε την εικόνα σε τύπο αρχείου JPG, PNG, GIF, SVG ή WEBP." image_not_processable: "Η συνημμένη εικόνα δεν υποστηρίζεται." new: - title: "Καινουργιο ΠΡΟΪΟΝ" - new_product: "Καινουργιο ΠΡΟΪΟΝ" + title: "Καινούργιο προϊόν" + new_product: "Καινούργιο προϊόν" supplier: "Προμηθευτής" supplier_select_placeholder: "Διαλέξτε ένα προμηθευτή" product_name: "Ονομασία προϊόντος" @@ -4282,6 +4270,7 @@ el: product_name: Ονομασία προϊόντος primary_taxon_form: product_category: Κατηγορία προϊόντων + search_for_categories: "Αναζήτηση κατηγοριών" group_buy_form: group_buy: "Ομαδική αγορά;" bulk_unit_size: Μέγεθος μονάδας χύδην @@ -4358,9 +4347,10 @@ el: total: "Σύνολο" billing_address_name: "Όνομα" taxons: + index: + title: "Κατηγορίες προϊόντος" form: name: Όνομα - permalink: Permalink meta_title: Τίτλος Meta meta_description: Περιγραφή Meta meta_keywords: Λέξεις-κλειδιά Meta @@ -4601,7 +4591,6 @@ el: key_cleared: "Το κλειδί διαγράφηκε" shipment: cannot_ready: "Δεν είναι έτοιμη η αποστολή." - invalid_taxonomy_id: "Μη έγκυρο αναγνωριστικό ταξινόμησης." toggle_api_key_view: "Εμφάνιση προβολής κλειδιού API για τον χρήστη" activerecord: models: diff --git a/config/locales/en.yml b/config/locales/en.yml index 75abf73de9..22d339a1fd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -103,7 +103,7 @@ en: errors: models: enterprise_fee: - inherit_tax_requires_per_item_calculator: "Inheriting the tax categeory requires a per-item calculator." + inherit_tax_requires_per_item_calculator: "Inheriting the tax category requires a per-item calculator." spree/image: attributes: attachment: @@ -120,8 +120,6 @@ en: attributes: base: card_expired: "has expired" - spree/product: - must_exist: 'must exist' order_cycle: attributes: orders_close_at: @@ -129,7 +127,6 @@ en: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: confirmation: "doesn't match %{attribute}" @@ -532,7 +529,7 @@ en: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -607,8 +604,13 @@ en: clone: Clone delete: Delete remove: Remove + preview: Preview image: edit: Edit + product_preview: + product_preview: Product preview + shop_tab: Shop + product_details_tab: Product details adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." # Common properties / models @@ -855,7 +857,7 @@ en: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" + back_to_products_list: "Back To Products List" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -895,7 +897,9 @@ en: search: Search sort: pagination: - total_html: "%{total} products found for your search criteria. Showing %{from} to %{to}." + products_total_html: + one: "%{count} product found for your search criteria. Showing %{from} to %{to}." + other: "%{count} products found for your search criteria. Showing %{from} to %{to}." per_page: show: Show per_page: "%{num} per page" @@ -1464,10 +1468,10 @@ en: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list + back_link: Back To Enterprises List new: title: New Enterprise - back_link: Back to enterprises list + back_link: Back To Enterprises List welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1504,6 +1508,11 @@ en: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + title: 'Orders are linked to this order cycle.' + content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' + proceed: 'Proceed anyway' + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -3036,7 +3045,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Northcote, 3070'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order." @@ -3358,7 +3366,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using 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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3696,6 +3704,8 @@ 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: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3956,7 +3966,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" - new_taxon: "New taxon" new_user: "New user" no_pending_payments: "No pending payments" remove: "Remove" @@ -3975,10 +3984,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using delivery: "Signed, sealed, delivered" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - taxonomy_tree_error: "There was an error updating the taxonomy tree." - taxonomy_tree_instruction: "Right-click on an item to add, rename, remove or edit." - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" @@ -4080,7 +4085,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" + back_to_tax_rates_list: "Back To Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" @@ -4095,17 +4100,14 @@ See the %{link} to find out more about %{sitename}'s features and to start using iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" + back_to_countries_list: "Back To Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" @@ -4274,7 +4276,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back To Return Authorization List" + back_to_return_authorizations_list: "Back To Return Authorizations List" continue: "Continue" edit: receive: "receive" @@ -4487,6 +4489,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using new_product: "New Product" supplier: "Supplier" supplier_select_placeholder: "Select a supplier" + search_for_suppliers: "Search for suppliers" + search_for_units: "Search for units" product_name: "Product Name" units: "Unit Size" value: "Value" @@ -4521,6 +4525,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using product_name: Product Name primary_taxon_form: product_category: Product Category + search_for_categories: "Search for categories" group_buy_form: group_buy: "Group Buy?" bulk_unit_size: Bulk unit size @@ -4598,9 +4603,20 @@ See the %{link} to find out more about %{sitename}'s features and to start using total: "Total" billing_address_name: "Name" taxons: + back_to_list: "Back To Product Categories List" + index: + title: "Product Categories" + new_taxon: 'New product category' + new: + title: "New Product Category" + edit: + title: "Edit Product Category" + destroy: + delete_taxon: + success: "Successfully deleted the product category" + error: "Unable to delete the product category due to assigned products." form: name: Name - permalink: Permalink meta_title: Meta Title meta_description: Meta Description meta_keywords: Meta Keywords @@ -4842,7 +4858,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." toggle_api_key_view: "Show API key view for user" activerecord: models: diff --git a/config/locales/en_AU.yml b/config/locales/en_AU.yml index a8e1c84b4c..8495fd546c 100644 --- a/config/locales/en_AU.yml +++ b/config/locales/en_AU.yml @@ -65,7 +65,6 @@ en_AU: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -285,7 +284,6 @@ en_AU: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -350,6 +348,8 @@ en_AU: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop begins_at: Begins At begins_on: Begins On customer: Customer @@ -534,7 +534,6 @@ en_AU: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -1015,10 +1014,8 @@ en_AU: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1053,6 +1050,8 @@ en_AU: back_to_list: "Back To List" save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2333,7 +2332,6 @@ en_AU: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Northcote, 3070'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." @@ -2614,7 +2612,7 @@ en_AU: 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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -2932,6 +2930,8 @@ en_AU: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3215,7 +3215,6 @@ en_AU: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3228,14 +3227,11 @@ en_AU: iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3379,7 +3375,6 @@ en_AU: continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back To Return Authorization List" continue: "Continue" edit: receive: "receive" @@ -3654,9 +3649,10 @@ en_AU: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name - permalink: Permalink description: Description general_settings: edit: @@ -3838,7 +3834,6 @@ en_AU: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." activerecord: models: spree/payment: diff --git a/config/locales/en_BE.yml b/config/locales/en_BE.yml index f2ac862297..af01409c7f 100644 --- a/config/locales/en_BE.yml +++ b/config/locales/en_BE.yml @@ -57,7 +57,6 @@ en_BE: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -269,7 +268,6 @@ en_BE: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" on_hand: "On Hand" on hand: "On Hand" ship: "Ship" @@ -324,6 +322,8 @@ en_BE: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop begins_at: Begins At begins_on: Begins On customer: Customer @@ -502,7 +502,6 @@ en_BE: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" tabs: group_buy_options: "Group Buy Options" images: "Images" @@ -938,10 +937,8 @@ en_BE: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -972,6 +969,8 @@ en_BE: next: "Next" cancel: "Cancel" choose_products_from: "Choose Products From:" + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2192,7 +2191,6 @@ en_BE: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Northcote, 3070'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." @@ -2466,7 +2464,7 @@ en_BE: order_cycles_bulk_update_notice: 'Order cycles have been updated.' 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" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -2759,6 +2757,8 @@ en_BE: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -2948,7 +2948,6 @@ en_BE: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -2961,14 +2960,11 @@ en_BE: iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3308,6 +3304,8 @@ en_BE: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name description: Description diff --git a/config/locales/en_CA.yml b/config/locales/en_CA.yml index f97338b584..9093a04463 100644 --- a/config/locales/en_CA.yml +++ b/config/locales/en_CA.yml @@ -82,7 +82,7 @@ en_CA: errors: models: enterprise_fee: - inherit_tax_requires_per_item_calculator: "Inheriting the tax category requires a per-item calculator" + inherit_tax_requires_per_item_calculator: "Inheriting the tax category requires a per-item calculator." spree/image: attributes: attachment: @@ -106,7 +106,6 @@ en_CA: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because you are using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if 'on demand' is used" limited_stock_but_no_count_on_hand: "must be specified because you are forcing limited stock" messages: confirmation: "doesn't match %{attribute}" @@ -484,7 +483,7 @@ en_CA: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -557,6 +556,8 @@ en_CA: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." begins_at: Begins At @@ -675,6 +676,16 @@ en_CA: info_html: "Matomo is a Web and Mobile Analytics application. You can either host Matomo on-premises or use a cloud-hosted service. See 1matomo.org1 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." config_instructions_tag_manager_html: "Setting the Matomo Tag Manager URL enables Matomo Tag Manager. This tool allows you to set up analytics events. The Matomo Tag Manager URL is copied from the Install Code section of Matomo Tag Manager. Ensure you select the right container and environment as these options change the URL." + connected_app_settings: + edit: + title: "Connected app settings" + info_html: "Enabled apps will appear under Enterprise Settings > Connected Apps." + enabled_legend: "Enabled connected apps" + connected_apps_enabled: + discover_regen: Discover Regenerative portal + affiliate_sales_data: DFC anonymised orders API for research purposes + update: + resource: Connected app settings customers: index: new_customer: "New Customer" @@ -772,7 +783,7 @@ en_CA: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" + back_to_products_list: "Back To Products List" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -812,7 +823,6 @@ en_CA: search: Search sort: pagination: - total_html: "%{total} products found for your search criteria. Showing %{from} to %{to}." per_page: show: Show per_page: "%{num} per page" @@ -1299,6 +1309,15 @@ en_CA: disable: "Stop sharing" loading: "Loading" need_to_be_manager: "Only managers can connect apps." + description_html: | +

+ INRAE and UFC QUE CHOISIR are teaming up to study food prices in short food systems and compare them with prices in the supermarket, for a given set of products. The data that is used by INRAE is mixed with data coming from other short food chain platforms in France. No individual product prices will be publically disclosed through this project. +

+

+ Learn more about this research project + +

discover_regen: title: "Waterloo Food Directory" tagline: "Allow OFN Canada to publish your enterprise information." @@ -1308,8 +1327,8 @@ en_CA: need_to_be_manager: "Only managers can connect apps." note: | Your Open Food Network account is connected to the Waterloo Food Directory. - Changes made to your OFN profile will automatically update your Waterloo Food Directory listing. For any other changes please contact OFN Support. - link_label: "Manage listing" + Changes made to your OFN profile will update your Waterloo Food Directory listing when you click the 'Update listing' button. For any other changes please contact OFN Support at support@openfoodnetwork.ca. + link_label: "Update listing" description_html: |

Eligible producers can showcase call to actions to Eat Local, Give Local, and/or Grow Local. @@ -1371,10 +1390,10 @@ en_CA: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list + back_link: Back To Enterprises List new: title: New Enterprise - back_link: Back to enterprises list + back_link: Back To Enterprises List welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1411,6 +1430,11 @@ en_CA: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + title: 'Orders are linked to this order cycle.' + content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' + proceed: 'Proceed anyway' + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -1931,7 +1955,7 @@ en_CA: all_terms_and_conditions: message_html: "I agree to the seller's %{terms_and_conditions_link} and the platform %{tos_link}" terms_and_conditions: "Terms and Conditions" - submit: Complete order + submit: Click to place order cancel: Back to Payment method errors: saving_failed: "Saving failed, please update the highlighted fields." @@ -2870,7 +2894,6 @@ en_CA: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Your City'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order" @@ -3190,7 +3213,7 @@ en_CA: 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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back to Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3533,6 +3556,8 @@ en_CA: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3756,7 +3781,6 @@ en_CA: more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" - new_taxon: "New taxon" new_user: "New user" no_pending_payments: "No pending payments" remove: "Remove" @@ -3775,10 +3799,6 @@ en_CA: delivery: "Signed, sealed, delivered" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - taxonomy_tree_error: "There was an error updating the taxonomy tree." - taxonomy_tree_instruction: "Right-click on an item to add, rename, remove or edit." - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" @@ -3876,7 +3896,7 @@ en_CA: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calcuations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" + back_to_tax_rates_list: "Back To Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3889,14 +3909,12 @@ en_CA: iso_name: "ISO Name" states_required: "Provinces Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" + back_to_countries_list: "Back To Countries List" states: "Provinces" abbreviation: "Abbreviation" new_state: "New Province" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Delivery/Pick-up Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -4056,7 +4074,7 @@ en_CA: continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back to Return Authorization List" + back_to_return_authorizations_list: "Back To Return Authorizations List" continue: "Continue" edit: receive: "receive" @@ -4121,7 +4139,7 @@ en_CA: email: "Customer E-Mail" invoice: issued_on: "Issued on" - tax_invoice: "Order Number" + tax_invoice: "INVOICE" code: "Code" from: "From" to: "Bill to" @@ -4269,6 +4287,8 @@ en_CA: new_product: "New Product" supplier: "Supplier" supplier_select_placeholder: "Select a supplier" + search_for_suppliers: "Search for suppliers" + search_for_units: "Search for units" product_name: "Product Name" units: "Unit Size" value: "Value" @@ -4303,6 +4323,7 @@ en_CA: product_name: Product Name primary_taxon_form: product_category: Product Category + search_for_categories: "Search for categories" group_buy_form: group_buy: "Group Buy?" bulk_unit_size: Bulk unit size @@ -4380,9 +4401,20 @@ en_CA: total: "Total" billing_address_name: "Name" taxons: + back_to_list: "Back To Product Categories List" + index: + title: "Product Categories" + new_taxon: 'New product category' + new: + title: "New Product Category" + edit: + title: "Edit Product Category" + destroy: + delete_taxon: + success: "Successfully deleted the product category" + error: "Unable to delete the product category due to assigned products." form: name: Name - permalink: Permalink meta_title: Meta Title meta_description: Meta Description meta_keywords: Meta Keywords @@ -4624,7 +4656,6 @@ en_CA: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." toggle_api_key_view: "Show API key view for user" activerecord: models: diff --git a/config/locales/en_DE.yml b/config/locales/en_DE.yml index 643eb4af53..602c6b14f1 100644 --- a/config/locales/en_DE.yml +++ b/config/locales/en_DE.yml @@ -57,7 +57,6 @@ en_DE: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -272,7 +271,6 @@ en_DE: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -329,6 +327,8 @@ en_DE: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop begins_at: Begins At begins_on: Begins On customer: Customer @@ -507,7 +507,6 @@ en_DE: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" tabs: group_buy_options: "Group Buy Options" images: "Images" @@ -946,10 +945,8 @@ en_DE: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -980,6 +977,8 @@ en_DE: next: "Next" cancel: "Cancel" choose_products_from: "Choose Products From:" + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2201,7 +2200,6 @@ en_DE: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Northcote, 3070'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." @@ -2474,7 +2472,7 @@ en_DE: order_cycles_bulk_update_notice: 'Order cycles have been updated.' 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" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -2767,6 +2765,8 @@ en_DE: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -2959,7 +2959,6 @@ en_DE: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -2972,14 +2971,11 @@ en_DE: iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3323,6 +3319,8 @@ en_DE: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name description: Description diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index 07e8a98094..c1b1d344d3 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -82,7 +82,7 @@ en_FR: errors: models: enterprise_fee: - inherit_tax_requires_per_item_calculator: "Inheriting the tax categeory requires a per-item calculator." + inherit_tax_requires_per_item_calculator: "Inheriting the tax category requires a per-item calculator." spree/image: attributes: attachment: @@ -99,8 +99,6 @@ en_FR: attributes: base: card_expired: "has expired" - spree/product: - must_exist: 'must exist' order_cycle: attributes: orders_close_at: @@ -108,7 +106,6 @@ en_FR: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: confirmation: "doesn't match %{attribute}" @@ -486,7 +483,7 @@ en_FR: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -559,6 +556,8 @@ en_FR: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." begins_at: Begins At @@ -677,6 +676,16 @@ en_FR: info_html: "Matomo is a Web and Mobile Analytics application. You can either host Matomo on-premises or use a cloud-hosted service. See matomo.orgfor 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." config_instructions_tag_manager_html: "Setting the Matomo Tag Manager URL enables Matomo Tag Manager. This tool allows you to set up analytics events. The Matomo Tag Manager URL is copied from the Install Code section of Matomo Tag Manager. Ensure you select the right container and environment as these options change the URL." + connected_app_settings: + edit: + title: "Connected app settings" + info_html: "Enabled apps will appear under Enterprise Settings > Connected Apps." + enabled_legend: "Enabled connected apps" + connected_apps_enabled: + discover_regen: Discover Regenerative portal + affiliate_sales_data: DFC anonymised orders API for research purposes + update: + resource: Connected app settings customers: index: new_customer: "New Customer" @@ -774,7 +783,7 @@ en_FR: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" + back_to_products_list: "Back To Products List" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -814,7 +823,9 @@ en_FR: search: Search sort: pagination: - total_html: "%{total} products found for your search criteria. Showing %{from} to %{to}." + products_total_html: + one: "%{count} product found for your search criteria. Showing %{from} to %{to}." + other: "%{count} products found for your search criteria. Showing %{from} to %{to}." per_page: show: Show per_page: "%{num} per page" @@ -1385,10 +1396,10 @@ en_FR: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list + back_link: Back To Enterprises List new: title: New Enterprise - back_link: Back to enterprises list + back_link: Back To Enterprises List welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1425,6 +1436,11 @@ en_FR: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + title: 'Orders are linked to this order cycle.' + content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' + proceed: 'Proceed anyway' + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2884,7 +2900,6 @@ en_FR: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Northcote, 3070'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order." @@ -3204,7 +3219,7 @@ en_FR: 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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3548,6 +3563,8 @@ en_FR: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3771,7 +3788,6 @@ en_FR: more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" - new_taxon: "New taxon" new_user: "New user" no_pending_payments: "No pending payments" remove: "Remove" @@ -3790,10 +3806,6 @@ en_FR: delivery: "Signed, sealed, delivered" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - taxonomy_tree_error: "There was an error updating the taxonomy tree." - taxonomy_tree_instruction: "Right-click on an item to add, rename, remove or edit." - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" @@ -3891,7 +3903,7 @@ en_FR: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" + back_to_tax_rates_list: "Back To Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3904,14 +3916,12 @@ en_FR: iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" + back_to_countries_list: "Back To Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -4071,7 +4081,7 @@ en_FR: continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back To Return Authorization List" + back_to_return_authorizations_list: "Back To Return Authorizations List" continue: "Continue" edit: receive: "receive" @@ -4284,6 +4294,8 @@ en_FR: new_product: "New Product" supplier: "Supplier" supplier_select_placeholder: "Select a supplier" + search_for_suppliers: "Search for suppliers" + search_for_units: "Search for units" product_name: "Product Name" units: "Unit Size" value: "Value" @@ -4318,6 +4330,7 @@ en_FR: product_name: Product Name primary_taxon_form: product_category: Product Category + search_for_categories: "Search for categories" group_buy_form: group_buy: "Group Buy?" bulk_unit_size: Bulk unit size @@ -4395,9 +4408,20 @@ en_FR: total: "Total" billing_address_name: "Name" taxons: + back_to_list: "Back To Product Categories List" + index: + title: "Product Categories" + new_taxon: 'New product category' + new: + title: "New Product Category" + edit: + title: "Edit Product Category" + destroy: + delete_taxon: + success: "Successfully deleted the product category" + error: "Unable to delete the product category due to assigned products." form: name: Name - permalink: Permalink meta_title: Meta Title meta_description: Meta Description meta_keywords: Meta Keywords @@ -4639,7 +4663,6 @@ en_FR: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." toggle_api_key_view: "Show API key view for user" activerecord: models: diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index db025ca486..e0875b57bf 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -1,5 +1,8 @@ en_GB: language_name: "English" + time: + formats: + long: "%B %d, %Y %-l:%M %p" activerecord: models: spree/product: Product @@ -78,8 +81,6 @@ en_GB: white_label_logo_link: "Link for the logo used in shopfront" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Inheriting the tax category requires a per-item calculator." spree/image: attributes: attachment: @@ -103,7 +104,6 @@ en_GB: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: confirmation: "doesn't match %{attribute}" @@ -194,6 +194,9 @@ en_GB: transaction_not_allowed: "The card has been declined for an unknown reason." try_again_later: "The card has been declined for an unknown reason." withdrawal_count_limit_exceeded: "The customer has exceeded the balance or credit limit available on their card." + disconnect_failure: "Failed to disconnect Stripe." + success_code: + disconnected: "Stripe account disconnected." activemodel: errors: messages: @@ -219,10 +222,16 @@ en_GB: not_available_to_shop: "is not available to %{shop}" card_details: "Card details" card_type: "Card type" + card_type_is: "Card type is" + unrecognized_card_type: "Unrecognised card type" + use_new_cc: "Use a new credit card" + what_is_this: "What is this?" cardholder_name: "Cardholder name" community_forum_url: "Community forum URL" customer_instructions: "Customer instructions" additional_information: "Additional Information" + connect_app: + url: "https://n8n.openfoodnetwork.org/webhook/regen/connect-enterprise" devise: passwords: spree_user: @@ -472,7 +481,7 @@ en_GB: password_confirmation: Password Confirmation reset_password_token: Reset password expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "In Stock" @@ -545,6 +554,8 @@ en_GB: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." begins_at: Begins At @@ -598,6 +609,7 @@ en_GB: show_n_more: Show %{num} more choose: "Choose..." please_select: Please select... + column_save_as_default: Save as default columns: Columns actions: Actions viewing: "Viewing: %{current_view_name}" @@ -646,6 +658,7 @@ en_GB: status: Status ok: Ok instance_secret_key: Instance Secret Key + instance_publishable_key: Instance Publishable Key account_id: Account ID business_name: Business Name charges_enabled: Charges Enabled @@ -661,6 +674,16 @@ en_GB: info_html: "Matomo is a Web and Mobile Analytics application. 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." config_instructions_tag_manager_html: "Setting the Matomo Tag Manager URL enables Matomo Tag Manager. This tool allows you to set up analytics events. The Matomo Tag Manager URL is copied from the Install Code section of Matomo Tag Manager. Ensure you select the right container and environment as these options change the URL." + connected_app_settings: + edit: + title: "Connected app settings" + info_html: "Enabled apps will appear under Enterprise Settings > Connected Apps." + enabled_legend: "Enabled connected apps" + connected_apps_enabled: + discover_regen: Discover Regenerative portal + affiliate_sales_data: DFC anonymised orders API for research purposes + update: + resource: Connected app settings customers: index: new_customer: "New Customer" @@ -685,6 +708,9 @@ en_GB: balance_due: "Balance Due" destroy: has_associated_subscriptions: "Delete failed: This customer has active subscriptions. Cancel them first." + column_preferences: + bulk_update: + success: "Column preferences saved" contents: edit: title: Content @@ -700,6 +726,7 @@ en_GB: map: Map dfc_product_imports: index: + title: "Importing a DFC product catalogue" imported_products: "Imported products:" enterprise_fees: index: @@ -754,7 +781,6 @@ en_GB: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -794,7 +820,9 @@ en_GB: search: Search sort: pagination: - total_html: "%{total} products found for your search criteria. Showing %{from} to %{to}." + products_total_html: + one: "%{count} product found for your search criteria. Showing %{from} to %{to}." + other: "%{count} products found for your search criteria. Showing %{from} to %{to}." per_page: show: Show per_page: "%{num} per page" @@ -828,9 +856,13 @@ en_GB: error: Unable to delete the variant variant_row: none_tax_category: None + search_for_tax_categories: "Search for tax categories" category_field_name: "Category" tax_category_field_name: "Tax Category" producer_field_name: "Producer" + clone: + success: Successfully cloned the product + error: Unable to clone the product product_import: title: Product Import file_not_found: File not found or could not be opened @@ -873,7 +905,9 @@ en_GB: tax_categories: Tax Categories shipping_categories: Shipping Categories dfc_import_form: + title: "Import from DFC catalogue" enterprise: "Enterprise" + catalog_url: "DFC catalogue URL" import: "Import" import: review: Review @@ -977,6 +1011,7 @@ en_GB: edit: order_sure_want_to: Are you sure you want to %{event} this order? voucher_tax_included_in_price: "%{label}(tax included in voucher)" + tax_on_fees: "Tax on fees" invoice_email_sent: 'Invoice email has been sent' order_email_resent: 'Order email has been resent' bulk_management: @@ -1183,6 +1218,7 @@ en_GB: open_date: "Open Date" close_date: "Close Date" display_ordering_in_shopfront: "Display ordering in shopfront:" + shopfront_sort_by_product: "By product" shopfront_sort_by_category: "By category" shopfront_sort_by_producer: "By producer" shopfront_sort_by_category_placeholder: "Category" @@ -1267,15 +1303,18 @@ en_GB: connected_apps: legend: "Connected apps" affiliate_sales_data: + tagline: "Allow this research project to access your orders data anonymously" enable: "Allow data sharing" disable: "Stop sharing" loading: "Loading" + need_to_be_manager: "Only managers can connect apps." discover_regen: title: "Discover Regenerative" tagline: "Allow Discover Regenerative to publish your enterprise information." enable: "Allow data sharing" disable: "Stop sharing" loading: "Loading" + need_to_be_manager: "Only managers can connect apps." note: | Your Open Food Network account is connected to Discover Regenerative. Add or update information on your Discover Regenerative listing here. @@ -1344,10 +1383,8 @@ en_GB: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1384,6 +1421,11 @@ en_GB: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + title: 'Orders are linked to this order cycle.' + content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' + proceed: 'Proceed anyway' + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -1583,6 +1625,9 @@ en_GB: pack_by_customer: Pack By Customer pack_by_supplier: Pack By Supplier pack_by_product: Pack By Product + display: + report_is_big: "This report is big and may slow down your device." + display_anyway: "Display anyway" download: button: "Download Report" show: @@ -2160,7 +2205,7 @@ en_GB: system_step3_text: "Set up payment methods and add your collection and delivery options." cta_headline: "The sustainable food network of our future." cta_label: "Start shopping" - stats_headline: "Join over 15k shoppers and producers in creating a new food system" + stats_headline: "Join over 18k shoppers and producers in creating a new food system" stats_producers: "food producers" stats_shops: "food shops" stats_shoppers: "food shoppers" @@ -2493,6 +2538,7 @@ en_GB: orders_bought_already_confirmed: "* already confirmed" orders_confirm_cancel: "Are you sure you want to cancel this order?" order_processed_successfully: "Your order has been processed successfully" + thank_you_for_your_order: "Thank you for your order" products_cart_distributor_choice: "Distributor for your order:" products_cart_distributor_change: "Your distributor for this order will be changed to %{name} if you add this product to your cart." products_cart_distributor_is: "Your distributor for this order is %{name}." @@ -2839,7 +2885,6 @@ en_GB: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Newcastle, NE1 1AA'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order." @@ -3159,7 +3204,7 @@ en_GB: 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" order_cycle_closed: "The order cycle you've selected has just closed." - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3509,6 +3554,8 @@ en_GB: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3723,6 +3770,7 @@ en_GB: editing_tax_category: "Editing tax category" editing_tax_rate: "Editing tax rate" editing_zone: "Editing zone" + editing_state: "Editing County" expiration: "Expiration" invalid_payment_provider: "Invalid payment provider" items_cannot_be_shipped: "Items cannot be shipped" @@ -3731,7 +3779,6 @@ en_GB: more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" - new_taxon: "New taxon" new_user: "New user" no_pending_payments: "No pending payments" remove: "Remove" @@ -3750,10 +3797,6 @@ en_GB: delivery: "Signed, sealed, delivered" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - taxonomy_tree_error: "There was an error updating the taxonomy tree." - taxonomy_tree_instruction: "Right-click on an item to add, rename, remove or edit." - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" @@ -3761,6 +3804,7 @@ en_GB: resend: "Resend" back_to_orders_list: "Back To Orders List" back_to_payments_list: "Back To Payments List" + back_to_states_list: "Back To Counties List" return_authorizations: "Return Authorisations" cannot_create_returns: "Cannot create returns as this order has no shipped units." select_stock: "Select stock" @@ -3850,7 +3894,6 @@ en_GB: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3863,14 +3906,11 @@ en_GB: iso_name: "ISO Name" states_required: "Counties Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "Counties" abbreviation: "Abbreviation" new_state: "New County" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -4030,7 +4070,6 @@ en_GB: continue: "Continue" new: new_return_authorization: "New Return Authorisation" - back_to_return_authorizations_list: "Back To Return Authorisation List" continue: "Continue" edit: receive: "receive" @@ -4277,6 +4316,7 @@ en_GB: product_name: Product Name primary_taxon_form: product_category: Product Category + search_for_categories: "Search for categories" group_buy_form: group_buy: "Group Buy?" bulk_unit_size: Bulk unit size @@ -4354,9 +4394,18 @@ en_GB: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" + new_taxon: 'New product category' + new: + title: "New Product Category" + edit: + title: "Edit Product Category" + destroy: + delete_taxon: + success: "Successfully deleted the product category" form: name: Name - permalink: Permalink meta_title: Meta Title meta_description: Meta Description meta_keywords: Meta Keywords @@ -4598,7 +4647,6 @@ en_GB: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." toggle_api_key_view: "Show API key view for user" activerecord: models: diff --git a/config/locales/en_IE.yml b/config/locales/en_IE.yml index 8c8b70123b..b983f32980 100644 --- a/config/locales/en_IE.yml +++ b/config/locales/en_IE.yml @@ -81,8 +81,6 @@ en_IE: white_label_logo_link: "Link for the logo used in shopfront" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Inheriting the tax categeory requires a per-item calculator." spree/image: attributes: attachment: @@ -106,7 +104,6 @@ en_IE: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: confirmation: "doesn't match %{attribute}" @@ -475,7 +472,7 @@ en_IE: password_confirmation: Password Confirmation reset_password_token: Reset password expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "In Stock" @@ -547,6 +544,8 @@ en_IE: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." begins_at: Begins At @@ -753,7 +752,6 @@ en_IE: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -791,7 +789,6 @@ en_IE: search: Search sort: pagination: - total_html: "%{total} products found for your search criteria. Showing %{from} to %{to}." per_page: show: Show per_page: "%{num} per page" @@ -1341,10 +1338,8 @@ en_IE: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1381,6 +1376,8 @@ en_IE: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2836,7 +2833,6 @@ en_IE: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Dublin, D01 XXXX'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order." @@ -3156,7 +3152,7 @@ en_IE: 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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3506,6 +3502,8 @@ en_IE: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3728,7 +3726,6 @@ en_IE: more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" - new_taxon: "New taxon" new_user: "New user" no_pending_payments: "No pending payments" remove: "Remove" @@ -3747,10 +3744,6 @@ en_IE: delivery: "Signed, sealed, delivered" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - taxonomy_tree_error: "There was an error updating the taxonomy tree." - taxonomy_tree_instruction: "Right-click on an item to add, rename, remove or edit." - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" @@ -3847,7 +3840,6 @@ en_IE: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3860,14 +3852,11 @@ en_IE: iso_name: "ISO Name" states_required: "Counties Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "Counties" abbreviation: "Abbreviation" new_state: "New County" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -4027,7 +4016,6 @@ en_IE: continue: "Continue" new: new_return_authorization: "New Return Authorisation" - back_to_return_authorizations_list: "Back To Return Authorisation List" continue: "Continue" edit: receive: "receive" @@ -4274,6 +4262,7 @@ en_IE: product_name: Product Name primary_taxon_form: product_category: Product Category + search_for_categories: "Search for categories" group_buy_form: group_buy: "Group Buy?" bulk_unit_size: Bulk unit size @@ -4351,9 +4340,10 @@ en_IE: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name - permalink: Permalink meta_title: Meta Title meta_description: Meta Description meta_keywords: Meta Keywords @@ -4595,7 +4585,6 @@ en_IE: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." toggle_api_key_view: "Show API key view for user" activerecord: models: diff --git a/config/locales/en_IN.yml b/config/locales/en_IN.yml index f6f4f26cf3..832a159876 100644 --- a/config/locales/en_IN.yml +++ b/config/locales/en_IN.yml @@ -61,7 +61,6 @@ en_IN: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -280,7 +279,6 @@ en_IN: password_confirmation: Password Confirmation reset_password_token: Reset password expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "In Stock" @@ -337,6 +335,8 @@ en_IN: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop begins_at: Begins At begins_on: Begins On customer: Customer @@ -518,7 +518,6 @@ en_IN: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -971,10 +970,8 @@ en_IN: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1009,6 +1006,8 @@ en_IN: back_to_list: "Back To List" save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2271,7 +2270,6 @@ en_IN: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Newcastle, NE1 1AA'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." @@ -2547,7 +2545,7 @@ en_IN: 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" order_cycle_closed: "The order cycle you've selected has just closed." - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -2857,6 +2855,8 @@ en_IN: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3129,7 +3129,6 @@ en_IN: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3142,14 +3141,11 @@ en_IN: iso_name: "ISO Name" states_required: "Counties Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "Counties" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3286,7 +3282,6 @@ en_IN: continue: "Continue" new: new_return_authorization: "New Return Authorisation" - back_to_return_authorizations_list: "Back To Return Authorisation List" continue: "Continue" edit: receive: "receive" @@ -3556,6 +3551,8 @@ en_IN: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name description: Description @@ -3732,7 +3729,6 @@ en_IN: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." unit: unit components: search_input: diff --git a/config/locales/en_NZ.yml b/config/locales/en_NZ.yml index a1add26c14..acc97f78cc 100644 --- a/config/locales/en_NZ.yml +++ b/config/locales/en_NZ.yml @@ -70,7 +70,6 @@ en_NZ: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -407,7 +406,7 @@ en_NZ: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -474,6 +473,8 @@ en_NZ: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." begins_at: Begins At @@ -673,7 +674,6 @@ en_NZ: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -1164,10 +1164,8 @@ en_NZ: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1204,6 +1202,8 @@ en_NZ: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2564,7 +2564,6 @@ en_NZ: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Northcote, 3070'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order." @@ -2848,7 +2847,7 @@ en_NZ: 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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3169,6 +3168,8 @@ en_NZ: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3455,7 +3456,6 @@ en_NZ: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3468,14 +3468,11 @@ en_NZ: iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3620,7 +3617,6 @@ en_NZ: continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back To Return Authorization List" continue: "Continue" edit: receive: "receive" @@ -3906,9 +3902,10 @@ en_NZ: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name - permalink: Permalink description: Description general_settings: edit: @@ -4125,7 +4122,6 @@ en_NZ: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." activerecord: models: spree/payment: diff --git a/config/locales/en_PH.yml b/config/locales/en_PH.yml index f6dc004c68..e7fd58bcd3 100644 --- a/config/locales/en_PH.yml +++ b/config/locales/en_PH.yml @@ -61,7 +61,6 @@ en_PH: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -279,7 +278,6 @@ en_PH: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "On Hand" @@ -336,6 +334,8 @@ en_PH: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop begins_at: Begins At begins_on: Begins On customer: Customer @@ -514,7 +514,6 @@ en_PH: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -961,10 +960,8 @@ en_PH: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -999,6 +996,8 @@ en_PH: back_to_list: "Back To List" save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2242,7 +2241,6 @@ en_PH: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Kapayapaan St., Tobias Fornier, Antique 5716'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." @@ -2516,7 +2514,7 @@ en_PH: order_cycles_bulk_update_notice: 'Order cycles have been updated.' 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" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -2816,6 +2814,8 @@ en_PH: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3082,7 +3082,6 @@ en_PH: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3095,14 +3094,11 @@ en_PH: iso_name: "ISO Name" states_required: "Provinces Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "Provinces" abbreviation: "Abbreviation" new_state: "New Province" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Category" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3233,7 +3229,6 @@ en_PH: continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back To Return Authorization List" continue: "Continue" edit: receive: "receive" @@ -3491,6 +3486,8 @@ en_PH: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name description: Description @@ -3657,7 +3654,6 @@ en_PH: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid category ID." unit: unit components: search_input: diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index 183564ef11..0b6cf12bd8 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -68,7 +68,6 @@ en_US: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -383,7 +382,7 @@ en_US: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" + back_to_payments_list: "Back To Payments List" maestro_or_solo_cards: "Debit cards" backordered: "Backordered" on_hand: "On Hand" @@ -450,6 +449,8 @@ en_US: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." begins_at: Begins At @@ -649,7 +650,6 @@ en_US: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -1125,10 +1125,8 @@ en_US: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1165,6 +1163,8 @@ en_US: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2516,7 +2516,6 @@ en_US: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick up at Community Center'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 High Street, Asheville, NC 22806'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_cycle_error: "Please choose an order cycle for this order." @@ -2802,7 +2801,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" order_cycle_closed: "The order cycle you've selected has just closed. Please try again with a different order cycle!" - back_to_orders_list: "Back to order list" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -3132,6 +3131,8 @@ 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: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3346,7 +3347,6 @@ en_US: more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" - new_taxon: "New taxon" new_user: "New user" no_pending_payments: "No pending payments" remove: "Remove" @@ -3363,8 +3363,6 @@ en_US: UPS Ground: "UPS Ground" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your cart is empty, please search for and add a product above" add_product: "Add Product" @@ -3459,7 +3457,6 @@ en_US: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3472,14 +3469,11 @@ en_US: iso_name: "ISO Name" states_required: "States Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "States" abbreviation: "Abbreviation" new_state: "New State" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3633,7 +3627,6 @@ en_US: continue: "Continue" new: new_return_authorization: "New Return Authorization" - back_to_return_authorizations_list: "Back To Return Authorization List" continue: "Continue" edit: receive: "receive" @@ -3924,9 +3917,10 @@ en_US: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name - permalink: Permalink description: Description general_settings: edit: @@ -4147,7 +4141,6 @@ en_US: key_cleared: "Key cleared" shipment: cannot_ready: "Cannot ready shipment." - invalid_taxonomy_id: "Invalid taxonomy id." activerecord: models: spree/payment: diff --git a/config/locales/en_ZA.yml b/config/locales/en_ZA.yml index 0bdc4080a1..b4bd46097c 100644 --- a/config/locales/en_ZA.yml +++ b/config/locales/en_ZA.yml @@ -61,7 +61,6 @@ en_ZA: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: blank: "can't be blank" @@ -279,7 +278,6 @@ en_ZA: password_confirmation: Password Confirmation reset_password_token: Reset password expired: has expired, please request a new one - back_to_payments_list: "Back to Payments List" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "Backordered" on_hand: "In Stock" @@ -336,6 +334,8 @@ en_ZA: remove: Remove image: edit: Edit + product_preview: + shop_tab: Shop begins_at: Begins At begins_on: Begins On customer: Customer @@ -514,7 +514,6 @@ en_ZA: variants: infinity: "Infinity" to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." - back_to_products_list: "Back to products list" editing_product: "Editing Product" tabs: product_details: "Product Details" @@ -962,10 +961,8 @@ en_ZA: contact_name: Contact Name edit: editing: 'Settings:' - back_link: Back to enterprises list new: title: New Enterprise - back_link: Back to enterprises list welcome: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a @@ -1000,6 +997,8 @@ en_ZA: back_to_list: "Back To List" save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" + date_time_warning_modal_content: + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -2251,7 +2250,6 @@ en_ZA: spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Church Street, Newcastle, 2094'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." @@ -2525,7 +2523,7 @@ en_ZA: order_cycles_bulk_update_notice: 'Order cycles have been updated.' 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" + back_to_orders_list: "Back To Orders List" no_orders_found: "No Orders Found" order_information: "Order Information" new_payment: "New Payment" @@ -2831,6 +2829,8 @@ en_ZA: This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" + bulk_save_error: "Oh no! I was unable to save your changes." create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" @@ -3021,7 +3021,6 @@ en_ZA: tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" included_in_price: "Included in Price" show_rate_in_label: "Show rate in label" - back_to_tax_rates_list: "Back to Tax Rates List" tax_settings: "Tax Settings" zones: "Zones" new_zone: "New Zone" @@ -3034,14 +3033,11 @@ en_ZA: iso_name: "ISO Name" states_required: "Provinces Required" editing_country: "Editing Country" - back_to_countries_list: "Back to Countries List" states: "Provinces" abbreviation: "Abbreviation" new_state: "New Province" payment_methods: "Payment Methods" - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" shipment: "Shipment" @@ -3383,6 +3379,8 @@ en_ZA: total: "Total" billing_address_name: "Name" taxons: + index: + title: "Product Categories" form: name: Name description: Description diff --git a/config/locales/es.yml b/config/locales/es.yml index 908ef3ad36..73d23bc2c3 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -84,7 +84,6 @@ es: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "debe estar en blanco porque estás usando la configuración de estoc de la productora" - on_demand_but_count_on_hand_set: "debe estar en blanco si está bajo demanda" limited_stock_but_no_count_on_hand: "se debe especificar porque se ha definido estoc limitado" messages: blank: "no puede estar vacío" @@ -484,6 +483,8 @@ es: remove: Eliminar image: edit: Editar + product_preview: + shop_tab: Tienda adjustments: skipped_changing_canceled_order: "No puede cambiar un pedido cancelado." begins_at: Empieza en @@ -685,7 +686,6 @@ es: variants: infinity: "infinito" to_order_tip: "Los artículos hechos según demanda no tienen un nivel de stock, como por ejemplo panes hechos según demanda." - back_to_products_list: "Volver a la lista de productos" editing_product: "Editando producto" tabs: product_details: "Detalles del Producto" @@ -1195,10 +1195,8 @@ es: contact_name: Nombre de Contacto edit: editing: 'Configuración:' - back_link: Volver a la lista de organizaciones new: title: Nueva Organización - back_link: Volver a la lista de organizaciones welcome: welcome_title: Bienvenida a Open Food Network! welcome_text: Has creado correctamente un @@ -1235,6 +1233,8 @@ es: choose_products_from: "Escoger Productos desde:" re_notify_producers: Re notificar a los productores notify_producers_tip: Esto le enviará un correo a cada productor con su lista de pedidos. + date_time_warning_modal_content: + cancel: 'Cancelar' incoming: incoming: "Entrante" supplier: "Proveedora" @@ -2621,7 +2621,6 @@ es: spree_admin_single_enterprise_hint: "Sugerencia: Para permitir que la gente te encuentre, activa tu visibilidad" spree_admin_eg_pickup_from_school: "p.ej. 'Recogida en la Cooperativa'" spree_admin_eg_collect_your_order: "p.ej. 'Por favor recoge tu pedido en la Calle del Hormiguero nº3'" - spree_classification_primary_taxon_error: "La clasificación %{taxon} es la clasificación primaria de %{product} y no puede ser eliminada" spree_order_availability_error: "El Distribuidor o el Ciclo de Pedido no pueden suministrar los productos en su carrito" spree_order_populator_error: "Este Distribuidor o Ciclo de Pedido no puede suministrar todos los productos en tu carrito. Por favor, elige otro." spree_order_cycle_error: "Elija un ciclo de pedido para este pedido." @@ -3254,6 +3253,8 @@ 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: + unsaved_changes: "Tienes cambios sin guardar" + bulk_save_error: "¡Vaya! No se ha podido guardar los cambios." 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" @@ -3491,7 +3492,6 @@ es: more: "Más" new_adjustment: "Nuevo ajuste" new_tax_category: "Nueva categoría de impuestos" - new_taxon: "Nuevo taxón" new_user: "Nuevo usuario" no_pending_payments: "No tiene pagos pendientes" remove: "Eliminar" @@ -3509,8 +3509,6 @@ es: pick_up: "Recoger en la granja" start_date: "Fecha de inicio" successfully_removed: "Eliminado con éxito" - taxonomy_edit: "editar taxonomía" - tree: "Árbol" updating: "Actualizando" your_order_is_empty_add_product: "Su pedido está vacío, busque y añada un producto arriba" add_product: "Añadir Producto" @@ -3605,7 +3603,6 @@ es: tax_rate_amount_explanation: "Las tasas de impuestos son una cantidad decimal para ayudar en los cálculos (es decir, si la tasa de impuestos es del 5%, introduzca 0.05)" included_in_price: "Incluido en el precio" show_rate_in_label: "Mostrar impuesto en la etiqueta" - back_to_tax_rates_list: "Volver a la lista de impuestos" tax_settings: "Configuración de Impuestos" zones: "Zonas" new_zone: "Nueva zona" @@ -3618,14 +3615,11 @@ es: iso_name: "Nombre ISO" states_required: "Estados requeridos" editing_country: "Editar país" - back_to_countries_list: "Volver a la lista de países" states: "Estados" abbreviation: "Abreviatura" new_state: "Nuevo estado" payment_methods: "Métodos de Pago" - taxonomies: "Taxonomias" - new_taxonomy: "Nueva taxonomía" - back_to_taxonomies_list: "Volver a la Lista de Taxonomías" + taxons: "Categorías de Producto" shipping_methods: "Métodos de envío" shipping_method: "Método de envío" shipment: "Envío" @@ -3779,7 +3773,6 @@ es: continue: "Continuar" new: new_return_authorization: "Nueva autorización de devolución" - back_to_return_authorizations_list: "Back To Return Authorization List" continue: "Continuar" edit: receive: "recibir" @@ -4080,9 +4073,10 @@ es: total: "Total" billing_address_name: "Nombre" taxons: + index: + title: "Categorías de Producto" form: name: Nombre - permalink: Enlace permanente description: Descripción general_settings: edit: @@ -4309,7 +4303,6 @@ es: key_cleared: "valor borrado" shipment: cannot_ready: "No se puede completar envío" - invalid_taxonomy_id: "El identificador de taxonomía es inválido." activerecord: models: spree/payment: diff --git a/config/locales/es_CO.yml b/config/locales/es_CO.yml index c6e9fb8c23..f12ff74d77 100644 --- a/config/locales/es_CO.yml +++ b/config/locales/es_CO.yml @@ -68,7 +68,6 @@ es_CO: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "debe estar en blanco porque está usando la configuración de inventario del productor" - on_demand_but_count_on_hand_set: "debe estar en blanco si es bajo demanda" limited_stock_but_no_count_on_hand: "debe estar especificado porque se ha definido un inventario limitado" messages: blank: "no puede estar vacío" @@ -300,7 +299,6 @@ es_CO: password_confirmation: Confirmación de contraseña reset_password_token: Token de restablecimiento de contraseña expired: ha expirado, por favor solicite una nueva - back_to_payments_list: "Volver a la lista de pagos" maestro_or_solo_cards: "Solo tarjetas MasterCard" backordered: "Pedido pendiente" on_hand: "Disponibles" @@ -360,6 +358,8 @@ es_CO: remove: Eliminar image: edit: Editar + product_preview: + shop_tab: Tienda begins_at: Empieza en begins_on: Comienza en customer: Cliente @@ -541,7 +541,6 @@ es_CO: variants: infinity: "infinito" to_order_tip: "Los artículos hechos según el pedido no tienen un nivel fijado de inventario, como por ejemplo panes hechos según demanda." - back_to_products_list: "Volver a la lista de productos" editing_product: "Editando producto" tabs: product_details: "Detalles del producto" @@ -994,10 +993,8 @@ es_CO: contact_name: Nombre de contacto edit: editing: 'Configuración:' - back_link: Volver a la lista de organizaciones new: title: Nueva Organización - back_link: Volver a la lista de organizaciones welcome: welcome_title: ¡Bienvenido a Open Food Network! welcome_text: Ha creado correctamente un @@ -1032,6 +1029,8 @@ es_CO: back_to_list: "Regresar a la lista" save_and_back_to_list: "Guardar y volver a lista" choose_products_from: "Escoger productos desde:" + date_time_warning_modal_content: + cancel: 'Cancelar' incoming: incoming: "Entrante" supplier: "Proveedor" @@ -2300,7 +2299,6 @@ es_CO: spree_admin_single_enterprise_hint: "Sugerencia: Para permitir que la gente te encuentre, activa tu visibilidad" spree_admin_eg_pickup_from_school: "p.ej. 'Recogida en la Cooperativa'" spree_admin_eg_collect_your_order: "p.ej. 'Por favor recoge tu pedido en la Calle del Hormiguero nº3'" - spree_classification_primary_taxon_error: "La clasificación %{taxon} es la clasificación primaria de %{product} y no puede ser eliminada" spree_order_availability_error: "El Distribuidor o el Ciclo de Pedido no pueden suministrar los productos en su carrito" spree_order_populator_error: "Este Distribuidor o Ciclo de Pedido no puede suministrar todos los productos en tu carrito. Por favor, elige otro." spree_order_populator_availability_error: "Este producto no está disponible por el distribuidor o Ciclo de Pedido elegido." @@ -2886,6 +2884,8 @@ es_CO: Esto establecerá el nivel de stock a cero en todos los productos para esta organización que no están presentes en el archivo subido. order_cycles: + unsaved_changes: "Tiene cambios sin guardar" + bulk_save_error: "¡Vaya! No se ha podido guardar los cambios." 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" @@ -3188,7 +3188,6 @@ es_CO: tax_rate_amount_explanation: "Las tarifas de impuestos son una cantidad decimal para ayudar en los cálculos (es decir, si la tasa de impuestos es del 5%, introduzca 0.05)" included_in_price: "Incluido en el precio" show_rate_in_label: "Mostrar tarifa en la etiqueta" - back_to_tax_rates_list: "Volver a la lista de tarifas de impuestos" tax_settings: "Configuración de Impuestos" zones: "Zonas" new_zone: "Nueva zona" @@ -3201,14 +3200,11 @@ es_CO: iso_name: "Nombre de ISO" states_required: "Provincias requeridas" editing_country: "Editar país" - back_to_countries_list: "Volver a la lista de países" states: "Provincias" abbreviation: "Abreviatura" new_state: "Nueva provincia" payment_methods: "Métodos de pago" - taxonomies: "Taxonomías" - new_taxonomy: "Nueva taxonomía" - back_to_taxonomies_list: "Volver a la lista de taxonomías" + taxons: "Categorías de producto" shipping_methods: "Métodos de envío" shipping_method: "Método de envío" shipment: "Envío" @@ -3342,7 +3338,6 @@ es_CO: continue: "Continuar" new: new_return_authorization: "Nueva autorización de devolución" - back_to_return_authorizations_list: "Volver a lista de autorización de devolución" continue: "Continuar" edit: receive: "recibir" @@ -3616,6 +3611,8 @@ es_CO: total: "Total" billing_address_name: "Nombre" taxons: + index: + title: "Categorías de producto" form: name: Nombre description: Descripción @@ -3796,7 +3793,6 @@ es_CO: key_cleared: "Llave borrada" shipment: cannot_ready: "No se puede completar envío" - invalid_taxonomy_id: "El identificador de taxonomía es inválido." activerecord: models: spree/payment: diff --git a/config/locales/es_CR.yml b/config/locales/es_CR.yml index c281024b48..85a63c3e73 100644 --- a/config/locales/es_CR.yml +++ b/config/locales/es_CR.yml @@ -70,7 +70,6 @@ es_CR: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "debe estar en blanco porque está usando la configuración de inventario del productor" - on_demand_but_count_on_hand_set: "debe estar en blanco si es bajo demanda" limited_stock_but_no_count_on_hand: "debe estar especificado porque se ha definido un inventario limitado" messages: blank: "no puede estar en blanco" @@ -407,7 +406,6 @@ es_CR: password_confirmation: Confirmación de contraseña reset_password_token: Token de restablecimiento de contraseña expired: ha expirado, por favor solicite una nueva - back_to_payments_list: "Volver a la lista de pagos" maestro_or_solo_cards: "Solo tarjetas MasterCard" backordered: "Pedido pendiente" on_hand: "Disponibles" @@ -474,6 +472,8 @@ es_CR: remove: Eliminar image: edit: Editar + product_preview: + shop_tab: Tienda adjustments: skipped_changing_canceled_order: "No se puede cambiar una orden cancelada" begins_at: Empieza en @@ -677,7 +677,6 @@ es_CR: variants: infinity: "infinito" to_order_tip: "Los artículos hechos según el pedido no tienen un nivel fijado de inventario, como por ejemplo panes hechos según demanda." - back_to_products_list: "Volver a la lista de productos" editing_product: "Editando producto" tabs: product_details: "Detalles del producto" @@ -1171,10 +1170,8 @@ es_CR: contact_name: Nombre de contacto edit: editing: 'Configuración:' - back_link: Volver a la lista de organizaciones new: title: Nueva Organización - back_link: Volver a la lista de organizaciones welcome: welcome_title: ¡Bienvenido a Open Food Network (LaFeriaCR)! welcome_text: Ha creado correctamente un @@ -1211,6 +1208,8 @@ es_CR: choose_products_from: "Escoger productos desde:" re_notify_producers: Re notificar a los productores notify_producers_tip: Esto enviará un correo electrónico a cada productor con la lista de sus pedidos. + date_time_warning_modal_content: + cancel: 'Cancelar' incoming: incoming: "Entrante" supplier: "Proveedor" @@ -2579,7 +2578,6 @@ es_CR: spree_admin_single_enterprise_hint: "Sugerencia: Para permitir que la gente te encuentre, activa tu visibilidad" spree_admin_eg_pickup_from_school: "p.ej. 'Recogida en la Cooperativa'" spree_admin_eg_collect_your_order: "p.ej. 'Por favor recoge tu pedido en la Calle del Hormiguero nº3'" - spree_classification_primary_taxon_error: "La clasificación %{taxon} es la clasificación primaria de %{product} y no puede ser eliminada" spree_order_availability_error: "El Distribuidor o el Ciclo de Pedido no pueden suministrar los productos en su carrito" spree_order_populator_error: "Este Distribuidor o Ciclo de Pedido no puede suministrar todos los productos en tu carrito. Por favor, elige otro." spree_order_cycle_error: "Elija un ciclo de pedido para este pedido." @@ -3209,6 +3207,8 @@ es_CR: Esto establecerá el nivel de stock a cero en todos los productos para esta organización que no están presentes en el archivo subido. order_cycles: + unsaved_changes: "Tiene cambios sin guardar" + bulk_save_error: "¡Vaya! No se ha podido guardar los cambios." 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" @@ -3447,7 +3447,6 @@ es_CR: more: "Más" new_adjustment: "Nuevo ajuste" new_tax_category: "Nueva categoría de impuestos" - new_taxon: "Nuevo taxon" new_user: "Nuevo usuari" no_pending_payments: "No tiene pagos pendientes" remove: "Eliminar" @@ -3539,7 +3538,6 @@ es_CR: tax_rate_amount_explanation: "Las tarifas de impuestos son una cantidad decimal para ayudar en los cálculos (es decir, si la tasa de impuestos es del 5%, introduzca 0.05)" included_in_price: "Incluido en el precio" show_rate_in_label: "Mostrar tarifa en la etiqueta" - back_to_tax_rates_list: "Volver a la lista de tarifas de impuestos" tax_settings: "Configuración de Impuestos" zones: "Zonas" new_zone: "Nueva zona" @@ -3552,14 +3550,11 @@ es_CR: iso_name: "Nombre de ISO" states_required: "Provincias requeridas" editing_country: "Editar país" - back_to_countries_list: "Volver a la lista de países" states: "Provincias" abbreviation: "Abreviatura" new_state: "Nueva provincia" payment_methods: "Métodos de pago" - taxonomies: "Taxonomías" - new_taxonomy: "Nueva taxonomía" - back_to_taxonomies_list: "Volver a la lista de taxonomías" + taxons: "Categorías de producto" shipping_methods: "Métodos de envío" shipping_method: "Método de envío" shipment: "Envío" @@ -3691,7 +3686,6 @@ es_CR: continue: "Continuar" new: new_return_authorization: "Nueva autorización de devolución" - back_to_return_authorizations_list: "Volver a lista de autorización de devolución" continue: "Continuar" edit: receive: "recibir" @@ -3955,9 +3949,10 @@ es_CR: total: "Total" billing_address_name: "Nombre" taxons: + index: + title: "Categorías de producto" form: name: Nombre - permalink: Enlace permanente description: Descripción general_settings: edit: @@ -4123,7 +4118,6 @@ es_CR: key_cleared: "Llave borrada" shipment: cannot_ready: "No se puede completar envío" - invalid_taxonomy_id: "El identificador de taxonomía es inválido." unit: unidad components: search_input: diff --git a/config/locales/es_US.yml b/config/locales/es_US.yml index 21f59cf2fc..62d008adeb 100644 --- a/config/locales/es_US.yml +++ b/config/locales/es_US.yml @@ -68,7 +68,6 @@ es_US: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "debe estar en blanco porque estás usando la configuración de estoc de la productora" - on_demand_but_count_on_hand_set: "debe estar en blanco si está bajo demanda" limited_stock_but_no_count_on_hand: "se debe especificar porque se ha definido estoc limitado" messages: blank: "no puede estar vacío" @@ -382,7 +381,7 @@ es_US: password_confirmation: Confirmación de contraseña reset_password_token: token de restablecimiento de contraseña expired: ha expirado, por favor solicite una nueva - back_to_payments_list: "Volver a la lista de pagos" + back_to_payments_list: "Regresar a la lista de pagos" maestro_or_solo_cards: "Tarjetas Maestro/Solo" backordered: "Reabastecido" on_hand: "Disponibles" @@ -449,6 +448,8 @@ es_US: remove: Eliminar image: edit: Editar + product_preview: + shop_tab: Tienda adjustments: skipped_changing_canceled_order: "No puede modificar un pedido cancelado." begins_at: Empieza en @@ -647,7 +648,6 @@ es_US: variants: infinity: "infinito" to_order_tip: "Los artículos hechos según demanda no tienen un nivel de stock, como por ejemplo panes hechos según demanda." - back_to_products_list: "Volver a la lista de productos" editing_product: "Editando producto" tabs: product_details: "Detalles del Producto" @@ -1124,10 +1124,8 @@ es_US: contact_name: Nombre de Contacto edit: editing: 'Configuración:' - back_link: Volver a la lista de organizaciones new: title: Nueva Organización - back_link: Volver a la lista de organizaciones welcome: welcome_title: Bienvenida a Open Food Network! welcome_text: Has creado correctamente un @@ -1164,6 +1162,8 @@ es_US: choose_products_from: "Escoger Productos desde:" re_notify_producers: Re notificar a productores notify_producers_tip: Esto enviará un correo electrónico a cada productor con la lista de sus pedidos. + date_time_warning_modal_content: + cancel: 'Cancelar' incoming: incoming: "Entrante" supplier: "Proveedora" @@ -2466,7 +2466,6 @@ es_US: spree_admin_single_enterprise_hint: "Sugerencia: Para permitir que la gente te encuentre, activa tu visibilidad" spree_admin_eg_pickup_from_school: "p.ej. 'Recogida en la Cooperativa'" spree_admin_eg_collect_your_order: "p.ej. 'Por favor recoge tu pedido en la Calle del Hormiguero nº3'" - spree_classification_primary_taxon_error: "La clasificación %{taxon} es la clasificación primaria de %{product} y no puede ser eliminada" spree_order_availability_error: "El Distribuidor o el Ciclo de Pedido no pueden suministrar los productos en su carrito" spree_order_populator_error: "Este Distribuidor o Ciclo de Pedido no puede suministrar todos los productos en tu carrito. Por favor, elige otro." spree_order_cycle_error: "Elija un ciclo de pedido para este pedido." @@ -3076,6 +3075,8 @@ es_US: 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: + unsaved_changes: "Tienes cambios sin guardar" + bulk_save_error: "¡Vaya! No se ha podido guardar los cambios." 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" @@ -3315,7 +3316,6 @@ es_US: resend_authorization_email: "Reenviar email de autorización" refund: "Reembolso" server_error: "Error del Servidor" - tree: "Árbol" updating: "Actualizando" your_order_is_empty_add_product: "Su pedido está vacío, busque y añada un producto arriba" add_product: "Añadir Producto" @@ -3410,7 +3410,6 @@ es_US: tax_rate_amount_explanation: "Las tasas de impuestos son una cantidad decimal para ayudar en los cálculos (es decir, si la tasa de impuestos es del 5%, introduzca 0.05)" included_in_price: "Incluido en el precio" show_rate_in_label: "Mostrar impuesto en la etiqueta" - back_to_tax_rates_list: "Volver a la lista de impuestos" tax_settings: "Configuración de Impuestos" zones: "Zonas" new_zone: "Nueva zona" @@ -3423,14 +3422,11 @@ es_US: iso_name: "Nombre ISO" states_required: "Estados requeridos" editing_country: "Editar país" - back_to_countries_list: "Volver a la lista de países" states: "Estados" abbreviation: "Abreviatura" new_state: "Nuevo estado" payment_methods: "Métodos de Pago" - taxonomies: "Taxonomias" - new_taxonomy: "Nueva taxonomía" - back_to_taxonomies_list: "Volver a la Lista de Taxonomías" + taxons: "Categorías de Producto" shipping_methods: "Métodos de envío" shipping_method: "Método de envío" shipment: "Envío" @@ -3584,7 +3580,6 @@ es_US: continue: "Continuar" new: new_return_authorization: "Nueva autorización de devolución" - back_to_return_authorizations_list: "Back To Return Authorization List" continue: "Continuar" edit: receive: "recibir" @@ -3872,9 +3867,10 @@ es_US: total: "Total" billing_address_name: "Nombre" taxons: + index: + title: "Categorías de Producto" form: name: Nombre - permalink: Enlace permanente description: Descripción general_settings: edit: @@ -4091,7 +4087,6 @@ es_US: key_cleared: "valor borrado" shipment: cannot_ready: "No se puede completar envío" - invalid_taxonomy_id: "El identificador de taxonomía es inválido." activerecord: models: spree/payment: diff --git a/config/locales/fil_PH.yml b/config/locales/fil_PH.yml index f9a4b73116..e69a44085e 100644 --- a/config/locales/fil_PH.yml +++ b/config/locales/fil_PH.yml @@ -61,7 +61,6 @@ fil_PH: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "ay dapat blanko sapagkat ginagamit ang stock settings ng producer" - on_demand_but_count_on_hand_set: "ay dapat blanko kung on demand" limited_stock_but_no_count_on_hand: "ay dapat matukoy sapagkat ipipilit ang limitadong stock" messages: blank: "hindi maaaring iwanang blanko" @@ -279,7 +278,6 @@ fil_PH: password_confirmation: Kumpirmasyon ng password reset_password_token: i-reset ang password token expired: ay expired na, magrequest ng bago - back_to_payments_list: "bumalik sa listahan ng mga bayad" maestro_or_solo_cards: "Maestro/Solo cards" backordered: "na-backorder" on_hand: "on hand" @@ -336,6 +334,8 @@ fil_PH: remove: Tanggalin image: edit: i-edit + product_preview: + shop_tab: Shop begins_at: nagsimula sa begins_on: nagsisimula sa customer: Customer @@ -514,7 +514,6 @@ fil_PH: variants: infinity: "walang katapusan" to_order_tip: "ang mga item na made to order ay hindi maaaring i-set ng lebel ng stock, tulad ng mga tinapay na ginagawa lamang kapag may order." - back_to_products_list: "bumalik sa listahan ng mga produkto" editing_product: "ine-edit ang mga produkto" tabs: product_details: "mga detalye ng produkto" @@ -962,10 +961,8 @@ fil_PH: contact_name: Contact name edit: editing: 'Settings:' - back_link: bumalik sa listahan ng mga enterprise new: title: bagong enterprise - back_link: bumalik sa listahan ng mga enterprise welcome: welcome_title: Welcome sa Open Food Network! welcome_text: matagumpay mong nagawa ang @@ -1000,6 +997,8 @@ fil_PH: back_to_list: "bumalik sa listahan" save_and_back_to_list: "i-save at bumalik sa listahan" choose_products_from: "pumili ng mga produkto mula sa:" + date_time_warning_modal_content: + cancel: 'kanselahin' incoming: incoming: "paparating" supplier: "Supplier" @@ -2244,7 +2243,6 @@ fil_PH: spree_admin_single_enterprise_hint: "payo: para mahanap ka ng ibang tao, i-turn on ang visibility sa ilalim ng" spree_admin_eg_pickup_from_school: "hal. 'Pick-up mula sa Primary School'" spree_admin_eg_collect_your_order: "hal. 'Kolektahin ang inyong order sa 123 Kapayapaan St., Tobias Fornier, Antique 5716'" - spree_classification_primary_taxon_error: "ang Tax sa%{taxon}ay ang pangunahing tax ng %{product}at hindi maaaring tanggalin" spree_order_availability_error: "ang distributor o order cycle ay hindi kayang ibigay ang mga produkto sa inyong cart" spree_order_populator_error: "ang distributor o order cycle na ito ay hindi kayang ibigay lahat ng mga produkto sa inyong cart. pumili ng iba." spree_order_populator_availability_error: "ang produktong ito ay hindi ngayon available mula sa napiling distributor o order cycle." @@ -2520,7 +2518,7 @@ fil_PH: order_cycles_bulk_update_notice: 'ang mga order cycle ay na-update na' order_cycles_no_permission_to_coordinate_error: "wala sa inyong mga enterprise ay may pahintulot na magsa-ayos ng order cycle" order_cycles_no_permission_to_create_error: "wala kang pahintulot na gumawa ng order cycle na isinaayos ng enterprise na iyon." - back_to_orders_list: "bumalik sa listahan ng order" + back_to_orders_list: "bumalik sa listahan ng mga order" no_orders_found: "walang mahanap na mga order" order_information: "impormasyon tungkol sa order" new_payment: "Bagong Pagbabayad" @@ -2827,6 +2825,8 @@ fil_PH: ise-set nito ang lebel ng stock sa zero para sa lahat ng mga produkto para dito enterprise na hindi makikita sa inupload na file. order_cycles: + unsaved_changes: "May mga pagbabago na hindi na-save" + bulk_save_error: "paumanhin! hindi ko na-save ang mga pagbabagong ginawa mo." create_failure: "hindi nagtagumpay sa paggawa ng order cycle" update_success: 'ang iyong order cycle ay na-update na' update_failure: "hindi nagtagumpay sa pag-update ng order cycle" @@ -3093,7 +3093,6 @@ fil_PH: tax_rate_amount_explanation: "ang mga rate ng tax ay mga halagang may decimal na makakatulong sa pagkukuwenta, (hal. kung ang rate ng tax ay 5% ang ilalagay ay 0.05)" included_in_price: "nakasama sa presyo" show_rate_in_label: "ipakita ang rate sa tatak" - back_to_tax_rates_list: "bumalik sa listahan ng rate ng tax" tax_settings: "Settings ng tax" zones: "mga sona" new_zone: "bagong sona" @@ -3106,14 +3105,11 @@ fil_PH: iso_name: "pangalan ng ISO" states_required: "Kailangang nakasaad ang mga Lalawigan" editing_country: "pag-edit ng bansa" - back_to_countries_list: "bumalik sa listahan ng mga bansa" states: "Mga Lalawigan" abbreviation: "Pinaikli" new_state: "Bagong Status" payment_methods: "Mga Paraan ng Pagbabayad" - taxonomies: "taxonomies" - new_taxonomy: "bagong taxonomy" - back_to_taxonomies_list: "bumalik sa listahan ng mga taxonomy" + taxons: "kategorya ng produkto" shipping_methods: "mga paraan ng pagpapadala" shipping_method: "paraan ng pagpapadala" shipment: "kargamento" @@ -3244,7 +3240,6 @@ fil_PH: continue: "Magpatuloy" new: new_return_authorization: "bagong awtorisasyon sa pagbabalik" - back_to_return_authorizations_list: "bumalik sa listahan ng awtorisasyon sa pagbabalik" continue: "Magpatuloy" edit: receive: "tumanggap" @@ -3502,6 +3497,8 @@ fil_PH: total: "kabuuan" billing_address_name: "Pangalan" taxons: + index: + title: "kategorya ng produkto" form: name: Pangalan description: paglalarawan @@ -3668,7 +3665,6 @@ fil_PH: key_cleared: "binura ang key" shipment: cannot_ready: "hindi maihanda ang kargamento" - invalid_taxonomy_id: "hindi valid na taxonomy id." unit: yunit components: search_input: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ca96bfcd67..7ce1745a4e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -82,7 +82,7 @@ fr: errors: models: enterprise_fee: - inherit_tax_requires_per_item_calculator: "Déterminer la catégorie de taxe requiert l'utilisation du calculateur par article. " + inherit_tax_requires_per_item_calculator: "Déterminer la catégorie de taxe requiert l'utilisation du calculateur par article" spree/image: attributes: attachment: @@ -99,8 +99,6 @@ fr: attributes: base: card_expired: "a expiré" - spree/product: - must_exist: 'doit exister' order_cycle: attributes: orders_close_at: @@ -108,7 +106,6 @@ fr: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "doit être vide car utilise les informations de stock du producteur" - on_demand_but_count_on_hand_set: "doit être vide si \"à volonté\"" limited_stock_but_no_count_on_hand: "doit être spécifié car pas \"à volonté\"" messages: confirmation: "ne correspond pas %{attribute}" @@ -676,6 +673,16 @@ fr: info_html: "Matomo est un outil de suivi de traffic et de comportement des utilisateurs. Matomo est open source et validée par la CNIL française car non intrusif dans les données personnelles des utilisateurs. Pour plus d'infos : matomo.org." config_instructions_html: "Pour utiliser Matomo, vous devez configurer l'intégration avec CoopCircuits. L'URL de l'instance sur Matomo correspond à l'url du site internet visé par le suivi de la navigation utilisateur. Si le champ est vide, Matomo n'effectuera aucune analyse sur ce site. L'ID de l'instance sur Matomo n'est pas obligatoire, mais nécessaire si vous souhaitez analyser plusieurs sites web sur une seule instance Matomo. Cet ID peut être trouvé sur l'espace administrateur Matomo." config_instructions_tag_manager_html: "Ajouter l'URL du Tag Manager rend actif Matomo Tag Manager. Cet outil vous permet de suivre des évènements en particulier. Pour trouver l'URL rendez-vous sur l' \"Install Code section\" du Tag Manager. Attention à bien copier le bon contener et le bon environnement, car ils font varier l'URL." + connected_app_settings: + edit: + title: "Paramètres de l'application connectée" + info_html: "Les applications autorisées apparaitront dans Entreprises > Configuration > Applications connectées" + enabled_legend: "Applications connectées autorisées" + connected_apps_enabled: + discover_regen: Portail Discover Regenerative + affiliate_sales_data: API de commandes anonymisées DFC à fins de recherche + update: + resource: Paramètres de l'application connectée customers: index: new_customer: "Nouvel acheteur" @@ -773,7 +780,7 @@ fr: variants: infinity: "Infini" to_order_tip: "Les articles fabriqués sur commande n'ont pas un niveau de stock défini, comme des pains faits à la main." - back_to_products_list: "Retour à la liste produits" + back_to_products_list: "Revenir à la liste des produits" editing_product: "Modifier le produit" tabs: product_details: "Détails" @@ -813,7 +820,10 @@ fr: search: Rechercher sort: pagination: - total_html: "%{total} produits trouvés selon vos critères de recherche. Résultats %{from} à %{to}." + products_total_html: + one: "%{count} produit trouvé pour vos critères de recherche. Affichage %{from} à %{to} ." + many: "%{count} produits trouvés pour vos critères de recherche. Affichage %{from} à %{to} ." + other: "%{count} produits trouvés pour vos critères de recherche. Affichage %{from} à %{to} ." per_page: show: Montrer per_page: "%{num} par page" @@ -1386,7 +1396,7 @@ fr: contact_name: Nom du contact principal edit: editing: 'Configuration:' - back_link: Revenir à la liste des entreprises + back_link: Retour à la liste des entreprises new: title: Nouvelle entreprise back_link: Revenir à la liste des entreprises @@ -1426,6 +1436,11 @@ fr: choose_products_from: "Choisir produits depuis :" re_notify_producers: Notifier les producteurs notify_producers_tip: Cette action va envoyer un mail à tous les producteurs avec le contenu du cycle de vente. + date_time_warning_modal_content: + title: 'Les commandes sont liées à ce cycle de vente.' + content: 'Si vous souhaitez créer un nouveau cycle de vente, il est recommandé de dupliquer un cycle de vente existant puis de changer les dates.' + proceed: 'Procéder quand même' + cancel: 'Annuler' incoming: incoming: "Produits entrants (pouvant être mis en vente par les boutiques du cycle de vente)" supplier: "Fournisseur" @@ -2889,7 +2904,6 @@ fr: spree_admin_single_enterprise_hint: "Astuce: Pour permettre aux gens de vous trouver, activez votre visibilité " spree_admin_eg_pickup_from_school: "ex : \"Retrait des produits à l'Ecole Marimati / Au Café du coin / chez Babette / ...\"" spree_admin_eg_collect_your_order: "ex : \"Veuillez récupérer votre commande au 34 rue Victor Hugo, 75018 Paris\"" - spree_classification_primary_taxon_error: "La catégorie %{taxon} est utilisée par %{product} et ne peut être supprimée" spree_order_availability_error: "Le distributeur ne peut fournir les produits de votre panier pour ce cycle de vente." spree_order_populator_error: "Le distributeur ne peut fournir tous les produits de votre panier pour ce cycle de vente. Merci de choisir un autre distributeur ou un autre cycle de vente." spree_order_cycle_error: "Veuillez sélectionner une option pour cette commande." @@ -3581,6 +3595,8 @@ fr: Cette action remettra tous les niveaux de stock à zero pour cette entreprise pour les produits non présents dans ce fichier. order_cycles: + unsaved_changes: "Des modifications n'ont pas été sauvegardées" + bulk_save_error: "Oups ! Nous n'avons pas réussi à sauvegarder vos modification :-(" create_failure: "La création du cycle de vente a échoué" update_success: 'Votre cycle de vente a été mis à jour.' update_failure: "La mise à jour du cycle de vente à échoué" @@ -3828,7 +3844,6 @@ fr: more: "Plus" new_adjustment: "Nouvel ajustement" new_tax_category: "Nouvelle catégorie de taxe" - new_taxon: "Nouvelle taxonomie" new_user: "Nouvel utilisateur" no_pending_payments: "Aucun paiement en attente." remove: "Supprimer" @@ -3847,10 +3862,6 @@ fr: delivery: "Signé, scellé, livré" start_date: "Date de début" successfully_removed: "Supprimé avec succès" - taxonomy_edit: "Modifier la taxonomie" - taxonomy_tree_error: "Erreur lors de la mise à jour de l'arbre de taxonomie." - taxonomy_tree_instruction: "Faites un clic droit sur un article pour ajouter, renommer, supprimer ou modifier." - tree: "Arbre" updating: "Mettre à jour" your_order_is_empty_add_product: "Votre commande est vide, veuillez ajouter des produits" add_product: "Ajouter un produit" @@ -3948,7 +3959,7 @@ fr: tax_rate_amount_explanation: "Les taux de taxe sont présentés en nombres décimaux pour faciliter les calculs (ex : si le taux est 5,5% saisissez 0.055)" included_in_price: "Inclus dans le prix" show_rate_in_label: "Montrer le taux dans le nom" - back_to_tax_rates_list: "Retour à la liste des taux de taxe" + back_to_tax_rates_list: "Revenir à la liste des taux de taxe" tax_settings: "Paramètres TVA" zones: "Zones" new_zone: "Nouvelle zone" @@ -3961,14 +3972,12 @@ fr: iso_name: "Noms ISO" states_required: "États/Départements requis" editing_country: "Pays en cours de mise à jour" - back_to_countries_list: "Retour à la liste des pays" + back_to_countries_list: "Revenir à la liste des pays" states: "Départements" abbreviation: "Code" new_state: "Nouveau département" payment_methods: "Méthodes de paiement" - taxonomies: "Taxonomies" - new_taxonomy: "Nouvelle taxonomie" - back_to_taxonomies_list: "Retour à la liste des taxonomies" + taxons: "Catégorie Produit" shipping_methods: "Méthodes de livraison" shipping_method: "Option d'expédition" shipment: "Livraison" @@ -4341,6 +4350,8 @@ fr: new_product: "Nouveau Produit" supplier: "Fournisseur" supplier_select_placeholder: "Sélectionner un producteur" + search_for_suppliers: "Chercher des fournisseurs" + search_for_units: "Chercher des unités" product_name: "Nom du Produit" units: "Unité de mesure" value: "Quantité" @@ -4375,6 +4386,7 @@ fr: product_name: Nom du Produit primary_taxon_form: product_category: Catégorie Produit + search_for_categories: "Rechercher" group_buy_form: group_buy: "Achat groupé de lots fixes ?" bulk_unit_size: Quantité totale du lot @@ -4452,9 +4464,20 @@ fr: total: "Total" billing_address_name: "Nom" taxons: + back_to_list: "Revenir à la liste des catégories de produits" + index: + title: "Catégorie Produit" + new_taxon: 'Nouvelle catégorie de produit' + new: + title: "Nouvelle catégorie de produit" + edit: + title: "Editer la catégorie de produit" + destroy: + delete_taxon: + success: "Catégorie de produit supprimée" + error: "Impossible de supprimer la catégorie de produit car des produits sont liés à cette catégorie." form: name: Nom - permalink: Permalien meta_title: Méta titre meta_description: Méta description meta_keywords: Méta mots clés @@ -4697,7 +4720,6 @@ fr: key_cleared: "Clé supprimée" shipment: cannot_ready: "La livraison ne peut pas être préparée." - invalid_taxonomy_id: "L'identifiant de la catégorie n'est pas valide." toggle_api_key_view: "Montrer la clé API pour cet utilisateur" activerecord: models: diff --git a/config/locales/fr_BE.yml b/config/locales/fr_BE.yml index a05d383b5b..af40396788 100644 --- a/config/locales/fr_BE.yml +++ b/config/locales/fr_BE.yml @@ -73,8 +73,6 @@ fr_BE: white_label_logo_link: "Lien pour le logo utilisé en vitrine" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "L'héritage de la catégorie de taxe nécessite un calculateur par article." spree/user: attributes: email: @@ -94,7 +92,6 @@ fr_BE: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "doit être vide car les paramètres de stock du producteur sont utilisés" - on_demand_but_count_on_hand_set: "doit être vide si à la demande" limited_stock_but_no_count_on_hand: "doit être spécifié car le stock limité est forcé" messages: confirmation: "ne correspond pas à %{attribute}" @@ -488,6 +485,8 @@ fr_BE: remove: Supprimer image: edit: Modifier + product_preview: + shop_tab: Comptoir adjustments: skipped_changing_canceled_order: "Vous ne pouvez pas modifier une commande annulée." begins_at: Commence @@ -693,7 +692,7 @@ fr_BE: variants: infinity: "Infinité" to_order_tip: "Les articles fabriqués sur commande n'ont pas un niveau de stock défini, comme des pains faits à la main." - back_to_products_list: "Retour à la liste produits" + back_to_products_list: "Retour à la liste des produits" editing_product: "Éditer le produit" tabs: product_details: "Détails du produit" @@ -720,7 +719,10 @@ fr_BE: search: Rechercher sort: pagination: - total_html: "%{total} produits trouvés pour vos critères de recherche. Affichage %{from} à %{to} ." + products_total_html: + one: "%{count} produit trouvé pour vos critères de recherche. Affichage %{from} à %{to} ." + many: "%{count} produits trouvés pour vos critères de recherche. Affichage %{from} à %{to} ." + other: "%{count} produits trouvés pour vos critères de recherche. Affichage %{from} à %{to} ." per_page: show: Montrer per_page: "%{num} par page" @@ -1232,10 +1234,8 @@ fr_BE: contact_name: Nom du contact principal edit: editing: 'Configuration:' - back_link: Revenir à la liste des entreprises new: title: Nouvelle entreprise - back_link: Revenir à la liste des entreprises welcome: welcome_title: Bienvenue sur Open Food Network ! welcome_text: 'Vous avez créé avec succès ' @@ -1272,6 +1272,11 @@ fr_BE: choose_products_from: "Choisir produits depuis :" re_notify_producers: Notifier les producteurs notify_producers_tip: Cette action va envoyer un mail à tous les producteurs avec leur contenu du cycle de vente. + date_time_warning_modal_content: + title: 'Des commandes sont liées à ce cycle de commande.' + content: 'Si vous souhaitez créer un nouveau cycle de commande, il est recommandé de dupliquer d''abord le cycle de commande, puis de modifier les dates.' + proceed: 'Continuer quand même' + cancel: 'Annuler' incoming: incoming: "Produits entrants (pouvant être mis en vente par les comptoirs)" supplier: "Disitributeur·trice" @@ -1469,6 +1474,9 @@ fr_BE: Désolé, ce rapport a pris trop de temps à traiter. Il peut contenir beaucoup de données ou nous sommes occupés par d'autres rapports. Vous pouvez réessayer plus tard. + report_taking_longer_html: > + Le traitement de ce rapport prend plus de temps. Il peut contenir beaucoup + de données ou nous sommes occupés avec d'autres rapports. report_link_label: Télécharger le rapport (si disponible) revenues_by_hub: name: CA par comptoir @@ -2658,7 +2666,6 @@ fr_BE: spree_admin_single_enterprise_hint: "Astuce: Pour permettre aux gens de vous trouver, activez votre visibilité " spree_admin_eg_pickup_from_school: "ex : \"Retrait des produits à l'Ecole Marimati / Au Café du coin / chez Babette / ...\"" spree_admin_eg_collect_your_order: "ex : \"Veuillez récupérer votre commande au 131 Rue Provinciale, 1300 Wavre\"" - spree_classification_primary_taxon_error: "La catégorie %{taxon}est utilisée par %{product} et ne peut être supprimée" spree_order_availability_error: "Le distributeur ne peut fournir les produits de votre panier pour ce cycle de vente." spree_order_populator_error: "Le distributeur ne peut fournir tous les produits de votre panier pour ce cycle de vente. Merci de choisir un autre distributeur ou un autre cycle de vente." spree_order_cycle_error: "Veuillez choisir un cycle de commande pour cette commande." @@ -2971,7 +2978,7 @@ fr_BE: order_cycles_no_permission_to_coordinate_error: "Aucune de vos entreprises n'a les droits requis pour coordonner un cycle de vente" order_cycles_no_permission_to_create_error: "Vous n'avez pas les droits requis pour créer un cycle de vente coordonné par cette entreprise" order_cycle_closed: "Le cycle de commande que vous avez sélectionner vient de fermer. Essayer plus tard." - back_to_orders_list: "Retour à la liste des commandes" + back_to_orders_list: "Retour vers la liste des commandes " no_orders_found: "Aucune commande trouvée pour ces critères" order_information: "Info commande" new_payment: "Nouveau paiement" @@ -3316,6 +3323,8 @@ fr_BE: Cette action remettra tous les niveaux de stock à zero pour cette entreprise pour les produits non présents dans ce fichier. order_cycles: + unsaved_changes: "Des modifications n'ont pas été sauvegardées" + bulk_save_error: "Oups ! Nous n'avons pas réussi à sauvegarder vos modification :-(" create_failure: "La création du cycle de vente a échoué" update_success: 'Votre cycle de vente a été mis à jour.' update_failure: "La mise à jour du cycle de vente a échoué" @@ -3462,13 +3471,13 @@ fr_BE: notice_messages: variant_deleted: "Variante supprimée" start_date: "Date de début" - taxonomy_tree_instruction: "Faites un clic droit sur un élément pour ajouter, renommer, supprimer ou modifier." updating: "Mettre à jour" your_order_is_empty_add_product: "Votre commande est vide, merci de chercher et d'ajouter un des produits ci-dessus" add_product: "Ajoutez un produit" name_or_sku: "Nom ou N° d'article (entrer au moins 4 lettres du nom du produit) " resend: "Renvoyer" back_to_orders_list: "Retour vers la liste des commandes " + back_to_payments_list: "Retour à la liste des paiements" back_to_states_list: "Retour à la liste des provinces" return_authorizations: "Autorisations de retour" cannot_create_returns: "Impossible de créer des retours car cette commande n'a pas d'unités expédiées." @@ -3553,7 +3562,6 @@ fr_BE: tax_rate_amount_explanation: "Les taux sont décimaux pour faciliter les calculs (par ex. un taux de 5% sera entré en tant que 0.05) " included_in_price: "Inclus dans le prix " show_rate_in_label: "Montrer le taux sur l'étiquette " - back_to_tax_rates_list: "Retour à la liste des taux de TVA" tax_settings: "Paramètres TVA" zones: "Zones" new_zone: "Nouvelle zone " @@ -3566,14 +3574,11 @@ fr_BE: iso_name: "Nom ISO" states_required: "Provinces requises" editing_country: "Éditer le pays" - back_to_countries_list: "Retouner vers la liste des pays" states: "Provinces" abbreviation: "Abréviation" new_state: "Nouvelle Province" payment_methods: "Méthodes de paiement" - taxonomies: "Taxonomies " - new_taxonomy: "Nouvelle taxonomie" - back_to_taxonomies_list: "Retour à la liste des taxonomies" + taxons: "Catégorie Produit" shipping_methods: "Méthodes de livraison" shipping_method: "Méthode de Livraison" shipment: "Envoi" @@ -3724,7 +3729,6 @@ fr_BE: continue: "Suivant" new: new_return_authorization: "Nouvelle autorisation de retour" - back_to_return_authorizations_list: "Retour vers la liste d'autorisation de retour" continue: "Suivant" edit: receive: "recevoir" @@ -3952,6 +3956,7 @@ fr_BE: product_name: Nom du Produit primary_taxon_form: product_category: Catégorie Produit + search_for_categories: "Rechercher des catégories" group_buy_form: group_buy: "Achat groupé de lots fixes ?" bulk_unit_size: Quantité totale du lot @@ -4026,9 +4031,10 @@ fr_BE: total: "Total" billing_address_name: "Nom" taxons: + index: + title: "Catégorie Produit" form: name: Nom - permalink: 'Permalien : Nom pour URL (sans espace)' description: Description general_settings: edit: @@ -4220,7 +4226,6 @@ fr_BE: key_cleared: "clé effacée" shipment: cannot_ready: "Impossible de préparer l'expédition." - invalid_taxonomy_id: "Id taxonomique invalide." unit: unité per_unit: Montant par unité datetime: diff --git a/config/locales/fr_CA.yml b/config/locales/fr_CA.yml index cdae1f1664..a9d6950651 100644 --- a/config/locales/fr_CA.yml +++ b/config/locales/fr_CA.yml @@ -106,7 +106,6 @@ fr_CA: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "doit être vide car utilise les informations de stock du producteur" - on_demand_but_count_on_hand_set: "doit être vide si \"à volonté\"" limited_stock_but_no_count_on_hand: "doit être spécifié car pas \"à volonté\"" messages: confirmation: "ne correspond pas %{attribute}" @@ -557,6 +556,8 @@ fr_CA: remove: Supprimer image: edit: Modifier + product_preview: + shop_tab: Boutique adjustments: skipped_changing_canceled_order: "Vous ne pouvez pas modifier une commande annulée." begins_at: Commence à @@ -675,6 +676,16 @@ fr_CA: info_html: "Matomo est un outil de suivi de traffic et de comportement des utilisateurs. Matomo est open source et validée par la CNIL française car non intrusif dans les données personnelles des utilisateurs. Pour plus d'infos : 1matomo.org1." config_instructions_html: "Pour utiliser Matomo, vous devez configurer l'intégration avec Open Food France. L'URL de l'instance sur Matomo correspond à l'url du site internet visé par le suivi de la navigation utilisateur. Si le champ est vide, Matomo n'effectuera aucune analyse sur ce site. L'ID de l'instance sur Matomo n'est pas obligatoire, mais nécessaire si vous souhaitez analyser plusieurs sites web sur une seule instance Matomo. Cet ID peut être trouvé sur l'espace administrateur Matomo." config_instructions_tag_manager_html: "Ajouter l'URL du Tag Manager rend actif Matomo Tag Manager. Cet outil vous permet de suivre des évènements en particulier. Pour trouver l'URL rendez-vous sur l' \"Install Code section\" du Tag Manager. Attention à bien copier le bon contener et le bon environnement, car ils font varier l'URL." + connected_app_settings: + edit: + title: "Paramètres de l'application connectée" + info_html: "Les applications autorisées apparaitront dans Entreprises > Configuration > Applications connectées" + enabled_legend: "Applications connectées autorisées" + connected_apps_enabled: + discover_regen: Portail Discover Regenerative + affiliate_sales_data: API de commandes anonymisées DFC à fins de recherche + update: + resource: Paramètres de l'application connectée customers: index: new_customer: "Nouveau client" @@ -772,7 +783,7 @@ fr_CA: variants: infinity: "Infini" to_order_tip: "Les articles fabriqués sur commande n'ont pas un niveau de stock défini, comme des pains faits à la main." - back_to_products_list: "Retour à la liste produits" + back_to_products_list: "Revenir à la liste des produits" editing_product: "Modifier le produit" tabs: product_details: " Détails" @@ -800,8 +811,10 @@ fr_CA: filters: search_products: Chercher des produits search_for_producers: Rechercher des producteurs + select_producer: Sélectionner producteur all_producers: Tous les producteurs search_for_categories: Rechercher des catégories + select_category: Sélectionner catégorie all_categories: Toutes les catégories producers: label: Producteurs @@ -810,7 +823,6 @@ fr_CA: search: Chercher sort: pagination: - total_html: "%{total} produits trouvés selon vos critères de recherche. Montrer %{from} à %{to}." per_page: show: Montrer per_page: "%{num} par page" @@ -1294,10 +1306,21 @@ fr_CA: connected_apps: legend: "Applications connectées" affiliate_sales_data: + title: "Programme de recherche INRAE / UFC QUE CHOISIR" + tagline: "Autoriser ce programme de recherche à accéder à vos données de commandes anonymisées" enable: "Autoriser le partage de données" disable: "Arrêter le partage" loading: "Chargement en cours" need_to_be_manager: "Seuls les gestionnaires peuvent connecter des applications." + description_html: | +

+ L'INRAE et UFC QUE CHOISIR travaillent conjointement sur les prix des produits distribués en circuit court, comparés aux prix des mêmes produits vendus en supermarché, pour un échantillon de produits. Les données utilisées dans le cadre de ce programme de recherche sont une agrégation des données fournies par plusieurs plateformes de vente en circuit court en France. Aucun prix de produit d'une boutique en particulier ne sera transmis publiquement à travers ce programme. +

+

+ En savoir plus à propos de ce programme de recherche + +

discover_regen: title: "Annuaire alimentaire de Waterloo" tagline: "Permettre à l'OFN Canada de publier les informations relatives à votre entreprise." @@ -1307,8 +1330,8 @@ fr_CA: need_to_be_manager: "Seuls les gestionnaires peuvent connecter des applications." note: | Votre compte Open Food Network est connecté à Annuaire alimentaire de Waterloo. - Les modifications apportées à votre profil OFN mettront automatiquement à jour votre liste Annuaire alimentaire de Waterloo. Pour toute autre modification, veuillez contacter le service d'assistance de l'OFN. - link_label: "Gérer la liste" + Les modifications apportées à votre profil OFN mettront à jour votre liste Waterloo Food Directory lorsque vous cliquerez sur le bouton 'Mise à jour de la liste'. Pour toute autre modification, veuillez contacter le service d'assistance de l'OFN et support@openfoodnetwork.ca. + link_label: "Mise à jour de la liste" description_html: |

Les producteurs éligibles peuvent présenter des appels à l'action en faveur du manger local, donner local, et/ou cultiver local.. Cela simplifie la façon dont les producteurs peuvent se connecter dans la région de Waterloo autour de la justice et de la souveraineté alimentaires. @@ -1370,10 +1393,10 @@ fr_CA: contact_name: Nom du contact principal edit: editing: 'Configuration:' - back_link: Revenir à la liste des entreprises + back_link: Retour à la liste des entreprises new: title: Nouvelle entreprise - back_link: Revenir à la liste des entreprises + back_link: Retour à la liste des entreprises welcome: welcome_title: Bienvenue sur Open Food Network ! welcome_text: 'Vous avez créé avec succès ' @@ -1410,6 +1433,11 @@ fr_CA: choose_products_from: "Choisir produits depuis :" re_notify_producers: Notifier les producteurs notify_producers_tip: Cette action va envoyer un mail à tous les producteurs avec le contenu du cycle de vente. + date_time_warning_modal_content: + title: 'Les commandes sont liées à ce cycle de vente.' + content: 'Si vous souhaitez créer un nouveau cycle de vente, il est recommandé de dupliquer un cycle de vente existant puis de changer les dates.' + proceed: 'Procéder quand même' + cancel: 'Annuler' incoming: incoming: "Produits entrants (pouvant être mis en vente par les hubs)" supplier: "Fournisseur" @@ -2872,7 +2900,6 @@ fr_CA: spree_admin_single_enterprise_hint: "Astuce: Pour permettre aux gens de vous trouver, activez votre visibilité " spree_admin_eg_pickup_from_school: "ex : \"Retrait des produits à l'Ecole Marimati / Au Café du coin / chez Babette / ...\"" spree_admin_eg_collect_your_order: "ex : \"Veuillez récupérer votre commande au 123 Parliament Street, Toronto, Ontario 3070 \"" - spree_classification_primary_taxon_error: "L'intitulé %{taxon} est l'intitulé de base pour %{product} et ne peut être supprimé" spree_order_availability_error: "Le distributeur ne peut fournir les produits de votre panier pour ce cycle de vente." spree_order_populator_error: "Le distributeur ne peut fournir tous les produits de votre panier pour ce cycle de vente. Merci de choisir un autre distributeur ou un autre cycle de vente." spree_order_cycle_error: "Veuillez sélectionner une option pour cette commande." @@ -3548,6 +3575,8 @@ fr_CA: Cette action remettra tous les niveaux de stock à zero pour cette entreprises pour les produits non présents dans ce fichier. order_cycles: + unsaved_changes: "Des modifications n'ont pas été sauvegardées" + bulk_save_error: "Oups ! Nous n'avons pas réussi à sauvegarder vos modification :-(" create_failure: "La création du cycle de vente a échoué" update_success: 'Votre cycle de vente a été mis à jour.' update_failure: "La mise à jour du cycle de vente à échoué" @@ -3795,7 +3824,6 @@ fr_CA: more: "Plus" new_adjustment: "Nouvel ajustement" new_tax_category: "Nouvelle catégorie de taxe" - new_taxon: "Nouvelle taxonomie" new_user: "Nouvel utilisateur" no_pending_payments: "Aucun paiement en attente." remove: "Supprimer" @@ -3814,10 +3842,6 @@ fr_CA: delivery: "Signé, scellé, livré" start_date: "Date de début" successfully_removed: "Supprimé avec succès" - taxonomy_edit: "Modifier la taxonomie" - taxonomy_tree_error: "Erreur lors de la mise à jour de l'arbre de taxonomie." - taxonomy_tree_instruction: "Faites un clic droit sur un article pour ajouter, renommer, supprimer ou modifier." - tree: "Arbre" updating: "Mettre à jour" your_order_is_empty_add_product: "Votre commande est vide, veuillez ajouter des produits" add_product: "Ajouter un produit" @@ -3915,7 +3939,7 @@ fr_CA: tax_rate_amount_explanation: "Les taux de taxe sont présentés en nombres décimaux pour faciliter les calculs (ex : si le taux est 5% saisissez 0.05)" included_in_price: "Inclus dans le prix" show_rate_in_label: "Montrer le taux dans le nom" - back_to_tax_rates_list: "Retour à la liste des taux de taxe" + back_to_tax_rates_list: "Revenir à la liste des taux de taxe" tax_settings: "Paramètres taxe" zones: "Zones" new_zone: "Nouvelle zone" @@ -3928,14 +3952,12 @@ fr_CA: iso_name: "Noms ISO" states_required: "États/Départements requis" editing_country: "Pays en cours de mise à jour" - back_to_countries_list: "Retour à la liste des pays" + back_to_countries_list: "Revenir à la liste des pays" states: "États/Départements requis" abbreviation: "Code" new_state: "Nouveau Région:" payment_methods: "Méthodes de paiement" - taxonomies: "Taxonomies" - new_taxonomy: "Nouvelle taxonomie" - back_to_taxonomies_list: "Retour à la liste des taxonomies" + taxons: "Catégorie Produit" shipping_methods: "Méthodes de livraison" shipping_method: "Option d'expédition" shipment: "Livraison" @@ -4308,6 +4330,8 @@ fr_CA: new_product: "Nouveau Produit" supplier: "Fournisseur" supplier_select_placeholder: "Sélectionner un producteur" + search_for_suppliers: "Chercher des fournisseurs" + search_for_units: "Chercher des unités" product_name: "Nom du Produit" units: "Unité de mesure" value: "Nb unités" @@ -4342,6 +4366,7 @@ fr_CA: product_name: Nom du Produit primary_taxon_form: product_category: Catégorie Produit + search_for_categories: "Rechercher des catégories" group_buy_form: group_buy: "Achat groupé ?" bulk_unit_size: Quantité totale du lot @@ -4419,9 +4444,20 @@ fr_CA: total: "Total" billing_address_name: "Nom" taxons: + back_to_list: "Revenir à la liste des catégories de produits" + index: + title: "Catégorie Produit" + new_taxon: 'Nouvelle catégorie de produit' + new: + title: "Nouvelle catégorie de produit" + edit: + title: "Editer la catégorie de produit" + destroy: + delete_taxon: + success: "Catégorie de produit supprimée" + error: "Impossible de supprimer la catégorie de produit car des produits sont liés à cette catégorie." form: name: Nom - permalink: Nom pour URL (sans espace) meta_title: Méta titre meta_description: Méta description meta_keywords: Méta mots clés @@ -4664,7 +4700,6 @@ fr_CA: key_cleared: "Clé supprimée" shipment: cannot_ready: "La livraison ne peut pas être préparée." - invalid_taxonomy_id: "L'identifiant de la catégorie n'est pas valide." toggle_api_key_view: "Montrer la clé API pour cet utilisateur" activerecord: models: diff --git a/config/locales/fr_CH.yml b/config/locales/fr_CH.yml index 3526797864..99498246ab 100644 --- a/config/locales/fr_CH.yml +++ b/config/locales/fr_CH.yml @@ -68,7 +68,6 @@ fr_CH: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "doit être vide car utilise les informations de stock du producteur" - on_demand_but_count_on_hand_set: "doit être vide si \"à volonté\"" limited_stock_but_no_count_on_hand: "doit être spécifié car pas \"à volonté\"" messages: blank: "Champ obligatoire" @@ -471,6 +470,8 @@ fr_CH: remove: Supprimer image: edit: Modifier + product_preview: + shop_tab: Faire mes courses adjustments: skipped_changing_canceled_order: "Vous ne pouvez pas modifier une commande annulée." begins_at: Commence @@ -669,7 +670,6 @@ fr_CH: variants: infinity: "Infini" to_order_tip: "Les articles fabriqués sur commande n'ont pas un niveau de stock défini, comme des pains faits à la main." - back_to_products_list: "Retour à la liste produits" editing_product: "Modifier le produit" tabs: product_details: "Détails" @@ -1157,10 +1157,8 @@ fr_CH: contact_name: Nom du contact principal edit: editing: 'Configuration:' - back_link: Revenir à la liste des entreprises new: title: Nouvelle entreprise - back_link: Revenir à la liste des entreprises welcome: welcome_title: Bienvenue sur Open Food Suisse ! welcome_text: 'Vous avez créé avec succès ' @@ -1197,6 +1195,8 @@ fr_CH: choose_products_from: "Choisir produits depuis :" re_notify_producers: Notifier les producteurs notify_producers_tip: Cette action va envoyer un mail à tous les producteurs avec le contenu du cycle de vente. + date_time_warning_modal_content: + cancel: 'Annuler' incoming: incoming: "Produits entrants (pouvant être mis en vente par les boutiques du cycle de vente)" supplier: "Fournisseur" @@ -2560,7 +2560,6 @@ fr_CH: spree_admin_single_enterprise_hint: "Astuce: Pour permettre aux gens de vous trouver, activez votre visibilité " spree_admin_eg_pickup_from_school: "ex : \"Retrait des produits à l'Ecole Marimati / Au Café du coin / chez Babette / ...\"" spree_admin_eg_collect_your_order: "ex : \"Veuillez récupérer votre commande au 34 rue Victor Hugo, 75018 Paris\"" - spree_classification_primary_taxon_error: "La catégorie %{taxon} est utilisée par %{product} et ne peut être supprimée" spree_order_availability_error: "Le distributeur ne peut fournir les produits de votre panier pour ce cycle de vente." spree_order_populator_error: "Le distributeur ne peut fournir tous les produits de votre panier pour ce cycle de vente. Merci de choisir un autre distributeur ou un autre cycle de vente." spree_order_cycle_error: "Veuillez sélectionner une option pour cette commande." @@ -3214,6 +3213,8 @@ fr_CH: Cette action remettra tous les niveaux de stock à zero pour cette entreprise pour les produits non présents dans ce fichier. order_cycles: + unsaved_changes: "Des modifications n'ont pas été sauvegardées" + bulk_save_error: "Oups ! Nous n'avons pas réussi à sauvegarder vos modification :-(" create_failure: "La création du cycle de vente a échoué" update_success: 'Votre cycle de vente a été mis à jour.' update_failure: "La mise à jour du cycle de vente à échoué" @@ -3452,7 +3453,6 @@ fr_CH: more: "Plus" new_adjustment: "Nouvel ajustement" new_tax_category: "Nouvelle catégorie de taxe" - new_taxon: "Nouvelle taxonomie" new_user: "Nouvel utilisateur" no_pending_payments: "Aucun paiement en attente." remove: "Supprimer" @@ -3469,8 +3469,6 @@ fr_CH: UPS Ground: "UPS Ground" start_date: "Date de début" successfully_removed: "Supprimé avec succès" - taxonomy_edit: "Modifier la taxonomie" - tree: "Arbre" updating: "Mettre à jour" your_order_is_empty_add_product: "Votre commande est vide, veuillez ajouter des produits" add_product: "Ajouter un produit" @@ -3565,7 +3563,6 @@ fr_CH: tax_rate_amount_explanation: "Les taux de taxe sont présentés en nombres décimaux pour faciliter les calculs (ex : si le taux est 5,5% saisissez 0.055)" included_in_price: "Inclus dans le prix" show_rate_in_label: "Montrer le taux dans le nom" - back_to_tax_rates_list: "Retour à la liste des taux de taxe" tax_settings: "Paramètres TVA" zones: "Zones" new_zone: "Nouvelle zone" @@ -3578,14 +3575,11 @@ fr_CH: iso_name: "Noms ISO" states_required: "États/Départements requis" editing_country: "Pays en cours de mise à jour" - back_to_countries_list: "Retour à la liste des pays" states: "Départements" abbreviation: "Code" new_state: "Nouveau département" payment_methods: "Méthodes de paiement" - taxonomies: "Taxonomies" - new_taxonomy: "Nouvelle taxonomie" - back_to_taxonomies_list: "Retour à la liste des taxonomies" + taxons: "Catégorie Produit" shipping_methods: "Méthodes de livraison" shipping_method: "Option d'expédition" shipment: "Livraison" @@ -3739,7 +3733,6 @@ fr_CH: continue: "Suivant" new: new_return_authorization: "Nouvelle autorisation de retour" - back_to_return_authorizations_list: "Revenir à la liste des autorisations" continue: "Suivant" edit: receive: "reçoit" @@ -4030,9 +4023,10 @@ fr_CH: total: "Total" billing_address_name: "Produit/Variante" taxons: + index: + title: "Catégorie Produit" form: name: Produit/Variante - permalink: Permalien description: Description general_settings: edit: @@ -4253,7 +4247,6 @@ fr_CH: key_cleared: "Clé supprimée" shipment: cannot_ready: "La livraison ne peut pas être préparée." - invalid_taxonomy_id: "L'identifiant de la catégorie n'est pas valide." activerecord: models: spree/payment: diff --git a/config/locales/fr_CM.yml b/config/locales/fr_CM.yml index 0a0ff82302..471eae5da6 100644 --- a/config/locales/fr_CM.yml +++ b/config/locales/fr_CM.yml @@ -68,7 +68,6 @@ fr_CM: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "doit être vide car utilise les informations de stock du producteur" - on_demand_but_count_on_hand_set: "doit être vide si \"à volonté\"" limited_stock_but_no_count_on_hand: "doit être spécifié car pas \"à volonté\"" messages: blank: "Champ obligatoire" @@ -408,6 +407,8 @@ fr_CM: remove: Supprimer image: edit: Modifier + product_preview: + shop_tab: Faire mes courses adjustments: skipped_changing_canceled_order: "Vous ne pouvez pas modifier une commande annulée." begins_at: Commence @@ -606,7 +607,6 @@ fr_CM: variants: infinity: "Infini" to_order_tip: "Les articles fabriqués sur commande n'ont pas un niveau de stock défini, comme des pains faits à la main." - back_to_products_list: "Retour à la liste produits" editing_product: "Modifier le produit" tabs: product_details: "Détails" @@ -1081,10 +1081,8 @@ fr_CM: contact_name: Nom du contact principal edit: editing: 'Configuration:' - back_link: Revenir à la liste des entreprises new: title: Nouvelle entreprise - back_link: Revenir à la liste des entreprises welcome: welcome_title: Bienvenue sur CoopCircuits ! welcome_text: 'Vous avez créé avec succès ' @@ -1121,6 +1119,8 @@ fr_CM: choose_products_from: "Choisir produits depuis :" re_notify_producers: Notifier les producteurs notify_producers_tip: Cette action va envoyer un mail à tous les producteurs avec le contenu du cycle de vente. + date_time_warning_modal_content: + cancel: 'Annuler' incoming: incoming: "Produits entrants (pouvant être mis en vente par les boutiques du cycle de vente)" supplier: "Fournisseur" @@ -2469,7 +2469,6 @@ fr_CM: spree_admin_single_enterprise_hint: "Astuce: Pour permettre aux gens de vous trouver, activez votre visibilité " spree_admin_eg_pickup_from_school: "ex : \"Retrait des produits à l'Ecole Marimati / Au Café du coin / chez Babette / ...\"" spree_admin_eg_collect_your_order: "ex : \"Veuillez récupérer votre commande au 34 rue Victor Hugo, 75018 Paris\"" - spree_classification_primary_taxon_error: "La catégorie %{taxon}est utilisée par %{product} et ne peut être supprimée" spree_order_availability_error: "Le distributeur ne peut fournir les produits de votre panier pour ce cycle de vente." spree_order_populator_error: "Le distributeur ne peut fournir tous les produits de votre panier pour ce cycle de vente. Merci de choisir un autre distributeur ou un autre cycle de vente." spree_order_cycle_error: "Veuillez sélectionner une option pour cette commande." @@ -3108,6 +3107,8 @@ fr_CM: Cette action remettra tous les niveaux de stock à zero pour cette entreprise pour les produits non présents dans ce fichier. order_cycles: + unsaved_changes: "Des modifications n'ont pas été sauvegardées" + bulk_save_error: "Oups ! Nous n'avons pas réussi à sauvegarder vos modification :-(" create_failure: "La création du cycle de vente a échoué" update_success: 'Votre cycle de vente a été mis à jour.' update_failure: "La mise à jour du cycle de vente à échoué" @@ -3346,7 +3347,6 @@ fr_CM: more: "Plus" new_adjustment: "Nouvel ajustement" new_tax_category: "Nouvelle catégorie de taxe" - new_taxon: "Nouvelle taxonomie" new_user: "Nouvel utilisateur" no_pending_payments: "Aucun paiement en attente." remove: "Supprimer" @@ -3363,8 +3363,6 @@ fr_CM: UPS Ground: "UPS Ground" start_date: "Date de début" successfully_removed: "Supprimé avec succès" - taxonomy_edit: "Modifier la taxonomie" - tree: "Arbre" updating: "Mettre à jour" your_order_is_empty_add_product: "Votre commande est vide, veuillez ajouter des produits" add_product: "Ajouter un produit" @@ -3459,7 +3457,6 @@ fr_CM: tax_rate_amount_explanation: "Les taux de taxe sont présentés en nombres décimaux pour faciliter les calculs (ex : si le taux est 5,5% saisissez 0.055)" included_in_price: "Inclus dans le prix" show_rate_in_label: "Montrer le taux dans le nom" - back_to_tax_rates_list: "Retour à la liste des taux de taxe" tax_settings: "Paramètres TVA" zones: "Zones" new_zone: "Nouvelle zone" @@ -3472,14 +3469,11 @@ fr_CM: iso_name: "Noms ISO" states_required: "États/Départements requis" editing_country: "Pays en cours de mise à jour" - back_to_countries_list: "Retour à la liste des pays" states: "Départements" abbreviation: "Code" new_state: "Nouveau département" payment_methods: "Méthodes de paiement" - taxonomies: "Taxonomies" - new_taxonomy: "Nouvelle taxonomie" - back_to_taxonomies_list: "Retour à la liste des taxonomies" + taxons: "Catégorie Produit" shipping_methods: "Méthodes de livraison" shipping_method: "Option d'expédition" shipment: "Livraison" @@ -3633,7 +3627,6 @@ fr_CM: continue: "Suivant" new: new_return_authorization: "Nouvelle autorisation de retour" - back_to_return_authorizations_list: "Revenir à la liste des autorisations" continue: "Suivant" edit: receive: "reçoit" @@ -3924,9 +3917,10 @@ fr_CM: total: "Total" billing_address_name: "Produit/Variante" taxons: + index: + title: "Catégorie Produit" form: name: Produit/Variante - permalink: Permalien description: Description general_settings: edit: @@ -4147,7 +4141,6 @@ fr_CM: key_cleared: "Clé supprimée" shipment: cannot_ready: "La livraison ne peut pas être préparée." - invalid_taxonomy_id: "L'identifiant de la catégorie n'est pas valide." activerecord: models: spree/payment: diff --git a/config/locales/hi.yml b/config/locales/hi.yml index cf469138a5..788d44d392 100644 --- a/config/locales/hi.yml +++ b/config/locales/hi.yml @@ -81,8 +81,6 @@ hi: white_label_logo_link: "शॉपफ्रन्ट में इस्तेमाल किए गए लोगो के लिए लिंक" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "टैक्स की श्रेणी को इन्हेरिट करने के लिए प्रति-आइटम कॅल्क्युलेटर की आवश्यकता है।" spree/user: attributes: email: @@ -102,7 +100,6 @@ hi: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "उत्पादक स्टॉक सेटिंग्स का उपयोग करने के कारण खाली होना ही चाहिए" - on_demand_but_count_on_hand_set: "यदि मांग हो तो खाली होना ही चाहिए" limited_stock_but_no_count_on_hand: "सीमित स्टॉक को मजबूर करने के कारण निर्दिष्ट किया जाना ही चाहिए" messages: confirmation: "%{attribute} से मेल नहीं खाता" @@ -541,6 +538,8 @@ hi: remove: मिटाएँ image: edit: एडिट करें + product_preview: + shop_tab: शॉप adjustments: skipped_changing_canceled_order: "आप रद्द किए गए ऑर्डर को बदल नहीं सकते।" begins_at: को शुरू होता है @@ -744,7 +743,6 @@ hi: variants: infinity: "इन्फिनिटी" to_order_tip: "ऑर्डर करने के लिए बनाई गई वस्तुओं का एक निर्धारित स्टॉक स्तर नहीं होता है, जैसे कि ऑर्डर करने के लिए ताजा बनाई गई ब्रेड की रोटियां।" - back_to_products_list: "उत्पादों की सूची पर वापस जाएं" editing_product: "उत्पाद की एडिटिंग हो रही है" tabs: product_details: "उत्पाद का विवरण" @@ -769,7 +767,6 @@ hi: search: सर्च sort: pagination: - total_html: "%{total} उत्पाद आपके सर्च क्राइटेरिया के लिए मिले। %{from} से %{to} दिखा रहा है।" per_page: show: दिखाएं per_page: "%{num} प्रति पेज" @@ -1288,10 +1285,8 @@ hi: contact_name: संपर्क का नाम edit: editing: 'सेटिंग्स:' - back_link: एंटरप्राइज़ सूची पर वापस जाएं new: title: न्यू एंटरप्राइज़ - back_link: एंटरप्राइज़ सूची पर वापस जाएं welcome: welcome_title: Open Food Network में आपका स्वागत है! welcome_text: आपने सफलतापूर्वक एक बनाया है @@ -1328,6 +1323,8 @@ hi: choose_products_from: "इनमें से उत्पाद चुनें:" re_notify_producers: उत्पादकों को फिर से नोटिफाई करें notify_producers_tip: यह प्रत्येक उत्पादक को उनके ऑर्डर्स की सूची के साथ एक ईमेल भेजेगा। + date_time_warning_modal_content: + cancel: 'रद्द करें' incoming: incoming: "इनकमिंग" supplier: "आपूर्तिकर्ता" @@ -2771,7 +2768,6 @@ hi: spree_admin_single_enterprise_hint: "संकेत: लोगों को आपको ढूंढने की अनुमति देने के लिए, नीचे से अपनी दृश्यता चालू करें" spree_admin_eg_pickup_from_school: "उदाहरण 'प्राइमरी स्कूल से पिक-अप'" spree_admin_eg_collect_your_order: "उदाहरण 'कृपया 123 इमेजिनरी सेंट, नॉर्थकोट, 3070 से अपना ऑर्डर एकत्र करें'" - spree_classification_primary_taxon_error: "टैक्सोन %{taxon} %{product} का प्राथमिक टैक्सोन है और इसे हटाया नहीं जा सकता" spree_order_availability_error: "आपके कार्ट में वितरक या ऑर्डर साइकिल उत्पादों की आपूर्ति नहीं कर सकता है" spree_order_populator_error: "वह वितरक या ऑर्डर साइकिल आपके कार्ट में सभी उत्पादों की आपूर्ति नहीं कर सकता है। कृपया कोई दूसरा चुनें।" spree_order_cycle_error: "कृपया इस ऑर्डर के लिए ऑर्डर साइकल चुनें।" @@ -3434,6 +3430,8 @@ hi: यह इस एंटरप्राइज़ के लिए उन सभी उत्पादों पर स्टॉक स्तर शून्य पर सेट कर देगा जो अपलोड की गई फ़ाइल में मौजूद नहीं हैं। order_cycles: + unsaved_changes: "आपके पास बिना सेव किए गए परिवर्तन हैं" + bulk_save_error: "अरे नहीं! मैं आपके परिवर्तनों को सेव करने में असमर्थ था।" create_failure: "ऑर्डर साइकल बनाने में विफल हुआ" update_success: 'आपका ऑर्डर साइकल अपडेट कर दिया गया है।' update_failure: "ऑर्डर साइकल अपडेट करने में विफल हुआ" @@ -3656,7 +3654,6 @@ hi: more: "और ज्यादा" new_adjustment: "नया एडजस्टमेंट" new_tax_category: "नई टैक्स श्रेणी" - new_taxon: "नया टैक्सोन" new_user: "नया यूज़र" no_pending_payments: "कोई पेंडिंग भुगतान नहीं" remove: "मिटाएँ" @@ -3675,8 +3672,6 @@ hi: delivery: "हस्ताक्षरित, सीलबंद, डिलीवर किया गया" start_date: "शुरू होने की तिथि" successfully_removed: "सफलतापूर्वक हटाया गया" - taxonomy_edit: "टैक्सोनॉमी एडिट करें" - tree: "ट्री" updating: "अपडेट किया जा रहा है" your_order_is_empty_add_product: "आपका ऑर्डर खाली है, कृपया ऊपर दिए गए उत्पाद को सर्च करें और जोड़ें" add_product: "उत्पाद जोड़ें" @@ -3773,7 +3768,6 @@ hi: tax_rate_amount_explanation: "कैलकुलेशन में सहायता के लिए टैक्स दरें एक दशमलव राशि हैं, (यानी यदि टैक्स की दर 5% है तो 0.05 एंटर करें)" included_in_price: "कीमत में शामिल" show_rate_in_label: "लेबल में रेट दिखाएं" - back_to_tax_rates_list: "टैक्स दरों की सूची पर वापस जाएं" tax_settings: "टैक्स सेटिंग्स" zones: "ज़ोन" new_zone: "न्यू ज़ोन" @@ -3786,14 +3780,11 @@ hi: iso_name: "ISO का नाम" states_required: "आवश्यक राज्य" editing_country: "एडिटिंग देश" - back_to_countries_list: "देशों की सूची पर वापस जाएं" states: "राज्य" abbreviation: "संक्षेपण" new_state: "नया राज्य" payment_methods: "भुगतान की विधियाँ" - taxonomies: "टैक्सोनॉमीज़" - new_taxonomy: "न्यू टैक्सोनॉमी" - back_to_taxonomies_list: "टैक्सोनॉमीज़ सूची पर वापस जाएं" + taxons: "उत्पाद श्रेणियां" shipping_methods: "शिपिंग की विधियाँ" shipping_method: "शिपिंग की विधि" shipment: "शिपमेंट" @@ -3951,7 +3942,6 @@ hi: continue: "जारी रखें" new: new_return_authorization: "नया रिटर्न प्राधिकरण" - back_to_return_authorizations_list: "रिटर्न प्राधिकरण सूची पर वापस जाएं" continue: "जारी रखें" edit: receive: "प्राप्त करें" @@ -4266,9 +4256,10 @@ hi: total: "कुल" billing_address_name: "नाम" taxons: + index: + title: "उत्पाद श्रेणियां" form: name: नाम - permalink: पर्मलिंक description: विवरण general_settings: edit: @@ -4506,7 +4497,6 @@ hi: key_cleared: "कुंजी साफ़ हो गई" shipment: cannot_ready: "शिपमेंट तैयार नहीं कर सकते।" - invalid_taxonomy_id: "अमान्य टैक्सोनॉमी id।" toggle_api_key_view: "यूज़र्स के लिए API कुंजी दृश्य दिखाएं" activerecord: models: diff --git a/config/locales/hu.yml b/config/locales/hu.yml index eb9338a8be..48a5e7dba9 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -81,8 +81,6 @@ hu: white_label_logo_link: "A főoldalon használt logo linkje" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Az adókategória örökléséhez tételenkénti kalkulátor szükséges." spree/image: attributes: attachment: @@ -99,8 +97,6 @@ hu: attributes: base: card_expired: "lejárt" - spree/product: - must_exist: 'léteznie kell' order_cycle: attributes: orders_close_at: @@ -108,7 +104,6 @@ hu: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "üresnek kell lennie, mert a termelői készlet beállításait használja" - on_demand_but_count_on_hand_set: "igény esetén üresnek kell lennie" limited_stock_but_no_count_on_hand: "meg kell adni, mert korlátozott a készlet" messages: confirmation: "nem egyezik %{attribute}" @@ -347,7 +342,7 @@ hu: link_label: "%{name}" shipment_mailer: shipped_email: - dear_customer: "Kedves Ügyfelünk," + dear_customer: "Kedves Felhasználónk," instructions: "Megrendelésed kiszállították" shipment_summary: "Szállítási összefoglaló" subject: "Szállítási értesítés" @@ -559,6 +554,8 @@ hu: remove: Eltávolítás image: edit: Szerkesztés + product_preview: + shop_tab: Átvételi pont adjustments: skipped_changing_canceled_order: "A törölt rendelést nem módosíthatod." begins_at: 'Kezdete:' @@ -588,7 +585,7 @@ hu: schedule: Menetrend shipping: Szállítás shipping_method: Szállítási Mód - shop: Átadási pont + shop: Átvételi pont sku: SKU status_state: Ország tags: Címkék @@ -774,7 +771,6 @@ hu: variants: infinity: "Végtelen" to_order_tip: "A megrendelésre készült termékeknek nincs meghatározott készletszintjük, például a rendelésre frissen készült kenyérnek." - back_to_products_list: "Vissza a termékek listájához" editing_product: "Termék szerkesztése" tabs: product_details: "Termék leírás" @@ -814,7 +810,6 @@ hu: search: Keresés sort: pagination: - total_html: "%{total} terméket találtunk a keresési feltételek alapján. %{from}-től %{to}-ig mutat." per_page: show: Mutasd per_page: "%{num}per oldal" @@ -885,7 +880,7 @@ hu: spreadsheet: Táblázat choose_import_type: Válaszd ki az importálás típusát import_into: Importálás típusa - product_list: Termék + product_list: Terméklista inventories: Készletek import: Importálás upload: Feltöltés @@ -996,7 +991,7 @@ hu: no_matching_new_products: Egyetlen új termék sem felel meg a keresési feltételeknek inventory_powertip: Ez a termékkészleted. Termékek hozzáadásához a válaszd az "Új Termék" lehetőséget a legördülő menüből.. hidden_powertip: Ezeket a termékeket elrejtetted a készletedből, és nem lesz elérhető az oldalon. A "Hozzáadás" gombra kattintva hozzáadhatsz egy terméket a készletedhez. - new_powertip: Ezeket a termékeket hozzáadhatja a készletéhez. Kattintson a 'Hozzáadás' gombra, ha hozzá szeretne adni egy terméket a készletéhez, vagy a 'Elrejtés' gombra, ha el szeretné rejteni a terméket. Később bármikor meggondolhatja magát! + new_powertip: Ezeket a termékeket hozzáadhatod a készletedhez. Kattints a 'Hozzáadás' gombra, ha hozzá szeretnél adni egy terméket a készletéhez, vagy a 'Elrejtés' gombra, ha el szeretnéd rejteni a terméket. Később bármikor meggondolhatod magad! controls: back_to_my_inventory: Vissza a leltárhoz orders: @@ -1380,10 +1375,8 @@ hu: contact_name: Kapcsolattartó neve edit: editing: 'Beállítások:' - back_link: Vissza a vállalkozások listájához new: title: Új Vállalkozás - back_link: Vissza a vállalkozások listájához welcome: welcome_title: Üdvözölünk az Open Food Network-ben! welcome_text: Sikeresen létrehoztad a @@ -1420,6 +1413,10 @@ hu: choose_products_from: "Válasszon termékeket a következők közül:" re_notify_producers: Értesítse újra a termelőket notify_producers_tip: Ez minden termelőnak e-mailt küld a rendeléseik listájával. + date_time_warning_modal_content: + title: 'Ebben a rendelési ciklusban már vannak rendelések.' + content: 'Ha új rendelési ciklust szeretnél létrehozni, ajánlott először megduplázni a rendelési ciklust, majd módosítani a dátumokat.' + cancel: 'Törlés' incoming: incoming: "Beérkező" supplier: "Beszállító" @@ -1547,7 +1544,7 @@ hu: resume: could_not_resume_the_order: Nem sikerült folytatni a rendelést select2: - minimal_search_length: Kérlek írj be 1%{count} vagy több karaktert + minimal_search_length: Kérlek írj be %{count} vagy több karaktert searching: Keresés... no_matches: Nincs találat shared: @@ -1664,7 +1661,7 @@ hu: enterprise_fee_summary: name: "Vállalkozási díjak összefoglalója" description: "Összefoglaló a beszedett vállalkozási díjakról" - enterprise_fees_with_tax_report_by_order: "Vállalkozási díjak adójelentéssel megrendelés alapján" + enterprise_fees_with_tax_report_by_order: "Vállalkozási díjak adójelentéssei megrendelés alapján" enterprise_fees_with_tax_report_by_producer: "Vállalkozási díjak termelői adóbevallással" errors: no_report_type: "Adja meg a jelentés típusát" @@ -2002,9 +1999,9 @@ hu: customer_required: login: "Belépés" contact: "kapcsolatba lépni" - require_customer_login: "Csak jóváhagyott vásárlók férhetnek hozzá ehhez az üzlethez." - require_login_link_html: "Ha már jóváhagyott ügyfél, %{login} a folytatáshoz." - require_login_2_html: "Itt szeretne vásárolni? Kérjük, %{contact} %{enterprise} és érdeklődjön a csatlakozásról." + require_customer_login: "Csak regisztrált és jóváhagyott vásárlók férhetnek hozzá az átadópont kínálatához." + require_login_link_html: "Ha már regisztrált felhasználó vagy, a folytatáshoz be kell lépned: %{login}" + require_login_2_html: "Ezen az átadóponton szeretnél vásárolni? %{enterprise}-el itt tudsz %{contact}, és érdeklődni a csatlakozásról." require_customer_html: "Ha itt szeretne vásárolni, kérjük, %{contact} %{enterprise} érdeklődjön a csatlakozásról." select_oc: select_oc_html: "Kérjük, válaszd ki, hogy mikorra szeretnéd megrendelni, hogy megtudd, milyen termékek állnak rendelkezésre." @@ -2028,7 +2025,7 @@ hu: invoice_column_tax_rate: "Adókulcs" invoice_tax_total: "ÁFA összesen:" invoice_cancel_and_replace_invoice: "érvényteleníti és cseréli a számlát" - tax_invoice: "Rendelés sorszáma" + tax_invoice: "VEVŐI LAP" tax_total: "Teljes adó (%{rate}):" invoice_shipping_category_delivery: "Szállítás" invoice_shipping_category_pickup: "Átvétel" @@ -2037,7 +2034,7 @@ hu: total_all_tax: "Összes adó:" abn: "Adószám:" acn: "Közösségi Adószám (EU):" - invoice_issued_on: "Kiállított számla:" + invoice_issued_on: "Kiállítás dátuma:" order_number: "Rendelés sorszáma:" date_of_transaction: "A tranzakció dátuma:" menu_1_title: "Átvételi pontok" @@ -2261,11 +2258,11 @@ hu: products_at: "itt: %{distributor}" products_elsewhere: "Máshol talált termékek" email_confirmed: "Köszönjük, hogy megerősítette e-mail címét." - email_confirmation_activate_account: "Mielőtt aktiválhatnánk új fiókját, meg kell erősíteni az e-mail címét." + email_confirmation_activate_account: "Mielőtt aktiválhatnánk az új fiókodat, meg kell erősíteni az e-mail címed." email_confirmation_greeting: "Kedves %{contact}!" email_confirmation_profile_created: "A %{name} profilod sikeresen létrejött! Profilod aktiválásához meg kell erősíteni ezt az e-mail címet." - email_confirmation_click_link: "Kérjük, kattintson az alábbi linkre az e-mail-cím megerősítéséhez és a profil beállításának folytatásához." - email_confirmation_link_label: "Erősítse meg ezt az e-mail címet »" + email_confirmation_click_link: "Kérjük, kattints az alábbi linkre az e-mail-cím megerősítéséhez és a profil beállításának folytatásához." + email_confirmation_link_label: "E-mail cím megerősítése »" email_confirmation_help_html: "Az e-mail-cím megerősítése után hozzáférhetsz a vállalkozás adminisztrációs fiókjához. Tekintsd meg a %{link} webhelyet, hogy többet megtudj %{sitename} szolgáltatásairól, és elkezdhesd használni profilod vagy online áruházad." email_confirmation_notice_unexpected: "Azért kaptad ezt az üzenetet, mert regisztráltál a(z) %{sitename} webhelyen, vagy egy olyan személy, akit valószínűleg ismersz, meghívott. Ha nem érted, miért kaptad ezt az e-mailt, kérjük, írj a %{contact} címre." email_social: "Lépj kapcsolatba velünk:" @@ -2317,14 +2314,14 @@ hu: email_shipping_collection_time: "Átvétel időpontja:" email_shipping_collection_instructions: "Átvételi információk:" email_special_instructions: "Megjegyzés:" - email_signup_greeting: Helló! - email_signup_welcome: "Üdvözlünk a %{sitename} oldalon!" - email_signup_confirmed_email: "Köszönjük, hogy megerősítette e-mailjét." - email_signup_shop_html: "Most már bejelentkezhet a %{link} címen." + email_signup_greeting: Üdv! + email_signup_welcome: "Köszöntünk az %{sitename} oldalon!" + email_signup_confirmed_email: "Köszönjük, hogy megerősítetted az e-mail címed." + email_signup_shop_html: "Most már bejelentkezhetsz a %{link} címen." email_signup_text: "Köszönjük, hogy csatlakoztál a hálózathoz! " email_signup_help_html: "Szívesen fogadunk minden kérdést és visszajelzést; ehhez bátran használd a Visszajelzés küldése gombot a weboldalon, vagy írhatsz nekünk egy e-mailt a következő címre: %{email}" invite_email: - greeting: "Helló!" + greeting: "Üdv!" invited_to_manage: "Meghívást kaptál, hogy kezeld a(z) %{enterprise} %{instance} szolgáltatást." confirm_your_email: "Egy megerősítő linket tartalmazó e-mailt kellett volna kapnia, vagy hamarosan kapni fog. Addig nem férhetsz hozzá %{enterprise} profiljához, amíg meg nem erősíted az e-mail címedet." set_a_password: "Ezután a rendszer kéri, hogy állítson be egy jelszót, mielőtt felügyelheti a vállalkozásot." @@ -2397,7 +2394,7 @@ hu: products_updating_cart: "Kosár frissítése..." products_cart_empty: "Kosár üres" products_edit_cart: "Kosár szerkesztése" - products_from: Elosztó + products_from: elosztó products_change: "Nincs mentendő módosítás." products_update_error: "A mentés a következő hibá(k) miatt nem sikerült:" products_update_error_msg: "A mentés nem sikerült." @@ -2477,7 +2474,7 @@ hu: sell_headline: "Legyél Te is a hálózat tagja!" sell_motivation: "Mutasd be termékeidet, tedd elérhetővé a fogyasztók számára." sell_producers: "Termelők" - sell_hubs: "Átvételi Pont" + sell_hubs: "Átvételi Pontok" sell_groups: "Csoportok" sell_producers_detail: "Őstermelő, kistermelő, családi gazdálkodó, vagy épp kisválllalkozás vagy? Hozz létre termelői profilt az OFN-en percek alatt. Bármikor frissítheted profilod egy online áruházra, és közvetlenül ajánlhatod termékeidet a fogyasztóknak." sell_hubs_detail: "Bevásárlóközösséget indítanál? Vagy csak összefognál a termelőtársakkal és közösen szólítanátok meg a fogyasztókat? Hozz létre új profilt, vagy bármikor frissítheted a profilodat egy többtermelős átvételi pontra." @@ -2538,7 +2535,7 @@ hu: products_cart_distributor_is: "Megrendelésed szolgáltatója: %{name}." products_distributor_error: "Kérjük, fejezd be a rendelésed a %{link} címen, mielőtt másik szolgáltatónál vásárolnál." products_oc: "Rendelési ciklus a megrendeléshez:" - products_oc_change: "Ha ezt a terméket a kosárba helyezi, a rendelési ciklusa a következőre módosul: %{name}." + products_oc_change: "Ha ezt a terméket a kosárba helyezed, a rendelési ciklus a következőre módosul: %{name}." products_oc_is: "Rendelési ciklusod ehhez a rendeléshez: %{name}." products_oc_error: "Kérjük, fejezd be rendelésed a %{link} webhelyről, mielőtt másik rendelési ciklusban vásárolnál." products_oc_current: "aktuális rendelési ciklusod" @@ -2592,7 +2589,7 @@ hu: signup_email: "Email címed" choose_password: "Adj meg egy jelszót" confirm_password: "Jelszó megerősítése" - action_signup: "Regisztrálj most" + action_signup: "Regisztrálok" forgot_password: "Elfelejtett jelszó" password_reset_sent: "Elküldtük a jelszó visszaállítására vonatkozó utasításokat tartalmazó e-mailt!" reset_password: "Jelszó visszaállítása" @@ -2607,7 +2604,7 @@ hu: registration_enterprise_address: "Vállalkozási cím" registration_contact_details: "Elsődleges elérhetőségek" registration_logo: "A logó képe" - registration_promo_image: "Fejléc a profilodhoz" + registration_promo_image: "Fejléckép a profilodhoz" registration_about_us: "Bemutatkozó szöveg" registration_outcome_headline: "Mit kapsz?" registration_outcome1_html: "Profilod segít az embereknek megtalálni és kapcsolatba lépni Veled az Open Food Network hálózaton." @@ -2879,7 +2876,6 @@ hu: spree_admin_single_enterprise_hint: "Tipp: Ha szeretnéd, hogy mások megtaláljanak, kapcsold be a nyilvános beállítást:" spree_admin_eg_pickup_from_school: "például. \"Átvétel az Általános iskolából\"" spree_admin_eg_collect_your_order: "például. \"Kérjük, vegye át rendelését a 123 Imaginary St, Northcote, 3070 címről\"" - spree_classification_primary_taxon_error: "A %{taxon} taxon a %{product} elsődleges taxonja, és nem törölhető" spree_order_availability_error: "A forgalmazó vagy a rendelési ciklus nem tudja szállítani a kosárban lévő termékeket" spree_order_populator_error: "Ez a forgalmazó vagy a rendelési ciklus nem tudja a kosárban lévő összes terméket szállítani. Kérjük, válasszon másikat." spree_order_cycle_error: "Kérjük, válassz rendelési ciklust ehhez a rendeléshez." @@ -3118,7 +3114,7 @@ hu: report_header_total_taxable_admin: Összes adóköteles rendszergazdai korrekció (adóval együtt) report_header_voucher_label: Kupon címke report_header_voucher_amount: "Kuponösszeg (%{currency_symbol})" - report_line_cost_of_produce: Termékek költsége, termelési költségek? + report_line_cost_of_produce: Termékek költsége, termelési költségek report_line_line_items: tételsor report_header_last_completed_order_date: Utolsó teljesített rendelés dátuma report_xero_configuration: Xero konfiguráció @@ -3269,7 +3265,7 @@ hu: elrejtése) a megadott címkével rendelkező ügyfelek számára. terms_and_conditions_info: title: "Általános Szerződési Feltételek feltöltése" - message_1: "Az Általános Szerződési Feltételek az Ön, az eladó és a vásárló közötti szerződés. Ha feltölt egy fájlt ide, a vásárlóknak el kell fogadniuk az Általános Szerződési Feltételeket a fizetés befejezéséhez. A vásárló számára ez egy jelölőnégyzetként jelenik meg a pénztárnál, amelyet be kell jelölnie a fizetés folytatásához. Javasoljuk, hogy a nemzeti jogszabályokkal összhangban töltse fel az Általános Szerződési Feltételeket." + message_1: "Az Általános Szerződési Feltételek az Ön, az eladó és a vásárló közötti szerződés. Ha feltöltesz egy fájlt ide, a vásárlóknak el kell fogadniuk az Általános Szerződési Feltételeket a fizetés befejezéséhez. A vásárló számára ez egy jelölőnégyzetként jelenik meg a pénztárnál, amelyet be kell jelölnie a fizetés folytatásához. Javasoljuk, hogy a nemzeti jogszabályokkal összhangban tölts fel Általános Szerződési Feltételeket." message_2: "A vásárlóknak csak egyszer kell elfogadniuk az Általános Szerződési Feltételeket. Ha azonban megváltoztatja az Általános Szerződési Feltételeket, a vásárlóknak ismét el kell fogadniuk azokat, mielőtt fizetésre kerülne a rendelésük." terms_and_conditions_warning: title: "Általános Szerződési Feltételek feltöltése" @@ -3399,7 +3395,7 @@ hu: index: per_page: "%{results} találat oldalanként" view_file: "Fájl megtekintése" - compiling_invoices: "Számlák összeállítása" + compiling_invoices: "Vevői lapok összeállítása" bulk_invoice_created: "Tömeges számla létrehozva" bulk_invoice_failed: "Nem sikerült létrehozni a tömeges számlát" please_wait: "A modul bezárása előtt várja meg, amíg a PDF elkészül." @@ -3469,7 +3465,7 @@ hu: customers: index: add_customer: "Ügyfél hozzáadása" - add_a_new_customer_for: "Új vásárló hozzáadása a %{shop_name}számára" + add_a_new_customer_for: "Új vásárló hozzáadása a %{shop_name}számára" customer_placeholder: "ügyfél@example.org" valid_email_error: "Kérjük valós e-mail címet adj meg" subscriptions: @@ -3547,6 +3543,8 @@ hu: Ez nullára állítja a készletszintet a vállalkozás összes olyan termékénél, amely nem szerepel a feltöltött fájlban. order_cycles: + unsaved_changes: "Nem mentett módosításaid vannak" + bulk_save_error: "Hopsz! Nem tudtuk menteni a módosításokat." create_failure: "Nem sikerült létrehozni a rendelési ciklust" update_success: 'Megrendelési ciklusod frissült.' update_failure: "Nem sikerült frissíteni a rendelési ciklust" @@ -3565,7 +3563,7 @@ hu: users: order: "Rendelés" registration: - welcome_to_ofn: "Üdvözlünk az Open Food Hálózatában!" + welcome_to_ofn: "Köszöntünk az Open Food Hálózatában!" signup_or_login: "Kezd a regisztrációval (vagy bejelentkezéssel)" have_an_account: "Már van fiókod?" action_login: "Jelentkezz be most." @@ -3768,9 +3766,8 @@ hu: gateway_config_unavailable: "Az átjáró konfigurációja nem érhető el" gateway_error: "Fizetés meghiúsult" more: "Több" - new_adjustment: "Új beállítás" + new_adjustment: "Új kiigazítás" new_tax_category: "Új adókategória" - new_taxon: "Új taxon" new_user: "Új felhasználó" no_pending_payments: "Nincsenek függőben lévő kifizetések" remove: "Eltávolítás" @@ -3789,10 +3786,6 @@ hu: delivery: "Aláírva, lepecsételve, kézbesítve" start_date: "Kezdő dátum" successfully_removed: "Sikeresen eltávolítva" - taxonomy_edit: "Termékkategória szerkesztése" - taxonomy_tree_error: "Hiba lépett fel a Kategória-fa frissítésében." - taxonomy_tree_instruction: "Kattints a jobb gombbal egy elemre a hozzáadáshoz, átnevezéshez, eltávolításhoz vagy szerkesztéshez." - tree: "Fa" updating: "Frissítés" your_order_is_empty_add_product: "Rendelésed üres, kérünk adj hozzá legalább egy terméket" add_product: "Termék hozzáadása" @@ -3890,7 +3883,6 @@ hu: tax_rate_amount_explanation: "Az adókulcsok tizedesjegyek, amelyek segítik a számításokat (azaz ha az adókulcs 5%, akkor írj be 0,05-öt)" included_in_price: "Az ár tartalmazza" show_rate_in_label: "Mutassa az arányát a címkén" - back_to_tax_rates_list: "Vissza az adókulcs-listához" tax_settings: "Adóbeállítások" zones: "Zónák" new_zone: "Új zóna" @@ -3903,14 +3895,11 @@ hu: iso_name: "ISO név" states_required: "Állam kötelező" editing_country: "Ország szerkesztése" - back_to_countries_list: "Vissza az országok listájához" states: "Államok" abbreviation: "Rövidítés" new_state: "Új állam" payment_methods: "Fizetési módok" - taxonomies: "Taxonómiák" - new_taxonomy: "Új termék kategória" - back_to_taxonomies_list: "Vissza a taxonómiák listájához" + taxons: "Termékkategóriák" shipping_methods: "Szállítási módok" shipping_method: "Szállítási Mód" shipment: "Szállítás" @@ -4070,7 +4059,6 @@ hu: continue: "Tovább" new: new_return_authorization: "Új visszaküldési engedély" - back_to_return_authorizations_list: "Vissza a visszaküldési engedélyek listájához" continue: "Tovább" edit: receive: "kap" @@ -4135,7 +4123,7 @@ hu: email: "Ügyfél e-mail" invoice: issued_on: "Kiadott" - tax_invoice: "Rendelés sorszáma" + tax_invoice: "VEVŐI LAP" code: "Kód" from: "Küldő" to: "Vevő" @@ -4317,6 +4305,7 @@ hu: product_name: Termék név primary_taxon_form: product_category: Termékkategória + search_for_categories: "Keresés termékkategóriára" group_buy_form: group_buy: "Csoportos vásárlás?" bulk_unit_size: Tömeges mértékegység @@ -4394,9 +4383,10 @@ hu: total: "Összesen" billing_address_name: "Név" taxons: + index: + title: "Termékkategóriák" form: name: Név - permalink: Permalink meta_title: Meta Cím (SEO cím) meta_description: Meta Leírás meta_keywords: Meta Kulcsszavak @@ -4470,7 +4460,7 @@ hu: details: "Itt vannak a megrendelt termékek részletei:" unpaid_order: "Megrendelésed nem lett kifizetve, így visszatérítés nem történt" paid_order: "Megrendelésed fizetésre került, így %{distributor} visszatérítette a teljes összeget" - credit_order: "Megrendelésed kifizették, számládon jóváhagyták." + credit_order: "Megrendelésed kifizetésre került, számládon jóváhagyták a tranzakciót." subject: "Megrendelés törlése" cancel_email_for_shop: greeting: "Kedves %{name}!" @@ -4570,7 +4560,7 @@ hu: header: Esemény típusa url: header: Végpont URL - create_placeholder: Adja meg a távoli webhook végpont URL-címét + create_placeholder: Add meg a távoli webhook végpont URL-címét developer_settings: title: Fejlesztői beállítások form: @@ -4582,14 +4572,14 @@ hu: cards: Bankkártyák transactions: Tranzakciók settings: Fiók beállítások - unconfirmed_email: "E-mail megerősítésre vár: %{unconfirmed_email}. Az Ön e-mail címe frissítésre kerül, amint az új e-mailt megerősítjük." + unconfirmed_email: "E-mail megerősítésre vár: %{unconfirmed_email}. E-mail címed frissítésre kerül, amint az új e-mailt megerősítjük." orders: open_orders: Rendelések megnyitása past_orders: Korábbi megrendelések transactions: transaction_history: Tranzakciós előzmények authorisation_required: Engedély szükséges - authorise: Engedélyezze + authorise: Engedélyezd open_orders: order: Rendelés shop: Átvételi Pont @@ -4615,7 +4605,7 @@ hu: delete?: Töröl? cards: authorised_shops: Engedélyezett átvételi pontok - authorised_shops_agreement: Ez azon átvételi pontok listája, amelyek megterhelhetik alapértelmezett hitelkártyáját az esetleges előfizetéseiért (azaz ismétlődő rendeléseiért). Kártyája adatait biztonságban tartjuk, és nem osztjuk meg az üzlet tulajdonosaival. Mindig értesítést kap, amikor felszámítják. Ha bejelöli az üzlet jelölőnégyzetét, beleegyezel abba, hogy felhatalmazod az üzletet, hogy utasításokat küldjön a kártyáját kibocsátó pénzintézetnek a fizetések fogadására az adott üzlettel létrehozott előfizetés feltételei szerint. + authorised_shops_agreement: Ez azon átvételi pontok listája, amelyek megterhelhetik alapértelmezett hitelkártyád az esetleges előfizetési díjakkal (azaz ismétlődő rendeléseid összegével). Kártyád adatait biztonságban tartjuk, és nem osztjuk meg az adott termelő vagy átvételi pont kezelőivel. Mindig értesítést kapsz, amikor díj felszámítás történik. Ha bejelölöd az átvételi pont jelölőnégyzetét, beleegyezel abba, hogy felhatalmazod az átvételi pontot, hogy utasításokat küldjön a kártyád kibocsátó pénzintézetének a fizetések fogadására az adott átvételi ponttal létrehozott előfizetés feltételei szerint. saved_cards_popover: Ez azon kártyák listája, amelyeket későbbi használatra ment el. A rendszer automatikusan kiválasztja az „alapértelmezett” kártyát a rendelés leadásakor, és ezt minden olyan üzlet megterhelheti, ahol ezt engedélyezted (lásd jobbra). authorised_shops: shop_name: "Átvételi pont neve" @@ -4638,7 +4628,6 @@ hu: key_cleared: "Kulcs törölve" shipment: cannot_ready: "Nem áll készen a szállításra." - invalid_taxonomy_id: "Érvénytelen termékkategória azonosító." toggle_api_key_view: "API-kulcs nézet megjelenítése a felhasználó számára" activerecord: models: @@ -4698,5 +4687,5 @@ hu: next: Következő previous: Előző invisible_captcha: - sentence_for_humans: "Kérjük hagyja üresen" + sentence_for_humans: "Kérjük hagyd üresen" timestamp_error_message: "Kérjük, próbáld meg 5 másodperc múlva." diff --git a/config/locales/it.yml b/config/locales/it.yml index 33c73cdd42..c06344ed23 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -84,7 +84,6 @@ it: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "deve restare vuoto perché si stanno utilizzando le impostazioni di magazzino del produttore" - on_demand_but_count_on_hand_set: "deve restare vuoto se su richiesta" limited_stock_but_no_count_on_hand: "deve essere specificato a causa di una disponibilità limitata" messages: blank: "non può essere lasciato vuoto" @@ -433,7 +432,7 @@ it: password_confirmation: 'Conferma Password ' reset_password_token: Token per il reset della password expired: è scaduto, si prega di richiederne uno nuovo - back_to_payments_list: "Torna alla Lista dei Pagamenti" + back_to_payments_list: "Torna Alla Lista Pagamenti" maestro_or_solo_cards: "Carte Maestro/Solo" backordered: "Ordini arretrati" on_hand: "Disponibile" @@ -504,6 +503,8 @@ it: remove: Rimuovi image: edit: Modifica + product_preview: + shop_tab: Negozio adjustments: skipped_changing_canceled_order: "Non puoi modificare una richiesta annullata" begins_at: Inizia a @@ -707,7 +708,6 @@ it: variants: infinity: "Infinito" to_order_tip: "Gli articoli messi in ordine non hanno un livello di stock impostato, come ad esempio il Pane fresco su ordinazione." - back_to_products_list: "Indietro alla lista dei prodotti" editing_product: "Modifica prodotto" tabs: product_details: "Dettagli Prodotto" @@ -1202,10 +1202,8 @@ it: 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 @@ -1242,6 +1240,8 @@ it: choose_products_from: "Scegli i prodotti da:" re_notify_producers: Ri-notifica i produttori notify_producers_tip: Sarà inviata una mail a ciascun produttore con la lista dei suoi ordini. + date_time_warning_modal_content: + cancel: 'Annulla' incoming: incoming: "In arrivo" supplier: "Fornitore" @@ -2631,7 +2631,6 @@ it: spree_admin_single_enterprise_hint: "Suggerimento: Per permettere alle persone di trovarti, abilita la tua visibilità sotto" spree_admin_eg_pickup_from_school: "es. \"Ritiro presso la Cooperativa\"" spree_admin_eg_collect_your_order: "es. \"Prego ritira il tuo ordine in via Roma 45\"" - spree_classification_primary_taxon_error: "La classificazione %{taxon} è la classificazione primaria di %{product} e non può essere cancellata" 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_cycle_error: "Per favore seleziona un ciclo di richieste per questa gentile richiesta." @@ -2946,7 +2945,7 @@ it: 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" order_cycle_closed: "Il ciclo di richieste che hai selezionato è appena chiuso. Riprova per favore!" - back_to_orders_list: "Indietro alla lista delle gentili richieste" + back_to_orders_list: "Torna alla lista delle richieste" no_orders_found: "Nessuna gentile richiesta trovata" order_information: "Informazioni Gentile Richiesta" new_payment: "Nuovo pagamento" @@ -3283,6 +3282,8 @@ it: Questo imposterà il livello delle scorte a zero su tutti i prodotti per questa impresa che non sono presenti nel file caricato. order_cycles: + unsaved_changes: "Hai modifiche non salvate" + bulk_save_error: "Oh no! non siamo riusciti a salvare le tue modifiche" create_failure: "Impossibile creare il ciclo dell'ordine" update_success: 'Il tuo ciclo di richieste è stato aggiornato' update_failure: "Impossibile aggiornare il ciclo dell'ordine" @@ -3521,7 +3522,6 @@ it: more: "Di più" new_adjustment: "Nuovo aggiustamento" new_tax_category: "Nuova Categoria imposta" - new_taxon: "Nuova tassonomia" new_user: "Nuovo utente" no_pending_payments: "Nessun pagamento in sospeso" remove: "Rimuovi" @@ -3540,8 +3540,6 @@ it: delivery: "Firmato, sigillato, consegnato" start_date: "Data inizio" successfully_removed: "Rimosso con successo" - taxonomy_edit: "Modifica tassonomia" - tree: "Schema" updating: "In aggiornamento" your_order_is_empty_add_product: "Il tuo ordine è vuoto, cerca e aggiungi un prodotto qui sopra" add_product: "Aggiungi prodotto" @@ -3637,7 +3635,6 @@ it: tax_rate_amount_explanation: "Le tariffe sono un importo decimale da utilizzare nei calcoli (ad esempio, se l'aliquota fiscale è del 5%, inserire 0,05)" included_in_price: "Incluso nel prezzo" show_rate_in_label: "Mostra tariffa in etichetta" - back_to_tax_rates_list: "Torna all'elenco delle tariffe" tax_settings: "Impostazione contributi" zones: "Zone" new_zone: "Nuova zona" @@ -3650,14 +3647,11 @@ it: iso_name: "Nome ISO" states_required: "Provincia richiesta" editing_country: "Modifica Paese" - back_to_countries_list: "Torna all'elenco dei Paesi" states: "Stati" abbreviation: "Abbreviazione" new_state: "Nuovo Stato" payment_methods: "Metodi di pagamento" - taxonomies: "Tassonomie" - new_taxonomy: "Nuova tassonomia" - back_to_taxonomies_list: "Torna all'elenco delle tassonomie" + taxons: "Categorie Prodotti" shipping_methods: "Metodi di spedizione" shipping_method: "Metodo di spedizione" shipment: "Spedizione" @@ -3814,7 +3808,6 @@ it: continue: "Continua" new: new_return_authorization: "Nuova autorizzazione resi" - back_to_return_authorizations_list: "Torna alla lista di autorizzazioni resi" continue: "Continua" edit: receive: "ricevi" @@ -4119,9 +4112,10 @@ it: total: "Totale" billing_address_name: "Nome" taxons: + index: + title: "Categorie Prodotti" form: name: Nome - permalink: Permalink description: Descrizione general_settings: edit: @@ -4349,7 +4343,6 @@ it: key_cleared: "Chiave cancellata" shipment: cannot_ready: "Impossibile spedizione immediata." - invalid_taxonomy_id: "ID tassonomia non valido." toggle_api_key_view: "Mostra la chiave API per l'utente" activerecord: models: diff --git a/config/locales/it_CH.yml b/config/locales/it_CH.yml index b3fb3fac14..d5a376509f 100644 --- a/config/locales/it_CH.yml +++ b/config/locales/it_CH.yml @@ -68,7 +68,6 @@ it_CH: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "deve restare vuoto perché si stanno utilizzando le impostazioni di magazzino del produttore" - on_demand_but_count_on_hand_set: "deve restare vuoto se su richiesta" limited_stock_but_no_count_on_hand: "deve essere specificato a causa di una disponibilità limitata" messages: blank: "non può essere lasciato vuoto" @@ -382,7 +381,7 @@ it_CH: password_confirmation: 'Conferma Password ' reset_password_token: Token per il reset della password expired: è scaduto, si prega di richiederne uno nuovo - back_to_payments_list: "Torna alla Lista dei Pagamenti" + back_to_payments_list: "Torna Alla Lista Pagamenti" maestro_or_solo_cards: "Carte Maestro/Solo" backordered: "Ordini arretrati" on_hand: "Disponibile" @@ -449,6 +448,8 @@ it_CH: remove: Rimuovi image: edit: Modifica + product_preview: + shop_tab: Negozio adjustments: skipped_changing_canceled_order: "Non puoi modificare una richiesta annullata" begins_at: Inizia a @@ -647,7 +648,6 @@ it_CH: variants: infinity: "Infinito" to_order_tip: "Gli articoli messi in ordine non hanno un livello di stock impostato, come ad esempio il Pane fresco su ordinazione." - back_to_products_list: "Indietro alla lista dei prodotti" editing_product: "Modifica prodotto" tabs: product_details: "Dettagli Prodotto" @@ -1131,10 +1131,8 @@ it_CH: 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 Svizzera! welcome_text: Hai creato con successo una @@ -1171,6 +1169,8 @@ it_CH: choose_products_from: "Scegli i prodotti da:" re_notify_producers: Ri-notifica i produttori notify_producers_tip: Sarà inviata una mail a ciascun produttore con la lista dei suoi ordini. + date_time_warning_modal_content: + cancel: 'Annulla' incoming: incoming: "In arrivo" supplier: "Fornitore" @@ -2524,7 +2524,6 @@ it_CH: spree_admin_single_enterprise_hint: "Suggerimento: Per permettere alle persone di trovarti, abilita la tua visibilità sotto" spree_admin_eg_pickup_from_school: "es. \"Ritiro presso la Cooperativa\"" spree_admin_eg_collect_your_order: "es. \"Prego ritira il tuo ordine in via Roma 45\"" - spree_classification_primary_taxon_error: "La classificazione %{taxon} è la classificazione primaria di %{product} e non può essere cancellata" 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_cycle_error: "Per favore seleziona un ciclo di richieste per questa gentile richiesta." @@ -2809,7 +2808,7 @@ it_CH: 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" order_cycle_closed: "Il ciclo di richieste che hai selezionato è appena chiuso. Riprova per favore!" - back_to_orders_list: "Indietro alla lista delle gentili richieste" + back_to_orders_list: "Torna alla lista delle richieste" no_orders_found: "Nessuna gentile richiesta trovata" order_information: "Informazioni Gentile Richiesta" new_payment: "Nuovo pagamento" @@ -3143,6 +3142,8 @@ it_CH: Questo imposterà il livello delle scorte a zero su tutti i prodotti per questa impresa che non sono presenti nel file caricato. order_cycles: + unsaved_changes: "Hai modifiche non salvate" + bulk_save_error: "Oh no! non siamo riusciti a salvare le tue modifiche" create_failure: "Impossibile creare il ciclo dell'ordine" update_success: 'Il tuo ciclo di richieste è stato aggiornato' update_failure: "Impossibile aggiornare il ciclo dell'ordine" @@ -3381,7 +3382,6 @@ it_CH: more: "Di più" new_adjustment: "Nuovo aggiustamento" new_tax_category: "Nuova Categoria imposta" - new_taxon: "Nuova tassonomia" new_user: "Nuovo utente" no_pending_payments: "Nessun pagamento in sospeso" remove: "Rimuovi" @@ -3398,8 +3398,6 @@ it_CH: UPS Ground: "UPS Ground" start_date: "Data inizio" successfully_removed: "Rimosso con successo" - taxonomy_edit: "Modifica tassonomia" - tree: "Schema" updating: "In aggiornamento" your_order_is_empty_add_product: "Il tuo ordine è vuoto, cerca e aggiungi un prodotto qui sopra" add_product: "Aggiungi prodotto" @@ -3494,7 +3492,6 @@ it_CH: tax_rate_amount_explanation: "Le tariffe sono un importo decimale da utilizzare nei calcoli (ad esempio, se l'aliquota fiscale è del 5%, inserire 0,05)" included_in_price: "Incluso nel prezzo" show_rate_in_label: "Mostra tariffa in etichetta" - back_to_tax_rates_list: "Torna all'elenco delle tariffe" tax_settings: "Impostazione contributi" zones: "Zone" new_zone: "Nuova zona" @@ -3507,14 +3504,11 @@ it_CH: iso_name: "Nome ISO" states_required: "Provincia richiesta" editing_country: "Modifica Paese" - back_to_countries_list: "Torna all'elenco dei Paesi" states: "Stati" abbreviation: "Abbreviazione" new_state: "Nuovo Stato" payment_methods: "Metodi di pagamento" - taxonomies: "Tassonomie" - new_taxonomy: "Nuova tassonomia" - back_to_taxonomies_list: "Torna all'elenco delle tassonomie" + taxons: "Categorie Prodotti" shipping_methods: "Metodi di spedizione" shipping_method: "Metodo di spedizione" shipment: "Spedizione" @@ -3668,7 +3662,6 @@ it_CH: continue: "Continua" new: new_return_authorization: "Nuova autorizzazione resi" - back_to_return_authorizations_list: "Torna alla lista di autorizzazioni resi" continue: "Continua" edit: receive: "ricevi" @@ -3959,9 +3952,10 @@ it_CH: total: "Totale" billing_address_name: "Nome" taxons: + index: + title: "Categorie Prodotti" form: name: Nome - permalink: Permalink description: Descrizione general_settings: edit: @@ -4181,7 +4175,6 @@ it_CH: key_cleared: "Chiave cancellata" shipment: cannot_ready: "Impossibile spedizione immediata." - invalid_taxonomy_id: "ID tassonomia non valido." activerecord: models: spree/payment: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index d0078b9015..a0f33b802e 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -68,7 +68,6 @@ ko: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "생산자 제고 관리를 위해 빈 칸으로 남겨야 합니다." - on_demand_but_count_on_hand_set: "요구 사항이 있을 경우 빈 칸으로 남겨야 합니다." limited_stock_but_no_count_on_hand: "한정된 재고 사항에 따라 명시되어야 합니다." messages: blank: "빈칸이 될 수 없습니다." @@ -405,7 +404,7 @@ ko: password_confirmation: 비밀번호 확인 reset_password_token: 비밀번호 초기화 expired: 만료되었습니다. 새로 요청하세요. - back_to_payments_list: "결제 리스트로 돌아가기" + back_to_payments_list: "결제 목록으로 돌아가기" maestro_or_solo_cards: "마스터/전용 카드" backordered: "일시 품절됨" on_hand: "대기 중" @@ -472,6 +471,8 @@ ko: remove: 제거 image: edit: 편집 + product_preview: + shop_tab: 쇼핑 adjustments: skipped_changing_canceled_order: "취소된 명령을 바꿀 수 없습니다." begins_at: 시작 시간 @@ -670,7 +671,6 @@ ko: variants: infinity: "무한성" to_order_tip: "주문 제작 품목은 갓 주문한 빵 덩어리와 같이 정해진 재고 수준이 없습니다." - back_to_products_list: "제품 목록으로 돌아가기" editing_product: "제품 편집" tabs: product_details: "상품 세부사항" @@ -1151,10 +1151,8 @@ ko: contact_name: 연락처 이름 edit: editing: '설정 :' - back_link: 회사 목록으로 돌아가기 new: title: 새로운 회사 - back_link: 회사 목록으로 돌아가기 welcome: welcome_title: Open Food Network에 오신 것을 환영합니다! welcome_text: 성공적으로 생성되었습니다. @@ -1191,6 +1189,8 @@ ko: choose_products_from: "제품 위치 선택:" re_notify_producers: 생산자에게 재통보 notify_producers_tip: 이렇게 하면 각 생산자에게 주문 목록과 함께 이메일이 전송됩니다. + date_time_warning_modal_content: + cancel: '취소' incoming: incoming: "새로 들어옴" supplier: "공급업체" @@ -2554,7 +2554,6 @@ ko: spree_admin_single_enterprise_hint: "힌트 : 다른 사용자가 사용자를 찾을 수 있도록 하려면 다음에서 사용자 가시성을 켜십시오." spree_admin_eg_pickup_from_school: "예. '초등학교에서 픽업'" spree_admin_eg_collect_your_order: "예. '123 Imaginary St, Northcote, 3070번지에서 주문을 받으십시오.'" - spree_classification_primary_taxon_error: "분류군%{taxon}은 %{product}의 기본 분류군이며 삭제할 수 없습니다." spree_order_availability_error: "대리점 또는 주문 주기가 카트의 제품을 제공할 수 없습니다." spree_order_populator_error: "해당 유통업체나 주문 주기가 카트에 있는 모든 제품을 공급할 수는 없습니다. 다른 항목을 선택하십시오." spree_order_cycle_error: "이 주문에 대한 주문 주기를 선택하십시오." @@ -3157,6 +3156,8 @@ ko: confirmation: | 업로드된 파일에 없는 이 기업의 모든 제품에 대한 재고 수준이 0으로 설정됩니다. order_cycles: + unsaved_changes: "저장되지 않은 변경이 있습니다." + bulk_save_error: "이런! 변경 내용을 저장할 수 없습니다." create_failure: "주문 주기를 생성하지 못했습니다." update_success: '주문 주기가 업데이트 되었습니다.' update_failure: "주문 주기를 업데이트하지 못했습니다." @@ -3347,7 +3348,6 @@ ko: more: "추가 정보" new_adjustment: "새 조정" new_tax_category: "신규 세금 카테고리" - new_taxon: "새 분류군" new_user: "신규 사용자" no_pending_payments: "미지금급" remove: "제거" @@ -3364,8 +3364,6 @@ ko: UPS Ground: "UPS 배경" start_date: "시작 날짜" successfully_removed: "성공적으로 제거됨" - taxonomy_edit: "분류법 편집" - tree: "트리" updating: "업데이트 중" your_order_is_empty_add_product: "주문이 비어 있습니다. 위의 제품을 검색하여 추가하십시오." add_product: "상품 추가" @@ -3460,7 +3458,6 @@ ko: tax_rate_amount_explanation: "세율은 소수로 계산하면 도움이 됩니다. (예: 세율이 5%인 경우라면 0.05를 입력하세요.)" included_in_price: "가격에 포함되어있음" show_rate_in_label: "레이블에 비율 표시" - back_to_tax_rates_list: "세율 리스트로 돌아가기" tax_settings: "세금 설정" zones: "구역" new_zone: "신규 구역" @@ -3473,14 +3470,11 @@ ko: iso_name: "ISO 이름" states_required: "주를 작성하셔야 합니다." editing_country: "국가 편집" - back_to_countries_list: "국가 리스트로 돌아가기" states: "주" abbreviation: "축약" new_state: "신규 주" payment_methods: "결제 방법" - taxonomies: "분류법" - new_taxonomy: "신규 분류법" - back_to_taxonomies_list: "분류법 리스트로 돌아가기" + taxons: "제품 카테고리" shipping_methods: "전송 방법" shipping_method: "배송 방법" shipment: "수송" @@ -3634,7 +3628,6 @@ ko: continue: "계속하기" new: new_return_authorization: "신규 반품 승인" - back_to_return_authorizations_list: "반품 승인 리스트로 돌아가기" continue: "계속하기" edit: receive: "수신" @@ -3926,9 +3919,10 @@ ko: total: "총합" billing_address_name: "이름" taxons: + index: + title: "제품 카테고리" form: name: 이름 - permalink: 하이퍼링크 description: 설명 general_settings: edit: @@ -4148,7 +4142,6 @@ ko: key_cleared: "키 지워짐" shipment: cannot_ready: "발송을 준비할 수 없습니다." - invalid_taxonomy_id: "분류법 ID가 잘못되었습니다." activerecord: models: spree/payment: diff --git a/config/locales/ml.yml b/config/locales/ml.yml index 2ab418835a..a8849d8778 100644 --- a/config/locales/ml.yml +++ b/config/locales/ml.yml @@ -81,8 +81,6 @@ ml: white_label_logo_link: "കടയുടെ മുൻവശത്ത് ഉപയോഗിക്കുന്ന ലോഗോയ്ക്കുള്ള ലിങ്ക്" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "നികുതി വിഭാഗം അവകാശമായി ലഭിക്കുന്നതിന് ഓരോ ഇനത്തിനും കാൽക്കുലേറ്റർ ആവശ്യമാണ്." spree/user: attributes: email: @@ -102,7 +100,6 @@ ml: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "പ്രൊഡ്യൂസർ സ്റ്റോക്ക് ക്രമീകരണങ്ങൾ ഉപയോഗിക്കുന്നതിനാൽ ശൂന്യമായിരിക്കണം" - on_demand_but_count_on_hand_set: "ആവശ്യകത കൂടിയിരുന്നാൽ ശൂന്യമായിരിക്കണം" limited_stock_but_no_count_on_hand: "സ്റ്റോക്ക് പരിമിതമായതിനാൽ വ്യക്തമാക്കണം" messages: confirmation: "%{attribute} എന്നതുമായി പൊരുത്തപ്പെടുന്നില്ല" @@ -541,6 +538,8 @@ ml: remove: നീക്കം ചെയ്യുക image: edit: എഡിറ്റ് ചെയ്യുക + product_preview: + shop_tab: ഷോപ്പ് adjustments: skipped_changing_canceled_order: "നിങ്ങൾക്ക് റദ്ദാക്കിയ ഓർഡർ മാറ്റാൻ കഴിയില്ല." begins_at: ആരംഭിക്കുന്നത് @@ -747,7 +746,6 @@ ml: variants: infinity: "അനന്തത" to_order_tip: "ഓർഡർ ചെയ്യാൻ തയ്യാറാക്കിയ ഇനങ്ങൾക്ക് സെറ്റ് സ്റ്റോക്ക് ലെവൽ ഇല്ല, ഉദാഹരണത്തിന്, ഓർഡർ അനുസരിച്ച് തയ്യാറാക്കുന്ന ബ്രഡ് പാക്കറ്റുകൾ." - back_to_products_list: "ഉൽപ്പന്നങ്ങളുടെ പട്ടികയിലേക്ക് മടങ്ങുക" editing_product: "ഉൽപ്പന്നം തിരുത്തുന്നു" tabs: product_details: "ഉൽപ്പന്നത്തിന്റെ വിവരം" @@ -772,7 +770,6 @@ ml: search: തിരയുക sort: pagination: - total_html: "നിങ്ങളുടെ തിരയൽ മാനദണ്ഡങ്ങൾക്കനുസരിച്ച് %{total} ഉൽപ്പന്നങ്ങൾ കണ്ടെത്തി. %{from} മുതൽ %{to} വരെ കാണിക്കുന്നു." per_page: show: കാണിക്കുക per_page: "ഓരോ പേജിലും %{num}" @@ -1297,10 +1294,8 @@ ml: contact_name: ബന്ധപ്പെടാനുള്ള പേര് edit: editing: 'ക്രമീകരണങ്ങൾ:' - back_link: എന്റർപ്രൈസസ് ലിസ്റ്റിലേക്ക് മടങ്ങുക new: title: പുതിയ എന്റർപ്രൈസ് - back_link: എന്റർപ്രൈസസ് ലിസ്റ്റിലേക്ക് മടങ്ങുക welcome: welcome_title: ഓപ്പൺ ഫുഡ് നെറ്റ്‌വർക്കിലേക്ക് സ്വാഗതം! welcome_text: നിങ്ങൾ വിജയകരമായി സൃഷ്ടിച്ചു @@ -1337,6 +1332,8 @@ ml: choose_products_from: "ഇതിൽ നിന്ന് ഉൽപ്പന്നങ്ങൾ തിരഞ്ഞെടുക്കുക:" re_notify_producers: പ്രൊഡ്യൂസേഴ്സിനെ വീണ്ടും അറിയിക്കുക notify_producers_tip: ഇത് ഓരോ പ്രൊഡ്യൂസേഴ്സിനും അവരുടെ ഓർഡറുകളുടെ ലിസ്റ്റുമായി ഒരു ഇമെയിൽ അയയ്ക്കും. + date_time_warning_modal_content: + cancel: 'റദ്ദാക്കുക' incoming: incoming: "ഇൻകമിംഗ്" supplier: "വിതരണക്കാരൻ" @@ -2787,7 +2784,6 @@ ml: spree_admin_single_enterprise_hint: "സൂചന: നിങ്ങളെ കണ്ടെത്താൻ ആളുകളെ അനുവദിക്കുന്നതിന്, ചുവടെ നിങ്ങളുടെ ദൃശ്യപരത ഓണാക്കുക" spree_admin_eg_pickup_from_school: "ഉദാ. 'പ്രൈമറി സ്കൂളിൽ നിന്ന് പിക്കപ്പ് ചെയ്യുക'" spree_admin_eg_collect_your_order: "ഉദാ. 'ദയവായി 123 ഇമാജിനറി സ്ട്രീറ്റ്, നോർത്ത്കോട്ട്, 3070-ൽ നിന്ന് നിങ്ങളുടെ ഓർഡർ ശേഖരിക്കുക'" - spree_classification_primary_taxon_error: "ടാക്സോൺ %{taxon} എന്നത് %{product} -ന്റെ പ്രാഥമിക ടാക്സോണാണ്, അത് ഇല്ലാതാക്കാൻ കഴിയില്ല" spree_order_availability_error: "ഡിസ്ട്രിബ്യൂട്ടർക്കോ അല്ലെങ്കിൽ ഓർഡർ സൈക്കിളിനോ നിങ്ങളുടെ കാർട്ടിലെ ഉൽപ്പന്നങ്ങൾ വിതരണം ചെയ്യാൻ കഴിയില്ല" spree_order_populator_error: "നിങ്ങളുടെ കാർട്ടിലെ എല്ലാ ഉൽപ്പന്നങ്ങളും വിതരണം ചെയ്യാൻ ആ വിതരണക്കാരനോ ഓർഡർ സൈക്കിളിനോ കഴിയില്ല. ദയവായി മറ്റൊന്ന് തിരഞ്ഞെടുക്കുക." spree_order_cycle_error: "ഈ ഓർഡറിനായി ഒരു ഓർഡർ സൈക്കിൾ തിരഞ്ഞെടുക്കുക." @@ -3458,6 +3454,8 @@ ml: ഇതിനായുള്ള എല്ലാ ഉൽപ്പന്നങ്ങളുടെയും സ്റ്റോക്ക് ലെവൽ പൂജ്യമായി സജ്ജമാക്കും അപ്‌ലോഡ് ചെയ്ത ഫയലിൽ ഇല്ലാത്ത എന്റർപ്രൈസ്. order_cycles: + unsaved_changes: "നിങ്ങൾക്ക് സേവ് ചെയ്യാത്ത മാറ്റങ്ങളുണ്ട്" + bulk_save_error: "അയ്യോ! നിങ്ങളുടെ മാറ്റങ്ങൾ സേവ് ചെയ്യാൻ എനിക്ക് കഴിഞ്ഞില്ല." create_failure: "ഓർഡർ സൈക്കിൾ സൃഷ്ടിക്കുന്നതിൽ പരാജയപ്പെട്ടു" update_success: 'നിങ്ങളുടെ ഓർഡർ സൈക്കിൾ അപ്ഡേറ്റ് ചെയ്തു.' update_failure: "ഓർഡർ സൈക്കിൾ അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു" @@ -3680,7 +3678,6 @@ ml: more: "കൂടുതൽ" new_adjustment: "പുതിയ മാറ്റംവരുത്തൽ" new_tax_category: "പുതിയ നികുതി വിഭാഗം" - new_taxon: "പുതിയ ടാക്സൺ" new_user: "പുതിയ ഉപയോക്താവ്" no_pending_payments: "തീർപ്പാക്കാത്ത പേയ്‌മെന്റുകളൊന്നുമില്ല" remove: "നീക്കം ചെയ്യുക" @@ -3699,8 +3696,6 @@ ml: delivery: "ഒപ്പിട്ടു, സീൽ ചെയ്തു, എത്തിച്ചു" start_date: "ആരംഭിക്കുന്ന തീയതി" successfully_removed: "വിജയകരമായി നീക്കം ചെയ്തു" - taxonomy_edit: "ടാക്സോണമി എഡിറ്റ്" - tree: "ട്രീ" updating: "അപ്ഡേറ്റ് ചെയ്യുന്നു" your_order_is_empty_add_product: "നിങ്ങളുടെ ഓർഡർ ശൂന്യമാണ്, മുകളിൽ ഒരു ഉൽപ്പന്നം തിരയുകയും ചേർക്കുകയും ചെയ്യുക" add_product: "ഉൽപ്പന്നം ചേർക്കുക" @@ -3797,7 +3792,6 @@ ml: tax_rate_amount_explanation: "കണക്കുകൂട്ടലുകളെ സഹായിക്കുന്നതിനുള്ള ഒരു ദശാംശ തുകയാണ് നികുതി നിരക്കുകൾ, (അതായത് നികുതി നിരക്ക് 5% ആണെങ്കിൽ 0.05 നൽകുക)" included_in_price: "വിലയിൽ ഉൾപ്പെടുത്തിയിട്ടുണ്ട്" show_rate_in_label: "ലേബലിൽ നിരക്ക് കാണിക്കുക" - back_to_tax_rates_list: "നികുതി നിരക്കുകളുടെ പട്ടികയിലേക്ക് മടങ്ങുക" tax_settings: "നികുതി ക്രമീകരണങ്ങൾ" zones: "സോണുകൾ" new_zone: "പുതിയ സോൺ" @@ -3810,14 +3804,11 @@ ml: iso_name: "ഐഎസ്ഓ നാമം" states_required: "ആവശ്യമുള്ള സംസ്ഥാനങ്ങൾ" editing_country: "രാജ്യം എഡിറ്റ് ചെയ്യുന്നു" - back_to_countries_list: "രാജ്യങ്ങളുടെ പട്ടികയിലേക്ക് മടങ്ങുക" states: "സംസ്ഥാനങ്ങൾ" abbreviation: "ചുരുക്കെഴുത്ത്" new_state: "പുതിയ സംസ്ഥാനം" payment_methods: "പേയ്മെന്റ് രീതികൾ" - taxonomies: "ടാക്സോണമികൾ" - new_taxonomy: "പുതിയ ടാക്സോണമി" - back_to_taxonomies_list: "ടാക്‌സോണമി ലിസ്റ്റിലേക്ക് മടങ്ങുക" + taxons: "ഉൽപ്പന്ന വിഭാഗങ്ങൾ" shipping_methods: "ഷിപ്പിംഗ് രീതികൾ" shipping_method: "ഷിപ്പിംഗ് രീതി" shipment: "കയറ്റുമതി" @@ -3975,7 +3966,6 @@ ml: continue: "തുടരുക" new: new_return_authorization: "പുതിയ റിട്ടേൺ അനുമതി" - back_to_return_authorizations_list: "റിട്ടേൺ അനുമതി ലിസ്റ്റിലേക്ക് മടങ്ങുക" continue: "തുടരുക" edit: receive: "സ്വീകരിക്കുക" @@ -4293,9 +4283,10 @@ ml: total: "ആകെ" billing_address_name: "പേര്" taxons: + index: + title: "ഉൽപ്പന്ന വിഭാഗങ്ങൾ" form: name: പേര് - permalink: പെർമലിങ്ക് description: വിവരണം general_settings: edit: @@ -4533,7 +4524,6 @@ ml: key_cleared: "കീ മായ്ച്ചു" shipment: cannot_ready: "കയറ്റുമതി തയ്യാറാക്കാൻ കഴിയില്ല." - invalid_taxonomy_id: "ടാക്സോണമി ഐഡി അസാധുവാണ്." toggle_api_key_view: "ഉപയോക്താവിനായി എപിഐ കീ കാണിക്കുക" activerecord: models: diff --git a/config/locales/mr.yml b/config/locales/mr.yml index fa0807fbf8..b6dbd18185 100644 --- a/config/locales/mr.yml +++ b/config/locales/mr.yml @@ -81,8 +81,6 @@ mr: white_label_logo_link: "शॉपफ्रंटमध्ये वापरलेल्या लोगोची लिंक" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "कर श्रेणीचा वारसा मिळवण्यासाठी प्रति-आयटम कॅल्क्युलेटर आवश्यक आहे." spree/user: attributes: email: @@ -102,7 +100,6 @@ mr: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "उत्पादक स्टॉक सेटिंग्ज वापरल्याने रिक्त असणे आवश्यक आहे" - on_demand_but_count_on_hand_set: "मागणी केली असल्यास रिक्त असणे आवश्यक आहे" limited_stock_but_no_count_on_hand: "मर्यादित स्टॉकची सक्ती केल्यामुळे निर्दिष्ट करणे आवश्यक आहे" messages: confirmation: "%{attribute} जुळत नाही" @@ -470,7 +467,6 @@ mr: password_confirmation: पासवर्ड पुष्टीकरण reset_password_token: पासवर्ड टोकन रीसेट करा expired: कालबाह्य झाले आहे, कृपया नवीन विनंती करा - back_to_payments_list: "पेमेंट्स लिस्टकडे परत" maestro_or_solo_cards: "मेस्ट्रो/सोलो कार्ड" backordered: "बॅकऑर्डर केलेले" on_hand: "आता उपलब्ध" @@ -538,6 +534,8 @@ mr: clone: क्लोन image: edit: सुधारित करा + product_preview: + shop_tab: शॉप adjustments: skipped_changing_canceled_order: "तुम्ही रद्द केलेली ऑर्डर बदलू शकत नाही." begins_at: वाजता सुरू होते @@ -741,7 +739,6 @@ mr: variants: infinity: "अनंत" to_order_tip: "ऑर्डरप्रमाणे बनवलेल्या वस्तूंची स्टॉक लेव्हल नसते, जसे की ऑर्डरप्रमाणे बनवलेले ताजे ब्रेडस्. " - back_to_products_list: "उत्पादनांच्या सूचीकडे परत" editing_product: "उत्पादन संपादित करत आहे" tabs: product_details: "उत्पादन तपशील" @@ -764,7 +761,6 @@ mr: search: शोधा sort: pagination: - total_html: "तुमच्या शोध निकषांसाठी %{total} उत्पादने आढळली. %{from} ते %{to} दाखवत आहे." per_page: show: दाखवा per_page: "%{num} प्रति पृष्ठ" @@ -1277,10 +1273,8 @@ mr: contact_name: संपर्क नाव edit: editing: 'सेटिंग्ज:' - back_link: उपक्रमांच्या सूचीकडे परत new: title: नवे एंटरप्राइज - back_link: एंटरप्राइझ लिस्टकडे परता welcome: welcome_title: ओपन फूड नेटवर्कमध्ये आपले स्वागत आहे! welcome_text: आपण यशस्वीरित्या तयार केले आहे @@ -1311,6 +1305,8 @@ mr: choose_products_from: "यामधून उत्पादने निवडा:" re_notify_producers: उत्पादकांना पुन्हा सूचित करा notify_producers_tip: हे प्रत्येक उत्पादकाला त्यांच्या ऑर्डरच्या सूचीचे ईमेल पाठवेल. + date_time_warning_modal_content: + cancel: 'रद्द करा' incoming: incoming: "येणारे" supplier: "पुरवठादार" @@ -2695,7 +2691,6 @@ mr: spree_admin_single_enterprise_hint: "सूचना: लोकांना तुम्हाला शोधण्याची अनुमती देण्यासाठी, या अंतर्गत तुमची दृश्यमानता चालू करा" spree_admin_eg_pickup_from_school: "उदा. 'प्राथमिक शाळेतून पिक-अप'" spree_admin_eg_collect_your_order: "उदा. 'कृपया 123 सरदार वल्लभभाई पटेल रोड, सुरत, 301070 वरून तुमची ऑर्डर प्राप्त करा'" - spree_classification_primary_taxon_error: "टॅक्सन %{taxon} हा %{product} चा प्राथमिक टॅक्सन आहे आणि तो हटवला जाऊ शकत नाही" spree_order_availability_error: "वितरक किंवा ऑर्डर सायकल तुमच्या कार्ट मधील उत्पादने पुरवू शकत नाही" spree_order_populator_error: "तो वितरक किंवा ऑर्डर सायकल तुमच्या कार्टमधील सर्व उत्पादने पुरवू शकत नाही. कृपया दुसरा वितरक किंवा दुसरी ऑर्डर सायकल निवडा." spree_order_cycle_error: "कृपया या ऑर्डरसाठी ऑर्डर सायकल निवडा." @@ -3013,7 +3008,6 @@ mr: order_cycles_no_permission_to_coordinate_error: "तुमच्या कोणत्याही एंटरप्राइझला ऑर्डर सायकल को-ऑर्डिनेट करण्याची परवानगी नाही" order_cycles_no_permission_to_create_error: "तुम्हाला त्या एंटरप्राइझद्वारे को-ऑर्डिनेट केलेल्या ऑर्डर सायकल तयार करण्याची परवानगी नाही" order_cycle_closed: "तुम्ही निवडलेले ऑर्डर सायकल नुकतेच बंद झाले आहे. कृपया पुन्हा प्रयत्न करा!" - back_to_orders_list: "ऑर्डर सूचीकडे परत" no_orders_found: "कोणत्याही ऑर्डर आढळल्या नाहीत" order_information: "ऑर्डर माहिती" new_payment: "नवीन पेमेंट" @@ -3358,6 +3352,8 @@ mr: हे अपलोड केलेल्या फाइलमध्ये उपस्थित नसलेल्या एंटरप्राइझसाठी सर्व उत्पादनांचा साठा शून्यावर सेट करेल. order_cycles: + unsaved_changes: "तुमच्याकडे सेव्ह न केलेले बदल आहेत" + bulk_save_error: "क्षमस्व ! तुमचे बदल मी सेव्ह करू शकलो नाही." create_failure: "ऑर्डर सायकल तयार करण्यात अयशस्वी" update_success: 'तुमची ऑर्डर सायकल अपडेट केली गेली आहे.' update_failure: "ऑर्डर सायकल अपडेट करण्यात अयशस्वी" @@ -3580,7 +3576,6 @@ mr: more: "अधिक" new_adjustment: "नवीन समायोजन" new_tax_category: "नवीन कर श्रेणी" - new_taxon: "नवीन टॅक्सन" new_user: "नवीन वापरकर्ता" no_pending_payments: "कोणतीही प्रलंबित देयके नाहीत" none: "कोणतेही नाही" @@ -3598,8 +3593,6 @@ mr: delivery: "स्वाक्षरी, सीलबंद, वितरित" start_date: "प्रारंभ तारीख" successfully_removed: "यशस्वीरित्या काढले" - taxonomy_edit: "वर्गीकरण संपादन" - tree: "वृक्ष" updating: "अपडेट करत आहे" your_order_is_empty_add_product: "तुमची ऑर्डर रिकामी आहे, कृपया उत्पादन शोधा आणि वर समाविष्ट करा" add_product: "उत्पादन जोडा" @@ -3696,7 +3689,6 @@ mr: tax_rate_amount_explanation: "कर दर ही गणना करण्यात मदत करण्यासाठीची दशांश रक्कम आहे, (म्हणजे कर दर 5% असल्यास 0.05 प्रविष्ट करा)" included_in_price: "किंमतीमध्ये समाविष्ट आहे" show_rate_in_label: "लेबलमध्ये दर दर्शवा" - back_to_tax_rates_list: "कर दर सूचीकडे परत" tax_settings: "कर सेटिंग्ज" zones: "क्षेत्रे" new_zone: "नवीन क्षेत्र" @@ -3709,14 +3701,10 @@ mr: iso_name: "आयएसओ नाव" states_required: "राज्ये आवश्यक" editing_country: "संपादन देश" - back_to_countries_list: "देशांच्या यादीकडे परत" states: "राज्ये" abbreviation: "संक्षेप" new_state: "नवीन राज्य" payment_methods: "पेमेंट पद्धती" - taxonomies: "वर्गीकरण" - new_taxonomy: "नवीन वर्गीकरण" - back_to_taxonomies_list: "वर्गीकरण सूचीकडे परत जा" shipping_methods: "शिपिंग पद्धती" shipping_method: "शिपिंग पद्धत " shipment: "शिपमेंट" @@ -3868,7 +3856,6 @@ mr: continue: "सुरू ठेवा " new: new_return_authorization: "रिटर्न अधिकृतता" - back_to_return_authorizations_list: "रिटर्न अधिकृतता सूचीकडे परता" continue: "सुरू ठेवा " edit: receive: "प्राप्त" @@ -4421,7 +4408,6 @@ mr: key_cleared: "Key क्लिअर केली" shipment: cannot_ready: "शिपमेंट तयार करू शकत नाही." - invalid_taxonomy_id: "अवैध वर्गीकरण आयडी." toggle_api_key_view: "वापरकर्त्यासाठी API key दृश्य दर्शवा" activerecord: models: diff --git a/config/locales/nb.yml b/config/locales/nb.yml index 79d6eea4fd..7272f9597a 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -81,8 +81,6 @@ nb: white_label_logo_link: "Link til logoen som brukes i butikk" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Å arve skattekategorien krever en varekalkulator." spree/image: attributes: attachment: @@ -99,8 +97,6 @@ nb: attributes: base: card_expired: "har utgått" - spree/product: - must_exist: 'må eksistere' order_cycle: attributes: orders_close_at: @@ -108,7 +104,6 @@ nb: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "må være tomt fordi du bruker produsentens lagerinnstillinger" - on_demand_but_count_on_hand_set: "må være tom hvis på forespørsel" limited_stock_but_no_count_on_hand: "må spesifiseres fordi det tvinger begrenset lager" messages: confirmation: "samsvarer ikke med %{attribute}" @@ -559,6 +554,8 @@ nb: remove: Fjern image: edit: Rediger + product_preview: + shop_tab: Butikk adjustments: skipped_changing_canceled_order: "Du kan ikke endre en avbrutt bestilling." begins_at: Begynner på @@ -774,7 +771,6 @@ nb: variants: infinity: "Uendelig" to_order_tip: "Varer laget for bestilling har ikke et lagernivå, slik som ferske skiver brød laget for bestilling." - back_to_products_list: "Tilbake til produktlisten" editing_product: "Redigerer produkt" tabs: product_details: "Produktdetaljer" @@ -814,7 +810,6 @@ nb: search: Søk sort: pagination: - total_html: "%{total} produkter funnet for søkekriteriene dine. Viser %{from} til %{to} ." per_page: show: Vis per_page: "%{num} per side" @@ -1385,10 +1380,8 @@ nb: contact_name: Kontaktnavn edit: editing: 'innstillinger:' - back_link: Tilbake til bedriftsliste new: title: Ny Bedrift - back_link: Tilbake til bedriftsliste welcome: welcome_title: Velkommen til Open Food Network! welcome_text: Du har opprettet en @@ -1425,6 +1418,8 @@ nb: choose_products_from: "Velg Produkter Fra:" re_notify_producers: Varsle produsentene på nytt notify_producers_tip: Dette vil sende en epost til hver produsent med listen over deres bestillinger. + date_time_warning_modal_content: + cancel: 'Avbryt' incoming: incoming: "Innkommende" supplier: "Leverandør" @@ -2884,7 +2879,6 @@ nb: spree_admin_single_enterprise_hint: "Hint: For å hjelpe folk til å finne deg, skru på din synlighet under" spree_admin_eg_pickup_from_school: "f.eks. 'Henting ved Sammfunnshuset'" spree_admin_eg_collect_your_order: "f.eks. 'Vennligst hente din bestilling fra 123 Matveien, Nesodden, 3070'" - spree_classification_primary_taxon_error: "Gruppe %{taxon} er hovedgruppe til %{product} og kan ikke slettes" spree_order_availability_error: "Distributør eller bestillingsrunde kan ikke levere produktene i handlekurven din" spree_order_populator_error: "Den distributøren eller bestillingsrunden kan ikke levere alle produktene i handlekurven din. Vennligst velg en annen." spree_order_cycle_error: "Vennligst velg en bestillingsrunde for denne bestillingen." @@ -3204,7 +3198,7 @@ nb: order_cycles_no_permission_to_coordinate_error: "Ingen av bedriftene dine har tillatelse til å koordinere en bestillingsrunde" order_cycles_no_permission_to_create_error: "Du har ikke rettigheter til å opprette en bestillingsrunde som er koordinert av den bedriften" order_cycle_closed: "Bestillingsrunden du har valgt har akkurat stengt. Vennligst prøv igjen!" - back_to_orders_list: "Tilbake til bestillingslisten" + back_to_orders_list: "Tilbake til Bestillingsliste" no_orders_found: "Ingen bestillinger funnet" order_information: "Bestillingsinformasjon" new_payment: "Ny betaling" @@ -3547,6 +3541,8 @@ nb: confirmation: | Dette vil sette varebeholdning til null på alle produkter for denne virksomheten som ikke er til stede i den nedlastede filen. order_cycles: + unsaved_changes: "Du har ulagrede endringer" + bulk_save_error: "Å nei! Jeg kunne ikke lagre endringene dine." create_failure: "Kunne ikke opprette bestillingsrunde" update_success: 'Din bestillingsrunde har blitt oppdatert.' update_failure: "Kunne ikke oppdatere bestillingsrunde" @@ -3768,7 +3764,6 @@ nb: more: "Mer" new_adjustment: "Ny justering" new_tax_category: "Ny avgiftskategori" - new_taxon: "Ny kategori" new_user: "Ny bruker" no_pending_payments: "Ingen ventende betalinger" remove: "Fjern" @@ -3787,10 +3782,6 @@ nb: delivery: "Signert, forseglet, levert" start_date: "Startdato" successfully_removed: "Fjernet OK" - taxonomy_edit: "Rediger kategori" - taxonomy_tree_error: "Det oppsto en feil under oppdatering av kategoritreet." - taxonomy_tree_instruction: "Høyreklikk på et element for å legge til, gi nytt navn, fjerne eller redigere." - tree: "Tre" updating: "Oppdaterer" your_order_is_empty_add_product: "Bestillingen din er tom, vennligst søk etter og legg til et produkt over" add_product: "Legg til produkt" @@ -3888,7 +3879,6 @@ nb: tax_rate_amount_explanation: "Avgiftssatser er et desimalbeløp til hjelp i beregninger, (dvs. dersom avgiftssatsen er 5%, angi 0,05)" included_in_price: "Inkludert i Pris" show_rate_in_label: "Vis sats på merkelapp" - back_to_tax_rates_list: "Tilbake til siden for Avgiftssats" tax_settings: "Avgiftsinnstillinger" zones: "Soner" new_zone: "Ny Sone" @@ -3901,14 +3891,11 @@ nb: iso_name: "ISO-navn" states_required: "Regioner Påkrevd" editing_country: "Redigerer Land" - back_to_countries_list: "Tilbake til Landlisten" states: "Regioner" abbreviation: "Forkortelse" new_state: "Ny Region" payment_methods: "Betalingsmetoder" - taxonomies: "Taksonomier" - new_taxonomy: "Ny Taksonomi" - back_to_taxonomies_list: "Tilbake til Taksonomiliste" + taxons: "Produktkategorier" shipping_methods: "Leveringsmetoder" shipping_method: "Leveringsmetode" shipment: "Leveranse" @@ -4068,7 +4055,6 @@ nb: continue: "Fortsett" new: new_return_authorization: "Ny Returautorisasjon" - back_to_return_authorizations_list: "Tilbake til liste over Returautorisasjoner" continue: "Fortsett" edit: receive: "motta" @@ -4315,6 +4301,7 @@ nb: product_name: Produktnavn primary_taxon_form: product_category: Produktkategori + search_for_categories: "Søk etter kategorier" group_buy_form: group_buy: "Gruppekjøp?" bulk_unit_size: Bulk enhetsstørrelse @@ -4392,9 +4379,10 @@ nb: total: "Total" billing_address_name: "Navn" taxons: + index: + title: "Produktkategorier" form: name: Navn - permalink: Permalink meta_title: Metatittel meta_description: Metabeskrivelse meta_keywords: Meta nøkkelord @@ -4636,7 +4624,6 @@ nb: key_cleared: "Nøkkelen ble fjernet" shipment: cannot_ready: "Kan ikke gjøre klar forsendelse." - invalid_taxonomy_id: "Ugyldig kategori-id." toggle_api_key_view: "Vis API-nøkkelvisning for bruker" activerecord: models: diff --git a/config/locales/nl_BE.yml b/config/locales/nl_BE.yml index 02bce0f0f6..a62582ba15 100644 --- a/config/locales/nl_BE.yml +++ b/config/locales/nl_BE.yml @@ -65,7 +65,6 @@ nl_BE: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "moet leeg zijn omdat u de instellingen voor de voorraad van de producent gebruikt" - on_demand_but_count_on_hand_set: "moet leeg zijn op aanvraag" limited_stock_but_no_count_on_hand: "moet worden opgegeven omdat een beperkte voorraad wordt geforceerd" messages: blank: "kan niet leeg zijn" @@ -285,7 +284,6 @@ nl_BE: password_confirmation: Wachtwoord Bevestiging reset_password_token: Reset wachtwoord token expired: is verlopen, gelieve een nieuwe aan te vragen - back_to_payments_list: "Terug naar Betalingslijst" maestro_or_solo_cards: "Maestro/Solo kaart" backordered: "Niet in stock" on_hand: "Bij de Hand" @@ -345,6 +343,8 @@ nl_BE: remove: Verwijderen image: edit: bewerking + product_preview: + shop_tab: winkel begins_at: Begint Bij begins_on: Begint Op customer: Klant @@ -527,7 +527,6 @@ nl_BE: variants: infinity: "Oneindigheid" to_order_tip: "Op bestelling gemaakte artikelen hebben geen vast voorraadniveau, zoals broden die vers op bestelling worden gemaakt." - back_to_products_list: "Terug naar productenlijst" editing_product: "Product bijwerken" tabs: product_details: "Product details" @@ -985,10 +984,8 @@ nl_BE: contact_name: Naam contactpersoon edit: editing: 'Instellingen:' - back_link: Terug naar bedrijvenlijst new: title: Nieuwe onderneming - back_link: Terug naar bedrijvenlijst welcome: welcome_title: Welkom bij het Open Food Network! welcome_text: U heeft met succes een @@ -1023,6 +1020,8 @@ nl_BE: back_to_list: "Terug naar lijst" save_and_back_to_list: "Opslaan en terug naar lijst" choose_products_from: "Kies Producten Uit:" + date_time_warning_modal_content: + cancel: 'Annuleren' incoming: incoming: "Binnenkomend" supplier: "Leverancier" @@ -2294,7 +2293,6 @@ nl_BE: spree_admin_single_enterprise_hint: "Hint: Om het voor mensen mogelijk te maken je te vinden, zet je zichtbaarheid aan" spree_admin_eg_pickup_from_school: "bv. 'Ophaling op een basisschool'" spree_admin_eg_collect_your_order: "bv. 'Verzamel alsjeblieft je bestelling op de stationsstraat 123, 9000 Gent'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is de eerste taxon van %{product} en kan niet verwijderd worden." spree_order_availability_error: "De verdeler of de bestelcyclus kan de producten in je winkelwagentje niet toevoegen" spree_order_populator_error: "De verdeler of de bestelcyclus kan al de producten in je winkelwagentje niet toevoegen. Gelieve een andere te kiezen." spree_order_populator_availability_error: "Dit produkt is niet toegelaten op de gekozen verdeler of bestelcyclus." @@ -2575,7 +2573,7 @@ nl_BE: order_cycles_no_permission_to_coordinate_error: "Geen van uw bedrijven heeft de vereiste rechten om een verkoopcyclus te coördineren" order_cycles_no_permission_to_create_error: "U bezit de vereiste rechten niet om een bestelcyclus te maken die gecoördineerd wordt door dat bedrijf" order_cycle_closed: "De hub die u hebt geselecteerd is tijdelijk gesloten. Probeer gerust later terug." - back_to_orders_list: "Terug naar de bestellijst" + back_to_orders_list: "Terug naar de bestellingslijst" no_orders_found: "Geen bestelling gevonden" order_information: "Info bestelling" new_payment: "Nieuwe betaling" @@ -2885,6 +2883,8 @@ nl_BE: Deze handeling zet het stockpeil op nul bedrijf voor de produkten niet aanwezig in deze file order_cycles: + unsaved_changes: "U heeft niet opgeslagen wijzigingen" + bulk_save_error: "Ah nee! Ik kon onmogelijk uw wijzigingen opslaan." create_failure: "Het maken van de bestelcyclus is mislukt" update_success: 'Uw bestelcyclus is bijgewerkt' update_failure: "De bijwerking van de bestelcyclus is mislukt" @@ -3086,7 +3086,6 @@ nl_BE: tax_rate_amount_explanation: "BTW percentages zijn decimaal om de berekening gemakkelijker te maken, (bv. als de percentage 5% is dan 0.05 invoeren)" included_in_price: "In de prijs inbegrepen " show_rate_in_label: "Toon de percentahe op de label " - back_to_tax_rates_list: "Terug naar de lijst BTW percentages" tax_settings: "Belastings Instellingen" zones: "Zones " new_zone: "Nieuwe zone" @@ -3099,14 +3098,11 @@ nl_BE: iso_name: "ISO Naam" states_required: "Staten Vereist" editing_country: "Editing Land" - back_to_countries_list: "Terug naar landenlijst" states: "Provincies" abbreviation: "Afkortingen " new_state: "Nieuw Provincie" payment_methods: "Betalingsmethode" - taxonomies: "Taxonomies " - new_taxonomy: "Nieuwe taxonomie " - back_to_taxonomies_list: "Terug naar de lijst Taxonomies" + taxons: "Productcategorieën" shipping_methods: "Methode Verzending" shipping_method: "Het verschepen Methode" shipment: "Verzending" @@ -3240,7 +3236,6 @@ nl_BE: continue: "Ga verder" new: new_return_authorization: "Nieuwe terugkeer goedkeuring" - back_to_return_authorizations_list: "Terug naar goedkeuringslijst" continue: "Ga verder" edit: receive: "Ontvangst" @@ -3515,9 +3510,10 @@ nl_BE: total: "Totaal" billing_address_name: "Naam" taxons: + index: + title: "Productcategorieën" form: name: Naam - permalink: Permalink description: Beschrijving general_settings: edit: @@ -3698,7 +3694,6 @@ nl_BE: key_cleared: "Sleutel gewist" shipment: cannot_ready: "Kan levering niet klaarmaken" - invalid_taxonomy_id: "Onjuiste taxonomie id." components: search_input: placeholder: Zoeken diff --git a/config/locales/pa.yml b/config/locales/pa.yml index a0da7d78e1..97cf9822b0 100644 --- a/config/locales/pa.yml +++ b/config/locales/pa.yml @@ -78,8 +78,6 @@ pa: white_label_logo_link: "ਸ਼ੌਪਫਰੰਟ ਵਿੱਚ ਵਰਤੇ ਗਏ ਲੋਗੋ ਦਾ ਲਿੰਕ" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "ਟੈਕਸ ਸ਼੍ਰੇਣੀ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਪ੍ਰਤੀ-ਆਈਟਮ ਕੈਲਕੁਲੇਟਰ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ।" spree/user: attributes: email: @@ -99,7 +97,6 @@ pa: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "ਖਾਲੀ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ ਕਿਉਂਕਿ ਉਤਪਾਦਕ ਸਟਾਕ ਸੈਟਿੰਗਾਂ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਹੈ" - on_demand_but_count_on_hand_set: "ਜੇ ਡਿਮਾਂਡ ਤੇ ਹੋਵੇ ਤਾਂ ਖਾਲੀ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ" limited_stock_but_no_count_on_hand: "ਸੀਮਤ ਸਟਾਕ ਨੂੰ ਮਜਬੂਰ ਕਰਨ ਕਰਕੇ ਨਿਰਧਾਰਤ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ" messages: confirmation: "%{attribute} ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦਾ" @@ -463,7 +460,7 @@ pa: password_confirmation: ਪਾਸਵਰਡ ਦੀ ਪੁਸ਼ਟੀ reset_password_token: ਪਾਸਵਰਡ ਟੋਕਨ ਨੂੰ ਰੀਸੈਟ ਕਰੋ expired: ਮਿਆਦ ਪੁੱਗ ਗਈ ਹੈ, ਕਿਰਪਾ ਕਰਕੇ ਨਵੇਂ ਲਈ ਬੇਨਤੀ ਕਰੋ - back_to_payments_list: "ਭੁਗਤਾਨ ਸੂਚੀ ਤੇ ਵਾਪਸ" + back_to_payments_list: "ਭੁਗਤਾਨ ਸੂਚੀ ਤੇ ਵਾਪਸ ਜਾਓ" maestro_or_solo_cards: "ਮਾਏਸਟ੍ਰੋ/ਸੋਲੋ ਕਾਰਡ" backordered: "ਬੈਕਆਰਡਰ ਕੀਤਾ ਗਿਆ" on_hand: "ਹੱਥ ਵਿਚ" @@ -534,6 +531,8 @@ pa: remove: Remove image: edit: ਸੰਪਾਦਿਤ ਕਰੋ + product_preview: + shop_tab: ਸ਼ਾਪ adjustments: skipped_changing_canceled_order: "ਤੁਸੀਂ ਰੱਦ ਕੀਤੇ ਆਰਡਰ ਨੂੰ ਬਦਲ ਨਹੀਂ ਸਕਦੇ ਹੋ।" begins_at: ਇਸਤੇ ਸ਼ੁਰੂ ਹੋਵੇਗਾ @@ -736,7 +735,6 @@ pa: variants: infinity: "ਇਨਫਿਨਿਟੀ" to_order_tip: "ਆਰਡਰ ਉਤੇ ਬਣੀਆਂ ਆਈਟਮਾਂ ਵਿੱਚ ਸਟਾਕ ਦੇ ਪੱਧਰ ਨਿਰਧਾਰਤ ਨਹੀਂ ਹੁੰਦੇ, ਜਿਵੇਂ ਕਿ ਆਰਡਰ ਤੇ ਬਣਾਈ ਗਈ ਤਾਜ਼ਾ ਬ੍ਰੈਡ।" - back_to_products_list: "ਉਤਪਾਦਾਂ ਦੀ ਸੂਚੀ ਤੇ ਵਾਪਿਸ" editing_product: "ਉਤਪਾਦ ਦਾ ਸੰਪਾਦਨ" tabs: product_details: "ਉਤਪਾਦ ਵੇਰਵੇ" @@ -761,7 +759,6 @@ pa: search: ਖੋਜੋ sort: pagination: - total_html: "ਤੁਹਾਡੇ ਖੋਜ ਮਾਪਦੰਡ ਲਈ %{total} ਉਤਪਾਦ ਮਿਲੇ ਹਨ। %{from} ਤੋਂ %{to} ਦਿਖਾ ਰਹੇ ਹਨ।" per_page: show: ਵਿਖਾਓ per_page: "%{num} ਪ੍ਰਤੀ ਪੇਜ" @@ -1272,10 +1269,8 @@ pa: contact_name: ਸੰਪਰਕ ਨਾਮ edit: editing: 'ਸੈਟਿੰਗਾਂ:''' - back_link: ਐਂਟਰਪ੍ਰਾਈਜ਼ ਸੂਚੀ ਤੇ ਵਾਪਸ ਜਾਓ new: title: ਐਂਟਰਪ੍ਰਾਈਜ਼ਜ਼ - back_link: ਐਂਟਰਪ੍ਰਾਈਜ਼ ਸੂਚੀ ਤੇ ਵਾਪਸ ਜਾਓ welcome: welcome_title: ਓਪਨ ਫੂਡ ਨੈਟਵਰਕ ਵਿੱਚ ਤੁਹਾਡਾ ਸੁਆਗਤ ਹੈ! welcome_text: ਤੁਸੀਂ ਸਫਲਤਾਪੂਰਵਕ ਇਹ ਬਣਾ ਲਿਆ ਹੈ @@ -1312,6 +1307,8 @@ pa: choose_products_from: "ਇਥੋਂ ਉਤਪਾਦ ਚੁਣੋ:" re_notify_producers: ਉਤਪਾਦਕਾਂ ਨੂੰ ਦੁਬਾਰਾ ਸੂਚਿਤ ਕਰੋ notify_producers_tip: ਇਹ ਹਰੇਕ ਉਤਪਾਦਕ ਨੂੰ ਉਹਨਾਂ ਦੇ ਆਰਡਰ ਦੀ ਸੂਚੀ ਦੇ ਨਾਲ ਇੱਕ ਈਮੇਲ ਭੇਜੇਗਾ। + date_time_warning_modal_content: + cancel: 'ਰੱਦ ਕਰੋ' incoming: incoming: "ਅੰਦਰ ਆਉਣ ਵਾਲੇ" supplier: "ਸਪਲਾਇਰ" @@ -2741,7 +2738,6 @@ pa: spree_admin_single_enterprise_hint: "ਸੰਕੇਤ: ਲੋਕਾਂ ਨੂੰ ਤੁਹਾਨੂੰ ਲੱਭਣ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣ ਲਈ, ਇਸ ਦੇ ਹੇਠਾਂ ਆਪਣੀ ਦਿੱਖ ਨੂੰ ਚਾਲੂ ਕਰੋ" spree_admin_eg_pickup_from_school: "ਜਿਵੇਂ ਕਿ 'ਪ੍ਰਾਇਮਰੀ ਸਕੂਲ ਤੋਂ ਪਿਕ-ਅੱਪ'" spree_admin_eg_collect_your_order: "ਜਿਵੇਂ ਕਿ 'ਕਿਰਪਾ ਕਰਕੇ 123 ਇਮੇਜਿਨਰੀ ਸੇਂਟ, ਨੌਰਥਕੋਟ, 3070' ਤੋਂ ਆਪਣਾ ਆਰਡਰ ਲਵੋ" - spree_classification_primary_taxon_error: "ਟੈਕਸਨ %{taxon}, %{product} ਦਾ ਪ੍ਰਾਇਮਰੀ ਟੈਕਸਨ ਹੈ ਅਤੇ ਇਸ ਨੂੰ ਮਿਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ" spree_order_availability_error: "ਵਿਤਰਕ ਜਾਂ ਆਰਡਰ ਸਾਈਕਲ ਤੁਹਾਡੇ ਕਾਰਟ ਵਿੱਚ ਉਤਪਾਦਾਂ ਦੀ ਸਪਲਾਈ ਨਹੀਂ ਕਰ ਸਕਦਾ" spree_order_populator_error: "ਉਹ ਵਿਤਰਕ ਜਾਂ ਆਰਡਰ ਸਾਈਕਲ ਤੁਹਾਡੇ ਕਾਰਟ ਵਿੱਚ ਦਿੱਤੇ ਸਾਰੇ ਉਤਪਾਦਾਂ ਦੀ ਸਪਲਾਈ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਕਿਰਪਾ ਕਰਕੇ ਕੋਈ ਹੋਰ ਚੁਣੋ।" spree_order_cycle_error: "ਕਿਰਪਾ ਕਰਕੇ ਇਸ ਆਰਡਰ ਲਈ ਇੱਕ ਆਰਡਰ ਸਾਈਕਲ ਚੁਣੋ।" @@ -3403,6 +3399,8 @@ pa: confirmation: | ਇਹ ਇਸ ਐਂਟਰਪ੍ਰਾਈਜ਼ ਲਈ ਉਹਨਾਂ ਸਾਰੇ ਉਤਪਾਦਾਂ ਦੇ ਸਟਾਕ ਪੱਧਰ ਨੂੰ ਜ਼ੀਰੋ ਤੇ ਸੇਟ ਕਰ ਦੇਵੇਗਾ ਜੋ ਅੱਪਲੋਡ ਕੀਤੀ ਫਾਈਲ ਵਿੱਚ ਮੌਜੂਦ ਨਹੀਂ ਹਨ। order_cycles: + unsaved_changes: "ਤੁਹਾਡੇ ਕੋਲ ਸੇਵ ਨਾ ਕੀਤੀਆਂ ਤਬਦੀਲੀਆਂ ਹਨ" + bulk_save_error: "ਓ ਨਹੀਂ! ਮੈਂ ਤੁਹਾਡੇ ਬਦਲਾਵਾਂ ਨੂੰ ਸੇਵ ਕਰਨ ਵਿੱਚ ਅਸਮਰੱਥ ਰਿਹਾ।" create_failure: "ਆਰਡਰ ਸਾਈਕਲ ਬਣਾਉਣ ਵਿੱਚ ਅਸਫਲ" update_success: 'ਤੁਹਾਡਾ ਆਰਡਰ ਸਾਈਕਲ ਅੱਪਡੇਟ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ।''' update_failure: "ਆਰਡਰ ਸਾਈਕਲ ਅੱਪਡੇਟ ਕਰਨ ਵਿੱਚ ਅਸਫਲ" @@ -3550,7 +3548,6 @@ pa: more: "ਹੋਰ" new_adjustment: "ਨਵਾਂ ਸਮਾਯੋਜਨ" new_tax_category: "ਨਵੀਂ ਟੈਕਸ ਸ਼੍ਰੇਣੀ" - new_taxon: "ਨਵਾਂ ਟੈਕਸੋਨ" new_user: "ਨਵਾਂ ਉਪਭੋਗਤਾ" no_pending_payments: "ਕੋਈ ਬਕਾਇਆ ਭੁਗਤਾਨ ਨਹੀਂ ਹੈ" remove: "Remove" @@ -3569,8 +3566,6 @@ pa: delivery: "ਦਸਤਖਤ ਕੀਤੇ, ਸੀਲ ਕੀਤੇ, ਡਿਲੀਵਰ ਕੀਤੇ ਗਏ" start_date: "ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਮਿਤੀ" successfully_removed: "ਸਫਲਤਾਪੂਰਵਕ ਹਟਾਇਆ ਗਿਆ" - taxonomy_edit: "ਟੈਕਸੋਨੌਮੀ ਸੰਪਾਦਨ" - tree: "ਟ੍ਰੀ" updating: "ਅੱਪਡੇਟ ਹੋ ਰਿਹਾ ਹੈ" your_order_is_empty_add_product: "ਤੁਹਾਡਾ ਆਰਡਰ ਖਾਲੀ ਹੈ, ਕਿਰਪਾ ਕਰਕੇ ਉਪਰ ਇੱਕ ਉਤਪਾਦ ਦੀ ਖੋਜ ਕਰੋ ਅਤੇ ਜੋੜੋ" add_product: "ਉਤਪਾਦ ਜੋੜੋ" @@ -3667,7 +3662,6 @@ pa: tax_rate_amount_explanation: "ਟੈਕਸ ਦੀਆਂ ਦਰਾਂ ਗਣਨਾ ਵਿੱਚ ਸਹਾਇਤਾ ਲਈ ਇੱਕ ਦਸ਼ਮਲਵ ਰਕਮ ਹਨ, (ਜਿਵੇਂ ਕਿ ਜੇਕਰ ਟੈਕਸ ਦਰ 5% ਹੈ ਤਾਂ 0.05 ਦਰਜ ਕਰੋ)" included_in_price: "ਕੀਮਤ ਵਿੱਚ ਸ਼ਾਮਲ" show_rate_in_label: "ਲੇਬਲ ਵਿੱਚ ਦਰ ਦਿਖਾਓ" - back_to_tax_rates_list: "ਟੈਕਸ ਦਰਾਂ ਦੀ ਸੂਚੀ ਤੇ ਵਾਪਸ" tax_settings: "ਟੈਕਸ ਸੈਟਿੰਗਾਂ" zones: "ਜ਼ੋਨਾਂ" new_zone: "ਨਵਾਂ ਜ਼ੋਨ" @@ -3680,14 +3674,11 @@ pa: iso_name: "ISO ਨਾਂ" states_required: "ਰਾਜਾਂ ਦੀ ਲੋੜ ਹੈ" editing_country: "ਦੇਸ਼ ਦਾ ਸੰਪਾਦਨ" - back_to_countries_list: "ਦੇਸ਼ਾਂ ਦੀ ਸੂਚੀ ਤੇ ਵਾਪਸ" states: "ਰਾਜ" abbreviation: "ਸੰਖਿਪਤ ਰੂਪ" new_state: "ਨਵਾਂ ਰਾਜ" payment_methods: "ਭੁਗਤਾਨ ਦੇ ਢੰਗ" - taxonomies: "ਟੈਕਸੋਨੌਮੀਜ਼" - new_taxonomy: "ਨਵਾਂ ਟੈਕਸੋਨੌਮੀ" - back_to_taxonomies_list: "\"ਟੈਕਸਨੋਮੀਜ਼ ਸੂਚੀ ਤੇ ਵਾਪਸ" + taxons: "ਉਤਪਾਦ ਸ਼੍ਰੇਣੀਆਂ" shipping_methods: "ਸ਼ਿਪਿੰਗ ਦੇ ਢੰਗ" shipping_method: "ਸ਼ਿਪਿੰਗ ਦਾ ਤਰੀਕਾ" shipment: "ਸ਼ਿਪਮੈਂਟ" @@ -3845,7 +3836,6 @@ pa: continue: "ਜਾਰੀ ਰੱਖੋ" new: new_return_authorization: "ਨਵਾਂ ਵਾਪਸੀ ਅਧਿਕਾਰ" - back_to_return_authorizations_list: "ਵਾਪਸੀ ਅਧਿਕਾਰ ਸੂਚੀ ਤੇ ਵਾਪਸ" continue: "ਜਾਰੀ ਰੱਖੋ" edit: receive: "ਪ੍ਰਾਪਤ ਕਰੋ" @@ -4160,9 +4150,10 @@ pa: total: "ਕੁੱਲ" billing_address_name: "ਨਾਮ" taxons: + index: + title: "ਉਤਪਾਦ ਸ਼੍ਰੇਣੀਆਂ" form: name: ਨਾਂ - permalink: ਪਰਮਾਲਿੰਕ description: ਵਰਣਨ general_settings: edit: @@ -4400,7 +4391,6 @@ pa: key_cleared: "ਕੁੰਜੀ ਹਟਾਈ ਗਈ" shipment: cannot_ready: "ਸ਼ਿਪਮੈਂਟ ਤਿਆਰ ਨਹੀਂ ਹੋ ਸਕਦੀ।" - invalid_taxonomy_id: "ਅਵੈਧ ਟੈਕਸੋਨੌਮੀ ਆਈਡੀ।" toggle_api_key_view: "ਉਪਭੋਗਤਾ ਲਈ API ਕੁੰਜੀ ਦ੍ਰਿਸ਼ ਵਿਖਾਓ" unit: ਯੂਨਿਟ per_unit: ਪ੍ਰਤੀ ਯੂਨਿਟ diff --git a/config/locales/pl.yml b/config/locales/pl.yml index c88ef74532..40ce862678 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -63,7 +63,6 @@ pl: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "musi być puste, ponieważ używane są ustawienia zapasów producenta" - on_demand_but_count_on_hand_set: "musi być puste, jeśli na żądanie" limited_stock_but_no_count_on_hand: "należy określić, z powodu ograniczonych zapasów" messages: blank: "nie może być puste" @@ -286,7 +285,6 @@ pl: password_confirmation: Potwierdzenie hasła reset_password_token: Token resetowania hasła expired: wygasł, poproś o nowy - back_to_payments_list: "Powrót do listy płatności" maestro_or_solo_cards: "Karty Maestro / Solo" backordered: "Zamówienie zaległe" on_hand: "Dostępne" @@ -341,6 +339,8 @@ pl: remove: Usuń image: edit: Edytuj + product_preview: + shop_tab: Sklep begins_at: Początek o begins_on: Początek się w dniu customer: Klient @@ -511,7 +511,6 @@ pl: variants: infinity: "Nieskończoność" to_order_tip: "Przedmioty wykonane na zamówienie nie mają ustalonego poziomu zapasów, np. Świeże bochenki chleba na zamówienie." - back_to_products_list: "Powrót do listy produktów" editing_product: "Edycja produktu" tabs: product_details: "Szczegóły produktu" @@ -933,10 +932,8 @@ pl: contact_name: Nazwa Kontaktu edit: editing: 'Ustawienia:' - back_link: Powrót do listy podmiotów new: title: Nowy podmiot - back_link: Powrót do listy podmiotów welcome: welcome_title: Witamy w Open Food Network! welcome_text: Pomyślnie utworzyłeś @@ -967,6 +964,8 @@ pl: back_to_list: "Powrót do listy" save_and_back_to_list: "Zapisz i wróć do listy" choose_products_from: "Wybierz produkty z:" + date_time_warning_modal_content: + cancel: 'Anuluj' incoming: incoming: "Przychodzący" supplier: "Dostawca" @@ -2173,7 +2172,6 @@ pl: spree_admin_single_enterprise_hint: "Wskazówka: aby inni mogli Cię znaleźć, włącz widoczność poniżej" spree_admin_eg_pickup_from_school: "np: 'Odbiór w Domu Kultury Krąg'" spree_admin_eg_collect_your_order: "np. „Proszę odebrać zamówienie pod adresem Słowackiego 19, Poznań”" - spree_classification_primary_taxon_error: "Takson %{taxon} jest głównym taksonem %{product} i nie można go usunąć" spree_order_availability_error: "Dystrybutor lub cykl zamówień nie mogą dostarczyć produktów w Twoim koszyku" spree_order_populator_error: "Ten dystrybutor lub cykl zamówień nie może dostarczyć wszystkich produktów w Twoim koszyku. Proszę wybierz inny." spree_order_populator_availability_error: "Ten produkt nie jest dostępny u wybranego dystrybutora lub cyklu zamówień." @@ -2444,7 +2442,6 @@ pl: order_cycles_no_permission_to_coordinate_error: "Żaden z Twoich podmiotów nie ma uprawnień do koordynowania cyklu zamówień" order_cycles_no_permission_to_create_error: "Nie masz uprawnień do tworzenia cyklu zamówień koordynowanego przez ten podmiot" order_cycle_closed: "Wybrany cykl zamówień właśnie się zakończył. Proszę spróbuj ponownie!" - back_to_orders_list: "Powrót do listy zamówień" no_orders_found: "Nie znaleziono zamówień" order_information: "Szczegóły zamówienia" date_completed: "Data zakończenia" @@ -2748,6 +2745,7 @@ pl: confirmation: | Spowoduje to ustawienie poziomu zapasów na zero dla wszystkich produktów tego podmiotu, których nie ma w przesłanym pliku. order_cycles: + unsaved_changes: "Masz niezapisane zmiany" create_failure: "Nie udało się utworzyć cyklu zamówień" update_success: 'Twój cykl zamówień został zaktualizowany.' update_failure: "Nie udało się zaktualizować cyklu zamówień" @@ -3070,7 +3068,6 @@ pl: tax_rate_amount_explanation: "Stawki podatku są kwotami dziesiętnymi, aby ułatwić obliczenia (tj. Jeśli stawka podatku wynosi 5%, wprowadź 0,05)" included_in_price: "Wliczony w cenę" show_rate_in_label: "Pokaż kurs na etykiecie" - back_to_tax_rates_list: "Powrót do listy stawek podatkowych" tax_settings: "Ustawienia podatkowe" zones: "Strefy" new_zone: "Nowa strefa" @@ -3083,14 +3080,11 @@ pl: iso_name: "Nazwa ISO" states_required: "Województwo wymagane" editing_country: "Edycja kraju" - back_to_countries_list: "Powrót do listy krajów" states: "Województwa" abbreviation: "Skrót" new_state: "Nowe województwo" payment_methods: "Metody płatności" - taxonomies: "Taksonomie" - new_taxonomy: "Nowa taksonomia" - back_to_taxonomies_list: "Powrót do listy taksonomii" + taxons: "Kategorie produktów" shipping_methods: "Metody dostaw" shipping_method: "Metoda dostawy" payment: "Płatność" @@ -3217,7 +3211,6 @@ pl: continue: "Kontyntynuj" new: new_return_authorization: "Nowa autoryzacja zwrotu" - back_to_return_authorizations_list: "Powrót do listy autoryzacji zwrotu" continue: "Kontyntynuj" edit: receive: "otrzymać" @@ -3481,6 +3474,8 @@ pl: total: "Razem" billing_address_name: "Nazwa" taxons: + index: + title: "Kategorie produktów" form: name: Nazwa description: Opis @@ -3656,7 +3651,6 @@ pl: key_cleared: "Klucz wyczyszczony" shipment: cannot_ready: "Nie można przygotować wysyłki." - invalid_taxonomy_id: "Nieprawidłowy identyfikator taksonomii." activerecord: models: spree/payment: diff --git a/config/locales/pt.yml b/config/locales/pt.yml index bc8ab99d9d..746d9b6cf0 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -68,7 +68,6 @@ pt: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "deve ser vazio dado estar a usar as configurações do produtor" - on_demand_but_count_on_hand_set: "deve ser vazio dado estar sob encomenda" limited_stock_but_no_count_on_hand: "deve ser definido dado não estar sob encomenda" messages: blank: "não pode ser vazio" @@ -317,7 +316,6 @@ pt: password_confirmation: Confirmação de Palavra-passe reset_password_token: Redefinir palavra-passe expired: expirou, por favor peça uma nova - back_to_payments_list: "Voltar à lista de pagamentos" maestro_or_solo_cards: "Cartões Maestro/Solo" backordered: "Encomendado" on_hand: "Disponível" @@ -382,6 +380,8 @@ pt: remove: Remover image: edit: Editar + product_preview: + shop_tab: Loja begins_at: Começa às begins_on: Começa em customer: Consumidor/a @@ -566,7 +566,6 @@ pt: variants: infinity: "Infinito" to_order_tip: "Os itens feitos por encomenda não têm um nível de stock definido, como pão fresco, por exemplo." - back_to_products_list: "Voltar à lista de produtos" editing_product: "Editando um Produto" tabs: product_details: "Detalhes do Produto" @@ -1025,10 +1024,8 @@ pt: contact_name: Nome do contacto edit: editing: 'Configurações' - back_link: Voltar à lista de organizações new: title: Nova Organização - back_link: Voltar à lista de organizações welcome: welcome_title: Bem-vindo à Open Food Network! welcome_text: Você criou com sucesso uma @@ -1063,6 +1060,8 @@ pt: back_to_list: "Voltar à Lista" save_and_back_to_list: "Guardar e Voltar à Lista" choose_products_from: "Escolha produtos de:" + date_time_warning_modal_content: + cancel: 'Cancelar' incoming: incoming: "Entrada" supplier: "Fornecedor" @@ -2299,7 +2298,6 @@ pt: spree_admin_single_enterprise_hint: "Dica: Para permitir que as pessoas te encontrem, ative sua visibilidade em" spree_admin_eg_pickup_from_school: "ex: \"Levantar na Escola Primária\"" spree_admin_eg_collect_your_order: "ex: \"Por favor levante a sua encomenda na Rua Imaginária, nº 123, 3070\"" - spree_classification_primary_taxon_error: "A Taxon %{taxon}é a taxon primária de %{product} e não pode ser apagada." spree_order_availability_error: "O distribuidor ou ciclo de encomendas não pode fornecer os produtos do seu carrinho" spree_order_populator_error: "Esse distribuidor ou ciclo de encomendas não pode fornecer todos os produtos do seu carrinho. Por favor escolha outro." spree_order_populator_availability_error: "Esse produto não está disponível no distribuidor ou ciclo de encomendas selecionado." @@ -2575,7 +2573,7 @@ pt: order_cycles_bulk_update_notice: 'Os ciclos de encomendas foram actualizados.' 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" + back_to_orders_list: "Voltar à Lista de Encomendas" no_orders_found: "Nenhuma encomenda encontrada" order_information: "Informação da Encomenda" new_payment: "Novo Pagamento" @@ -2861,6 +2859,8 @@ pt: confirmation: | Isto colocará a zero o stock de todos os produtos desta organização que não estejam presentes no ficheiro carregado. order_cycles: + unsaved_changes: "Tem alterações por guardar" + bulk_save_error: "Oh não! Não conseguimos guardar as suas alterações." create_failure: "Falhou a criação do ciclo de encomendas" update_success: 'O seu ciclo de encomendas foi actualizado.' update_failure: "Falhou a atualização do ciclo de encomendas" @@ -3100,7 +3100,6 @@ pt: tax_rate_amount_explanation: "Taxas de Imposto são uma quantidade decimal que suportam o cálculo (por exemplo, se a taxa de imposto for 5%, insira 0.05)" included_in_price: "Incluído no Preço" show_rate_in_label: "Mostrar taxa na etiqueta" - back_to_tax_rates_list: "Voltar à lista de Taxas de Imposto" tax_settings: "Configurações de Impostos" zones: "Zonas" new_zone: "Nova Zona" @@ -3113,14 +3112,11 @@ pt: iso_name: "Nome ISO" states_required: "Regiões obrigatórias" editing_country: "Editar País" - back_to_countries_list: "Voltar à Lista de Países" states: "Regiões" abbreviation: "Abreviatura" new_state: "Nova Região" payment_methods: "Métodos de pagamento" - taxonomies: "Taxonomias" - new_taxonomy: "Nova Taxonomia" - back_to_taxonomies_list: "Voltar à Lista de Taxonomias" + taxons: "Categorias de Produtos" shipping_methods: "Métodos de Envio" shipping_method: "Método de Envio" shipment: "Envio" @@ -3480,6 +3476,8 @@ pt: total: "Total" billing_address_name: "Nome" taxons: + index: + title: "Categorias de Produtos" form: name: Nome description: Descrição diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index 26b7bbfa2d..2c47f965b5 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -66,7 +66,6 @@ pt_BR: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "deve estar em branco porque está usando configurações de estoque do produtor" - on_demand_but_count_on_hand_set: "deve ficar em branco se sob demanda" limited_stock_but_no_count_on_hand: "deve ser especificado porque está forçando o estoque limitado" messages: blank: "Não pode ser vazio" @@ -345,7 +344,7 @@ pt_BR: password_confirmation: Confirmação de senha reset_password_token: Redefinir chave de senha expired: expirou, por favor solicite um novo - back_to_payments_list: "voltar para lista de pagamento" + back_to_payments_list: "Voltar pra Lista de Pagamentos" maestro_or_solo_cards: "Cartões Maestro / Solo" backordered: "Pedidos em atraso" on_hand: "Disponível" @@ -410,6 +409,8 @@ pt_BR: remove: Remover image: edit: Editar + product_preview: + shop_tab: Comprar adjustments: skipped_changing_canceled_order: "Não é possível modificar um pedido cancelado" begins_at: Começa em @@ -608,7 +609,6 @@ pt_BR: variants: infinity: "Infinidade" to_order_tip: "Os itens feitos por encomenda não têm um nível de estoque definido, como pão fresco, por exemplo." - back_to_products_list: "Voltar à lista de produtos" editing_product: "Editando Produto" tabs: product_details: "Detalhes do Produto" @@ -1064,10 +1064,8 @@ pt_BR: contact_name: Nome para contato edit: editing: 'Configurações:' - back_link: Voltar à lista de iniciativas new: title: Nova iniciativa - back_link: Voltar à lista de iniciativas welcome: welcome_title: Bem-vindo à Ajunta! welcome_text: Você criou com sucesso uma @@ -1102,6 +1100,8 @@ pt_BR: back_to_list: "Voltar Para a Lista" save_and_back_to_list: "Salvar e Voltar para a Lista" choose_products_from: "Escolha produtos de:" + date_time_warning_modal_content: + cancel: 'Cancelar' incoming: incoming: "Entrada" supplier: "Fornecedor" @@ -2403,7 +2403,6 @@ pt_BR: spree_admin_single_enterprise_hint: "Dica: Para permitir que as pessoas te encontrem, ative sua visibilidade em" spree_admin_eg_pickup_from_school: "ex: 'Buscar na Escola'" spree_admin_eg_collect_your_order: "ex: 'Por favor, colete seu pedido na Rua dos Sonhos, 123, casa 5, Bairro Liberdade" - spree_classification_primary_taxon_error: "O táxon %{taxon} é o táxon principal de %{product} e não pode ser excluído" spree_order_availability_error: "O distribuidor ou o ciclo de pedidos não pode fornecer os produtos no seu carrinho" spree_order_populator_error: "Distribuidor ou ciclo de pedidos não pode fornecer os produtos no seu carrinho. Por favor, escolha outro." spree_order_cycle_error: "Favor escolher um ciclo de pedidos para este pedido" @@ -2685,7 +2684,7 @@ pt_BR: order_cycles_no_permission_to_coordinate_error: "Nenhuma das suas iniciativas tem permissão para coordenar um ciclo de pedidos" order_cycles_no_permission_to_create_error: "Você não tem permissão para criar um ciclo de pedidos coordenado por esta iniciativa" order_cycle_closed: "O ciclo de pedidos selecionado acabou de fechar, por favor tente novamente depois!" - back_to_orders_list: "Voltar à lista de pedidos" + back_to_orders_list: "Voltar Para Lista de Pedidos" no_orders_found: "Nenhum pedido encontrado" order_information: "Informações sobre pedidos" new_payment: "Novo Pagamento" @@ -3009,6 +3008,8 @@ pt_BR: Isso ajustará o nível de estoque para zero em todos os produtos para esta iniciativa que não está presente no arquivo carregado. order_cycles: + unsaved_changes: "Você possui alterações não salvas" + bulk_save_error: "Ah não! Não consegui salvar suas alterações." create_failure: "Falha ao criar o ciclo do pedidos" update_success: 'O seu ciclo de pedidos foi atualizado.' update_failure: "Falha ao atualizar o ciclo do pedidos" @@ -3241,7 +3242,6 @@ pt_BR: more: "Mais" new_adjustment: "Novo ajuste" new_tax_category: "Nova Categoria de Taxa" - new_taxon: "Nova taxonomia" new_user: "Novo usuário" no_pending_payments: "Nenhum pagamento pendente" remove: "Remover" @@ -3255,8 +3255,6 @@ pt_BR: server_error: "Erro de servidor" start_date: "Data de início" successfully_removed: "Removido com Sucesso" - taxonomy_edit: "Editar Taxonomia" - tree: "Árvore" updating: "Atualizando" your_order_is_empty_add_product: "Seu pedido está vazio, pesquise e adicione um produto acima" add_product: "Adicionar produto" @@ -3351,7 +3349,6 @@ pt_BR: tax_rate_amount_explanation: "As alíquotas são um valor decimal para ajudar nos cálculos (por exemplo, se a alíquota for de 5%, digite 0,05)" included_in_price: "Incluído no preço" show_rate_in_label: "Mostrar taxa no rótulo" - back_to_tax_rates_list: "Voltar à lista de alíquotas." tax_settings: "Configurações de Taxas" zones: "Zonas" new_zone: "Nova Zona" @@ -3364,14 +3361,10 @@ pt_BR: iso_name: "Nome ISO" states_required: "Estados Necessários" editing_country: "Editando País" - back_to_countries_list: "Voltar à lista de países" states: "Estados" abbreviation: "Abreviação" new_state: "Estado novo" payment_methods: "Métodos de pagamento" - taxonomies: "Taxonomias" - new_taxonomy: "Nova taxonomia" - back_to_taxonomies_list: "Voltar à lista de taxonomias" shipping_methods: "Métodos de envio" shipping_method: "Método de envio" shipment: "Envio" @@ -3525,7 +3518,6 @@ pt_BR: continue: "Continuar" new: new_return_authorization: "Nova Autorização de Retorno" - back_to_return_authorizations_list: "Voltar Para Lista de Autorização de Retorno" continue: "Continuar" edit: receive: "receber" @@ -3812,7 +3804,6 @@ pt_BR: taxons: form: name: Nome - permalink: Permalink description: Descrição general_settings: edit: @@ -4029,7 +4020,6 @@ pt_BR: key_cleared: "Chave apagada" shipment: cannot_ready: "Não é possível completar a entrega." - invalid_taxonomy_id: "Id invalido de taxonomia" activerecord: models: spree/payment: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index a442599358..4d9dc86bbf 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -81,8 +81,6 @@ ru: white_label_logo_link: "Ссылка на логотип, используемый на витрине магазина" errors: models: - enterprise_fee: - inherit_tax_requires_per_item_calculator: "Для наследования налоговой категории требуется поэлементный калькулятор." spree/image: attributes: attachment: @@ -106,7 +104,6 @@ ru: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "должно быть пустым, потому что используются настройки склада производителя" - on_demand_but_count_on_hand_set: "должен быть пустым, если по требованию'" limited_stock_but_no_count_on_hand: "необходимо указать, поскольку принудительный ограниченный запас" messages: confirmation: "не соответствует %{attribute}" @@ -476,7 +473,7 @@ ru: password_confirmation: Подтверждение Пароля reset_password_token: Сброс пароля токена expired: истёк, пожалуйста запросите новый - back_to_payments_list: "Назад к Списку Платежей" + back_to_payments_list: "Вернуться К Списку Платежей" maestro_or_solo_cards: "Дебитовые карты" backordered: "Предзаказано" on_hand: "В наличии" @@ -549,6 +546,8 @@ ru: remove: Удалить image: edit: Изменить + product_preview: + shop_tab: Магазин adjustments: skipped_changing_canceled_order: "Вы не можете изменить отмененный заказ." begins_at: Начинается С @@ -759,7 +758,6 @@ ru: variants: infinity: "Безграничный" to_order_tip: "Товары, изготовленные на заказ, не имеют определенного уровня запасов, например, буханки хлеба, приготовленные на заказ." - back_to_products_list: "Назад к списку товаров" editing_product: "Изменение Товара" tabs: product_details: "Информация о Товаре" @@ -797,7 +795,6 @@ ru: search: Поиск sort: pagination: - total_html: "%{total} товаров найдено по вашим критериям поиска. Показаны %{from} до %{to} ." per_page: show: Показывать per_page: "%{num} на странице" @@ -1341,10 +1338,8 @@ ru: contact_name: Имя Контакта edit: editing: 'Настройки:' - back_link: Назад к списку Предприятий new: title: Новое Предприятие - back_link: Назад к списку Предприятий welcome: welcome_title: Добро пожаловать в Открытую Сеть Продуктов! welcome_text: Вы успешно создали @@ -1381,6 +1376,8 @@ ru: choose_products_from: "Выбрать Товары Из:" re_notify_producers: Повторно уведомить производителей notify_producers_tip: Каждому производителю будет отправлено электронное письмо со списком их заказов. + date_time_warning_modal_content: + cancel: 'Выход' incoming: incoming: "Входящие" supplier: "Поставщик" @@ -2842,7 +2839,6 @@ ru: spree_admin_single_enterprise_hint: "Подсказка: чтобы люди могли вас найти, включите видимость в" spree_admin_eg_pickup_from_school: "напр. 'Самовывоз у школы'" spree_admin_eg_collect_your_order: "напр. 'Пожалуйста, заберите свой заказ по адресу: ул. Школьная д.1, Воронеж'" - spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Дистрибьютор или цикл заказа не могут поставить продукты в Вашей корзине" spree_order_populator_error: "Этот дистрибьютор или цикл заказа не может поставить все продукты в Вашей корзине. Пожалуйста, выберите другой." spree_order_cycle_error: "Выберите цикл для этого заказа." @@ -3162,7 +3158,7 @@ ru: order_cycles_no_permission_to_coordinate_error: "Ни одно из ваших Предприятий не имеет разрешения координировать цикл заказа" order_cycles_no_permission_to_create_error: "У вас нет разрешения на создание цикла заказов, координируемого этим Предприятием" order_cycle_closed: "Выбранный вами цикл заказов закрылся. Пожалуйста, попробуйте еще раз!" - back_to_orders_list: "Назад к списку заказов" + back_to_orders_list: "Назад К Списку Заказов" no_orders_found: "Заказов Не Найдено" order_information: "Информация по Заказу" new_payment: "Новый Платёж" @@ -3513,6 +3509,8 @@ ru: Это установит нулевой уровень запасов для всех продуктов этого предприятия, которых нет в загруженном файле. order_cycles: + unsaved_changes: "Есть не сохраненные изменения" + bulk_save_error: "Ой! Не удалось сохранить Ваши изменения" create_failure: "Не удалось создать цикл заказа" update_success: 'Ваш цикл заказа был обновлен.' update_failure: "Не удалось обновить цикл заказа" @@ -3783,7 +3781,6 @@ ru: more: "Ещё" new_adjustment: "Новая корректировка" new_tax_category: "Новая Налоговая Категория" - new_taxon: "Новая таксономия" new_user: "Новый пользователь" no_pending_payments: "Нет ожидающих платежей" remove: "Удалить" @@ -3802,10 +3799,6 @@ ru: delivery: "Подписано, запечатано, доставлено" start_date: "Дата начала" successfully_removed: "Успешно удалено" - taxonomy_edit: "Изменить таксономию" - taxonomy_tree_error: "Произошла ошибка при обновлении дерева таксономии." - taxonomy_tree_instruction: "Щелкните правой кнопкой мыши элемент, чтобы добавить, переименовать, удалить или отредактировать." - tree: "Дерево" updating: "Обновление" your_order_is_empty_add_product: "Ваша корзина пуста, пожалуйста, найдите и добавьте товар выше" add_product: "Добавить Товар" @@ -3902,7 +3895,6 @@ ru: tax_rate_amount_explanation: "Налоговые ставки представляют собой десятичную сумму для помощи в расчетах (т.е. если налоговая ставка составляет 5%, введите 0,05)" included_in_price: "Включено в Стоимость" show_rate_in_label: "Показывать ставку в метке" - back_to_tax_rates_list: "Назад к Списку Налоговых Ставок" tax_settings: "Настройки Налогов" zones: "Торговая зона" new_zone: "Новая Торговая зона" @@ -3915,14 +3907,11 @@ ru: iso_name: "ISO Имя" states_required: "Области Обязательны" editing_country: "Редактирование Страны" - back_to_countries_list: "Назад к Списку Стран" states: "Регионы" abbreviation: "Сокращение" new_state: "Новый Регион" payment_methods: "Способы Оплаты" - taxonomies: "Таксономии" - new_taxonomy: "Новая Таксономия" - back_to_taxonomies_list: "Назад к Списку Таксономий" + taxons: "Категории Товаров" shipping_methods: "Способы Доставки" shipping_method: "Способ Доставки" shipment: "Доставка" @@ -4082,7 +4071,6 @@ ru: continue: "Продолжить" new: new_return_authorization: "Новое разрешение на возврат" - back_to_return_authorizations_list: "Назад К Списку Разрешений На Возврат" continue: "Продолжить" edit: receive: "получить" @@ -4329,6 +4317,7 @@ ru: product_name: Имя Товара primary_taxon_form: product_category: Категория Товара + search_for_categories: "Поиск категорий" group_buy_form: group_buy: "Групповая покупка?" bulk_unit_size: Размер оптовой еденицы @@ -4406,9 +4395,10 @@ ru: total: "Всего" billing_address_name: "Название" taxons: + index: + title: "Категории Товаров" form: name: Название - permalink: Постоянная ссылка description: Описание general_settings: edit: @@ -4647,7 +4637,6 @@ ru: key_cleared: "Ключ очищен" shipment: cannot_ready: "Не может готовая отгрузка." - invalid_taxonomy_id: "Неверный ID таксономии." toggle_api_key_view: "Показать ключ API для пользователя" activerecord: models: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index bd04ceb7bd..ddac7dc728 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -180,6 +180,8 @@ sv: remove: Ta bort image: edit: Redigera + product_preview: + shop_tab: 'Butik ' customer: Kund date: Datum email: epost @@ -613,11 +615,8 @@ sv: 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: - back_link: Åter till företagslistan new: title: Nya företag - back_link: Åter till företagslistan welcome: welcome_title: Välkommen till OFN! welcome_text: Du har korrekt slutfört en @@ -638,6 +637,8 @@ sv: next: "Näst" cancel: "Avbryt" choose_products_from: "Välj produkter från:" + date_time_warning_modal_content: + cancel: 'Avbryt' incoming: incoming: "Inkommande" supplier: "Leverantör" @@ -1595,7 +1596,6 @@ sv: spree_admin_single_enterprise_hint: "Tips: För att folk skall hitta dig lättare, slå på din reklam under" spree_admin_eg_pickup_from_school: "t.ex. \"Upphämtning från grundskolan\"" spree_admin_eg_collect_your_order: "t.ex. \"Var snäll och samla din beställning från Exempelgatan, 100 00 Stad '" - spree_classification_primary_taxon_error: "Gruppen %{taxon} är den primära gruppen för %{product} och kan inte raderas" spree_order_availability_error: "Distributören eller beställningsomgången kan inte leverera produkterna i din kundvagn" spree_order_populator_error: "Denna distributör eller beställningsomgång kan inte leverera alla produkter i din kundvagn. Vänligen välj en annan." spree_order_populator_availability_error: "Den produkten är inte tillgänglig från den valda distributören eller denna beställningsomgång." @@ -2085,6 +2085,8 @@ sv: confirmation: | Detta kommer att ställa lagersaldot till noll på alla produkter för detta företag, som inte är närvarande i den uppladdade filen. order_cycles: + unsaved_changes: "Du har osparade ändringar" + bulk_save_error: "Å nej! Jag kunde inte spara dina ändringar." update_success: 'Din beställningsomgång har uppdaterats.' no_distributors: Det finns inga distributörer i denna beställningsomgång. Den kommer inte att vara synlig för kunderna tills du lägger till en distributör. Vill du fortsätta och spara denna beställningsomgång? ' enterprises: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index e9525c3929..1c7d15f1cc 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -68,7 +68,6 @@ tr: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "Boş bırakılmalı çünkü üretici stok ayarları kullanılıyor" - on_demand_but_count_on_hand_set: "talep üzerine is boş bırakılmalıdır" limited_stock_but_no_count_on_hand: "Sınırlı stok durumundan dolayı belirtilmeli" messages: blank: "boş olamaz" @@ -390,6 +389,8 @@ tr: remove: Kaldır image: edit: Düzenle + product_preview: + shop_tab: DÜKKAN adjustments: skipped_changing_canceled_order: "İptal edilmiş bir siparişi değiştiremezsiniz." begins_at: Başlangıç @@ -588,7 +589,6 @@ tr: variants: infinity: "Sonsuz" to_order_tip: "Sipariş üzerine yapılan ürünlerin, belirlenmiş stok seviyeleri yoktur." - back_to_products_list: "Ürün listesine geri dön" editing_product: "Ürün Düzenleme" tabs: product_details: "Ürün Detayları" @@ -1053,10 +1053,8 @@ tr: contact_name: İLETİŞİM Kurulacak KİŞİ edit: editing: 'Ayarlar:' - back_link: İşletmeler LİSTESİNE gerİ dön new: title: Yeni İşletme - back_link: İşletmeler LİSTESİNE gerİ dön welcome: welcome_title: Açık Gıda Ağı'na Hoşgeldiniz! welcome_text: 'BAŞARILI ŞEKİLDE OLUŞTURULDU:' @@ -1091,6 +1089,8 @@ tr: back_to_list: "Listeye geri dön" save_and_back_to_list: "KAYDET VE LİSTEYE DÖN" choose_products_from: "Ürünlerİ Buradan SeÇ:" + date_time_warning_modal_content: + cancel: 'İptal et' incoming: incoming: "Gelen" supplier: "TEDARİKÇİ" @@ -2415,7 +2415,6 @@ tr: spree_admin_single_enterprise_hint: "İpucu: İnsanların sizi bulmasına izin vermek için 'Görünür' olmayı ayarlamayı unutmayın:" spree_admin_eg_pickup_from_school: "Örn. 'Teslimat noktası: Moda İlkokulu Bahçesi'" spree_admin_eg_collect_your_order: "Örn. Lütfen siparişinizi Moda Cad. No:17 Temiz Dükkan'dan teslim alınız." - spree_classification_primary_taxon_error: "%{taxon} cinsi, %{product}ürününün birincil cinsidir ve silinemez" spree_order_availability_error: "Dağıtımcı veya sipariş dönemi sepetinizdeki ürünleri tedarik edemiyor" spree_order_populator_error: "Bu dağıtımcı veya sipariş dönemi, sepetinizdeki tüm ürünleri tedarik edemiyor. Lütfen başka birini seçin." spree_order_cycle_error: "Bu sipariş için bir sipariş dönemi seçin." @@ -2699,7 +2698,7 @@ tr: order_cycles_no_permission_to_coordinate_error: "Hiçbir işletmenizin sipariş dönemini koordine etme izni yok" order_cycles_no_permission_to_create_error: "Bu işletme tarafından koordine edilen bir sipariş dönemi oluşturma izniniz yok" order_cycle_closed: "Seçmiş olduğunuz sipariş dönemi az önce kapandı. Lütfen daha sonra tekrar deneyin :(" - back_to_orders_list: "Sipariş listesine geri dön" + back_to_orders_list: "SİPARİŞLER LİSTESİNE GERİ DÖN" no_orders_found: "SİPARİŞ BULUNAMADI" order_information: "Sipariş Bilgisi" new_payment: "Yeni Ödeme" @@ -3025,6 +3024,8 @@ tr: Yüklenen dosyada bulunmayan tüm işletmeler için tüm ürünlerin stok seviyelerini sıfır olarak ayar. order_cycles: + unsaved_changes: "kaydedilmemiş değişiklikleriniz var" + bulk_save_error: "Pardon! Yaptığınız değişiklikleri kaydedemedim." create_failure: "Sipariş dönemi oluşturulamadı" update_success: 'Sipariş döneminiz güncellendi.' update_failure: "Sipariş dönemi güncellenemedi" @@ -3238,7 +3239,6 @@ tr: more: "Daha Fazla" new_adjustment: "Yeni düzenleme" new_tax_category: "Yeni Vergi Kategorisi" - new_taxon: "Yeni kategori" new_user: "Yeni kullanıcı" no_pending_payments: "Bekleyen ödeme yok" remove: "Kaldır" @@ -3253,8 +3253,6 @@ tr: server_error: "Server hatası" start_date: "Başlangıç tarihi" successfully_removed: "Başarıyla Kaldırıldı" - taxonomy_edit: "Kategori düzenleme" - tree: "Ağaç" updating: "Güncelleniyor" your_order_is_empty_add_product: "Sepetiniz boş, lütfen bir ürün arayın ve ekleyin" add_product: "ürün ekle" @@ -3349,7 +3347,6 @@ tr: tax_rate_amount_explanation: "Vergi oranları ondalık bir tutar olmalı (yani vergi oranı % 5 ise 0.05 girin)" included_in_price: "Fiyata Dahil" show_rate_in_label: "Oranı etikette göster" - back_to_tax_rates_list: "Vergi Oranları Listesine Geri Dön" tax_settings: "Vergi Ayarları" zones: "bölgeler" new_zone: "Yeni Bölge" @@ -3362,14 +3359,11 @@ tr: iso_name: "ISO Adı" states_required: "Şehİr Gerekli" editing_country: "Ülkeyi Düzenle" - back_to_countries_list: "Ülke Listesine Geri Dön" states: "Şehİrler" abbreviation: "Kısaltma" new_state: "Yeni Şehir" payment_methods: "ÖDEME YÖNTEMLERİ" - taxonomies: "KATEGORİ" - new_taxonomy: "YENİ KATEGORİ" - back_to_taxonomies_list: "Kategori Listesine Geri Dön" + taxons: "Ürün Kategorileri" shipping_methods: "TESLİMAT YÖNTEMLERİ" shipping_method: "TESLİMAT YÖNTEMİ" shipment: "Teslimat" @@ -3523,7 +3517,6 @@ tr: continue: "Devam et" new: new_return_authorization: "Yeni İade Yetkisi" - back_to_return_authorizations_list: "İade Yetkisi Listesine Geri Dön" continue: "Devam et" edit: receive: "Al" @@ -3813,9 +3806,10 @@ tr: total: "Toplam" billing_address_name: "Ad" taxons: + index: + title: "Ürün Kategorileri" form: name: Ad - permalink: Permalink description: Açıklama general_settings: edit: @@ -4035,7 +4029,6 @@ tr: key_cleared: "Anahtar temizlendi" shipment: cannot_ready: "Gönderim hazırlanamıyor." - invalid_taxonomy_id: "Geçersiz kategori kimliği." activerecord: models: spree/payment: diff --git a/config/locales/uk.yml b/config/locales/uk.yml index a60b0cef26..58c2732fa7 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -70,7 +70,6 @@ uk: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "має бути порожнім, оскільки використовуються налаштування запасів виробника" - on_demand_but_count_on_hand_set: "має бути порожнім, якщо на вимогу" limited_stock_but_no_count_on_hand: "необхідно вказати, оскільки запаси обмежені" messages: blank: "не може бути порожнім" @@ -474,6 +473,8 @@ uk: remove: Видалити image: edit: Редагувати + product_preview: + shop_tab: Магазин adjustments: skipped_changing_canceled_order: "Ви не можете змінити скасоване замовлення." begins_at: Починається о @@ -675,7 +676,6 @@ uk: variants: infinity: "Нескінченність" to_order_tip: "Товари, виготовлені на замовлення, не мають встановленого рівня запасів, наприклад, хліб, свіжий на замовлення." - back_to_products_list: "Повернутися до списку продуктів" editing_product: "Редагування продукту" tabs: product_details: "Деталі продукту" @@ -1168,10 +1168,8 @@ uk: contact_name: Контактна Особа edit: editing: 'Налаштування:' - back_link: Повернутися до списку підприємств new: title: Нове підприємство - back_link: Повернутися до списку підприємств welcome: welcome_title: Ласкаво просимо до Open Food Network! welcome_text: 'Ви успішно створили ' @@ -1208,6 +1206,8 @@ uk: choose_products_from: "Виберіть продукти з:" re_notify_producers: Повторно повідомте виробників notify_producers_tip: Це надішле електронний лист кожному виробнику зі списком їхніх замовлень. + date_time_warning_modal_content: + cancel: 'Скасувати' incoming: incoming: "Вхідні" supplier: "Постачальник" @@ -2577,7 +2577,6 @@ uk: spree_admin_single_enterprise_hint: "Підказка: щоб люди могли вас знайти, увімкніть свою видимість під" spree_admin_eg_pickup_from_school: "напр. «Забір із першої школи»" spree_admin_eg_collect_your_order: "напр. \"Будь ласка, заберіть своє замовлення за адресою: 123 Imaginary St, Northcote \";" - spree_classification_primary_taxon_error: "Таксон %{taxon} є основним таксоном %{product} і не може бути видалений" spree_order_availability_error: "Дистриб’ютор або цикл замовлення не можуть надати продукти у вашому кошику" spree_order_populator_error: "Цей дистриб’ютор або цикл замовлення не може забезпечити всі продукти у вашому кошику. Виберіть інший." spree_order_cycle_error: "Будь ласка, виберіть цикл замовлення для цього замовлення." @@ -2871,7 +2870,7 @@ uk: order_cycles_no_permission_to_coordinate_error: "Жодне з ваших підприємств не має дозволу на координацію циклу замовлення" order_cycles_no_permission_to_create_error: "У вас немає дозволу на створення циклу замовлення, який координує це підприємство" order_cycle_closed: "Вибраний вами цикл замовлення щойно закрито. Будь ласка спробуйте ще раз!" - back_to_orders_list: "Повернутися до списку замовлень" + back_to_orders_list: "Назад до списку замовлень" no_orders_found: "Замовлень не знайдено" order_information: "Інформація про замовлення" new_payment: "Нова оплата" @@ -3204,6 +3203,8 @@ uk: confirmation: | Це призведе до нульового рівня запасів для всіх продуктів для цього підприємства, яких немає в завантаженому файлі. order_cycles: + unsaved_changes: "У вас є незбережені зміни" + bulk_save_error: "О ні! Мені не вдалося зберегти ваші зміни." create_failure: "Не вдалося створити цикл замовлення" update_success: 'Ваш цикл замовлення оновлено.' update_failure: "Не вдалося оновити цикл замовлення" @@ -3464,7 +3465,6 @@ uk: more: "Більше" new_adjustment: "Нове коригування" new_tax_category: "Нова податкова категорія" - new_taxon: "Новий таксон" new_user: "Новий користувач" no_pending_payments: "Немає незавершених платежів" remove: "Видалити" @@ -3481,8 +3481,6 @@ uk: UPS Ground: "UPS наземна" start_date: "Дата початку" successfully_removed: "Успішно видалено" - taxonomy_edit: "Редагування таксономії" - tree: "Дерево" updating: "Оновлення" your_order_is_empty_add_product: "Ваше замовлення порожнє, знайдіть і додайте продукт вище" add_product: "Додати продукт" @@ -3577,7 +3575,6 @@ uk: tax_rate_amount_explanation: "Ставки податку – це десяткова сума, яка допомагає під час розрахунків (тобто, якщо ставка податку становить 5%, введіть 0,05)" included_in_price: "Входить у вартість" show_rate_in_label: "Показати ставку в етикетці" - back_to_tax_rates_list: "Повернутися до списку податкових ставок" tax_settings: "Налаштування податку" zones: "Зони" new_zone: "Нова зона" @@ -3590,14 +3587,11 @@ uk: iso_name: "Назва ISO" states_required: "Регіони обовязкові" editing_country: "Країна редагування" - back_to_countries_list: "Назад до списку країн" states: "Області" abbreviation: "Абревіатура" new_state: "Новий регіон" payment_methods: "методи оплати" - taxonomies: "Таксономії" - new_taxonomy: "Нова таксономія" - back_to_taxonomies_list: "Назад до списку таксономій" + taxons: "Категорії продуктів" shipping_methods: "Способи доставки" shipping_method: "Спосіб доставки" shipment: "Відвантаження" @@ -3751,7 +3745,6 @@ uk: continue: "Продовжити" new: new_return_authorization: "Авторизація на нове повернення" - back_to_return_authorizations_list: "Назад до списку Авторизації повернень" continue: "Продовжити" edit: receive: "отримати" @@ -4051,9 +4044,10 @@ uk: total: "Всього" billing_address_name: "Ім'я" taxons: + index: + title: "Категорії продуктів" form: name: Ім'я - permalink: Постійне посилання description: Опис general_settings: edit: @@ -4273,7 +4267,6 @@ uk: key_cleared: "Ключ видалено" shipment: cannot_ready: "Неможливо підготувати відправлення." - invalid_taxonomy_id: "Недійсний ID таксономії." activerecord: models: spree/payment: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index fe91db5633..8036788d46 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -79,6 +79,8 @@ Openfoodnetwork::Application.routes.draw do delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy' delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant' post 'clone/:id', to: 'products_v3#clone', as: 'clone_product' + + resources :product_preview, only: [:show] end resources :variant_overrides do @@ -135,6 +137,7 @@ Openfoodnetwork::Application.routes.draw do end get '/reports', to: 'reports#index', as: :reports - match '/reports/:report_type(/:report_subtype)', to: 'reports#show', via: [:get, :post], as: :report + match '/reports/:report_type(/:report_subtype)', to: 'reports#show', via: :get, as: :report + match '/reports/:report_type(/:report_subtype)', to: 'reports#create', via: :post end end diff --git a/config/routes/api.rb b/config/routes/api.rb index 74f2ad3652..30bd7cd1b1 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -79,18 +79,7 @@ Openfoodnetwork::Application.routes.draw do resources :states, :only => [:index, :show] - resources :taxons, :only => [:index] - - resources :taxonomies do - member do - get :jstree - end - resources :taxons do - member do - get :jstree - end - end - end + resources :taxons, except: %i[show edit] get '/reports/:report_type(/:report_subtype)', to: 'reports#show', constraints: lambda { |_| OpenFoodNetwork::FeatureToggle.enabled?(:api_reports) } diff --git a/config/routes/spree.rb b/config/routes/spree.rb index f59e4e7dca..e9d21a66cb 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -142,15 +142,7 @@ Spree::Core::Engine.routes.draw do end resources :states - resources :taxonomies do - collection do - post :update_positions - end - member do - get :get_children - end - resources :taxons - end + resources :taxons, except: :show resources :tax_rates resource :tax_settings diff --git a/db/migrate/20240510033206_sanitize_enterprise_long_description.rb b/db/migrate/20240510033206_sanitize_enterprise_long_description.rb new file mode 100644 index 0000000000..5f69464ed3 --- /dev/null +++ b/db/migrate/20240510033206_sanitize_enterprise_long_description.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class SanitizeEnterpriseLongDescription < ActiveRecord::Migration[7.0] + class Enterprise < ApplicationRecord + end + + # This is a copy from our application code at the time of writing. + # We prefer to keep migrations isolated and not affected by changing + # application code in the future. + # If we need to change the sanitizer in the future we may need a new + # migration (not change the old one) to sanitise the data properly. + class HtmlSanitizer + ALLOWED_TAGS = %w[h1 h2 h3 h4 div p br b i u a strong em del pre blockquote ul ol li hr + figure].freeze + ALLOWED_ATTRIBUTES = %w[href target].freeze + ALLOWED_TRIX_DATA_ATTRIBUTES = %w[data-trix-attachment].freeze + + def self.sanitize(html) + @sanitizer ||= Rails::HTML5::SafeListSanitizer.new + @sanitizer.sanitize( + html, tags: ALLOWED_TAGS, attributes: (ALLOWED_ATTRIBUTES + ALLOWED_TRIX_DATA_ATTRIBUTES) + ) + end + end + + def up + Enterprise.where.not(long_description: [nil, ""]).find_each do |enterprise| + long_description = HtmlSanitizer.sanitize(enterprise.long_description) + enterprise.update!(long_description:) + end + end + + private + + def sanitize(html) + HtmlSanitizer.sanitize(html) + end +end diff --git a/db/migrate/20240806135838_drop_spree_taxonomies_table.rb b/db/migrate/20240806135838_drop_spree_taxonomies_table.rb new file mode 100644 index 0000000000..99a24f1ad8 --- /dev/null +++ b/db/migrate/20240806135838_drop_spree_taxonomies_table.rb @@ -0,0 +1,19 @@ +class DropSpreeTaxonomiesTable < ActiveRecord::Migration[7.0] + def change + # Remove columns + remove_column :spree_taxons, :lft, :integer + remove_column :spree_taxons, :rgt, :integer + + # Remove references + remove_reference :spree_taxons, :parent, index: true, foreign_key: { to_table: :spree_taxons } + remove_reference :spree_taxons, :taxonomy, index: true, foreign_key: { to_table: :spree_taxonomies } + + # Drop table + drop_table :spree_taxonomies, id: :serial, force: :cascade do |t| + t.string "name", limit: 255, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "position", default: 0 + end + end +end diff --git a/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb b/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb new file mode 100644 index 0000000000..cc412c0b58 --- /dev/null +++ b/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb @@ -0,0 +1,9 @@ +class ActivateAdminStyleV3For75PcUsers < ActiveRecord::Migration[7.0] + def up + Flipper.enable_percentage_of_actors(:admin_style_v3, 75) + end + + def down + Flipper.enable_percentage_of_actors(:admin_style_v3, 50) + end +end diff --git a/db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb b/db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb new file mode 100644 index 0000000000..db2fd6007f --- /dev/null +++ b/db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb @@ -0,0 +1,5 @@ +class FullyEnableAdminStyleV3Flag < ActiveRecord::Migration[7.0] + def up + Flipper.enable(:admin_style_v3) + end +end diff --git a/db/schema.rb b/db/schema.rb index 04013a7caf..c398b00eb3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_08_01_034710) do +ActiveRecord::Schema[7.0].define(version: 2024_08_28_203544) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -878,31 +878,18 @@ ActiveRecord::Schema[7.0].define(version: 2024_08_01_034710) do t.datetime "deleted_at", precision: nil end - create_table "spree_taxonomies", id: :serial, force: :cascade do |t| - t.string "name", limit: 255, null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.integer "position", default: 0 - end - create_table "spree_taxons", id: :serial, force: :cascade do |t| - t.integer "parent_id" t.integer "position", default: 0 t.string "name", limit: 255, null: false t.string "permalink", limit: 255 - t.integer "taxonomy_id" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.integer "lft" - t.integer "rgt" t.text "description" t.string "meta_title", limit: 255 t.string "meta_description", limit: 255 t.string "meta_keywords", limit: 255 t.string "dfc_id" - t.index ["parent_id"], name: "index_taxons_on_parent_id" t.index ["permalink"], name: "index_taxons_on_permalink" - t.index ["taxonomy_id"], name: "index_taxons_on_taxonomy_id" end create_table "spree_tokenized_permissions", id: :serial, force: :cascade do |t| @@ -1220,8 +1207,6 @@ ActiveRecord::Schema[7.0].define(version: 2024_08_01_034710) do add_foreign_key "spree_stock_movements", "spree_stock_items", column: "stock_item_id" add_foreign_key "spree_tax_rates", "spree_tax_categories", column: "tax_category_id", name: "spree_tax_rates_tax_category_id_fk" add_foreign_key "spree_tax_rates", "spree_zones", column: "zone_id", name: "spree_tax_rates_zone_id_fk" - add_foreign_key "spree_taxons", "spree_taxonomies", column: "taxonomy_id", name: "spree_taxons_taxonomy_id_fk" - add_foreign_key "spree_taxons", "spree_taxons", column: "parent_id", name: "spree_taxons_parent_id_fk" add_foreign_key "spree_users", "spree_addresses", column: "bill_address_id", name: "spree_users_bill_address_id_fk" add_foreign_key "spree_users", "spree_addresses", column: "ship_address_id", name: "spree_users_ship_address_id_fk" add_foreign_key "spree_variants", "enterprises", column: "supplier_id" diff --git a/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb new file mode 100644 index 0000000000..bff7447301 --- /dev/null +++ b/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module DfcProvider + # Aggregates anonymised sales data for a research project. + class AffiliateSalesDataController < DfcProvider::ApplicationController + rescue_from Date::Error, with: -> { head :bad_request } + + def show + person = AffiliateSalesDataBuilder.person(current_user, filter_params) + + render json: DfcIo.export(person) + end + + private + + def filter_params + { + start_date: parse_date(params[:startDate]), + end_date: parse_date(params[:endDate]), + } + end + + def parse_date(string) + return if string.blank? + + Date.parse(string) + end + end +end diff --git a/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb b/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb new file mode 100644 index 0000000000..26d11b3a98 --- /dev/null +++ b/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AffiliateSalesDataBuilder < DfcBuilder + class << self + def person(user, filters = {}) + data = AffiliateSalesQuery.data(user.affiliate_enterprises, **filters) + suppliers = data.map do |row| + AffiliateSalesDataRowBuilder.new(row).build_supplier + end + + DataFoodConsortium::Connector::Person.new( + urls.affiliate_sales_data_url, + affiliatedOrganizations: suppliers, + ) + end + end +end diff --git a/engines/dfc_provider/app/services/affiliate_sales_data_row_builder.rb b/engines/dfc_provider/app/services/affiliate_sales_data_row_builder.rb new file mode 100644 index 0000000000..073097a04f --- /dev/null +++ b/engines/dfc_provider/app/services/affiliate_sales_data_row_builder.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# Represents a single row of the aggregated sales data. +class AffiliateSalesDataRowBuilder < DfcBuilder + attr_reader :item + + def initialize(row) + super() + @item = AffiliateSalesQuery.label_row(row) + end + + def build_supplier + DataFoodConsortium::Connector::Enterprise.new( + nil, + localizations: [build_address(item[:supplier_postcode])], + suppliedProducts: [build_product], + ) + end + + def build_distributor + DataFoodConsortium::Connector::Enterprise.new( + nil, + localizations: [build_address(item[:distributor_postcode])], + ) + end + + def build_product + DataFoodConsortium::Connector::SuppliedProduct.new( + nil, + name: item[:product_name], + quantity: build_product_quantity, + ).tap do |product| + product.registerSemanticProperty("dfc-b:concernedBy") { + build_order_line + } + end + end + + def build_order_line + DataFoodConsortium::Connector::OrderLine.new( + nil, + quantity: build_line_quantity, + price: build_price, + order: build_order, + ) + end + + def build_order + DataFoodConsortium::Connector::Order.new( + nil, + saleSession: build_sale_session, + ) + end + + def build_sale_session + DataFoodConsortium::Connector::SaleSession.new( + nil, + ).tap do |session| + session.registerSemanticProperty("dfc-b:objectOf") { + build_coordination + } + end + end + + def build_coordination + DfcProvider::Coordination.new( + nil, + coordinator: build_distributor, + ) + end + + def build_product_quantity + DataFoodConsortium::Connector::QuantitativeValue.new( + unit: QuantitativeValueBuilder.unit(item[:unit_type]), + value: item[:units]&.to_f, + ) + end + + def build_line_quantity + DataFoodConsortium::Connector::QuantitativeValue.new( + unit: DfcLoader.connector.MEASURES.PIECE, + value: item[:quantity_sold]&.to_f, + ) + end + + def build_price + DataFoodConsortium::Connector::QuantitativeValue.new( + value: item[:price]&.to_f, + ) + end + + def build_address(postcode) + DataFoodConsortium::Connector::Address.new( + nil, + postalCode: postcode, + ) + end +end diff --git a/engines/dfc_provider/app/services/affiliate_sales_query.rb b/engines/dfc_provider/app/services/affiliate_sales_query.rb new file mode 100644 index 0000000000..3193ed5623 --- /dev/null +++ b/engines/dfc_provider/app/services/affiliate_sales_query.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +class AffiliateSalesQuery + class << self + def data(enterprises, start_date: nil, end_date: nil) + end_date = end_date&.end_of_day # Include the whole end date. + + Spree::LineItem + .joins(tables) + .where( + spree_orders: { + state: "complete", distributor_id: enterprises, + completed_at: [start_date..end_date], + }, + ) + .group(key_fields) + .pluck(fields) + end + + # Create a hash with labels for an array of data points: + # + # { product_name: "Apple", ... } + def label_row(row) + labels.zip(row).to_h + end + + private + + # We want to collect a lot of data from only a few columns. + # It's more efficient with `pluck`. But therefore we need well named + # tables and columns, especially because we are going to join some tables + # twice for different columns. For example the distributer postcode and + # the supplier postcode. That's why we need SQL here instead of nice Rails + # associations. + def tables + <<~SQL.squish + JOIN spree_variants ON spree_variants.id = spree_line_items.variant_id + JOIN spree_products ON spree_products.id = spree_variants.product_id + JOIN enterprises AS suppliers ON suppliers.id = spree_variants.supplier_id + JOIN spree_addresses AS supplier_addresses ON supplier_addresses.id = suppliers.address_id + JOIN spree_orders ON spree_orders.id = spree_line_items.order_id + JOIN enterprises AS distributors ON distributors.id = spree_orders.distributor_id + JOIN spree_addresses AS distributor_addresses ON distributor_addresses.id = distributors.address_id + SQL + end + + def fields + <<~SQL.squish + spree_products.name AS product_name, + spree_variants.display_name AS unit_name, + spree_products.variant_unit AS unit_type, + spree_variants.unit_value AS units, + spree_variants.unit_presentation, + spree_line_items.price, + distributor_addresses.zipcode AS distributor_postcode, + supplier_addresses.zipcode AS supplier_postcode, + + SUM(spree_line_items.quantity) AS quantity_sold + SQL + end + + def key_fields + <<~SQL.squish + product_name, + unit_name, + unit_type, + units, + spree_variants.unit_presentation, + spree_line_items.price, + distributor_postcode, + supplier_postcode + SQL + end + + # A list of column names as symbols to be used as hash keys. + def labels + %i[ + product_name + unit_name + unit_type + units + unit_presentation + price + distributor_postcode + supplier_postcode + quantity_sold + ] + end + end +end diff --git a/engines/dfc_provider/app/services/fdc_request.rb b/engines/dfc_provider/app/services/fdc_request.rb deleted file mode 100644 index 5252cff85a..0000000000 --- a/engines/dfc_provider/app/services/fdc_request.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require "private_address_check" -require "private_address_check/tcpsocket_ext" - -# Request a JSON document from the FDC API with authentication. -# -# Currently, the API doesn't quite comply with the DFC standard and we need -# to authenticate a little bit differently. -# -# And then we get slightly different data as well. -class FdcRequest < DfcRequest - # Override main method to POST authorization data. - def call(url, data = {}) - response = request(url, auth_data.merge(data).to_json) - - if response.status >= 400 && token_stale? - refresh_access_token! - response = request(url, auth_data.merge(data).to_json) - end - - response.body - end - - private - - def auth_data - { - userId: @user.oidc_account.uid, - accessToken: @user.oidc_account.token, - } - end -end diff --git a/engines/dfc_provider/app/services/quantitative_value_builder.rb b/engines/dfc_provider/app/services/quantitative_value_builder.rb index 6baac1eecf..ecbc0f4c59 100644 --- a/engines/dfc_provider/app/services/quantitative_value_builder.rb +++ b/engines/dfc_provider/app/services/quantitative_value_builder.rb @@ -11,13 +11,13 @@ class QuantitativeValueBuilder < DfcBuilder def self.quantity(variant) DataFoodConsortium::Connector::QuantitativeValue.new( - unit: unit(variant), + unit: unit(variant.product.variant_unit), value: variant.unit_value, ) end - def self.unit(variant) - case variant.product.variant_unit + def self.unit(unit_name) + case unit_name when "volume" DfcLoader.connector.MEASURES.LITRE when "weight" diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb index 5bde8f23ba..8fc0850729 100644 --- a/engines/dfc_provider/config/routes.rb +++ b/engines/dfc_provider/config/routes.rb @@ -12,4 +12,6 @@ DfcProvider::Engine.routes.draw do resources :affiliated_by, only: [:create, :destroy], module: 'enterprise_groups' end resources :persons, only: [:show] + + resource :affiliate_sales_data, only: [:show] end diff --git a/engines/dfc_provider/lib/dfc_provider.rb b/engines/dfc_provider/lib/dfc_provider.rb index 526cceb9c3..51f3ec2ef4 100644 --- a/engines/dfc_provider/lib/dfc_provider.rb +++ b/engines/dfc_provider/lib/dfc_provider.rb @@ -9,6 +9,7 @@ require "dfc_provider/engine" # Custom data types require "dfc_provider/supplied_product" require "dfc_provider/address" +require "dfc_provider/coordination" module DfcProvider DataFoodConsortium::Connector::Importer.register_type(SuppliedProduct) diff --git a/engines/dfc_provider/lib/dfc_provider/coordination.rb b/engines/dfc_provider/lib/dfc_provider/coordination.rb new file mode 100644 index 0000000000..49b16aa97a --- /dev/null +++ b/engines/dfc_provider/lib/dfc_provider/coordination.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +if defined? DataFoodConsortium::Connector::Coordination + ActiveSupport::Deprecation.warn <<~TEXT + DataFoodConsortium::Connector::Coordination is now available. + Please replace your own implementation with the official class. + TEXT +end + +module DfcProvider + class Coordination + include VirtualAssembly::Semantizer::SemanticObject + + SEMANTIC_TYPE = "dfc-b:Coordination" + + attr_accessor :coordinator + + def initialize(semantic_id, coordinator: nil) + super(semantic_id) + + self.semanticType = SEMANTIC_TYPE + + @coordinator = coordinator + registerSemanticProperty("dfc-b:coordinatedBy", &method("coordinator")) + .valueSetter = method("coordinator=") + end + end +end diff --git a/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb b/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb new file mode 100644 index 0000000000..3199fc4a22 --- /dev/null +++ b/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative "../swagger_helper" + +RSpec.describe "AffiliateSalesData", swagger_doc: "dfc.yaml", rswag_autodoc: true do + let(:user) { create(:oidc_user) } + + before { login_as user } + + path "/api/dfc/affiliate_sales_data" do + parameter name: :startDate, in: :query, type: :string + parameter name: :endDate, in: :query, type: :string + + get "Show sales data of person's affiliate enterprises" do + produces "application/json" + + response "200", "successful", feature: :affiliate_sales_data do + let(:startDate) { Date.yesterday } + let(:endDate) { Time.zone.today } + + before do + order = create(:order_with_totals_and_distribution, :completed) + ConnectedApps::AffiliateSalesData.new( + enterprise: order.distributor + ).connect({}) + end + + context "with date filters" do + let(:startDate) { Date.tomorrow } + let(:endDate) { Date.tomorrow } + + run_test! do + expect(json_response).to include( + "@id" => "http://test.host/api/dfc/affiliate_sales_data", + "@type" => "dfc-b:Person", + ) + + expect(json_response["dfc-b:affiliates"]).to eq nil + end + end + + context "not filtered" do + run_test! do + expect(json_response).to include( + "@id" => "http://test.host/api/dfc/affiliate_sales_data", + "@type" => "dfc-b:Person", + ) + expect(json_response["dfc-b:affiliates"]).to be_present + end + end + end + + response "400", "bad request" do + let(:startDate) { "yesterday" } + let(:endDate) { "tomorrow" } + + run_test! + end + end + end +end diff --git a/engines/dfc_provider/spec/services/affiliate_sales_data_builder_spec.rb b/engines/dfc_provider/spec/services/affiliate_sales_data_builder_spec.rb new file mode 100644 index 0000000000..9673b70045 --- /dev/null +++ b/engines/dfc_provider/spec/services/affiliate_sales_data_builder_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +RSpec.describe AffiliateSalesDataBuilder do + let(:user) { build(:user) } + + describe ".person" do + let(:person) { described_class.person(user) } + + it "returns data as Person" do + expect(person).to be_a DataFoodConsortium::Connector::Person + expect(person.semanticId).to eq "http://test.host/api/dfc/affiliate_sales_data" + end + + describe "with sales data" do + before do + supplier = create( + :supplier_enterprise, + owner: user, + users: [user], + address: create(:address, zipcode: "5555"), + ) + product = create( + :product, + name: "Pomme", + supplier_id: supplier.id, + variant_unit: "item", + ) + variant = product.variants.first + distributor = create( + :distributor_enterprise, + owner: user, + address: create(:address, zipcode: "6666"), + ) + ConnectedApps::AffiliateSalesData.new(enterprise: distributor).connect({}) + line_item = build( + :line_item, + variant:, + quantity: 2, + price: 3, + ) + order_cycle = create( + :order_cycle, + suppliers: [supplier], + distributors: [distributor], + ) + order_cycle.exchanges.incoming.first.variants << variant + order_cycle.exchanges.outgoing.first.variants << variant + create( + :order, + state: "complete", + order_cycle:, + distributor:, + line_items: [line_item], + ) + end + + it "returns required sales data", feature: :affiliate_sales_data do + supplier = person.affiliatedOrganizations[0] + product = supplier.suppliedProducts[0] + line = product.semanticPropertyValue("dfc-b:concernedBy") + session = line.order.saleSession + coordination = session.semanticPropertyValue("dfc-b:objectOf") + distributor = coordination.coordinator + + expect(supplier.localizations[0].postalCode).to eq "5555" + expect(distributor.localizations[0].postalCode).to eq "6666" + + expect(product.name).to eq "Pomme" + expect(product.quantity.unit).to eq DfcLoader.connector.MEASURES.PIECE + expect(product.quantity.value).to eq 1 + + expect(line.quantity.unit).to eq DfcLoader.connector.MEASURES.PIECE + expect(line.quantity.value).to eq 2 + expect(line.price.value).to eq 3 + end + end + end +end diff --git a/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb b/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb new file mode 100644 index 0000000000..f1ab830977 --- /dev/null +++ b/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +RSpec.describe AffiliateSalesQuery do + subject(:query) { described_class } + + describe ".data" do + let(:order) { create(:order_with_totals_and_distribution, :completed) } + let(:today) { Time.zone.today } + let(:yesterday) { Time.zone.yesterday } + let(:tomorrow) { Time.zone.tomorrow } + + it "returns data" do + # Test data creation takes time. + # So I'm executing more tests in one `it` block here. + # And make it simpler to call the subject many times: + count_rows = lambda do |**args| + query.data(order.distributor, **args).count + end + + # Without any filters: + expect(count_rows.call).to eq 1 + + # From today: + expect(count_rows.call(start_date: today)).to eq 1 + + # Until today: + expect(count_rows.call(end_date: today)).to eq 1 + + # Just today: + expect(count_rows.call(start_date: today, end_date: today)).to eq 1 + + # Yesterday: + expect(count_rows.call(start_date: yesterday, end_date: yesterday)).to eq 0 + + # Until yesterday: + expect(count_rows.call(end_date: yesterday)).to eq 0 + + # From tomorrow: + expect(count_rows.call(start_date: tomorrow)).to eq 0 + end + end + + describe ".label_row" do + it "converts an array to a hash" do + row = [ + "Apples", + "item", "item", nil, nil, + 15.50, + "3210", "3211", + 3, + ] + expect(query.label_row(row)).to eq( + { + product_name: "Apples", + unit_name: "item", + unit_type: "item", + units: nil, + unit_presentation: nil, + price: 15.50, + distributor_postcode: "3210", + supplier_postcode: "3211", + quantity_sold: 3, + } + ) + end + end +end diff --git a/engines/dfc_provider/spec/services/dfc_request_spec.rb b/engines/dfc_provider/spec/services/dfc_request_spec.rb index ea17ae02af..0de024b670 100644 --- a/engines/dfc_provider/spec/services/dfc_request_spec.rb +++ b/engines/dfc_provider/spec/services/dfc_request_spec.rb @@ -59,4 +59,28 @@ RSpec.describe DfcRequest do # would raise errors because we didn't setup Webmock or VCR. # The absence of errors makes this test pass. end + + it "refreshes the access token and retrieves the FDC catalog", vcr: true do + # A refresh is only attempted if the token is stale. + account.uid = "testdfc@protonmail.com" + account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") + account.updated_at = 1.day.ago + + response = nil + expect { + response = api.call( + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" + ) + }.to change { + account.token + }.and change { + account.refresh_token + } + + json = JSON.parse(response) + + graph = DfcIo.import(json) + products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } + expect(products).to be_present + end end diff --git a/engines/dfc_provider/spec/services/fdc_request_spec.rb b/engines/dfc_provider/spec/services/fdc_request_spec.rb deleted file mode 100644 index c1f59d40f8..0000000000 --- a/engines/dfc_provider/spec/services/fdc_request_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative "../spec_helper" - -RSpec.describe FdcRequest do - subject(:api) { FdcRequest.new(user) } - - let(:user) { build(:oidc_user) } - let(:account) { user.oidc_account } - let(:url) { - "https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com" - } - - it "refreshes the access token and retrieves a catalog", vcr: true do - # A refresh is only attempted if the token is stale. - account.uid = "testdfc@protonmail.com" - account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") - account.updated_at = 1.day.ago - - response = nil - expect { - response = api.call(url) - }.to change { - account.token - }.and change { - account.refresh_token - } - - json = JSON.parse(response) - expect(json["message"]).to eq "Products retrieved successfully" - - graph = DfcIo.import(json["products"]) - products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } - expect(products).to be_present - end -end diff --git a/engines/order_management/app/services/order_management/subscriptions/variants_list.rb b/engines/order_management/app/services/order_management/subscriptions/variants_list.rb index fb06ed8829..f3a817219a 100644 --- a/engines/order_management/app/services/order_management/subscriptions/variants_list.rb +++ b/engines/order_management/app/services/order_management/subscriptions/variants_list.rb @@ -32,7 +32,9 @@ module OrderManagement .merge(Enterprise.is_primary_producer) .pluck(:parent_id) - other_permitted_producer_ids | [distributor.id] + # Append to the potentially gigantic array instead of using union, which creates a new array + # The db IN statement won't care if there's a duplicate. + other_permitted_producer_ids << distributor.id end def self.outgoing_exchange_variant_ids(distributor) diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb index f851efc794..9da9ef36e9 100644 --- a/lib/open_food_network/column_preference_defaults.rb +++ b/lib/open_food_network/column_preference_defaults.rb @@ -77,7 +77,9 @@ module OpenFoodNetwork } end - def products_v3_index_columns + def products_v3_index_columns(user) + producer_visibility = display_producer_column?(user) + I18n.with_options scope: 'admin.products_page.columns' do { image: { name: t(:image), visible: true }, @@ -87,7 +89,7 @@ module OpenFoodNetwork unit_scale: { name: t(:unit_scale), visible: true }, price: { name: t(:price), visible: true }, on_hand: { name: t(:on_hand), visible: true }, - producer: { name: t(:producer), visible: true }, + producer: { name: t(:producer), visible: producer_visibility }, category: { name: t(:category), visible: true }, tax_category: { name: t(:tax_category), visible: true }, inherits_properties: { name: t(:inherits_properties), visible: true }, @@ -134,5 +136,12 @@ module OpenFoodNetwork shipping_method: { name: I18n.t("admin.shipping_method"), visible: false } } end + + def display_producer_column?(user) + producers = OpenFoodNetwork::Permissions.new(user) + .managed_product_enterprises.is_primary_producer + + producers.many? + end end end diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 31801c63f9..046d2f4941 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -2,40 +2,55 @@ module OpenFoodNetwork class EnterpriseInjectionData + # By default, data is fetched for *every* enterprise in the DB, but we specify some ids of + # enterprises that we are interested in, there is a lot less data to fetch + def initialize(enterprise_ids = nil) + @enterprise_ids = enterprise_ids + end + def active_distributor_ids @active_distributor_ids ||= - Enterprise.distributors_with_active_order_cycles.ready_for_checkout.pluck(:id) + begin + enterprises = Enterprise.distributors_with_active_order_cycles.ready_for_checkout + enterprises = enterprises.where(id: @enterprise_ids) if @enterprise_ids.present? + enterprises.pluck(:id) + end end def earliest_closing_times - @earliest_closing_times ||= OrderCycle.earliest_closing_times + @earliest_closing_times ||= OrderCycle.earliest_closing_times(@enterprise_ids) end def shipping_method_services - @shipping_method_services ||= CacheService.cached_data_by_class("shipping_method_services", - Spree::ShippingMethod) do + @shipping_method_services ||= CacheService.cached_data_by_class( + "shipping_method_services_#{@enterprise_ids.hash}", + Spree::ShippingMethod + ) do # This result relies on a simple join with DistributorShippingMethod. # Updated DistributorShippingMethod records touch their associated Spree::ShippingMethod. - Spree::ShippingMethod.services + Spree::ShippingMethod.services(@enterprise_ids) end end def supplied_taxons - @supplied_taxons ||= CacheService.cached_data_by_class("supplied_taxons", Spree::Taxon) do + @supplied_taxons ||= CacheService.cached_data_by_class( + "supplied_taxons_#{@enterprise_ids.hash}", + Spree::Taxon + ) do # This result relies on a join with associated supplied products, through the # class Classification which maps the relationship. Classification records touch # their associated Spree::Taxon when updated. A Spree::Product's primary_taxon # is also touched when changed. - Spree::Taxon.supplied_taxons + Spree::Taxon.supplied_taxons(@enterprise_ids) end end def all_distributed_taxons - @all_distributed_taxons ||= Spree::Taxon.distributed_taxons(:all) + @all_distributed_taxons ||= Spree::Taxon.distributed_taxons(:all, @enterprise_ids) end def current_distributed_taxons - @current_distributed_taxons ||= Spree::Taxon.distributed_taxons(:current) + @current_distributed_taxons ||= Spree::Taxon.distributed_taxons(:current, @enterprise_ids) end end end diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index aedef176fb..a3b1d8b253 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -44,6 +44,10 @@ module OpenFoodNetwork Enterprise data can be shared with another app. The first example is the Australian Discover Regenerative Portal. DESC + "affiliate_sales_data" => <<~DESC, + Activated for a user. + The user (INRAE researcher) has access to anonymised sales. + DESC }.freeze # Features you would like to be enabled to start with. diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index d7a29cbb9c..90bfef39d4 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -43,11 +43,7 @@ module OpenFoodNetwork # - updates variant_override.count_on_hand # - does not create stock_movement # - does not update stock_item.count_on_hand - # If it is a variant override with on_demand: - # - don't change stock or call super (super would change the variant's stock) def move(quantity, originator = nil) - return if @variant_override&.on_demand - if @variant_override&.stock_overridden? @variant_override.move_stock! quantity else diff --git a/lib/open_food_network/scope_variants_for_search.rb b/lib/open_food_network/scope_variants_for_search.rb index 14a1bc25df..e154096524 100644 --- a/lib/open_food_network/scope_variants_for_search.rb +++ b/lib/open_food_network/scope_variants_for_search.rb @@ -42,7 +42,7 @@ module OpenFoodNetwork end def distributor - Enterprise.find params[:distributor_id] + @distributor ||= Enterprise.find params[:distributor_id] end def scope_to_schedule diff --git a/lib/reporting/reports/bulk_coop/supplier_report.rb b/lib/reporting/reports/bulk_coop/supplier_report.rb index 6e797cd389..33f87263c6 100644 --- a/lib/reporting/reports/bulk_coop/supplier_report.rb +++ b/lib/reporting/reports/bulk_coop/supplier_report.rb @@ -10,7 +10,7 @@ module Reporting def columns { - supplier: :variant_product_supplier_name, + supplier: :variant_supplier_name, product: :variant_product_name, bulk_unit_size: :variant_product_group_buy_unit_size_f, variant: :full_name, @@ -44,8 +44,8 @@ module Reporting private - def variant_product_supplier_name(line_items) - line_items.first.variant.product.supplier.name + def variant_supplier_name(line_items) + line_items.first.variant.supplier.name end end end diff --git a/lib/reporting/reports/orders_and_distributors/base.rb b/lib/reporting/reports/orders_and_distributors/base.rb index 4b695d33d1..47a389de41 100644 --- a/lib/reporting/reports/orders_and_distributors/base.rb +++ b/lib/reporting/reports/orders_and_distributors/base.rb @@ -15,7 +15,7 @@ module Reporting customer_city: proc { |line_item| line_item.order.bill_address.city }, sku: proc { |line_item| line_item.product.sku }, item_name: proc { |line_item| line_item.product.name }, - variant: proc { |line_item| line_item.options_text }, + variant: proc { |line_item| line_item.unit_to_display }, quantity: proc { |line_item| line_item.quantity }, max_quantity: proc { |line_item| line_item.max_quantity }, cost: proc { |line_item| line_item.price * line_item.quantity }, diff --git a/lib/spree/core.rb b/lib/spree/core.rb index 4c39461913..e50c687f2f 100644 --- a/lib/spree/core.rb +++ b/lib/spree/core.rb @@ -2,7 +2,6 @@ require 'active_merchant' require 'acts_as_list' -require 'awesome_nested_set' require 'cancan' require 'pagy' require 'mail' @@ -35,7 +34,3 @@ require 'spree/core/permalinks' require 'spree/core/token_resource' require 'spree/core/product_duplicator' require 'spree/core/gateway_error' - -ActiveRecord::Base.class_eval do - include CollectiveIdea::Acts::NestedSet -end diff --git a/lib/tasks/sample_data/taxon_factory.rb b/lib/tasks/sample_data/taxon_factory.rb index d9545c24d1..46e26c396e 100644 --- a/lib/tasks/sample_data/taxon_factory.rb +++ b/lib/tasks/sample_data/taxon_factory.rb @@ -8,23 +8,20 @@ module SampleData def create_samples log "Creating taxonomies:" - taxonomy = Spree::Taxonomy.find_or_create_by!(name: 'Products') taxons = ['Vegetables', 'Fruit', 'Oils', 'Preserves and Sauces', 'Dairy', 'Fungi'] taxons.each do |taxon_name| - create_taxon(taxonomy, taxon_name) + create_taxon(taxon_name) end end private - def create_taxon(taxonomy, taxon_name) + def create_taxon(taxon_name) return if Spree::Taxon.where(name: taxon_name).exists? log "- #{taxon_name}" Spree::Taxon.create!( - name: taxon_name, - parent_id: taxonomy.root.id, - taxonomy_id: taxonomy.id + name: taxon_name ) end end diff --git a/lib/tasks/simplecov.rake b/lib/tasks/simplecov.rake new file mode 100644 index 0000000000..b0c896ecb7 --- /dev/null +++ b/lib/tasks/simplecov.rake @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +namespace :simplecov do + desc "Collates all result sets produced during parallel test runs" + task :collate_results, # rubocop:disable Rails/RakeEnvironment doesn't need the full env + [:path_to_results, :coverage_dir] do |_t, args| + require "simplecov" + + path_to_results = args[:path_to_results].presence || "tmp/simplecov" + output_path = args[:coverage_dir].presence || "coverage" + + SimpleCov.collate Dir[File.join(path_to_results, "**", ".resultset.json")], "rails" do + formatter(SimpleCov::Formatter::HTMLFormatter) + coverage_dir(output_path) + end + end +end diff --git a/package.json b/package.json index 1c20cce038..6673c18670 100644 --- a/package.json +++ b/package.json @@ -10,23 +10,22 @@ "pretty-quick": "pretty-quick" }, "dependencies": { - "@floating-ui/dom": "^1.6.10", + "@floating-ui/dom": "^1.6.11", "@hotwired/stimulus": "^3.2", - "@hotwired/turbo": "^8.0.5", + "@hotwired/turbo": "^8.0.10", "@rails/webpacker": "5.4.4", "@stimulus-components/rails-nested-form": "^5.0.0", "cable_ready": "5.0.5", - "debounced": "^0.0.5", "flatpickr": "^4.6.9", "foundation-sites": "^5.5.3", "hotkeys-js": "^3.13.7", "jquery-ui": "1.14.0", - "js-big-decimal": "^2.0.7", + "js-big-decimal": "^2.1.0", "leaflet": "1.9.4", "leaflet-geosearch": "4.0.0", "leaflet-providers": "2.0.0", "moment": "^2.30.1", - "mrujs": "^1.0.0", + "mrujs": "^1.0.2", "select2": "^4.0.13", "shortcut-buttons-flatpickr": "^0.4.0", "stimulus": "^3.2.2", @@ -34,10 +33,11 @@ "stimulus_reflex": "3.5.1", "tom-select": "^2.3.1", "trix": "^2.1.5", + "turbo_power": "^0.7.0", "webpack": "~4" }, "devDependencies": { - "jasmine-core": "~5.2.0", + "jasmine-core": "~5.3.0", "jest": "^27.4.7", "karma": "~6.4.4", "karma-chrome-launcher": "~3.2.0", diff --git a/spec/base_spec_helper.rb b/spec/base_spec_helper.rb index b56cb83492..5579890b64 100644 --- a/spec/base_spec_helper.rb +++ b/spec/base_spec_helper.rb @@ -4,8 +4,9 @@ ENV["RAILS_ENV"] ||= 'test' +# for full configuration, see .simplecov require 'simplecov' if ENV["COVERAGE"] -require 'rubygems' + require 'pry' unless ENV['CI'] require 'view_component/test_helpers' @@ -27,6 +28,12 @@ end require 'knapsack_pro' KnapsackPro::Adapters::RSpecAdapter.bind +if ENV["COVERAGE"] && defined?(SimpleCov) + KnapsackPro::Hooks::Queue.before_queue do + SimpleCov.command_name("rspec_ci_node_#{KnapsackPro::Config::Env.ci_node_index}") + end +end + # Allow connections to selenium whilst raising errors when connecting to external sites require 'webmock/rspec' WebMock.enable! diff --git a/spec/components/admin_tooltip_component_spec.rb b/spec/components/admin_tooltip_component_spec.rb new file mode 100644 index 0000000000..4e0039dcea --- /dev/null +++ b/spec/components/admin_tooltip_component_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe AdminTooltipComponent, type: :component do + it "displays the tooltip link" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here")) + + expect(page).to have_selector "a", text: "Hover here" + end + + describe "text" do + it "displays the tooltip text" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here")) + + expect(page).to have_selector ".tooltip", text: "Tooltip description" + end + + it "sanitizes the tooltip text" do + render_inline(described_class.new( text: "Tooltip description", + link_text: "Hover here")) + + expect(page).to have_selector ".tooltip", text: "Tooltip description" + end + end + + describe "placement" do + it "uses top as default" do + render_inline(described_class.new(text: "Tooltip description", + link_text: "Hover here")) + + expect(page).to have_selector '[data-tooltip-placement-value="top"]' + end + + it "uses the given placement" do + render_inline(described_class.new(text: "Tooltip description", + link_text: "Hover here", placement: "left")) + expect(page).to have_selector '[data-tooltip-placement-value="left"]' + end + end + + it "adds the correct link" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here", + link: "www.ofn.com")) + + expect(page).to have_selector '[href="www.ofn.com"]' + end + + it "adds the correct link_class" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here", + link_class: "pretty")) + expect(page).to have_selector 'a[class="pretty"]' + end +end diff --git a/spec/components/modal_component_spec.rb b/spec/components/modal_component_spec.rb new file mode 100644 index 0000000000..e50dbd3d1f --- /dev/null +++ b/spec/components/modal_component_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe ModalComponent, type: :component do + it "renders default 'data-action' and 'data-controller'" do + render_inline(described_class.new(id: "test-id")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector '[data-controller="modal"]' + expect(page).to have_selector '[data-action="keyup@document->modal#closeIfEscapeKey"]' + end + + it "accepts html attributes options" do + render_inline(described_class.new(id: "test-id", 'data-test': "some data")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector '[data-test="some data"]' + end + + it "merges 'data-controller' attribute when present in options" do + render_inline(described_class.new(id: "test-id", 'data-controller': "other-controller")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector '[data-controller="modal other-controller"]' + end + + it "merges 'data-action' attribute when present in options" do + render_inline(described_class.new(id: "test-id", 'data-action': "click->other-controller#test")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector( + '[data-action="keyup@document->modal#closeIfEscapeKey click->other-controller#test"]' + ) + end +end diff --git a/spec/controllers/admin/order_cycles_controller_spec.rb b/spec/controllers/admin/order_cycles_controller_spec.rb index 2a859e7210..6f02b81802 100644 --- a/spec/controllers/admin/order_cycles_controller_spec.rb +++ b/spec/controllers/admin/order_cycles_controller_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' module Admin RSpec.describe OrderCyclesController, type: :controller do let!(:distributor_owner) { create(:user) } + let(:datetime_confirmation_attrs) { + { confirm_datetime_change: nil, + error_class: Admin::OrderCyclesController::DateTimeChangeError } + } before do allow(controller).to receive_messages spree_current_user: distributor_owner @@ -276,7 +280,8 @@ module Admin it "can update preference product_selection_from_coordinator_inventory_only" do expect(OrderCycles::FormService).to receive(:new). with(order_cycle, - { "preferred_product_selection_from_coordinator_inventory_only" => true }, + { "preferred_product_selection_from_coordinator_inventory_only" => true, + **datetime_confirmation_attrs }, anything) { form_mock } allow(form_mock).to receive(:save) { true } @@ -289,7 +294,7 @@ module Admin it "can update preference automatic_notifications" do expect(OrderCycles::FormService).to receive(:new). with(order_cycle, - { "automatic_notifications" => true }, + { "automatic_notifications" => true, **datetime_confirmation_attrs }, anything) { form_mock } allow(form_mock).to receive(:save) { true } @@ -323,13 +328,17 @@ module Admin format: :json, id: order_cycle.id, order_cycle: allowed.merge(restricted) } } - let(:form_mock) { instance_double(OrderCycles::FormService, save: true) } + let(:form_mock) { + instance_double(OrderCycles::FormService, save: true) + } before { allow(controller).to receive(:spree_current_user) { user } } context "as a manager of the coordinator" do let(:user) { coordinator.owner } - let(:expected) { [order_cycle, allowed.merge(restricted), user] } + let(:expected) { + [order_cycle, allowed.merge(restricted).merge(datetime_confirmation_attrs), user] + } it "allows me to update exchange information for exchanges, name and dates" do expect(OrderCycles::FormService).to receive(:new).with(*expected) { form_mock } @@ -339,7 +348,7 @@ module Admin context "as a producer supplying to an order cycle" do let(:user) { producer.owner } - let(:expected) { [order_cycle, allowed, user] } + let(:expected) { [order_cycle, allowed.merge(datetime_confirmation_attrs), user] } it "allows me to update exchange information for exchanges, but not name or dates" do expect(OrderCycles::FormService).to receive(:new).with(*expected) { form_mock } diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 4501adf433..81eeccbf77 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -310,14 +310,15 @@ RSpec.describe Admin::ReportsController, type: :controller do end it "triggers the delivery report" do - spree_post :show, { + spree_post :create, { + format: :turbo, q: { completed_at_lt: 1.day.ago }, shipping_method_in: ["123"], # We just need to search for shipping methods report_type: :order_cycle_management, report_subtype: "delivery", } - expect(response).to have_http_status(:no_content) + expect(response).to have_http_status(:ok) end end diff --git a/spec/controllers/api/v0/shops_controller_spec.rb b/spec/controllers/api/v0/shops_controller_spec.rb index c00be92165..b3acf2d710 100644 --- a/spec/controllers/api/v0/shops_controller_spec.rb +++ b/spec/controllers/api/v0/shops_controller_spec.rb @@ -32,10 +32,19 @@ RSpec.describe Api::V0::ShopsController, type: :controller do end describe "#closed_shops" do + let!(:hub_open_order_cycle) { + create(:simple_order_cycle, orders_open_at: 10.days.ago, + orders_close_at: 17.days.from_now, + suppliers: [create(:supplier_enterprise)], + distributors: [hub], variants: [product.variants.first]) + } + it "returns data for all closed shops" do get :closed_shops, params: {} - expect(json_response).not_to match hub.name + # `hub` has an open order cycle (hub_open_order_cycle), so it should be excluded from + # results + expect(json_response.inspect).not_to match hub.name response_ids = json_response.pluck(:id) expect(response_ids).to contain_exactly(closed_hub1.id, closed_hub2.id) diff --git a/spec/controllers/api/v0/taxonomies_controller_spec.rb b/spec/controllers/api/v0/taxonomies_controller_spec.rb deleted file mode 100644 index 3426342ed5..0000000000 --- a/spec/controllers/api/v0/taxonomies_controller_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Api - RSpec.describe V0::TaxonomiesController do - render_views - - let(:taxonomy) { create(:taxonomy) } - let(:taxon) { create(:taxon, name: "Ruby", taxonomy:) } - let(:taxon2) { create(:taxon, name: "Rails", taxonomy:) } - let(:attributes) { [:id, :name] } - - before do - allow(controller).to receive(:spree_current_user) { current_api_user } - - taxon2.children << create(:taxon, name: "3.2.2", taxonomy:) - taxon.children << taxon2 - taxonomy.root.children << taxon - end - - context "as a normal user" do - let(:current_api_user) { build(:user) } - - it "gets the jstree-friendly version of a taxonomy" do - api_get :jstree, id: taxonomy.id - - expect(json_response["data"]).to eq(taxonomy.root.name) - expect(json_response["attr"]).to eq("id" => taxonomy.root.id, "name" => taxonomy.root.name) - expect(json_response["state"]).to eq("closed") - end - end - end -end diff --git a/spec/controllers/api/v0/taxons_controller_spec.rb b/spec/controllers/api/v0/taxons_controller_spec.rb index 2c333027e9..a41f7c7d75 100644 --- a/spec/controllers/api/v0/taxons_controller_spec.rb +++ b/spec/controllers/api/v0/taxons_controller_spec.rb @@ -5,30 +5,19 @@ require 'spec_helper' RSpec.describe Api::V0::TaxonsController do render_views - let(:taxonomy) { create(:taxonomy) } - let(:taxon) { create(:taxon, name: "Ruby", taxonomy:) } - let(:taxon2) { create(:taxon, name: "Rails", taxonomy:) } - let(:attributes) { - ["id", "name", "pretty_name", "permalink", "position", "parent_id", "taxonomy_id"] + let!(:taxon) { create(:taxon, name: "Ruby") } + let!(:taxon2) { create(:taxon, name: "Rails") } + let!(:attributes) { + ["id", "name", "permalink", "position"] } before do allow(controller).to receive(:spree_current_user) { current_api_user } - - taxon2.children << create(:taxon, name: "3.2.2", taxonomy:) - taxon.children << taxon2 - taxonomy.root.children << taxon end context "as a normal user" do let(:current_api_user) { build(:user) } - it "gets all taxons for a taxonomy" do - api_get :index, taxonomy_id: taxonomy.id - - expect(json_response.first['name']).to eq taxon.name - end - it "gets all taxons" do api_get :index @@ -43,31 +32,21 @@ RSpec.describe Api::V0::TaxonsController do expect(json_response.first['name']).to eq "Ruby" end - it "gets all taxons in JSTree form" do - api_get :jstree, taxonomy_id: taxonomy.id, id: taxon.id - - response = json_response.first - expect(response["data"]).to eq(taxon2.name) - expect(response["attr"]).to eq("name" => taxon2.name, "id" => taxon2.id) - expect(response["state"]).to eq("closed") - end - it "cannot create a new taxon if not an admin" do - api_post :create, taxonomy_id: taxonomy.id, taxon: { name: "Location" } + api_post :create, taxon: { name: "Location" } assert_unauthorized! end it "cannot update a taxon" do - api_put :update, taxonomy_id: taxonomy.id, - id: taxon.id, + api_put :update, id: taxon.id, taxon: { name: "I hacked your store!" } assert_unauthorized! end it "cannot delete a taxon" do - api_delete :destroy, taxonomy_id: taxonomy.id, id: taxon.id + api_delete :destroy, id: taxon.id assert_unauthorized! end @@ -77,42 +56,25 @@ RSpec.describe Api::V0::TaxonsController do let(:current_api_user) { build(:admin_user) } it "can create" do - api_post :create, taxonomy_id: taxonomy.id, taxon: { name: "Colors" } + api_post :create, taxon: { name: "Colors" } expect(attributes.all? { |a| json_response.include? a }).to be true expect(response.status).to eq(201) - - expect(taxonomy.reload.root.children.count).to eq 2 - - expect(Spree::Taxon.last.parent_id).to eq taxonomy.root.id - expect(Spree::Taxon.last.taxonomy_id).to eq taxonomy.id end it "cannot create a new taxon with invalid attributes" do - api_post :create, taxonomy_id: taxonomy.id, taxon: {} + api_post :create, taxon: {} expect(response.status).to eq(422) expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") errors = json_response["errors"] - expect(taxonomy.reload.root.children.count).to eq 1 - end - - it "cannot create a new taxon with invalid taxonomy_id" do - api_post :create, taxonomy_id: 1000, taxon: { name: "Colors" } - - expect(response.status).to eq(422) - expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") - - errors = json_response["errors"] - expect(errors["taxonomy_id"]).not_to be_nil - expect(errors["taxonomy_id"].first).to eq "Invalid taxonomy id." - - expect(taxonomy.reload.root.children.count).to eq 1 + expect(Spree::Taxon.last).to eq taxon2 + expect(errors['name']).to eq ["can't be blank"] end it "can destroy" do - api_delete :destroy, taxonomy_id: taxonomy.id, id: taxon2.id + api_delete :destroy, id: taxon2.id expect(response.status).to eq(204) end diff --git a/spec/controllers/spree/admin/taxons_controller_spec.rb b/spec/controllers/spree/admin/taxons_controller_spec.rb index 3abc6669aa..4afa43166b 100644 --- a/spec/controllers/spree/admin/taxons_controller_spec.rb +++ b/spec/controllers/spree/admin/taxons_controller_spec.rb @@ -5,28 +5,68 @@ require 'spec_helper' RSpec.describe Spree::Admin::TaxonsController do render_views - let(:taxonomy) { create(:taxonomy) } - let(:taxon) { create(:taxon, name: "Ruby", taxonomy:) } - let(:taxon2) { create(:taxon, name: "Rails", taxonomy:) } + let!(:taxon) { create(:taxon, name: "Ruby") } + let!(:taxon2) { create(:taxon, name: "Rails") } + let(:valid_attributes) { attributes_for(:taxon) } before do allow(controller).to receive(:spree_current_user) { current_api_user } - - taxonomy.root.children << taxon - taxonomy.root.children << taxon2 end - context "as an admin" do + describe 'admin user' do let(:current_api_user) { build(:admin_user) } - it "can reorder taxons" do - spree_post :update, - taxonomy_id: taxonomy.id, - id: taxon2.id, - taxon: { parent_id: taxonomy.root.id, position: 0 } + it "can view all taxons" do + spree_get :index - expect(taxon2.reload.lft).to eq 2 - expect(Spree::Taxonomy.find(taxonomy.id).root.children.first).to eq(taxon2) + expect(response).to have_http_status :ok + end + + it "open taxon edit form" do + spree_get :edit, { id: taxon.id } + + expect(response).to have_http_status :ok + end + + it "open taxon edit form" do + spree_get :new + + expect(response).to have_http_status :ok + end + + context "create" do + it "persist data with valid attributes" do + spree_post :create, valid_attributes + + expect(Spree::Taxon.last.name).to eq valid_attributes[:name] + expect(response).to have_http_status :found + end + + it "returns error with invalid attributes" do + spree_post :create, { name: '' } + + expect(Spree::Taxon.count).to eq 2 + expect(response).to have_http_status :unprocessable_entity + end + end + + context "update" do + let!(:new_taxon) { create(:taxon, valid_attributes) } + it "persist data with valid attributes" do + spree_post :update, id: new_taxon.id, + taxon: valid_attributes.merge({ name: 'Taxon name updated' }) + + expect(new_taxon.reload.name).to eq 'Taxon name updated' + expect(response).to have_http_status :found + end + + it "returns error with invalid attributes" do + spree_post :update, id: new_taxon.id, + taxon: { **valid_attributes, name: '' } + + expect(new_taxon.reload.name).to eq valid_attributes[:name] + expect(response).to have_http_status :unprocessable_entity + end end end end diff --git a/spec/factories/taxon_factory.rb b/spec/factories/taxon_factory.rb index fa8bae12d3..2344e35fe5 100644 --- a/spec/factories/taxon_factory.rb +++ b/spec/factories/taxon_factory.rb @@ -3,7 +3,5 @@ FactoryBot.define do factory :taxon, class: Spree::Taxon do name { 'Ruby on Rails' } - taxonomy - parent_id { nil } end end diff --git a/spec/factories/taxonomy_factory.rb b/spec/factories/taxonomy_factory.rb deleted file mode 100644 index eafe9f8a61..0000000000 --- a/spec/factories/taxonomy_factory.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :taxonomy, class: Spree::Taxonomy do - name { 'Brand' } - end -end diff --git a/spec/fixtures/simplecov/customer/.resultset.json b/spec/fixtures/simplecov/customer/.resultset.json new file mode 100644 index 0000000000..130cfe0072 --- /dev/null +++ b/spec/fixtures/simplecov/customer/.resultset.json @@ -0,0 +1,85 @@ +{ + "RSpec": { + "coverage": { + "/Users/josephjohansen/code/open_food_foundation/openfoodnetwork/app/models/customer.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + 1, + null, + 1, + null, + 1, + null, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + 1, + 1, + null, + null, + null, + null, + null, + 8, + 1, + 6, + null, + 2, + 2, + null, + 1, + null, + 1, + 0, + null, + null, + 1, + null, + 1, + 31, + null, + null, + 1, + 31, + null, + null, + 1, + 24, + null, + null, + 1, + 0, + 0, + 0, + null, + 0, + null, + null + ] + } + }, + "timestamp": 1724235565 + } +} diff --git a/spec/fixtures/simplecov/voucher/.resultset.json b/spec/fixtures/simplecov/voucher/.resultset.json new file mode 100644 index 0000000000..db9d793e0e --- /dev/null +++ b/spec/fixtures/simplecov/voucher/.resultset.json @@ -0,0 +1,68 @@ +{ + "RSpec": { + "coverage": { + "/Users/josephjohansen/code/open_food_foundation/openfoodnetwork/app/models/voucher.rb": { + "lines": [ + null, + null, + 1, + 1, + null, + 1, + null, + 1, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + 1, + null, + 1, + 15, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + 3, + null, + null, + null, + null, + null, + null, + null, + null, + 3, + null, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null + ] + } + }, + "timestamp": 1724239602 + } +} diff --git a/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml b/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml index ad4af0b09b..3b476e79da 100644 --- a/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml +++ b/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml @@ -1,11 +1,11 @@ --- http_interactions: - request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":""}' + encoding: US-ASCII + string: '' headers: Content-Type: - application/json @@ -23,33 +23,27 @@ http_interactions: message: Forbidden headers: Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716531220&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=GSiP%2FtCyGGyQZrjxJKzy4%2F8ZDbqeNOf8qWTTKv61%2FjQ%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716531220&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=GSiP%2FtCyGGyQZrjxJKzy4%2F8ZDbqeNOf8qWTTKv61%2FjQ%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' + - openresty + Date: + - Fri, 02 Aug 2024 05:29:39 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '78' Connection: - keep-alive X-Powered-By: - Express Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '62' Etag: - - W/"3e-3yNPCMU4MDQmKmieGPWfDcA/0Eg" - Date: - - Fri, 24 May 2024 06:13:41 GMT - Via: - - 1.1 vegur + - W/"4e-571rdPbjoh7u5Gmg96Iozzasopg" + Strict-Transport-Security: + - max-age=15811200 body: encoding: UTF-8 - string: '{"message":"User access denied","error":"User not authorized"}' - recorded_at: Fri, 24 May 2024 06:13:41 GMT + string: '{"message":"User access denied - token expired","error":"User not authorized"}' + recorded_at: Fri, 02 Aug 2024 05:29:39 GMT - request: method: get uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration @@ -69,7 +63,7 @@ http_interactions: message: OK headers: Date: - - Fri, 24 May 2024 06:13:42 GMT + - Fri, 02 Aug 2024 05:29:41 GMT Content-Type: - application/json;charset=UTF-8 Transfer-Encoding: @@ -79,14 +73,14 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1716531223.827.7041.811327|6055218c9898cae39f8ffd531999e49a; + - AUTH_SESSION_ID=1722576582.635.145555.256088|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-cache, must-revalidate, no-transform, no-store Referrer-Policy: - no-referrer Strict-Transport-Security: - - max-age=15724800; includeSubDomains + - max-age=31536000; includeSubDomains X-Content-Type-Options: - nosniff X-Frame-Options: @@ -97,7 +91,7 @@ http_interactions: encoding: ASCII-8BIT string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' - recorded_at: Fri, 24 May 2024 06:13:43 GMT + recorded_at: Fri, 02 Aug 2024 05:29:41 GMT - request: method: post uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token @@ -121,7 +115,7 @@ http_interactions: message: OK headers: Date: - - Fri, 24 May 2024 06:13:44 GMT + - Fri, 02 Aug 2024 05:29:42 GMT Content-Type: - application/json Transfer-Encoding: @@ -131,7 +125,7 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1716531225.15.7041.192535|6055218c9898cae39f8ffd531999e49a; + - AUTH_SESSION_ID=1722576583.864.9579.316995|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-store @@ -140,7 +134,7 @@ http_interactions: Referrer-Policy: - no-referrer Strict-Transport-Security: - - max-age=15724800; includeSubDomains + - max-age=31536000; includeSubDomains X-Content-Type-Options: - nosniff X-Frame-Options: @@ -149,15 +143,15 @@ http_interactions: - 1; mode=block body: encoding: ASCII-8BIT - string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31357813,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"cfaa4a60-c2aa-4590-9fdf-a117f23d564f","scope":"openid + string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31453394,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"207aea32-9912-47cb-b8ad-7508448912b8","scope":"openid profile email"}' - recorded_at: Fri, 24 May 2024 06:13:44 GMT + recorded_at: Fri, 02 Aug 2024 05:29:43 GMT - request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":""}' + encoding: US-ASCII + string: '' headers: Content-Type: - application/json @@ -175,32 +169,30 @@ http_interactions: message: OK headers: Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716531225&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=zHpdjRNvPwW4u7pYofDRsdOcjztCveqnM3K9GcGjhMU%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716531225&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=zHpdjRNvPwW4u7pYofDRsdOcjztCveqnM3K9GcGjhMU%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' + - openresty + Date: + - Fri, 02 Aug 2024 05:29:44 GMT + Content-Type: + - text/html; charset=utf-8 + Content-Length: + - '15329' Connection: - keep-alive X-Powered-By: - Express Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '41161' Etag: - - W/"a0c9-f4oAeN9fidSaWKNQXG3R8vniAac" - Date: - - Fri, 24 May 2024 06:13:49 GMT - Via: - - 1.1 vegur + - W/"3be1-OcCyKhhY7ZDkbp72mY+FlALOBIo" + Set-Cookie: + - SRVGROUP=common; path=/; HttpOnly + X-Resolver-Ip: + - 185.172.100.59 + Strict-Transport-Security: + - max-age=15811200 body: encoding: ASCII-8BIT string: !binary |- - eyJwcm9kdWN0cyI6IntcIkBjb250ZXh0XCI6XCJodHRwczovL3d3dy5kYXRhZm9vZGNvbnNvcnRpdW0ub3JnXCIsXCJAZ3JhcGhcIjpbe1wiQGlkXCI6XCJfOmIxXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC40XCJ9LHtcIkBpZFwiOlwiXzpiMTBcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjIuODlcIn0se1wiQGlkXCI6XCJfOmIxMVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNFwifSx7XCJAaWRcIjpcIl86YjEyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIwLjk5XCJ9LHtcIkBpZFwiOlwiXzpiMTNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjNcIn0se1wiQGlkXCI6XCJfOmIxNFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMi45OVwifSx7XCJAaWRcIjpcIl86YjE1XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNC44XCJ9LHtcIkBpZFwiOlwiXzpiMTZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjE4Ljg1XCJ9LHtcIkBpZFwiOlwiXzpiMTdcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCI0LjhcIn0se1wiQGlkXCI6XCJfOmIxOFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNy40MlwifSx7XCJAaWRcIjpcIl86YjE5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNVwifSx7XCJAaWRcIjpcIl86YjJcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjIuMDlcIn0se1wiQGlkXCI6XCJfOmIyMFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTIuNjBcIn0se1wiQGlkXCI6XCJfOmIyMVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjQuOFwifSx7XCJAaWRcIjpcIl86YjIyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCI4Ljc2XCJ9LHtcIkBpZFwiOlwiXzpiMjNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIxLjA1XCJ9LHtcIkBpZFwiOlwiXzpiMjRcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEzLjA1XCJ9LHtcIkBpZFwiOlwiXzpiMjVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCI0LjhcIn0se1wiQGlkXCI6XCJfOmIyNlwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNi43NlwifSx7XCJAaWRcIjpcIl86YjI3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiM1wifSx7XCJAaWRcIjpcIl86YjI4XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxNS45MFwifSx7XCJAaWRcIjpcIl86YjI5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTJcIn0se1wiQGlkXCI6XCJfOmIzXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC40XCJ9LHtcIkBpZFwiOlwiXzpiMzBcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiMzFcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMlwifSx7XCJAaWRcIjpcIl86YjMyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMVwifSx7XCJAaWRcIjpcIl86YjMzXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTBcIn0se1wiQGlkXCI6XCJfOmIzNFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmIzNVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEyXCJ9LHtcIkBpZFwiOlwiXzpiMzZcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiMzdcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCI2XCJ9LHtcIkBpZFwiOlwiXzpiMzhcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiMzlcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMlwifSx7XCJAaWRcIjpcIl86YjRcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEuMTlcIn0se1wiQGlkXCI6XCJfOmI0MFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI0MVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEwXCJ9LHtcIkBpZFwiOlwiXzpiNDJcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiNVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNVwifSx7XCJAaWRcIjpcIl86YjZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEuNjlcIn0se1wiQGlkXCI6XCJfOmI3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC40XCJ9LHtcIkBpZFwiOlwiXzpiOFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMS4zOVwifSx7XCJAaWRcIjpcIl86YjlcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjE3NVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmRlc2NyaXB0aW9uXCI6XCI8dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHIgc3R5bGU9XFxcImJvcmRlcjogMHB4O1xcXCI+XFxuPHRkIGJnY29sb3I9XFxcIiNkNmZiZWRcXFwiIHN0eWxlPVxcXCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7XFxcIj48c3Ryb25nPlRoZXkncmUgYmFjayE8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxwPjxzdHJvbmc+VGhpbmsgYmFrZWQgYmVhbnMgYXJlIEJyaXRpc2g/IFRoZXkgYXJlIG5vdyEgV2UgdXNlIG9ubHkgQnJpdGlzaC1ncm93biBmYXZhIGJlYW5zIC0gQnJpdGFpbidzIG9yaWdpbmFsIGJlYW4sIGdyb3duIGhlcmUgc2luY2UgdGhlIElyb24gQWdlLiBPdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgZGVsaWNpb3VzbHkgZGlmZmVyZW50LCB3aXRoIGxhcmdlIG1lYXR5IGZhdmEgYmVhbnMgaW4gYSB0YXN0eSB0b21hdG8gc2F1Y2UuPC9zdHJvbmc+PC9wPlxcbjxwPjxzdHJvbmc+PGEgdGl0bGU9XFxcIldoYXQgYXJlIGZhdmEgYmVhbnM/IEFyZW4ndCB0aGV5IGp1c3QgYnJvYWQgYmVhbnM/XFxcIiBocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiIGRhdGEtbWNlLWZyYWdtZW50PVxcXCIxXFxcIiBkYXRhLW1jZS1ocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiPldoYXQgYXJlIGZhdmEgYmVhbnM/IEZpbmQgb3V0IGhlcmUuLi48L2E+PC9zdHJvbmc+PC9wPlxcbjwhLS0gc3BsaXQgLS0+PGgzPkNvbXBsZXRlIFByb2R1Y3QgRGV0YWlsczwvaDM+PHA+T3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGNvb2tlZCBhbmQgcmVhZHkgdG8gZWF0LCBob3Qgb3IgY29sZC4gVGhleSdyZSBnb29kIHNlcnZlZCBvbiB0b2FzdCBidXQgYWxzbyBkZWxpY2lvdXMgYWRkZWQgdG8gc3Rld3MsIGN1cnJpZXMgb3IgY2Fzc2Vyb2xlcy4gT3IgZXZlbiBpbiBhIHBpZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPjxzdHJvbmc+Q29va2luZyBvbiB0aGUgSG9iPC9zdHJvbmc+PGJyPkVtcHR5IGNvbnRlbnRzIGludG8gc2F1Y2VwYW4uIEhlYXQgZ2VudGx5IGZvciA0LTUgbWludXRlcyB3aGlsZSBzdGlycmluZy4gRm9yIGJlc3QgZmxhdm91ciBkbyBub3QgYm9pbCBvciBvdmVyY29vay4gRG8gbm90IHJlaGVhdC48L3A+XFxuPHA+PHN0cm9uZz5NaWNyb3dhdmUgQ29va2luZzwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIGEgbm9uLW1ldGFsbGljIGJvd2wgYW5kIGNvdmVyLiBIZWF0IGZvciAyIHRvIDMgbWludXRlcywgc3RpcnJpbmcgaGFsZndheS4gQ2hlY2sgdGhlIGZvb2QgaXMgaG90LCBzdGlyIHdlbGwgYW5kIHNlcnZlLiBEbyBub3QgcmVoZWF0LjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5UbyBTdG9yZTwvaDU+XFxuPHA+U3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIE9uY2Ugb3BlbmVkLCB0cmFuc2ZlciBjb250ZW50cyB0byBhIG5vbi1tZXRhbGxpYyBjb250YWluZXIsIGNvdmVyIHJlZnJpZ2VyYXRlIGFuZCB1c2Ugd2l0aCAyIGRheXMuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD5GYXZhIEJlYW5zIChCcm9hZCBCZWFucykgKDQyJSksIFdhdGVyLCBUb21hdG8gUHVyZWUsIFN1Z2FyLCBNb2RpZmllZCBNYWl6ZSBTdGFyY2gsIFNhbHQsIEhlcmJzICZhbXA7IFNwaWNlcywgQ29uY2VudHJhdGVkIExlbW9uIEp1aWNlPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPk5vIEFsbGVyZ2VuczwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+Mjkya0ogKDY5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4wLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjEwLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+NC42ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+NGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC42ZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHA+RGVsaWNpb3VzLCBudXRyaXRpb3VzIGFuZCBnb29kIGZvciB0aGUgc29pbCwgZmF2YSBiZWFucyBhcmUgYSB2YXJpZXR5IG9mIGJyb2FkIGJlYW4sIFZpY2lhIGZhYmEsIGxlZnQgdG8gcmlwZW4gYW5kIGRyeSBiZWZvcmUgaGFydmVzdC4gVGhleeKAmXJlIGFsc28ga25vd24gYXMgZmllbGQgYmVhbnMsIGhvcnNlIGJlYW5zLCBXaW5kc29yIGJlYW5zIG9yIGZ1bC48L3A+XFxuPHA+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8L3A+XFxuXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfOTgzeDY1Nl81MTM3NThlNi0yNjE2LTQ2ODctYThiMi1iYTZkZGU4NjQ5MjMuanBnP3Y9MTY3Nzc2MDc3OFwiLFwiZGZjLWI6bmFtZVwiOlwiQmFrZWQgQnJpdGlzaCBCZWFucyAtIFJldGFpbCBjYW4sIDQwMGcgKGNhbilcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQi9UNFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjJcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjI5XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjMwXCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwM1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwM1wiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0ciBzdHlsZT1cXFwiYm9yZGVyOiAwcHg7XFxcIj5cXG48dGQgYmdjb2xvcj1cXFwiI2Q2ZmJlZFxcXCIgc3R5bGU9XFxcImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcXFwiPjxzdHJvbmc+VGhleSdyZSBiYWNrITwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+XFxuPHA+PHN0cm9uZz5UaGluayBiYWtlZCBiZWFucyBhcmUgQnJpdGlzaD8gVGhleSBhcmUgbm93ISBXZSB1c2Ugb25seSBCcml0aXNoLWdyb3duIGZhdmEgYmVhbnMgLSBCcml0YWluJ3Mgb3JpZ2luYWwgYmVhbiwgZ3Jvd24gaGVyZSBzaW5jZSB0aGUgSXJvbiBBZ2UuIE91ciBCYWtlZCBCcml0aXNoIEJlYW5zIGFyZSBkZWxpY2lvdXNseSBkaWZmZXJlbnQsIHdpdGggbGFyZ2UgbWVhdHkgZmF2YSBiZWFucyBpbiBhIHRhc3R5IHRvbWF0byBzYXVjZS48L3N0cm9uZz48L3A+XFxuPHA+PHN0cm9uZz48YSB0aXRsZT1cXFwiV2hhdCBhcmUgZmF2YSBiZWFucz8gQXJlbid0IHRoZXkganVzdCBicm9hZCBiZWFucz9cXFwiIGhyZWY9XFxcIi9ibG9ncy9uZXdzL3doYXQtYXJlLWZhdmEtYmVhbnMtYXJlLXRoZXktanVzdC1icm9hZC1iZWFuc1xcXCIgZGF0YS1tY2UtZnJhZ21lbnQ9XFxcIjFcXFwiIGRhdGEtbWNlLWhyZWY9XFxcIi9ibG9ncy9uZXdzL3doYXQtYXJlLWZhdmEtYmVhbnMtYXJlLXRoZXktanVzdC1icm9hZC1iZWFuc1xcXCI+V2hhdCBhcmUgZmF2YSBiZWFucz8gRmluZCBvdXQgaGVyZS4uLjwvYT48L3N0cm9uZz48L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48cD5PdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgY29va2VkIGFuZCByZWFkeSB0byBlYXQsIGhvdCBvciBjb2xkLiBUaGV5J3JlIGdvb2Qgc2VydmVkIG9uIHRvYXN0IGJ1dCBhbHNvIGRlbGljaW91cyBhZGRlZCB0byBzdGV3cywgY3VycmllcyBvciBjYXNzZXJvbGVzLiBPciBldmVuIGluIGEgcGllLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+PHN0cm9uZz5Db29raW5nIG9uIHRoZSBIb2I8L3N0cm9uZz48YnI+RW1wdHkgY29udGVudHMgaW50byBzYXVjZXBhbi4gSGVhdCBnZW50bHkgZm9yIDQtNSBtaW51dGVzIHdoaWxlIHN0aXJyaW5nLiBGb3IgYmVzdCBmbGF2b3VyIGRvIG5vdCBib2lsIG9yIG92ZXJjb29rLiBEbyBub3QgcmVoZWF0LjwvcD5cXG48cD48c3Ryb25nPk1pY3Jvd2F2ZSBDb29raW5nPC9zdHJvbmc+PGJyPkVtcHR5IGNvbnRlbnRzIGludG8gYSBub24tbWV0YWxsaWMgYm93bCBhbmQgY292ZXIuIEhlYXQgZm9yIDIgdG8gMyBtaW51dGVzLCBzdGlycmluZyBoYWxmd2F5LiBDaGVjayB0aGUgZm9vZCBpcyBob3QsIHN0aXIgd2VsbCBhbmQgc2VydmUuIERvIG5vdCByZWhlYXQuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPlRvIFN0b3JlPC9oNT5cXG48cD5TdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS4gT25jZSBvcGVuZWQsIHRyYW5zZmVyIGNvbnRlbnRzIHRvIGEgbm9uLW1ldGFsbGljIGNvbnRhaW5lciwgY292ZXIgcmVmcmlnZXJhdGUgYW5kIHVzZSB3aXRoIDIgZGF5cy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPkZhdmEgQmVhbnMgKEJyb2FkIEJlYW5zKSAoNDIlKSwgV2F0ZXIsIFRvbWF0byBQdXJlZSwgU3VnYXIsIE1vZGlmaWVkIE1haXplIFN0YXJjaCwgU2FsdCwgSGVyYnMgJmFtcDsgU3BpY2VzLCBDb25jZW50cmF0ZWQgTGVtb24gSnVpY2U8L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Tm8gQWxsZXJnZW5zPC9wPlxcbjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0cj5cXG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XFxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5FbmVyZ3k8L3RkPlxcbjx0ZD4yOTJrSiAoNjlrY2FsKTwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZhdDwvdGQ+XFxuPHRkPjAuNGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzYXR1cmF0ZXM8L3RkPlxcbjx0ZD4wLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cXG48dGQ+MTAuMWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxcbjx0ZD40LjZnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmlicmU8L3RkPlxcbjx0ZD41ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlByb3RlaW48L3RkPlxcbjx0ZD40ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlNhbHQ8L3RkPlxcbjx0ZD4wLjZnPC90ZD5cXG48L3RyPlxcbjwvdGJvZHk+XFxuPC90YWJsZT48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Nb3JlPC9oNT5cXG48cD5EZWxpY2lvdXMsIG51dHJpdGlvdXMgYW5kIGdvb2QgZm9yIHRoZSBzb2lsLCBmYXZhIGJlYW5zIGFyZSBhIHZhcmlldHkgb2YgYnJvYWQgYmVhbiwgVmljaWEgZmFiYSwgbGVmdCB0byByaXBlbiBhbmQgZHJ5IGJlZm9yZSBoYXJ2ZXN0LiBUaGV54oCZcmUgYWxzbyBrbm93biBhcyBmaWVsZCBiZWFucywgaG9yc2UgYmVhbnMsIFdpbmRzb3IgYmVhbnMgb3IgZnVsLjwvcD5cXG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczwvcD5cXG5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxNVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfOTgzeDY1Nl81MTM3NThlNi0yNjE2LTQ2ODctYThiMi1iYTZkZGU4NjQ5MjMuanBnP3Y9MTY3Nzc2MDc3OFwiLFwiZGZjLWI6bmFtZVwiOlwiQmFrZWQgQnJpdGlzaCBCZWFucyAtIENhc2UsIDEyIHggNDAwZyAoY2FuKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwMy9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwMy9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOQ0JCL0NEXCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwMy9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiMTZcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0ciBzdHlsZT1cXFwiYm9yZGVyOiAwcHg7XFxcIj5cXG48dGQgYmdjb2xvcj1cXFwiI2Q2ZmJlZFxcXCIgc3R5bGU9XFxcImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcXFwiPjxzdHJvbmc+U29ycnksIHN0YW5kYXJkIGJhcmxleSBmbGFrZXMgYXJlIG5vIGxvbmdlciBhdmFpbGFibGUgYnV0IG91ciBkZWxpY2lvdXMgIE9yZ2FuaWMgTmFrZWQgQmFybGV5IEZsYWtlcyBhcmUgYmFjay48L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxwPk91ciByaWNoIGFuZCBtYWx0eSBiYXJsZXkgZmxha2VzIGFyZSBhIHN0b3JlIGN1cGJvYXJkIHN0YXBsZS4gT3JnYW5pY2FsbHkgZ3Jvd24gYW5kIG1pbGxlZCBpbiB0aGUgVUssIHRoZXkgYWRkIHRleHR1cmUgdG8gZmxhcGphY2sgYW5kIGJpc2N1aXQgcmVjaXBlcywgb3IgdG8gbWFrZSBhIGhlYXJ0aWVyLCBydXN0aWMgcG9ycmlkZ2Ug4oCTIHRyeSBibGVuZGluZyB3aXRoIG91ciBvdGhlciBmbGFrZXM8L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48bGkgaWQ9XFxcInRhYjFcXFwiIGNsYXNzPVxcXCJhY3RpdmVcXFwiPlxcbjxwPkJhcmxleSBmbGFrZXMgYXJlIGdyZWF0IGFkZGVkIHRvIG11ZXNsaSBvciBncmFub2xhLCBvciB1c2VkIGluIGJha2luZyBhcyBhIHRvcHBpbmcgb3IgbWl4ZWQgaW50byBkb3VnaC4gRWF0IHRoZW0gYXMgYSBjZXJlYWwsIGJha2Ugd2l0aCB0aGVtLCBvciBhZGQgdGhlbSB0byBzb3VwcyBhbmQgc3Rld3MgdG8gdGhpY2tlbiwgYm9vc3QgdGhlaXIgbnV0cml0aW9uIGFuZCBhZGQgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPlRvIGVhdCBhcyBhIG11ZXNsaSwgY29tYmluZSB3aXRoIG90aGVyIGNlcmVhbCBmbGFrZXMgYW5kIGVuam95LiBPciB1c2UgYXMgYW4gb2F0IHN1YnN0aXR1dGUgaW4gYW55IGJha2luZyByZWNpcGUuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD48Yj5CYXJsZXnCoDwvYj5GbGFrZXM8L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Rm9yIGFsbGVyZ2VucywgaW5jbHVkaW5nIGNlcmVhbHMgY29udGFpbmluZyBnbHV0ZW4sIHNlZSBpbmdyZWRpZW50cyBpbiA8c3Ryb25nPmJvbGQ8L3N0cm9uZz48L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjEsNDAxa0ogKDMzMmtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+Mi4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD41OC4zZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjEuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjE3LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjExLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1Plxcbjx1bD5cXG48bGk+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnNcXG48L2xpPlxcbjxsaT5ObyBhcnRpZmljaWFsIGluZ3JlZGllbnRzXFxuPC9saT5cXG48bGk+R00gZnJlZVxcbjwvbGk+XFxuPGxpPkhpZ2ggRmlicmVcXG48L2xpPlxcbjwvdWw+XFxuPC9saT5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI1XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0JhcmxleS1GbGFrZXMtMjQwMHgxNjAwX2MxMjE0MDdjLTZmZDItNDZjYS1hMTI0LWRiNWRmOTQ0MjM2OC5qcGc/dj0xNjc3NzYwNzgxXCIsXCJkZmMtYjpuYW1lXCI6XCJCYXJsZXkgRmxha2VzLCBPcmdhbmljIC0gUmV0YWlsIHBhY2ssIDUwMGdcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiT0tCQVI1XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxL3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzRcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyIHN0eWxlPVxcXCJib3JkZXI6IDBweDtcXFwiPlxcbjx0ZCBiZ2NvbG9yPVxcXCIjZDZmYmVkXFxcIiBzdHlsZT1cXFwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1xcXCI+PHN0cm9uZz5Tb3JyeSwgc3RhbmRhcmQgYmFybGV5IGZsYWtlcyBhcmUgbm8gbG9uZ2VyIGF2YWlsYWJsZSBidXQgb3VyIGRlbGljaW91cyAgT3JnYW5pYyBOYWtlZCBCYXJsZXkgRmxha2VzIGFyZSBiYWNrLjwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PHA+T3VyIHJpY2ggYW5kIG1hbHR5IGJhcmxleSBmbGFrZXMgYXJlIGEgc3RvcmUgY3VwYm9hcmQgc3RhcGxlLiBPcmdhbmljYWxseSBncm93biBhbmQgbWlsbGVkIGluIHRoZSBVSywgdGhleSBhZGQgdGV4dHVyZSB0byBmbGFwamFjayBhbmQgYmlzY3VpdCByZWNpcGVzLCBvciB0byBtYWtlIGEgaGVhcnRpZXIsIHJ1c3RpYyBwb3JyaWRnZSDigJMgdHJ5IGJsZW5kaW5nIHdpdGggb3VyIG90aGVyIGZsYWtlczwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxsaSBpZD1cXFwidGFiMVxcXCIgY2xhc3M9XFxcImFjdGl2ZVxcXCI+XFxuPHA+QmFybGV5IGZsYWtlcyBhcmUgZ3JlYXQgYWRkZWQgdG8gbXVlc2xpIG9yIGdyYW5vbGEsIG9yIHVzZWQgaW4gYmFraW5nIGFzIGEgdG9wcGluZyBvciBtaXhlZCBpbnRvIGRvdWdoLiBFYXQgdGhlbSBhcyBhIGNlcmVhbCwgYmFrZSB3aXRoIHRoZW0sIG9yIGFkZCB0aGVtIHRvIHNvdXBzIGFuZCBzdGV3cyB0byB0aGlja2VuLCBib29zdCB0aGVpciBudXRyaXRpb24gYW5kIGFkZCBmbGF2b3VyLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+VG8gZWF0IGFzIGEgbXVlc2xpLCBjb21iaW5lIHdpdGggb3RoZXIgY2VyZWFsIGZsYWtlcyBhbmQgZW5qb3kuIE9yIHVzZSBhcyBhbiBvYXQgc3Vic3RpdHV0ZSBpbiBhbnkgYmFraW5nIHJlY2lwZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPjxiPkJhcmxlecKgPC9iPkZsYWtlczwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5Gb3IgYWxsZXJnZW5zLCBpbmNsdWRpbmcgY2VyZWFscyBjb250YWluaW5nIGdsdXRlbiwgc2VlIGluZ3JlZGllbnRzIGluIDxzdHJvbmc+Ym9sZDwvc3Ryb25nPjwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+MSw0MDFrSiAoMzMya2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4yLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjU4LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+MS43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+MTcuM2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+MTEuNGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHVsPlxcbjxsaT5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuc1xcbjwvbGk+XFxuPGxpPk5vIGFydGlmaWNpYWwgaW5ncmVkaWVudHNcXG48L2xpPlxcbjxsaT5HTSBmcmVlXFxuPC9saT5cXG48bGk+SGlnaCBGaWJyZVxcbjwvbGk+XFxuPC91bD5cXG48L2xpPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjE5XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0JhcmxleS1GbGFrZXMtMjQwMHgxNjAwX2MxMjE0MDdjLTZmZDItNDZjYS1hMTI0LWRiNWRmOTQ0MjM2OC5qcGc/dj0xNjc3NzYwNzgxXCIsXCJkZmMtYjpuYW1lXCI6XCJCYXJsZXkgRmxha2VzLCBPcmdhbmljIC0gU3RhbmRhcmQgY2FzZSwgMTAgeCA1MDBnXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1NjU5MzkvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9LQkFDWFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1NjU5Mzkvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjIwXCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmRlc2NyaXB0aW9uXCI6XCI8cD48c3Ryb25nPkZlcm1lbnRlZCB3aG9sZWdyYWluIG5ha2VkIGJhcmxleSzCoHRhc3R5IGFuZCBzdWNjdWxlbnTCoGdyYWlucyBvZiByaWNoIG1hbHR5IHVtYW1pIGZsYXZvdXIuPC9zdHJvbmc+PC9wPlxcbjxwPlRoZXNlIHdob2xlIGZlcm1lbnRlZCBiYXJsZXnCoGdyYWlucyBhcmUgcGFja2VkIHdpdGggZGVlcCBmbGF2b3VyIGFuZCBtYWtlIGEgZGVsaWNpb3VzIGFkZGl0aW9uIHRvIGJyZWFkLCByaXNvdHRvLCBzdGV3cywgc2FsYWRzIGFuZCBtb3JlLjwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPlxcbjxwPkFkZCBpbnRlbnNlbHkgZmxhdm91cmVkIG1hbHR5IGFuZCBzdWNjdWxlbnQgZ3JhaW5zwqBmdWxsIG9mIHVtYW1pIHJpY2huZXNzIHRvIGJyZWFkcywgcmlzb3R0bywgc3Rld3MsIHNvdXBzLCBhbmQgZXZlbiBzYWxhZHMuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkNvb2tpbmcgaW5zdHJ1Y3Rpb25zPC9oNT5cXG48cD5BZGQgYWJvdXQgaGFsZiBhIHRlYXNwb29uIHBlciBzZXJ2aW5nIHRvIGFsbW9zdCBhbnkgZGlzaCBmb3IgYWRkZWQgZGVwdGgsIHVtYW1pIHJpY2huZXNzIGFuZCBtYWx0eSBmbGF2b3VyLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5UbyBzdG9yZTwvaDU+XFxuPHA+S2VlcCByZWZyaWdlcmF0ZWQgYW5kIHVzZSB3aXRoaW4gNCB3ZWVrcyBvZiBvcGVuaW5nLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5JbmdyZWRpZW50czwvaDU+XFxuPHA+TmFrZWQgPHN0cm9uZz5CYXJsZXk8L3N0cm9uZz4sIFdhdGVyLCA8c3Ryb25nPldoZWF0PC9zdHJvbmc+IEZsb3VyLCBTYWx0LCBMaXZlIEN1bHR1cmVzKjxicj4gKjxlbT5MYWN0b2JhY2lsbHVzIGRlbGJydWVja2lpPC9lbT4sIDxlbT5Bc3BlcmdpbGx1cyBzb2phZTwvZW0+LCA8ZW0+Wnlnb3NhY2NoYXJvbXljZXMgcm91eGlpPC9lbT48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Q29udGFpbnMgPHN0cm9uZz5CYXJsZXkgKEdsdXRlbik8L3N0cm9uZz4sPHN0cm9uZz4gV2hlYXQgKEdsdXRlbik8L3N0cm9uZz48L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjUwMGtKICgxMTlrY2FsKTwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZhdDwvdGQ+XFxuPHRkPjEuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzYXR1cmF0ZXM8L3RkPlxcbjx0ZD4wLjVnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cXG48dGQ+MTkuMWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxcbjx0ZD4yLjdnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmlicmU8L3RkPlxcbjx0ZD41LjJnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjQuMmc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+OC41ZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHA+T3VyIEZlcm1lbnRlZCBXaG9sZWdyYWluIE5ha2VkIEJhcmxleSBpcyBmZXJtZW50ZWQgaW4gdGhlIHNhbWUgd2F5IGFzIG1hbnkgc295YSBmZXJtZW50cyB1c2VkIGZvciBibGFjayBiZWFucywgYnV0IHVzaW5nwqBuYWtlZCBiYXJsZXkgZ3JhaW4gaW5zdGVhZCBvZiBzb3kgYmVhbnMuIEl0J3MgbWFkZSB3aXRoIGp1c3QgbmFrZWQgYmFybGV5IGdyYWlucywgd2F0ZXIgYW5kIHNhbHQsIGZlcm1lbnRlZCB3aXRoIGEgbGl2ZSBjdWx0dXJlIG9mIDxlbT5MYWN0b2JhY2lsbHVzIGRlbGJydWVja2lpPC9lbT4sIDxlbT5Bc3BlcmdpbGx1cyBzb2phZTwvZW0+IGFuZCA8ZW0+Wnlnb3NhY2NoYXJvbXljZXMgcm91eGlpPC9lbT4uPC9wPlxcbjxwPlN1aXRhYmxlIGZvciB2ZWdhbnMuPC9wPlxcbjxwPlBhY2tlZCBpbiByZWN5Y2xhYmxlIGdsYXNzIGphciB3aXRoIG1ldGFsIGxpZC48L3A+XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiOVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9GZXJtZW50ZWQtV2hvbGVncmFpbi1OYWtlZC1CYXJsZXktU3Bvb24tMTYwMHgxMDAwX2Q2ZmVhMDkyLWZkZTQtNGE5OC1iZWM4LWJiM2NhMGExZmQ0ZC5qcGc/dj0xNjc3NzYwODYwXCIsXCJkZmMtYjpuYW1lXCI6XCJGZXJtZW50ZWQgTmFrZWQgQmFybGV5IC0gUmV0YWlsIGphciwgMTc1ZyAoamFyKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOTU5CL0pGXCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiMTBcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjM3XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjM4XCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4M1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4M1wiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjxwPjxzdHJvbmc+RmVybWVudGVkIHdob2xlZ3JhaW4gbmFrZWQgYmFybGV5LMKgdGFzdHkgYW5kIHN1Y2N1bGVudMKgZ3JhaW5zIG9mIHJpY2ggbWFsdHkgdW1hbWkgZmxhdm91ci48L3N0cm9uZz48L3A+XFxuPHA+VGhlc2Ugd2hvbGUgZmVybWVudGVkIGJhcmxlecKgZ3JhaW5zIGFyZSBwYWNrZWQgd2l0aCBkZWVwIGZsYXZvdXIgYW5kIG1ha2UgYSBkZWxpY2lvdXMgYWRkaXRpb24gdG8gYnJlYWQsIHJpc290dG8sIHN0ZXdzLCBzYWxhZHMgYW5kIG1vcmUuPC9wPlxcbjwhLS0gc3BsaXQgLS0+PGgzPkNvbXBsZXRlIFByb2R1Y3QgRGV0YWlsczwvaDM+XFxuPHA+QWRkIGludGVuc2VseSBmbGF2b3VyZWQgbWFsdHkgYW5kIHN1Y2N1bGVudCBncmFpbnPCoGZ1bGwgb2YgdW1hbWkgcmljaG5lc3MgdG8gYnJlYWRzLCByaXNvdHRvLCBzdGV3cywgc291cHMsIGFuZCBldmVuIHNhbGFkcy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPkFkZCBhYm91dCBoYWxmIGEgdGVhc3Bvb24gcGVyIHNlcnZpbmcgdG8gYWxtb3N0IGFueSBkaXNoIGZvciBhZGRlZCBkZXB0aCwgdW1hbWkgcmljaG5lc3MgYW5kIG1hbHR5IGZsYXZvdXIuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPlRvIHN0b3JlPC9oNT5cXG48cD5LZWVwIHJlZnJpZ2VyYXRlZCBhbmQgdXNlIHdpdGhpbiA0IHdlZWtzIG9mIG9wZW5pbmcuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD5OYWtlZCA8c3Ryb25nPkJhcmxleTwvc3Ryb25nPiwgV2F0ZXIsIDxzdHJvbmc+V2hlYXQ8L3N0cm9uZz4gRmxvdXIsIFNhbHQsIExpdmUgQ3VsdHVyZXMqPGJyPiAqPGVtPkxhY3RvYmFjaWxsdXMgZGVsYnJ1ZWNraWk8L2VtPiwgPGVtPkFzcGVyZ2lsbHVzIHNvamFlPC9lbT4sIDxlbT5aeWdvc2FjY2hhcm9teWNlcyByb3V4aWk8L2VtPjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5Db250YWlucyA8c3Ryb25nPkJhcmxleSAoR2x1dGVuKTwvc3Ryb25nPiw8c3Ryb25nPiBXaGVhdCAoR2x1dGVuKTwvc3Ryb25nPjwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+NTAwa0ogKDExOWtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+MS43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuNWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD4xOS4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjIuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjUuMmc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+NC4yZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlNhbHQ8L3RkPlxcbjx0ZD44LjVnPC90ZD5cXG48L3RyPlxcbjwvdGJvZHk+XFxuPC90YWJsZT48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Nb3JlPC9oNT5cXG48cD5PdXIgRmVybWVudGVkIFdob2xlZ3JhaW4gTmFrZWQgQmFybGV5IGlzIGZlcm1lbnRlZCBpbiB0aGUgc2FtZSB3YXkgYXMgbWFueSBzb3lhIGZlcm1lbnRzIHVzZWQgZm9yIGJsYWNrIGJlYW5zLCBidXQgdXNpbmfCoG5ha2VkIGJhcmxleSBncmFpbiBpbnN0ZWFkIG9mIHNveSBiZWFucy4gSXQncyBtYWRlIHdpdGgganVzdCBuYWtlZCBiYXJsZXkgZ3JhaW5zLCB3YXRlciBhbmQgc2FsdCwgZmVybWVudGVkIHdpdGggYSBsaXZlIGN1bHR1cmUgb2YgPGVtPkxhY3RvYmFjaWxsdXMgZGVsYnJ1ZWNraWk8L2VtPiwgPGVtPkFzcGVyZ2lsbHVzIHNvamFlPC9lbT4gYW5kIDxlbT5aeWdvc2FjY2hhcm9teWNlcyByb3V4aWk8L2VtPi48L3A+XFxuPHA+U3VpdGFibGUgZm9yIHZlZ2Fucy48L3A+XFxuPHA+UGFja2VkIGluIHJlY3ljbGFibGUgZ2xhc3MgamFyIHdpdGggbWV0YWwgbGlkLjwvcD5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIyM1wiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9GZXJtZW50ZWQtV2hvbGVncmFpbi1OYWtlZC1CYXJsZXktU3Bvb24tMTYwMHgxMDAwX2Q2ZmVhMDkyLWZkZTQtNGE5OC1iZWM4LWJiM2NhMGExZmQ0ZC5qcGc/dj0xNjc3NzYwODYwXCIsXCJkZmMtYjpuYW1lXCI6XCJGZXJtZW50ZWQgTmFrZWQgQmFybGV5IC0gQ2FzZSwgNiB4IDE3NWcgKGphcilcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzODE2ODMvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzODE2ODMvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTk1OQi9DNlwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzODE2ODMvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjI0XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNDQ4NTFcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxN1wiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfYTRkNTg0NTktYmY1Mi00OGE5LWJhZTctODA3ZjQwMzViODdmLmpwZz92PTE2Nzc3NjA3NzdcIixcImRmYy1iOm5hbWVcIjpcIkJha2VkIEJyaXRpc2ggQmVhbnMgKFRvTCkgLSBDYXNlIC0gMTIgeCA0MDBnIGNhbnNcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNDQ4NTEvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNDQ4NTEvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQkNEXCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiMThcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjNcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2X2E0ZDU4NDU5LWJmNTItNDhhOS1iYWU3LTgwN2Y0MDM1Yjg3Zi5qcGc/dj0xNjc3NzYwNzc3XCIsXCJkZmMtYjpuYW1lXCI6XCJCYWtlZCBCcml0aXNoIEJlYW5zIChUb0wpIC0gU2luZ2xlIC0gNDAwZyBjYW5cIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQlQ0XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIjIwXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzJcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3MjQ0ODUxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMjVcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvV2hvbGUtRmF2YS1CZWFucy1PcmdhbmljLUNhbm5lZF9mY2I2NGZkNy04Y2EzLTQ2NWEtOGY1Ni00NDNjZjI4ZTBiNzEuanBnP3Y9MTY3Nzc2MDk3N1wiLFwiZGZjLWI6bmFtZVwiOlwiT3JnYW5pYyBXaG9sZSBGYXZhIEJlYW5zIGluIFdhdGVyIChUb0wpIC0gQ2FzZSAtIDEyIHggNDAwZyBjYW5zXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg3ODQ5NDcvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DRkJDRFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg3ODQ5NDcvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjI2XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxMVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9XaG9sZS1GYXZhLUJlYW5zLU9yZ2FuaWMtQ2FubmVkX2ZjYjY0ZmQ3LThjYTMtNDY1YS04ZjU2LTQ0M2NmMjhlMGI3MS5qcGc/dj0xNjc3NzYwOTc3XCIsXCJkZmMtYjpuYW1lXCI6XCJPcmdhbmljIFdob2xlIEZhdmEgQmVhbnMgaW4gV2F0ZXIgKFRvTCkgLSBTaW5nbGUgLSA0MDBnIGNhblwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPQ0ZCVDRcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmIxMlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzlcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDBcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiN1wiLFwiZGZjLWI6bmFtZVwiOlwiQ2FybGluIFBlYXMgaW4gV2F0ZXIsIE9yZ2FuaWMgKERJU1RSSUJVVE9SKSAtIFJldGFpbCBjYW4gKDQwMGcgY2FuKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPQ0NQVDRcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI4XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6Y29uc3VtZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzVcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIzNVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIzNlwiLFwiZGZjLWI6cHJvZHVjZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3RyYW5zZm9ybWF0aW9uXCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkVHJhbnNmb3JtYXRpb25cIixcImRmYy1iOmhhc0luY29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpoYXNPdXRjb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIyMVwiLFwiZGZjLWI6bmFtZVwiOlwiQ2FybGluIFBlYXMgaW4gV2F0ZXIsIE9yZ2FuaWMgKERJU1RSSUJVVE9SKSAtIFN0YW5kYXJkIGNhc2UgKDEyIHggNDAwZyBjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DQ1BDRFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjIyXCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxM1wiLFwiZGZjLWI6bmFtZVwiOlwiUm9hc3RlZCBGYXZhIEJlYW5zLCBMaWdodGx5IFNlYSBTYWx0ZWQgKERJU1RSSUJVVE9SKSAtIFJldGFpbCBwYWNrICgzMDBnKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOUkZTUjNcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmIxNFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDJcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjMvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMjdcIixcImRmYy1iOm5hbWVcIjpcIlJvYXN0ZWQgRmF2YSBCZWFucywgTGlnaHRseSBTZWEgU2FsdGVkIChESVNUUklCVVRPUikgLSBTdGFuZGFyZCBjYXNlICgxMCB4IDMwMGcpXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5SRlNDWFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjI4XCJ9fV19IiwibGFzdElkIjoiODE0NzI5MjI1ODYxMSIsInJlbWFpbmluZ1Byb2R1Y3RzQ291bnRBZnRlciI6MCwic3VjY2VzcyI6dHJ1ZSwibWVzc2FnZSI6IlByb2R1Y3RzIHJldHJpZXZlZCBzdWNjZXNzZnVsbHkifQ== - recorded_at: Fri, 24 May 2024 06:13:50 GMT + eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjEyNSIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjEyNiIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIxMjciLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjI1In0seyJAaWQiOiJfOmIxMjgiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06RXVybyIsImRmYy1iOnZhbHVlIjoiNy4wMCJ9LHsiQGlkIjoiXzpiMTI5IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMTMwIiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOkV1cm8iLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIxMzEiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIxLjUifSx7IkBpZCI6Il86YjEzMiIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI3OS4wMCJ9LHsiQGlkIjoiXzpiMTMzIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTM0IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMTM1IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTM2IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjUiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIFJldGFpbCBib3R0bGUsIDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMzMifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEzNCIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9CRiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTI2In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjkiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIENhc2UsIDYgeCA0MG1sIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvT2ZmZXIiLCJkZmMtYjpza3UiOiJMSUIvTkFCVkkvQzYiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1NSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjEzMCJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMSIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIiBkYXRhLW1jZS1zdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgYmdjb2xvcj1cIiNkNmZiZWRcIiBkYXRhLW1jZS1zdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiPjxiPkRvcnNldCdzIGFuc3dlciB0byBtYXBsZSBzeXJ1cCwgTGliZXJ0eSBGaWVsZHMnIEFwcGxlIFN5cnVwIGlzIGEgbHV4dXJpb3VzbHkgcmljaCBhbmQgaW50ZW5zZSBuYXR1cmFsIHN3ZWV0ZW5lciwgbWFkZSBvbmx5IGZyb20gdGhlIGFwcGxlcyBvZiB0aGVpciBvcmNoYXJkcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgc3lydXAgY29udGFpbnMgbm8gYWRkaXRpdmVzLCBjaGVtaWNhbHMsIGZpbmluZyBhZ2VudHMgb3Igc3VnYXIuIE92ZXIgMmtnIGFwcGxlcyBnbyBpbnRvIGVhY2ggYm90dGxlLjwvcD5cbjxsaSBpZD1cInRhYjFcIiBjbGFzcz1cImFjdGl2ZVwiPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBhcyBhIHN3ZWV0ZW5lciwgbGlrZSBtYXBsZSBzeXJ1cC7CoERlbGljaW91cyB3aXRoIHBhbmNha2VzLCB5b2dodXJ0IHBvcnJpZGdlLCBpY2UgY3JlYW0sIEZyZW5jaCB0b2FzdCBhbmQgbW9yZS4gTWl4IHdpdGggc3BhcmtsaW5nIHdhdGVyIG9yIGxlbW9uYWRlIHRvIG1ha2UgYSBub24tYWxjb2hvbGljIHN1bW1lciBjdXAsIG9yIHVzZSBhcyBhIHN3ZWV0ZW5lciBpbiBjb29raW5nLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBSZWZyaWdlcmF0ZSBhZnRlciBvcGVuaW5nIGFuZCB1c2Ugd2l0aGluIDYgd2Vla3M8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy4gRnJlZSBmcm9tIHN1bHBoaXRlcy48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+MTA3MWtKICgyNTJrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+NjIuOWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnMqPC90ZD5cbjx0ZD41Mi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5Qcm9kdWN0IG9mwqBEb3JzZXQ8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+PC9wPlxuPC9saT4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEyNyIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLVN5cnVwLTIwMG1sX2IxZDFlOGNjLTk1MzAtNDBmZS05NmQ4LTVjNTJmNmRhOGEwMC5qcGc/dj0xNjc3NzYwNzc0IiwiZGZjLWI6bmFtZSI6IkFwcGxlIFN5cnVwIC0gUmV0YWlsIGJvdHRsZSwgMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMzUifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEzNiIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxL0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9CVCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTI4In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIiBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+RG9yc2V0J3MgYW5zd2VyIHRvIG1hcGxlIHN5cnVwLCBMaWJlcnR5IEZpZWxkcycgQXBwbGUgU3lydXAgaXMgYSBsdXh1cmlvdXNseSByaWNoIGFuZCBpbnRlbnNlIG5hdHVyYWwgc3dlZXRlbmVyLCBtYWRlIG9ubHkgZnJvbSB0aGUgYXBwbGVzIG9mIHRoZWlyIG9yY2hhcmRzLjwvYj48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+TGliZXJ0eSBGaWVsZHMgcHJvZHVjZSBzbWFsbCBiYXRjaGVzIG9mIHN1cGVyYiBzeXJ1cCwgYmFsc2FtaWMgdmluZWdhciwgY2lkZXIgYW5kIHZvZGthIGJ5IGhhbmQgZnJvbSB0aGUgZnJ1aXQgb2YgdGhlaXIgb3duIERvcnNldCBhcHBsZSBvcmNoYXJkcywgcGxhbnRlZCBmcm9tIDIwMTAuPGJyPjwvcD5cbjxwPlRoZSBzeXJ1cCBjb250YWlucyBubyBhZGRpdGl2ZXMsIGNoZW1pY2FscywgZmluaW5nIGFnZW50cyBvciBzdWdhci4gT3ZlciAya2cgYXBwbGVzIGdvIGludG8gZWFjaCBib3R0bGUuPC9wPlxuPGxpIGlkPVwidGFiMVwiIGNsYXNzPVwiYWN0aXZlXCI+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGFzIGEgc3dlZXRlbmVyLCBsaWtlIG1hcGxlIHN5cnVwLsKgRGVsaWNpb3VzIHdpdGggcGFuY2FrZXMsIHlvZ2h1cnQgcG9ycmlkZ2UsIGljZSBjcmVhbSwgRnJlbmNoIHRvYXN0IGFuZCBtb3JlLiBNaXggd2l0aCBzcGFya2xpbmcgd2F0ZXIgb3IgbGVtb25hZGUgdG8gbWFrZSBhIG5vbi1hbGNvaG9saWMgc3VtbWVyIGN1cCwgb3IgdXNlIGFzIGEgc3dlZXRlbmVyIGluIGNvb2tpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5UbyBzdG9yZTxicj5cbjwvaDU+XG48cD5Gb3IgYmVzdCBiZWZvcmUgZGF0ZSBzZWUgcGFjay4gU3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIFJlZnJpZ2VyYXRlIGFmdGVyIG9wZW5pbmcgYW5kIHVzZSB3aXRoaW4gNiB3ZWVrczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLiBGcmVlIGZyb20gc3VscGhpdGVzLjwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xMDcxa0ogKDI1MmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD42Mi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2Fycyo8L3RkPlxuPHRkPjUyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuMGc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+XG48L2xpPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMTMxIiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTGliZXJ0eS1GaWVsZHMtQXBwbGUtU3lydXAtMjAwbWxfYjFkMWU4Y2MtOTUzMC00MGZlLTk2ZDgtNWM1MmY2ZGE4YTAwLmpwZz92PTE2Nzc3NjA3NzQiLCJkZmMtYjpuYW1lIjoiQXBwbGUgU3lydXAgLSBDYXNlLCA2IHggMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTMyIn19XX0= + recorded_at: Fri, 02 Aug 2024 05:29:45 GMT recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml b/spec/fixtures/vcr_cassettes/DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml new file mode 100644 index 0000000000..f162e73216 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml @@ -0,0 +1,198 @@ +--- +http_interactions: +- request: + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts + body: + encoding: US-ASCII + string: '' + headers: + Content-Type: + - application/json + Authorization: + - "" + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 403 + message: Forbidden + headers: + Server: + - openresty + Date: + - Fri, 02 Aug 2024 05:35:50 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '78' + Connection: + - keep-alive + X-Powered-By: + - Express + Access-Control-Allow-Origin: + - "*" + Etag: + - W/"4e-vJeBLxgahmv23yP9gdPJW/woako" + Strict-Transport-Security: + - max-age=15811200 + body: + encoding: UTF-8 + string: '{"message":"User access denied - token missing","error":"User not authorized"}' + recorded_at: Fri, 02 Aug 2024 05:35:50 GMT +- request: + method: get + uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - SWD 2.0.3 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 02 Aug 2024 05:35:52 GMT + Content-Type: + - application/json;charset=UTF-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - Accept-Encoding + Set-Cookie: + - AUTH_SESSION_ID=1722576953.323.145752.731488|78230f584c0d7db97d376e98de5321dc; + Path=/; Secure; HttpOnly + Cache-Control: + - no-cache, must-revalidate, no-transform, no-store + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + body: + encoding: ASCII-8BIT + string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token + token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' + recorded_at: Fri, 02 Aug 2024 05:35:52 GMT +- request: + method: post + uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token + body: + encoding: UTF-8 + string: grant_type=refresh_token&refresh_token= + headers: + User-Agent: + - Rack::OAuth2 (2.2.1) + Authorization: + - "" + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 02 Aug 2024 05:35:53 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - Accept-Encoding + Set-Cookie: + - AUTH_SESSION_ID=1722576954.541.145720.245289|78230f584c0d7db97d376e98de5321dc; + Path=/; Secure; HttpOnly + Cache-Control: + - no-store + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + body: + encoding: ASCII-8BIT + string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31453023,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"207aea32-9912-47cb-b8ad-7508448912b8","scope":"openid + profile email"}' + recorded_at: Fri, 02 Aug 2024 05:35:53 GMT +- request: + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts + body: + encoding: US-ASCII + string: '' + headers: + Content-Type: + - application/json + Authorization: + - "" + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - openresty + Date: + - Fri, 02 Aug 2024 05:35:55 GMT + Content-Type: + - text/html; charset=utf-8 + Content-Length: + - '15329' + Connection: + - keep-alive + X-Powered-By: + - Express + Access-Control-Allow-Origin: + - "*" + Etag: + - W/"3be1-Kh0ReRKdXCT7KNerKk4EkoR/TQI" + Set-Cookie: + - SRVGROUP=common; path=/; HttpOnly + X-Resolver-Ip: + - 185.172.100.59 + Strict-Transport-Security: + - max-age=15811200 + body: + encoding: ASCII-8BIT + string: !binary |- + eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjEzNyIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjEzOCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIxMzkiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjI1In0seyJAaWQiOiJfOmIxNDAiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06RXVybyIsImRmYy1iOnZhbHVlIjoiNy4wMCJ9LHsiQGlkIjoiXzpiMTQxIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMTQyIiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOkV1cm8iLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIxNDMiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIxLjUifSx7IkBpZCI6Il86YjE0NCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI3OS4wMCJ9LHsiQGlkIjoiXzpiMTQ1IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTQ2IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMTQ3IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTQ4IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMzciLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIFJldGFpbCBib3R0bGUsIDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxNDUifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjE0NiIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9CRiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTM4In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxNDEiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIENhc2UsIDYgeCA0MG1sIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvT2ZmZXIiLCJkZmMtYjpza3UiOiJMSUIvTkFCVkkvQzYiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1NSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjE0MiJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMSIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIiBkYXRhLW1jZS1zdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgYmdjb2xvcj1cIiNkNmZiZWRcIiBkYXRhLW1jZS1zdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiPjxiPkRvcnNldCdzIGFuc3dlciB0byBtYXBsZSBzeXJ1cCwgTGliZXJ0eSBGaWVsZHMnIEFwcGxlIFN5cnVwIGlzIGEgbHV4dXJpb3VzbHkgcmljaCBhbmQgaW50ZW5zZSBuYXR1cmFsIHN3ZWV0ZW5lciwgbWFkZSBvbmx5IGZyb20gdGhlIGFwcGxlcyBvZiB0aGVpciBvcmNoYXJkcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgc3lydXAgY29udGFpbnMgbm8gYWRkaXRpdmVzLCBjaGVtaWNhbHMsIGZpbmluZyBhZ2VudHMgb3Igc3VnYXIuIE92ZXIgMmtnIGFwcGxlcyBnbyBpbnRvIGVhY2ggYm90dGxlLjwvcD5cbjxsaSBpZD1cInRhYjFcIiBjbGFzcz1cImFjdGl2ZVwiPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBhcyBhIHN3ZWV0ZW5lciwgbGlrZSBtYXBsZSBzeXJ1cC7CoERlbGljaW91cyB3aXRoIHBhbmNha2VzLCB5b2dodXJ0IHBvcnJpZGdlLCBpY2UgY3JlYW0sIEZyZW5jaCB0b2FzdCBhbmQgbW9yZS4gTWl4IHdpdGggc3BhcmtsaW5nIHdhdGVyIG9yIGxlbW9uYWRlIHRvIG1ha2UgYSBub24tYWxjb2hvbGljIHN1bW1lciBjdXAsIG9yIHVzZSBhcyBhIHN3ZWV0ZW5lciBpbiBjb29raW5nLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBSZWZyaWdlcmF0ZSBhZnRlciBvcGVuaW5nIGFuZCB1c2Ugd2l0aGluIDYgd2Vla3M8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy4gRnJlZSBmcm9tIHN1bHBoaXRlcy48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+MTA3MWtKICgyNTJrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+NjIuOWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnMqPC90ZD5cbjx0ZD41Mi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5Qcm9kdWN0IG9mwqBEb3JzZXQ8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+PC9wPlxuPC9saT4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEzOSIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLVN5cnVwLTIwMG1sX2IxZDFlOGNjLTk1MzAtNDBmZS05NmQ4LTVjNTJmNmRhOGEwMC5qcGc/dj0xNjc3NzYwNzc0IiwiZGZjLWI6bmFtZSI6IkFwcGxlIFN5cnVwIC0gUmV0YWlsIGJvdHRsZSwgMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxNDcifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjE0OCIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxL0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9CVCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTQwIn19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIiBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+RG9yc2V0J3MgYW5zd2VyIHRvIG1hcGxlIHN5cnVwLCBMaWJlcnR5IEZpZWxkcycgQXBwbGUgU3lydXAgaXMgYSBsdXh1cmlvdXNseSByaWNoIGFuZCBpbnRlbnNlIG5hdHVyYWwgc3dlZXRlbmVyLCBtYWRlIG9ubHkgZnJvbSB0aGUgYXBwbGVzIG9mIHRoZWlyIG9yY2hhcmRzLjwvYj48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+TGliZXJ0eSBGaWVsZHMgcHJvZHVjZSBzbWFsbCBiYXRjaGVzIG9mIHN1cGVyYiBzeXJ1cCwgYmFsc2FtaWMgdmluZWdhciwgY2lkZXIgYW5kIHZvZGthIGJ5IGhhbmQgZnJvbSB0aGUgZnJ1aXQgb2YgdGhlaXIgb3duIERvcnNldCBhcHBsZSBvcmNoYXJkcywgcGxhbnRlZCBmcm9tIDIwMTAuPGJyPjwvcD5cbjxwPlRoZSBzeXJ1cCBjb250YWlucyBubyBhZGRpdGl2ZXMsIGNoZW1pY2FscywgZmluaW5nIGFnZW50cyBvciBzdWdhci4gT3ZlciAya2cgYXBwbGVzIGdvIGludG8gZWFjaCBib3R0bGUuPC9wPlxuPGxpIGlkPVwidGFiMVwiIGNsYXNzPVwiYWN0aXZlXCI+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGFzIGEgc3dlZXRlbmVyLCBsaWtlIG1hcGxlIHN5cnVwLsKgRGVsaWNpb3VzIHdpdGggcGFuY2FrZXMsIHlvZ2h1cnQgcG9ycmlkZ2UsIGljZSBjcmVhbSwgRnJlbmNoIHRvYXN0IGFuZCBtb3JlLiBNaXggd2l0aCBzcGFya2xpbmcgd2F0ZXIgb3IgbGVtb25hZGUgdG8gbWFrZSBhIG5vbi1hbGNvaG9saWMgc3VtbWVyIGN1cCwgb3IgdXNlIGFzIGEgc3dlZXRlbmVyIGluIGNvb2tpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5UbyBzdG9yZTxicj5cbjwvaDU+XG48cD5Gb3IgYmVzdCBiZWZvcmUgZGF0ZSBzZWUgcGFjay4gU3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIFJlZnJpZ2VyYXRlIGFmdGVyIG9wZW5pbmcgYW5kIHVzZSB3aXRoaW4gNiB3ZWVrczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLiBGcmVlIGZyb20gc3VscGhpdGVzLjwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xMDcxa0ogKDI1MmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD42Mi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2Fycyo8L3RkPlxuPHRkPjUyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuMGc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+XG48L2xpPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMTQzIiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTGliZXJ0eS1GaWVsZHMtQXBwbGUtU3lydXAtMjAwbWxfYjFkMWU4Y2MtOTUzMC00MGZlLTk2ZDgtNWM1MmY2ZGE4YTAwLmpwZz92PTE2Nzc3NjA3NzQiLCJkZmMtYjpuYW1lIjoiQXBwbGUgU3lydXAgLSBDYXNlLCA2IHggMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTQ0In19XX0= + recorded_at: Fri, 02 Aug 2024 05:35:56 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml b/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml deleted file mode 100644 index 636e061690..0000000000 --- a/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml +++ /dev/null @@ -1,206 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com - body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":null}' - headers: - Content-Type: - - application/json - Authorization: - - "" - User-Agent: - - Faraday v2.9.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - response: - status: - code: 403 - message: Forbidden - headers: - Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716515324&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=QHSHL9RlLovwwwatlK4mrZMZ6powGfrf8MG7QDavBV4%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716515324&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=QHSHL9RlLovwwwatlK4mrZMZ6powGfrf8MG7QDavBV4%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' - Connection: - - keep-alive - X-Powered-By: - - Express - Access-Control-Allow-Origin: - - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '62' - Etag: - - W/"3e-3yNPCMU4MDQmKmieGPWfDcA/0Eg" - Date: - - Fri, 24 May 2024 01:48:44 GMT - Via: - - 1.1 vegur - body: - encoding: UTF-8 - string: '{"message":"User access denied","error":"User not authorized"}' - recorded_at: Fri, 24 May 2024 01:48:44 GMT -- request: - method: get - uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration - body: - encoding: US-ASCII - string: '' - headers: - User-Agent: - - SWD 2.0.3 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 24 May 2024 01:48:46 GMT - Content-Type: - - application/json;charset=UTF-8 - Transfer-Encoding: - - chunked - Connection: - - keep-alive - Vary: - - Accept-Encoding - Set-Cookie: - - AUTH_SESSION_ID=1716515327.317.9431.725800|6055218c9898cae39f8ffd531999e49a; - Path=/; Secure; HttpOnly - Cache-Control: - - no-cache, must-revalidate, no-transform, no-store - Referrer-Policy: - - no-referrer - Strict-Transport-Security: - - max-age=15724800; includeSubDomains - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Xss-Protection: - - 1; mode=block - body: - encoding: ASCII-8BIT - string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token - token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' - recorded_at: Fri, 24 May 2024 01:48:46 GMT -- request: - method: post - uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token - body: - encoding: UTF-8 - string: grant_type=refresh_token&refresh_token= - headers: - User-Agent: - - Rack::OAuth2 (2.2.1) - Authorization: - - "" - Content-Type: - - application/x-www-form-urlencoded - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - response: - status: - code: 200 - message: OK - headers: - Date: - - Fri, 24 May 2024 01:48:47 GMT - Content-Type: - - application/json - Transfer-Encoding: - - chunked - Connection: - - keep-alive - Vary: - - Accept-Encoding - Set-Cookie: - - AUTH_SESSION_ID=1716515328.538.9431.297717|6055218c9898cae39f8ffd531999e49a; - Path=/; Secure; HttpOnly - Cache-Control: - - no-store - Pragma: - - no-cache - Referrer-Policy: - - no-referrer - Strict-Transport-Security: - - max-age=15724800; includeSubDomains - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Xss-Protection: - - 1; mode=block - body: - encoding: ASCII-8BIT - string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31373710,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"cfaa4a60-c2aa-4590-9fdf-a117f23d564f","scope":"openid - profile email"}' - recorded_at: Fri, 24 May 2024 01:48:47 GMT -- request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com - body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":""}' - headers: - Content-Type: - - application/json - Authorization: - - "" - User-Agent: - - Faraday v2.9.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - response: - status: - code: 200 - message: OK - headers: - Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716515329&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=db8%2Bqll%2F9ViX4tDoArQRI69fIFO5okGU%2F86h1whY9lM%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716515329&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=db8%2Bqll%2F9ViX4tDoArQRI69fIFO5okGU%2F86h1whY9lM%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' - Connection: - - keep-alive - X-Powered-By: - - Express - Access-Control-Allow-Origin: - - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '41179' - Etag: - - W/"a0db-ySojxiWOF5gtH86VVAw3VoRbZ/o" - Date: - - Fri, 24 May 2024 01:48:49 GMT - Via: - - 1.1 vegur - body: - encoding: ASCII-8BIT - string: !binary |- - eyJwcm9kdWN0cyI6IntcIkBjb250ZXh0XCI6XCJodHRwczovL3d3dy5kYXRhZm9vZGNvbnNvcnRpdW0ub3JnXCIsXCJAZ3JhcGhcIjpbe1wiQGlkXCI6XCJfOmI0M1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNFwifSx7XCJAaWRcIjpcIl86YjQ0XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIyLjA5XCJ9LHtcIkBpZFwiOlwiXzpiNDVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjRcIn0se1wiQGlkXCI6XCJfOmI0NlwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMS4xOVwifSx7XCJAaWRcIjpcIl86YjQ3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC41XCJ9LHtcIkBpZFwiOlwiXzpiNDhcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEuNjlcIn0se1wiQGlkXCI6XCJfOmI0OVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNFwifSx7XCJAaWRcIjpcIl86YjUwXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxLjM5XCJ9LHtcIkBpZFwiOlwiXzpiNTFcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjE3NVwifSx7XCJAaWRcIjpcIl86YjUyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIyLjg5XCJ9LHtcIkBpZFwiOlwiXzpiNTNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjRcIn0se1wiQGlkXCI6XCJfOmI1NFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC45OVwifSx7XCJAaWRcIjpcIl86YjU1XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC4zXCJ9LHtcIkBpZFwiOlwiXzpiNTZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjIuOTlcIn0se1wiQGlkXCI6XCJfOmI1N1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjQuOFwifSx7XCJAaWRcIjpcIl86YjU4XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxOC44NVwifSx7XCJAaWRcIjpcIl86YjU5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNC44XCJ9LHtcIkBpZFwiOlwiXzpiNjBcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjcuNDJcIn0se1wiQGlkXCI6XCJfOmI2MVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjVcIn0se1wiQGlkXCI6XCJfOmI2MlwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTIuNjBcIn0se1wiQGlkXCI6XCJfOmI2M1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjQuOFwifSx7XCJAaWRcIjpcIl86YjY0XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCI4Ljc2XCJ9LHtcIkBpZFwiOlwiXzpiNjVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIxLjA1XCJ9LHtcIkBpZFwiOlwiXzpiNjZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEzLjA1XCJ9LHtcIkBpZFwiOlwiXzpiNjdcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCI0LjhcIn0se1wiQGlkXCI6XCJfOmI2OFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNi43NlwifSx7XCJAaWRcIjpcIl86YjY5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiM1wifSx7XCJAaWRcIjpcIl86YjcwXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxNS45MFwifSx7XCJAaWRcIjpcIl86YjcxXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTJcIn0se1wiQGlkXCI6XCJfOmI3MlwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI3M1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEyXCJ9LHtcIkBpZFwiOlwiXzpiNzRcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiNzVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMFwifSx7XCJAaWRcIjpcIl86Yjc2XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMVwifSx7XCJAaWRcIjpcIl86Yjc3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTJcIn0se1wiQGlkXCI6XCJfOmI3OFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI3OVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjZcIn0se1wiQGlkXCI6XCJfOmI4MFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI4MVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEyXCJ9LHtcIkBpZFwiOlwiXzpiODJcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiODNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMFwifSx7XCJAaWRcIjpcIl86Yjg0XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmRlc2NyaXB0aW9uXCI6XCI8dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHIgc3R5bGU9XFxcImJvcmRlcjogMHB4O1xcXCI+XFxuPHRkIGJnY29sb3I9XFxcIiNkNmZiZWRcXFwiIHN0eWxlPVxcXCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7XFxcIj48c3Ryb25nPlRoZXkncmUgYmFjayE8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxwPjxzdHJvbmc+VGhpbmsgYmFrZWQgYmVhbnMgYXJlIEJyaXRpc2g/IFRoZXkgYXJlIG5vdyEgV2UgdXNlIG9ubHkgQnJpdGlzaC1ncm93biBmYXZhIGJlYW5zIC0gQnJpdGFpbidzIG9yaWdpbmFsIGJlYW4sIGdyb3duIGhlcmUgc2luY2UgdGhlIElyb24gQWdlLiBPdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgZGVsaWNpb3VzbHkgZGlmZmVyZW50LCB3aXRoIGxhcmdlIG1lYXR5IGZhdmEgYmVhbnMgaW4gYSB0YXN0eSB0b21hdG8gc2F1Y2UuPC9zdHJvbmc+PC9wPlxcbjxwPjxzdHJvbmc+PGEgdGl0bGU9XFxcIldoYXQgYXJlIGZhdmEgYmVhbnM/IEFyZW4ndCB0aGV5IGp1c3QgYnJvYWQgYmVhbnM/XFxcIiBocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiIGRhdGEtbWNlLWZyYWdtZW50PVxcXCIxXFxcIiBkYXRhLW1jZS1ocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiPldoYXQgYXJlIGZhdmEgYmVhbnM/IEZpbmQgb3V0IGhlcmUuLi48L2E+PC9zdHJvbmc+PC9wPlxcbjwhLS0gc3BsaXQgLS0+PGgzPkNvbXBsZXRlIFByb2R1Y3QgRGV0YWlsczwvaDM+PHA+T3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGNvb2tlZCBhbmQgcmVhZHkgdG8gZWF0LCBob3Qgb3IgY29sZC4gVGhleSdyZSBnb29kIHNlcnZlZCBvbiB0b2FzdCBidXQgYWxzbyBkZWxpY2lvdXMgYWRkZWQgdG8gc3Rld3MsIGN1cnJpZXMgb3IgY2Fzc2Vyb2xlcy4gT3IgZXZlbiBpbiBhIHBpZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPjxzdHJvbmc+Q29va2luZyBvbiB0aGUgSG9iPC9zdHJvbmc+PGJyPkVtcHR5IGNvbnRlbnRzIGludG8gc2F1Y2VwYW4uIEhlYXQgZ2VudGx5IGZvciA0LTUgbWludXRlcyB3aGlsZSBzdGlycmluZy4gRm9yIGJlc3QgZmxhdm91ciBkbyBub3QgYm9pbCBvciBvdmVyY29vay4gRG8gbm90IHJlaGVhdC48L3A+XFxuPHA+PHN0cm9uZz5NaWNyb3dhdmUgQ29va2luZzwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIGEgbm9uLW1ldGFsbGljIGJvd2wgYW5kIGNvdmVyLiBIZWF0IGZvciAyIHRvIDMgbWludXRlcywgc3RpcnJpbmcgaGFsZndheS4gQ2hlY2sgdGhlIGZvb2QgaXMgaG90LCBzdGlyIHdlbGwgYW5kIHNlcnZlLiBEbyBub3QgcmVoZWF0LjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5UbyBTdG9yZTwvaDU+XFxuPHA+U3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIE9uY2Ugb3BlbmVkLCB0cmFuc2ZlciBjb250ZW50cyB0byBhIG5vbi1tZXRhbGxpYyBjb250YWluZXIsIGNvdmVyIHJlZnJpZ2VyYXRlIGFuZCB1c2Ugd2l0aCAyIGRheXMuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD5GYXZhIEJlYW5zIChCcm9hZCBCZWFucykgKDQyJSksIFdhdGVyLCBUb21hdG8gUHVyZWUsIFN1Z2FyLCBNb2RpZmllZCBNYWl6ZSBTdGFyY2gsIFNhbHQsIEhlcmJzICZhbXA7IFNwaWNlcywgQ29uY2VudHJhdGVkIExlbW9uIEp1aWNlPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPk5vIEFsbGVyZ2VuczwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+Mjkya0ogKDY5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4wLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjEwLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+NC42ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+NGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC42ZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHA+RGVsaWNpb3VzLCBudXRyaXRpb3VzIGFuZCBnb29kIGZvciB0aGUgc29pbCwgZmF2YSBiZWFucyBhcmUgYSB2YXJpZXR5IG9mIGJyb2FkIGJlYW4sIFZpY2lhIGZhYmEsIGxlZnQgdG8gcmlwZW4gYW5kIGRyeSBiZWZvcmUgaGFydmVzdC4gVGhleeKAmXJlIGFsc28ga25vd24gYXMgZmllbGQgYmVhbnMsIGhvcnNlIGJlYW5zLCBXaW5kc29yIGJlYW5zIG9yIGZ1bC48L3A+XFxuPHA+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8L3A+XFxuXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDNcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2Xzk4M3g2NTZfNTEzNzU4ZTYtMjYxNi00Njg3LWE4YjItYmE2ZGRlODY0OTIzLmpwZz92PTE2Nzc3NjA3NzhcIixcImRmYy1iOm5hbWVcIjpcIkJha2VkIEJyaXRpc2ggQmVhbnMgLSBSZXRhaWwgY2FuLCA0MDBnIChjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5DQkIvVDRcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI0NFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNzFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNzJcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyIHN0eWxlPVxcXCJib3JkZXI6IDBweDtcXFwiPlxcbjx0ZCBiZ2NvbG9yPVxcXCIjZDZmYmVkXFxcIiBzdHlsZT1cXFwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1xcXCI+PHN0cm9uZz5UaGV5J3JlIGJhY2shPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjwvdGJvZHk+XFxuPC90YWJsZT5cXG48cD48c3Ryb25nPlRoaW5rIGJha2VkIGJlYW5zIGFyZSBCcml0aXNoPyBUaGV5IGFyZSBub3chIFdlIHVzZSBvbmx5IEJyaXRpc2gtZ3Jvd24gZmF2YSBiZWFucyAtIEJyaXRhaW4ncyBvcmlnaW5hbCBiZWFuLCBncm93biBoZXJlIHNpbmNlIHRoZSBJcm9uIEFnZS4gT3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGRlbGljaW91c2x5IGRpZmZlcmVudCwgd2l0aCBsYXJnZSBtZWF0eSBmYXZhIGJlYW5zIGluIGEgdGFzdHkgdG9tYXRvIHNhdWNlLjwvc3Ryb25nPjwvcD5cXG48cD48c3Ryb25nPjxhIHRpdGxlPVxcXCJXaGF0IGFyZSBmYXZhIGJlYW5zPyBBcmVuJ3QgdGhleSBqdXN0IGJyb2FkIGJlYW5zP1xcXCIgaHJlZj1cXFwiL2Jsb2dzL25ld3Mvd2hhdC1hcmUtZmF2YS1iZWFucy1hcmUtdGhleS1qdXN0LWJyb2FkLWJlYW5zXFxcIiBkYXRhLW1jZS1mcmFnbWVudD1cXFwiMVxcXCIgZGF0YS1tY2UtaHJlZj1cXFwiL2Jsb2dzL25ld3Mvd2hhdC1hcmUtZmF2YS1iZWFucy1hcmUtdGhleS1qdXN0LWJyb2FkLWJlYW5zXFxcIj5XaGF0IGFyZSBmYXZhIGJlYW5zPyBGaW5kIG91dCBoZXJlLi4uPC9hPjwvc3Ryb25nPjwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxwPk91ciBCYWtlZCBCcml0aXNoIEJlYW5zIGFyZSBjb29rZWQgYW5kIHJlYWR5IHRvIGVhdCwgaG90IG9yIGNvbGQuIFRoZXkncmUgZ29vZCBzZXJ2ZWQgb24gdG9hc3QgYnV0IGFsc28gZGVsaWNpb3VzIGFkZGVkIHRvIHN0ZXdzLCBjdXJyaWVzIG9yIGNhc3Nlcm9sZXMuIE9yIGV2ZW4gaW4gYSBwaWUuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkNvb2tpbmcgaW5zdHJ1Y3Rpb25zPC9oNT5cXG48cD48c3Ryb25nPkNvb2tpbmcgb24gdGhlIEhvYjwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIHNhdWNlcGFuLiBIZWF0IGdlbnRseSBmb3IgNC01IG1pbnV0ZXMgd2hpbGUgc3RpcnJpbmcuIEZvciBiZXN0IGZsYXZvdXIgZG8gbm90IGJvaWwgb3Igb3ZlcmNvb2suIERvIG5vdCByZWhlYXQuPC9wPlxcbjxwPjxzdHJvbmc+TWljcm93YXZlIENvb2tpbmc8L3N0cm9uZz48YnI+RW1wdHkgY29udGVudHMgaW50byBhIG5vbi1tZXRhbGxpYyBib3dsIGFuZCBjb3Zlci4gSGVhdCBmb3IgMiB0byAzIG1pbnV0ZXMsIHN0aXJyaW5nIGhhbGZ3YXkuIENoZWNrIHRoZSBmb29kIGlzIGhvdCwgc3RpciB3ZWxsIGFuZCBzZXJ2ZS4gRG8gbm90IHJlaGVhdC48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+VG8gU3RvcmU8L2g1PlxcbjxwPlN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBPbmNlIG9wZW5lZCwgdHJhbnNmZXIgY29udGVudHMgdG8gYSBub24tbWV0YWxsaWMgY29udGFpbmVyLCBjb3ZlciByZWZyaWdlcmF0ZSBhbmQgdXNlIHdpdGggMiBkYXlzLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5JbmdyZWRpZW50czwvaDU+XFxuPHA+RmF2YSBCZWFucyAoQnJvYWQgQmVhbnMpICg0MiUpLCBXYXRlciwgVG9tYXRvIFB1cmVlLCBTdWdhciwgTW9kaWZpZWQgTWFpemUgU3RhcmNoLCBTYWx0LCBIZXJicyAmYW1wOyBTcGljZXMsIENvbmNlbnRyYXRlZCBMZW1vbiBKdWljZTwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5ObyBBbGxlcmdlbnM8L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjI5MmtKICg2OWtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+MC40ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuMWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD4xMC4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjQuNmc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjVnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjAuNmc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1PlxcbjxwPkRlbGljaW91cywgbnV0cml0aW91cyBhbmQgZ29vZCBmb3IgdGhlIHNvaWwsIGZhdmEgYmVhbnMgYXJlIGEgdmFyaWV0eSBvZiBicm9hZCBiZWFuLCBWaWNpYSBmYWJhLCBsZWZ0IHRvIHJpcGVuIGFuZCBkcnkgYmVmb3JlIGhhcnZlc3QuIFRoZXnigJlyZSBhbHNvIGtub3duIGFzIGZpZWxkIGJlYW5zLCBob3JzZSBiZWFucywgV2luZHNvciBiZWFucyBvciBmdWwuPC9wPlxcbjxwPlN1aXRhYmxlIGZvciB2ZWdhbnMgYW5kIHZlZ2V0YXJpYW5zPC9wPlxcblwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjU3XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL1BhY2stQ2FuLUJha2VkLUJlYW5zLTE4MDB4Nl85ODN4NjU2XzUxMzc1OGU2LTI2MTYtNDY4Ny1hOGIyLWJhNmRkZTg2NDkyMy5qcGc/dj0xNjc3NzYwNzc4XCIsXCJkZmMtYjpuYW1lXCI6XCJCYWtlZCBCcml0aXNoIEJlYW5zIC0gQ2FzZSwgMTIgeCA0MDBnIChjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MDA0MDMvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5DQkIvQ0RcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI1OFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyIHN0eWxlPVxcXCJib3JkZXI6IDBweDtcXFwiPlxcbjx0ZCBiZ2NvbG9yPVxcXCIjZDZmYmVkXFxcIiBzdHlsZT1cXFwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1xcXCI+PHN0cm9uZz5Tb3JyeSwgc3RhbmRhcmQgYmFybGV5IGZsYWtlcyBhcmUgbm8gbG9uZ2VyIGF2YWlsYWJsZSBidXQgb3VyIGRlbGljaW91cyAgT3JnYW5pYyBOYWtlZCBCYXJsZXkgRmxha2VzIGFyZSBiYWNrLjwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PHA+T3VyIHJpY2ggYW5kIG1hbHR5IGJhcmxleSBmbGFrZXMgYXJlIGEgc3RvcmUgY3VwYm9hcmQgc3RhcGxlLiBPcmdhbmljYWxseSBncm93biBhbmQgbWlsbGVkIGluIHRoZSBVSywgdGhleSBhZGQgdGV4dHVyZSB0byBmbGFwamFjayBhbmQgYmlzY3VpdCByZWNpcGVzLCBvciB0byBtYWtlIGEgaGVhcnRpZXIsIHJ1c3RpYyBwb3JyaWRnZSDigJMgdHJ5IGJsZW5kaW5nIHdpdGggb3VyIG90aGVyIGZsYWtlczwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxsaSBpZD1cXFwidGFiMVxcXCIgY2xhc3M9XFxcImFjdGl2ZVxcXCI+XFxuPHA+QmFybGV5IGZsYWtlcyBhcmUgZ3JlYXQgYWRkZWQgdG8gbXVlc2xpIG9yIGdyYW5vbGEsIG9yIHVzZWQgaW4gYmFraW5nIGFzIGEgdG9wcGluZyBvciBtaXhlZCBpbnRvIGRvdWdoLiBFYXQgdGhlbSBhcyBhIGNlcmVhbCwgYmFrZSB3aXRoIHRoZW0sIG9yIGFkZCB0aGVtIHRvIHNvdXBzIGFuZCBzdGV3cyB0byB0aGlja2VuLCBib29zdCB0aGVpciBudXRyaXRpb24gYW5kIGFkZCBmbGF2b3VyLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+VG8gZWF0IGFzIGEgbXVlc2xpLCBjb21iaW5lIHdpdGggb3RoZXIgY2VyZWFsIGZsYWtlcyBhbmQgZW5qb3kuIE9yIHVzZSBhcyBhbiBvYXQgc3Vic3RpdHV0ZSBpbiBhbnkgYmFraW5nIHJlY2lwZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPjxiPkJhcmxlecKgPC9iPkZsYWtlczwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5Gb3IgYWxsZXJnZW5zLCBpbmNsdWRpbmcgY2VyZWFscyBjb250YWluaW5nIGdsdXRlbiwgc2VlIGluZ3JlZGllbnRzIGluIDxzdHJvbmc+Ym9sZDwvc3Ryb25nPjwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+MSw0MDFrSiAoMzMya2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4yLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjU4LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+MS43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+MTcuM2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+MTEuNGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHVsPlxcbjxsaT5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuc1xcbjwvbGk+XFxuPGxpPk5vIGFydGlmaWNpYWwgaW5ncmVkaWVudHNcXG48L2xpPlxcbjxsaT5HTSBmcmVlXFxuPC9saT5cXG48bGk+SGlnaCBGaWJyZVxcbjwvbGk+XFxuPC91bD5cXG48L2xpPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjQ3XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0JhcmxleS1GbGFrZXMtMjQwMHgxNjAwX2MxMjE0MDdjLTZmZDItNDZjYS1hMTI0LWRiNWRmOTQ0MjM2OC5qcGc/dj0xNjc3NzYwNzgxXCIsXCJkZmMtYjpuYW1lXCI6XCJCYXJsZXkgRmxha2VzLCBPcmdhbmljIC0gUmV0YWlsIHBhY2ssIDUwMGdcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiT0tCQVI1XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNDhcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86Yjc1XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86Yjc2XCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0ciBzdHlsZT1cXFwiYm9yZGVyOiAwcHg7XFxcIj5cXG48dGQgYmdjb2xvcj1cXFwiI2Q2ZmJlZFxcXCIgc3R5bGU9XFxcImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcXFwiPjxzdHJvbmc+U29ycnksIHN0YW5kYXJkIGJhcmxleSBmbGFrZXMgYXJlIG5vIGxvbmdlciBhdmFpbGFibGUgYnV0IG91ciBkZWxpY2lvdXMgIE9yZ2FuaWMgTmFrZWQgQmFybGV5IEZsYWtlcyBhcmUgYmFjay48L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxwPk91ciByaWNoIGFuZCBtYWx0eSBiYXJsZXkgZmxha2VzIGFyZSBhIHN0b3JlIGN1cGJvYXJkIHN0YXBsZS4gT3JnYW5pY2FsbHkgZ3Jvd24gYW5kIG1pbGxlZCBpbiB0aGUgVUssIHRoZXkgYWRkIHRleHR1cmUgdG8gZmxhcGphY2sgYW5kIGJpc2N1aXQgcmVjaXBlcywgb3IgdG8gbWFrZSBhIGhlYXJ0aWVyLCBydXN0aWMgcG9ycmlkZ2Ug4oCTIHRyeSBibGVuZGluZyB3aXRoIG91ciBvdGhlciBmbGFrZXM8L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48bGkgaWQ9XFxcInRhYjFcXFwiIGNsYXNzPVxcXCJhY3RpdmVcXFwiPlxcbjxwPkJhcmxleSBmbGFrZXMgYXJlIGdyZWF0IGFkZGVkIHRvIG11ZXNsaSBvciBncmFub2xhLCBvciB1c2VkIGluIGJha2luZyBhcyBhIHRvcHBpbmcgb3IgbWl4ZWQgaW50byBkb3VnaC4gRWF0IHRoZW0gYXMgYSBjZXJlYWwsIGJha2Ugd2l0aCB0aGVtLCBvciBhZGQgdGhlbSB0byBzb3VwcyBhbmQgc3Rld3MgdG8gdGhpY2tlbiwgYm9vc3QgdGhlaXIgbnV0cml0aW9uIGFuZCBhZGQgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPlRvIGVhdCBhcyBhIG11ZXNsaSwgY29tYmluZSB3aXRoIG90aGVyIGNlcmVhbCBmbGFrZXMgYW5kIGVuam95LiBPciB1c2UgYXMgYW4gb2F0IHN1YnN0aXR1dGUgaW4gYW55IGJha2luZyByZWNpcGUuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD48Yj5CYXJsZXnCoDwvYj5GbGFrZXM8L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Rm9yIGFsbGVyZ2VucywgaW5jbHVkaW5nIGNlcmVhbHMgY29udGFpbmluZyBnbHV0ZW4sIHNlZSBpbmdyZWRpZW50cyBpbiA8c3Ryb25nPmJvbGQ8L3N0cm9uZz48L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjEsNDAxa0ogKDMzMmtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+Mi4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD41OC4zZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjEuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjE3LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjExLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1Plxcbjx1bD5cXG48bGk+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnNcXG48L2xpPlxcbjxsaT5ObyBhcnRpZmljaWFsIGluZ3JlZGllbnRzXFxuPC9saT5cXG48bGk+R00gZnJlZVxcbjwvbGk+XFxuPGxpPkhpZ2ggRmlicmVcXG48L2xpPlxcbjwvdWw+XFxuPC9saT5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI2MVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9CYXJsZXktRmxha2VzLTI0MDB4MTYwMF9jMTIxNDA3Yy02ZmQyLTQ2Y2EtYTEyNC1kYjVkZjk0NDIzNjguanBnP3Y9MTY3Nzc2MDc4MVwiLFwiZGZjLWI6bmFtZVwiOlwiQmFybGV5IEZsYWtlcywgT3JnYW5pYyAtIFN0YW5kYXJkIGNhc2UsIDEwIHggNTAwZ1wiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPS0JBQ1hcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI2MlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHA+PHN0cm9uZz5GZXJtZW50ZWQgd2hvbGVncmFpbiBuYWtlZCBiYXJsZXkswqB0YXN0eSBhbmQgc3VjY3VsZW50wqBncmFpbnMgb2YgcmljaCBtYWx0eSB1bWFtaSBmbGF2b3VyLjwvc3Ryb25nPjwvcD5cXG48cD5UaGVzZSB3aG9sZSBmZXJtZW50ZWQgYmFybGV5wqBncmFpbnMgYXJlIHBhY2tlZCB3aXRoIGRlZXAgZmxhdm91ciBhbmQgbWFrZSBhIGRlbGljaW91cyBhZGRpdGlvbiB0byBicmVhZCwgcmlzb3R0bywgc3Rld3MsIHNhbGFkcyBhbmQgbW9yZS48L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz5cXG48cD5BZGQgaW50ZW5zZWx5IGZsYXZvdXJlZCBtYWx0eSBhbmQgc3VjY3VsZW50IGdyYWluc8KgZnVsbCBvZiB1bWFtaSByaWNobmVzcyB0byBicmVhZHMsIHJpc290dG8sIHN0ZXdzLCBzb3VwcywgYW5kIGV2ZW4gc2FsYWRzLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+QWRkIGFib3V0IGhhbGYgYSB0ZWFzcG9vbiBwZXIgc2VydmluZyB0byBhbG1vc3QgYW55IGRpc2ggZm9yIGFkZGVkIGRlcHRoLCB1bWFtaSByaWNobmVzcyBhbmQgbWFsdHkgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+VG8gc3RvcmU8L2g1PlxcbjxwPktlZXAgcmVmcmlnZXJhdGVkIGFuZCB1c2Ugd2l0aGluIDQgd2Vla3Mgb2Ygb3BlbmluZy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPk5ha2VkIDxzdHJvbmc+QmFybGV5PC9zdHJvbmc+LCBXYXRlciwgPHN0cm9uZz5XaGVhdDwvc3Ryb25nPiBGbG91ciwgU2FsdCwgTGl2ZSBDdWx0dXJlcyo8YnI+ICo8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiwgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+PC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPkNvbnRhaW5zIDxzdHJvbmc+QmFybGV5IChHbHV0ZW4pPC9zdHJvbmc+LDxzdHJvbmc+IFdoZWF0IChHbHV0ZW4pPC9zdHJvbmc+PC9wPlxcbjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0cj5cXG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XFxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5FbmVyZ3k8L3RkPlxcbjx0ZD41MDBrSiAoMTE5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4xLjdnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC41ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjE5LjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+Mi43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NS4yZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlByb3RlaW48L3RkPlxcbjx0ZD40LjJnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjguNWc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1PlxcbjxwPk91ciBGZXJtZW50ZWQgV2hvbGVncmFpbiBOYWtlZCBCYXJsZXkgaXMgZmVybWVudGVkIGluIHRoZSBzYW1lIHdheSBhcyBtYW55IHNveWEgZmVybWVudHMgdXNlZCBmb3IgYmxhY2sgYmVhbnMsIGJ1dCB1c2luZ8KgbmFrZWQgYmFybGV5IGdyYWluIGluc3RlYWQgb2Ygc295IGJlYW5zLiBJdCdzIG1hZGUgd2l0aCBqdXN0IG5ha2VkIGJhcmxleSBncmFpbnMsIHdhdGVyIGFuZCBzYWx0LCBmZXJtZW50ZWQgd2l0aCBhIGxpdmUgY3VsdHVyZSBvZiA8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiBhbmQgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+LjwvcD5cXG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zLjwvcD5cXG48cD5QYWNrZWQgaW4gcmVjeWNsYWJsZSBnbGFzcyBqYXIgd2l0aCBtZXRhbCBsaWQuPC9wPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjUxXCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0Zlcm1lbnRlZC1XaG9sZWdyYWluLU5ha2VkLUJhcmxleS1TcG9vbi0xNjAweDEwMDBfZDZmZWEwOTItZmRlNC00YTk4LWJlYzgtYmIzY2EwYTFmZDRkLmpwZz92PTE2Nzc3NjA4NjBcIixcImRmYy1iOm5hbWVcIjpcIkZlcm1lbnRlZCBOYWtlZCBCYXJsZXkgLSBSZXRhaWwgamFyLCAxNzVnIChqYXIpXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5NTkIvSkZcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI1MlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNzlcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiODBcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzgxNjgzXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzgxNjgzXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHA+PHN0cm9uZz5GZXJtZW50ZWQgd2hvbGVncmFpbiBuYWtlZCBiYXJsZXkswqB0YXN0eSBhbmQgc3VjY3VsZW50wqBncmFpbnMgb2YgcmljaCBtYWx0eSB1bWFtaSBmbGF2b3VyLjwvc3Ryb25nPjwvcD5cXG48cD5UaGVzZSB3aG9sZSBmZXJtZW50ZWQgYmFybGV5wqBncmFpbnMgYXJlIHBhY2tlZCB3aXRoIGRlZXAgZmxhdm91ciBhbmQgbWFrZSBhIGRlbGljaW91cyBhZGRpdGlvbiB0byBicmVhZCwgcmlzb3R0bywgc3Rld3MsIHNhbGFkcyBhbmQgbW9yZS48L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz5cXG48cD5BZGQgaW50ZW5zZWx5IGZsYXZvdXJlZCBtYWx0eSBhbmQgc3VjY3VsZW50IGdyYWluc8KgZnVsbCBvZiB1bWFtaSByaWNobmVzcyB0byBicmVhZHMsIHJpc290dG8sIHN0ZXdzLCBzb3VwcywgYW5kIGV2ZW4gc2FsYWRzLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+QWRkIGFib3V0IGhhbGYgYSB0ZWFzcG9vbiBwZXIgc2VydmluZyB0byBhbG1vc3QgYW55IGRpc2ggZm9yIGFkZGVkIGRlcHRoLCB1bWFtaSByaWNobmVzcyBhbmQgbWFsdHkgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+VG8gc3RvcmU8L2g1PlxcbjxwPktlZXAgcmVmcmlnZXJhdGVkIGFuZCB1c2Ugd2l0aGluIDQgd2Vla3Mgb2Ygb3BlbmluZy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPk5ha2VkIDxzdHJvbmc+QmFybGV5PC9zdHJvbmc+LCBXYXRlciwgPHN0cm9uZz5XaGVhdDwvc3Ryb25nPiBGbG91ciwgU2FsdCwgTGl2ZSBDdWx0dXJlcyo8YnI+ICo8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiwgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+PC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPkNvbnRhaW5zIDxzdHJvbmc+QmFybGV5IChHbHV0ZW4pPC9zdHJvbmc+LDxzdHJvbmc+IFdoZWF0IChHbHV0ZW4pPC9zdHJvbmc+PC9wPlxcbjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0cj5cXG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XFxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5FbmVyZ3k8L3RkPlxcbjx0ZD41MDBrSiAoMTE5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4xLjdnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC41ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjE5LjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+Mi43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NS4yZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlByb3RlaW48L3RkPlxcbjx0ZD40LjJnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjguNWc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1PlxcbjxwPk91ciBGZXJtZW50ZWQgV2hvbGVncmFpbiBOYWtlZCBCYXJsZXkgaXMgZmVybWVudGVkIGluIHRoZSBzYW1lIHdheSBhcyBtYW55IHNveWEgZmVybWVudHMgdXNlZCBmb3IgYmxhY2sgYmVhbnMsIGJ1dCB1c2luZ8KgbmFrZWQgYmFybGV5IGdyYWluIGluc3RlYWQgb2Ygc295IGJlYW5zLiBJdCdzIG1hZGUgd2l0aCBqdXN0IG5ha2VkIGJhcmxleSBncmFpbnMsIHdhdGVyIGFuZCBzYWx0LCBmZXJtZW50ZWQgd2l0aCBhIGxpdmUgY3VsdHVyZSBvZiA8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiBhbmQgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+LjwvcD5cXG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zLjwvcD5cXG48cD5QYWNrZWQgaW4gcmVjeWNsYWJsZSBnbGFzcyBqYXIgd2l0aCBtZXRhbCBsaWQuPC9wPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjY1XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0Zlcm1lbnRlZC1XaG9sZWdyYWluLU5ha2VkLUJhcmxleS1TcG9vbi0xNjAweDEwMDBfZDZmZWEwOTItZmRlNC00YTk4LWJlYzgtYmIzY2EwYTFmZDRkLmpwZz92PTE2Nzc3NjA4NjBcIixcImRmYy1iOm5hbWVcIjpcIkZlcm1lbnRlZCBOYWtlZCBCYXJsZXkgLSBDYXNlLCA2IHggMTc1ZyAoamFyKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzgxNjgzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOTU5CL0M2XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNjZcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjU5XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL1BhY2stQ2FuLUJha2VkLUJlYW5zLTE4MDB4Nl9hNGQ1ODQ1OS1iZjUyLTQ4YTktYmFlNy04MDdmNDAzNWI4N2YuanBnP3Y9MTY3Nzc2MDc3N1wiLFwiZGZjLWI6bmFtZVwiOlwiQmFrZWQgQnJpdGlzaCBCZWFucyAoVG9MKSAtIENhc2UgLSAxMiB4IDQwMGcgY2Fuc1wiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3MjQ0ODUxL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOQ0JCQ0RcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3MjQ0ODUxL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI2MFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDVcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2X2E0ZDU4NDU5LWJmNTItNDhhOS1iYWU3LTgwN2Y0MDM1Yjg3Zi5qcGc/dj0xNjc3NzYwNzc3XCIsXCJkZmMtYjpuYW1lXCI6XCJCYWtlZCBCcml0aXNoIEJlYW5zIChUb0wpIC0gU2luZ2xlIC0gNDAwZyBjYW5cIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQlQ0XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIjIwXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNDZcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjczXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86Yjc0XCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0N1wiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjY3XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL1dob2xlLUZhdmEtQmVhbnMtT3JnYW5pYy1DYW5uZWRfZmNiNjRmZDctOGNhMy00NjVhLThmNTYtNDQzY2YyOGUwYjcxLmpwZz92PTE2Nzc3NjA5NzdcIixcImRmYy1iOm5hbWVcIjpcIk9yZ2FuaWMgV2hvbGUgRmF2YSBCZWFucyBpbiBXYXRlciAoVG9MKSAtIENhc2UgLSAxMiB4IDQwMGcgY2Fuc1wiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0Ny9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0Ny9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPQ0ZCQ0RcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI2OFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNTNcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvV2hvbGUtRmF2YS1CZWFucy1PcmdhbmljLUNhbm5lZF9mY2I2NGZkNy04Y2EzLTQ2NWEtOGY1Ni00NDNjZjI4ZTBiNzEuanBnP3Y9MTY3Nzc2MDk3N1wiLFwiZGZjLWI6bmFtZVwiOlwiT3JnYW5pYyBXaG9sZSBGYXZhIEJlYW5zIGluIFdhdGVyIChUb0wpIC0gU2luZ2xlIC0gNDAwZyBjYW5cIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiT0NGQlQ0XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNTRcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjgxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjgyXCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0N1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjQ5XCIsXCJkZmMtYjpuYW1lXCI6XCJDYXJsaW4gUGVhcyBpbiBXYXRlciwgT3JnYW5pYyAoRElTVFJJQlVUT1IpIC0gUmV0YWlsIGNhbiAoNDAwZyBjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DQ1BUNFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjUwXCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6Y29uc3VtZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzVcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI3N1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI3OFwiLFwiZGZjLWI6cHJvZHVjZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3RyYW5zZm9ybWF0aW9uXCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkVHJhbnNmb3JtYXRpb25cIixcImRmYy1iOmhhc0luY29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpoYXNPdXRjb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI2M1wiLFwiZGZjLWI6bmFtZVwiOlwiQ2FybGluIFBlYXMgaW4gV2F0ZXIsIE9yZ2FuaWMgKERJU1RSSUJVVE9SKSAtIFN0YW5kYXJkIGNhc2UgKDEyIHggNDAwZyBjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DQ1BDRFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjY0XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI1NVwiLFwiZGZjLWI6bmFtZVwiOlwiUm9hc3RlZCBGYXZhIEJlYW5zLCBMaWdodGx5IFNlYSBTYWx0ZWQgKERJU1RSSUJVVE9SKSAtIFJldGFpbCBwYWNrICgzMDBnKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOUkZTUjNcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI1NlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiODNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiODRcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjMvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNjlcIixcImRmYy1iOm5hbWVcIjpcIlJvYXN0ZWQgRmF2YSBCZWFucywgTGlnaHRseSBTZWEgU2FsdGVkIChESVNUUklCVVRPUikgLSBTdGFuZGFyZCBjYXNlICgxMCB4IDMwMGcpXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5SRlNDWFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjcwXCJ9fV19IiwibGFzdElkIjoiODE0NzI5MjI1ODYxMSIsInJlbWFpbmluZ1Byb2R1Y3RzQ291bnRBZnRlciI6MCwic3VjY2VzcyI6dHJ1ZSwibWVzc2FnZSI6IlByb2R1Y3RzIHJldHJpZXZlZCBzdWNjZXNzZnVsbHkifQ== - recorded_at: Fri, 24 May 2024 01:48:50 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/helpers/injection_helper_spec.rb b/spec/helpers/injection_helper_spec.rb index d7cc660951..1001d763a1 100644 --- a/spec/helpers/injection_helper_spec.rb +++ b/spec/helpers/injection_helper_spec.rb @@ -16,39 +16,60 @@ RSpec.describe InjectionHelper, type: :helper do } let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: user.id) } + let(:sm) { create(:shipping_method) } + let(:pm) { create(:payment_method) } + let(:distributor) { + create(:distributor_enterprise, shipping_methods: [sm], payment_methods: [pm]) + } + let(:order) { create(:order, distributor:) } + + before do + allow_any_instance_of(EnterprisesHelper).to receive(:current_distributor).and_return distributor + allow_any_instance_of(EnterprisesHelper).to receive(:current_order).and_return order + end + it "will inject via AMS" do expect(helper.inject_json_array("test", [enterprise], Api::IdSerializer)).to match /#{enterprise.id}/ end - it "injects enterprises" do - expect(helper.inject_enterprises).to match enterprise.name - expect(helper.inject_enterprises).to match enterprise.facebook + describe "#inject_enterprises" do + it "injects enterprises" do + expect(helper.inject_enterprises).to match enterprise.name + expect(helper.inject_enterprises).to match enterprise.facebook + end + + it "only injects activated enterprises" do + inactive_enterprise = create(:enterprise, sells: 'unspecified') + expect(helper.inject_enterprises).not_to match inactive_enterprise.name + end end - it "only injects activated enterprises" do - inactive_enterprise = create(:enterprise, sells: 'unspecified') - expect(helper.inject_enterprises).not_to match inactive_enterprise.name + describe "#inject_enterprise_and_relatives" do + let(:child) { create :distributor_enterprise } + let!(:relationship) { create :enterprise_relationship, parent: distributor, child: } + + it "injects the current distributor and its relatives" do + expect(helper.inject_enterprise_and_relatives).to match distributor.name + expect(helper.inject_enterprise_and_relatives).to match child.name + end end - it "injects shipping_methods" do - sm = create(:shipping_method) - current_distributor = create(:distributor_enterprise, shipping_methods: [sm]) - order = create(:order, distributor: current_distributor) - allow(helper).to receive(:current_order) { order } - allow(helper).to receive(:spree_current_user) { nil } + describe "#inject_group_enterprises" do + let(:group) { create :enterprise_group, enterprises: [enterprise] } + + it "injects an enterprise group's enterprises" do + expect(helper.inject_group_enterprises(group)).to match enterprise.name + end end - it "injects payment methods" do - pm = create(:payment_method) - current_distributor = create(:distributor_enterprise, payment_methods: [pm]) - order = create(:order, distributor: current_distributor) - allow(helper).to receive(:current_order) { order } - allow(helper).to receive(:spree_current_user) { nil } + describe "#inject_current_hub" do + it "injects the current distributor" do + expect(helper.inject_current_hub).to match distributor.name + end end it "injects current order" do - allow(helper).to receive(:current_order).and_return order = create(:order) expect(helper.inject_current_order).to match order.id.to_s end 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 8946a17db4..e77ee3780e 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 @@ -121,18 +121,24 @@ describe "LineItemsCtrl", -> scope.line_items = [ line_item1, line_item2 ] it "show popup about order cancellation only on last item deletion", -> + spyOn(window, "confirm").and.callFake(-> return true) spyOn(window, "ofnCancelOrderAlert") + scope.deleteLineItem(line_item2) + expect(confirm).toHaveBeenCalled() expect(ofnCancelOrderAlert).not.toHaveBeenCalled() + scope.deleteLineItem(line_item1) expect(ofnCancelOrderAlert).toHaveBeenCalled() it "deletes the line item", -> spyOn(window, "confirm").and.callFake(-> return true) spyOn(LineItems, "delete") + scope.deleteLineItem(line_item2) + expect(confirm).toHaveBeenCalled() expect(LineItems.delete).toHaveBeenCalledWith(line_item2, jasmine.anything()) - + describe "deleting 'checked' line items", -> line_item1 = line_item2 = line_item3 = line_item4 = null order1 = order2 = order3 = null @@ -165,10 +171,14 @@ describe "LineItemsCtrl", -> it "asks for confirmation only if orders will be canceled", -> spyOn(window, "ofnCancelOrderAlert") + line_item3.checked = true scope.deleteLineItems(scope.line_items) + line_item1.checked = true scope.deleteLineItems(scope.line_items) + expect(ofnCancelOrderAlert).toHaveBeenCalled() + describe "check boxes for line items", -> line_item1 = line_item2 = null diff --git a/spec/jobs/report_job_spec.rb b/spec/jobs/report_job_spec.rb index e1ce4a1a56..fd8c9aaf86 100644 --- a/spec/jobs/report_job_spec.rb +++ b/spec/jobs/report_job_spec.rb @@ -6,13 +6,14 @@ RSpec.describe ReportJob do include CableReady::Broadcaster let(:report_args) { - { report_class:, user:, params:, format:, filename: } + { report_class:, user:, params:, format:, blob: } } let(:report_class) { Reporting::Reports::UsersAndEnterprises::Base } let(:user) { enterprise.owner } let(:enterprise) { create(:enterprise) } let(:params) { {} } let(:format) { :csv } + let(:blob) { ReportBlob.create_for_upload_later!(filename) } let(:filename) { "report.csv" } it "generates a report" do @@ -25,11 +26,12 @@ RSpec.describe ReportJob do it "enqueues a job for async processing" do expect { ReportJob.perform_later(**report_args) - }.not_to change { ActiveStorage::Blob.count } + }.not_to change { blob.checksum } expect { perform_enqueued_jobs(only: ReportJob) - }.to change { ActiveStorage::Blob.count } + blob.reload + }.to change { blob.checksum } expect_csv_report end @@ -44,7 +46,8 @@ RSpec.describe ReportJob do expect { perform_enqueued_jobs(only: ReportJob) - }.to change { ActiveStorage::Blob.count } + blob.reload + }.to change { blob.checksum } end it "triggers an email when the report is done" do diff --git a/spec/lib/open_food_network/enterprise_injection_data_spec.rb b/spec/lib/open_food_network/enterprise_injection_data_spec.rb index e69de29bb2..7297b8f698 100644 --- a/spec/lib/open_food_network/enterprise_injection_data_spec.rb +++ b/spec/lib/open_food_network/enterprise_injection_data_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'open_food_network/enterprise_injection_data' + +RSpec.describe OpenFoodNetwork::EnterpriseInjectionData do + let(:enterprise1) { create :distributor_enterprise, with_payment_and_shipping: true } + let(:enterprise2) { create :distributor_enterprise, with_payment_and_shipping: true } + let(:enterprise3) { create :distributor_enterprise, with_payment_and_shipping: true } + let(:enterprise4) { create :distributor_enterprise, with_payment_and_shipping: true } + + before do + [enterprise1, enterprise2, enterprise3].each do |ent| + create :open_order_cycle, distributors: [ent] + end + end + + let!(:closed_oc) { create :closed_order_cycle, coordinator: enterprise4 } + + context "when scoped to specific enterprises" do + subject { + described_class.new([enterprise1.id, enterprise2.id]) + } + + describe "#active_distributor_ids" do + it "should include enterprise1.id and enterprise2.id" do + ids = subject.active_distributor_ids + expect(ids).to include enterprise1.id + expect(ids).to include enterprise2.id + expect(ids).not_to include enterprise3.id + end + end + end + + context "when unscoped to specific enterprises" do + let(:subject) { described_class.new } + + describe "#active_distributor_ids" do + it "should include all enterprise ids" do + ids = subject.active_distributor_ids + expect(ids).to include enterprise1.id + expect(ids).to include enterprise2.id + expect(ids).to include enterprise3.id + end + end + end +end diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index cbff22dc35..faa5d0583b 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -181,9 +181,10 @@ module OpenFoodNetwork scoper.scope v2 end - it "doesn't reduce variant's stock" do + it "reduces override stock, not variant's stock" do v2.move(-2) expect(Spree::Variant.find(v2.id).on_hand).to eq 5 + expect(v2.on_hand).to eq(-2) end end diff --git a/spec/lib/open_food_network/scope_variants_to_search_spec.rb b/spec/lib/open_food_network/scope_variants_for_search_spec.rb similarity index 98% rename from spec/lib/open_food_network/scope_variants_to_search_spec.rb rename to spec/lib/open_food_network/scope_variants_for_search_spec.rb index c8f6104ae5..fc16580d15 100644 --- a/spec/lib/open_food_network/scope_variants_to_search_spec.rb +++ b/spec/lib/open_food_network/scope_variants_for_search_spec.rb @@ -65,6 +65,11 @@ RSpec.describe OpenFoodNetwork::ScopeVariantsForSearch do let(:params) { { q: "product", distributor_id: d2.id } } it "returns all products distributed through that distributor" do + expect{ result }.to query_database [ + "Enterprise Load", + "VariantOverride Load", + "SQL" + ] expect(result).to include v4 expect(result).not_to include v1, v2, v3 end diff --git a/spec/lib/reports/orders_and_distributors_report_spec.rb b/spec/lib/reports/orders_and_distributors_report_spec.rb index 1b31eb68f9..3edccd5ff6 100644 --- a/spec/lib/reports/orders_and_distributors_report_spec.rb +++ b/spec/lib/reports/orders_and_distributors_report_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Reporting::Reports::OrdersAndDistributors::Base do bill_address.city, line_item.product.sku, line_item.product.name, - line_item.options_text, + line_item.unit_to_display, line_item.quantity, line_item.max_quantity, line_item.price * line_item.quantity, diff --git a/spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb index 4681387af4..ff73f6d12e 100644 --- a/spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb @@ -7,37 +7,88 @@ module Reporting module OrdersAndFulfillment RSpec.describe OrderCycleSupplierTotalsByDistributor do let!(:distributor) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) } - let!(:order) do - create(:completed_order_with_totals, line_items_count: 3, distributor:) + describe "as the distributor" do + let!(:order) do + create(:completed_order_with_totals, line_items_count: 3, distributor:) + end + let(:current_user) { distributor.owner } + let(:params) { { display_summary_row: true } } + let(:report) do + OrderCycleSupplierTotalsByDistributor.new(current_user, params) + end + + let(:report_table) do + report.table_rows + end + + it "generates the report" do + expect(report_table.length).to eq(6) + end + + it "has a variant row under the distributor" do + supplier = order.line_items.first.variant.supplier + expect(report.rows.first.producer).to eq supplier.name + expect(report.rows.first.hub).to eq distributor.name + end + + it "lists products sorted by name" do + order.line_items[0].variant.product.update(name: "Cucumber") + order.line_items[1].variant.product.update(name: "Apple") + order.line_items[2].variant.product.update(name: "Banane") + product_names = report.rows.map(&:product).filter(&:present?) + expect(product_names).to eq(["Apple", "Banane", "Cucumber"]) + end end - let(:current_user) { distributor.owner } - let(:params) { { display_summary_row: true } } - let(:report) do - OrderCycleSupplierTotalsByDistributor.new(current_user, params) - end + describe "as the supplier permitting products in the order cycle" do + let!(:order) { + create(:completed_order_with_totals, line_items_count: 0, distributor:, + order_cycle_id: order_cycle.id) + } + let(:supplier){ order.line_items.first.variant.supplier } - let(:report_table) do - report.table_rows - end + before do + 3.times do + owner = create(:user) + s = create(:supplier_enterprise, owner:) + variant = create(:variant, supplier: s) + create(:line_item_with_shipment, variant:, quantity: 1, order:) + end - it "generates the report" do - expect(report_table.length).to eq(6) - end + create(:enterprise_relationship, parent: supplier, child: distributor, + permissions_list: [:add_to_order_cycle]) + end - it "has a variant row under the distributor" do - supplier = order.line_items.first.variant.supplier - expect(report.rows.first.producer).to eq supplier.name - expect(report.rows.first.hub).to eq distributor.name - end + let(:current_user) { supplier.owner } + let(:params) { { display_summary_row: true } } + let(:report) do + OrderCycleSupplierTotalsByDistributor.new(current_user, params) + end - it "lists products sorted by name" do - order.line_items[0].variant.product.update(name: "Cucumber") - order.line_items[1].variant.product.update(name: "Apple") - order.line_items[2].variant.product.update(name: "Banane") - product_names = report.rows.map(&:product).filter(&:present?) - expect(product_names).to eq(["Apple", "Banane", "Cucumber"]) + let(:report_table) do + report.table_rows + end + + it "generates the report" do + expect(report_table.length).to eq(2) + end + + it "has a variant row under the distributor" do + expect(report.rows.first.producer).to eq supplier.name + expect(report.rows.first.hub).to eq distributor.name + end + + it "lists products sorted by name" do + order.line_items[0].variant.product.update(name: "Cucumber") + order.line_items[1].variant.product.update(name: "Apple") + order.line_items[2].variant.product.update(name: "Banane") + product_names = report.rows.map(&:product).filter(&:present?) + # only the supplier's variant is displayed + expect(product_names).to include("Cucumber") + expect(product_names).not_to include("Apple", "Banane") + end end end end diff --git a/spec/lib/tasks/simplecov_spec.rb b/spec/lib/tasks/simplecov_spec.rb new file mode 100644 index 0000000000..68e9d0bc5e --- /dev/null +++ b/spec/lib/tasks/simplecov_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rake' + +RSpec.describe "simplecov.rake" do + before(:all) do + Rake.application.rake_require("tasks/simplecov") + end + + describe "simplecov:collate_results" do + context "when there are reports to merge" do + let(:input_dir) { Rails.root.join("spec/fixtures/simplecov") } + + it "creates a new combined report" do + Dir.mktmpdir do |tmp_dir| + output_dir = File.join(tmp_dir, "output") + + expect { + Rake.application.invoke_task( + "simplecov:collate_results[#{input_dir},#{output_dir}]" + ) + }.to change { Dir.exist?(output_dir) }. + from(false). + to(true). + + and change { File.exist?(File.join(output_dir, "index.html")) }. + from(false). + to(true) + end + end + end + end +end diff --git a/spec/mailers/report_mailer_spec.rb b/spec/mailers/report_mailer_spec.rb index b23932d3a3..92bf7032c1 100644 --- a/spec/mailers/report_mailer_spec.rb +++ b/spec/mailers/report_mailer_spec.rb @@ -10,7 +10,7 @@ RSpec.describe ReportMailer do blob:, ).report_ready } - let(:blob) { ReportBlob.create!("customers.csv", "report content") } + let(:blob) { ReportBlob.create_locally!("customers.csv", "report content") } it "notifies about a report" do expect(email.subject).to eq "Report ready" diff --git a/spec/migrations/20240510033206_sanitize_enterprise_long_description_spec.rb b/spec/migrations/20240510033206_sanitize_enterprise_long_description_spec.rb new file mode 100644 index 0000000000..ee3d7b57ae --- /dev/null +++ b/spec/migrations/20240510033206_sanitize_enterprise_long_description_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative '../../db/migrate/20240510033206_sanitize_enterprise_long_description' + +RSpec.describe SanitizeEnterpriseLongDescription do + describe "#up" do + let!(:enterprise_nil_desc) { create(:enterprise, long_description: nil) } + let!(:enterprise_empty_desc) { create(:enterprise, long_description: "") } + let!(:enterprise_normal) { create(:enterprise, long_description: normal_desc) } + let!(:enterprise_bad) { + # The attribute is sanitised at assignment. So we need to inject into the + # database differently: + create(:enterprise).tap do |enterprise| + enterprise.update_columns(long_description: bad_desc) + end + } + let(:normal_desc) { + <<~HTML.squish +

𝐂𝐎̛𝐌 𝐓𝐀̂́𝐌 𝐂𝐇𝐔́ 𝐁𝐄́ is now available in Melbourne, everyone. 😂
+ <>>> The story is this is a person who loves to eat...

+ HTML + } + let(:normal_desc_sanitised) { + <<~HTML.squish +

𝐂𝐎̛𝐌 𝐓𝐀̂́𝐌 𝐂𝐇𝐔́ 𝐁𝐄́ is now available in Melbourne, everyone. 😂
+ <>>> The story is this is a person who loves to eat...

+ HTML + } + let(:bad_desc) { + <<~HTML.squish +

Fred Farmer is a certified organic + ...

+ HTML + } + let(:bad_desc_sanitised) { + "

Fred Farmer is a certified organic alert(\"Gotcha!\")...

" + } + + it "sanitises the long description" do + expect { subject.up }.to change { + enterprise_bad.reload.attributes["long_description"] + }.from(bad_desc).to(bad_desc_sanitised) + + expect(enterprise_nil_desc.reload.long_description).to eq nil + expect(enterprise_empty_desc.reload.long_description).to eq "" + expect(enterprise_normal.reload.attributes["long_description"]) + .to eq normal_desc_sanitised + end + end +end diff --git a/spec/models/enterprise_fee_spec.rb b/spec/models/enterprise_fee_spec.rb index e2881f3a05..769ab93fb0 100644 --- a/spec/models/enterprise_fee_spec.rb +++ b/spec/models/enterprise_fee_spec.rb @@ -47,7 +47,7 @@ RSpec.describe EnterpriseFee do ) expect(subject.save).to eq false expect(subject.errors.full_messages.first).to eq( - "Inheriting the tax categeory requires a per-item calculator." + "Inheriting the tax category requires a per-item calculator." ) end end diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index d3661313e4..de519e52e8 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -422,11 +422,6 @@ RSpec.describe Enterprise do subject.long_description = "Hello dearest monster." expect(subject.long_description).to eq "Hello alert dearest monster." end - - it "sanitises existing HTML in long_description" do - subject[:long_description] = "Hello dearest monster." - expect(subject.long_description).to eq "Hello alert dearest monster." - end end describe "callbacks" do diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 5c903f4985..873f6730ad 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -423,6 +423,30 @@ RSpec.describe OrderCycle do it "returns the earliest closing time" do expect(OrderCycle.earliest_closing_times[e2.id].round).to eq(time2.round) end + + context "when scoped by distributors" do + it "returns times for the given distributors" do + expect(OrderCycle.earliest_closing_times([e1.id])).to have_key e1.id + expect(OrderCycle.earliest_closing_times([e2.id])).to have_key e2.id + end + + it "doesn't return times for other distributors" do + expect(OrderCycle.earliest_closing_times([e1.id])).not_to have_key e2.id + expect(OrderCycle.earliest_closing_times([e2.id])).not_to have_key e1.id + end + + it "returns the correct values" do + expect(OrderCycle.earliest_closing_times([e1.id])[e1.id].round).to eq time1.round + expect(OrderCycle.earliest_closing_times([e2.id])[e2.id].round).to eq time2.round + end + end + + context "when not scoped by distributors" do + it "returns times for all distributors" do + expect(OrderCycle.earliest_closing_times).to have_key e1.id + expect(OrderCycle.earliest_closing_times).to have_key e2.id + end + end end describe "finding all line items sold by to a user by a given shop" do @@ -803,4 +827,31 @@ RSpec.describe OrderCycle do expect(order_cycle).not_to be_simple end end + + describe "same_datetime_value" do + it 'returns true when old and new values are nil' do + order_cycle = create(:order_cycle, orders_open_at: nil, orders_close_at: nil) + expect(order_cycle.same_datetime_value(:orders_open_at, nil)).to be_truthy + end + + it 'returns false if one value is nil and other not nil' do + order_cycle = create(:order_cycle, orders_open_at: "2024-09-30 06:09", orders_close_at: nil) + expect(order_cycle.same_datetime_value(:orders_open_at, nil)).to be_falsey + expect(order_cycle.same_datetime_value(:orders_close_at, "2024-09-30 06:09")).to be_falsey + end + + it 'returns true if either values are same' do + order_cycle = create(:order_cycle, orders_open_at: Time.zone.parse("2024-09-30 06:09"), + orders_close_at: Time.zone.parse("2024-11-30 06:09")) + expect(order_cycle.same_datetime_value(:orders_open_at, "2024-09-30 06:09")).to be_truthy + expect(order_cycle.same_datetime_value(:orders_close_at, "2024-11-30 06:09")).to be_truthy + end + + it 'returns false if either values are not same' do + order_cycle = create(:order_cycle, orders_open_at: Time.zone.parse("2024-09-30 06:09"), + orders_close_at: Time.zone.parse("2024-11-30 06:09")) + expect(order_cycle.same_datetime_value(:orders_open_at, "2024-10-30 06:09")).to be_falsey + expect(order_cycle.same_datetime_value(:orders_close_at, "2024-12-30 06:09")).to be_falsey + end + end end diff --git a/spec/models/report_blob_spec.rb b/spec/models/report_blob_spec.rb index 764925ba11..32cfd69a9d 100644 --- a/spec/models/report_blob_spec.rb +++ b/spec/models/report_blob_spec.rb @@ -7,8 +7,16 @@ RSpec.describe ReportBlob, type: :model do content = "This works. ✓" expect do - blob = ReportBlob.create!("customers.html", content) + blob = ReportBlob.create_locally!("customers.html", content) content = blob.result end.not_to change { content.encoding }.from(Encoding::UTF_8) end + + it "can be created first and filled later" do + blob = ReportBlob.create_for_upload_later!("customers.html") + + expect { blob.store("Hello") } + .to change { blob.checksum }.from("0") + .and change { blob.result }.from(nil).to("Hello") + end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index aa31506e30..c2b0ee3a50 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -97,22 +97,15 @@ RSpec.describe Spree::Ability do end end - context 'for Product' do - let(:resource) { Spree::Product.new } - context 'requested by any user' do - it_should_behave_like 'read only' - end - end - context 'for ProductProperty' do - let(:resource) { Spree::Product.new } + let(:resource) { Spree::ProductProperty.new } context 'requested by any user' do it_should_behave_like 'read only' end end context 'for Property' do - let(:resource) { Spree::Product.new } + let(:resource) { Spree::Property.new } context 'requested by any user' do it_should_behave_like 'read only' end @@ -153,13 +146,6 @@ RSpec.describe Spree::Ability do end end - context 'for Taxonomy' do - let(:resource) { Spree::Taxonomy.new } - context 'requested by any user' do - it_should_behave_like 'read only' - end - end - context 'for User' do context 'requested by same user' do let(:resource) { user } @@ -366,6 +352,20 @@ RSpec.describe Spree::Ability do is_expected.to have_ability(:create, for: Spree::Product) end + it "should be able to read/write their enterprises' products" do + is_expected.to have_ability( + [:admin, :read, :index, :update, :seo, :group_buy_options, :bulk_update, :clone, :delete, + :destroy], for: p1 + ) + end + + it "should not be able to read/write other enterprises' products" do + is_expected.not_to have_ability( + [:admin, :read, :index, :update, :seo, :group_buy_options, :bulk_update, :clone, :delete, + :destroy], for: p2 + ) + end + it "should be able to read/write their enterprises' product variants" do is_expected.to have_ability([:create], for: Spree::Variant) is_expected.to have_ability( @@ -422,7 +422,7 @@ RSpec.describe Spree::Ability do it "should be able to read some reports" do is_expected.to have_ability( - [:admin, :index, :show], for: Admin::ReportsController + [:admin, :index, :show, :create], for: Admin::ReportsController ) is_expected.to have_ability( [:customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory, @@ -658,7 +658,7 @@ RSpec.describe Spree::Ability do it "should be able to read some reports" do is_expected.to have_ability( - [:admin, :index, :show], for: Admin::ReportsController + [:admin, :index, :show, :create], for: Admin::ReportsController ) is_expected.to have_ability( [:customers, :sales_tax, :group_buys, :bulk_coop, :payments, diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index d715fec503..b6fecf1f33 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -313,8 +313,8 @@ module Spree expect(order.shipment.manifest.first.variant).to eq line_item.variant end - it "does not reduce the variant's stock level" do - expect(variant_on_demand.reload.on_hand).to eq 1 + it "reduces the variant's stock level" do + expect(variant_on_demand.reload.on_hand).to eq(-9) end it "does not mark inventory units as backorderd" do diff --git a/spec/models/spree/shipment_spec.rb b/spec/models/spree/shipment_spec.rb index a559d8b855..ecaba70205 100644 --- a/spec/models/spree/shipment_spec.rb +++ b/spec/models/spree/shipment_spec.rb @@ -264,6 +264,37 @@ RSpec.describe Spree::Shipment do end end + describe "#finalize!" do + subject(:shipment) { order.shipments.first } + let(:variant) { order.variants.first } + let(:order) { create(:order_ready_for_confirmation) } + + it "reduces stock" do + variant.on_hand = 5 + + expect { shipment.finalize! } + .to change { variant.on_hand }.from(5).to(4) + end + + it "reduces stock of a variant override" do + variant.on_hand = 5 + variant_override = VariantOverride.create!( + variant:, + hub: order.distributor, + count_on_hand: 7, + on_demand: false, + ) + + expect { + shipment.finalize! + variant.reload + variant_override.reload + } + .to change { variant_override.count_on_hand }.from(7).to(6) + .and change { variant.on_hand }.by(0) + end + end + context "when order is completed" do before do allow(order).to receive_messages completed?: true diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 49797f69f4..c699fd583f 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -2,102 +2,108 @@ require 'spec_helper' -module Spree - RSpec.describe Taxon do - let(:taxon) { Spree::Taxon.new(name: "Ruby on Rails") } +RSpec.describe Spree::Taxon do + let(:taxon) { described_class.new(name: "Ruby on Rails") } - let(:e) { create(:supplier_enterprise) } - let(:t1) { create(:taxon) } - let(:t2) { create(:taxon) } + let(:e) { create(:supplier_enterprise) } + let(:e2) { create(:supplier_enterprise) } + let(:t1) { create(:taxon) } + let(:t2) { create(:taxon) } - describe "finding all supplied taxons" do - let!(:p1) { - create(:simple_product, primary_taxon_id: t1.id, supplier_id: e.id) - } + describe ".supplied_taxons" do + let!(:p1) { + create(:simple_product, primary_taxon_id: t1.id, supplier_id: e.id) + } + let!(:p2) { + create(:simple_product, primary_taxon_id: t2.id, supplier_id: e2.id) + } + context "when scoped to specific enterprises" do it "finds taxons" do - expect(Taxon.supplied_taxons).to eq(e.id => Set.new([t1.id])) + expect(described_class.supplied_taxons([e.id])).to eq(e.id => Set.new([t1.id])) + expect(described_class.supplied_taxons([e2.id])).to eq(e2.id => Set.new([t2.id])) + expect(described_class.supplied_taxons([e.id, e2.id])).to eq( + e.id => Set.new([t1.id]), + e2.id => Set.new([t2.id]) + ) end end - describe "finding distributed taxons" do - let!(:oc_open) { - create(:open_order_cycle, distributors: [e], variants: [p_open.variants.first]) - } - let!(:oc_closed) { - create(:closed_order_cycle, distributors: [e], variants: [p_closed.variants.first]) - } - let!(:p_open) { create(:simple_product, primary_taxon: t1) } - let!(:p_closed) { create(:simple_product, primary_taxon: t2) } - - it "finds all distributed taxons" do - expect(Taxon.distributed_taxons(:all)).to eq(e.id => Set.new([t1.id, t2.id])) - end - - it "finds currently distributed taxons" do - expect(Taxon.distributed_taxons(:current)).to eq(e.id => Set.new([t1.id])) - end - end - - describe "touches" do - let!(:taxon1) { create(:taxon) } - let!(:taxon2) { create(:taxon) } - let!(:product) { create(:simple_product, primary_taxon_id: taxon1.id) } - let(:variant) { product.variants.first } - - it "is touched when assignment of primary_taxon on a variant changes" do - expect do - variant.update(primary_taxon: taxon2) - end.to change { taxon2.reload.updated_at } - end - end - - context "set_permalink" do - it "should set permalink correctly when no parent present" do - taxon.set_permalink - expect(taxon.permalink).to eq "ruby-on-rails" - end - - it "should support Chinese characters" do - taxon.name = "你好" - taxon.set_permalink - expect(taxon.permalink).to eq 'ni-hao' - end - - context "with parent taxon" do - before do - allow(taxon).to receive_messages parent_id: 123 - allow(taxon).to receive_messages parent: build_stubbed(:taxon, permalink: "brands") - end - - it "should set permalink correctly when taxon has parent" do - taxon.set_permalink - expect(taxon.permalink).to eq "brands/ruby-on-rails" - end - - it "should set permalink correctly with existing permalink present" do - taxon.permalink = "b/rubyonrails" - taxon.set_permalink - expect(taxon.permalink).to eq "brands/rubyonrails" - end - - it "should support Chinese characters" do - taxon.name = "我" - taxon.set_permalink - expect(taxon.permalink).to eq "brands/wo" - end - end - end - - # Regression test for Spree #2620 - context "creating a child node using first_or_create" do - let(:taxonomy) { create(:taxonomy) } - - it "does not error out" do - expect { - taxonomy.root.children.where(name: "Some name").first_or_create - }.not_to raise_error + context "when not scoped to specific enterprises" do + it "finds taxons" do + expect(described_class.supplied_taxons).to eq( + e.id => Set.new([t1.id]), + e2.id => Set.new([t2.id]) + ) end end end + + describe ".distributed_taxons" do + before do + [e, e2].each do |ent| + p_open = create(:simple_product, primary_taxon: t1) + p_closed = create(:simple_product, primary_taxon: t2) + create(:open_order_cycle, distributors: [ent], variants: [p_open.variants.first]) + create(:closed_order_cycle, distributors: [ent], variants: [p_closed.variants.first]) + end + end + + context "when scoped to specific enterprises" do + it "finds all distributed taxons" do + expect(described_class.distributed_taxons(:all, [e.id])).to eq( + e.id => Set.new([t1.id, t2.id]) + ) + expect(described_class.distributed_taxons(:all, [e2.id])).to eq( + e2.id => Set.new([t1.id, t2.id]) + ) + expect(described_class.distributed_taxons(:all, [e.id, e2.id])).to eq( + e.id => Set.new([t1.id, t2.id]), + e2.id => Set.new([t1.id, t2.id]), + ) + end + + it "finds currently distributed taxons" do + expect(described_class.distributed_taxons(:current, [e.id])).to eq( + e.id => Set.new([t1.id]) + ) + expect(described_class.distributed_taxons(:current, [e2.id])).to eq( + e2.id => Set.new([t1.id]) + ) + expect(described_class.distributed_taxons(:current, [e.id, e2.id])).to eq( + e.id => Set.new([t1.id]), + e2.id => Set.new([t1.id]), + ) + end + end + + context "when not scoped to specific enterprises" do + it "finds all distributed taxons" do + expect(described_class.distributed_taxons(:all)).to eq( + e.id => Set.new([t1.id, t2.id]), + e2.id => Set.new([t1.id, t2.id]), + ) + end + + it "finds currently distributed taxons" do + expect(described_class.distributed_taxons(:current)).to eq( + e.id => Set.new([t1.id]), + e2.id => Set.new([t1.id]), + ) + end + end + end + + describe "touches" do + let!(:taxon1) { create(:taxon) } + let!(:taxon2) { create(:taxon) } + let!(:product) { create(:simple_product, primary_taxon_id: taxon1.id) } + let(:variant) { product.variants.first } + + it "is touched when assignment of primary_taxon on a variant changes" do + expect do + variant.update(primary_taxon: taxon2) + end.to change { taxon2.reload.updated_at } + end + end end diff --git a/spec/models/spree/taxonomy_spec.rb b/spec/models/spree/taxonomy_spec.rb deleted file mode 100644 index 1ace138313..0000000000 --- a/spec/models/spree/taxonomy_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Spree::Taxonomy do - context "#destroy" do - before do - @taxonomy = create(:taxonomy) - @root_taxon = @taxonomy.root - @child_taxon = create(:taxon, taxonomy_id: @taxonomy.id, parent: @root_taxon) - end - - it "should destroy all associated taxons" do - @taxonomy.destroy - expect{ Spree::Taxon.find(@root_taxon.id) }.to raise_error(ActiveRecord::RecordNotFound) - expect{ Spree::Taxon.find(@child_taxon.id) }.to raise_error(ActiveRecord::RecordNotFound) - end - end -end diff --git a/spec/models/spree/variant_stock_spec.rb b/spec/models/spree/variant_stock_spec.rb new file mode 100644 index 0000000000..224fa3e855 --- /dev/null +++ b/spec/models/spree/variant_stock_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: false + +require 'spec_helper' + +RSpec.describe Spree::Variant do + # This method is defined in app/models/concerns/variant_stock.rb. + # There is a separate spec for that concern but here I want to test + # the interplay of Spree::Variant and VariantOverride. + # + # A variant can be scoped to a hub which means that all stock methods + # like this one get overridden. Future calls to `variant.move` are then + # handled by the ScopeVariantToHub module which may call the + # VariantOverride. + describe "#move" do + subject(:variant) { create(:variant, on_hand: 5) } + + it "changes stock" do + expect { variant.move(-2) }.to change { variant.on_hand }.from(5).to(3) + end + + it "reduces stock even when on demand" do + variant.on_demand = true + + expect { variant.move(-2) }.to change { variant.on_hand }.from(5).to(3) + end + + it "rejects negative stock" do + expect { variant.move(-7) }.to raise_error( + ActiveRecord::RecordInvalid, + "Validation failed: Count on hand must be greater than or equal to 0" + ) + end + + describe "with VariantOverride" do + subject(:hub_variant) { + Spree::Variant.find(variant.id).tap { |v| scoper.scope(v) } + } + let(:override) { + VariantOverride.create!( + variant:, + hub: create(:distributor_enterprise), + count_on_hand: 7, + on_demand: false, + ) + } + let(:scoper) { OpenFoodNetwork::ScopeVariantToHub.new(override.hub) } + + it "changes stock only on the variant override" do + expect { + hub_variant.move(-3) + override.reload + } + .to change { override.count_on_hand }.from(7).to(4) + .and change { hub_variant.on_hand }.from(7).to(4) + .and change { variant.on_hand }.by(0) + end + + it "reduces stock when on demand" do + override.update!(on_demand: true, count_on_hand: 7) + + expect { + hub_variant.move(-3) + override.reload + } + .to change { override.count_on_hand }.from(7).to(4) + .and change { hub_variant.on_hand }.from(7).to(4) + .and change { variant.on_hand }.by(0) + end + + it "doesn't prevent negative stock" do + # VariantOverride relies on other stock checks during checkout. :-( + expect { + hub_variant.move(-8) + override.reload + } + .to change { override.count_on_hand }.from(7).to(-1) + .and change { hub_variant.on_hand }.from(7).to(-1) + .and change { variant.on_hand }.by(0) + + # The update didn't run validations and now it's invalid: + expect(override).not_to be_valid + end + + it "doesn't fail on negative stock when on demand" do + override.update!(on_demand: true, count_on_hand: nil) + + expect { + hub_variant.move(-8) + override.reload + } + .to change { override.count_on_hand }.from(nil).to(-8) + .and change { hub_variant.on_hand }.from(nil).to(-8) + .and change { variant.on_hand }.by(0) + end + end + end +end diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index 15c9a1f4bf..41122e6e71 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -97,11 +97,8 @@ RSpec.describe VariantOverride do context "when count_on_hand is set" do let(:count_on_hand) { 1 } - it "is invalid" do - expect(variant_override).not_to be_valid - error_message = I18n.t("on_demand_but_count_on_hand_set", - scope: [i18n_scope_for_error, "count_on_hand"]) - expect(variant_override.errors[:count_on_hand]).to eq([error_message]) + it "is valid" do + expect(variant_override).to be_valid end end end @@ -168,7 +165,7 @@ RSpec.describe VariantOverride do describe "with nil count on hand" do let(:variant_override) do - build_stubbed( + build( :variant_override, variant: build_stubbed(:variant), hub: build_stubbed(:distributor_enterprise), @@ -178,15 +175,18 @@ RSpec.describe VariantOverride do end describe "stock_overridden?" do - it "returns false" do - expect(variant_override.stock_overridden?).to be false + it "returns true" do + expect(variant_override.stock_overridden?).to be true end end describe "move_stock!" do it "silently logs an error" do - expect(Bugsnag).to receive(:notify) - variant_override.move_stock!(5) + expect { + variant_override.move_stock!(5) + }.to change { + variant_override.count_on_hand + }.from(nil).to(5) end end end diff --git a/spec/queries/product_scope_query_spec.rb b/spec/queries/product_scope_query_spec.rb index 27f2ade0cf..24fa18ff99 100755 --- a/spec/queries/product_scope_query_spec.rb +++ b/spec/queries/product_scope_query_spec.rb @@ -13,7 +13,9 @@ RSpec.describe ProductScopeQuery do before { current_api_user.enterprise_roles.create(enterprise: supplier2) } describe '#bulk_products' do - let!(:product3) { create(:product, supplier_id: supplier2.id) } + let!(:product3) { + create(:product, supplier_id: supplier2.id, primary_taxon_id: create(:taxon).id) + } it "returns a list of products" do expect(ProductScopeQuery.new(current_api_user, {}).bulk_products) diff --git a/spec/requests/admin/products_v3_spec.rb b/spec/requests/admin/products_v3_spec.rb new file mode 100644 index 0000000000..69e5562e27 --- /dev/null +++ b/spec/requests/admin/products_v3_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Admin::ProductsV3" do + include AuthenticationHelper + + let(:user) { create(:user) } + let(:headers) { { Accept: "text/vnd.turbo-stream.html" } } + let(:product) { create(:simple_product, supplier_id: create(:supplier_enterprise).id) } + + before do + login_as user + end + + describe "DELETE /admin/product_v3/:id" do + it "checks for permission" do + delete(admin_product_destroy_path(product), headers: ) + + expect(response).to redirect_to('/unauthorized') + end + end + + describe "POST /admin/clone/:id" do + it "checks for permission" do + post(admin_clone_product_path(product), headers: ) + + expect(response).to redirect_to('/unauthorized') + end + end + + describe "DELETE /admin/product_v3/destroy_variant/:id" do + it "checks for permission" do + delete(admin_destroy_variant_path(product.variants.first), headers: ) + + expect(response).to redirect_to('/unauthorized') + end + end + + describe "POST /admin/products/bulk_update" do + it "checks for permission" do + variant = product.variants.first + + params = { + products: { + '0': { + id: product.id, + name: "Updated product name", + variants_attributes: { + '0': { + id: variant.id, + display_name: "Updated variant display name", + } + } + } + } + } + + post(admin_products_bulk_update_path, params:, headers: ) + + expect(response).to redirect_to('/unauthorized') + end + end +end diff --git a/spec/serializers/api/uncached_enterprise_serializer_spec.rb b/spec/serializers/api/uncached_enterprise_serializer_spec.rb new file mode 100644 index 0000000000..b86ed91955 --- /dev/null +++ b/spec/serializers/api/uncached_enterprise_serializer_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Api::UncachedEnterpriseSerializer do + let(:serializer) { + described_class.new enterprise, { data: OpenFoodNetwork::EnterpriseInjectionData.new } + } + let(:enterprise) { create :enterprise } + + before do + allow_any_instance_of(OpenFoodNetwork::EnterpriseInjectionData).to( + receive(:earliest_closing_times). + and_return(data) + ) + end + + describe '#orders_close_at' do + context "for an enterprise with an active order cycle" do + let(:order_cycle) { create :open_order_cycle, coordinator: enterprise } + let(:data) { { enterprise.id => order_cycle.orders_close_at } } + + it "returns a closing time for an enterprise" do + expect(serializer.orders_close_at).to eq order_cycle.orders_close_at + end + end + + context "for an enterprise without an active order cycle" do + let(:data) { {} } + + it "returns nil for an enterprise without a closing time" do + expect(serializer.orders_close_at).to be_nil + end + end + end +end diff --git a/spec/services/shops_list_service_spec.rb b/spec/services/shops_list_service_spec.rb index c3ae5e3ae7..4e6ea26a87 100644 --- a/spec/services/shops_list_service_spec.rb +++ b/spec/services/shops_list_service_spec.rb @@ -5,16 +5,19 @@ require "spec_helper" RSpec.describe ShopsListService do subject { described_class.new } before do - create_list :enterprise, 3, :with_logo_image, :with_promo_image - create_list :distributor_enterprise, 3, - :with_logo_image, - :with_promo_image, - with_payment_and_shipping: true + enterprises = create_list :enterprise, 3, :with_logo_image, :with_promo_image + distributors = create_list :distributor_enterprise, 3, + :with_logo_image, + :with_promo_image, + with_payment_and_shipping: true + create :distributor_order_cycle, distributors: [distributors[0]], suppliers: [enterprises[0]] end - let(:shop) { subject.open_shops.first } + let(:shop) { shops.first } describe "#open_shops" do + let(:shops) { subject.open_shops } + it "preloads promo images" do expect(shop.association(:promo_image_attachment).loaded?).to be true expect(shop.promo_image.association(:blob).loaded?).to be true @@ -24,10 +27,16 @@ RSpec.describe ShopsListService do expect(shop.association(:logo_attachment).loaded?).to be true expect(shop.logo.association(:blob).loaded?).to be true end + + it "only fetches enterprises with an active order cycle" do + open_enterprise_ids = Enterprise.distributors_with_active_order_cycles.pluck(:id).to_set + expect(open_enterprise_ids).not_to be_empty + expect(shops.pluck(:id)).to all be_in open_enterprise_ids + end end describe "#closed_shops" do - let(:shop) { subject.closed_shops.first } + let(:shops) { subject.closed_shops } it "preloads promo images" do expect(shop.association(:promo_image_attachment).loaded?).to be true @@ -38,5 +47,14 @@ RSpec.describe ShopsListService do expect(shop.association(:logo_attachment).loaded?).to be true expect(shop.logo.association(:blob).loaded?).to be true end + + it "fetches enterprises without active order cycles" do + open_enterprise_ids = Enterprise.distributors_with_active_order_cycles.pluck(:id).to_set + expect(open_enterprise_ids).not_to be_empty + + shops.pluck(:id).each do |shop_id| + expect(shop_id).not_to be_in open_enterprise_ids + end + end end end diff --git a/spec/support/reports_helper.rb b/spec/support/reports_helper.rb index d8f6df5f46..fda57daca0 100644 --- a/spec/support/reports_helper.rb +++ b/spec/support/reports_helper.rb @@ -12,4 +12,25 @@ module ReportsHelper expect(page).not_to have_selector ".loading" expect(page).to have_button "Go", disabled: false end + + def generate_report + run_report + click_on "Download Report" + wait_for_download + end + + def load_file_txt(extension, downloaded_filename) + case extension + when "csv" + CSV.read(downloaded_filename).join(" ") + when "xlsx" + xlsx = Roo::Excelx.new(downloaded_filename) + xlsx.map(&:to_a).join(" ") + end + end + + def table_headers + rows = find("table.report__table").all("thead tr") + rows.map { |r| r.all("th").map { |c| c.text.strip } } + end end diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index a5c4924a83..21f5159c07 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -88,6 +88,16 @@ module WebHelper find(:css, ".select2-result-label", text: options[:select_text] || value).click end + def clear_select2(selector) + page.find(selector).scroll_to(page.find(selector)) + .find(:css, '.select2-choice, .select2-search-field').click + page.find(selector).scroll_to(page.find(selector)) + .find(:css, '.select2-choice, .select2-search-field').send_keys :backspace + page.find(selector).scroll_to(page.find(selector)) + .find(:css, '.select2-choice, .select2-search-field').send_keys :backspace + find("body").send_keys(:escape) + end + def request_monitor_finished(controller = nil) page.evaluate_script("#{angular_scope(controller)}.scope().RequestMonitor.loading == false") end diff --git a/spec/system/admin/configuration/taxonomies_spec.rb b/spec/system/admin/configuration/taxonomies_spec.rb deleted file mode 100644 index 5f4771cbd6..0000000000 --- a/spec/system/admin/configuration/taxonomies_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'system_helper' - -RSpec.describe "Taxonomies" do - include AuthenticationHelper - include WebHelper - - before(:each) do - login_as_admin - visit spree.edit_admin_general_settings_path - end - - context "show" do - it "should display existing taxonomies" do - create(:taxonomy, name: 'Brand') - create(:taxonomy, name: 'Categories') - click_link "Taxonomies" - within("table.index tbody") do - expect(page).to have_content("Brand") - expect(page).to have_content("Categories") - end - end - end - - context "create" do - before(:each) do - click_link "Taxonomies" - click_link "admin_new_taxonomy_link" - end - - it "should allow an admin to create a new taxonomy" do - expect(page).to have_content("New Taxonomy") - fill_in "taxonomy_name", with: "sports" - click_button "Create" - expect(page).to have_content("successfully created!") - end - - it "should display validation errors" do - fill_in "taxonomy_name", with: "" - click_button "Create" - expect(page).to have_content("can't be blank") - end - end - - context "edit" do - it "should allow an admin to update an existing taxonomy" do - create(:taxonomy) - click_link "Taxonomies" - within_row(1) { find(".icon-edit").click } - fill_in "taxonomy_name", with: "sports 99" - sleep 1 - click_button "Update" - expect(page).to have_current_path spree.admin_taxonomies_path - expect(page).to have_content("successfully updated!") - expect(page).to have_content("sports 99") - end - end -end diff --git a/spec/system/admin/dfc_product_import_spec.rb b/spec/system/admin/dfc_product_import_spec.rb index dff70ff168..64ca6ce30c 100644 --- a/spec/system/admin/dfc_product_import_spec.rb +++ b/spec/system/admin/dfc_product_import_spec.rb @@ -53,7 +53,7 @@ RSpec.describe "DFC Product Import" do select enterprise.name, from: "Enterprise" - url = "https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com" + url = "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" fill_in "catalog_url", with: url expect { diff --git a/spec/system/admin/enterprise_fees_spec.rb b/spec/system/admin/enterprise_fees_spec.rb index 2c90e748e8..79e960280d 100644 --- a/spec/system/admin/enterprise_fees_spec.rb +++ b/spec/system/admin/enterprise_fees_spec.rb @@ -154,7 +154,7 @@ RSpec.describe ' # editing to an invalid combination select 'Flat Rate (per order)', from: "#{prefix}_calculator_type" expect{ click_button 'Update' }.not_to change { fee.reload.calculator_type } - expect(page).to have_content "Inheriting the tax categeory requires a per-item calculator." + expect(page).to have_content "Inheriting the tax category requires a per-item calculator." end end @@ -299,7 +299,7 @@ RSpec.describe ' end context "an error message is displayed" do - message = 'Inheriting the tax categeory requires a per-item calculator.' + message = 'Inheriting the tax category requires a per-item calculator.' it_behaves_like "setting it up", 'Inherit From Product', 'Flat Rate (per order)', message, 0 end diff --git a/spec/system/admin/invoice_print_spec.rb b/spec/system/admin/invoice_print_spec.rb index ef1ac6d405..3c9a6adb39 100644 --- a/spec/system/admin/invoice_print_spec.rb +++ b/spec/system/admin/invoice_print_spec.rb @@ -300,6 +300,21 @@ RSpec.describe ' expect(page).to have_content "Total (Excl. tax): $1,458.67" end end + + context "Line item with variant having variant_unit as 'items'" do + before do + line_item1.variant.update!(variant_unit: "items", display_as: "1 bucket") + login_as_admin + visit spree.print_admin_order_path(order1, params: url_params) + convert_pdf_to_page + end + + it 'should have correct display as value' do + # first line item + expect(page).to have_content Spree::Product.first.name.to_s + expect(page).to have_content "(1 bucket)" # display as + end + end end context "added" do @@ -535,10 +550,10 @@ RSpec.describe ' # header expect(page).to have_content "Item Qty" expect(page).to have_content "Weight / VOL." - expect(page).to have_content "Price Per unit (Excl. tax)" - expect(page).to have_content "Total price (Excl. tax)" + expect(page).to have_content "Price Per unit (Excl." + expect(page).to have_content "Total price (Excl." expect(page).to have_content "Tax rate" - expect(page).to have_content "Total price (Incl. tax)" + expect(page).to have_content "Total price (Incl." # first line item, no tax expect(page).to have_content Spree::Product.first.name.to_s expect(page).to have_content "($12,540.00 / kg)" # unit price @@ -639,10 +654,10 @@ RSpec.describe ' # header expect(page).to have_content "Item Qty" expect(page).to have_content "Weight / VOL." - expect(page).to have_content "Price Per unit (Excl. tax)" - expect(page).to have_content "Total price (Excl. tax)" + expect(page).to have_content "Price Per unit (Excl." + expect(page).to have_content "Total price (Excl." expect(page).to have_content "Tax rate" - expect(page).to have_content "Total price (Incl. tax)" + expect(page).to have_content "Total price (Incl." # first line item, no tax expect(page).to have_content Spree::Product.first.name.to_s expect(page).to have_content "($12,540.00 / kg)" # unit price diff --git a/spec/system/admin/order_cycles/complex_editing_multiple_updation_spec.rb b/spec/system/admin/order_cycles/complex_editing_multiple_updation_spec.rb index 66ce80f988..bb0cfcff78 100644 --- a/spec/system/admin/order_cycles/complex_editing_multiple_updation_spec.rb +++ b/spec/system/admin/order_cycles/complex_editing_multiple_updation_spec.rb @@ -111,9 +111,6 @@ RSpec.describe ' click_link 'Orders' end - # Click dismiss on distributor warning - click_button 'Dismiss' - # Click cancel with unsaved changes dismiss_confirm "" do click_button 'Cancel' @@ -168,9 +165,6 @@ RSpec.describe ' # Go to incoming step click_button 'Next' - # Click dismiss on distributor warning - click_button 'Dismiss' - # Go to outgoing step click_button 'Next' @@ -192,9 +186,6 @@ RSpec.describe ' click_link 'Orders' end - # Click dismiss on distributor warning - click_button 'Dismiss' - # Click cancel with unsaved changes dismiss_confirm "" do click_button 'Cancel' diff --git a/spec/system/admin/order_cycles/edit_spec.rb b/spec/system/admin/order_cycles/edit_spec.rb new file mode 100644 index 0000000000..adb91dee0e --- /dev/null +++ b/spec/system/admin/order_cycles/edit_spec.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require 'system_helper' + +RSpec.describe ' + As an administrator + I want to edit a specific order cycle +' do + include AdminHelper + include AuthenticationHelper + include WebHelper + + let(:order_cycle_attrs) { + { orders_open_at: "2024-03-01 08:00", orders_close_at: "2024-03-20 20:00", } + } + describe 'simple order cycle' do + let(:coordinator) { create(:distributor_enterprise, sells: 'own') } + let(:order_cycle) { + create(:simple_order_cycle, coordinator:, **order_cycle_attrs, suppliers: [coordinator], + distributors: [coordinator]) + } + + context 'with attached order(s)' do + let!(:order) { create(:order, order_cycle: ) } + + it "shows warning modal when datetime field values change" do + login_as_admin + visit edit_admin_order_cycle_path(order_cycle) + + # change non-date range field + fill_in 'order_cycle_name', with: "Order cycle name updated" + # Set Ready for value to enable save button + fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' + + expect(page).to have_content('You have unsaved changes') + click_button 'Save' + + expect(page).not_to have_content "Orders are linked to this order cycle" + expect(page).to have_field 'order_cycle_name', with: "Order cycle name updated" + expect(page).to have_content('Your order cycle has been updated.') + + # change date range field value + find('#order_cycle_orders_close_at').click + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + expect(page).to have_content('You have unsaved changes') + + # click save to open warning modal + click_button('Save') + expect(page).to have_content('You have unsaved changes') + expect(page).to have_content "Orders are linked to this order cycle." + + # confirm to close modal and update order cycle changed fields + click_button('Proceed anyway') + expect(page).not_to have_content "Orders are linked to this cycle" + expect(page).to have_field 'order_cycle_orders_close_at', with: '2024-03-30 00:00' + expect(page).to have_content('Your order cycle has been updated.') + end + + it "it redirects user to oc list when modal cancel is clicked" do + login_as_admin + visit edit_admin_order_cycle_path(order_cycle) + + # change date range field value + find('#order_cycle_orders_close_at').click + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + + # click save to open warning modal + click_button('Save') + expect(page).to have_content('You have unsaved changes') + expect(page).to have_content "Orders are linked to this order cycle." + + # Now cancel modal + within('.modal-actions') do + click_link('Cancel') + end + expect(page).not_to have_content('You have unsaved changes') + expect(page).to have_current_path admin_order_cycles_path + end + end + + context 'with no attached order' do + it "does not show warning modal", retry: 3 do + login_as_admin + visit edit_admin_order_cycle_path(order_cycle) + + # change non-date range field value + fill_in 'order_cycle_name', with: "OC1 name updated" + expect(page).to have_content('You have unsaved changes') + + # click save + click_button('Save') + expect(page).to have_field 'order_cycle_name', with: 'OC1 name updated' + expect(page).to have_content('Your order cycle has been updated.') + + # Now change date range field value + find('#order_cycle_orders_close_at').click + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + expect(page).to have_content('You have unsaved changes') + + click_button('Save') + expect(page).to have_field 'order_cycle_orders_close_at', with: '2024-03-30 00:00' + expect(page).to have_content('Your order cycle has been updated.') + end + end + end + + describe 'non simple order cycle' do + let(:coordinator) { create(:supplier_enterprise, sells: 'any') } + let(:order_cycle) { create(:simple_order_cycle, coordinator:, **order_cycle_attrs) } + + context 'with attached orders' do + let!(:order) { create(:order, order_cycle: ) } + + it "shows warning modal when datetime field values change" do + login_as_admin + visit edit_admin_order_cycle_path(order_cycle) + + # change non-date range field + fill_in 'order_cycle_name', with: "Order cycle name updated" + expect(page).to have_content('You have unsaved changes') + click_button 'Save' + expect(page).not_to have_content "Orders are linked to this order cycle" + expect(page).to have_content('Your order cycle has been updated.') + expect(page).to have_field 'order_cycle_name', with: "Order cycle name updated" + + # change date range field value + find('#order_cycle_orders_close_at').click + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + + expect(page).to have_content('You have unsaved changes') + + # click save to open warning modal + click_button('Save') + expect(page).to have_content('You have unsaved changes') + expect(page).to have_content "Orders are linked to this order cycle." + + # confirm to close modal and update order cycle changed fields + click_button('Proceed anyway') + expect(page).not_to have_content "Orders are linked to this cycle" + expect(page).to have_field 'order_cycle_orders_close_at', with: '2024-03-30 00:00' + expect(page).to have_content('Your order cycle has been updated.') + end + end + + context 'with no attached orders' do + it "does not show warning modal", retry: 3 do + login_as_admin + visit edit_admin_order_cycle_path(order_cycle) + + # change non-date range field value + fill_in 'order_cycle_name', with: "OC1 name updated" + expect(page).to have_content('You have unsaved changes') + sleep(2) + + # click save + click_button('Save') + expect(page).to have_content('Your order cycle has been updated.') + expect(page.find('#order_cycle_name').value).to eq 'OC1 name updated' + + # Now change date range field value + find('#order_cycle_orders_close_at').click + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + expect(page).to have_content('You have unsaved changes') + sleep(2) + + click_button('Save') + expect(page).to have_content('Your order cycle has been updated.') + expect(page).not_to have_content "Orders are linked to this cycle" + expect(page).to have_field 'order_cycle_orders_close_at', with: '2024-03-30 00:00' + end + end + end +end diff --git a/spec/system/admin/order_cycles/list_spec.rb b/spec/system/admin/order_cycles/list_spec.rb index eddb8a6d6c..0d16945a79 100644 --- a/spec/system/admin/order_cycles/list_spec.rb +++ b/spec/system/admin/order_cycles/list_spec.rb @@ -200,6 +200,56 @@ RSpec.describe ' end end end + describe 'updating order cycles' do + let!(:order_cycle) { create(:simple_order_cycle) } + before(:each) do + login_as_admin + visit admin_order_cycles_path + end + + context 'with attached order cycles' do + let!(:order) { create(:order, order_cycle: ) } + it('renders warning modal with datetime value changed') do + within("tr.order-cycle-#{order_cycle.id}") do + find('input.datetimepicker', match: :first).click + end + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + expect(page).to have_content('You have unsaved changes') + + # click save to open warning modal + click_button('Save') + expect(page).to have_content('You have unsaved changes') + expect(page).to have_content "Orders are linked to this order cycle." + + # confirm to close modal and update order cycle changed fields + click_button('Proceed anyway') + expect(page).not_to have_content "Orders are linked to this cycle" + expect(page).to have_content('Order cycles have been updated.') + end + end + + context 'with no attached order cycles' do + it('renders warnig modal with datetime value changed') do + within("tr.order-cycle-#{order_cycle.id}") do + find('input.datetimepicker', match: :first).click + end + within(".flatpickr-calendar.open") do + expect(page).to have_selector '.shortcut-buttons-flatpickr-buttons' + select_datetime_from_datepicker Time.zone.parse("2024-03-30 00:00") + find("button", text: "Close").click + end + expect(page).to have_content('You have unsaved changes') + + click_button('Save') + expect(page).not_to have_content "Orders are linked to this order cycle." + expect(page).to have_content('Order cycles have been updated.') + end + end + end private diff --git a/spec/system/admin/order_spec.rb b/spec/system/admin/order_spec.rb index 90c5028f39..685e1174cb 100644 --- a/spec/system/admin/order_spec.rb +++ b/spec/system/admin/order_spec.rb @@ -778,7 +778,6 @@ RSpec.describe ' visit spree.edit_admin_order_path(order) expect(page).not_to have_content different_shipping_method_for_distributor1.name - click_button 'Dismiss' find('.edit-method').click @@ -1099,8 +1098,6 @@ RSpec.describe ' expect(page).to have_selector 'h1', text: 'Customer Details' click_link "Order Details" - click_button 'Dismiss' - expect(page).to have_content 'Add Product' select2_select product.name, from: 'add_variant_id', search: true @@ -1111,8 +1108,6 @@ RSpec.describe ' expect(page).to have_select2 'order_distributor_id', with_options: [distributor1.name] expect(page).not_to have_select2 'order_distributor_id', with_options: [distributor2.name] - click_button 'Dismiss' - expect(page).to have_select2 'order_order_cycle_id', with_options: ["#{order_cycle1.name} (open)"] expect(page).not_to have_select2 'order_order_cycle_id', diff --git a/spec/system/admin/overview_spec.rb b/spec/system/admin/overview_spec.rb index 47df19512e..a6ca4d1ed3 100644 --- a/spec/system/admin/overview_spec.rb +++ b/spec/system/admin/overview_spec.rb @@ -117,6 +117,24 @@ RSpec.describe ' text: "MANAGE ORDER CYCLES" end end + + context "with open order cycles of distributors not ready for checkout" do + let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1]) } + + it 'should only display the order cycle warning once after login' do + # First visit the page after login + visit spree.admin_dashboard_path + expected_oc_warning = I18n.t( + :active_distributors_not_ready_for_checkout_message_singular, + distributor_names: d1.name + ) + expect(page).to have_content(expected_oc_warning) + + # Reload the page + visit spree.admin_dashboard_path + expect(page).not_to have_content(expected_oc_warning) + end + end end end end diff --git a/spec/system/admin/product_import_spec.rb b/spec/system/admin/product_import_spec.rb index e916a56a74..49c9069918 100644 --- a/spec/system/admin/product_import_spec.rb +++ b/spec/system/admin/product_import_spec.rb @@ -624,33 +624,6 @@ RSpec.describe "Product Import" do expect(page).not_to have_content "line 3: Sprouts" end - it "handles on_demand and on_hand validations with inventory - With both values set" do - csv_data = <<~CSV - name, distributor, producer, category, on_hand, price, units, on_demand - Beans, Another Enterprise, User Enterprise, Vegetables, 6, 3.20, 500, 1 - Sprouts, Another Enterprise, User Enterprise, Vegetables, 6, 6.50, 500, 1 - Cabbage, Another Enterprise, User Enterprise, Vegetables, 0, 1.50, 500, 1 - CSV - File.write('/tmp/test.csv', csv_data) - - visit main_app.admin_product_import_path - select 'Inventories', from: "settings_import_into" - attach_file 'file', '/tmp/test.csv' - click_button 'Upload' - - proceed_to_validation - - expect(page).to have_selector '.item-count', text: "3" - expect(page).to have_selector '.invalid-count', text: "3" - - find('div.header-description', text: 'Items contain errors').click - expect(page).to have_content "line 2: Beans - Count_on_hand must be blank if on demand" - expect(page).to have_content "line 3: Sprouts - Count_on_hand must be blank if on demand" - expect(page).to have_content "line 4: Cabbage - Count_on_hand must be blank if on demand" - expect(page).to have_content "Imported file contains invalid entries" - expect(page).not_to have_selector 'input[type=submit][value="Save"]' - end - it "imports lines with all allowed units" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", diff --git a/spec/system/admin/products_spec.rb b/spec/system/admin/products_spec.rb index 9fad202f79..325d48677b 100644 --- a/spec/system/admin/products_spec.rb +++ b/spec/system/admin/products_spec.rb @@ -184,7 +184,7 @@ RSpec.describe ' click_button 'Create' expect(current_path).to eq spree.admin_products_path - expect(page).to have_content "Product Category must exist" + expect(page).to have_content "Product Category can't be blank" end it "creating product with empty product supplier fails" do @@ -201,7 +201,7 @@ RSpec.describe ' click_button 'Create' expect(current_path).to eq spree.admin_products_path - expect(page).to have_content "Supplier must exist" + expect(page).to have_content "Supplier can't be blank" end describe "localization settings" do @@ -309,69 +309,66 @@ RSpec.describe ' describe "editing page" do let!(:product) { create(:simple_product, name: 'a product', supplier_id: supplier2.id) } + describe "'Back To Products List' and 'Cancel' buttons" do + context "navigates to edit from the bulk product update page with searched results" do + it "should navigate back to the same searched results page" do + # Navigating to a searched URL + visit admin_products_url({ + page: 1, + per_page: 25, + search_term: 'product', + producer_id: supplier2.id + }) + + products_page_url = current_url + within row_containing_name('a product') do + page.find(".vertical-ellipsis-menu").click + click_link('Edit', href: spree.edit_admin_product_path(product)) + end + + expect(page).to have_link('Back To Products List', + href: products_page_url) + expect(page).to have_link('Cancel', + href: products_page_url) + end + end + + context "directly navigates to the edit page" do + it "should navigate back to all the products page" do + # Navigating to a searched URL + visit spree.edit_admin_product_path(product) + + expect(page).to have_link('Back To Products List', + href: admin_products_url) + expect(page).to have_link('Cancel', + href: admin_products_url) + end + end + end + it "editing a product" do + login_as_admin visit spree.edit_admin_product_path product fill_in_trix_editor 'product_description', with: 'A description...' + click_button 'Update' + expect(flash_message).to eq('Product "a product" has been successfully updated!') product.reload expect(product.description).to eq("
A description...
") - end - it "editing a product comming from the bulk product update page with filter" do - visit spree.edit_admin_product_path(product, filter) + # Product preview + click_link 'Preview' - click_button 'Update' - expect(flash_message).to eq('Product "a product" has been successfully updated!') + within "#product-preview-modal" do + expect(page).to have_content("Product preview") + expect(page).to have_selector("h3 a span", text: "a product") - # Check the url still includes the filters - uri = URI.parse(current_url) - expect("#{uri.path}?#{uri.query}").to eq spree.edit_admin_product_path(product, filter) + click_button "Close" + end - # Link back to the bulk product update page should include the filters - expected_admin_product_url = - Regexp.new(Regexp.escape("#{spree.admin_products_path}#?#{filter.to_query}")) - expect(page).to have_link('Back to products list', - href: expected_admin_product_url) - expect(page).to have_link('Cancel', href: expected_admin_product_url) - - expected_product_url = Regexp.new(Regexp.escape(spree.edit_admin_product_path( - product.id, filter - ))) - expect(page).to have_link('Product Details', - href: expected_product_url) - - expected_product_image_url = Regexp.new(Regexp.escape(spree.admin_product_images_path( - product.id, filter - ))) - expect(page).to have_link('Images', - href: expected_product_image_url) - - expected_product_variant_url = Regexp.new(Regexp.escape(spree.admin_product_variants_path( - product.id, filter - ))) - expect(page).to have_link('Variants', - href: expected_product_variant_url) - - expected_product_properties_url = - Regexp.new(Regexp.escape(spree.admin_product_product_properties_path( - product.id, filter - ))) - expect(page).to have_link('Product Properties', - href: expected_product_properties_url) - - expected_product_group_buy_option_url = - Regexp.new(Regexp.escape(spree.group_buy_options_admin_product_path( - product.id, filter - ))) - expect(page).to have_link('Group Buy Options', - href: expected_product_group_buy_option_url) - - expected_product_seo_url = Regexp.new(Regexp.escape(spree.seo_admin_product_path( - product.id, filter - ))) - expect(page).to have_link('Search', href: expected_product_seo_url) + expect(page).not_to have_content("Product preview") end it "editing product group buy options" do diff --git a/spec/system/admin/products_v3/actions_spec.rb b/spec/system/admin/products_v3/actions_spec.rb index e864218c1d..74ce58cbe0 100644 --- a/spec/system/admin/products_v3/actions_spec.rb +++ b/spec/system/admin/products_v3/actions_spec.rb @@ -34,42 +34,74 @@ RSpec.describe 'As an enterprise user, I can manage my products' do describe "column selector" do let!(:product) { create(:simple_product) } - before do - visit admin_products_url + context "with one producer only" do + before do + visit admin_products_url + end + + it "hides column and remembers saved preference" do + # Name shows by default + expect(page).to have_checked_field "Name" + expect(page).to have_selector "th", text: "Name" + expect_other_columns_visible + + # Producer is hidden by if only one producer is present + expect(page).to have_unchecked_field "Producer" + expect(page).not_to have_selector "th", text: "Producer" + + # Show Producer column + ofn_drop_down("Columns").click + within ofn_drop_down("Columns") do + check "Producer" + end + + # Preference saved + save_preferences + expect(page).to have_selector "th", text: "Producer" + + # Name is hidden + ofn_drop_down("Columns").click + within ofn_drop_down("Columns") do + uncheck "Name" + end + expect(page).not_to have_selector "th", text: "Name" + expect_other_columns_visible + + # Preference saved + save_preferences + + # Preference remembered + ofn_drop_down("Columns").click + within ofn_drop_down("Columns") do + expect(page).to have_unchecked_field "Name" + end + expect(page).not_to have_selector "th", text: "Name" + expect_other_columns_visible + end + + def expect_other_columns_visible + expect(page).to have_selector "th", text: "Price" + expect(page).to have_selector "th", text: "On Hand" + end + + def save_preferences + # Preference saved + click_on "Save as default" + expect(page).to have_content "Column preferences saved" + refresh + end end - it "hides column and remembers saved preference" do - # Name shows by default - expect(page).to have_checked_field "Name" - expect(page).to have_selector "th", text: "Name" - expect_other_columns_visible + context "with multiple producers" do + let!(:producer2) { create(:supplier_enterprise, owner: user) } - # Name is hidden - ofn_drop_down("Columns").click - within ofn_drop_down("Columns") do - uncheck "Name" + before { visit admin_products_url } + + it "has selected producer column by default" do + # Producer shows by default + expect(page).to have_checked_field "Producer" + expect(page).to have_selector "th", text: "Producer" end - expect(page).not_to have_selector "th", text: "Name" - expect_other_columns_visible - - # Preference saved - click_on "Save as default" - expect(page).to have_content "Column preferences saved" - refresh - - # Preference remembered - ofn_drop_down("Columns").click - within ofn_drop_down("Columns") do - expect(page).to have_unchecked_field "Name" - end - expect(page).not_to have_selector "th", text: "Name" - expect_other_columns_visible - end - - def expect_other_columns_visible - expect(page).to have_selector "th", text: "Producer" - expect(page).to have_selector "th", text: "Price" - expect(page).to have_selector "th", text: "On Hand" end end @@ -202,6 +234,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do page.find(".vertical-ellipsis-menu").click expect(page).to have_link "Edit", href: spree.edit_admin_product_path(product_a) end + close_action_menu within row_containing_name("Medium box") do page.find(".vertical-ellipsis-menu").click @@ -232,6 +265,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do page.find(".vertical-ellipsis-menu").click expect(page).to have_link "Clone", href: admin_clone_product_path(product_a) end + close_action_menu within row_containing_name("Medium box") do page.find(".vertical-ellipsis-menu").click @@ -351,11 +385,12 @@ RSpec.describe 'As an enterprise user, I can manage my products' do page.find(".vertical-ellipsis-menu").click page.find(delete_option_selector).click end + within modal_selector do click_button "Keep product" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content "Delete Product" expect(page).to have_selector(product_selector) # Keep Variant @@ -367,7 +402,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do click_button "Keep variant" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content("Delete Variant") expect(page).to have_selector(variant_selector) end end @@ -387,7 +422,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do click_button "Delete variant" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content("Delete variant") expect(page).not_to have_selector(variant_selector) within success_flash_message_selector do expect(page).to have_content("Successfully deleted the variant") @@ -402,7 +437,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do within modal_selector do click_button "Delete product" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content("Delete product") expect(page).not_to have_selector(product_selector) within success_flash_message_selector do expect(page).to have_content("Successfully deleted the product") @@ -472,6 +507,39 @@ RSpec.describe 'As an enterprise user, I can manage my products' do end end end + + describe "Preview" do + let(:product) { create(:product, name: "Apples") } + let!(:variant) { create(:variant, product:) } + + it "show product preview modal" do + visit admin_products_url + + within row_containing_name("Apples") do + open_action_menu + click_link "Preview" + end + + expect(page).to have_content("Product preview") + + within "#product-preview-modal" do + # Shop tab + expect(page).to have_selector("h3", text: "Apples") + add_buttons = page.all(".add-variant") + expect(add_buttons.length).to eql(2) + + # Product Details tab + find("a", text: "Product details").click # click_link doesn't work + expect(page).to have_selector("h3", text: "Apples") + expect(page).to have_selector(".product-img") + + # Closing the modal + click_button "Close" + end + + expect(page).not_to have_content("Product preview") + end + end end context "as an enterprise manager" do @@ -557,4 +625,12 @@ RSpec.describe 'As an enterprise user, I can manage my products' do expect(page).to have_selector row_containing_name("Pommes") end end + + def open_action_menu + page.find(".vertical-ellipsis-menu").click + end + + def close_action_menu + page.find("div#content").click + end end diff --git a/spec/system/admin/products_v3/create_spec.rb b/spec/system/admin/products_v3/create_spec.rb index ce3b1502cf..3f44d09c36 100644 --- a/spec/system/admin/products_v3/create_spec.rb +++ b/spec/system/admin/products_v3/create_spec.rb @@ -7,6 +7,9 @@ RSpec.describe 'As an enterprise user, I can manage my products' do include WebHelper let!(:supplier) { create(:supplier_enterprise) } + # Creating another producer such that producer column is visible + # otherwise on one producer, it's hidden by default + let!(:supplier2) { create(:supplier_enterprise) } let!(:taxon) { create(:taxon) } describe "creating a new product" do diff --git a/spec/system/admin/products_v3/index_spec.rb b/spec/system/admin/products_v3/index_spec.rb index f19172894a..15990c3460 100644 --- a/spec/system/admin/products_v3/index_spec.rb +++ b/spec/system/admin/products_v3/index_spec.rb @@ -36,7 +36,6 @@ RSpec.describe 'As an enterprise user, I can manage my products' do expect(page).to have_selector "th", text: "Unit" expect(page).to have_selector "th", text: "Price" expect(page).to have_selector "th", text: "On Hand" - expect(page).to have_selector "th", text: "Producer" expect(page).to have_selector "th", text: "Category" expect(page).to have_selector "th", text: "Tax Category" expect(page).to have_selector "th", text: "Inherits Properties?" @@ -197,7 +196,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "searchable product" expect(page).to have_field "search_term", with: "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -216,7 +215,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "searchable product" expect(page).to have_field "search_term", with: "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -229,7 +228,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "Big box" expect(page).to have_field "search_term", with: "Big box" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -246,7 +245,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do expect_per_page_to_be 15 expect_products_count_to_be 1 search_for "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -256,7 +255,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "searchable product" expect(page).to have_field "search_term", with: "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 expect(page).to have_field "Name", with: product_by_name.name @@ -348,7 +347,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_by_category "Category 1" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect(page).to have_select "category_id", selected: "Category 1" expect_products_count_to_be 1 expect(page).to have_field "Name", with: product_by_category.name diff --git a/spec/system/admin/products_v3/update_spec.rb b/spec/system/admin/products_v3/update_spec.rb index fec4236177..57245ecb6b 100644 --- a/spec/system/admin/products_v3/update_spec.rb +++ b/spec/system/admin/products_v3/update_spec.rb @@ -9,7 +9,8 @@ RSpec.describe 'As an enterprise user, I can update my products' do include FileHelper let(:producer) { create(:supplier_enterprise) } - let(:user) { create(:user, enterprises: [producer]) } + let(:producer2) { create(:supplier_enterprise) } + let(:user) { create(:user, enterprises: [producer, producer2]) } before do login_as user diff --git a/spec/system/admin/reports/enterprise_fee_summaries_spec.rb b/spec/system/admin/reports/enterprise_fee_summaries_spec.rb index 4d1b70fee7..3bbed9a354 100644 --- a/spec/system/admin/reports/enterprise_fee_summaries_spec.rb +++ b/spec/system/admin/reports/enterprise_fee_summaries_spec.rb @@ -5,6 +5,7 @@ require "system_helper" RSpec.describe "enterprise fee summaries" do include AuthenticationHelper include WebHelper + include ReportsHelper let!(:distributor) { create(:distributor_enterprise) } let!(:other_distributor) { create(:distributor_enterprise) } @@ -78,61 +79,48 @@ RSpec.describe "enterprise fee summaries" do end end - describe "csv downloads" do + describe "permissions" do describe "smoke test for generation of report based on permissions" do + let!(:order) do + create(:completed_order_with_fees, order_cycle:, + distributor:) + end + let!(:other_order) do + create(:completed_order_with_fees, order_cycle: other_order_cycle, + distributor: other_distributor) + end context "when logged in as admin" do - let!(:order) do - create(:completed_order_with_fees, order_cycle:, - distributor:) - end - let(:current_user) { create(:admin_user) } + let!(:current_user) { create(:admin_user) } before do visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') end it "generates file with data for all enterprises" do - select "CSV" - click_on "Go" - perform_enqueued_jobs(only: ReportJob) - click_on "Download Report" - expect(downloaded_filename).to include ".csv" - expect(downloaded_content).to have_content(distributor.name) + run_report + expect(page).to have_content(distributor.name) + expect(page).to have_content(other_distributor.name) end end context "when logged in as enterprise user" do - let!(:order) do - create(:completed_order_with_fees, order_cycle:, - distributor:) - end - let!(:other_order) do - create(:completed_order_with_fees, order_cycle: other_order_cycle, - distributor: other_distributor) - end - let(:current_user) { distributor.owner } + let!(:current_user) { distributor.owner } before do visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') end it "generates file with data for the enterprise" do - select "CSV" - click_on "Go" - perform_enqueued_jobs(only: ReportJob) - click_on "Download Report" - - expect(downloaded_filename).to include ".csv" - expect(downloaded_content).to have_content(distributor.name) - expect(downloaded_content).not_to have_content(other_distributor.name) + run_report + expect(page).to have_content(distributor.name) + expect(page).not_to have_content(other_distributor.name) end end end - describe "smoke test for filtering report based on filters" do + describe "downloading the report" do let!(:second_distributor) { create(:distributor_enterprise) } let!(:second_order_cycle) { create(:simple_order_cycle, coordinator: second_distributor) } - let!(:order) do create(:completed_order_with_fees, order_cycle:, distributor:) @@ -146,22 +134,29 @@ RSpec.describe "enterprise fee summaries" do before do visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') - end - - it "generates file with data for selected order cycle" do find("#s2id_q_order_cycle_ids").click select order_cycle.name - - find("#report_format").click - select "CSV" - click_on "Go" - perform_enqueued_jobs(only: ReportJob) - click_on "Download Report" - - expect(downloaded_filename).to include ".csv" - expect(downloaded_content).to have_content(distributor.name) - expect(downloaded_content).not_to have_content(second_distributor.name) end + + shared_examples "reports generated as" do |output_type, extension| + context output_type.to_s do + it "downloads the #{output_type} file" do + select output_type, from: "report_format" + + expect { generate_report }.to change { downloaded_filenames.length }.from(0).to(1) + + expect(downloaded_filename).to match(/.*\.#{extension}/) + + downloaded_file_txt = load_file_txt(extension, downloaded_filename) + + expect(downloaded_file_txt).to have_content(distributor.name) + expect(downloaded_file_txt).not_to have_content(second_distributor.name) + end + end + end + + it_behaves_like "reports generated as", "CSV", "csv" + it_behaves_like "reports generated as", "Spreadsheet", "xlsx" end end diff --git a/spec/system/admin/reports/orders_and_distributors_spec.rb b/spec/system/admin/reports/orders_and_distributors_spec.rb new file mode 100644 index 0000000000..3ee051923b --- /dev/null +++ b/spec/system/admin/reports/orders_and_distributors_spec.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +require "system_helper" + +RSpec.describe "Orders And Distributors" do + include AuthenticationHelper + include WebHelper + include ReportsHelper + + describe "Orders And Distributors" do + let!(:report_url) { admin_report_path(report_type: :orders_and_distributors) } + let!(:distributor) { create(:distributor_enterprise, name: "By Bike") } + let!(:distributor2) { create(:distributor_enterprise, name: "By Moto") } + let!(:completed_at) { Time.zone.now.to_fs(:db) } + + around do |example| + Timecop.travel(completed_at) { example.run } + end + + let!(:order) { + create(:order_ready_to_ship, distributor_id: distributor.id, completed_at:) + } + let!(:order2) { + create(:order_ready_to_ship, distributor_id: distributor2.id, completed_at:) + } + + context "as an enterprise user" do + let(:header) { + ["Order date", "Order Id", "Customer Name", "Customer Email", "Customer Phone", + "Customer City", "SKU", "Item name", "Variant", "Quantity", "Max Quantity", + "Cost", "Shipping Cost", "Payment Method", "Distributor", "Distributor address", + "Distributor city", "Distributor postcode", "Shipping Method", + "Shipping instructions"] + } + let(:line_item1) { + [completed_at, order.id, "John Doe", order.email, "123-456-7890", "Herndon", + "ABC", Spree::Product.first.name.to_s, "1g", "1", "none", "10.0", "none", "Check", + "By Bike", "10 Lovely Street", "Herndon", "20170", "UPS Ground", "none"].join(" ") + } + let(:line_item2) { + [completed_at, order.id, "John Doe", order.email, "123-456-7890", "Herndon", + "ABC", Spree::Product.first.name.to_s, "1g", "1", "none", "10.0", "none", "Check", + "By Bike", "10 Lovely Street", "Herndon", "20170", "UPS Ground", "none"].join(" ") + } + let(:line_item3) { + [completed_at.to_s, order.id, "John Doe", order.email, "123-456-7890", "Herndon", + "ABC", Spree::Product.first.name.to_s, "1g", "1", "none", "10.0", "none", "Check", + "By Bike", "10 Lovely Street", "Herndon", "20170", "UPS Ground", "none"].join(" ") + } + let(:line_item4) { + [completed_at.to_s, order.id, "John Doe", order.email, "123-456-7890", "Herndon", + "ABC", Spree::Product.first.name.to_s, "1g", "1", "none", "10.0", "none", "Check", + "By Bike", "10 Lovely Street", "Herndon", "20170", "UPS Ground", "none"].join(" ") + } + let(:line_item5) { + [completed_at.to_s, order.id, "John Doe", order.email, "123-456-7890", "Herndon", + "ABC", Spree::Product.first.name.to_s, "1g", "1", "none", "10.0", "none", "Check", + "By Bike", "10 Lovely Street", "Herndon", "20170", "UPS Ground", "none"].join(" ") + } + + before do + login_as(distributor.owner) + visit report_url + run_report + end + + it "generates the report" do + expect(table_headers).to eq([header]) + + # Total rows should equal nr. of line items, per order + expect(all('table.report__table tbody tr').count).to eq(5) + + # displays only orders from the hub it is managing + within ".report__table" do + expect(page).to have_content(distributor.name, count: 5) + end + + # only sees line items from orders it manages + expect(page).not_to have_content(distributor2.name) + + # displayes table contents correctly, per line item + table = page.find("table.report__table tbody") + expect(table).to have_content(line_item1) + expect(table).to have_content(line_item2) + expect(table).to have_content(line_item3) + expect(table).to have_content(line_item4) + expect(table).to have_content(line_item5) + end + + describe "downloading the report" do + shared_examples "reports generated as" do |output_type, extension| + context output_type.to_s do + it "downloads the #{output_type} file" do + select output_type, from: "report_format" + + expect { generate_report }.to change { downloaded_filenames.length }.from(0).to(1) + + expect(downloaded_filename).to match(/.*\.#{extension}/) + + downloaded_file_txt = load_file_txt(extension, downloaded_filename) + + expect(downloaded_file_txt).to have_text header.join(" ") + expect(downloaded_file_txt).to have_text( + "By Bike 10 Lovely Street Herndon 20170 UPS Ground", count: 5 + ) + end + end + end + + it_behaves_like "reports generated as", "CSV", "csv" + it_behaves_like "reports generated as", "Spreadsheet", "xlsx" + end + end + + context "as admin" do + before do + login_as_admin + visit report_url + run_report + end + + context "with two orders on the same day at different times" do + let(:completed_at1) { 1500.hours.ago } # 1500 hours in the past + let(:completed_at2) { 1700.hours.ago } # 1700 hours in the past + let(:datetime_start1) { 1600.hours.ago } # 1600 hours in the past + let(:datetime_start2) { 1800.hours.ago } # 1600 hours in the past + let(:datetime_end) { 1400.hours.ago } # 1400 hours in the past + let!(:order3) { + create(:order_ready_to_ship, distributor_id: distributor.id, completed_at: completed_at1) + } + let!(:order4) { + create(:order_ready_to_ship, distributor_id: distributor.id, completed_at: completed_at2) + } + + context "applying time/date filters" do + it "is precise to time of day, not just date" do + # When I generate a customer report + # with a timeframe that includes one order but not the other + find("input.datepicker").click + select_dates_from_daterangepicker datetime_start1, datetime_end + find(".shortcut-buttons-flatpickr-button").click # closes flatpickr + + run_report + # Then I should see the rows for the first order but not the second + # One row per line item - order3 only + within ".report__table" do + expect(page).to have_content(distributor.name, count: 5) + end + expect(page).to have_text(order3.email, count: 5) + + # setting a time interval to include both orders + find("input.datepicker").click + select_dates_from_daterangepicker datetime_start2, Time.zone.now + + run_report + # Then I should see the both orders + within ".report__table" do + expect(page).to have_content(distributor.name, count: 10) + end + expect(page).to have_text(order3.email, count: 5) + expect(page).to have_text(order4.email, count: 5) + end + end + + context "applying distributor filters" do + it "displays line items from the correct distributors" do + # for one distributor + select2_select distributor.name, from: "q_distributor_id_in" + run_report + + within ".report__table" do + expect(page).to have_content(distributor.name, count: 15) + end + clear_select2("#s2id_q_distributor_id_in") + + # for another distributor + select2_select distributor2.name, from: "q_distributor_id_in" + run_report + + within ".report__table" do + expect(page).to have_content(distributor2.name, count: 5) + end + end + end + end + end + end +end diff --git a/spec/system/admin/reports/orders_and_fulfillment_spec.rb b/spec/system/admin/reports/orders_and_fulfillment_spec.rb index 1aa808e2fc..49fd56071d 100644 --- a/spec/system/admin/reports/orders_and_fulfillment_spec.rb +++ b/spec/system/admin/reports/orders_and_fulfillment_spec.rb @@ -256,12 +256,12 @@ RSpec.describe "Orders And Fulfillment" do describe "Totals" do before do click_link "Order Cycle Supplier Totals" - run_report end context "with the header row option not selected" do before do find("#display_header_row").set(false) # hides the header row + run_report end it "displays the report" do @@ -341,31 +341,115 @@ RSpec.describe "Orders And Fulfillment" do end describe "Totals by Distributor" do - before do - click_link "Order Cycle Supplier Totals by Distributor" + context "as the distributor" do + let(:current_user) { distributor.owner } + + before do + login_as(current_user) + visit admin_reports_path + click_link "Order Cycle Supplier Totals by Distributor" + end + + context "with the header row option not selected" do + before do + find("#display_header_row").set(false) # hides the header row + run_report + end + + it "displays the report" do + # displays the producer column + expect(table_headers).to eq([ + ["Producer", + "Product", + "Variant", + "Hub", + "Quantity", + "Curr. Cost per Unit", + "Total Cost", + "Shipping Method"] + ]) + + # displays the producer name in the respective column + # does not display the header row + within "td" do + expect(page).to have_content("Supplier Name") + expect(page).not_to have_css("td.header-row") + end + end + + it "aggregates results per variant" do + expect(all('table.report__table tbody tr').count).to eq(4) + # 1 row per variant = 2 rows + # 2 TOTAL rows + # 4 rows total + + rows = find("table.report__table").all("tbody tr") + table = rows.map { |r| r.all("td").map { |c| c.text.strip } } + + expect(table[0]).to eq(["Supplier Name", "Baked Beans", "1g Big", + "Distributor Name", "3", "10.0", "30.0", "UPS Ground"]) + expect(table[1]).to eq(["", "", "", "TOTAL", "3", "", "30.0", ""]) + expect(table[2]).to eq(["Supplier Name", "Baked Beans", "1g Small", + "Distributor Name", "7", "10.0", "70.0", "UPS Ground"]) + expect(table[3]).to eq(["", "", "", "TOTAL", "7", "", "70.0", ""]) + end + end + + context "with the header row option selected" do + before do + find("#display_header_row").set(true) # displays the header row + run_report + end + + it "displays the report" do + rows = find("table.report__table").all("thead tr") + table = rows.map { |r| r.all("th").map { |c| c.text.strip } } + + # hides the producer column + expect(table).to eq([ + ["Product", + "Variant", + "Quantity", + "Curr. Cost per Unit", + "Total Cost", + "Shipping Method"] + ]) + + # displays the producer name in own row + within "td.header-row" do + expect(page).to have_content("Supplier Name") + end + end + end end - context "with the header row option not selected" do + context "as the supplier granting P-OC to distributor" do + let(:current_user) { supplier.owner } + before do - find("#display_header_row").set(false) # hides the header row + create(:enterprise_relationship, parent: supplier, child: distributor, + permissions_list: [:add_to_order_cycle]) + + login_as(current_user) + visit admin_report_path(:orders_and_fulfillment, + :order_cycle_supplier_totals_by_distributor) + + uncheck "Header Row" run_report end it "displays the report" do - rows = find("table.report__table").all("thead tr") - table = rows.map { |r| r.all("th").map { |c| c.text.strip } } - # displays the producer column - expect(table).to eq([ - ["Producer", - "Product", - "Variant", - "Hub", - "Quantity", - "Curr. Cost per Unit", - "Total Cost", - "Shipping Method"] - ]) + expect(table_headers).to eq([ + ["Producer", + "Product", + "Variant", + "Hub", + "Quantity", + "Curr. Cost per Unit", + "Total Cost", + "Shipping Method"] + ]) # displays the producer name in the respective column # does not display the header row @@ -375,49 +459,21 @@ RSpec.describe "Orders And Fulfillment" do end end - xit "aggregates results per variant" do - pending '#9678' - expect(all('table.report__table tbody tr').count).to eq(4) - # 1 row per variant = 2 rows - # 2 TOTAL rows - # 4 rows total - + it "aggregates results per variant" do rows = find("table.report__table").all("tbody tr") table = rows.map { |r| r.all("td").map { |c| c.text.strip } } - expect(table[0]).to eq(["Supplier Name", "Baked Beans", "1g Small, S", - "Distributor Name", "7", "10.0", "70.0", "UPS Ground"]) - expect(table[1]).to eq(["", "", "", "TOTAL", "7", "", "70.0", ""]) - expect(table[2]).to eq(["Supplier Name", "Baked Beans", "1g Big, S", + expect(table.count).to eq(4) + # 1 row per variant = 2 rows + # 2 TOTAL rows for distributors + # 4 rows total + + expect(table[0]).to eq(["Supplier Name", "Baked Beans", "1g Big", "Distributor Name", "3", "10.0", "30.0", "UPS Ground"]) - expect(table[3]).to eq(["", "", "", "TOTAL", "3", "", "30.0", ""]) - end - end - - context "with the header row option selected" do - before do - find("#display_header_row").set(true) # displays the header row - run_report - end - - it "displays the report" do - rows = find("table.report__table").all("thead tr") - table = rows.map { |r| r.all("th").map { |c| c.text.strip } } - - # hides the producer column - expect(table).to eq([ - ["Product", - "Variant", - "Quantity", - "Curr. Cost per Unit", - "Total Cost", - "Shipping Method"] - ]) - - # displays the producer name in own row - within "td.header-row" do - expect(page).to have_content("Supplier Name") - end + expect(table[1]).to eq(["", "", "", "TOTAL", "3", "", "30.0", ""]) + expect(table[2]).to eq(["Supplier Name", "Baked Beans", "1g Small", + "Distributor Name", "7", "10.0", "70.0", "UPS Ground"]) + expect(table[3]).to eq(["", "", "", "TOTAL", "7", "", "70.0", ""]) end end end @@ -453,21 +509,18 @@ RSpec.describe "Orders And Fulfillment" do end it "displays the report" do - rows = find("table.report__table").all("thead tr") - table = rows.map { |r| r.all("th").map { |c| c.text.strip } } - # displays the producer column - expect(table).to eq([ - ["Hub", - "Producer", - "Product", - "Variant", - "Quantity", - "Curr. Cost per Unit", - "Total Cost", - "Total Shipping Cost", - "Shipping Method"] - ]) + expect(table_headers).to eq([ + ["Hub", + "Producer", + "Product", + "Variant", + "Quantity", + "Curr. Cost per Unit", + "Total Cost", + "Total Shipping Cost", + "Shipping Method"] + ]) # displays the Distributor name in the respective column # does not display the header row @@ -484,16 +537,19 @@ RSpec.describe "Orders And Fulfillment" do # 1 TOTAL rows # 4 rows total - rows = find("table.report__table").all("tbody tr") - table = rows.map { |r| r.all("td").map { |c| c.text.strip } } - - expect(table[0]).to eq(["Distributor Name", "Another Supplier Name", "Salted Peanuts", - "1g Bag, S", "2", "10.0", "20.0", "", "UPS Ground"]) - expect(table[1]).to eq(["Distributor Name", "Supplier Name", "Baked Beans", - "1g Small, S", "3", "10.0", "30.0", "", "UPS Ground"]) - expect(table[2]).to eq(["Distributor Name", "Supplier Name", "Baked Beans", - "1g Big, S", "3", "10.0", "30.0", "", "UPS Ground"]) - expect(table[3]).to eq(["", "", "", "", "", "TOTAL", "80.0", "0.0", ""]) + expect(table_headers[0]).to eq( + ["Distributor Name", "Another Supplier Name", "Salted Peanuts", + "1g Bag, S", "2", "10.0", "20.0", "", "UPS Ground"] + ) + expect(table_headers[1]).to eq( + ["Distributor Name", "Supplier Name", "Baked Beans", + "1g Small, S", "3", "10.0", "30.0", "", "UPS Ground"] + ) + expect(table_headers[2]).to eq( + ["Distributor Name", "Supplier Name", "Baked Beans", + "1g Big, S", "3", "10.0", "30.0", "", "UPS Ground"] + ) + expect(table_headers[3]).to eq(["", "", "", "", "", "TOTAL", "80.0", "0.0", ""]) end end @@ -505,20 +561,17 @@ RSpec.describe "Orders And Fulfillment" do it "displays the report" do run_report - rows = find("table.report__table").all("thead tr") - table = rows.map { |r| r.all("th").map { |c| c.text.strip } } - # hides the Hub column - expect(table).to eq([ - ["Producer", - "Product", - "Variant", - "Quantity", - "Curr. Cost per Unit", - "Total Cost", - "Total Shipping Cost", - "Shipping Method"] - ]) + expect(table_headers).to eq([ + ["Producer", + "Product", + "Variant", + "Quantity", + "Curr. Cost per Unit", + "Total Cost", + "Total Shipping Cost", + "Shipping Method"] + ]) # displays the Distributor name in own row within "td.header-row" do diff --git a/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb b/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb index 877081295a..690d2df0e7 100644 --- a/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb +++ b/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb @@ -3,6 +3,8 @@ require 'system_helper' RSpec.describe "Sales Tax Totals By order" do + include ReportsHelper + # Scenarion 1: added tax # 1 producer # 1 distributor @@ -457,7 +459,6 @@ RSpec.describe "Sales Tax Totals By order" do it_behaves_like "reports generated as", "CSV", "csv", false it_behaves_like "reports generated as", "Spreadsheet", "xlsx", true - it_behaves_like "reports generated as", "PDF", "pdf", true end end @@ -468,24 +469,4 @@ RSpec.describe "Sales Tax Totals By order" do report_subtype: :sales_tax_totals_by_order ) end - - def generate_report - run_report - click_on "Download Report" - wait_for_download - end - - def load_file_txt(extension, downloaded_filename) - case extension - when "csv" - CSV.read(downloaded_filename).join(" ") - when "xlsx" - xlsx = Roo::Excelx.new(downloaded_filename) - xlsx.map(&:to_a).join(" ") - when "pdf" - # Load PDF pages and contents join into one big string - pdf = PDF::Reader.new(downloaded_filename) - pdf.pages.map(&:text).join(" ") - end - end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 62de258992..57d7dfff22 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -139,14 +139,15 @@ RSpec.describe ' # Unlocking the breakpoint will continue execution of the controller. breakpoint.unlock - # We have to wait to be sure that the "loading" spinner won't appear - # within the next half second. The default wait time would wait for - # 10 seconds which slows down the spec. - using_wait_time 0.5 do - page.has_selector? ".loading" - end + # Now the controller response will show the loading spinner again and + # the fallback mechanism will render the report later. + expect(page).to have_selector ".loading" + + # Wait for the fallback mechanism: + sleep 3 expect(page).not_to have_selector ".loading" + expect(page).to have_content "First Name Last Name Billing Address Email" end end @@ -210,43 +211,6 @@ RSpec.describe ' visit admin_reports_path end - it "generates the orders and distributors report" do - click_link 'Orders And Distributors' - run_report - - rows = find("table.report__table").all("thead tr") - table_headers = rows.map { |r| r.all("th").map { |c| c.text.strip } } - - expect(table_headers).to eq([ - ['Order date', - 'Order Id', - 'Customer Name', - 'Customer Email', - 'Customer Phone', - 'Customer City', - 'SKU', - 'Item name', - 'Variant', - 'Quantity', - 'Max Quantity', - 'Cost', - 'Shipping Cost', - 'Payment Method', - 'Distributor', - 'Distributor address', - 'Distributor city', - 'Distributor postcode', - 'Shipping Method', - 'Shipping instructions'] - ]) - - expect(all('table.report__table tbody tr').count).to eq( - Spree::LineItem.where( - order_id: ready_to_ship_order.id # Total rows should equal number of line items, per order - ).count - ) - end - it "generates the payments reports" do click_link 'Payments By Type' run_report @@ -499,6 +463,8 @@ RSpec.describe ' end describe 'bulk coop report' do + let!(:order) { create(:completed_order_with_totals) } + before do login_as_admin visit admin_reports_path diff --git a/spec/system/consumer/shopping/products_spec.rb b/spec/system/consumer/shopping/products_spec.rb index f2ff5b0d5a..87d155c0ec 100644 --- a/spec/system/consumer/shopping/products_spec.rb +++ b/spec/system/consumer/shopping/products_spec.rb @@ -130,7 +130,7 @@ RSpec.describe "As a consumer I want to view products" do add_variant_to_order_cycle(exchange1, variant2) end - context "product taxonomies" do + context "product taxons" do before do distributor.preferred_shopfront_product_sorting_method = "by_category" distributor.preferred_shopfront_taxon_order = taxon.id.to_s diff --git a/spec/system/consumer/shopping/variant_overrides_spec.rb b/spec/system/consumer/shopping/variant_overrides_spec.rb index 01dd56c349..4cc8d11eb0 100644 --- a/spec/system/consumer/shopping/variant_overrides_spec.rb +++ b/spec/system/consumer/shopping/variant_overrides_spec.rb @@ -222,13 +222,13 @@ RSpec.describe "shopping with variant overrides defined" do expect(product1_variant1_override.reload.count_on_hand).to be_nil end - it "does not subtract stock from variants where the override has on_demand: true" do + it "subtracts stock from override but not variants where the override has on_demand: true" do click_add_to_cart product4_variant1, 2 click_checkout expect do complete_checkout end.to change { product4_variant1.reload.on_hand }.by(0) - expect(product4_variant1_override.reload.count_on_hand).to be_nil + expect(product4_variant1_override.reload.count_on_hand).to eq(-2) end it "does not show out of stock flags on order confirmation page" do diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index cc6fa07771..68e1524b1f 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -69,6 +69,99 @@ paths: dfc-b:region: Victoria '404': description: not found + "/api/dfc/affiliate_sales_data": + parameters: + - name: startDate + in: query + schema: + type: string + - name: endDate + in: query + schema: + type: string + get: + summary: Show sales data of person's affiliate enterprises + tags: + - AffiliateSalesData + responses: + '200': + description: successful + content: + application/json: + examples: + test_example: + value: + "@context": https://www.datafoodconsortium.org + "@id": http://test.host/api/dfc/affiliate_sales_data + "@type": dfc-b:Person + dfc-b:logo: '' + dfc-b:firstName: '' + dfc-b:familyName: '' + dfc-b:affiliates: + "@type": dfc-b:Enterprise + dfc-b:hasAddress: + "@type": dfc-b:Address + dfc-b:hasStreet: '' + dfc-b:hasPostalCode: '20170' + dfc-b:hasCity: '' + dfc-b:hasCountry: '' + dfc-b:latitude: 0.0 + dfc-b:longitude: 0.0 + dfc-b:region: '' + dfc-b:logo: '' + dfc-b:name: '' + dfc-b:hasDescription: '' + dfc-b:VATnumber: '' + dfc-b:supplies: + "@type": dfc-b:SuppliedProduct + dfc-b:name: 'Product #3 - 7198' + dfc-b:description: '' + dfc-b:hasQuantity: + "@type": dfc-b:QuantitativeValue + dfc-b:hasUnit: dfc-m:Gram + dfc-b:value: 1.0 + dfc-b:alcoholPercentage: 0.0 + dfc-b:lifetime: '' + dfc-b:usageOrStorageCondition: '' + dfc-b:totalTheoreticalStock: 0.0 + dfc-b:concernedBy: + "@type": dfc-b:OrderLine + dfc-b:description: '' + dfc-b:quantity: + "@type": dfc-b:QuantitativeValue + dfc-b:hasUnit: dfc-m:Piece + dfc-b:value: 1.0 + dfc-b:hasPrice: + "@type": dfc-b:QuantitativeValue + dfc-b:value: 10.0 + dfc-b:partOf: + "@type": dfc-b:Order + dfc-b:orderNumber: '' + dfc-b:date: '' + dfc-b:belongsTo: + "@type": dfc-b:SaleSession + dfc-b:beginDate: '' + dfc-b:endDate: '' + dfc-b:quantity: 0.0 + dfc-b:objectOf: + "@type": dfc-b:Coordination + dfc-b:coordinatedBy: + "@type": dfc-b:Enterprise + dfc-b:hasAddress: + "@type": dfc-b:Address + dfc-b:hasStreet: '' + dfc-b:hasPostalCode: '20170' + dfc-b:hasCity: '' + dfc-b:hasCountry: '' + dfc-b:latitude: 0.0 + dfc-b:longitude: 0.0 + dfc-b:region: '' + dfc-b:logo: '' + dfc-b:name: '' + dfc-b:hasDescription: '' + dfc-b:VATnumber: '' + '400': + description: bad request "/api/dfc/enterprises/{enterprise_id}/catalog_items": parameters: - name: enterprise_id diff --git a/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js b/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js deleted file mode 100755 index d5d2ea9155..0000000000 --- a/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js +++ /dev/null @@ -1,4540 +0,0 @@ -/* - * jsTree 1.0-rc3 - * http://jstree.com/ - * - * Copyright (c) 2010 Ivan Bozhanov (vakata.com) - * - * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $ - * $Revision: 236 $ - */ - -/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */ -/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/ - -"use strict"; - -// top wrapper to prevent multiple inclusion (is this OK?) -(function () { if(jQuery && jQuery.jstree) { return; } - var is_ie6 = false, is_ie7 = false, is_ff2 = false; - -/* - * jsTree core - */ -(function ($) { - // Common functions not related to jsTree - // decided to move them to a `vakata` "namespace" - $.vakata = {}; - // CSS related functions - $.vakata.css = { - get_css : function(rule_name, delete_flag, sheet) { - rule_name = rule_name.toLowerCase(); - var css_rules = sheet.cssRules || sheet.rules, - j = 0; - do { - if(css_rules.length && j > css_rules.length + 5) { return false; } - if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) { - if(delete_flag === true) { - if(sheet.removeRule) { sheet.removeRule(j); } - if(sheet.deleteRule) { sheet.deleteRule(j); } - return true; - } - else { return css_rules[j]; } - } - } - while (css_rules[++j]); - return false; - }, - add_css : function(rule_name, sheet) { - if($.jstree.css.get_css(rule_name, false, sheet)) { return false; } - if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); } - return $.vakata.css.get_css(rule_name); - }, - remove_css : function(rule_name, sheet) { - return $.vakata.css.get_css(rule_name, true, sheet); - }, - add_sheet : function(opts) { - var tmp = false, is_new = true; - if(opts.str) { - if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; } - if(tmp) { is_new = false; } - else { - tmp = document.createElement("style"); - tmp.setAttribute('type',"text/css"); - if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); } - } - if(tmp.styleSheet) { - if(is_new) { - document.getElementsByTagName("head")[0].appendChild(tmp); - tmp.styleSheet.cssText = opts.str; - } - else { - tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; - } - } - else { - tmp.appendChild(document.createTextNode(opts.str)); - document.getElementsByTagName("head")[0].appendChild(tmp); - } - return tmp.sheet || tmp.styleSheet; - } - if(opts.url) { - if(document.createStyleSheet) { - try { tmp = document.createStyleSheet(opts.url); } catch (e) { } - } - else { - tmp = document.createElement('link'); - tmp.rel = 'stylesheet'; - tmp.type = 'text/css'; - tmp.media = "all"; - tmp.href = opts.url; - document.getElementsByTagName("head")[0].appendChild(tmp); - return tmp.styleSheet; - } - } - } - }; - - // private variables - var instances = [], // instance array (used by $.jstree.reference/create/focused) - focused_instance = -1, // the index in the instance array of the currently focused instance - plugins = {}, // list of included plugins - prepared_move = {}; // for the move_node function - - // jQuery plugin wrapper (thanks to jquery UI widget function) - $.fn.jstree = function (settings) { - var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node") - args = Array.prototype.slice.call(arguments, 1), - returnValue = this; - - // if a method call execute the method on all selected instances - if(isMethodCall) { - if(settings.substring(0, 1) == '_') { return returnValue; } - this.each(function() { - var instance = instances[$.data(this, "jstree-instance-id")], - methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance; - if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; } - }); - } - else { - this.each(function() { - // extend settings and allow for multiple hashes and $.data - var instance_id = $.data(this, "jstree-instance-id"), - a = [], - b = settings ? $.extend({}, true, settings) : {}, - c = $(this), - s = false, - t = []; - a = a.concat(args); - if(c.data("jstree")) { a.push(c.data("jstree")); } - b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b; - - // if an instance already exists, destroy it first - if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); } - // push a new empty object to the instances array - instance_id = parseInt(instances.push({}),10) - 1; - // store the jstree instance id to the container element - $.data(this, "jstree-instance-id", instance_id); - // clean up all plugins - b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice(); - b.plugins.unshift("core"); - // only unique plugins - b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(","); - - // extend defaults with passed data - s = $.extend(true, {}, $.jstree.defaults, b); - s.plugins = b.plugins; - $.each(plugins, function (i, val) { - if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } - else { t.push(i); } - }); - s.plugins = t; - - // push the new object to the instances array (at the same time set the default classes to the container) and init - instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); - // init all activated plugins for this instance - $.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; }); - $.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } }); - // initialize the instance - setTimeout(function() { instances[instance_id].init(); }, 0); - }); - } - // return the jquery selection (or if it was a method call that returned a value - the returned value) - return returnValue; - }; - // object to store exposed functions and objects - $.jstree = { - defaults : { - plugins : [] - }, - _focused : function () { return instances[focused_instance] || null; }, - _reference : function (needle) { - // get by instance id - if(instances[needle]) { return instances[needle]; } - // get by DOM (if still no luck - return null - var o = $(needle); - if(!o.length && typeof needle === "string") { o = $("#" + needle); } - if(!o.length) { return null; } - return instances[o.closest(".jstree").data("jstree-instance-id")] || null; - }, - _instance : function (index, container, settings) { - // for plugins to store data in - this.data = { core : {} }; - this.get_settings = function () { return $.extend(true, {}, settings); }; - this._get_settings = function () { return settings; }; - this.get_index = function () { return index; }; - this.get_container = function () { return container; }; - this.get_container_ul = function () { return container.children("ul:eq(0)"); }; - this._set_settings = function (s) { - settings = $.extend(true, {}, settings, s); - }; - }, - _fn : { }, - plugin : function (pname, pdata) { - pdata = $.extend({}, { - __init : $.noop, - __destroy : $.noop, - _fn : {}, - defaults : false - }, pdata); - plugins[pname] = pdata; - - $.jstree.defaults[pname] = pdata.defaults; - $.each(pdata._fn, function (i, val) { - val.plugin = pname; - val.old = $.jstree._fn[i]; - $.jstree._fn[i] = function () { - var rslt, - func = val, - args = Array.prototype.slice.call(arguments), - evnt = new $.Event("before.jstree"), - rlbk = false; - - if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; } - - // Check if function belongs to the included plugins of this instance - do { - if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; } - func = func.old; - } while(func); - if(!func) { return; } - - // context and function to trigger events, then finally call the function - if(i.indexOf("_") === 0) { - rslt = func.apply(this, args); - } - else { - rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin }); - if(rslt === false) { return; } - if(typeof rslt !== "undefined") { args = rslt; } - - rslt = func.apply( - $.extend({}, this, { - __callback : function (data) { - this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk }); - }, - __rollback : function () { - rlbk = this.get_rollback(); - return rlbk; - }, - __call_old : function (replace_arguments) { - return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) ); - } - }), args); - } - - // return the result - return rslt; - }; - $.jstree._fn[i].old = val.old; - $.jstree._fn[i].plugin = pname; - }); - }, - rollback : function (rb) { - if(rb) { - if(!$.isArray(rb)) { rb = [ rb ]; } - $.each(rb, function (i, val) { - instances[val.i].set_rollback(val.h, val.d); - }); - } - } - }; - // set the prototype for all instances - $.jstree._fn = $.jstree._instance.prototype = {}; - - // load the css when DOM is ready - $(function() { - // code is copied from jQuery ($.browser is deprecated + there is a bug in IE) - var u = navigator.userAgent.toLowerCase(), - v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1], - css_string = '' + - '.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + - '.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + - '.jstree-rtl li { margin-left:0; margin-right:18px; } ' + - '.jstree > ul > li { margin-left:0px; } ' + - '.jstree-rtl > ul > li { margin-right:0px; } ' + - '.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + - '.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + - '.jstree a:focus { outline: none; } ' + - '.jstree a > ins { height:16px; width:16px; } ' + - '.jstree a > .jstree-icon { margin-right:3px; } ' + - '.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + - 'li.jstree-open > ul { display:block; } ' + - 'li.jstree-closed > ul { display:none; } '; - // Correct IE 6 (does not support the > CSS selector) - if(/msie/.test(u) && parseInt(v, 10) == 6) { - is_ie6 = true; - - // fix image flicker and lack of caching - try { - document.execCommand("BackgroundImageCache", false, true); - } catch (err) { } - - css_string += '' + - '.jstree li { height:18px; margin-left:0; margin-right:0; } ' + - '.jstree li li { margin-left:18px; } ' + - '.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + - 'li.jstree-open ul { display:block; } ' + - 'li.jstree-closed ul { display:none !important; } ' + - '.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + - '.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + - '.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } '; - } - // Correct IE 7 (shifts anchor nodes onhover) - if(/msie/.test(u) && parseInt(v, 10) == 7) { - is_ie7 = true; - css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } '; - } - // correct ff2 lack of display:inline-block - if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) { - is_ff2 = true; - css_string += '' + - '.jstree ins { display:-moz-inline-box; } ' + - '.jstree li { line-height:12px; } ' + // WHY?? - '.jstree a { display:-moz-inline-box; } ' + - '.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } '; - /* this shouldn't be here as it is theme specific */ - } - // the default stylesheet - $.vakata.css.add_sheet({ str : css_string, title : "jstree" }); - }); - - // core functions (open, close, create, update, delete) - $.jstree.plugin("core", { - __init : function () { - this.data.core.locked = false; - this.data.core.to_open = this.get_settings().core.initially_open; - this.data.core.to_load = this.get_settings().core.initially_load; - }, - defaults : { - html_titles : false, - animation : 500, - initially_open : [], - initially_load : [], - open_parents : true, - notify_plugins : true, - rtl : false, - load_open : false, - strings : { - loading : "Loading ...", - new_node : "New node", - multiple_selection : "Multiple selection" - } - }, - _fn : { - init : function () { - this.set_focus(); - if(this._get_settings().core.rtl) { - this.get_container().addClass("jstree-rtl").css("direction", "rtl"); - } - this.get_container().html(""); - this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18; - - this.get_container() - .delegate("li > ins", "click.jstree", $.proxy(function (event) { - var trgt = $(event.target); - if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); } - }, this)) - .bind("mousedown.jstree", $.proxy(function () { - this.set_focus(); // This used to be setTimeout(set_focus,0) - why? - }, this)) - .bind("dblclick.jstree", function (event) { - var sel; - if(document.selection && document.selection.empty) { document.selection.empty(); } - else { - if(window.getSelection) { - sel = window.getSelection(); - try { - sel.removeAllRanges(); - sel.collapse(); - } catch (err) { } - } - } - }); - if(this._get_settings().core.notify_plugins) { - this.get_container() - .bind("load_node.jstree", $.proxy(function (e, data) { - var o = this._get_node(data.rslt.obj), - t = this; - if(o === -1) { o = this.get_container_ul(); } - if(!o.length) { return; } - o.find("li").each(function () { - var th = $(this); - if(th.data("jstree")) { - $.each(th.data("jstree"), function (plugin, values) { - if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) { - t["_" + plugin + "_notify"].call(t, th, values); - } - }); - } - }); - }, this)); - } - if(this._get_settings().core.load_open) { - this.get_container() - .bind("load_node.jstree", $.proxy(function (e, data) { - var o = this._get_node(data.rslt.obj), - t = this; - if(o === -1) { o = this.get_container_ul(); } - if(!o.length) { return; } - o.find("li.jstree-open:not(:has(ul))").each(function () { - t.load_node(this, $.noop, $.noop); - }); - }, this)); - } - this.__callback(); - this.load_node(-1, function () { this.loaded(); this.reload_nodes(); }); - }, - destroy : function () { - var i, - n = this.get_index(), - s = this._get_settings(), - _this = this; - - $.each(s.plugins, function (i, val) { - try { plugins[val].__destroy.apply(_this); } catch(err) { } - }); - this.__callback(); - // set focus to another instance if this one is focused - if(this.is_focused()) { - for(i in instances) { - if(instances.hasOwnProperty(i) && i != n) { - instances[i].set_focus(); - break; - } - } - } - // if no other instance found - if(n === focused_instance) { focused_instance = -1; } - // remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events - this.get_container() - .unbind(".jstree") - .undelegate(".jstree") - .removeData("jstree-instance-id") - .find("[class^='jstree']") - .andSelf() - .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); }); - $(document) - .unbind(".jstree-" + n) - .undelegate(".jstree-" + n); - // remove the actual data - instances[n] = null; - delete instances[n]; - }, - - _core_notify : function (n, data) { - if(data.opened) { - this.open_node(n, false, true); - } - }, - - lock : function () { - this.data.core.locked = true; - this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7"); - this.__callback({}); - }, - unlock : function () { - this.data.core.locked = false; - this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1"); - this.__callback({}); - }, - is_locked : function () { return this.data.core.locked; }, - save_opened : function () { - var _this = this; - this.data.core.to_open = []; - this.get_container_ul().find("li.jstree-open").each(function () { - if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } - }); - this.__callback(_this.data.core.to_open); - }, - save_loaded : function () { }, - reload_nodes : function (is_callback) { - var _this = this, - done = true, - current = [], - remaining = []; - if(!is_callback) { - this.data.core.reopen = false; - this.data.core.refreshing = true; - this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); - this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); - if(this.data.core.to_open.length) { - this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open); - } - } - if(this.data.core.to_load.length) { - $.each(this.data.core.to_load, function (i, val) { - if(val == "#") { return true; } - if($(val).length) { current.push(val); } - else { remaining.push(val); } - }); - if(current.length) { - this.data.core.to_load = remaining; - $.each(current, function (i, val) { - if(!_this._is_loaded(val)) { - _this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); }); - done = false; - } - }); - } - } - if(this.data.core.to_open.length) { - $.each(this.data.core.to_open, function (i, val) { - _this.open_node(val, false, true); - }); - } - if(done) { - // TODO: find a more elegant approach to syncronizing returning requests - if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); } - this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50); - this.data.core.refreshing = false; - this.reopen(); - } - }, - reopen : function () { - var _this = this; - if(this.data.core.to_open.length) { - $.each(this.data.core.to_open, function (i, val) { - _this.open_node(val, false, true); - }); - } - this.__callback({}); - }, - refresh : function (obj) { - var _this = this; - this.save_opened(); - if(!obj) { obj = -1; } - obj = this._get_node(obj); - if(!obj) { obj = -1; } - if(obj !== -1) { obj.children("UL").remove(); } - else { this.get_container_ul().empty(); } - this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); }); - }, - // Dummy function to fire after the first load (so that there is a jstree.loaded event) - loaded : function () { - this.__callback(); - }, - // deal with focus - set_focus : function () { - if(this.is_focused()) { return; } - var f = $.jstree._focused(); - if(f) { f.unset_focus(); } - - this.get_container().addClass("jstree-focused"); - focused_instance = this.get_index(); - this.__callback(); - }, - is_focused : function () { - return focused_instance == this.get_index(); - }, - unset_focus : function () { - if(this.is_focused()) { - this.get_container().removeClass("jstree-focused"); - focused_instance = -1; - } - this.__callback(); - }, - - // traverse - _get_node : function (obj) { - var $obj = $(obj, this.get_container()); - if($obj.is(".jstree") || obj == -1) { return -1; } - $obj = $obj.closest("li", this.get_container()); - return $obj.length ? $obj : false; - }, - _get_next : function (obj, strict) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().find("> ul > li:first-child"); } - if(!obj.length) { return false; } - if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; } - - if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); } - else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); } - else { return obj.parentsUntil(".jstree","li").next("li").eq(0); } - }, - _get_prev : function (obj, strict) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().find("> ul > li:last-child"); } - if(!obj.length) { return false; } - if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; } - - if(obj.prev("li").length) { - obj = obj.prev("li").eq(0); - while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); } - return obj; - } - else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; } - }, - _get_parent : function (obj) { - obj = this._get_node(obj); - if(obj == -1 || !obj.length) { return false; } - var o = obj.parentsUntil(".jstree", "li:eq(0)"); - return o.length ? o : -1; - }, - _get_children : function (obj) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); } - if(!obj.length) { return false; } - return obj.children("ul:eq(0)").children("li"); - }, - get_path : function (obj, id_mode) { - var p = [], - _this = this; - obj = this._get_node(obj); - if(obj === -1 || !obj || !obj.length) { return false; } - obj.parentsUntil(".jstree", "li").each(function () { - p.push( id_mode ? this.id : _this.get_text(this) ); - }); - p.reverse(); - p.push( id_mode ? obj.attr("id") : this.get_text(obj) ); - return p; - }, - - // string functions - _get_string : function (key) { - return this._get_settings().core.strings[key] || key; - }, - - is_open : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); }, - is_closed : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); }, - is_leaf : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); }, - correct_state : function (obj) { - obj = this._get_node(obj); - if(!obj || obj === -1) { return false; } - obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove(); - this.__callback({ "obj" : obj }); - }, - // open/close - open_node : function (obj, callback, skip_animation) { - obj = this._get_node(obj); - if(!obj.length) { return false; } - if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; } - var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, - t = this; - if(!this._is_loaded(obj)) { - obj.children("a").addClass("jstree-loading"); - this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback); - } - else { - if(this._get_settings().core.open_parents) { - obj.parentsUntil(".jstree",".jstree-closed").each(function () { - t.open_node(this, false, true); - }); - } - if(s) { obj.children("ul").css("display","none"); } - obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading"); - if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); } - else { t.after_open(obj); } - this.__callback({ "obj" : obj }); - if(callback) { callback.call(); } - } - }, - after_open : function (obj) { this.__callback({ "obj" : obj }); }, - close_node : function (obj, skip_animation) { - obj = this._get_node(obj); - var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, - t = this; - if(!obj.length || !obj.hasClass("jstree-open")) { return false; } - if(s) { obj.children("ul").attr("style","display:block !important"); } - obj.removeClass("jstree-open").addClass("jstree-closed"); - if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); } - else { t.after_close(obj); } - this.__callback({ "obj" : obj }); - }, - after_close : function (obj) { this.__callback({ "obj" : obj }); }, - toggle_node : function (obj) { - obj = this._get_node(obj); - if(obj.hasClass("jstree-closed")) { return this.open_node(obj); } - if(obj.hasClass("jstree-open")) { return this.close_node(obj); } - }, - open_all : function (obj, do_animation, original_obj) { - obj = obj ? this._get_node(obj) : -1; - if(!obj || obj === -1) { obj = this.get_container_ul(); } - if(original_obj) { - obj = obj.find("li.jstree-closed"); - } - else { - original_obj = obj; - if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); } - else { obj = obj.find("li.jstree-closed"); } - } - var _this = this; - obj.each(function () { - var __this = this; - if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); } - else { _this.open_node(this, false, !do_animation); } - }); - // so that callback is fired AFTER all nodes are open - if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); } - }, - close_all : function (obj, do_animation) { - var _this = this; - obj = obj ? this._get_node(obj) : this.get_container(); - if(!obj || obj === -1) { obj = this.get_container_ul(); } - obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this, !do_animation); }); - this.__callback({ "obj" : obj }); - }, - clean_node : function (obj) { - obj = obj && obj != -1 ? $(obj) : this.get_container_ul(); - obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li"); - obj.removeClass("jstree-last") - .filter("li:last-child").addClass("jstree-last").end() - .filter(":has(li)") - .not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed"); - obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove(); - this.__callback({ "obj" : obj }); - }, - // rollback - get_rollback : function () { - this.__callback(); - return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; - }, - set_rollback : function (html, data) { - this.get_container().empty().append(html); - this.data = data; - this.__callback(); - }, - // Dummy functions to be overwritten by any datastore plugin included - load_node : function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); }, - _is_loaded : function (obj) { return true; }, - - // Basic operations: create - create_node : function (obj, position, js, callback, is_loaded) { - obj = this._get_node(obj); - position = typeof position === "undefined" ? "last" : position; - var d = $("
  • "), - s = this._get_settings().core, - tmp; - - if(obj !== -1 && !obj.length) { return false; } - if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; } - - this.__rollback(); - - if(typeof js === "string") { js = { "data" : js }; } - if(!js) { js = {}; } - if(js.attr) { d.attr(js.attr); } - if(js.metadata) { d.data(js.metadata); } - if(js.state) { d.addClass("jstree-" + js.state); } - if(!js.data) { js.data = this._get_string("new_node"); } - if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); } - $.each(js.data, function (i, m) { - tmp = $(""); - if($.isFunction(m)) { m = m.call(this, js); } - if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); } - else { - if(!m.attr) { m.attr = {}; } - if(!m.attr.href) { m.attr.href = '#'; } - tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title); - if(m.language) { tmp.addClass(m.language); } - } - tmp.prepend(" "); - if(m.icon) { - if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); } - else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); } - } - d.append(tmp); - }); - d.prepend(" "); - if(obj === -1) { - obj = this.get_container(); - if(position === "before") { position = "first"; } - if(position === "after") { position = "last"; } - } - switch(position) { - case "before": obj.before(d); tmp = this._get_parent(obj); break; - case "after" : obj.after(d); tmp = this._get_parent(obj); break; - case "inside": - case "first" : - if(!obj.children("ul").length) { obj.append("