diff --git a/DOCKER.md b/DOCKER.md index 3df0f7d345..65f6815425 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -52,3 +52,5 @@ The default admin user is 'ofn@example.com' with 'ofn123' password. Check the app in the browser at `http://localhost:3000`. You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal. + +You can find some useful tips and commands [here](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Docker:-useful-tips-and-commands). diff --git a/Dockerfile b/Dockerfile index 93caceecc1..202bbeccf7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,9 @@ RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' apt-get update && \ apt-get install -yqq --no-install-recommends postgresql-client-9.5 libpq-dev +# Install node +RUN apt-get install -y nodejs + # Install Chrome RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \ diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 84b38663aa..67f38e7390 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -2,6 +2,8 @@ This is a general guide to setting up an Open Food Network development environment on your local machine. +The fastest way to make it work locally is to use Docker, see the [Docker setup guide](DOCKER.md). + The following guides are located in the wiki and provide more OS-specific step-by-step instructions: - [Ubuntu Setup Guide][ubuntu] @@ -11,7 +13,7 @@ The following guides are located in the wiki and provide more OS-specific step-b ### Dependencies * Rails 3.2.x -* Ruby 2.1.9 +* Ruby 2.3.7 * PostgreSQL database * PhantomJS (for testing) * See Gemfile for a list of gems required @@ -58,10 +60,10 @@ Now, your dreams of spinning up a development server can be realised: bundle exec rails server -To login as Spree default user, use: +To login as the default user, use: - email: spree@example.com - password: spree123 + email: ofn@example.com + password: ofn123 ### Testing diff --git a/Gemfile b/Gemfile index e3621237fc..ae833df9f7 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ gem 'rails_safe_tasks', '~> 1.0' gem "activerecord-import" gem "catalog", path: "./engines/catalog" +gem 'dfc_provider', path: './engines/dfc_provider' gem "order_management", path: "./engines/order_management" gem 'web', path: './engines/web' diff --git a/Gemfile.lock b/Gemfile.lock index ddb960f768..90cd4a831d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,6 +60,13 @@ PATH specs: catalog (0.0.1) +PATH + remote: engines/dfc_provider + specs: + dfc_provider (0.0.1) + jwt (~> 2.2) + rspec (~> 3.9) + PATH remote: engines/order_management specs: @@ -198,7 +205,7 @@ GEM activerecord (>= 3.2.0, < 5.0) fog (~> 1.0) rails (>= 3.2.0, < 5.0) - ddtrace (0.35.0) + ddtrace (0.35.2) msgpack debugger-linecache (1.2.0) delayed_job (4.1.8) @@ -712,6 +719,7 @@ DEPENDENCIES delayed_job_web devise (~> 3.0.1) devise-encryptable + dfc_provider! diffy eventmachine (>= 1.2.3) factory_bot_rails (= 4.10.0) diff --git a/app/assets/images/icn-close.png b/app/assets/images/icn-close.png new file mode 100644 index 0000000000..6ef99bffec Binary files /dev/null and b/app/assets/images/icn-close.png differ diff --git a/app/assets/images/icn-search-grey.png b/app/assets/images/icn-search-grey.png new file mode 100644 index 0000000000..15af5ed712 Binary files /dev/null and b/app/assets/images/icn-search-grey.png differ diff --git a/app/assets/images/map_009-cluster.svg b/app/assets/images/map_009-cluster.svg new file mode 100644 index 0000000000..aa8928871b --- /dev/null +++ b/app/assets/images/map_009-cluster.svg @@ -0,0 +1,2 @@ + +1+ \ No newline at end of file diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee index 4acd61e84b..fe5fc34992 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee @@ -29,7 +29,7 @@ angular.module("admin.enterprises") # from a directive "nav-check" in the page - if we pass it here it will be called in the test suite, # and on all new uses of this contoller, and we might not want that. enterpriseNavCallback = -> - if $scope.enterprise_form != undefined && $scope.enterprise_form.$dirty + if $scope.enterprise_form?.$dirty t('admin.unsaved_confirm_leave') # Register the NavigationCheck callback 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 73a6b3fcba..84634bca7b 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -1,5 +1,5 @@ angular.module('admin.orderCycles') - .controller 'AdminEditOrderCycleCtrl', ($scope, $controller, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) -> + .controller 'AdminEditOrderCycleCtrl', ($scope, $controller, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, NavigationCheck, ocInstance) -> $controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance}) order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] @@ -18,5 +18,12 @@ angular.module('admin.orderCycles') $scope.submit = ($event, destination) -> $event.preventDefault() + NavigationCheck.clear() StatusMessage.display 'progress', t('js.saving') OrderCycle.update(destination, $scope.order_cycle_form) + + warnAboutUnsavedChanges = -> + if $scope.order_cycle_form?.$dirty + t('admin.unsaved_confirm_leave') + + NavigationCheck.register(warnAboutUnsavedChanges) \ No newline at end of file diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee index 217118ee82..bc72b597c2 100644 --- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) -> +Darkswarm.controller "ProductsCtrl", ($scope, $sce, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) -> $scope.Products = Products $scope.Cart = Cart $scope.query = "" @@ -10,6 +10,7 @@ Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, Ord $scope.order_cycle = OrderCycle.order_cycle $scope.supplied_taxons = null $scope.supplied_properties = null + $scope.showFilterSidebar = false $rootScope.$on "orderCycleSelected", -> $scope.update_filters() @@ -75,15 +76,24 @@ Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, Ord $scope.appliedTaxonsList = -> $scope.activeTaxons.map( (taxon_id) -> Taxons.taxons_by_id[taxon_id].name - ).join(" #{t('products_or')} ") if $scope.activeTaxons? + ).join($scope.filtersJoinWord()) if $scope.activeTaxons? $scope.appliedPropertiesList = -> $scope.activeProperties.map( (property_id) -> Properties.properties_by_id[property_id].name - ).join(" #{t('products_or')} ") if $scope.activeProperties? + ).join($scope.filtersJoinWord()) if $scope.activeProperties? + + $scope.filtersJoinWord = -> + $sce.trustAsHtml(" #{t('products_or')} ") $scope.clearAll = -> + $scope.clearQuery() + $scope.clearFilters() + + $scope.clearQuery = -> $scope.query = "" + + $scope.clearFilters = -> $scope.taxonSelectors.clearAll() $scope.propertySelectors.clearAll() @@ -94,3 +104,9 @@ Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, Ord $scope.Products.products = [] $scope.update_filters() $scope.loadProducts() + + $scope.filtersCount = () -> + $scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive() + + $scope.toggleFilterSidebar = -> + $scope.showFilterSidebar = !$scope.showFilterSidebar diff --git a/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee index 7d28868856..e62357f4e8 100644 --- a/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee @@ -1,2 +1,2 @@ -Darkswarm.controller "ProducersTabCtrl", ($scope, Shopfront, EnterpriseModal) -> +Darkswarm.controller "ProducersTabCtrl", ($scope, Shopfront) -> $scope.shopfront = Shopfront.shopfront diff --git a/app/assets/javascripts/darkswarm/directives/darker_background.js.coffee b/app/assets/javascripts/darkswarm/directives/darker_background.js.coffee new file mode 100644 index 0000000000..629b6a377a --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/darker_background.js.coffee @@ -0,0 +1,11 @@ +Darkswarm.directive "darkerBackground", -> + restrict: "A" + link: (scope, elm, attr)-> + toggleClass = (value) -> + elm.closest('.page-view').toggleClass("with-darker-background", value) + + toggleClass(true) + + # if an OrderCycle is selected, disable darker background + scope.$watch 'order_cycle.order_cycle_id', (newvalue, oldvalue) -> + toggleClass(false) if newvalue diff --git a/app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee index ad0d93a1b6..1bd5d177c1 100644 --- a/app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee @@ -7,4 +7,4 @@ Darkswarm.directive "enterpriseModal", (EnterpriseModal) -> elem.on "click", (event) => event.stopPropagation() - scope.modalInstance = EnterpriseModal.open scope.enterprise \ No newline at end of file + scope.modalInstance = EnterpriseModal.open scope.enterprise diff --git a/app/assets/javascripts/darkswarm/directives/focus_search.coffee b/app/assets/javascripts/darkswarm/directives/focus_search.coffee new file mode 100644 index 0000000000..076c310351 --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/focus_search.coffee @@ -0,0 +1,6 @@ +Darkswarm.directive "focusSearch", -> + restrict: 'A' + link: (scope, element, attr)-> + element.bind 'click', (event) -> + # Focus seach field, ready for typing + $(element).siblings('#search').focus() diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index bdd0aa3676..55f7f6da1a 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService) -> +Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService, RailsFlashLoader) -> # Handles syncing of current cart/order state to server new class Cart dirty: false @@ -50,7 +50,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo @popQueue() if @update_enqueued .error (response, status)=> - @scheduleRetry(status) + RailsFlashLoader.loadFlash({error: t('js.cart.add_to_cart_failed')}) @update_running = false compareAndNotifyStockLevels: (stockLevels) => @@ -87,13 +87,6 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo max_quantity: li.max_quantity {variants: variants} - scheduleRetry: (status) => - console.log "Error updating cart: #{status}. Retrying in 3 seconds..." - $timeout => - console.log "Retrying cart update" - @orderChanged() - , 3000 - saved: => @dirty = false $(window).unbind "beforeunload" diff --git a/app/assets/javascripts/darkswarm/services/enterprise_list_modal.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_list_modal.js.coffee new file mode 100644 index 0000000000..62412f7e59 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/enterprise_list_modal.js.coffee @@ -0,0 +1,10 @@ +Darkswarm.factory "EnterpriseListModal", ($modal, $rootScope, $http, EnterpriseModal)-> + new class EnterpriseListModal + open: (enterprises)-> + scope = $rootScope.$new(true) + scope.enterprises = enterprises + scope.openModal = EnterpriseModal.open + if Object.keys(enterprises).length > 1 + $modal.open(templateUrl: "enterprise_list_modal.html", scope: scope) + else + EnterpriseModal.open enterprises[Object.keys(enterprises)[0]] diff --git a/app/assets/javascripts/darkswarm/services/map.js.coffee b/app/assets/javascripts/darkswarm/services/map.js.coffee index 45370061fd..2758f299f8 100644 --- a/app/assets/javascripts/darkswarm/services/map.js.coffee +++ b/app/assets/javascripts/darkswarm/services/map.js.coffee @@ -1,21 +1,39 @@ -Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal) -> +Darkswarm.factory "OfnMap", (Enterprises, EnterpriseListModal, MapConfiguration) -> new class OfnMap constructor: -> - @enterprises = @enterprise_markers(Enterprises.enterprises) - @enterprises = @enterprises.filter (enterprise) -> - enterprise.latitude != null || enterprise.longitude != null # Remove enterprises w/o lat or long + @coordinates = {} + @enterprises = Enterprises.enterprises.filter (enterprise) -> + # Remove enterprises w/o lat or long + enterprise.latitude != null || enterprise.longitude != null + @enterprises = @enterprise_markers(@enterprises) enterprise_markers: (enterprises) -> @extend(enterprise) for enterprise in enterprises + enterprise_hash: (hash, enterprise) -> + hash[enterprise.id] = { id: enterprise.id, name: enterprise.name, icon: enterprise.icon_font } + hash + + extend_marker: (marker, enterprise) -> + marker.latitude = enterprise.latitude + marker.longitude = enterprise.longitude + marker.icon = enterprise.icon + marker.id = [enterprise.id] + marker.enterprises = @enterprise_hash({}, enterprise) + # Adding methods to each enterprise extend: (enterprise) -> - new class MapMarker - # We cherry-pick attributes because GMaps tries to crawl - # our data, and our data is cyclic, so it breaks - latitude: enterprise.latitude - longitude: enterprise.longitude - icon: enterprise.icon - id: enterprise.id - reveal: => - EnterpriseModal.open enterprise + marker = @coordinates[[enterprise.latitude, enterprise.longitude]] + if marker + marker.icon = MapConfiguration.options.cluster_icon + @enterprise_hash(marker.enterprises, enterprise) + marker.id.push(enterprise.id) + else + marker = new class MapMarker + # We cherry-pick attributes because GMaps tries to crawl + # our data, and our data is cyclic, so it breaks + reveal: => + EnterpriseListModal.open this.enterprises + @extend_marker(marker, enterprise) + @coordinates[[enterprise.latitude, enterprise.longitude]] = marker + marker diff --git a/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee b/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee index 1979affeab..93e4104f0a 100644 --- a/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee +++ b/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee @@ -4,6 +4,7 @@ Darkswarm.factory "MapConfiguration", -> center: latitude: -37.4713077 longitude: 144.7851531 + cluster_icon: 'assets/map_009-cluster.svg' zoom: 12 additional_options: # mapTypeId: 'satellite' diff --git a/app/assets/javascripts/templates/enterprise_list_modal.html.haml b/app/assets/javascripts/templates/enterprise_list_modal.html.haml new file mode 100644 index 0000000000..046029d829 --- /dev/null +++ b/app/assets/javascripts/templates/enterprise_list_modal.html.haml @@ -0,0 +1,2 @@ +%ng-include{src: "'partials/enterprise_listing.html'"} +%ng-include{src: "'partials/close.html'"} diff --git a/app/assets/javascripts/templates/filter_selector.html.haml b/app/assets/javascripts/templates/filter_selector.html.haml index cd4135ebee..742ace2a0e 100644 --- a/app/assets/javascripts/templates/filter_selector.html.haml +++ b/app/assets/javascripts/templates/filter_selector.html.haml @@ -1,4 +1,3 @@ %ul %active-selector{ ng: { repeat: "selector in allSelectors", show: "ifDefined(selector.fits, true)" } } - %render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} } %span{"ng-bind" => "::selector.object.name"} diff --git a/app/assets/javascripts/templates/partials/enterprise_listing.html.haml b/app/assets/javascripts/templates/partials/enterprise_listing.html.haml new file mode 100644 index 0000000000..74485865c0 --- /dev/null +++ b/app/assets/javascripts/templates/partials/enterprise_listing.html.haml @@ -0,0 +1,10 @@ +.modal-list + .row{"ng-repeat" => "(id, enterprise) in ::enterprises"} + .highlight + .highlight-top.row.enterprise + .small-12.medium-12.large-12.columns + %h4 + %a.heading{"ng-click" => "::openModal(enterprise)"} + %i{"ng-class" => "enterprise.icon"} + %span{"ng-bind" => "enterprise.name"} + %img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"} diff --git a/app/assets/javascripts/templates/price_breakdown_button.html.haml b/app/assets/javascripts/templates/price_breakdown_button.html.haml index 5eff1ef237..1aa104eaca 100644 --- a/app/assets/javascripts/templates/price_breakdown_button.html.haml +++ b/app/assets/javascripts/templates/price_breakdown_button.html.haml @@ -1,2 +1,2 @@ -%button.graph-button{"ng-class" => "{open: tt_isOpen}"} +%button.graph-button{"ng-class" => "{open: tt_isOpen}", type: 'button'} / %i.ofn-i_058-graph diff --git a/app/assets/stylesheets/darkswarm/_shop-filters.css.scss b/app/assets/stylesheets/darkswarm/_shop-filters.css.scss index 16bca77cbc..5bb8b5e381 100644 --- a/app/assets/stylesheets/darkswarm/_shop-filters.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-filters.css.scss @@ -2,6 +2,7 @@ @import "branding"; @import "big-input"; @import "animations"; +@import "variables"; @mixin filter-selector($base-clr, $border-clr, $hover-clr) { &.inline-block, ul.inline-block { @@ -14,7 +15,7 @@ @include border-radius(0); padding: 0; - margin: 0 0 0.25rem 0.25rem; + margin: 0 0.5rem 0.5rem 0; &:hover, &:focus { background: transparent; @@ -107,26 +108,63 @@ // Alert when search, taxon, filter is triggered .alert-box.search-alert { - background-color: $clr-yellow-light; - border-color: $clr-yellow-light; + background-color: $white; color: #777; - font-size: 0.75rem; - padding: 0.5rem 0.75rem; + font-size: 1em; + padding: 0.35em 0 0; + border: 0; + margin: 0; - span.applied-properties { - color: #333; + .clear-all { + color: $grey-500; + margin-left: 1.5em; + + &:hover { + color: $grey-600; + } } - span.applied-taxons { - color: $clr-blue; + .no-results-bar { + @include breakpoint(desktop) { + text-align: center; + } } - span.applied-search { - color: $clr-brick; + .no-results { + color: $grey-800; + font-style: italic; + font-size: 1.25em; } - span.filter-label { - opacity: 0.75; + .clear-search { + background-color: transparent; + padding: 0; + margin: 0; + color: $orange-500; + font-size: 1.25em; + + &:hover { + color: $orange-400; + } + } + + span { + color: $grey-800; + font-style: italic; + + &.applied-taxons, &.applied-properties { + color: $clr-blue; + font-weight: bold; + + .join-word { + font-weight: normal; + } + } + + &.applied-search { + font-weight: bold; + color: $teal-500; + } } } @@ -140,25 +178,6 @@ .filter-shopfront { &.taxon-selectors, &.property-selectors { background: transparent; - - single-line-selectors { - overflow-x: hidden; - white-space: nowrap; - - .f-dropdown { - overflow-x: auto; - white-space: normal; - } - } - - ul { - margin: 0; - display: inline-block; - } - - ul, ul li { - list-style: none; - } } // Shopfront taxons @@ -170,4 +189,8 @@ &.property-selectors { @include filter-selector(#666, #ccc, #777); } + + ul { + margin: 0; + } } diff --git a/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss b/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss index 3cf5d6e497..59a3bd0c67 100644 --- a/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss @@ -7,13 +7,6 @@ // #search @include placeholder(rgba(0, 0, 0, 0.4), #777); - input#search { - @include medium-input(rgba(0, 0, 0, 0.3), #777, $clr-brick); - - // avoid zoom on iphone, see issue #4535 - font-size: 1rem; - } - // ordering product { input { @@ -30,22 +23,22 @@ border-color: #b3b3b3; text-align: right; - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { width: 8rem; } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { width: 7rem; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { float: left !important; font-size: 0.75rem; padding-left: 0.25rem; padding-right: 0.25rem; } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { width: 5.8rem; } @@ -69,15 +62,15 @@ input.bulk { width: 5rem; - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { width: 4rem; } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { width: 3.5rem; } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { width: 2.8rem; } } @@ -93,7 +86,7 @@ .bulk-input-container { float: right; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { float: left !important; } diff --git a/app/assets/stylesheets/darkswarm/_shop-navigation.css.scss b/app/assets/stylesheets/darkswarm/_shop-navigation.css.scss index 80c2bfe09a..95bd6b1059 100644 --- a/app/assets/stylesheets/darkswarm/_shop-navigation.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-navigation.css.scss @@ -1,11 +1,13 @@ +@import "mixins"; @import "typography"; +@import "variables"; ordercycle { float: right; background: $grey-050; color: $grey-800; width: 100%; - border-radius: 0.5em 0.5em 0 0; + border-radius: $radius-medium $radius-medium 0 0; margin-top: 1em; padding: 1em 1.25em 0; @@ -17,7 +19,7 @@ ordercycle { margin-right: 0.3rem; } - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { float: none; padding: 0.5em 1em; width: 100%; @@ -33,7 +35,7 @@ ordercycle { } } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { padding: 0.5em 1em 0.75em; } @@ -41,15 +43,15 @@ ordercycle { border: 1px solid $teal-300; display: inline-block; font-size: 1em; - border-radius: 0.25em; + border-radius: $radius-small; .select-label { background-color: rgba($teal-300, 0.5); display: inline-block; - border-radius: 0.25em 0 0 0.25em; + border-radius: $radius-small 0 0 $radius-small; float: left; font-size: 1em; - line-height: 1.5em; + line-height: 1.3em; padding: 0.5em 0.75em; height: 2.35em; @@ -60,6 +62,15 @@ ordercycle { } select { + background-image: url('/assets/white-caret.svg'); + } + + p { + text-align: left; + } + + select, + p { width: inherit; display: inline-block; color: $white; @@ -67,29 +78,34 @@ ordercycle { border: 0; margin-bottom: 0; font-size: 1em; - line-height: 1.5em; + line-height: 1.3em; padding: 0.5em 1.25em 0.5em 0.75em; height: 2.35em; - background-image: url('/assets/white-caret.svg'); background-size: 30px auto; - border-radius: 0 0.25em 0.25em 0; + border-radius: 0 $radius-small $radius-small 0; min-width: 13em; - @media all and (max-width: 480px) { + @include breakpoint(mobile) { width: 100%; + min-width: 0; } } - @media all and (max-width: 1024px) { + option { + color: $grey-700; + } + + @include breakpoint(desktop) { float: none; margin-right: 1em; } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { float: none; + margin-right: 0; } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { display: flex; } } @@ -102,7 +118,7 @@ ordercycle { padding: 0.5em 0; span { - @media all and (max-width: 768px) { + @include breakpoint(tablet) { font-size: 0.875em; } } @@ -136,7 +152,7 @@ shop ordercycle { color: $white; padding: 0 0 12px; - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { float: none; display: inline-block; padding: 0.2em 0 0; @@ -144,7 +160,7 @@ shop ordercycle { margin-right: 1em; } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { float: none; padding: 0 0 10px; } diff --git a/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss b/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss index d233284767..dd5aa5a177 100644 --- a/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss @@ -114,7 +114,7 @@ button.graph-button { } } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { // Hide for small display: none; } diff --git a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss index d98457cd73..b0ea1faf39 100644 --- a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss @@ -1,3 +1,4 @@ +@import "mixins"; @import "branding"; @import "animations"; @@ -14,11 +15,11 @@ // outline: 1px solid red - @media all and (max-width: 768px) { + @include breakpoint(tablet) { font-size: 0.875rem; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { font-size: 0.75rem; } } @@ -56,13 +57,13 @@ .variant-name { padding-left: 7.9375rem; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { padding-left: 4.9375rem; } } .variant-name { - @media all and (max-width: 640px) { + @include breakpoint(phablet) { background: #333; color: white; padding-left: 0.9375rem; @@ -82,7 +83,7 @@ font-size: 0.875rem; overflow: hidden; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { font-size: 0.75rem; } } @@ -92,7 +93,7 @@ padding-left: 0.25rem; padding-right: 0.25rem; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { text-align: right; } } @@ -106,7 +107,7 @@ color: $med-drk-grey; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { background: #777; color: $disabled-med; @@ -132,7 +133,7 @@ padding-bottom: 1em; line-height: 1; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { padding-top: 0.65rem; padding-bottom: 0.65rem; } @@ -141,11 +142,11 @@ .summary-header { padding-left: 7.9375rem; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { padding-left: 4.9375rem; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { padding-left: 0.9375rem; } diff --git a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss index 8a75467c9c..c9f8794295 100644 --- a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss @@ -1,3 +1,4 @@ +@import "mixins"; @import "branding"; @import "animations"; @@ -56,7 +57,7 @@ } } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { top: 2px; width: 4rem; height: 4rem; @@ -70,7 +71,7 @@ } } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { display: none; width: 0rem; height: 0rem; diff --git a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss index 6a2c993600..e1b0d8ccbe 100644 --- a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss +++ b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss @@ -1,3 +1,5 @@ +@import "mixins"; + .darkswarm { products { product { @@ -10,7 +12,7 @@ padding-top: 0.25rem; z-index: 999999; - @media all and (max-width: 480px) { + @include breakpoint(mobile) { background-size: 28px 32px; min-height: 32px; width: 28px; @@ -27,11 +29,11 @@ } } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { margin-top: -0.85rem; } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { render-svg { svg { width: 18px; diff --git a/app/assets/stylesheets/darkswarm/account.css.scss b/app/assets/stylesheets/darkswarm/account.css.scss index 44d93b4cd1..bb6701f15d 100644 --- a/app/assets/stylesheets/darkswarm/account.css.scss +++ b/app/assets/stylesheets/darkswarm/account.css.scss @@ -1,5 +1,6 @@ @import "branding"; @import "mixins"; +@import "variables"; .account-summary { color: #4a4a4a; @@ -99,7 +100,7 @@ table { width: 100%; - border-radius: 0.5em 0.5em 0 0; + border-radius: $radius-medium $radius-medium 0 0; tr:nth-of-type(even) { background: transparent; diff --git a/app/assets/stylesheets/darkswarm/active_table.css.scss b/app/assets/stylesheets/darkswarm/active_table.css.scss index 66dca7743b..395ad7c876 100644 --- a/app/assets/stylesheets/darkswarm/active_table.css.scss +++ b/app/assets/stylesheets/darkswarm/active_table.css.scss @@ -26,7 +26,7 @@ display: block; border: 0; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { margin-bottom: 1rem; } @@ -45,7 +45,7 @@ } // Generic text resize - @media all and (max-width: 640px) { + @include breakpoint(phablet) { &, & * { font-size: 0.875rem; } @@ -114,7 +114,7 @@ .fat > div { border-top: 1px solid #aaa; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { margin-top: 1em; } diff --git a/app/assets/stylesheets/darkswarm/active_table_search.css.scss b/app/assets/stylesheets/darkswarm/active_table_search.css.scss index b934116cdc..1c26fcb74f 100644 --- a/app/assets/stylesheets/darkswarm/active_table_search.css.scss +++ b/app/assets/stylesheets/darkswarm/active_table_search.css.scss @@ -16,7 +16,7 @@ margin-top: 2px; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { margin-bottom: 1em; } } diff --git a/app/assets/stylesheets/darkswarm/big-input.scss b/app/assets/stylesheets/darkswarm/big-input.scss index 7b9ecdac8d..10336e30c6 100644 --- a/app/assets/stylesheets/darkswarm/big-input.scss +++ b/app/assets/stylesheets/darkswarm/big-input.scss @@ -23,7 +23,7 @@ box-shadow: none; color: $inputactv; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { font-size: 1.25rem; } diff --git a/app/assets/stylesheets/darkswarm/branding.css.scss b/app/assets/stylesheets/darkswarm/branding.css.scss index ca75eaa227..de705bbfa3 100644 --- a/app/assets/stylesheets/darkswarm/branding.css.scss +++ b/app/assets/stylesheets/darkswarm/branding.css.scss @@ -38,14 +38,18 @@ $med-drk-grey: #444; $dark-grey: #333; $light-grey: #ddd; $light-grey-transparency: rgba(0, 0, 0, .1); +$very-light-grey-transparency: rgba(0, 0, 0, .05); $black: #000; $white: #fff; $grey-050: #f7f7f7; - +$grey-100: #e6e6e6; +$grey-200: #ddd; +$grey-300: #ccc; $grey-400: #bbb; $grey-500: #999; $grey-600: #777; +$grey-650: #666; $grey-700: #555; $grey-800: #333; diff --git a/app/assets/stylesheets/darkswarm/checkout.css.scss b/app/assets/stylesheets/darkswarm/checkout.css.scss index 2e1a2a528c..df51d9de34 100644 --- a/app/assets/stylesheets/darkswarm/checkout.css.scss +++ b/app/assets/stylesheets/darkswarm/checkout.css.scss @@ -13,7 +13,7 @@ checkout { display: block; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { &.row .row { margin-left: 0; margin-right: 0; @@ -24,7 +24,7 @@ checkout { .button, table { width: 100%; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { form.edit_order { border: 1px solid $disabled-bright; margin-bottom: 2rem; diff --git a/app/assets/stylesheets/darkswarm/distributor_header.css.scss b/app/assets/stylesheets/darkswarm/distributor_header.css.scss index 8e3fdc2a9b..8e9ebf769b 100644 --- a/app/assets/stylesheets/darkswarm/distributor_header.css.scss +++ b/app/assets/stylesheets/darkswarm/distributor_header.css.scss @@ -1,3 +1,4 @@ +@import "mixins"; @import 'typography'; section { @@ -34,7 +35,7 @@ section { @include headingFont; } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { location, location + small { display: block; } @@ -44,7 +45,7 @@ section { margin-top: 0; padding-top: 0.45em; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { margin-bottom: 8px; } } diff --git a/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss b/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss index c1206ecc5e..210825a008 100644 --- a/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss +++ b/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss @@ -1,3 +1,4 @@ +@import "mixins"; @import "typography"; $large-menu-height: 4.6875rem; @@ -97,7 +98,7 @@ body.embedded { display: none; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { nav.top-bar { height: 3.4rem; padding: 0.2rem $gutter-width; @@ -141,7 +142,7 @@ body.embedded { } } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { ul.left li.powered-by span { display: none; } diff --git a/app/assets/stylesheets/darkswarm/home_panes.css.scss b/app/assets/stylesheets/darkswarm/home_panes.css.scss index 92053189e2..60f881462d 100644 --- a/app/assets/stylesheets/darkswarm/home_panes.css.scss +++ b/app/assets/stylesheets/darkswarm/home_panes.css.scss @@ -66,7 +66,7 @@ font-weight: 300; } - @media all and (max-width: 768px) { + @include breakpoint(tablet) { h2 { font-size: 52px; } @@ -87,7 +87,7 @@ padding-bottom: 0; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { .row .row { padding: 0; } @@ -139,7 +139,7 @@ font-weight: 300; color: $brand-colour; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { font-size: 45px; } } diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.scss b/app/assets/stylesheets/darkswarm/hub_node.css.scss index cf0ae9ca46..f7e1ac5686 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.scss +++ b/app/assets/stylesheets/darkswarm/hub_node.css.scss @@ -45,7 +45,7 @@ } //Hub Link - @media all and (max-width: 640px) { + @include breakpoint(phablet) { a.hub { display: block; } @@ -67,7 +67,7 @@ .active_table_row { border: 1px solid transparent; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { border-color: $clr-brick-light; } @@ -85,7 +85,7 @@ } &.open, &.closed { - @media all and (max-width: 640px) { + @include breakpoint(phablet) { .active_table_row:first-child .skinny-head { background-color: $clr-brick-light; @@ -164,7 +164,7 @@ } } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { .active_table_row:first-child .skinny-head { background-color: rgba(255, 255, 255, 0.85); } @@ -218,7 +218,7 @@ } // Small devices - @media all and (max-width: 640px) { + @include breakpoint(phablet) { .active_table_row:first-child .skinny-head { background-color: $disabled-bright; } @@ -226,7 +226,7 @@ } // Small devices - @media all and (max-width: 640px) { + @include breakpoint(phablet) { .active_table_row, .active_table_row:first-child, .active_table_row:last-child { border-color: $disabled-bright; background-color: transparent; @@ -253,7 +253,7 @@ cursor: auto; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { border-color: transparent; } } diff --git a/app/assets/stylesheets/darkswarm/images.css.scss b/app/assets/stylesheets/darkswarm/images.css.scss index 2a9f931f6e..cd939e3b42 100644 --- a/app/assets/stylesheets/darkswarm/images.css.scss +++ b/app/assets/stylesheets/darkswarm/images.css.scss @@ -13,7 +13,7 @@ &.placeholder { opacity: 0.35; - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { display: none; } } @@ -31,7 +31,7 @@ max-height: 260px; overflow: hidden; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { min-height: 68px; } } diff --git a/app/assets/stylesheets/darkswarm/map.css.scss b/app/assets/stylesheets/darkswarm/map.css.scss index d880b3025b..b0df5b66da 100644 --- a/app/assets/stylesheets/darkswarm/map.css.scss +++ b/app/assets/stylesheets/darkswarm/map.css.scss @@ -1,6 +1,7 @@ // Place all the styles related to the map controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ +@import "mixins"; @import "big-input"; .map-container { @@ -29,7 +30,7 @@ margin-top: 1.2rem; margin-left: 1rem; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { width: 80%; } diff --git a/app/assets/stylesheets/darkswarm/menu.css.scss b/app/assets/stylesheets/darkswarm/menu.css.scss index 23c2c8ae68..abefec7a23 100644 --- a/app/assets/stylesheets/darkswarm/menu.css.scss +++ b/app/assets/stylesheets/darkswarm/menu.css.scss @@ -171,7 +171,7 @@ nav.top-bar { .tab-bar { background-color: white; border-bottom: 1px solid $light-grey-transparency; - height: 2.8em; + height: $mobile-nav-height; position: fixed; width: 100%; z-index: 1; @@ -210,6 +210,10 @@ nav.top-bar { } } +.off-canvas-wrap { + overflow: inherit; +} + .off-canvas-list li.language-switcher ul li { list-style-type: none; padding-left: 0.5em; diff --git a/app/assets/stylesheets/darkswarm/mixins.scss b/app/assets/stylesheets/darkswarm/mixins.scss index 0643294c0c..2b38d89dea 100644 --- a/app/assets/stylesheets/darkswarm/mixins.scss +++ b/app/assets/stylesheets/darkswarm/mixins.scss @@ -16,7 +16,7 @@ padding-top: 100px; padding-bottom: 100px; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { padding-top: 25px; } } @@ -255,3 +255,18 @@ background-repeat: no-repeat; background-size: 922px 922px; } + +@mixin breakpoint($point) { + @if $point == desktop { + @media all and (max-width: 1024px) { @content; } + } + @else if $point == tablet { + @media all and (max-width: 768px) { @content; } + } + @else if $point == phablet { + @media all and (max-width: 640px) { @content; } + } + @else if $point == mobile { + @media all and (max-width: 480px) { @content; } + } +} diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss b/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss index 5d356028f8..c72aa79f71 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss @@ -1,5 +1,6 @@ -@import "branding"; -@import "mixins"; +@import 'branding'; +@import 'mixins'; +@import 'admin/globals/variables'; // Generic styles for use @@ -22,6 +23,24 @@ margin-bottom: 0.5rem; } +.modal-list { + text-align: center; + font-size: 1rem; + font-weight: 400; + border-bottom: 1px solid $light-grey; + margin-top: 0.75rem; + margin-bottom: 0.5rem; + + a.heading { + color: $color-link; + + &:hover { + color: $color-link-hover; + text-decoration: underline; + } + } +} + // Enterprise promo image and text .highlight { @@ -54,10 +73,16 @@ color: $clr-brick; } + &.enterprise { + margin: auto; + text-align: center; + width: 100%; + } + p { line-height: 2.4; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { line-height: 1.4; } } @@ -193,7 +218,7 @@ display: inline-block; border-bottom: 1px solid transparent; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { display: none; } } diff --git a/app/assets/stylesheets/darkswarm/page_alert.css.scss b/app/assets/stylesheets/darkswarm/page_alert.css.scss index 59ccf327d0..4be82c8c2e 100644 --- a/app/assets/stylesheets/darkswarm/page_alert.css.scss +++ b/app/assets/stylesheets/darkswarm/page_alert.css.scss @@ -1,3 +1,4 @@ +@import "mixins"; @import "branding"; @import "animations"; @import "compass/css3/transition"; @@ -19,7 +20,7 @@ $page-alert-height: 55px; margin: 0; h6 { - @media all and (max-width: 480px) { + @include breakpoint(mobile) { font-size: 10px; line-height: 24px; } diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.scss b/app/assets/stylesheets/darkswarm/producer_node.css.scss index 770a76564f..707c5d903b 100644 --- a/app/assets/stylesheets/darkswarm/producer_node.css.scss +++ b/app/assets/stylesheets/darkswarm/producer_node.css.scss @@ -4,7 +4,7 @@ .producers { .active_table .active_table_node { // Header row - @media all and (max-width: 640px) { + @include breakpoint(phablet) { .skinny-head { background-color: $clr-turquoise-light; @@ -137,7 +137,7 @@ .active_table_row.closed { border: 1px solid transparent; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { border-color: $clr-turquoise-light; } diff --git a/app/assets/stylesheets/darkswarm/shop.css.scss b/app/assets/stylesheets/darkswarm/shop.css.scss index 71fe929f89..6c477acb48 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.scss +++ b/app/assets/stylesheets/darkswarm/shop.css.scss @@ -11,18 +11,98 @@ @import "shop-taxon-flag"; @import "shop-popovers"; +$sidebar-small-width: 75%; +$sidebar-medium-width: 65%; +$sidebar-large-width: 45%; +$sidebar-footer-height: 5em; + .darkswarm { + .shop-filters-sidebar { + display: flex; + flex-direction: column; + height: 100%; + + .background { + position: fixed; + top: 0; + right: 0; + z-index: 200; + height: 100%; + width: 100%; + background-color: $shop-sidebar-overlay; + opacity: 0; + transition: opacity $transition-sidebar; + } + + &.shown { + .background { + opacity: 1; + } + + .sidebar, .sidebar-footer { + margin-right: 0; + } + } + + .sidebar { + position: fixed; + top: 0; + right: 0; + z-index: 210; + height: 100%; + width: $sidebar-large-width; + margin-right: -$sidebar-large-width; + background-color: rgba($white, 0.95); + padding: 1em; + transition: margin $transition-sidebar; + overflow-y: scroll; + + .property-selectors { + margin-bottom: $sidebar-footer-height + 2em; + } + } + + .sidebar-footer { + background-color: $grey-800; + width: $sidebar-large-width; + margin-right: -$sidebar-large-width; + height: $sidebar-footer-height; + position: fixed; + bottom: 0; + right: 0; + transition: margin $transition-sidebar; + padding: 1em; + + button { + width: 48%; + } + } + + @include breakpoint(tablet) { + .sidebar, .sidebar-footer { + width: $sidebar-medium-width; + margin-right: -$sidebar-medium-width; + } + } + + @include breakpoint(mobile) { + .sidebar, .sidebar-footer { + width: $sidebar-small-width; + margin-right: -$sidebar-small-width; + } + } + } + products { display: block; - padding-top: 20px; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { input.button.right { float: left; } } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { .add_to_cart { margin-top: 2rem; } @@ -69,7 +149,7 @@ .bulk-buy { font-size: 0.875rem; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { font-size: 0.75rem; } } @@ -92,7 +172,7 @@ font-size: 0.75em; padding-right: 0.9375rem; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { padding-right: 0.25rem; } } @@ -114,7 +194,16 @@ margin-bottom: 0px; } - .shopfront-message { + .select-oc-message { + margin-top: 1rem; + + .highlighted { + color: $red-700; + font-weight: bold; + } + } + + .open-shop-message { a { color: #0096ad; @@ -125,21 +214,44 @@ } } - .shopfront_closed_message, .shopfront_hidden_message { - padding: 15px; - border-radius: 5px; + .closed-shop-header { + background-color: $grey-650; + color: $white; + + h4 { + color: $white; + } + + p { + margin: 1rem 0 0.4rem; + } + + .message { + display: inline-block; + } } - .shopfront_closed_message { - border: 2px solid #eb4c46; - } + .warning-sign { + margin: 0 10px 0 5px; + display: inline-block; - .shopfront_closed_message { - margin: 2em 0em; - } + strong { + color: $grey-650; + display: block; + position: relative; + text-align: center; + width: 23px; + } - .shopfront_hidden_message { - border: 2px solid #db4; - margin: 2em 0em; + .rectangle { + background-color: $white; + border-radius: 4px; + color: $grey-650; + height: 23px; + position: absolute; + top: 27px; + transform: rotate(-315deg); + width: 23px; + } } } diff --git a/app/assets/stylesheets/darkswarm/shop_search.css.scss b/app/assets/stylesheets/darkswarm/shop_search.css.scss new file mode 100644 index 0000000000..cbe0bf6bd4 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/shop_search.css.scss @@ -0,0 +1,75 @@ +@import "mixins"; +@import "branding"; +@import "variables"; + +.shop-searchbar { + background-color: $grey-100; + height: 5em; + padding: 1em 0; + margin-bottom: 1em; + position: relative; + z-index: 5; + + .search-wrap { + position: relative; + width: 100%; + display: inline-flex; + + .clear { + height: 1em; + width: 1em; + margin-top: 1em; + position: absolute; + right: 1em; + } + } + + input#search { + height: 3em; + border-radius: $radius-small; + border: solid 1px $grey-300; + margin: 0; + padding: 0 2.25em 0 2.75em; + width: 100%; + min-width: 0; + background: $white url("/assets/icn-search-grey.png") 1em center no-repeat; + font-size: 1rem; // avoid zoom on iphone, see issue #4535 + + &::placeholder { + font-style: italic; + } + + // Remove conflicting "clear search" buttons added by Chrome + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + display: none; + } + } + + button { + background-color: $grey-600; + margin-left: 1em; + height: 3em; + width: 7em; + padding: 0; + font-size: 1em; + border-radius: $radius-small; + transition: none; + + &:hover { + background-color: $grey-700; + } + + @include breakpoint(mobile) { + margin-left: 0.75em; + } + } + + @include breakpoint(desktop) { + position: -webkit-sticky; + position: sticky; + top: $mobile-nav-height; + } +} diff --git a/app/assets/stylesheets/darkswarm/shop_tabs.css.scss b/app/assets/stylesheets/darkswarm/shop_tabs.css.scss index 7eaf76afeb..66bbb8998e 100644 --- a/app/assets/stylesheets/darkswarm/shop_tabs.css.scss +++ b/app/assets/stylesheets/darkswarm/shop_tabs.css.scss @@ -8,16 +8,18 @@ .tab-buttons { color: $dark-grey; box-shadow: $distributor-header-shadow; + position: relative; + z-index: 10; .columns { display: flex; - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { display: table; width: 100%; } - @media all and (max-width: 480px) { + @include breakpoint(mobile) { padding: 0; } } @@ -54,7 +56,7 @@ background: none; } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { padding: 0.35em 0 0.65em 0; } } @@ -67,7 +69,7 @@ } } - @media all and (max-width: 1024px) { + @include breakpoint(desktop) { display: table-cell; width: auto; } @@ -76,9 +78,9 @@ // content revealed in accordion .page-view { - margin-bottom: 5em; background: none; border: none; + padding-bottom: 5em; .content { padding: 1.25em 0; @@ -104,7 +106,7 @@ p { max-width: 100%; - @media all and (max-width: 768px) { + @include breakpoint(tablet) { height: auto !important; } } @@ -121,5 +123,13 @@ margin-bottom: 2px; } } + + &.with-darker-background { + background-color: $very-light-grey-transparency; + + a { + color: $teal-500; + } + } } } diff --git a/app/assets/stylesheets/darkswarm/tabset.css.scss b/app/assets/stylesheets/darkswarm/tabset.css.scss index c100bff672..4f4ab3c7b9 100644 --- a/app/assets/stylesheets/darkswarm/tabset.css.scss +++ b/app/assets/stylesheets/darkswarm/tabset.css.scss @@ -10,7 +10,7 @@ .tab { text-align: center; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { text-align: left; } @@ -24,7 +24,7 @@ padding: 1em; border: none; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { padding: 0.35em 0 0.65em 0; text-shadow: none; } @@ -37,7 +37,7 @@ border-bottom: 4px solid $clr-brick-bright; cursor: pointer; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { transition: none; color: white; background-color: $clr-brick-bright; @@ -46,7 +46,7 @@ a { color: $clr-brick-bright; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { color: #ffffff; } } @@ -55,14 +55,14 @@ &.selected { border-bottom: 4px solid $clr-brick; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { background-color: $clr-brick; } a { color: $clr-brick; - @media all and (max-width: 640px) { + @include breakpoint(phablet) { color: #ffffff; } } diff --git a/app/assets/stylesheets/darkswarm/taxons.css.scss b/app/assets/stylesheets/darkswarm/taxons.css.scss index a5db89b324..006e15de58 100644 --- a/app/assets/stylesheets/darkswarm/taxons.css.scss +++ b/app/assets/stylesheets/darkswarm/taxons.css.scss @@ -57,7 +57,7 @@ } } - @media all and (max-width: 640px) { + @include breakpoint(phablet) { render-svg { svg { width: 24px; diff --git a/app/assets/stylesheets/darkswarm/ui.css.scss b/app/assets/stylesheets/darkswarm/ui.css.scss index 4a50c4a1a0..66d72d6511 100644 --- a/app/assets/stylesheets/darkswarm/ui.css.scss +++ b/app/assets/stylesheets/darkswarm/ui.css.scss @@ -2,6 +2,7 @@ @import "branding"; @import "mixins"; @import "typography"; +@import "variables"; // Button class extensions @@ -123,9 +124,37 @@ button.success, .button.success { } } +button.large { + height: 3em; + font-size: 1em; + color: $white; + border-radius: $radius-medium; + margin: 0; + padding: 0; + + &.dark { + background-color: $grey-800; + border: 1px solid $grey-600; + } + + &.bright { + background-color: $orange-500; + border: none; + } +} + // Responsive @media screen and (min-width: 768px) { [role="main"] { padding: 0; } } + +.flex { + display: flex; +} + +.no-gutter { + padding-right: 0; + padding-left: 0; +} diff --git a/app/assets/stylesheets/darkswarm/variables.css.scss b/app/assets/stylesheets/darkswarm/variables.css.scss index 0b8104cea1..115212fc76 100644 --- a/app/assets/stylesheets/darkswarm/variables.css.scss +++ b/app/assets/stylesheets/darkswarm/variables.css.scss @@ -30,3 +30,11 @@ $topbar-dropdown-link-color: $black; $topbar-dropdown-bg: $white; $topbar-dropdown-link-bg: $white; $topbar-dropdown-link-bg-hover: $white; + +$mobile-nav-height: 2.8em; + +$radius-small: 0.25em; +$radius-medium: 0.5em; + +$shop-sidebar-overlay: rgba(0, 0, 0, 0.5); +$transition-sidebar: 250ms ease-in-out 0s; diff --git a/app/controllers/api/order_cycles_controller.rb b/app/controllers/api/order_cycles_controller.rb index 1240f3ff0a..77689932e0 100644 --- a/app/controllers/api/order_cycles_controller.rb +++ b/app/controllers/api/order_cycles_controller.rb @@ -1,13 +1,17 @@ module Api class OrderCyclesController < Api::BaseController include EnterprisesHelper - respond_to :json + include ApiActionCaching skip_authorization_check skip_before_filter :authenticate_user, :ensure_api_key, only: [:taxons, :properties] + caches_action :taxons, :properties, + expires_in: CacheService::FILTERS_EXPIRY, + cache_path: proc { |controller| controller.request.url } + def products - render_no_products unless order_cycle.open? + return render_no_products unless order_cycle.open? products = ProductsRenderer.new( distributor, diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 0a39b00d9b..afde45d3bd 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -18,9 +18,9 @@ class HomeController < BaseController private - # Cache the value of the query count for 24 hours - def cached_count(key, query) - Rails.cache.fetch("home_stats_count_#{key}", expires_in: 1.day, race_condition_ttl: 10) do + # Cache the value of the query count + def cached_count(statistic, query) + CacheService.home_stats(statistic) do query.count end end diff --git a/app/helpers/shop_helper.rb b/app/helpers/shop_helper.rb index 5aae96e101..a4c89bcd0d 100644 --- a/app/helpers/shop_helper.rb +++ b/app/helpers/shop_helper.rb @@ -13,7 +13,7 @@ module ShopHelper end def require_customer? - current_distributor.require_login? && !user_is_related_to_distributor? + @require_customer ||= current_distributor.require_login? && !user_is_related_to_distributor? end def user_is_related_to_distributor? @@ -48,6 +48,6 @@ module ShopHelper end def no_open_order_cycles? - @order_cycles && @order_cycles.empty? + @no_open_order_cycles ||= @order_cycles&.empty? end end diff --git a/app/jobs/job_logger.rb b/app/jobs/job_logger.rb new file mode 100644 index 0000000000..c45f2fa32d --- /dev/null +++ b/app/jobs/job_logger.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: false + +module JobLogger + class Formatter < ::Logger::Formatter + def call(_severity, timestamp, _progname, msg) + time = timestamp.strftime('%FT%T%z') + "#{time}: #{msg.is_a?(String) ? msg : msg.inspect}\n" + end + end + + def self.logger + @logger ||= begin + logger = Delayed::Worker.logger.clone + logger.formatter = Formatter.new + logger + end + end +end diff --git a/app/jobs/subscription_confirm_job.rb b/app/jobs/subscription_confirm_job.rb index 748257a340..9ed8223d0e 100644 --- a/app/jobs/subscription_confirm_job.rb +++ b/app/jobs/subscription_confirm_job.rb @@ -24,7 +24,7 @@ class SubscriptionConfirmJob # Confirm these proxy orders ProxyOrder.where(id: unconfirmed_proxy_orders_ids).each do |proxy_order| - Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}" + JobLogger.logger.info "Confirming Order for Proxy Order #{proxy_order.id}" confirm_order!(proxy_order.order) end diff --git a/app/jobs/subscription_placement_job.rb b/app/jobs/subscription_placement_job.rb index 44a4a27c75..adfbe5cba1 100644 --- a/app/jobs/subscription_placement_job.rb +++ b/app/jobs/subscription_placement_job.rb @@ -28,7 +28,7 @@ class SubscriptionPlacementJob end def place_order_for(proxy_order) - Rails.logger.info "Placing Order for Proxy Order #{proxy_order.id}" + JobLogger.logger.info("Placing Order for Proxy Order #{proxy_order.id}") proxy_order.initialise_order! place_order(proxy_order.order) end diff --git a/app/models/concerns/api_action_caching.rb b/app/models/concerns/api_action_caching.rb new file mode 100644 index 0000000000..b3e030e408 --- /dev/null +++ b/app/models/concerns/api_action_caching.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# API controllers inherit from ActionController::Metal to keep them slim and fast. +# This concern adds the minimum requirements needed to use Action Caching in the API. + +module ApiActionCaching + extend ActiveSupport::Concern + + included do + include ActionController::Caching + include ActionController::Caching::Actions + include AbstractController::Layouts + + # These configs are not assigned to the controller automatically with ActionController::Metal + self.cache_store = Rails.configuration.cache_store + self.perform_caching = true + + # ActionController::Caching asks for a controller's layout, but they're not used in the API + layout false + end +end diff --git a/app/models/distributor_shipping_method.rb b/app/models/distributor_shipping_method.rb index 5c69d18084..a2c81506dd 100644 --- a/app/models/distributor_shipping_method.rb +++ b/app/models/distributor_shipping_method.rb @@ -1,5 +1,5 @@ class DistributorShippingMethod < ActiveRecord::Base self.table_name = "distributors_shipping_methods" - belongs_to :shipping_method, class_name: Spree::ShippingMethod + belongs_to :shipping_method, class_name: Spree::ShippingMethod, touch: true belongs_to :distributor, class_name: Enterprise, touch: true end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index dca69869f7..066254b4f3 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -16,7 +16,8 @@ class OrderCycle < ActiveRecord::Base has_many :suppliers, -> { uniq }, source: :sender, through: :cached_incoming_exchanges has_many :distributors, -> { uniq }, source: :receiver, through: :cached_outgoing_exchanges - has_and_belongs_to_many :schedules, join_table: 'order_cycle_schedules' + has_many :schedules, through: :order_cycle_schedules + has_many :order_cycle_schedules has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } attr_accessor :incoming_exchanges, :outgoing_exchanges diff --git a/app/models/order_cycle_schedule.rb b/app/models/order_cycle_schedule.rb new file mode 100644 index 0000000000..bff4da5fc3 --- /dev/null +++ b/app/models/order_cycle_schedule.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class OrderCycleSchedule < ActiveRecord::Base + belongs_to :schedule + belongs_to :order_cycle +end diff --git a/app/models/schedule.rb b/app/models/schedule.rb index 4f5e84768f..c28b9ae029 100644 --- a/app/models/schedule.rb +++ b/app/models/schedule.rb @@ -1,7 +1,8 @@ class Schedule < ActiveRecord::Base - has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules' has_paper_trail meta: { custom_data: proc { |schedule| schedule.order_cycle_ids.to_s } } + has_many :order_cycles, through: :order_cycle_schedules + has_many :order_cycle_schedules, dependent: :destroy has_many :coordinators, -> { uniq }, through: :order_cycles validates :order_cycles, presence: true diff --git a/app/models/spree/classification_decorator.rb b/app/models/spree/classification_decorator.rb index fec270bc14..20608bad94 100644 --- a/app/models/spree/classification_decorator.rb +++ b/app/models/spree/classification_decorator.rb @@ -1,5 +1,6 @@ Spree::Classification.class_eval do belongs_to :product, class_name: "Spree::Product", touch: true + belongs_to :taxon, class_name: "Spree::Taxon", touch: true before_destroy :dont_destroy_if_primary_taxon diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index e4361a07dd..35f8af598d 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -12,7 +12,7 @@ Spree::Product.class_eval do has_many :option_types, through: :product_option_types, dependent: :destroy belongs_to :supplier, class_name: 'Enterprise', touch: true - belongs_to :primary_taxon, class_name: 'Spree::Taxon' + belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true delegate_belongs_to :master, :unit_value, :unit_description delegate :images_attributes=, :display_as=, to: :master diff --git a/app/models/spree/stock/quantifier.rb b/app/models/spree/stock/quantifier.rb index 8c437dc580..21f1750ca0 100644 --- a/app/models/spree/stock/quantifier.rb +++ b/app/models/spree/stock/quantifier.rb @@ -11,6 +11,10 @@ module Spree end def total_on_hand + # Associated stock_items no longer exist if the variant has been soft-deleted. A variant + # may still be in an active cart after it's deleted, so this will mark it as out of stock. + return 0 if @variant.deleted? + stock_items.sum(&:count_on_hand) end diff --git a/app/serializers/api/admin/order_serializer.rb b/app/serializers/api/admin/order_serializer.rb index b00aee730b..47fce01441 100644 --- a/app/serializers/api/admin/order_serializer.rb +++ b/app/serializers/api/admin/order_serializer.rb @@ -3,7 +3,7 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer :edit_path, :state, :payment_state, :shipment_state, :payments_path, :ready_to_ship, :ready_to_capture, :created_at, :distributor_name, :special_instructions, - :item_total, :adjustment_total, :payment_total, :total + :item_total, :adjustment_total, :payment_total, :total, :display_outstanding_balance has_one :distributor, serializer: Api::Admin::IdSerializer has_one :order_cycle, serializer: Api::Admin::IdSerializer @@ -16,6 +16,12 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer object.distributor.andand.name end + def display_outstanding_balance + return "" if object.outstanding_balance.zero? + + object.display_outstanding_balance.to_s + end + def edit_path return '' unless object.id diff --git a/app/serializers/api/cached_enterprise_serializer.rb b/app/serializers/api/cached_enterprise_serializer.rb index 8a6726e503..17588d4a67 100644 --- a/app/serializers/api/cached_enterprise_serializer.rb +++ b/app/serializers/api/cached_enterprise_serializer.rb @@ -62,10 +62,14 @@ module Api end def supplied_taxons + return [] unless enterprise.is_primary_producer + ids_to_objs data.supplied_taxons[enterprise.id] end def supplied_properties + return [] unless enterprise.is_primary_producer + (product_properties + producer_properties).uniq do |property_object| property_object.property.presentation end @@ -113,7 +117,7 @@ module Api end def active - data.active_distributor_ids.andand.include? enterprise.id + @active ||= data.active_distributor_ids.andand.include? enterprise.id end # Map svg icons. diff --git a/app/serializers/api/enterprise_shopfront_serializer.rb b/app/serializers/api/enterprise_shopfront_serializer.rb index 693f7442f5..035f8fde51 100644 --- a/app/serializers/api/enterprise_shopfront_serializer.rb +++ b/app/serializers/api/enterprise_shopfront_serializer.rb @@ -18,7 +18,8 @@ module Api end def active - enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists? + @active ||= + enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists? end def pickup @@ -73,12 +74,16 @@ module Api end def supplied_taxons + return [] unless enterprise.is_primary_producer + ActiveModel::ArraySerializer.new( enterprise.supplied_taxons, each_serializer: Api::TaxonSerializer ) end def supplied_properties + return [] unless enterprise.is_primary_producer + (product_properties + producer_properties).uniq do |property_object| property_object.property.presentation end @@ -118,7 +123,7 @@ module Api private def product_properties - enterprise.supplied_products.flat_map(&:properties) + enterprise.supplied_products.includes(:properties).flat_map(&:properties) end def producer_properties diff --git a/app/services/cache_service.rb b/app/services/cache_service.rb new file mode 100644 index 0000000000..1963b2f1dc --- /dev/null +++ b/app/services/cache_service.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class CacheService + HOME_STATS_EXPIRY = 1.day.freeze + FILTERS_EXPIRY = 30.seconds.freeze + SHOPS_EXPIRY = 15.seconds.freeze + + def self.cache(cache_key, options = {}) + Rails.cache.fetch cache_key.to_s, options do + yield + end + end + + # Yields a cached query, expired by the most recently updated record for a given class. + # E.g: if *any* Spree::Taxon record is updated, all keys based on Spree::Taxon will auto-expire. + def self.cached_data_by_class(cache_key, cached_class) + Rails.cache.fetch "#{cache_key}-#{cached_class}-#{latest_timestamp_by_class(cached_class)}" do + yield + end + end + + # Gets the :updated_at value of the most recently updated record for a given class, and returns + # it as a timestamp, eg: `1583836069`. + def self.latest_timestamp_by_class(cached_class) + cached_class.maximum(:updated_at).to_i + end + + def self.home_stats(statistic) + Rails.cache.fetch("home_stats_count_#{statistic}", + expires_in: HOME_STATS_EXPIRY, + race_condition_ttl: 10) do + yield + end + end + + module FragmentCaching + # Rails' caching in views is called "Fragment Caching" and uses some slightly different logic. + # Note: keys supplied here are actually prepended with "views/" under the hood. + + def self.ams_all_taxons_key + "inject-all-taxons-#{CacheService.latest_timestamp_by_class(Spree::Taxon)}" + end + + def self.ams_all_properties_key + "inject-all-properties-#{CacheService.latest_timestamp_by_class(Spree::Property)}" + end + + def self.ams_shops + [ + "shops/index/inject_enterprises", + { expires_in: SHOPS_EXPIRY } + ] + end + + def self.ams_shop(enterprise) + [ + "enterprises/shop/inject_enterprise_shopfront-#{enterprise.id}", + { expires_in: SHOPS_EXPIRY } + ] + end + end +end diff --git a/app/services/cart_service.rb b/app/services/cart_service.rb index 5818f8f91f..9df85cfe45 100644 --- a/app/services/cart_service.rb +++ b/app/services/cart_service.rb @@ -29,11 +29,15 @@ class CartService variants_data.each do |variant_data| loaded_variant = loaded_variants[variant_data[:variant_id].to_i] + + if loaded_variant.deleted? + remove_deleted_variant(loaded_variant) + next + end + next unless varies_from_cart(variant_data, loaded_variant) - attempt_cart_add( - loaded_variant, variant_data[:quantity], variant_data[:max_quantity] - ) + attempt_cart_add(loaded_variant, variant_data[:quantity], variant_data[:max_quantity]) end end @@ -41,12 +45,16 @@ class CartService @indexed_variants ||= begin variant_ids_in_data = variants_data.map{ |v| v[:variant_id] } - Spree::Variant.where(id: variant_ids_in_data). + Spree::Variant.with_deleted.where(id: variant_ids_in_data). includes(:default_price, :stock_items, :product). index_by(&:id) end end + def remove_deleted_variant(variant) + line_item_for_variant(variant).andand.destroy + end + def attempt_cart_add(variant, quantity, max_quantity = nil) quantity = quantity.to_i max_quantity = max_quantity.to_i if max_quantity diff --git a/app/services/variants_stock_levels.rb b/app/services/variants_stock_levels.rb index 0d778b9a0d..a72abc8ffb 100644 --- a/app/services/variants_stock_levels.rb +++ b/app/services/variants_stock_levels.rb @@ -8,7 +8,7 @@ class VariantsStockLevels variant_stock_levels = variant_stock_levels(order.line_items.includes(variant: :stock_items)) order_variant_ids = variant_stock_levels.keys - missing_variants = Spree::Variant.includes(:stock_items). + missing_variants = Spree::Variant.with_deleted.includes(:stock_items). where(id: (requested_variant_ids - order_variant_ids)) missing_variants.each do |missing_variant| diff --git a/app/views/enterprises/_change_order_cycle.html.haml b/app/views/enterprises/_change_order_cycle.html.haml new file mode 100644 index 0000000000..4f9f20bab2 --- /dev/null +++ b/app/views/enterprises/_change_order_cycle.html.haml @@ -0,0 +1,24 @@ +%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true} + %closing + %div{"ng-if" => "OrderCycle.selected()"} + = t :enterprises_next_closing + %strong {{ OrderCycle.orders_close_at() | date_in_words }} + %div{"ng-if" => "!OrderCycle.selected()"} + = t :enterprises_choose + + .order-cycle-select + .select-label + %span= t :enterprises_ready_for + + - if oc_select_options.count == 1 + %p + = oc_select_options.first[:time] + + - else + %select.select2.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id", + "ofn-change-order-cycle" => true, + "disabled" => require_customer?, + "ng-options" => "oc.id as oc.time for oc in #{oc_select_options.to_json}"} + + - if oc_select_options.count > 1 + %option{value: "", disabled: "", selected: ""}= t :shopping_oc_select diff --git a/app/views/enterprises/shop.html.haml b/app/views/enterprises/shop.html.haml index 7f98960b33..fdca8048c8 100644 --- a/app/views/enterprises/shop.html.haml +++ b/app/views/enterprises/shop.html.haml @@ -6,7 +6,8 @@ = current_distributor.logo.url - content_for :injection_data do - = inject_enterprise_shopfront(@enterprise) + - cache(*CacheService::FragmentCaching.ams_shop(@enterprise)) do + = inject_enterprise_shopfront(@enterprise) %shop.darkswarm - if @shopfront_layout == 'embedded' @@ -17,26 +18,7 @@ %a.close{ ng: { click: "alert.close()" } } × - content_for :order_cycle_form do - - %div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true} - %closing - %div{"ng-if" => "OrderCycle.selected()"} - = t :enterprises_next_closing - %strong {{ OrderCycle.orders_close_at() | date_in_words }} - %div{"ng-if" => "!OrderCycle.selected()"} - = t :enterprises_choose - - .order-cycle-select - .select-label - %span= t :enterprises_ready_for - - %select.select2.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id", - "ofn-change-order-cycle" => true, - "disabled" => require_customer?, - "ng-options" => "oc.id as oc.time for oc in #{oc_select_options.to_json}"} - - - if oc_select_options.count > 1 - %option{value: "", disabled: "", selected: ""}= t :shopping_oc_select + = render partial: "change_order_cycle" - content_for :ordercycle_sidebar do .show-for-large-up.large-4.columns diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 1f92137e9e..3986d9ace8 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -48,8 +48,10 @@ = inject_current_hub = inject_current_user = inject_rails_flash - = inject_taxons - = inject_properties + - cache CacheService::FragmentCaching.ams_all_taxons_key do + = inject_taxons + - cache CacheService::FragmentCaching.ams_all_properties_key do + = inject_properties = inject_current_order = inject_currency_config = yield :injection_data diff --git a/app/views/shared/menu/_joyride.html.haml b/app/views/shared/menu/_joyride.html.haml index 6d732e0294..eb1875f383 100644 --- a/app/views/shared/menu/_joyride.html.haml +++ b/app/views/shared/menu/_joyride.html.haml @@ -1,4 +1,4 @@ -.joyride-tip-guide{"ng-class" => "{ in: open }", "ng-show" => "open"} +.cart-dropdown.joyride-tip-guide{"ng-class" => "{ in: open }", "ng-show" => "open"} %span.joyride-nub.top .joyride-content-wrapper %h5 diff --git a/app/views/shop/_messages.html.haml b/app/views/shop/_messages.html.haml deleted file mode 100644 index 21639313ac..0000000000 --- a/app/views/shop/_messages.html.haml +++ /dev/null @@ -1,21 +0,0 @@ - -- if require_customer? - .row.footer-pad - .small-12.columns - .shopfront_hidden_message - = t '.require_customer_login' - - if spree_current_user.nil? - = t '.require_login_html', - {login: ('' + t('.login') + '').html_safe, - signup: ('' + t('.signup') + '').html_safe, - contact: link_to(t('.contact'), '#contact'), - enterprise: current_distributor.name} - - else - = t '.require_customer_html', - {contact: link_to(t('.contact'), '#contact'), - enterprise: current_distributor.name} -- elsif current_distributor.preferred_shopfront_message.present? - .row - .small-12.columns - .shopfront-message - = current_distributor.preferred_shopfront_message.html_safe diff --git a/app/views/shop/messages/_closed_shop.html.haml b/app/views/shop/messages/_closed_shop.html.haml new file mode 100644 index 0000000000..128abf2775 --- /dev/null +++ b/app/views/shop/messages/_closed_shop.html.haml @@ -0,0 +1,21 @@ +.row.closed-shop-header + .small-12.columns + .content{ "darker-background" => true } + %h4 + .warning-sign + .rectangle + %strong ! + .message + = t :shopping_oc_closed + %p + = render partial: "shopping_shared/next_order_cycle" + = render partial: "shopping_shared/last_order_cycle" + +.row + .small-12.columns + .content + .shopfront_closed_message + - if shopfront_closed_message? + = current_distributor.preferred_shopfront_closed_message.html_safe + - else + = t :shopping_oc_closed_description diff --git a/app/views/shop/messages/_customer_required.html.haml b/app/views/shop/messages/_customer_required.html.haml new file mode 100644 index 0000000000..146fee9fcf --- /dev/null +++ b/app/views/shop/messages/_customer_required.html.haml @@ -0,0 +1,19 @@ +.content{ "darker-background" => true } + .row.footer-pad + .small-12.columns + %strong + = t '.require_customer_login' + %p + - if spree_current_user.nil? + %p + = t '.require_login_html', + {login: ('' + t('.login') + '').html_safe, + signup: ('' + t('.signup') + '').html_safe} + %p + = t '.require_login_2_html', + {contact: link_to(t('.contact'), '#contact'), + enterprise: current_distributor.name} + - else + = t '.require_customer_html', + {contact: link_to(t('.contact'), '#contact'), + enterprise: current_distributor.name} diff --git a/app/views/shop/messages/_open_shop.html.haml b/app/views/shop/messages/_open_shop.html.haml new file mode 100644 index 0000000000..49180ebc88 --- /dev/null +++ b/app/views/shop/messages/_open_shop.html.haml @@ -0,0 +1,5 @@ +.content + .row + .small-12.columns + .open-shop-message + = current_distributor.preferred_shopfront_message.html_safe diff --git a/app/views/shop/messages/_select_oc.html.haml b/app/views/shop/messages/_select_oc.html.haml new file mode 100644 index 0000000000..69f037cdf4 --- /dev/null +++ b/app/views/shop/messages/_select_oc.html.haml @@ -0,0 +1,3 @@ +.content.footer-pad{ "darker-background" => true, "ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id == null" } + .select-oc-message + = t '.select_oc_html' diff --git a/app/views/shop/products/_applied_filters_feedback.haml b/app/views/shop/products/_applied_filters_feedback.haml new file mode 100644 index 0000000000..45730f39cf --- /dev/null +++ b/app/views/shop/products/_applied_filters_feedback.haml @@ -0,0 +1,9 @@ +%span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" } + = t :products_filters_in + +%span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'} + +%span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" } + = t :products_and + +%span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'} diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 100ccecc0a..f0e69d1985 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,5 +1,5 @@ -.filter-shopfront.taxon-selectors.text-right{ng: {show: 'supplied_taxons != null'}} - %single-line-selectors{ selectors: "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"} +.filter-shopfront.taxon-selectors{ng: {show: 'supplied_taxons != null'}} + %filter-selector{ 'selector-set' => "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"} -.filter-shopfront.property-selectors.text-right{ng: {show: 'supplied_properties != null'}} - %single-line-selectors{ selectors: "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"} +.filter-shopfront.property-selectors{ng: {show: 'supplied_properties != null'}} + %filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"} diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index 5e45c44fa1..c4e6ea6908 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -1,54 +1,48 @@ -.footer-pad.small-12.columns +%form{action: main_app.cart_path} %products{"ng-controller" => "ProductsCtrl", "ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true } - // TODO: Needs an ng-show to slide content down - .row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" } + = render partial: "shop/products/searchbar" + + .row .small-12.columns - .alert-box.search-alert.ng-scope - %a.right{"ng-click" => "clearAll()"} - = t :products_clear_all - %i.ofn-i_009-close - %span.filter-label - = t :products_showing - %span.applied-properties - {{ appliedPropertiesList() }} - %span.applied-taxons - {{ appliedTaxonsList() }} - %span{ ng: { hide: "!query"} } - %span{ "ng-show" => "appliedPropertiesList() || appliedTaxonsList()" } - = t :products_with - %span.applied-search "{{ query }}" - .row - .small-12.medium-6.large-5.columns - %input#search.text{"ng-model" => "query", - placeholder: t(:products_search), - "ng-debounce" => "200", - "ofn-disable-enter" => true} - - .small-12.medium-6.large-6.large-offset-1.columns - = render partial: "shop/products/filters" - - .row - %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' } - %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} - = render "shop/products/summary" - %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"} - - %product{"ng-show" => "Products.loading"} - .row.summary - .small-12.columns.text-center - = t :products_loading + .footer-pad.small-12.columns.no-gutter .row - .small-12.columns.text-center - %img.spinner{ src: "/assets/spinning-circles.svg" } + .medium-12.large-10.columns + = render partial: "shop/products/search_feedback" - %div{"ng-show" => "Products.products.length == 0 && !Products.loading"} - .row.summary - .small-12.columns - %p.no-results - = t :search_no_results_html, query: "{{query}}".html_safe - .row - .small-12.columns - %form{action: main_app.cart_path} - %i.ofn-i_011-spinner.cart-spinner{"ng-show" => "Cart.dirty"} - %input.small.button.primary.right.add_to_cart{type: :submit, value: "{{ Cart.dirty ? '#{t(:products_updating_cart)}' : (Cart.empty() ? '#{t(:products_cart_empty)}' : '#{t(:products_edit_cart)}' ) }}", "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" } + %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' } + %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} + = render "shop/products/summary" + %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"} + + %product{"ng-show" => "Products.loading"} + .row.summary + .small-12.columns.text-center + = t :products_loading + .row + .small-12.columns.text-center + %img.spinner{ src: "/assets/spinning-circles.svg" } + + .hide-for-medium-down.large-2.columns + %h5 + = t(:products_filter_by) + %span{ng: {show: 'filtersCount()' }} + = "({{ filtersCount() }} #{t(:products_filter_selected)})" + + = render partial: "shop/products/filters" + + .shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}} + .background{ng: {click: 'toggleFilterSidebar()'}} + .sidebar + %h5 + = t(:products_filter_by) + %span{ng: {show: 'filtersCount()' }} + = "({{ filtersCount() }} #{t(:products_filter_selected)})" + + = render partial: "shop/products/filters" + + .sidebar-footer + %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}} + = t(:products_filter_clear) + %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}} + = t(:products_filter_done) diff --git a/app/views/shop/products/_search_feedback.haml b/app/views/shop/products/_search_feedback.haml new file mode 100644 index 0000000000..ea6b0c7f22 --- /dev/null +++ b/app/views/shop/products/_search_feedback.haml @@ -0,0 +1,24 @@ +.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" } + .small-12.columns + .alert-box.search-alert.ng-scope + %div{"ng-show" => "Products.products.length > 0"} + + %a.clear-all.right{"ng-click" => "clearAll()"} + = t :products_clear + %i.ofn-i_009-close + + %span.filter-label + = t :products_results_for + %span{ ng: { hide: "!query"} } + %span.applied-search + {{ query }} + = render partial: 'shop/products/applied_filters_feedback' + + %div.no-results-bar{"ng-show" => "Products.products.length == 0 && !Products.loading"} + .row.summary + .small-12.columns + %p.no-results + = t :products_no_results_html, query: "{{query}}".html_safe + = render partial: 'shop/products/applied_filters_feedback' + %button.clear-search{type: 'button', ng: {click: 'clearAll()'}} + = t :products_clear_search diff --git a/app/views/shop/products/_searchbar.haml b/app/views/shop/products/_searchbar.haml new file mode 100644 index 0000000000..fa9fb962a4 --- /dev/null +++ b/app/views/shop/products/_searchbar.haml @@ -0,0 +1,17 @@ +.shop-searchbar + .row + .small-12.large-5.columns.flex + %div.search-wrap + %input#search.text{"ng-model" => "query", + type: 'search', + placeholder: t(:products_search), + "ng-debounce" => "200", + "ofn-disable-enter" => true} + %a.clear{type: 'button', ng: {show: 'query', click: 'clearQuery()'}, 'focus-search' => true} + %img{ src: "/assets/icn-close.png" } + + .hide-for-large-up + %button{type: 'button', ng: {click: 'toggleFilterSidebar()'}} + = t(:products_filter_heading) + %span{ng: {show: 'filtersCount()' }} + ({{ filtersCount() }}) diff --git a/app/views/shopping_shared/_last_order_cycle.html.haml b/app/views/shopping_shared/_last_order_cycle.html.haml index 8bbe9d7411..ca474fadd6 100644 --- a/app/views/shopping_shared/_last_order_cycle.html.haml +++ b/app/views/shopping_shared/_last_order_cycle.html.haml @@ -1,4 +1,2 @@ - if most_recently_closed = OrderCycle.most_recently_closed_for(@distributor) - ( = t :shopping_oc_last_closed, distance_of_time: distance_of_time_in_words_to_now(most_recently_closed.orders_close_at) - ) diff --git a/app/views/shopping_shared/_next_order_cycle.html.haml b/app/views/shopping_shared/_next_order_cycle.html.haml index 668493bab3..04f63fdcad 100644 --- a/app/views/shopping_shared/_next_order_cycle.html.haml +++ b/app/views/shopping_shared/_next_order_cycle.html.haml @@ -1,4 +1,2 @@ - if next_oc = OrderCycle.first_opening_for(@distributor) - ( = t :shopping_oc_next_open, distance_of_time: distance_of_time_in_words_to_now(next_oc.orders_open_at) - ) diff --git a/app/views/shopping_shared/_order_cycles.html.haml b/app/views/shopping_shared/_order_cycles.html.haml index 24148411fe..eb83cf70e7 100644 --- a/app/views/shopping_shared/_order_cycles.html.haml +++ b/app/views/shopping_shared/_order_cycles.html.haml @@ -1,7 +1,7 @@ - content_for :injection_data do = inject_current_order_cycle -- unless no_open_order_cycles? +- unless no_open_order_cycles? || require_customer? %ordercycle{"ng-controller" => "OrderCycleCtrl", "ng-cloak" => true, "ng-class" => "{'requires-selection': !OrderCycle.selected()}"} %form.custom diff --git a/app/views/shopping_shared/tabs/_home.html.haml b/app/views/shopping_shared/tabs/_home.html.haml index 827ace7283..8c2179b913 100644 --- a/app/views/shopping_shared/tabs/_home.html.haml +++ b/app/views/shopping_shared/tabs/_home.html.haml @@ -2,5 +2,12 @@ .order-cycle-bar.hide-for-large-up = render partial: "shopping_shared/order_cycles" - .content - = render partial: 'shop/messages' + - if require_customer? + = render partial: "shop/messages/customer_required" + + - else + - if no_open_order_cycles? + = render partial: "shop/messages/closed_shop" + + - else + = render partial: "shop/messages/open_shop" diff --git a/app/views/shopping_shared/tabs/_shop.html.haml b/app/views/shopping_shared/tabs/_shop.html.haml index be547535ca..2c23b4e8c1 100644 --- a/app/views/shopping_shared/tabs/_shop.html.haml +++ b/app/views/shopping_shared/tabs/_shop.html.haml @@ -2,23 +2,9 @@ .order-cycle-bar.hide-for-large-up = render partial: "shopping_shared/order_cycles" - .row - .small-12.columns - - if no_open_order_cycles? - .content - %h4 - %i.ofn-i_012-warning - = t :shopping_oc_closed - %small - %em - = render partial: "shopping_shared/next_order_cycle" - = render partial: "shopping_shared/last_order_cycle" - %p - = t :shopping_oc_closed_description + - if no_open_order_cycles? + = render partial: "shop/messages/closed_shop" - - if shopfront_closed_message? - .shopfront_closed_message - = current_distributor.preferred_shopfront_closed_message.html_safe - - - unless require_customer? - = render partial: "shop/products/form" + - else + = render partial: "shop/messages/select_oc" + = render partial: "shop/products/form" diff --git a/app/views/shops/index.html.haml b/app/views/shops/index.html.haml index d436b85e5e..032096ebb9 100644 --- a/app/views/shops/index.html.haml +++ b/app/views/shops/index.html.haml @@ -2,7 +2,8 @@ = t :shops_title - content_for :injection_data do - = inject_enterprises(@enterprises) + - cache(*CacheService::FragmentCaching.ams_shops) do + = inject_enterprises(@enterprises) #panes #shops.pane diff --git a/app/views/spree/admin/orders/_shipment_manifest.html.haml b/app/views/spree/admin/orders/_shipment_manifest.html.haml index 611cf4a5d7..fb9e51148f 100644 --- a/app/views/spree/admin/orders/_shipment_manifest.html.haml +++ b/app/views/spree/admin/orders/_shipment_manifest.html.haml @@ -1,26 +1,26 @@ - shipment.manifest.each do |item| - line_item = order.find_line_item_by_variant(item.variant) - - break if line_item.blank? - %tr.stock-item{ "data-item-quantity" => "#{item.quantity}" } - %td.item-image - = mini_image(item.variant) - %td.item-name - = item.variant.product_and_full_name - %td.item-price.align-center - = line_item.single_money.to_html - %td.item-qty-show.align-center - - item.states.each do |state,count| - = "#{count} x #{t(state.humanize.downcase, scope: [:spree, :shipment_states], default: [:missing, "none"])}" - - unless shipment.shipped? - %td.item-qty-edit.hidden - = number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5 - %td.item-total.align-center - = line_item_shipment_price(line_item, item.quantity) + - if line_item.present? + %tr.stock-item{ "data-item-quantity" => "#{item.quantity}" } + %td.item-image + = mini_image(item.variant) + %td.item-name + = item.variant.product_and_full_name + %td.item-price.align-center + = line_item.single_money.to_html + %td.item-qty-show.align-center + - item.states.each do |state,count| + = "#{count} x #{t(state.humanize.downcase, scope: [:spree, :shipment_states], default: [:missing, "none"])}" + - unless shipment.shipped? + %td.item-qty-edit.hidden + = number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5 + %td.item-total.align-center + = line_item_shipment_price(line_item, item.quantity) - %td.cart-item-delete.actions{ "data-hook" => "cart_item_delete" } - - if !shipment.shipped? && can?(:update, shipment) - = link_to '', '#', :class => 'save-item icon_link icon-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => t('actions.save'), :style => 'display: none' - = link_to '', '#', :class => 'cancel-item icon_link icon-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => t('actions.cancel'), :style => 'display: none' - = link_to '', '#', :class => 'edit-item icon_link icon-edit no-text with-tip', :data => {:action => 'edit'}, :title => t('actions.edit') - = link_to '', '#', :class => 'delete-item icon-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove', :confirm => t(:are_you_sure)}, :title => t('actions.delete') + %td.cart-item-delete.actions{ "data-hook" => "cart_item_delete" } + - if !shipment.shipped? && can?(:update, shipment) + = link_to '', '#', :class => 'save-item icon_link icon-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => t('actions.save'), :style => 'display: none' + = link_to '', '#', :class => 'cancel-item icon_link icon-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => t('actions.cancel'), :style => 'display: none' + = link_to '', '#', :class => 'edit-item icon_link icon-edit no-text with-tip', :data => {:action => 'edit'}, :title => t('actions.edit') + = link_to '', '#', :class => 'delete-item icon-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove', :confirm => t(:are_you_sure)}, :title => t('actions.delete') diff --git a/app/views/spree/admin/orders/index.html.haml b/app/views/spree/admin/orders/index.html.haml index 5359422dd0..596cd884c9 100644 --- a/app/views/spree/admin/orders/index.html.haml +++ b/app/views/spree/admin/orders/index.html.haml @@ -68,6 +68,8 @@ %span.state{'ng-class' => 'order.payment_state', 'ng-if' => 'order.payment_state'} %a{'ng-href' => '{{order.payments_path}}' } {{'js.admin.orders.payment_states.' + order.payment_state | t}} + %span{'ng-if' => 'order.display_outstanding_balance'} + ({{order.display_outstanding_balance}}) %td.align-center %span.state{'ng-class' => 'order.shipment_state', 'ng-if' => 'order.shipment_state'} {{'js.admin.orders.shipment_states.' + order.shipment_state | t}} diff --git a/config/locales/ar.yml b/config/locales/ar.yml index b4ef0f903c..68d10ea0d5 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -856,6 +856,10 @@ ar: save_and_back_to_list: "حفظ والعودة إلى القائمة" choose_products_from: "اختر المنتجات من:" incoming: + incoming: "الوارد" + supplier: "المورد" + products: "منتجات" + fees: "رسوم" save: "حفظ" save_and_next: "حفظ والتالي" next: "التالى" @@ -2869,8 +2873,9 @@ ar: blank: "لا يمكن أن تكون فارغة" layouts: admin: - header: - store: متجر + login_nav: + header: + store: متجر admin: tab: dashboard: "لوحة العرض" diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 5089fa3427..e2c05df293 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -865,6 +865,10 @@ ca: save_and_back_to_list: "Desa i torna a la llista" choose_products_from: "Trieu Productes des de:" incoming: + incoming: "Entrant" + supplier: "Proveïdora" + products: "Productes " + fees: "Comissions" save: "Desa" save_and_next: "Desa i següent" next: "Següent" @@ -2912,8 +2916,9 @@ ca: blank: "no es pot deixar en blanc" layouts: admin: - header: - store: Botiga + login_nav: + header: + store: Botiga admin: tab: dashboard: "Panell" diff --git a/config/locales/de_DE.yml b/config/locales/de_DE.yml index b9fd67477c..3fe53e180b 100644 --- a/config/locales/de_DE.yml +++ b/config/locales/de_DE.yml @@ -863,6 +863,10 @@ de_DE: save_and_back_to_list: "Speichern und zurück zur Liste" choose_products_from: "Wählen Sie Produkte von:" incoming: + incoming: "Eingehend" + supplier: "Anbieter" + products: "Produkte" + fees: "Gebühren" save: "Speichern" save_and_next: "Speichern und weiter" next: "Weiter" @@ -1210,7 +1214,7 @@ de_DE: menu_5_title: "Über Uns" menu_5_url: "https://wp.openfoodnetwork.de/" menu_6_title: "Verbinden" - menu_6_url: "https://openfoodnetwork.org/au/connect/" + menu_6_url: "https://wp.openfoodnetwork.de/support/" menu_7_title: "Mehr Erfahren" menu_7_url: "https://openfoodnetwork.org/au/learn/" logo: "Logo (640x130)" @@ -2906,8 +2910,9 @@ de_DE: blank: "kann nicht leer sein" layouts: admin: - header: - store: openfoodnetwork.de + login_nav: + header: + store: openfoodnetwork.de admin: tab: dashboard: "Übersicht" diff --git a/config/locales/en.yml b/config/locales/en.yml index e84c10c022..01ba7cebd2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -915,6 +915,11 @@ en: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + receival_details: "Receival Details" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -1234,12 +1239,16 @@ en: footer_data_cookies_policy: "cookies policy" shop: messages: - login: "login" - signup: "signup" - contact: "contact" - require_customer_login: "Only approved customers can access this shop." - require_login_html: "If you're already an approved customer, %{login} or %{signup} to proceed. Want to start shopping here? Please %{contact} %{enterprise} and ask about joining." - require_customer_html: "If you'd like to start shopping here, please %{contact} %{enterprise} to ask about joining." + customer_required: + login: "login" + signup: "signup" + contact: "contact" + require_customer_login: "Only approved customers can access this shop." + require_login_html: "If you're already an approved customer, %{login} or %{signup} to proceed." + require_login_2_html: "Want to start shopping here? Please %{contact} %{enterprise} and ask about joining." + require_customer_html: "If you'd like to start shopping here, please %{contact} %{enterprise} to ask about joining." + select_oc: + select_oc_html: "Please choose when you want your order, to see what products are available." # Front-end controller translations card_could_not_be_updated: Card could not be updated @@ -1639,11 +1648,19 @@ See the %{link} to find out more about %{sitename}'s features and to start using other: You have %{count} orders with %{shop} currently open for review. You can make changes until %{oc_close}. orders_changeable_orders_alert_html: This order has been confirmed, but you can make changes until %{oc_close}. - products_clear_all: Clear all + products_clear: Clear products_showing: "Showing:" - products_or: "OR" + products_results_for: "Results for" + products_or: "or" + products_and: "and" + products_filters_in: "in" products_with: with - products_search: "Search by product or producer" + products_search: "Search..." + products_filter_by: "Filter by" + products_filter_selected: "selected" + products_filter_heading: "Filters" + products_filter_clear: "Clear" + products_filter_done: "Done" products_loading: "Loading products..." products_updating_cart: "Updating cart..." products_cart_empty: "Cart empty" @@ -1654,6 +1671,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using products_update_error_msg: "Saving failed." products_update_error_data: "Save failed due to invalid data:" products_changes_saved: "Changes saved." + products_no_results_html: "Sorry, no results found for %{query}" + products_clear_search: "Clear search" search_no_results_html: "Sorry, no results found for %{query}. Try another search?" @@ -2438,6 +2457,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using resolve_errors: Please resolve the following errors more_items: "+ %{count} More" default_card_updated: Default Card Updated + cart: + add_to_cart_failed: > + There was a problem adding this product to the cart. + Perhaps it has become unavailable or the shop is closing. admin: enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it." modals: @@ -3071,8 +3094,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_AU.yml b/config/locales/en_AU.yml index ef6b678107..afe0aefca9 100644 --- a/config/locales/en_AU.yml +++ b/config/locales/en_AU.yml @@ -866,6 +866,10 @@ en_AU: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2818,8 +2822,9 @@ en_AU: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_BE.yml b/config/locales/en_BE.yml index 255310e643..ca369e5f97 100644 --- a/config/locales/en_BE.yml +++ b/config/locales/en_BE.yml @@ -834,6 +834,10 @@ en_BE: cancel: "Cancel" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" next: "Next" cancel: "Cancel" @@ -2765,8 +2769,9 @@ en_BE: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_CA.yml b/config/locales/en_CA.yml index 48e84cb3ce..1db4311eb6 100644 --- a/config/locales/en_CA.yml +++ b/config/locales/en_CA.yml @@ -857,6 +857,10 @@ en_CA: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2898,8 +2902,9 @@ en_CA: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_DE.yml b/config/locales/en_DE.yml index bd8269e646..02f8ee3944 100644 --- a/config/locales/en_DE.yml +++ b/config/locales/en_DE.yml @@ -842,6 +842,10 @@ en_DE: cancel: "Cancel" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" next: "Next" cancel: "Cancel" @@ -2779,8 +2783,9 @@ en_DE: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index 6722c384b4..8727a5d93e 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -862,6 +862,10 @@ en_FR: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2304,6 +2308,10 @@ en_FR: resolve_errors: Please resolve the following errors more_items: "+ %{count} More" default_card_updated: Default Card Updated + cart: + add_to_cart_failed: > + There was a problem adding this product to the cart. Perhaps it has become + unavailable or the shop is closing. admin: enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it." modals: @@ -2906,8 +2914,9 @@ en_FR: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index d65e15e69b..01cd7e148d 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -862,6 +862,10 @@ en_GB: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2912,8 +2916,9 @@ en_GB: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" @@ -3184,6 +3189,8 @@ en_GB: price: "Price" display_as: "Display As" display_name: "Display Name" + display_as_placeholder: 'eg. 2 kg' + display_name_placeholder: 'eg. Tomatoes' autocomplete: producer_name: "Producer" unit: "Unit" diff --git a/config/locales/en_NZ.yml b/config/locales/en_NZ.yml index dd94f9bd09..cf1a16dba6 100644 --- a/config/locales/en_NZ.yml +++ b/config/locales/en_NZ.yml @@ -861,6 +861,10 @@ en_NZ: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2904,8 +2908,9 @@ en_NZ: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_PH.yml b/config/locales/en_PH.yml index 13226a8bd4..f6f453b6be 100644 --- a/config/locales/en_PH.yml +++ b/config/locales/en_PH.yml @@ -861,6 +861,10 @@ en_PH: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2904,8 +2908,9 @@ en_PH: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index b60b89f930..2fb8a84782 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -857,6 +857,10 @@ en_US: save_and_back_to_list: "Save and Back to List" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" save_and_next: "Save and Next" next: "Next" @@ -2897,8 +2901,9 @@ en_US: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/en_ZA.yml b/config/locales/en_ZA.yml index baadbeccbc..8c49838cb4 100644 --- a/config/locales/en_ZA.yml +++ b/config/locales/en_ZA.yml @@ -838,6 +838,10 @@ en_ZA: cancel: "Cancel" choose_products_from: "Choose Products From:" incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + fees: "Fees" save: "Save" next: "Next" cancel: "Cancel" @@ -2779,8 +2783,9 @@ en_ZA: blank: "can't be blank" layouts: admin: - header: - store: Store + login_nav: + header: + store: Store admin: tab: dashboard: "Dashboard" diff --git a/config/locales/es.yml b/config/locales/es.yml index 4bafcc35b0..0cb3ead1c2 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -863,6 +863,10 @@ es: save_and_back_to_list: "Salvar y volver a lista" choose_products_from: "Escoger Productos desde:" incoming: + incoming: "Entrante" + supplier: "Proveedora" + products: "Productos" + fees: "Comisiones" save: "Guardar" save_and_next: "Salvar y continuar" next: "Siguiente" @@ -2910,8 +2914,9 @@ es: blank: "no puede estar vacío" layouts: admin: - header: - store: Tienda + login_nav: + header: + store: Tienda admin: tab: dashboard: "Panel de inicio" diff --git a/config/locales/es_CR.yml b/config/locales/es_CR.yml index ff2948a8b1..e33b00491f 100644 --- a/config/locales/es_CR.yml +++ b/config/locales/es_CR.yml @@ -864,6 +864,10 @@ es_CR: save_and_back_to_list: "Guardar y volver a lista" choose_products_from: "Escoger productos desde:" incoming: + incoming: "Entrante" + supplier: "Proveedor" + products: "Productos" + fees: "Comisiones" save: "Guardar" save_and_next: "Guardar y continuar" next: "Siguiente" @@ -2912,8 +2916,9 @@ es_CR: blank: "no puede estar en blanco" layouts: admin: - header: - store: Tienda + login_nav: + header: + store: Tienda admin: tab: dashboard: "Panel de inicio" diff --git a/config/locales/fil_PH.yml b/config/locales/fil_PH.yml index 92122208f7..f1c2228452 100644 --- a/config/locales/fil_PH.yml +++ b/config/locales/fil_PH.yml @@ -48,7 +48,7 @@ fil_PH: attributes: order_management/reports/enterprise_fee_summary/parameters: start_at: "Magsisimula" - end_at: "Matatapos" + end_at: "Magtatapos" distributor_ids: "Hubs" producer_ids: "Producers" order_cycle_ids: "Order Cycles" @@ -153,7 +153,7 @@ fil_PH: greeting: "Magandang araw%{name}," intro: "sa ilalim ay ang buod ng mga nauulit na order na na-finalize para sa%{shop}." summary_overview: - total: may kabuuuan na %{count}subscriptions ang minarkahan para sa awtomatikong pagproseso. + total: may kabuuuan na %{count}na nauulit na order ang minarkahan para sa awtomatikong pagproseso. success_zero: sa lahat ng ito, walang matagumpay na naproseso success_some: sa lahat ng ito, %{count}ay matagumpay na naproseso success_all: Ang lahat ay matagumpay na naproseso @@ -171,10 +171,10 @@ fil_PH: explainer: ang mga order na ito ay Kumpleto na at hindi na maaaring babaguhin processing: title: May Error na Kinaharap (%{count}na order) - explainer: ang awtomatikong pagproseso ng mga order na ito ay hindi nagtagumpay dahil sa isang error. ang error ay nailista na upang mabusisi kung saan posibleng nagsimula. + explainer: ang awtomatikong pagproseso ng mga order na ito ay hindi nagtagumpay dahil sa isang error. ang error ay itinala upang mabusisi kung saan posibleng nagsimula. failed_payment: title: Hindi Nagtagumpay Ang Pagbayad (%{count}na order) - explainer: ang awtomatikong pagproseso ng bayad para sa mga order na ito ay hindi nagtagumpay dahil sa isang error. ang error ay nailista upang mabusisi kung saan posibleng nagsimula. + explainer: ang awtomatikong pagproseso ng bayad para sa mga order na ito ay hindi nagtagumpay dahil sa isang error. ang error ay itinala upang mabusisi kung saan posibleng nagsimula. other: title: Iba Pang Dahilan ng Error (%{count}na order) explainer: Hindi naging matagumpay ang awtomatikong pagproseso ng mga order na ito dahil sa hindi maipaliwanag na kadahilanan. Makipag-uganayan sa amin kung nakikita ang mensaheng ito. @@ -193,7 +193,7 @@ fil_PH: view_order: "tignan ang order" edit_order: "i-edit ang order" ship_order: "i-ship ang order" - cancel_order: "i-cancel ang order" + cancel_order: "kanselahin ang order" confirm_send_invoice: "ang invoice para sa order na ito ay ipapadala sa customer. nais mo bang magpatuloy?" confirm_resend_order_confirmation: "nais mo bang ipadala muli ang order confirmation email?" must_have_valid_business_number: "%{enterprise_name}ay dapat may valid na TIN bago makapagpadala ng mga invoice" @@ -215,7 +215,7 @@ fil_PH: show_more: Ipakita lahat show_all: Ipakita lahat show_all_with_more: "Ipakita lahat (%{num}pa)" - cancel: i-cancel + cancel: kanselahin edit: i-edit clone: Gayahin distributors: Mga Distributor @@ -252,7 +252,7 @@ fil_PH: notes: mga tala error: Error processing_payment: "Pinoproseso ang bayad..." - no_pending_payments: "walang nakabinbin na mga bayarin" + no_pending_payments: "walang nakabinbin na mga bayad" invalid_payment_state: "hindi valid na status ng pagbabayad" filter_results: I-filter ang mga resulta quantity: Dami @@ -271,7 +271,7 @@ fil_PH: actions: create_and_add_another: "Gumawa at magdagdag ng isa pa" create: "gumawa" - cancel: "i-cancel" + cancel: "kanselahin" save: "i-save" edit: "i-edit" update: "i-update" @@ -289,7 +289,7 @@ fil_PH: on_demand: on demand on_demand?: on demand? order_cycle: Order Cycle - payment: kabayaran + payment: Bayad payment_method: Paraan ng pagbayad phone: Telepono price: Presyo @@ -381,7 +381,7 @@ fil_PH: update_address_error: 'Paunawa! Sagutan lahat ng mga kailangang impormasyon!' edit_bill_address: 'i-edit ang Billing Address' edit_ship_address: 'i-edit ang shipping address' - required_fileds: 'ang mga kailangang may sagot ay may nakalagay na asterisk' + required_fileds: 'ang mga patlang na kailangang may sagot ay may nakalagay na asterisk' select_country: 'Piliin ang bansa' select_state: 'pumili ng Lalawigan' edit: 'i-edit' @@ -413,7 +413,7 @@ fil_PH: calculator: "calculator" calculator_values: "calculator values" search: "hanapin" - name_placeholder: "hal. bayad sa pag-iimpake" + name_placeholder: "hal. bayad sa pagbalot" enterprise_groups: index: new_button: bagong grupo ng enterprise @@ -429,23 +429,23 @@ fil_PH: display_as: ipakita bilang category: kategorya tax_category: kategorya ng tax - inherits_properties?: minana ang mga katangian? + inherits_properties?: gayahin ang mga katangian? available_on: Magagamit Nakabukas av_on: "Av On" import_date: na-import upload_an_image: mag-upload ng larawan seo: product_search_keywords: "mga keyword sa paghahanap ng produkto" - product_search_tip: "isulat ang mga salita para mahanap ang inyong produkto sa mga shop. gumamit ng laktaw upang paghiwalayin ang bawat keyword." + product_search_tip: "mag-type ng mga salita upang matulungang mahanap ang inyong produkto sa mga shop. gumamit ng laktaw sa paghihiwalay ng bawat keyword." SEO_keywords: "SEO Keywords" - seo_tip: "isulat ang mga salita para mahanap ang inyong produkto sa web. gumamit ng laktaw upang paghiwalayin ang bawat keyword." + seo_tip: "mag-type ng mga salita upang matulungang mahanap ang inyong produkto sa web. gumamit ng laktaw sa paghihiwalay ng bawat keyword." search: "hanapin" properties: - property_name: "Pangalan ng Pag-aari" - inherited_property: "minanang pag-aari" + property_name: "Pangalan ng Katangian" + inherited_property: "gayahin ang katangian" variants: - infinity: "walang hanggan" - to_order_tip: "ang mga item na made to order ay walang paraan ng pag-set ng lebel ng stock, tulad ng mga tinapay na ginagawa lamang kapag may order." + 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: @@ -458,7 +458,7 @@ fil_PH: title: paglipat ng produkto file_not_found: 'ang file ay hindi mahanap o mabuksan ' no_data: walang data na mahanap sa spreadsheet - confirm_reset: "ito ay ilalagay ang lebel ng stock ng lahat ng mga produkto sa zero\nmga enterprise na hindi makikita sa nilagay na file." + confirm_reset: "ilalagay nito sa zero ang lebel ng stock ng lahat ng mga produkto ng\nenterprise na ito, na hindi makikita sa in-upload na file." model: no_file: "error: walang file na na-upload" could_not_process: "hindi maproseso ang file: invalid na uri ng file" @@ -500,7 +500,7 @@ fil_PH: save_imported: i-save ang inilipat na mga produkto no_valid_entries: walang nahanap na mga valid na entry none_to_save: Walang mga entry na maaaring i-save - some_invalid_entries: ang mga nilipat na file ay naglalaman na hindi valid na mga entry + some_invalid_entries: ang mga nilipat na file ay naglalaman ng hindi valid na mga entry fix_before_import: Ayusin ang error na ito at subukang ilipat muli ang file save_valid?: i-save ang mga valid na entry at hayaan ang iba? no_errors: walang error na nakita! @@ -511,7 +511,7 @@ fil_PH: no_name: walang pangalan blank_enterprise: ang ibang mga produkto ay walang tinutukoy na enterprise reset_absent?: i-reset ang mga nawawalang produkto - reset_absent_tip: i-set sa zero ang mga produkto na hindi makikita sa file + reset_absent_tip: i-set ang stock sa zero ng lahat ng mga produkto na hindi makikita sa file overwrite_all: sulatan lahat overwrite_empty: sulatan kung walang nakalagay default_stock: i-set ang lebel ng stock @@ -519,14 +519,14 @@ fil_PH: default_shipping_cat: i-set ang kategorya ng pagpapadala default_available_date: i-set ang available na petsa validation_overview: pagtingin sa pagpapatunay ng paglipat - entries_found: May mga entry na nahanap sa inilipat na file + entries_found: mga entry na nahanap sa inilipat na file entries_with_errors: ang mga item ay naglalaman ng error at hindi maililipat products_to_create: ang mga produkto ay gagawin products_to_update: ang mga produkto ay i-a-update inventory_to_create: ang imbentaryo ng mga item ay gagawin inventory_to_update: ang mga imbentaryong item ay i-a-update - products_to_reset: ang mga produkto na narito ay mare-reset ang stock sa zero - inventory_to_reset: ang imbentaryo ng mga item na narito ay mare-reset ang stock sa zero + products_to_reset: ang mga produkto ay mari-reset ang stock sa zero + inventory_to_reset: ang imbentaryo ng mga item ay mari-reset ang stock sa zero line: linya item_line: linya ng item import_review: @@ -537,7 +537,7 @@ fil_PH: save_results: final_results: Ilipat ang pinakahuling mga resulta products_created: ang mga produkto ay nagawa na - products_updated: inupdate ang mga produkto + products_updated: in-update ang mga produkto inventory_created: ang mga imbentaryo ng item ay nagawa na inventory_updated: inupdate ang mga inimbentaryong item products_reset: na-reset sa zero ang lebel ng stock ng mga produkto @@ -561,7 +561,7 @@ fil_PH: hide: itago import_date: na-import select_a_shop: pumili ng shop - review_now: tignan ngayon + review_now: surin ngayon new_products_alert_message: mayroong%{new_product_count}bagong mga produkto na maaaring idagdag sa inyong imbentaryo currently_empty: ang iyong imbentaryo ay kasalukuyang walang laman no_matching_products: Walang nahanap na parehong produkto sa inyong imbentaryo @@ -578,16 +578,16 @@ fil_PH: invoice_email_sent: 'ang invoice email ay naipadala na' order_email_resent: 'ang email ng order ay naipadala na muli' bulk_management: - tip: "gamitin ang pahina na ito para baguhin ang dami ng mga produkto sa maramihang mga order. maaari ding magtanggal ng mga produkto mula sa order kung kinakailangan." + tip: "gamitin ang pahina na ito para baguhin ang dami ng mga produkto sa maramihang mga order. maaari ding magtanggal ng mga produkto mula sa mga order kung kinakailangan." shared: "ibinahaging pinagkuhanan?" order_no: "Order No." order_date: "nakumpleto sa" max: "Max" product_unit: "Produkto: Yunit" weight_volume: "Timbang/Dami" - ask: "itanong?" - page_title: "Pamamahala sa pangmaramihang order" - actions_delete: "burahin ang napili" + ask: "magtanong?" + page_title: "Pamamahala sa Bulk order" + actions_delete: "tanggalin ang napili" loading: "nilo-load ang mga order" no_results: "walang nahanap na mga order" group_buy_unit_size: "laki ng yunit para sa maramihang pagbili" @@ -613,14 +613,14 @@ fil_PH: desc_short: maikling paglalarawan desc_short_placeholder: magsalaysay ukol sa inyong enterprise gamit ang isa hanggang dalawang pangungusap. desc_long: tungkol sa amin - desc_long_placeholder: magkuwento sa Customer tungkol sa inyong sarili. Ang impormasyon na ito ay makikita at lalabas sa inyong profile. + desc_long_placeholder: ipakilala ang inyong sarili sa mga customer. Ang impormasyon na ito ay makikita at lalabas sa inyong profile. business_details: abn: 'TIN:' abn_placeholder: hal. 99 123 456 789 acn: Branch TIN acn_placeholder: hal. 123 456 789 display_invoice_logo: ipakita ang Logo sa mga invoice - invoice_text: magdagdag ng sariling mensahe sa dulo ng bawat invoice + invoice_text: magdagdag ng sariling mensahe sa dulo ng mga invoice contact: name: pangalan name_placeholder: hal. Gustav Plum @@ -633,54 +633,54 @@ fil_PH: website_placeholder: hal. www.truffles.com enterprise_fees: name: pangalan - fee_type: uri ng kabayaran + fee_type: uri ng fees manage_fees: pamahalaan ang fees para sa enterprise no_fees_yet: wala ka pang kahit anong fees para sa enterprise - create_button: gumawa ng isa ngayon + create_button: lumikha ng isa ngayon images: logo: Logo promo_image_placeholder: 'ang larawan na ito ay makikita sa "tungkol sa amin"' promo_image_note1: 'TANDAAN:' - promo_image_note2: ang kahit anong larawan na ilalagay rito ay maka-crop sa 1200 x 260. - promo_image_note3: ang promo na larawan ay makikita sa taas na pahina ng profile ng enterprise at pop-ups. + promo_image_note2: ang kahit anong larawan na ilalagay dito ay maka-crop sa 1200 x 260. + promo_image_note3: ang promo na larawan ay makikita sa itaas na bahagi sa pahina ng profile ng enterprise at pop-ups. inventory_settings: text1: maaari mong piliin na pamahalaan ang lebel ng mga stock at presyo sa iyong inventory: imbentaryo text2: > - kung ikaw ay gumagamit ng tool para sa imbentaryo, piliin na ang mga - bagong produktong dinagdag ng inyong mga supplier ay kailangan munang - idagdag sa inyong imbentaryo bago ito mai-stock. kung hindi ka naman - gumagamit ng imbentaryo para pamahalaan ang inyong produkto, piliin + kung ikaw ay gumagamit ng tool para sa imbentaryo, maaaring piliin na + ang mga bagong produktong dinagdag ng inyong mga supplier ay kailangan + munang idagdag sa inyong imbentaryo bago ito mai-stock. kung hindi ka + naman gumagamit ng imbentaryo para pamahalaan ang inyong produkto, piliin ang "nirerekomenda" sa pagpipilian sa baba: - preferred_product_selection_from_inventory_only_yes: mga bagong produkto ay maaaring ilagay sa aking shopfront (nirerekomenda) + preferred_product_selection_from_inventory_only_yes: ang mga bagong produkto ay maaaring ilagay sa aking shopfront (nirerekomenda) preferred_product_selection_from_inventory_only_no: ang mga bagong produkto ay dapat idagdag muna sa aking imbentaryo bago mailagay sa aking shopfront. payment_methods: name: pangalan applies: naaangkop? manage: pamahalaan ang mga paraan ng pagbabayad no_method_yet: wala ka pang kahit anong paraan ng pagbabayad. - create_button: gumawa ng bagong paraan ng pagbabayad - create_one_button: gumawa ng isa ngayon + create_button: lumikha ng bagong paraan ng pagbabayad + create_one_button: lumikha ng isa ngayon primary_details: name: pangalan name_placeholder: hal. Professor Plum's Biodynamic Truffles groups: mga grupo groups_tip: pumili ng grupo o rehiyon kung saan ka kabilang. makakatulong ito sa mga customer sa paghahanap ng inyong enterprise. - groups_placeholder: magsimulang magsulat para maghanap ng mga grupo... - primary_producer: pangunahing producer + groups_placeholder: magsimulang magtype upang maghanap ng mga grupo... + primary_producer: pangunahing producer? primary_producer_tip: piliin ang "Producer" kung ikaw ay pangunahing producer ng pagkain. producer: Producer any: kahit ano none: wala own: sarili sells: nagbebenta - sells_tip: "Wala- ang mga enterprise ay hindi nagbebenta ng direkta sa mga customer
Sarili- ang mga enterprise ay nagbebenta ng sariling mga produkto sa mga customer
Iba- ang enterprise ay maaaring magbenta ng sariling produkto pati na rin ang mga produkto ng enterprise.
" + sells_tip: "Wala- ang enterprise ay hindi nagbebenta ng direkta sa mga customer
Sarili- ang enterprise ay nagbebenta ng sariling mga produkto sa mga customer
kahit ano- ang enterprise ay maaaring magbenta ng sariling produkto pati na rin ang mga produkto ng ibang mga enterprise.
" visible_in_search: makikita sa paghahanap? - visible_in_search_tip: timutukoy kung ang enterprise na ito ay makikita ng mga customer kapag naghahanap sa site. + visible_in_search_tip: tinutukoy kung ang enterprise na ito ay makikita ng mga customer kapag naghahanap sa site. visible: nakikita not_visible: hindi nakikita permalink: Permalink (walang espasyo) - permalink_tip: "ang permalink na ito ay ginamit para gumawa ng url sa inyong shop: %{link}your-shop-name/shop" + permalink_tip: "ang permalink na ito ay ginamit para lumikha ng url sa inyong shop: %{link}your-shop-name/shop" link_to_front: Link sa shop front link_to_front_tip: direktang link sa inyong shopfront sa Open Food Network. ofn_uid: UID sa OFN @@ -689,7 +689,7 @@ fil_PH: name: pangalan applies: naaangkop? manage: pamahalaan ang mga paraan ng pagpapadala - create_button: gumawa ng bagong paraan ng pagpapadala + create_button: lumikha ng bagong paraan ng pagpapadala create_one_button: gumawa ng isa ngayon no_method_yet: wala ka pang inilagay na paraan ng pagpapadala. shop_preferences: @@ -699,31 +699,31 @@ fil_PH: shopfront_requires_login_true: "nakikita lamang ng rehistradong mga customer" recommend_require_login: "nirerekomenda namin na kailangang maglog-in muna ang Users kapag ang mga order ay maaaring baguhin." allow_guest_orders: "mga order ng guest" - allow_guest_orders_tip: "payagang mag-checkout bilang guest o kailangang rehistrado ang user." + allow_guest_orders_tip: "pahintulutang mag-checkout bilang guest o kailangang rehistrado ang user." allow_guest_orders_false: "kailangang maglog-in para makapag-order" - allow_guest_orders_true: "payagan ang guest checkout" - allow_order_changes: "magpalit ng mga order" - allow_order_changes_tip: "payagan ang mga kustomer na palitan ang kanilang order hanggang bukas ang order cycle" - allow_order_changes_false: "ang na-place na mga order ay hindi na maaaring palitan o i-cancel" - allow_order_changes_true: "maaari pang palitan ng customer o i-cancel ang kanilang mga order habang ang order cycle ay bukas pa." - enable_subscriptions: "mga subscription" + allow_guest_orders_true: "pahintulutan ang guest checkout" + allow_order_changes: "palitan ang mga order" + allow_order_changes_tip: "pahintulutan ang mga customer na palitan ang kanilang order hangga't bukas pa ang order cycle" + allow_order_changes_false: "ang na-place na mga order ay hindi na maaaring palitan/kanselahin" + allow_order_changes_true: "maaari pang palitan/kanselahin ng mga customer ang kanilang mga order hangga't ang order cycle ay bukas pa." + enable_subscriptions: "mga nauulit na order" enable_subscriptions_tip: "paganahina ng paggamit ng mga subscription?" enable_subscriptions_false: "huwag paganahin" enable_subscriptions_true: "paganahin" shopfront_message: "mensahe sa Shopfront" shopfront_message_placeholder: > - opsyonal na mensahe para batiin ang mga customers at upang maipaliwanag - kung paano mamili sa iyong shop. kung ang teksto ay ipinasok dito, ito - ay makikita sa home tab kapag ang customer ay nasa loob na ng inyong + opsyonal na mensahe para sa pagbati sa mga customer at pagpapaliwanag + kung paano mamili sa inyong shop. kung ang teksto ay ipinasok dito, + ito ay makikita sa home tab kapag ang customer ay nasa loob na ng inyong shopfront. shopfront_message_link_tooltip: "ilagay/ i-edit ang link" shopfront_message_link_prompt: "ilagay ang URL na ipapasok" shopfront_closed_message: "pagsasarang mensahe ng Shopfront" shopfront_closed_message_placeholder: > isang mensahe na nagbibigay ng mas detalyadong paliwanag kung bakit - ang iyong shop ay sarado at/o kapag ang mga customer ay makakaasang - magbubukas ito muli. Ito ay makikita sa iyong shop kapag ikaw ay walang - aktibong order cycle (hal. sarado ang shop). + ang iyong shop ay sarado at/o kung ang mga customer ay makakaasang magbubukas + ito muli. Ito ay makikita sa iyong shop kapag ikaw ay walang aktibong + order cycle (hal. sarado ang shop). shopfront_category_ordering: "kategorya ng pag-order sa Shopfront" open_date: "petsa ng pagbubukas" close_date: "petsa ng pagsasara" @@ -734,16 +734,16 @@ fil_PH: linkedin_placeholder: "hal. www.linkedin.com/in/YourNameHere" stripe_connect: connect_with_stripe: "Kumonekta sa Stripe" - stripe_connect_intro: "upang tumanggap ng bayad gamit ang credit card, kailangan munang ikonekta ang iyong Stripe account sa Open Food Network. Gamitin ang pindutan sa kanan para magsimula." + stripe_connect_intro: "upang tumanggap ng bayad gamit ang credit card, kailangan munang ikonekta ang iyong Stripe account sa Open Food Network. Gamitin ang button sa kanan para magsimula." stripe_account_connected: "ang Stripe account ay konektado na." disconnect: "tanggalin ang koneksyon ng account" confirm_modal: title: Kumonekta sa Stripe part1: ang Stripe ay isang serbisyong nagpoproseso ng mga bayad na ginagawang posible para sa mga shop sa OFN na tumanggap ng bayad gamit ang mga credit card mula sa mga customer. part2: para magamit ito, kailangang ikonekta ang iyong Stripe account sa OFN. ang pagpindot ng "pumapayag ako" sa ibaba ay ididirekta ka sa Stripe website kung saan mo maaaring ikonek ang iyong Stripe account o gumawa ng bago kung wala ka pa nito. - part3: pinapayagan nito ang Open Food Network na tumanggap para sa iyo ng bayad gamit ang mga credit card mula sa mga customer. tandaan na kailangan mong pangalagaan ang sarili mong Stripe Account, bayaran ang serbisyo ng Stripe, asikasuhin ang mga chargebacks at Customer service sa sarili mong paraan. + part3: pinapayagan nito ang Open Food Network na tumanggap para sa iyo ng bayad gamit ang mga credit card mula sa mga customer. tandaan na kailangan mong pangalagaan ang sarili mong Stripe Account, bayaran ang serbisyo ng Stripe, asikasuhin ang mga chargebacks pati na rin ang Customer service sa sarili mong paraan. i_agree: pumapayag ako - cancel: i-cancel + cancel: kanselahin tag_rules: default_rules: by_default: bilang default @@ -759,19 +759,19 @@ fil_PH: resend: Ipadala muli owner: 'May-ari' contact: "Makipag-usap" - contact_tip: "ang tagapamahala na makakatanggap ng enterprise emails at notipikasyon. kailangang may kumpirmadong email address." + contact_tip: "ang tagapamahala na makakatanggap ng enterprise emails para sa mga order at mga abiso. kailangang may kumpirmadong email address." owner_tip: ang pangunahing gumagamit na responsable sa enterprise na ito. - notifications: notipikasyon - notifications_tip: ang mga notipikasyon tungkol sa mga order ay ipapadala sa email address na ito. + notifications: mga abiso + notifications_tip: ang mga abiso tungkol sa mga order ay ipapadala sa email address na ito. notifications_placeholder: hal. gustav@truffles.com notifications_note: 'Tandaan: ang bagong email address ay kailangang kumpirmado bago magamit' managers: Tagapamahala managers_tip: ang ibang gagamit na may permisong pamahalaan ang enterprise na ito. invite_manager: "Mag-imbita ng Tagapamahala" - invite_manager_tip: "mag-imbita ng hindi rehistradong gagamit na mag-sign-up at maging tagapamahala ng enterprise na ito." + invite_manager_tip: "mag-imbita ng hindi rehistradong user na mag-sign-up at maging tagapamahala ng enterprise na ito." add_unregistered_user: "magdagdag ng hindi rehistradong user" - email_confirmed: "ang email ay kumpirmado" - email_not_confirmed: "ang email ay hindi kumpirmado" + email_confirmed: "ang email ay kumpirmado na" + email_not_confirmed: "ang email ay hindi pa kumpirmado" actions: edit_profile: Settings properties: mga katangian @@ -790,27 +790,27 @@ fil_PH: producer: Producer change_type_form: producer_profile: Profile ng Producer - connect_ofn: kumonekta gamit ang OFN + connect_ofn: kumonekta sa pamamagitan ng OFN always_free: PALAGING LIBRE - producer_description_text: ilagay ang inyong mga produkto sa Open Food Network, upang mapayagan ang mga Hub na mag-stock ng iyong produkto sa kanilang mga tindahan. + producer_description_text: ilagay ang inyong mga produkto sa Open Food Network, upang mapahintulutan ang mga Hub na mag-stock ng inyong mga produkto sa kanilang mga tindahan. producer_shop: Producer Shop sell_your_produce: magbenta ng sariling produkto - producer_shop_description_text: ibenta ang iyong mga produkto direkta sa mga customer sa pamamagitan ng iyong sariling Open Foof Network shopfront. + producer_shop_description_text: ibenta ang inyong mga produkto direkta sa mga customer sa pamamagitan ng inyong sariling Open Food Network shopfront. producer_shop_description_text2: ang 'Producer Shop' ay para lamang sa iyong produkto, kung nais mong magbenta ng mga produktong itinanim o ginawa sa labas ng site, piliin ang 'Producer Hub' producer_hub: Producer Hub producer_hub_text: magbenta ng produkto mula sa sarili at sa iba - producer_hub_description_text: ang iyong enterprise ang saligan ng iyong lokal na sistema ng pagkain. maaari mong ibenta ang sarili mong produkto pati na rin ang mga produktong pinagsama sama mula sa ibang enterprise gamit ang iyong shop sa Open Food Network. + producer_hub_description_text: ang iyong enterprise ang sandigan ng iyong lokal na sistema ng pagkain. maaari mong ibenta ang sarili mong produkto pati na rin ang mga produktong pinagsama sama mula sa ibang enterprise gamit ang iyong shop sa Open Food Network. profile: Profile lamang get_listing: kumuha ng listahan profile_description_text: Maaari kang mahanap ng mga tao sa Open Food Network. Ang iyong enterprise ay makikita sa mapa, at maaaring mahanap sa mga listahan. hub_shop: Hub Shop hub_shop_text: magbenta ng produkto mula sa iba - hub_shop_description_text: ang iyong enterprise ang saligan ng iyong lokal na sistema ng pagkain. maaari mong pagsama samahin ang mga produkto mula sa ibang enterprise at ibenta ito gamit ang iyong shop sa Open Food Network. - choose_option: pumili mula sa pagpipilian sa itaas. + hub_shop_description_text: ang iyong enterprise ang sandigan ng iyong lokal na sistema ng pagkain. maaari mong pagsama samahin ang mga produkto mula sa ibang enterprise at ibenta ito gamit ang iyong shop sa Open Food Network. + choose_option: pumili ng isa mula sa pagpipilian sa itaas. change_now: palitan ngayon enterprise_user_index: loading_enterprises: NILO-LOAD ANG MGA ENTERPRISE - no_enterprises_found: walang mahanap na enterprise + no_enterprises_found: walang mahanap na enterprise. search_placeholder: hanapin gamit ang pangalan manage: pamahalaan manage_link: settings @@ -819,7 +819,7 @@ fil_PH: status: "Status" new_form: owner: may-ari - owner_tip: ang pangunahing gumagamit na responsable sa enterprise na ito. + owner_tip: ang pangunahing user na responsable sa enterprise na ito. i_am_producer: ako ay isang producer contact_name: Contact name edit: @@ -852,22 +852,26 @@ fil_PH: loading: NILO-LOAD... new: create: "gumawa" - cancel: "i-cancel" + cancel: "kanselahin" back_to_list: "bumalik sa listahan" edit: advanced_settings: "Advanced settings" save: "i-save" save_and_next: "i-save at sunod" next: "sunod" - cancel: "i-cancel" + cancel: "kanselahin" back_to_list: "bumalik sa listahan" save_and_back_to_list: "i-save at bumalik sa listahan" - choose_products_from: "pumili sa mga produkto mula sa:" + choose_products_from: "pumili ng mga produkto mula sa:" incoming: + incoming: "paparating" + supplier: "Supplier" + products: "mga produkto" + fees: "fees" save: "i-save" save_and_next: "i-save at sunod" next: "sunod" - cancel: "i-cancel" + cancel: "kanselahin" back_to_list: "bumalik sa listahan" outgoing: outgoing: "papalabas" @@ -879,16 +883,16 @@ fil_PH: previous: "nauna" save: "i-save" save_and_back_to_list: "i-save at bumalik sa listahan" - cancel: "i-cancel" + cancel: "kanselahin" back_to_list: "bumalik sa listahan" wizard_progress: edit: "1. Pangkalahatang Setting" - incoming: "2. Papasok na mga produkto" + incoming: "2. Paparating na mga produkto" outgoing: "3. Palabas na mga produkto" exchange_form: pickup_time_tip: kapag ang mga order mula sa OC ay handa na para sa customer pickup_instructions_placeholder: "mga panuto sa pagpick-up" - pickup_instructions_tip: ang mga panuto na ito ay ipapakita sa mga customer pagkatapos nilang makumpleto ang pag-order + pickup_instructions_tip: ang mga panuto na ito ay ipinapakita sa mga customer pagkatapos nilang makumpleto ang pag-order pickup_time_placeholder: "handa na para sa (hal. Date / Time)" receival_instructions_placeholder: "panuto sa pagtanggap" add_fee: 'idagdag ang bayad' @@ -937,7 +941,7 @@ fil_PH: variants: mga uri simple_form: ready_for: handa para sa - ready_for_placeholder: petsa/ oras + ready_for_placeholder: petsa / oras customer_instructions: panuto ng Customer customer_instructions_placeholder: tala para sa pag-pick-up o pagdeliver products: mga produkto @@ -949,14 +953,14 @@ fil_PH: no_data: may nangyaring mali. walang mahanap na order cycle. date_warning: msg: ang order cycle na ito ay naka-link sa %{n}na bukas na subscription order. ang pagpapalit ng petsa ay hindi maaapektuhan ang iba pang order na nagawa na, ngunit kung maaaring iwasan ay huwag na muling gawin. Gusto bang magpatuloy? - cancel: i-cancel + cancel: kanselahin proceed: magpatuloy producer_properties: index: title: mga katangian ng producer proxy_orders: cancel: - could_not_cancel_the_order: hindi ma-cancel ang order + could_not_cancel_the_order: hindi makansela ang order resume: could_not_resume_the_order: hindi maituloy ang order shared: @@ -969,7 +973,7 @@ fil_PH: enterprise_issues: create_new: gumawa ng bago resend_email: ipadala muli ang email - has_no_payment_methods: "%{enterprise}ay kasakuluyang walang mga paraan ng pagbabayad" + has_no_payment_methods: "%{enterprise}ay kasalukuyang walang mga paraan ng pagbabayad" has_no_shipping_methods: "%{enterprise}ay kasalukuyang walang mga paraan ng pagpapadala" email_confirmation: "ang kumpirmasyon ng email ay nakabinbin pa. kami ay nagpadala ng kumpirmasyon ng email sa %{email}." not_visible: "%{enterprise}ay hindi nakikita kaya hindi matagpuan sa mapa o sa kahit saang paghahanap" @@ -1009,7 +1013,7 @@ fil_PH: products_and_inventory: name: mga produkto at imbentaryo users_and_enterprises: - name: mga gumagamit at enterprise + name: mga user at enterprise description: pag-aari ng enterprise at status order_cycle_management: name: pamamahala sa order cycle @@ -1027,12 +1031,12 @@ fil_PH: subscriptions: mga subscription new: bagong subscription create: gumawa ng subscription - edit: ayusin ang subscription + edit: i-edit ang subscription table: - edit_subscription: ayusin ang subsciption + edit_subscription: i-edit ang subscription pause_subscription: ihinto saglit ang subscription unpause_subscription: ituloy ang subscription - cancel_subscription: itigil ang subscription + cancel_subscription: kanselahin ang nauulit na order filters: query_placeholder: "hanapin gamit ang email..." setup_explanation: @@ -1040,7 +1044,7 @@ fil_PH: enable_subscriptions: "paganahin ang subscription sa kahit isa sa iyong mga shop" enable_subscriptions_step_1_html: 1. pumunta sa%{enterprises_link}pahina, hanapin ang iyong shop at pindutin ang "pamahalaan" enable_subscriptions_step_2: 2. sa ilalim ng "shop preference", paganahin ang Subscriptions sa pagpipilian - set_up_shipping_and_payment_methods_html: iset-up ang %{shipping_link}at%{payment_link} na pagbabayad + set_up_shipping_and_payment_methods_html: iset-up ang paraan ng%{shipping_link}at%{payment_link} set_up_shipping_and_payment_methods_note_html: tandaan na Cash at Stripe na pamamaraan ang maaaring
gamitin sa mga subscription ensure_at_least_one_customer_html: siguraduhing mayroong kahit isang%{customer_link}na nakikita create_at_least_one_schedule: gumawa ng kahit isang iskedyul @@ -1091,15 +1095,15 @@ fil_PH: orders: number: bilang confirm_edit: sigurado ka bang nais mo ayusin ang order na ito? maaaring magdulot ito ng mas mahirap na awtomatikong pagsasaayos ng mga subscription sa hinaharap. - confirm_cancel_msg: "sigurado ka bang nais mong itigil ang subscription na ito? hindi na ito maaring ibalik kapag napalitan na." - cancel_failure_msg: "paumahin ngunit hindi naging matagumpay ang pagcancel!" + confirm_cancel_msg: "sigurado ka bang nais mong kanselahin ang nauulit na order na ito? hindi na ito maaaring ibalik kapag nagawa na." + cancel_failure_msg: "paumanhin ngunit hindi naging matagumpay ang pagkansela!" confirm_pause_msg: "sigurado ka bang nais mong pansamantalang itigil ang subscription na ito?" pause_failure_msg: "paumanhin ngunit hindi matagumpay ang pansamantalang pagtigil" confirm_unpause_msg: "kung ikaw ay may bukas na order cycle sa iskedyul ng subscription na ito, isang order ang gagawin para sa customer. sigurado ka bang nais mong ipagpatuloy ang subscription?" unpause_failure_msg: "paumanhin ngunit hindi naging matagumpay ang pagpatuloy!" - confirm_cancel_open_orders_msg: "ang ibang order sa subscription na ito ay kasalukuyang nakabukas. ang customer ay nasabihan nang ang order ay magpapatuloy. nais mo bang itigil ang order(mga order) o ipagpatuloy ito?" + confirm_cancel_open_orders_msg: "ang ibang order sa subscription na ito ay kasalukuyang nakabukas. ang customer ay naabisuhang ang order ay magpapatuloy. nais mo bang kanselahin ang order(mga order) o ipagpatuloy ito?" resume_canceled_orders_msg: "ang ibang order sa subsciption na ito ay maaaring ituloy. maaari mo itong ituloy sa dropdown ng mga order." - yes_cancel_them: wag ituloy + yes_cancel_them: kanselahin no_keep_them: ituloy yes_i_am_sure: sigurado ako order_update_issues_msg: ang ibang mga order ay hindi awtomatikong ma-update, sapagkat ito ay manwal na inayos. Tignan ang mga isyu na nakalista sa baba at gawin ang nararapat na pag-aayos sa bawat order kung kinakailangan. @@ -1112,7 +1116,7 @@ fil_PH: associated_subscriptions_error: ang iskedyul na ito ay hindi maialis sapagkat ito ay nauugnay sa ibang mga subscription. controllers: enterprises: - stripe_connect_cancelled: "ang pagkonekta sa Stripe ay kinansel" + stripe_connect_cancelled: "koneksyon sa Stripe ay kinansel" stripe_connect_success: "matagumpay na nakonekta ang stripe account" stripe_connect_fail: paumanhin, ang konekyson ng inyong Stripe account ay hindi nagtagumpay stripe_connect_settings: @@ -1127,7 +1131,7 @@ fil_PH: checkout: already_ordered: cart: "cart" - message_html: "mayroon ka ng order para sa order cycle na ito. tignan ang%{cart}para makita ang mga item na dating inorder. maaari mo ring i-cancel ang mga item habang ang order cycle ay bukas pa." + message_html: "mayroon ka ng order para sa order cycle na ito. tingnan ang%{cart}para makita ang mga item na dating inorder. maaari mo ring kanselahin ang mga item habang ang order cycle ay bukas pa." failed: "hindi matagumpay ang pag-check out. ipaalam sa amin upang maproseso ang inyong order." shops: hubs: @@ -1377,12 +1381,12 @@ fil_PH: checkout_headline: "ok, handa na ba magcheckout?" checkout_as_guest: "mag-checkout bilang bisita" checkout_details: "ang inyong detalye" - checkout_billing: "impormasyon sa paniningil" - checkout_default_bill_address: "i-save bilang default na address para sa paniningil" + checkout_billing: "Billing info" + checkout_default_bill_address: "i-save bilang default na billing address" checkout_shipping: impormasyon sa pagpapadala - checkout_default_ship_address: "i-save bilang default na address para sa pagpapadalahan" + checkout_default_ship_address: "i-save bilang default na shipping address" checkout_method_free: libre - checkout_address_same: address ng pagpapadalahan pareho sa address ng paniningil? + checkout_address_same: Shipping address ay pareho sa billing address? checkout_ready_for: "handa na para sa :" checkout_instructions: "komento o dagdag na tagubilin?" checkout_payment: kabayaran @@ -1397,7 +1401,7 @@ fil_PH: order_not_paid: HINDI PA BAYAD order_total: kabuuang order order_payment: "magbabayad sa pamamagitan ng:" - order_billing_address: address na pagsisingilan + order_billing_address: Billing address order_delivery_on: petsa ng delivery order_delivery_address: address kung saan ide-deliver order_delivery_time: oras ng pagde-deliver @@ -1657,10 +1661,10 @@ fil_PH: orders_show_title: Kumpirmasyon ng order orders_show_time: 'ang order ay handa na sa:' orders_show_order_number: "Order #%{number}" - orders_show_cancelled: Kinansela + orders_show_cancelled: kanselado orders_show_confirmed: Kumpirmado orders_your_order_has_been_cancelled: "ang inyong order ay nakansela" - orders_could_not_cancel: "paumanhin sapagkat hindi makansela ang inyong order" + orders_could_not_cancel: "paumanhin, hindi makansela ang inyong order" orders_cannot_remove_the_final_item: "hindi matanggal ang huling item mula sa isang order, maaaring kanselahin na lamang ang order." orders_bought_items_notice: one: "isang karagdagang item ang nakumpirma na para sa order cycle na ito" @@ -1858,7 +1862,7 @@ fil_PH: continue: "Magpatuloy" action_or: "O" enterprise_limit: Limitasyon ng enterprise - shipping_method_destroy_error: "ang paraan ng pagpapadala ay hindi maaaring burahin sapagkat ito ay nakasangguni na sa isang order:%{number}." + shipping_method_destroy_error: "ang paraan ng pagpapadala ay hindi maaaring tanggalin sapagkat ito ay nakasangguni na sa isang order:%{number}." fees: "fees" item_cost: "halaga ng item" bulk: "Bulto" @@ -2089,13 +2093,13 @@ fil_PH: report_header_ship_city: lungsod na pagpapadalahan report_header_ship_postcode: postcode ng pagpapadalahan report_header_ship_state: status ng pagpapadala - report_header_billing_street: address na papadalahan ng bayarin - report_header_billing_street_2: address na papadalahan ng bayarin 2 - report_header_billing_street_3: address na papadalahan ng bayarin 3 - report_header_billing_street_4: address na papadalahan ng bayarin 4 - report_header_billing_city: lungsod na papadalahan ng bayarin - report_header_billing_postcode: postcode ng papadalahan ng bayarin - report_header_billing_state: status ng papadalahan ng bayarin + report_header_billing_street: Billing Street + report_header_billing_street_2: Billing Street 2 + report_header_billing_street_3: Billing Street 3 + report_header_billing_street_4: Billing Street 4 + report_header_billing_city: Billing City + report_header_billing_postcode: Billing Postcode + report_header_billing_state: Billing Province report_header_incoming_transport: papasok na sasakyan report_header_special_instructions: espesyal na mga panuto report_header_order_number: numero ng order @@ -2130,9 +2134,9 @@ fil_PH: report_header_unit: yunit report_header_group_buy_unit_quantity: Dami ng yunit para sa Maramihang pagbili report_header_cost: gastos - report_header_shipping_cost: gastos sa pagpapadala + report_header_shipping_cost: bayad sa pagpapadala report_header_curr_cost_per_unit: kasalukuyang gastos kada yunit - report_header_total_shipping_cost: 'kabuuang gastos sa pagpapadala ' + report_header_total_shipping_cost: 'kabuuang bayad sa pagpapadala ' report_header_payment_method: paraan ng pagbayad report_header_sells: nagbebenta report_header_visible: nakikita @@ -2192,7 +2196,7 @@ fil_PH: report_header_total_taxable_produce: kabuuang produkto na taxable (may kasamang tax) report_header_total_untaxable_fees: kabuuang fees na untaxable (walang tax) report_header_total_taxable_fees: kabuuang fees na taxable (may kasamang tax) - report_header_delivery_shipping_cost: Singil sa pagdeliver ng pinapadala (kasama ang tax) + report_header_delivery_shipping_cost: bayad sa pagdeliver ng pinapadala (kasama ang tax) report_header_transaction_fee: Bayad sa Transaksyon (walang tax) report_header_total_untaxable_admin: kabuuang untaxable na pagsasaayos ng admin (walang tax) report_header_total_taxable_admin: kabuuang taxable na pagsasaayos ng admin (kasama ang tax) @@ -2319,10 +2323,10 @@ fil_PH: title: panuntunan sa pag-tag overview: pangkalahatang ideya overview_text: > - ang panuntunan sa tag na nagbibigay ng paraan upang maisalarawan kung - anong mga item ang nakikita at kung sa kaninong mga customer dapat ito - maipakita. Ang mga item ay maaaring paraan ng pagpapadala, paraan ng - pagbabayad, mga produkto at mga order cycle. + ang panuntunan sa pag-tag ay nagbibigay ng paraan upang maisalarawan + kung anong mga item ang makikita at kung kaninong mga customer dapat + ito maipakita. Ang mga item ay maaaring paraan ng pagpapadala, paraan + ng pagbabayad, mga produkto at mga order cycle. by_default_rules: "'By Default...' mga panuntunan" by_default_rules_text: > ang default na mga panuntunan ay papayagan ka na magtago ng mga item @@ -2465,7 +2469,7 @@ fil_PH: address: "tirahan" adjustments: "mga pagsasaayos" awaiting_return: "naghihintay ng pagbalik" - canceled: "na-cancel" + canceled: "kanselado" cart: "cart" complete: "kumpleto" confirm: "kumpirmahin" @@ -2482,7 +2486,7 @@ fil_PH: pending: "nakabinbin" ready: "handa na" shipped: "nai-ship na" - canceled: "na-cancel" + canceled: "kanselado" payment_states: balance_due: "balanse" completed: "nakumpleto na" @@ -2769,7 +2773,7 @@ fil_PH: edit: "i-edit" split: "paghiwalayin" delete: "tanggalin" - cannot_set_shipping_method_without_address: "hindi ma-set ang paraan ng pagdadala hanggang hindi binibigay ang detalye ng customer." + cannot_set_shipping_method_without_address: "hindi mai-set ang paraan ng pagpapadala hanggang hindi binibigay ang detalye ng customer." no_tracking_present: "walang detalye ng tracking na ibinigay." order_total: "kabuuan ng order" customer_details: "detalye ng customer" @@ -2777,7 +2781,7 @@ fil_PH: choose_a_customer: "pumili ng customer" account: "Account" billing_address: "Billing Address" - shipping_address: "Address kung saan ipapadala" + shipping_address: "Shipping Address" first_name: "Pangalan" last_name: "apelyido" street_address: "address ng kalye" @@ -2788,7 +2792,7 @@ fil_PH: state: "status" phone: "telepono" update: "i-update" - use_billing_address: "gamitin ang address para sa paniningil" + use_billing_address: "gamitin ang billing address" adjustments: "mga pagsasa-ayos" continue: "Magpatuloy" fill_in_customer_info: "sagutan ang impormasyon tungkol sa customer" @@ -2867,7 +2871,7 @@ fil_PH: shipping_methods: "mga paraan ng pagpapadala" shipping_categories: "mga kategorya ng pagpapadala" new_shipping_category: "bagong kategorya ng pagpapadala" - back_to_shipping_categories: "bumalik sa kategorya ng pagpapadala" + back_to_shipping_categories: "bumalik sa mga kategorya ng pagpapadala" name: "pangalan" description: "paglalarawan" type: "uri" @@ -2918,8 +2922,9 @@ fil_PH: blank: "hindi maaaring iwanang blanko" layouts: admin: - header: - store: tindahan + login_nav: + header: + store: tindahan admin: tab: dashboard: "Dashboard" @@ -2988,7 +2993,7 @@ fil_PH: states: authorized: "pinayagan" received: "natanggap" - canceled: "na-cancel" + canceled: "kanselado" orders: index: listing_orders: "listahan ng mga order" @@ -3062,7 +3067,7 @@ fil_PH: new_shipping_method: "bagong paraan ng pagpapadala" back_to_shipping_methods_list: "bumalik sa listahan ng mga paraan ng pagpapadala" edit: - editing_shipping_method: "iayos ang paraan ng pagpapadala" + editing_shipping_method: "i-edit ang paraan ng pagpapadala" new: "Bago" back_to_shipping_methods_list: "bumalik sa listahan ng mga paraan ng pagpapadala" form: @@ -3250,9 +3255,9 @@ fil_PH: order_mailer: cancel_email: customer_greeting: "hi %{name}!" - instructions: "ang iyong order ay NA-CANCEL. Panatilihin ang impormasyon na ito ng kanselasyon para sa inyong talaan." - order_summary_canceled: "Buod ng order [NA-CANCEL]" - subject: "Pag-cancel ng order" + instructions: "ang iyong order ay NAKANSELA. Panatilihin ang impormasyon na ito ng kanselasyon para sa inyong talaan." + order_summary_canceled: "Buod ng order [KANSELADO]" + subject: "Pagkansela ng order" confirm_email: subject: "Kumpirmasyon ng order" invoice_email: @@ -3262,7 +3267,7 @@ fil_PH: address: tirahan adjustments: mga pagsasaayos awaiting_return: naghihintay ng pagbalik - canceled: na-cancel + canceled: kanselado cart: cart complete: kumpleto confirm: kumpirmahin @@ -3278,7 +3283,7 @@ fil_PH: pending: nakabinbin ended: natapos na paused: nakahinto - canceled: na-cancel + canceled: kanselado user_mailer: reset_password_instructions: request_sent_text: | @@ -3313,7 +3318,7 @@ fil_PH: items: mga item total: kabuuan edit: i-edit - cancel: i-cancel + cancel: kanselahin closed: sarado until: hanggang past_orders: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d8e71f3328..b5564a20f6 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -864,6 +864,10 @@ fr: save_and_back_to_list: "Sauvegarder et revenir à la liste" choose_products_from: "Choisir produits depuis :" incoming: + incoming: "Produits entrants (pouvant être mis en vente par les hubs)" + supplier: "Fournisseur" + products: "Produits" + fees: "Commissions" save: "Sauvegarder" save_and_next: "Sauvegarder et suivant" next: "Suivant" @@ -2308,6 +2312,10 @@ fr: resolve_errors: Veuillez corriger les erreurs suivantes more_items: "+ %{count} en plus" default_card_updated: La carte bancaire par défaut a été mise à jour + cart: + add_to_cart_failed: > + Il y a eu un problème lors de l'ajout de votre produit au panier. Peut-être + qu'il n'est plus en stock ou que la boutique sur laquelle vous étiez a fermé. admin: enterprise_limit_reached: "Vous avez atteint le nombre limite d'entreprises autorisées par défaut. Ecrivez à %{contact_email}si vous avez besoin d'augmenter cette limite." modals: @@ -2936,8 +2944,9 @@ fr: blank: "Champ obligatoire" layouts: admin: - header: - store: Vue acheteur + login_nav: + header: + store: Vue acheteur admin: tab: dashboard: "Tableau de bord" diff --git a/config/locales/fr_BE.yml b/config/locales/fr_BE.yml index 842e6bda2a..eb59b7b304 100644 --- a/config/locales/fr_BE.yml +++ b/config/locales/fr_BE.yml @@ -863,6 +863,10 @@ fr_BE: save_and_back_to_list: "Sauver et retour à la liste" choose_products_from: "Choisir produits depuis :" incoming: + incoming: "Produits entrants (pouvant être mis en vente par les comptoirs)" + supplier: "Disitributeur·trice" + products: "Produits" + fees: "Commission" save: "Sauvergarder" save_and_next: "Sauver et suivant" next: "Suivant" @@ -2846,8 +2850,9 @@ fr_BE: blank: "Champ obligatoire" layouts: admin: - header: - store: Vue acheteur·euse + login_nav: + header: + store: Vue acheteur·euse admin: tab: dashboard: "Tableau de bord" diff --git a/config/locales/fr_CA.yml b/config/locales/fr_CA.yml index 47fb8fae13..c0506fdd84 100644 --- a/config/locales/fr_CA.yml +++ b/config/locales/fr_CA.yml @@ -859,6 +859,10 @@ fr_CA: save_and_back_to_list: "Sauvegarder et suivant" choose_products_from: "Choisir produits depuis :" incoming: + incoming: "Produits entrants (pouvant être mis en vente par les hubs)" + supplier: "Fournisseur" + products: "Produits" + fees: "Commissions" save: "Enregistrer" save_and_next: "Sauvegarder et suivant" next: "Suivant" @@ -2909,8 +2913,9 @@ fr_CA: blank: "Champ obligatoire" layouts: admin: - header: - store: Vue acheteur + login_nav: + header: + store: Vue acheteur admin: tab: dashboard: "Tableau de bord" diff --git a/config/locales/it.yml b/config/locales/it.yml index 68714c7bb5..0f69e621dd 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -331,7 +331,7 @@ it: unsaved_changes: "Hai modifiche non salvate" shopfront_settings: embedded_shopfront_settings: "Impostazioni di vetrina incorporate" - enable_embedded_shopfronts: "Disabilita Vetrine incorporata" + enable_embedded_shopfronts: "Abilita vetrine incorporate" embedded_shopfronts_whitelist: "Whitelist dei domini esterni" number_localization: number_localization_settings: "Settaggi del Numero di Localizzazione" @@ -858,6 +858,10 @@ it: save_and_back_to_list: "Salva e torna alla lista" choose_products_from: "Scegli i prodotti da:" incoming: + incoming: "In arrivo" + supplier: "Fornitore" + products: "Prodotti" + fees: "Tariffe" save: "Salva" save_and_next: "Salva e continua" next: "Prossimo" @@ -2903,8 +2907,9 @@ it: blank: "non può essere lasciato vuoto" layouts: admin: - header: - store: Memorizzare + login_nav: + header: + store: Memorizzare admin: tab: dashboard: "Pannello di controllo" diff --git a/config/locales/nb.yml b/config/locales/nb.yml index fc91cafb01..c7dee96e84 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -862,6 +862,10 @@ nb: save_and_back_to_list: "Lagre og Tilbake til Listen" choose_products_from: "Velg Produkter Fra:" incoming: + incoming: "Innkommende" + supplier: "Leverandør" + products: "Produkter" + fees: "Avgifter" save: "Lagre" save_and_next: "Lagre og Neste" next: "Neste" @@ -2905,8 +2909,9 @@ nb: blank: "kan ikke være tomt" layouts: admin: - header: - store: Butikk + login_nav: + header: + store: Butikk admin: tab: dashboard: "Dashboard" @@ -3177,6 +3182,8 @@ nb: price: "Pris" display_as: "Vis som" display_name: "Visningsnavn" + display_as_placeholder: 'f.eks. 2 kg' + display_name_placeholder: 'f.eks. Tomater' autocomplete: producer_name: "Produsent" unit: "Enhet" diff --git a/config/locales/nl_BE.yml b/config/locales/nl_BE.yml index 34cdb4eb4d..5b1cd23dc0 100644 --- a/config/locales/nl_BE.yml +++ b/config/locales/nl_BE.yml @@ -837,6 +837,10 @@ nl_BE: cancel: "Annuleren" choose_products_from: "Kies Producten Uit:" incoming: + incoming: "Binnenkomend" + supplier: "Leverancier" + products: "Producten" + fees: "Vergoedingen" save: "Save" next: "Volgende" cancel: "Annuleren" @@ -2774,8 +2778,9 @@ nl_BE: blank: "kan niet leeg zijn" layouts: admin: - header: - store: Zicht koper + login_nav: + header: + store: Zicht koper admin: tab: dashboard: "Dashboard" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 2abc2435fd..ec55300c55 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -824,6 +824,10 @@ pt: cancel: "Cancelar" choose_products_from: "Escolha produtos de:" incoming: + incoming: "Entrada" + supplier: "Fornecedor" + products: "Produtos" + fees: "Taxas" save: "Guardar" next: "Seguinte" cancel: "Cancelar" @@ -2720,8 +2724,9 @@ pt: blank: "não pode ser vazio" layouts: admin: - header: - store: Loja + login_nav: + header: + store: Loja admin: tab: dashboard: "Painel de controlo" diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index 6ba990a294..f53416f21f 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -862,6 +862,10 @@ pt_BR: save_and_back_to_list: "Salvar e Voltar para a Lista" choose_products_from: "Escolha produtos de:" incoming: + incoming: "Entrada" + supplier: "Fornecedor" + products: "Produtos" + fees: "Taxas" save: "Salvar" save_and_next: "Salvar e Seguir" next: "Próximo" @@ -2908,10 +2912,6 @@ pt_BR: errors: messages: blank: "Não pode ser vazio" - layouts: - admin: - header: - store: Loja admin: tab: dashboard: "Painel" diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 655db7eb4f..b0ebf8fe2a 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -492,6 +492,10 @@ sv: cancel: "Avbryt" choose_products_from: "Välj produkter från:" incoming: + incoming: "Inkommande" + supplier: "Leverantör" + products: "Produkter" + fees: "Avgifter" next: "Näst" cancel: "Avbryt" outgoing: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 8da6830930..652d46194e 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -3,26 +3,26 @@ tr: activerecord: attributes: enterprise_fee: - fee_type: Ücret Türü + fee_type: Ücret TÜRÜ spree/order: payment_state: Ödeme Durumu shipment_state: ' Teslimat Durumu' - completed_at: Oluşturulma Tarihi + completed_at: Oluşturulma TARİHİ number: Numara state: Durum email: Müşteri E-postası spree/payment: amount: Tutar spree/product: - primary_taxon: "Ürün Kategorisi" - supplier: "Tedarikçi" - shipping_category_id: "Teslimat Kategorisi" - variant_unit: "Varyant Birimi" + primary_taxon: "ÜRÜN KATEGORİSİ" + supplier: "TEDARİKÇİ" + shipping_category_id: "Teslimat KategOrİSİ" + variant_unit: "Varyant BİRİMİ" variant_unit_name: "Varyant Birim Adı" spree/credit_card: base: "Kredi kartı" order_cycle: - orders_close_at: Bitiş tarihi + orders_close_at: BİTİŞ TARİHİ errors: models: spree/user: @@ -47,14 +47,14 @@ tr: activemodel: attributes: order_management/reports/enterprise_fee_summary/parameters: - start_at: "Başlangıç" - end_at: "Bitiş" + start_at: "BAŞLANGIÇ" + end_at: "BİTİŞ" distributor_ids: "Pazarlar" producer_ids: "ÜRETİCİLER" - order_cycle_ids: "Sipariş Dönemleri" - enterprise_fee_ids: "Ücret İsimleri" - shipping_method_ids: "Teslimat Yöntemleri" - payment_method_ids: "Ödeme Yöntemleri" + order_cycle_ids: "SİPARİŞ DÖNEMLERİ" + enterprise_fee_ids: "ÜCRET İSİMLERİ" + shipping_method_ids: "TESLİMAT YÖNTEMLERİ" + payment_method_ids: "ÖDEME YÖNTEMLERİ" errors: messages: inclusion: "Listeye dahil değil" @@ -139,7 +139,7 @@ tr: dear_customer: "Değerli müşterimiz," instructions: "Siparişiniz kargoya teslim edildi" shipment_summary: "Teslimat Özeti" - subject: "Teslimat Bildirimi" + subject: "TESLİMAT BİLDİRİMİ" thanks: "İş birliğiniz için teşekkür ederim." track_information: "Takip Bilgileri: %{tracking}" track_link: "Takip Bağlantısı: %{url}" @@ -204,7 +204,7 @@ tr: ongoing: Devam eden bill_address: Fatura Adresi ship_address: Teslimat Adresi - sort_order_cycles_on_shopfront_by: "Sipariş Dönemlerini Mağazada Şuna Göre Sırala" + sort_order_cycles_on_shopfront_by: "SİPARİŞ DÖNEMLERİNİ MAĞAZAMDA ŞUNA GÖRE SIRALA" required_fields: Zorunlu alanlar yıldız ile belirtilmiştir select_continue: Seç ve Devam Et remove: Kaldır @@ -235,7 +235,7 @@ tr: admin_and_handling: Yönetici & İşlemler profile: Profil supplier_only: Sadece Tedarikçi - has_shopfront: Mağazası Var + has_shopfront: MAĞAZASı Var weight: Ağırlık volume: Hacim items: Kalemler @@ -335,8 +335,8 @@ tr: unsaved_confirm_leave: "Kaydedilmemiş değişiklikler var. Kaydetmeden devam etmek istiyor musunuz?" unsaved_changes: "Kaydedilmemiş değişiklikleriniz var" shopfront_settings: - embedded_shopfront_settings: "Yerleştirilmiş Mağaza Ayarları" - enable_embedded_shopfronts: "Gömülü Mağazaları Etkinleştir" + embedded_shopfront_settings: "YERLEŞTİRİLMİŞ MAĞAZA AYARLARI" + enable_embedded_shopfronts: "YERLEŞTİRİLMİŞ MAĞAZALARI ETKİNLEŞTİR" embedded_shopfronts_whitelist: "Beyaz Listedeki harici Domain'ler" number_localization: number_localization_settings: "Numara Yerelleştirme Ayarları" @@ -393,8 +393,8 @@ tr: has_associated_orders: 'Silme başarısız oldu: müşterinin bağlantılı siparişleri var' contents: edit: - title: İçerik - header: Başlık + title: İÇERİK + header: BaşlIK home_page: Ana Sayfa producer_signup_page: Üretici kayıt sayfası hub_signup_page: Pazar kayıt sayfası @@ -560,7 +560,7 @@ tr: add: Ekle hide: Sakla import_date: Aktarıldı - select_a_shop: Bir Tezgah Seçin + select_a_shop: Bir Mağaza Seçin review_now: Şimdi İncele new_products_alert_message: Stoklarınıza eklemek için %{new_product_count} yeni ürün var. currently_empty: Stoklarınız şu anda boş @@ -570,7 +570,7 @@ tr: no_new_products: Stoklara eklenecek yeni ürün yok no_matching_new_products: Arama kriterlerinize uyan yeni ürün yok inventory_powertip: Burası sizin ürün envanteriniz, stok takip merkeziniz. Ürün eklemek için menüdeki 'Yeni Ürün' butonun tıklayın. - hidden_powertip: Bu ürünler stoklarınızda gizlendi ve tezgahlarınızda görünür olmayacak. Stoklarınıza ürün eklemek için 'Ekle' butonuna tıklayabilirsiniz. + hidden_powertip: Bu ürünler stoklarınızda gizlendi ve mağazanızda görünür olmayacak. Stoklarınıza ürün eklemek için 'Ekle' butonuna tıklayabilirsiniz. new_powertip: Bu ürünler stoklarınıza eklenmeye hazır. Stoğunuza ürün eklemek için 'Ekle'yi veya tezgahınızda gizlemek için 'Gizle'yi tıklayın. İstediğiniz zaman fikrinizi değiştirebilirsiniz! controls: back_to_my_inventory: Stoklarıma geri dön @@ -651,7 +651,7 @@ tr: ürünün satış listenize eklenmeden önce Stok listenize eklenip eklenmemesi gerektiğini seçebilirsiniz. Eğer ürünlerinizi yönetmek için Stok sistemini kullanmıyorsanız 'tavsiye edilen' seçeneği ile devam etmeniz gerekir. - preferred_product_selection_from_inventory_only_yes: Mağazanıza yeni ürünler eklenebilir (önerilir) + preferred_product_selection_from_inventory_only_yes: MAĞAZANIZA YENİ ÜRÜNLER EKLENEBİLİR (ÖNERİLİR) preferred_product_selection_from_inventory_only_no: Yeni ürünler mağazanıza eklemeden önce Stok listesine girilmelidir payment_methods: name: Ad @@ -680,7 +680,7 @@ tr: not_visible: Gizli permalink: Bağlantı Adı (boşluk yok) permalink_tip: "Bağlantı adı, mağazanızın bağlantı URL'sini oluşturmak için kullanılır: %{link}sectiginiz-ad/tezgah" - link_to_front: Tezgah linkiniz + link_to_front: Mağaza linkiniz link_to_front_tip: Açık Gıda Ağı’ndaki mağazanıza doğrudan bağlantı linki ofn_uid: AGA KN ofn_uid_tip: Açık Gıda Ağı'na kayıtlı işletmenize özel tanımlanan kimlik numarası @@ -692,7 +692,7 @@ tr: create_one_button: Şimdi Oluştur no_method_yet: Henüz herhangi bir teslimat yönteminiz yok. shop_preferences: - shopfront_requires_login: "Mağazanız haritada görünür olsun mu?" + shopfront_requires_login: "MAĞAZANIZ HARİTADA GÖRÜNÜR OLSUN MU?" shopfront_requires_login_tip: "Mağazanızın yalnızca üyelerinize mi yoksa herkese mi açık olduğunu seçin." shopfront_requires_login_false: "Herkese açık" shopfront_requires_login_true: "Yalnızca kayıtlı müşteriler tarafından görülebilir" @@ -709,19 +709,19 @@ tr: enable_subscriptions_tip: "Üyelik işlevselliği etkinleştirilsin mi?" enable_subscriptions_false: "Kapalı" enable_subscriptions_true: "Etkin" - shopfront_message: "Mağaza Mesajınız" + shopfront_message: "MAĞAZA MESAJINIZ" shopfront_message_placeholder: > Müşterilerinize merhaba diyebilir, tezgahınız ve alışveriş şartlarınız ile ilgili bilgi verebilirsiniz. Yazdıklarınız, müşteriler mağazanızı ziyaret ettiğinde görünür olacak. shopfront_message_link_tooltip: "Bağlantı ekle / düzenle" shopfront_message_link_prompt: "Lütfen eklemek için bir URL girin" - shopfront_closed_message: "Kapalı Mağaza Mesajı" + shopfront_closed_message: "KAPALI MAĞAZA MESAJI" shopfront_closed_message_placeholder: > - Vitrininizin neden satışa kapalı olduğunu ve ne zaman açılacağını müşterilerinize + Mağazanızın neden satışa kapalı olduğunu ve ne zaman açılacağını müşterilerinize açıklayan bir mesaj yazın. Bu mesaj, açık sipariş döneminiz olmadığında sizi ziyaret edenler tarafından görülecek. - shopfront_category_ordering: "Mağaza Kategori Sıralaması" + shopfront_category_ordering: "MAĞAZA KATEGORİ SIRALAMASI" open_date: "Açılış Tarihi" close_date: "Kapanış Tarih" social: @@ -758,13 +758,13 @@ tr: contact: "İLETİŞİM" contact_tip: "İşletme adına sipariş ve bildirim e-postalarını alacak olan yönetici. Onaylanmış bir e-posta adresi olması gerekir." owner_tip: Bu işletmeden sorumlu birincil kullanıcı. - notifications: Bildirimler + notifications: BİLDİRİMLER notifications_tip: Siparişlerle ilgili bildirimler bu e-posta adresine gönderilecektir. notifications_placeholder: Örn. toprak@yesiller.com notifications_note: 'Not: Kullanmadan önce yeni bir e-posta adresinin onaylanması gerekebilir' - managers: Yöneticiler + managers: YÖNETİCİLER managers_tip: Bu işletmeyi yönetme izni olan diğer kullanıcılar. - invite_manager: "Yönetici Davet Et" + invite_manager: "YÖNETİCİ Davet Et" invite_manager_tip: "Sisteme kayıtlı olmayan bir kullanıcıyı hesap açmaya ve bu işletmenin yöneticisi olmaya davet edin." add_unregistered_user: "Kayıtlı olmayan bir kullanıcı ekle" email_confirmed: "E-posta onaylandı" @@ -802,7 +802,7 @@ tr: profile_description_text: İnsanlar Açık Gıda Ağı üzerinden sizi bulabilir ve sizinle iletişim kurabilir. İşletmeniz haritada ve listelerde görünür olacak. hub_shop: Türetici Pazarı hub_shop_text: Başkalarının ürünlerini satın - hub_shop_description_text: İşletmeniz yerel gıda sisteminizin belkemiğidir. Diğer işletmelerin ürünlerini bir araya getirebilir, Açık Gıda Ağı'na kayıtlı pazar yeriniz üzerinden alıcılara ulaştırabilirsiniz. + hub_shop_description_text: İşletmeniz yerel gıda sisteminizin belkemiğidir. Diğer işletmelerin ürünlerini bir araya getirebilir, Açık Gıda Ağı'na kayıtlı mağazanız üzerinden alıcılara ulaştırabilirsiniz. choose_option: Lütfen yukarıdaki seçeneklerden birini seçin. change_now: Değiştir enterprise_user_index: @@ -855,12 +855,16 @@ tr: advanced_settings: "Gelişmiş Ayarlar" save: "Kaydet" save_and_next: "Kaydet ve İlerle" - next: "Sonraki" + next: "Sonrakİ" cancel: "İptal et" back_to_list: "Listeye geri dön" save_and_back_to_list: "Kaydet ve Listeye Dön" - choose_products_from: "Ürünleri Buradan Seç:" + choose_products_from: "Ürünlerİ Buradan SeÇ:" incoming: + incoming: "Gelen" + supplier: "TEDARİKÇİ" + products: "Ürünler" + fees: "Ücretler" save: "Kaydet" save_and_next: "Kaydet ve İlerle" next: "Sonraki" @@ -886,7 +890,7 @@ tr: pickup_time_tip: Bu sipariş dönemine ait siparişlerin müşteriler için hazır olma tarihi pickup_instructions_placeholder: "Teslim Alma Talimatları" pickup_instructions_tip: Bu talimatlar, siparişi tamamladıktan sonra müşterilere iletilir - pickup_time_placeholder: "Şu tarihte hazır (örn. Tarih / Saat)" + pickup_time_placeholder: "ŞU TARİHTE HAZIR (örn. Tarİh / Saat)" receival_instructions_placeholder: "Teslim Alma talimatları" add_fee: 'Ücret ekle' remove: 'Kaldır' @@ -911,7 +915,7 @@ tr: general_settings: "Genel Ayarlar" incoming: Gelen supplier: Tedarikçi - receival_details: Alım detayları + receival_details: TESLİM ALMA DETAYLARI fees: Ücretler outgoing: Giden distributor: Dağıtımcı @@ -925,15 +929,15 @@ tr: new_schedule: Yeni Takvim name_and_timing_form: name: Ad - orders_open: 'Sipariş açılma vakti:' + orders_open: 'SİPARİŞ AÇILIŞ:' coordinator: Koordinatör - orders_close: Siparişler kapalı + orders_close: SİPARİŞ KAPANIŞ row: suppliers: tedarikçileri distributors: Dağıtımcılar variants: Varyantlar simple_form: - ready_for: Şu tarihte hazır + ready_for: ŞU TARİHTE HAZIR ready_for_placeholder: Tarih / saat customer_instructions: Müşteri talimatları customer_instructions_placeholder: Teslimat / Gönderim Notları @@ -1034,9 +1038,9 @@ tr: query_placeholder: "E-posta ile ara ..." setup_explanation: just_a_few_more_steps: 'Başlamadan önce birkaç adım kaldı:' - enable_subscriptions: "Tezgahlarınızdan/pazarlarınızdan en az biri için üyelikleri etkinleştirin" + enable_subscriptions: "Mağazalarınızdan en az biri için üyelikleri etkinleştirin" enable_subscriptions_step_1_html: 1. %{enterprises_link} sayfasına gidin, tezgahınızı bulun ve ‘Yönet’i tıklayın - enable_subscriptions_step_2: '''Tezgah Tercihleri'' altındaki Üyelikler seçeneğini etkinleştirin' + enable_subscriptions_step_2: 2. 'Mağaza Tercihleri' altındaki Üyelikler seçeneğini etkinleştirin set_up_shipping_and_payment_methods_html: '%{shipping_link} ve %{payment_link} yöntemlerini ayarlayın' set_up_shipping_and_payment_methods_note_html: Yalnızca Nakit ve Online ödeme yöntemlerinin
'ü üyeliklerle kullanılabilir ensure_at_least_one_customer_html: En az bir %{customer_link} bulunduğundan emin olun @@ -1128,8 +1132,8 @@ tr: failed: "Ödeme başarısız oldu. Siparişinizi işleme koyabilmemiz için lütfen bizimle iletişime geçin." shops: hubs: - show_closed_shops: "Kapalı dükkanları göster" - hide_closed_shops: "Kapalı dükkanları gizle" + show_closed_shops: "Kapalı mağazaları göster" + hide_closed_shops: "Kapalı mağazaları gizle" show_on_map: "Tümünü haritada göster" shared: menu: @@ -1164,7 +1168,7 @@ tr: footer_data_cookies_policy: "çerez politikası" shop: messages: - login: "oturum aç" + login: "Oturum Aç" signup: "Kaydol" contact: "İLETİŞİM" require_customer_login: "Yalnızca onaylı müşteriler buradan alışveriş yapabilir." @@ -1352,7 +1356,7 @@ tr: learn_cta: "İlham Alın" connect_body: "Yakınınızdaki adil ve temiz gıda tüccarlarını bulmak için üreticilerin, pazarların ve topluluklarının tümünü gözden geçirin. İşletmenizi veya topluluğunuzu Açık Gıda Ağı üzerinden listeleyin, böylece alıcılar sizi bulabilir. Ortak hareket etmek için diğer hesaplar ile iletişime geçmekten çekinmeyin, birlikte daha güçlüsünüz. Tavsiye almak ve sorunları birlikte çözmek için topluluğa katılın." connect_cta: "Keşfedin" - system_headline: "Alışveriş - işte böyle çalışıyor." + system_headline: "Nasıl çalışıyor ?" system_step1: "1. Ara" system_step1_text: "Yerel, adil, temiz ve mevsimsel gıda için, bağımsız ve cesur üreticilerimizin pazarlarından alışveriş yapın. Uzaklığa göre, ürün kategorisine veya teslimat tercihlerine göre arama yapabilirsiniz. " system_step2: "2. Alışveriş Yap" @@ -1368,7 +1372,7 @@ tr: stats_orders: "GIDA SİPARİŞLERİ" checkout_title: Ödeme Yap checkout_now: Alışverişi Tamamla - checkout_order_ready: Sipariş şu tarihte hazır + checkout_order_ready: SİPARİŞ ŞU TARİHTE HAZIR checkout_hide: Gizle checkout_expand: genişlet checkout_headline: "Tamam, ödeme yapmaya hazır mısın?" @@ -1380,7 +1384,7 @@ tr: checkout_default_ship_address: "Varsayılan teslimat adresi olarak kaydet" checkout_method_free: Ücretsiz checkout_address_same: Teslimat adresi fatura adresiyle aynı mı? - checkout_ready_for: "şu tarihte hazır" + checkout_ready_for: "ŞU TARİHTE HAZIR" checkout_instructions: "Yorumlarınız veya özel talimatlarınız var mı?" checkout_payment: Ödeme checkout_send: Şimdi sipariş ver @@ -1399,7 +1403,7 @@ tr: order_delivery_address: Teslimat adresi order_delivery_time: Teslimat zamanı order_special_instructions: "Notların:" - order_pickup_time: Teslim almak için hazır + order_pickup_time: TESLİM ALMAK İÇİN HAZIR order_pickup_instructions: Teslim Alma Talimatları order_produce: Üretim order_total_price: Toplam @@ -1468,7 +1472,7 @@ tr: email_shipping_delivery_time: "Teslimat tarihi:" email_shipping_delivery_address: "Teslimat adresi:" email_shipping_collection_details: Teslim Alma Bilgileri - email_shipping_collection_time: "teslim almak için hazır:" + email_shipping_collection_time: "TESLİM ALMAK İÇİN HAZIR" email_shipping_collection_instructions: "Teslim Alma talimatları:" email_special_instructions: "Notların:" email_signup_greeting: Merhaba! @@ -1488,7 +1492,7 @@ tr: producer_mail_order_text: "İşte ürünleriniz gelen için siparişlerin bir özeti:" producer_mail_delivery_instructions: "Stok teslimat/gönderim talimatları:" producer_mail_signoff: "En iyi dileklerimle" - shopping_oc_closed: Siparişler kapalı + shopping_oc_closed: SİPARİŞ KAPANIŞ shopping_oc_closed_description: "Lütfen bir sonraki dönem açılana kadar bekleyin (veya geç siparişleri kabul edip edemeyeceğimizi görmek için doğrudan bizimle iletişime geçin)" shopping_oc_last_closed: "Son dönem %{distance_of_time} önce kapandı" shopping_oc_next_open: "Bir sonraki dönem %{distance_of_time}'da açılıyor" @@ -1503,13 +1507,13 @@ tr: shopping_groups_part_of: "şunun parçası:" shopping_producers_of_hub: "%{hub} üreticileri:" enterprises_next_closing: "Bir sonraki sipariş kapanışı" - enterprises_ready_for: "şu tarihte hazır" + enterprises_ready_for: "ŞU TARİHTE HAZIR" enterprises_choose: "Siparişinizi ne zaman istediğinizi seçin:" maps_open: "Açık" maps_closed: "Kapalı" hubs_buy: "Neler Bulabilirsiniz:" hubs_shopping_here: "Alışveriş noktası" - hubs_orders_closed: "Siparişler kapalı" + hubs_orders_closed: "SİPARİŞ KAPANIŞ" hubs_profile_only: "Yalnızca profil" hubs_delivery_options: "Teslimat seçenekleri" hubs_pickup: "Teslimat Noktası" @@ -1536,7 +1540,7 @@ tr: products_updating_cart: "Sepet güncelleniyor ..." products_cart_empty: "Sepetiniz boş" products_edit_cart: "Sepetini düzenle" - products_from: itibaren + products_from: üretici products_change: "Kaydedilecek değişiklik yok." products_update_error: "Kaydetme başarısız oldu:" products_update_error_msg: "Kaydetme başarısız oldu." @@ -1622,7 +1626,7 @@ tr: sell_listing_price: "AGA üzerinde görünür olmak ücretsizdir. Fiyatlandırma hakkında daha fazla bilgi için, üst menüdeki Hakkında bağlantısını kullanarak Yazılım Platformu bölümünü ziyaret edin." sell_embed: "Açık Gıda Ağı üzerinden oluşturduğunuz tezgahınızı kendi web siteniz üzerinden de kullanmanıza yardımcı olabiliriz. Müşterileriniz mevcut internet siteniz üzerinden de aynı şekilde sipariş verebilirler. " sell_ask_services: "Bize AGA hizmetleri hakkında soru sorun." - shops_title: Dükkanlar + shops_title: Mağazalar shops_headline: Alışveriş biçim değiştiriyor shops_text: Gıda dönemsel yetiştirilir, dönemsel hasat edilir ve dönemsel sipariş edilir. Aradığınız mağazanın sipariş dönemi kapalı ise kısa süre sonra tekrar kontrol edin. shops_signup_title: Pazar olarak kaydolun @@ -1638,7 +1642,7 @@ tr: orders_fees: Ücretler ... orders_edit_title: Alışveriş Sepeti orders_edit_headline: Alışveriş sepetiniz - orders_edit_time: Sipariş şu tarihte hazır + orders_edit_time: SİPARİŞ ŞU TARİHTE HAZIR orders_edit_continue: Alışverişe devam orders_edit_checkout: Ödeme Yap orders_form_empty_cart: "Sepeti Boşalt" @@ -1681,7 +1685,7 @@ tr: password: Parola remember_me: Beni Hatırla are_you_sure: "Emin misiniz?" - orders_open: Siparişler açık + orders_open: 'SİPARİŞ AÇILIŞ:' closing: "Kapanış" going_back_to_home_page: "Sizi ana sayfaya götürüyor" creating: oluşturuluyor @@ -1863,7 +1867,7 @@ tr: shop_variant_quantity_max: "maks" follow: "Takip et" shop_for_products_html: "%{enterprise} ürünleri için şuradan alışveriş yapın:" - change_shop: "Pazarı şuna değiştir:" + change_shop: "Mağazayı şuna değiştir:" shop_at: "Şimdi alışveriş yapın:" price_breakdown: "Fiyat dökümü" admin_fee: "yönetici ücreti" @@ -1892,7 +1896,7 @@ tr: successfully_updated: '%{resource} başarıyla güncellendi!' running_balance: "Güncel Bakiye" outstanding_balance: "Ödenmemiş bakiye" - admin_enterprise_relationships: "İşletme İzinleri" + admin_enterprise_relationships: "İŞLETME İZİNLERİ" admin_enterprise_relationships_everything: "her şey" admin_enterprise_relationships_permits: "izinler" admin_enterprise_relationships_seach_placeholder: "Ara" @@ -2018,7 +2022,7 @@ tr: edit_profile_details_etc: "Profil açıklamanızı, resimlerinizi vb. değiştirin." order_cycle: "Sipariş Dönemi" order_cycles: "Sipariş Dönemleri" - enterprise_relationships: "İşletme izinleri" + enterprise_relationships: "İŞLETME İZİNLERİ" remove_tax: "Vergiyi kaldır" first_name_begins_with: "Adının BAŞ HARFİ" last_name_begins_with: "Soyadının BAŞ HARFİ" @@ -2298,11 +2302,15 @@ tr: unavailable: Kullanım dışı profile: Profil hub: pazar - shop: tezgah + shop: MAĞAZA choose: Seç resolve_errors: Lütfen aşağıdaki hataları düzeltin more_items: "+ %{count} Daha " default_card_updated: Varsayılan Kart Güncellendi + cart: + add_to_cart_failed: > + Bu ürünü sepete eklerken bir hata oluştu. Ürün stokları bitmiş veya mağaza + kapanıyor olabilir. admin: enterprise_limit_reached: "Hesap başına standart işletme sınırına ulaştınız. Artırmanız gerekiyorsa %{contact_email}'a yazın." modals: @@ -2339,7 +2347,7 @@ tr: hub_profile_text2: > Bir profil sahibi olmak, Açık Gıda Ağı üzerinden bağlantı kurmak her zaman ücretsiz olacaktır. - hub_shop: Türetici Pazarı + hub_shop: Pazar Mağazası hub_shop_text1: > İşletmeniz yerel gıda sisteminizin bel kemiğidir. Ulaşabilir olduğunuz bölgedeki üreticilerin ürünlerini bir araya getirip Açık Gıda Ağı üzerinden @@ -2457,7 +2465,7 @@ tr: awaiting_return: "dönüş bekleniyor" canceled: "iptal edildi" cart: "sepet" - complete: "tamamla" + complete: "Tamamla" confirm: "onayla" delivery: "Eve Teslim" paused: "Durduruldu" @@ -2513,7 +2521,7 @@ tr: subscriptions: error_saving: "Üyelik kaydedilirken hata oluştu" new: - please_select_a_shop: "Lütfen bir pazar seçin" + please_select_a_shop: "Lütfen bir mağaza seçin" insufficient_stock: "Yetersiz stok, sadece %{on_hand} kaldı" out_of_stock: reduced_stock_available: Azaltılmış stok mevcut @@ -2544,10 +2552,10 @@ tr: changing_on_hand_stock: Eldeki stok seviyeleri değiştiriliyor ... stock_reset: Stoklar varsayılana ayarlanır tag_rules: - show_hide_variants: 'Mağazamda varyantları Göster veya Gizle' + show_hide_variants: 'MAĞAZAMDA VARYANTLARI GÖSTER VEYA GİZLE' show_hide_shipping: 'Ödeme sırasında teslimat yöntemlerini göster veya gizle' show_hide_payment: 'Ödeme sırasında ödeme yöntemlerini göster veya gizle' - show_hide_order_cycles: 'Mağazamda sipariş dönemlerini göster veya gizle' + show_hide_order_cycles: 'MAĞAZAMDA SİPARİŞ DÖNEMLERİNİ GÖSTER VEYA GİZLE' visible: GÖRÜNÜR not_visible: GÖRÜNÜR DEĞİL services: @@ -2573,7 +2581,7 @@ tr: producer: "Üretici" non_producer: "Üretici Değil" customers: - select_shop: 'Lütfen önce bir pazar seçin' + select_shop: 'Lütfen önce bir mağaza seçin' could_not_create: Afedersiniz! Oluşturulamadı subscriptions: closes: kapanır @@ -2639,9 +2647,15 @@ tr: serve: one: "servis" other: "porsiyon" + tray: + one: "trays" + other: "trays" piece: one: "parça" other: "parça" + pot: + one: "pot" + other: "pots" bundle: one: "demeti" other: "demet" @@ -2822,7 +2836,7 @@ tr: new_tax_rate: "Yeni Vergi Oranı" tax_category: "Vergi Kategorisi" rate: "oran" - tax_rate_amount_explanation: "Vergi oranları ondalık bir tutar olmalı (yani vergi oranı% 5 ise 0,05 girin)" + 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" @@ -2836,10 +2850,10 @@ tr: countries: "Ülkeler" listing_countries: "Ülkeler Listeleniyor" iso_name: "ISO Adı" - states_required: "Şehir Gerekli" + states_required: "Şehİr Gerekli" editing_country: "Ülkeyi Düzenle" back_to_countries_list: "Ülke Listesine Geri Dön" - states: "Şehirler" + states: "Şehİrler" abbreviation: "Kısaltma" new_state: "Yeni Şehir" payment_methods: "Ödeme yöntemleri" @@ -2902,8 +2916,9 @@ tr: blank: "boş olamaz" layouts: admin: - header: - store: Ana Site + login_nav: + header: + store: Ana Site admin: tab: dashboard: "KONTROL PANELİ" @@ -2920,7 +2935,7 @@ tr: roles: "Roller" order_cycles: "Sipariş Dönemleri" enterprises: "İşletmeler" - enterprise_relationships: "İzinler" + enterprise_relationships: "İZİNLER" customers: "Müşteriler" groups: "Gruplar" product_properties: @@ -3002,7 +3017,7 @@ tr: issued_on: "İşlenme Tarihi" tax_invoice: "VERGİ FATURASI" code: "Kod" - from: "İtibaren" + from: "Üretici" to: "Fatura Adresi" shipping: "Teslimat" form: @@ -3174,6 +3189,8 @@ tr: price: "Fiyat" display_as: "Gösterme Şekli" display_name: "Ekran adı" + display_as_placeholder: 'örn. 2 kg' + display_name_placeholder: 'örn. Domates' autocomplete: producer_name: "Üretici" unit: "Birim" @@ -3292,7 +3309,7 @@ tr: transaction_history: İşlem Geçmişi open_orders: order: Sipariş - shop: Tezgah + shop: MAĞAZA changes_allowed_until: Değişiklikler Şu Zamana Kadar İzin Verildi items: Ürünler total: Toplam @@ -3302,7 +3319,7 @@ tr: until: Şu zamana kadar past_orders: order: Sipariş - shop: Tezgah + shop: MAĞAZA completed_at: Tamamlanma Tarihi items: Ürünler total: Toplam @@ -3314,9 +3331,9 @@ tr: cards: authorised_shops: Yetkili Pazarlar authorised_shops_popover: Sahip olabileceğiniz tüm üyelikler (mesela tekrarlayan siparişleriniz) için varsayılan kredi kartınızdan işlem yapmasına izin verilen işletmelerin listesidir. Kart bilgileriniz güvende tutulacak ve işletme sahipleriyle paylaşılmayacak. Ödeme alındığında her zaman bilgilendirileceksiniz. - saved_cards_popover: Bu, daha sonra kullanmak üzere kaydetmeyi seçtiğiniz sepetlerin listesidir. Bir siparişin ödemesi tamamlandığında 'varsayılan'ınız otomatik olarak seçilir ve izin verdiğiniz satıcılar tarafından ücretlendirilebilir (sağa bakın). + saved_cards_popover: Bu, daha sonra kullanmak üzere kaydetmeyi seçtiğiniz kartların listesidir. Bir siparişin ödemesi tamamlandığında 'varsayılan'ınız otomatik olarak seçilir ve izin verdiğiniz satıcılar tarafından ücretlendirilebilir (sağa bakın). authorised_shops: - shop_name: "Pazar adı" + shop_name: "MAĞAZA ADI" allow_charges?: "Ücretlere İzin Verilsin mi?" localized_number: invalid_format: geçersiz bir biçime sahip. Lütfen bir numara giriniz. diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js index 9309ed4188..8c4d5e971b 100644 --- a/config/ng-test.conf.js +++ b/config/ng-test.conf.js @@ -41,8 +41,21 @@ module.exports = function(config) { }, autoWatch: true, - - browsers: ['ChromeHeadless'], + browsers: ['CustomHeadlessChrome'], + customLaunchers: { + CustomHeadlessChrome: { + base: 'ChromeHeadless', + flags: [ + '--no-sandbox', + '--remote-debugging-port=9222', + '--enable-logging', + '--disable-background-timer-throttling', + '--disable-renderer-backgrounding', + '--proxy-bypass-list=*', + '--proxy-server=\'direct://\'' + ] + } + }, junitReporter: { outputFile: 'log/testacular-unit.xml', diff --git a/config/routes.rb b/config/routes.rb index 5a4638f81d..877ca407d3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,6 +89,9 @@ Openfoodnetwork::Application.routes.draw do get 'sitemap.xml', to: 'sitemap#index', defaults: { format: 'xml' } + # Mount DFC API endpoints + mount DfcProvider::Engine, at: '/' + # Mount Spree's routes mount Spree::Core::Engine, :at => '/' end diff --git a/db/default/users.rb b/db/default/users.rb index 4681ec1531..cc30bb5076 100644 --- a/db/default/users.rb +++ b/db/default/users.rb @@ -6,13 +6,13 @@ def prompt_for_admin_password password = ENV['ADMIN_PASSWORD'].dup say "Admin Password #{password}" else - password = ask('Password [spree123]: ') do |q| + password = ask('Password [ofn123]: ') do |q| q.echo = false q.validate = /^(|.{5,40})$/ q.responses[:not_valid] = 'Invalid password. Must be at least 5 characters long.' q.whitespace = :strip end - password = 'spree123' if password.blank? + password = 'ofn123' if password.blank? end password @@ -23,11 +23,11 @@ def prompt_for_admin_email email = ENV['ADMIN_EMAIL'].dup say "Admin User #{email}" else - email = ask('Email [spree@example.com]: ') do |q| + email = ask('Email [ofn@example.com]: ') do |q| q.echo = true q.whitespace = :strip end - email = 'spree@example.com' if email.blank? + email = 'ofn@example.com' if email.blank? end email @@ -35,8 +35,8 @@ end def create_admin_user if ENV.fetch("AUTO_ACCEPT", true) - password = ENV.fetch("ADMIN_PASSWORD", "spree123").dup - email = ENV.fetch("ADMIN_EMAIL", "spree@example.com").dup + password = ENV.fetch("ADMIN_PASSWORD", "ofn123").dup + email = ENV.fetch("ADMIN_EMAIL", "ofn@example.com").dup else puts 'Create the admin user (press enter for defaults).' #name = prompt_for_admin_name unless name diff --git a/db/migrate/20200430105459_add_timestamps_to_order_cycle_schedules.rb b/db/migrate/20200430105459_add_timestamps_to_order_cycle_schedules.rb new file mode 100644 index 0000000000..5f57cd13a2 --- /dev/null +++ b/db/migrate/20200430105459_add_timestamps_to_order_cycle_schedules.rb @@ -0,0 +1,7 @@ +class AddTimestampsToOrderCycleSchedules < ActiveRecord::Migration + def change + change_table :order_cycle_schedules do |t| + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 31b73524cf..c99d53de6b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(:version => 20200429122446) do +ActiveRecord::Schema.define(version: 20200430105459) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -275,6 +275,8 @@ ActiveRecord::Schema.define(:version => 20200429122446) do create_table "order_cycle_schedules", force: true do |t| t.integer "order_cycle_id", null: false t.integer "schedule_id", null: false + t.datetime "created_at" + t.datetime "updated_at" end add_index "order_cycle_schedules", ["order_cycle_id"], name: "index_order_cycle_schedules_on_order_cycle_id", using: :btree diff --git a/docker-compose.yml b/docker-compose.yml index f4407eb6a4..72d25a7d4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,14 +24,27 @@ services: depends_on: - db environment: - ADMIN_EMAIL: ofn@example.com - ADMIN_PASSWORD: ofn123 OFN_DB_HOST: db command: > bash -c "wait-for-it -t 30 db:5432 && rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" - + worker: + tty: true + stdin_open: true + build: . + volumes: + - .:/usr/src/app + - gems:/bundles + - ./config/database.yml:/usr/src/app/config/database.yml + - ./config/application.yml.example:/usr/src/app/config/application.yml + depends_on: + - db + environment: + OFN_DB_HOST: db + command: > + bash -c "wait-for-it -t 30 db:5432 && + bundle exec rake jobs:work" volumes: gems: postgres: diff --git a/engines/dfc_provider/README.md b/engines/dfc_provider/README.md new file mode 100644 index 0000000000..8e73633c46 --- /dev/null +++ b/engines/dfc_provider/README.md @@ -0,0 +1,10 @@ +# DfcProvider + +This engine is implementing the Data Food Consortium specifications in order to serve semantic data. +You can find more details about this on https://github.com/datafoodconsortium. + +Basically, it allows an OFN user linked to an enterprise: +* to serve his Products Catalog through a dedicated API using JSON-LD format, structured by the DFC Ontology +* to be authenticated thanks to an Access Token from DFC Authorization server (using an OIDC implementation) + +The API endpoint for the catalog is `/api/dfc_provider/enterprise/prodcuts.json` and you need to pass the token inside an authentication header (`Authentication: Bearer 123mytoken456`). diff --git a/engines/dfc_provider/app/controllers/dfc_provider/api/products_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/api/products_controller.rb new file mode 100644 index 0000000000..54c5bd4556 --- /dev/null +++ b/engines/dfc_provider/app/controllers/dfc_provider/api/products_controller.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# Controller used to provide the API products for the DFC application +module DfcProvider + module Api + class ProductsController < ::ActionController::Base + # To access 'base_url' helper + include Rails.application.routes.url_helpers + + before_filter :check_authorization, + :check_user, + :check_enterprise + + respond_to :json + + def index + products = @enterprise. + inventory_variants. + includes(:product, :inventory_items) + + serialized_data = ::DfcProvider::ProductSerializer. + new(products, base_url). + serialized_data + + render json: serialized_data + end + + private + + def check_enterprise + @enterprise = + if params[:enterprise_id] == 'default' + @user.enterprises.first + else + @user.enterprises.where(id: params[:enterprise_id]).first + end + + return if @enterprise.present? + + head :not_found + end + + def check_authorization + return if access_token.present? + + head :unprocessable_entity + end + + def check_user + @user = authorization_control.process + + return if @user.present? + + head :unauthorized + end + + def base_url + "#{root_url}api/dfc_provider" + end + + def access_token + request.headers['Authorization'].to_s.split(' ').last + end + + def authorization_control + DfcProvider::AuthorizationControl.new(access_token) + end + end + end +end diff --git a/engines/dfc_provider/app/serializers/dfc_provider/product_serializer.rb b/engines/dfc_provider/app/serializers/dfc_provider/product_serializer.rb new file mode 100644 index 0000000000..42749a3d8d --- /dev/null +++ b/engines/dfc_provider/app/serializers/dfc_provider/product_serializer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Serializer used to render the products passed +# into JSON-LD format based on DFC ontology +module DfcProvider + class ProductSerializer + def initialize(products, base_url) + @products = products + @base_url = base_url + end + + def serialized_data + { + "@context" => + { + "DFC" => "http://datafoodconsortium.org/ontologies/DFC_FullModel.owl#", + "@base" => @base_url + }, + "@id" => "/enterprise/products", + "DFC:supplies" => serialized_products + } + end + + private + + def serialized_products + @products.map do |variant| + { + "DFC:description" => variant.name, + "DFC:quantity" => variant.total_on_hand, + "@id" => variant.id, + "DFC:hasUnit" => { "@id" => "/unit/#{variant.unit_description.presence || 'piece'}" } + } + end + end + end +end diff --git a/engines/dfc_provider/app/services/dfc_provider/authorization_control.rb b/engines/dfc_provider/app/services/dfc_provider/authorization_control.rb new file mode 100644 index 0000000000..beb1885816 --- /dev/null +++ b/engines/dfc_provider/app/services/dfc_provider/authorization_control.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Service used to authorize the user on DCF Provider API +# It controls an OICD Access token and an enterprise. +module DfcProvider + class AuthorizationControl + def initialize(access_token) + @access_token = access_token + end + + def process + decode_token + find_ofn_user + end + + def decode_token + data = JWT.decode( + @access_token, + nil, + false + ) + + @header = data.last + @payload = data.first + end + + def find_ofn_user + Spree::User.where(email: @payload['email']).first + end + end +end diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb new file mode 100644 index 0000000000..b88e13e768 --- /dev/null +++ b/engines/dfc_provider/config/routes.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +DfcProvider::Engine.routes.draw do + namespace :api do + scope :dfc_provider, as: :dfc_provider, path: '/dfc_provider' do + resources :enterprises, only: :none do + resources :products, only: [:index] + end + end + end +end diff --git a/engines/dfc_provider/dfc_provider.gemspec b/engines/dfc_provider/dfc_provider.gemspec new file mode 100644 index 0000000000..4684aaaa18 --- /dev/null +++ b/engines/dfc_provider/dfc_provider.gemspec @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +$LOAD_PATH.push File.expand_path('lib', __dir__) + +# Maintain your gem's version: +require "dfc_provider/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |spec| + spec.name = 'dfc_provider' + spec.version = DfcProvider::VERSION + spec.authors = ["developers@ofn"] + spec.summary = 'Provides an API stack implementing DFC semantic ' \ + 'specifications' + + spec.files = Dir["{app,config,lib}/**/*"] + ['README.md'] + spec.test_files = Dir['spec/**/*'] + + spec.add_dependency 'jwt', '~> 2.2' + spec.add_dependency 'rspec', '~> 3.9' +end diff --git a/engines/dfc_provider/lib/dfc_provider.rb b/engines/dfc_provider/lib/dfc_provider.rb new file mode 100644 index 0000000000..b0cf98121a --- /dev/null +++ b/engines/dfc_provider/lib/dfc_provider.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "dfc_provider/engine" + +module DfcProvider +end diff --git a/engines/dfc_provider/lib/dfc_provider/engine.rb b/engines/dfc_provider/lib/dfc_provider/engine.rb new file mode 100644 index 0000000000..fedaef351a --- /dev/null +++ b/engines/dfc_provider/lib/dfc_provider/engine.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module DfcProvider + class Engine < ::Rails::Engine + isolate_namespace DfcProvider + end +end diff --git a/engines/dfc_provider/lib/dfc_provider/version.rb b/engines/dfc_provider/lib/dfc_provider/version.rb new file mode 100644 index 0000000000..3b891391e3 --- /dev/null +++ b/engines/dfc_provider/lib/dfc_provider/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module DfcProvider + VERSION = '0.0.1' +end diff --git a/engines/dfc_provider/spec/controllers/dfc_provider/api/products_controller_spec.rb b/engines/dfc_provider/spec/controllers/dfc_provider/api/products_controller_spec.rb new file mode 100644 index 0000000000..c3f40a16a0 --- /dev/null +++ b/engines/dfc_provider/spec/controllers/dfc_provider/api/products_controller_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DfcProvider::Api::ProductsController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:enterprise) { create(:distributor_enterprise, owner: user) } + let(:product) { create(:simple_product, supplier: enterprise ) } + let!(:visible_inventory_item) do + create(:inventory_item, + enterprise: enterprise, + variant: product.variants.first, + visible: true) + end + + describe('.index') do + context 'with authorization token' do + before do + request.env['Authorization'] = 'Bearer 123456.abcdef.123456' + end + + context 'with an authenticated user' do + before do + allow_any_instance_of(DfcProvider::AuthorizationControl) + .to receive(:process) + .and_return(user) + end + + context 'with an enterprise' do + context 'given with an id' do + context 'related to the user' do + before { get :index, enterprise_id: 'default' } + + it 'is successful' do + expect(response.status).to eq 200 + end + + it 'renders the related product' do + expect(response.body) + .to include(product.variants.first.name) + end + end + + context 'not related to the user' do + let(:enterprise) { create(:enterprise) } + + it 'returns not_found head' do + get :index, enterprise_id: enterprise.id + expect(response.status).to eq 404 + end + end + end + + context 'as default' do + before { get :index, enterprise_id: 'default' } + + it 'is successful' do + expect(response.status).to eq 200 + end + + it 'renders the related product' do + expect(response.body) + .to include(product.variants.first.name) + end + end + end + + context 'without a recorded enterprise' do + let(:enterprise) { create(:enterprise) } + + it 'returns not_found head' do + get :index, enterprise_id: 'default' + expect(response.status).to eq 404 + end + end + end + + context 'without an authenticated user' do + it 'returns unauthorized head' do + allow_any_instance_of(DfcProvider::AuthorizationControl) + .to receive(:process) + .and_return(nil) + + get :index, enterprise_id: 'default' + expect(response.status).to eq 401 + end + end + end + + context 'without an authorization token' do + it 'returns unprocessable_entity head' do + get :index, enterprise_id: enterprise.id + expect(response.status).to eq 422 + end + end + end +end diff --git a/engines/dfc_provider/spec/spec_helper.rb b/engines/dfc_provider/spec/spec_helper.rb new file mode 100644 index 0000000000..3492f4f944 --- /dev/null +++ b/engines/dfc_provider/spec/spec_helper.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "../../spec/spec_helper.rb" + +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } diff --git a/engines/order_management/app/services/order_management/subscriptions/summarizer.rb b/engines/order_management/app/services/order_management/subscriptions/summarizer.rb index 0642e22044..8029716338 100644 --- a/engines/order_management/app/services/order_management/subscriptions/summarizer.rb +++ b/engines/order_management/app/services/order_management/subscriptions/summarizer.rb @@ -18,7 +18,7 @@ module OrderManagement end def record_issue(type, order, message = nil) - Rails.logger.info "Issue in Subscription Order #{order.id}: #{type}" + JobLogger.logger.info("Issue in Subscription Order #{order.id}: #{type}") summary_for(order).record_issue(type, order, message) end @@ -28,7 +28,7 @@ module OrderManagement error = "Subscription#{type.to_s.camelize}Error" line1 = "#{error}: Cannot process order #{order.number} due to errors" line2 = "Errors: #{order.errors.full_messages.join(', ')}" - Rails.logger.info("#{line1}\n#{line2}") + JobLogger.logger.info("#{line1}\n#{line2}") record_issue(type, order, line2) end diff --git a/engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb b/engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb index 09013c204c..d6eca4d26a 100644 --- a/engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb +++ b/engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb @@ -8,7 +8,7 @@ module OrderManagement let(:order) { create(:order) } let(:summarizer) { OrderManagement::Subscriptions::Summarizer.new } - before { allow(Rails.logger).to receive(:info) } + before { allow(JobLogger.logger).to receive(:info) } describe "#summary_for" do let(:order) { double(:order, distributor_id: 123) } diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 8bc386b2a6..dd6db131af 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -10,11 +10,25 @@ module OpenFoodNetwork end def shipping_method_services - @shipping_method_services ||= Spree::ShippingMethod.services + @shipping_method_services ||= begin + CacheService.cached_data_by_class("shipping_method_services", Spree::ShippingMethod) do + # This result relies on a simple join with DistributorShippingMethod. + # Updated DistributorShippingMethod records touch their associated Spree::ShippingMethod. + Spree::ShippingMethod.services + end + end end def supplied_taxons - @supplied_taxons ||= Spree::Taxon.supplied_taxons + @supplied_taxons ||= begin + CacheService.cached_data_by_class("supplied_taxons", 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 + end + end end def all_distributed_taxons diff --git a/lib/open_food_network/order_and_distributor_report.rb b/lib/open_food_network/order_and_distributor_report.rb index b705c0cb16..570096cb23 100644 --- a/lib/open_food_network/order_and_distributor_report.rb +++ b/lib/open_food_network/order_and_distributor_report.rb @@ -92,7 +92,7 @@ module OpenFoodNetwork # @return [Array] def row_for(line_item, order) [ - order.created_at, + order.completed_at.strftime("%F %T"), order.id, order.bill_address.full_name, order.email, diff --git a/lib/open_food_network/sales_tax_report.rb b/lib/open_food_network/sales_tax_report.rb index a32d3fe311..1281dd28a4 100644 --- a/lib/open_food_network/sales_tax_report.rb +++ b/lib/open_food_network/sales_tax_report.rb @@ -57,7 +57,7 @@ module OpenFoodNetwork totals = totals_of order.line_items shipping_cost = shipping_cost_for order - [order.number, order.created_at, totals[:items], totals[:items_total], + [order.number, order.completed_at.strftime("%F %T"), totals[:items], totals[:items_total], totals[:taxable_total], totals[:sales_tax], shipping_cost, order.shipping_tax, order.enterprise_fee_tax, order.total_tax, order.bill_address.full_name, order.distributor.andand.name] end diff --git a/lib/spree/api/testing_support/setup.rb b/lib/spree/api/testing_support/setup.rb index f07b152999..3ff75abc04 100644 --- a/lib/spree/api/testing_support/setup.rb +++ b/lib/spree/api/testing_support/setup.rb @@ -4,7 +4,7 @@ module Spree module Setup def sign_in_as_user! let!(:current_api_user) do - user = Spree::LegacyUser.new(email: "spree@example.com") + user = Spree::LegacyUser.new(email: "ofn@example.com") user.stub(:has_spree_role?).with("admin").and_return(false) user.stub(:enterprises) { [] } user.stub(:owned_groups) { [] } @@ -31,7 +31,7 @@ module Spree def sign_in_as_admin! let!(:current_api_user) do - user = Spree::LegacyUser.new(email: "spree@example.com") + user = Spree::LegacyUser.new(email: "ofn@example.com") user.stub(:has_spree_role?).with("admin").and_return(true) # Stub enterprises, needed for cancan ability checks diff --git a/lib/tasks/sample_data/shipping_method_factory.rb b/lib/tasks/sample_data/shipping_method_factory.rb index 02b5b2d59f..5bfb382318 100644 --- a/lib/tasks/sample_data/shipping_method_factory.rb +++ b/lib/tasks/sample_data/shipping_method_factory.rb @@ -26,7 +26,7 @@ class ShippingMethodFactory def create_pickup(enterprise) create_shipping_method( enterprise, - name: "Pickup", + name: "Pickup #{enterprise.name}", description: "pick-up at your awesome hub gathering place", require_ship_address: false, calculator_type: "Calculator::Weight" @@ -36,7 +36,7 @@ class ShippingMethodFactory def create_delivery(enterprise) delivery = create_shipping_method( enterprise, - name: "Home delivery", + name: "Home delivery #{enterprise.name}", description: "yummy food delivered at your door", require_ship_address: true, calculator_type: "Spree::Calculator::FlatRate" diff --git a/script/setup b/script/setup index 65f46d7949..949cdcc234 100755 --- a/script/setup +++ b/script/setup @@ -54,7 +54,7 @@ if [ ! -f config/newrelic.yml ]; then fi # Set up the database for both development and test -# Confirming the default user and password Spree prompts +# Confirming the default user and password printf '\n\n' | bundle exec rake db:setup db:test:prepare printf '\n' @@ -65,8 +65,8 @@ printf '\n' printf "${YELLOW}WELCOME TO OPEN FOOD NETWORK!\n" printf '\n' -printf "To login as the Spree default user, use:" +printf "To login as the default user, use:" printf '\n' printf '\n' -printf ' email: spree@example.com\n' -printf " password: spree123\n${NO_COLOR}" +printf ' email: ofn@example.com\n' +printf " password: ofn123\n${NO_COLOR}" diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index 3742fcab2c..d501aa98fb 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::BaseController do context "signed in as a user using an authentication extension" do before do allow(controller).to receive_messages try_spree_current_user: - double(email: "spree@example.com") + double(email: "ofn@example.com") end it "can make a request" do diff --git a/spec/controllers/api/shipments_controller_spec.rb b/spec/controllers/api/shipments_controller_spec.rb index 659e1dc761..333d3c3220 100644 --- a/spec/controllers/api/shipments_controller_spec.rb +++ b/spec/controllers/api/shipments_controller_spec.rb @@ -131,7 +131,7 @@ describe Api::ShipmentsController, type: :controller do before do allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true) # For the shipment notification email - Spree::Config[:mails_from] = "spree@example.com" + Spree::Config[:mails_from] = "ofn@example.com" shipment.update!(shipment.order) expect(shipment.state).to eq("ready") diff --git a/spec/controllers/api/shops_controller_spec.rb b/spec/controllers/api/shops_controller_spec.rb index 3ae4e55a01..33343e8d84 100644 --- a/spec/controllers/api/shops_controller_spec.rb +++ b/spec/controllers/api/shops_controller_spec.rb @@ -37,8 +37,9 @@ module Api spree_get :closed_shops, nil expect(json_response).not_to match hub.name - expect(json_response[0]['id']).to eq closed_hub1.id - expect(json_response[1]['id']).to eq closed_hub2.id + + response_ids = json_response.map { |shop| shop['id'] } + expect(response_ids).to contain_exactly(closed_hub1.id, closed_hub2.id) end end end diff --git a/spec/controllers/spree/admin/mail_methods_controller_spec.rb b/spec/controllers/spree/admin/mail_methods_controller_spec.rb index b58fc75da5..422491660e 100644 --- a/spec/controllers/spree/admin/mail_methods_controller_spec.rb +++ b/spec/controllers/spree/admin/mail_methods_controller_spec.rb @@ -8,13 +8,13 @@ describe Spree::Admin::MailMethodsController do context "#update" do it "should reinitialize the mail settings" do expect(Spree::Core::MailSettings).to receive(:init) - spree_put :update, enable_mail_delivery: "1", mails_from: "spree@example.com" + spree_put :update, enable_mail_delivery: "1", mails_from: "ofn@example.com" end end it "can trigger testmail" do request.env["HTTP_REFERER"] = "/" - user = double('User', email: 'user@spree.com', + user = double('User', email: 'user@example.com', spree_api_key: 'fake', id: nil, owned_groups: nil) diff --git a/spec/factories/order_factory.rb b/spec/factories/order_factory.rb index 8b3dd9b24d..282d4772c1 100644 --- a/spec/factories/order_factory.rb +++ b/spec/factories/order_factory.rb @@ -46,8 +46,12 @@ FactoryBot.define do distributor { create(:distributor_enterprise) } order_cycle { create(:simple_order_cycle) } - after(:create) do |order| - create(:payment, amount: order.total + 10_000, order: order, state: "completed") + transient do + credit_amount { 10_000 } + end + + after(:create) do |order, evaluator| + create(:payment, amount: order.total + evaluator.credit_amount, order: order, state: "completed") order.reload end end @@ -56,8 +60,12 @@ FactoryBot.define do distributor { create(:distributor_enterprise) } order_cycle { create(:simple_order_cycle) } - after(:create) do |order| - create(:payment, amount: order.total - 1, order: order, state: "completed") + transient do + unpaid_amount { 1 } + end + + after(:create) do |order, evaluator| + create(:payment, amount: order.total - evaluator.unpaid_amount, order: order, state: "completed") order.reload end end diff --git a/spec/features/admin/configuration/mail_methods_spec.rb b/spec/features/admin/configuration/mail_methods_spec.rb index 6ff8db2d6b..d1e550db02 100644 --- a/spec/features/admin/configuration/mail_methods_spec.rb +++ b/spec/features/admin/configuration/mail_methods_spec.rb @@ -15,7 +15,7 @@ describe "Mail Methods" do end it "should be able to edit mail method settings" do - fill_in "mail_bcc", with: "spree@example.com99" + fill_in "mail_bcc", with: "ofn@example.com" click_button "Update" expect(page).to have_content("successfully updated!") end diff --git a/spec/features/admin/order_cycles_complex_nav_check_spec.rb b/spec/features/admin/order_cycles_complex_nav_check_spec.rb new file mode 100644 index 0000000000..8e6e4366fc --- /dev/null +++ b/spec/features/admin/order_cycles_complex_nav_check_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature ' + As an administrator + I want to be alerted when I navigate away from a dirty order cycle form +', js: true do + include AuthenticationWorkflow + + scenario "alert when navigating away from dirty form" do + # Given a 'complex' order cycle form + oc = create(:order_cycle) + + # When I edit the form + quick_login_as_admin + visit edit_admin_order_cycle_path(oc) + + wait_for_edit_form_to_load_order_cycle(oc) + + expect(page).to have_selector '.wizard-progress .current a', text: '1. GENERAL SETTINGS' + expect(page.find('#order_cycle_name').value).to eq(oc.name) + expect(page).to have_button("Save", disabled: true) + fill_in 'order_cycle_name', with: 'Plums & Avos' + + # Then the form is dirty and an alert warns about navigating away from the form + expect(page).to have_button("Save", disabled: false) + expect(page).to have_selector '.wizard-progress a', text: '2. INCOMING PRODUCTS' + accept_alert do + click_link '2. Incoming Products' + end + end + + private + + def wait_for_edit_form_to_load_order_cycle(order_cycle) + expect(page).to have_field "order_cycle_name", with: order_cycle.name + end +end diff --git a/spec/features/consumer/caching/darkwarm_caching_spec.rb b/spec/features/consumer/caching/darkwarm_caching_spec.rb new file mode 100644 index 0000000000..d32ebb8b9e --- /dev/null +++ b/spec/features/consumer/caching/darkwarm_caching_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "spec_helper" + +feature "Darkswarm data caching", js: true, caching: true do + let!(:taxon) { create(:taxon, name: "Cached Taxon") } + let!(:property) { create(:property, presentation: "Cached Property") } + + let!(:producer) { create(:supplier_enterprise) } + let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, is_primary_producer: true) } + let!(:product) { create(:simple_product, supplier: producer, primary_taxon: taxon, taxons: [taxon], properties: [property]) } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: distributor) } + let(:exchange) { order_cycle.exchanges.outgoing.where(receiver_id: distributor.id).first } + + before do + exchange.variants << product.variants.first + end + + describe "caching injected taxons and properties" do + it "caches taxons and properties" do + expect(Spree::Taxon).to receive(:all) { [taxon] } + expect(Spree::Property).to receive(:all) { [property] } + + visit shops_path + + expect(Spree::Taxon).to_not receive(:all) + expect(Spree::Property).to_not receive(:all) + + visit shops_path + end + + xit "invalidates caches for taxons and properties" do + visit shops_path + + taxon_timestamp1 = CacheService.latest_timestamp_by_class(Spree::Taxon) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_taxons_key}" + + property_timestamp1 = CacheService.latest_timestamp_by_class(Spree::Property) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_properties_key}" + + toggle_filters + + within "#hubs .filter-row" do + expect(page).to have_content taxon.name + expect(page).to have_content property.presentation + end + + taxon.update_attributes!(name: "Changed Taxon") + property.update_attributes!(presentation: "Changed Property") + + # Clear timed shops cache so we can test uncached supplied properties + clear_shops_cache + + visit shops_path + + taxon_timestamp2 = CacheService.latest_timestamp_by_class(Spree::Taxon) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_taxons_key}" + + property_timestamp2 = CacheService.latest_timestamp_by_class(Spree::Property) + expect_cached "views/#{CacheService::FragmentCaching.ams_all_properties_key}" + + expect(taxon_timestamp1).to_not eq taxon_timestamp2 + expect(property_timestamp1).to_not eq property_timestamp2 + + toggle_filters + + within "#hubs .filter-row" do + expect(page).to have_content "Changed Taxon" + expect(page).to have_content "Changed Property" + end + end + end + + def expect_cached(key) + expect(Rails.cache.exist?(key)).to be true + end + + def clear_shops_cache + cache_key = "views/#{CacheService::FragmentCaching.ams_shops[0]}" + Rails.cache.delete cache_key + end +end diff --git a/spec/features/consumer/caching/shops_caching_spec.rb b/spec/features/consumer/caching/shops_caching_spec.rb new file mode 100644 index 0000000000..654a2f4524 --- /dev/null +++ b/spec/features/consumer/caching/shops_caching_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "spec_helper" + +feature "Shops caching", js: true, caching: true do + include WebHelper + include UIComponentHelper + + let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, is_primary_producer: true) } + let!(:order_cycle) { create(:open_order_cycle, distributors: [distributor], coordinator: distributor) } + + describe "caching enterprises AMS data" do + it "caches data for all enterprises, with the provided options" do + visit shops_path + + key, options = CacheService::FragmentCaching.ams_shops + expect_cached "views/#{key}", options + end + + it "keeps data cached for a short time on subsequent requests" do + # Ensure sufficient time for requests to load and timed caches to expire + Timecop.travel(10.minutes.ago) do + visit shops_path + + expect(page).to have_content distributor.name + + distributor.name = "New Name" + distributor.save! + + visit shops_path + + expect(page).to_not have_content "New Name" # Displayed name is unchanged + end + + # A while later... + visit shops_path + expect(page).to have_content "New Name" # Displayed name is now changed + end + end + + describe "API action caching on taxons and properties" do + let!(:taxon) { create(:taxon, name: "Cached Taxon") } + let!(:taxon2) { create(:taxon, name: "New Taxon") } + let!(:property) { create(:property, presentation: "Cached Property") } + let!(:property2) { create(:property, presentation: "New Property") } + let!(:product) { create(:product, taxons: [taxon], primary_taxon: taxon, properties: [property]) } + let(:exchange) { order_cycle.exchanges.to_enterprises(distributor).outgoing.first } + + let(:test_domain) { "#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}" } + let(:taxons_key) { "views/#{test_domain}/api/order_cycles/#{order_cycle.id}/taxons?distributor=#{distributor.id}" } + let(:properties_key) { "views/#{test_domain}/api/order_cycles/#{order_cycle.id}/properties?distributor=#{distributor.id}" } + let(:options) { { expires_in: CacheService::FILTERS_EXPIRY } } + + before do + exchange.variants << product.variants.first + end + + it "caches rendered response for taxons and properties, with the provided options" do + visit enterprise_shop_path(distributor) + + expect(page).to have_content "Cached Taxon" + expect(page).to have_content "Cached Property" + + expect_cached taxons_key, options + expect_cached properties_key, options + end + + it "keeps data cached for a short time on subsequent requests" do + # Ensure sufficient time for requests to load and timed caches to expire + Timecop.travel(10.minutes.ago) do + visit enterprise_shop_path(distributor) + + expect(page).to have_content taxon.name + expect(page).to have_content property.presentation + + product.update_attribute(:taxons, [taxon2]) + product.update_attribute(:primary_taxon, taxon2) + product.update_attribute(:properties, [property2]) + + visit enterprise_shop_path(distributor) + + expect(page).to have_content taxon.name # Taxon list is unchanged + expect(page).to have_content property.presentation # Property list is unchanged + end + + # A while later... + visit enterprise_shop_path(distributor) + + expect(page).to have_content taxon2.name + expect(page).to have_content property2.presentation + end + end + + def expect_cached(key, options = {}) + expect(Rails.cache.exist?(key, options)).to be true + end +end diff --git a/spec/features/consumer/producers_spec.rb b/spec/features/consumer/producers_spec.rb index 11d2e52cf6..cf49db00df 100644 --- a/spec/features/consumer/producers_spec.rb +++ b/spec/features/consumer/producers_spec.rb @@ -16,8 +16,8 @@ feature ' let(:taxon_fruit) { create(:taxon, name: 'Fruit') } let(:taxon_veg) { create(:taxon, name: 'Vegetables') } - let!(:product1) { create(:simple_product, supplier: producer1, taxons: [taxon_fruit]) } - let!(:product2) { create(:simple_product, supplier: producer2, taxons: [taxon_veg]) } + let!(:product1) { create(:simple_product, supplier: producer1, primary_taxon: taxon_fruit, taxons: [taxon_fruit]) } + let!(:product2) { create(:simple_product, supplier: producer2, primary_taxon: taxon_veg, taxons: [taxon_veg]) } let(:shop) { create(:distributor_enterprise) } let!(:er) { create(:enterprise_relationship, parent: shop, child: producer1) } diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index ace11e775a..6a02206e1d 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -34,7 +34,7 @@ feature "full-page cart", js: true do click_link "Continue shopping" expect(page).to have_no_link "Continue shopping" - expect(page).to have_button "Edit your cart" + expect(page).to have_link "Shop" expect(page).to have_no_content distributor.preferred_shopfront_message end end diff --git a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb index 290c6af516..20c7bfccb8 100644 --- a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb +++ b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb @@ -46,9 +46,8 @@ feature "Using embedded shopfront functionality", js: true do it "allows shopping and checkout" do on_embedded_page do fill_in "variants[#{variant.id}]", with: 1 - wait_until_enabled 'input.add_to_cart' - first("input.add_to_cart:not([disabled='disabled'])").click + edit_cart expect(page).to have_text 'Your shopping cart' find('a#checkout-link').click @@ -91,11 +90,11 @@ feature "Using embedded shopfront functionality", js: true do it "redirects to embedded hub on logout when embedded" do on_embedded_page do - wait_for_shop_loaded + wait_for_cart find('ul.right li#login-link a').click login_with_modal - wait_for_shop_loaded + wait_for_cart wait_until { page.find('ul.right li.user-menu.has-dropdown').value.present? } logout_via_navigation @@ -106,14 +105,6 @@ feature "Using embedded shopfront functionality", js: true do private - # When you have pending changes and try to navigate away from a page, it asks you "Are you sure?". - # When we click the "Update" button to save changes, we need to wait - # until it is actually saved and "loading" disappears before doing anything else. - def wait_for_shop_loaded - page.has_no_content? "Loading" - page.has_no_css? "input[value='Updating cart...']" - end - def login_with_modal page.has_selector? 'div.login-modal', visible: true diff --git a/spec/features/consumer/shopping/products_spec.rb b/spec/features/consumer/shopping/products_spec.rb index 726346a9ed..0846845021 100644 --- a/spec/features/consumer/shopping/products_spec.rb +++ b/spec/features/consumer/shopping/products_spec.rb @@ -30,8 +30,6 @@ feature "As a consumer I want to view products", js: true do product.save! visit shop_path - select "monday", from: "order_cycle_id" - expect(page).to have_content product.name click_link product.name @@ -48,8 +46,6 @@ feature "As a consumer I want to view products", js: true do product.save! visit shop_path - select "monday", from: "order_cycle_id" - expect(page).to have_content product.name click_link product.name diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 432f750f17..e1679791d4 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -50,7 +50,8 @@ feature "As a consumer I want to shop with a distributor", js: true do it "selects an order cycle if only one is open" do exchange1.update_attribute :pickup_time, "turtles" visit shop_path - expect(page).to have_selector "option[selected]", text: 'turtles' + expect(page).to have_selector "p", text: 'turtles' + expect(page).not_to have_content "choose when you want your order" end describe "with multiple order cycles" do @@ -63,8 +64,10 @@ feature "As a consumer I want to shop with a distributor", js: true do it "shows a select with all order cycles, but doesn't show the products by default" do visit shop_path + expect(page).to have_selector "option", text: 'frogs' expect(page).to have_selector "option", text: 'turtles' + expect(page).to have_content "choose when you want your order" expect(page).not_to have_selector("input.button.right", visible: true) end @@ -209,8 +212,6 @@ feature "As a consumer I want to shop with a distributor", js: true do it "filters search results properly" do visit shop_path - select "frogs", from: "order_cycle_id" - fill_in "search", with: "74576345634XXXXXX" expect(page).to have_content "Sorry, no results found" expect(page).not_to have_content product2.name @@ -222,8 +223,6 @@ feature "As a consumer I want to shop with a distributor", js: true do it "returns search results for products where the search term matches one of the product's variant names" do visit shop_path - select "frogs", from: "order_cycle_id" - fill_in "search", with: "Badg" # For variant with display_name "Badgers" within('div.pad-top') do @@ -429,6 +428,34 @@ feature "As a consumer I want to shop with a distributor", js: true do end end end + + context "when a variant is soft-deleted" do + describe "adding the soft-deleted variant to the cart" do + it "handles it as if the variant has gone out of stock" do + variant.delete + + fill_in "variants[#{variant.id}]", with: '1' + + expect_out_of_stock_behavior + end + end + + context "when the soft-deleted variant has an associated override" do + describe "adding the soft-deleted variant to the cart" do + let!(:variant_override) { + create(:variant_override, variant: variant, hub: distributor, count_on_hand: 100) + } + + it "handles it as if the variant has gone out of stock" do + variant.delete + + fill_in "variants[#{variant.id}]", with: '1' + + expect_out_of_stock_behavior + end + end + end + end end context "when no order cycles are available" do @@ -467,6 +494,7 @@ feature "As a consumer I want to shop with a distributor", js: true do expect(page).to have_content "Only approved customers can access this shop." expect(page).to have_content "login or signup" expect(page).to have_no_content product.name + expect(page).not_to have_selector "ordercycle" end end @@ -484,6 +512,7 @@ feature "As a consumer I want to shop with a distributor", js: true do expect(page).to have_content "Only approved customers can access this shop." expect(page).to have_content "please contact #{distributor.name}" expect(page).to have_no_content product.name + expect(page).not_to have_selector "ordercycle" end end @@ -543,4 +572,24 @@ feature "As a consumer I want to shop with a distributor", js: true do # waiting period before submitting the data... sleep 0.6 end + + def expect_out_of_stock_behavior + wait_for_debounce + wait_until { !cart_dirty } + + # Shows an "out of stock" modal, with helpful user feedback + within(".out-of-stock-modal") do + expect(page).to have_content I18n.t('js.out_of_stock.out_of_stock_text') + end + + # Removes the item from the client-side cart and marks the variant as unavailable + expect(page).to have_field "variants[#{variant.id}]", with: '0', disabled: true + expect(page).to have_selector "#variant-#{variant.id}.out-of-stock" + expect(page).to have_selector "#variants_#{variant.id}[ofn-on-hand='0']" + expect(page).to have_selector "#variants_#{variant.id}[disabled='disabled']" + + # We need to wait again for the cart to finish updating in Angular or the test can fail + # as the session cannot be reset properly (after the test) while it's still loading + wait_until { !cart_dirty } + end end diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index 8b37f5d22e..a4279786da 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -86,7 +86,7 @@ feature "shopping with variant overrides defined", js: true do it "shows the correct prices in the shopping cart" do fill_in "variants[#{product1_variant1.id}]", with: "2" - add_to_cart + edit_cart expect(page).to have_selector "tr.line-item.variant-#{product1_variant1.id} .cart-item-price", text: with_currency(61.11) expect(page).to have_field "order[line_items_attributes][0][quantity]", with: '2' diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 891ba07e8b..a9c262817c 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -106,13 +106,13 @@ feature 'Shops', js: true do describe "taxon badges" do let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) } - let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) } + let!(:p_closed) { create(:simple_product, primary_taxon: taxon_closed, taxons: [taxon_closed]) } let(:shop) { create(:distributor_enterprise, with_payment_and_shipping: true) } let(:taxon_closed) { create(:taxon, name: 'Closed') } describe "open shops" do let!(:open_oc) { create(:open_order_cycle, distributors: [shop], variants: [p_open.variants.first]) } - let!(:p_open) { create(:simple_product, taxons: [taxon_open]) } + let!(:p_open) { create(:simple_product, primary_taxon: taxon_open, taxons: [taxon_open]) } let(:taxon_open) { create(:taxon, name: 'Open') } it "shows taxons for open order cycles only" do diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 0b9c398a85..4bed7bbd97 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -1,6 +1,7 @@ describe 'Cart service', -> Cart = null Variants = null + RailsFlashLoader = null variant = null order = null $httpBackend = null @@ -18,9 +19,15 @@ describe 'Cart service', -> ] } angular.module('Darkswarm').value('currentOrder', order) - inject ($injector, _$httpBackend_, _$timeout_)-> + + module ($provide)-> + $provide.value "railsFlash", null + null + + inject ($injector, _$httpBackend_, _$timeout_, _RailsFlashLoader_)-> Variants = $injector.get("Variants") Cart = $injector.get("Cart") + RailsFlashLoader = _RailsFlashLoader_ $httpBackend = _$httpBackend_ $timeout = _$timeout_ @@ -86,6 +93,9 @@ describe 'Cart service', -> describe "updating the cart", -> data = {variants: {}} + beforeEach -> + spyOn(RailsFlashLoader, "loadFlash") + it "sets update_running during the update, and clears it on success", -> $httpBackend.expectPOST("/cart/populate", data).respond 200, {} expect(Cart.update_running).toBe(false) @@ -127,12 +137,11 @@ describe 'Cart service', -> $httpBackend.flush() expect(Cart.popQueue).not.toHaveBeenCalled() - it "retries the update on failure", -> - spyOn(Cart, 'scheduleRetry') + it "shows an error on cart update failure", -> $httpBackend.expectPOST("/cart/populate", data).respond 404, {} Cart.update() $httpBackend.flush() - expect(Cart.scheduleRetry).toHaveBeenCalled() + expect(RailsFlashLoader.loadFlash).toHaveBeenCalledWith({error: t('js.cart.add_to_cart_failed')}) describe "verifying stock levels after update", -> describe "when an item is out of stock", -> @@ -220,12 +229,6 @@ describe 'Cart service', -> expect(Cart.update_enqueued).toBe(false) expect(Cart.scheduleUpdate).toHaveBeenCalled() - it "schedules retries of updates", -> - spyOn(Cart, 'orderChanged') - Cart.scheduleRetry() - $timeout.flush() - expect(Cart.orderChanged).toHaveBeenCalled() - it "clears the cart", -> expect(Cart.line_items).not.toEqual [] Cart.clear() diff --git a/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee index 669f0ca74d..fb4def8342 100644 --- a/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee @@ -6,6 +6,8 @@ describe "Hubs service", -> { id: 2 active: false + icon_font: 'abc' + name: 'BugSpray' orders_close_at: new Date() type: "hub" visible: true @@ -15,12 +17,36 @@ describe "Hubs service", -> { id: 3 active: false + icon_font: 'def' + name: 'Toothbrush' + orders_close_at: new Date() + type: "hub" + visible: true + latitude: 100 + longitude: 200 + } + { + id: 4 + active: false + icon_font: 'ghi' + name: 'Covidness' orders_close_at: new Date() type: "hub" visible: true latitude: null longitude: null } + { + id: 5 + active: false + icon_font: 'jkl' + name: 'Toothbrush for kids' + orders_close_at: new Date() + type: "hub" + visible: true + latitude: 100 + longitude: 200 + } ] beforeEach -> @@ -34,7 +60,19 @@ describe "Hubs service", -> OfnMap = $injector.get("OfnMap") it "builds MapMarkers from enterprises", -> - expect(OfnMap.enterprises[0].id).toBe enterprises[0].id + expect(OfnMap.enterprises[0].id[0]).toBe enterprises[0].id it "excludes enterprises without latitude or longitude", -> - expect(OfnMap.enterprises.map (e) -> e.id).not.toContain enterprises[1].id + expect(OfnMap.enterprises.map (e) -> e.id).not.toContain [enterprises[2].id] + + it "the MapMarkers will a field for enterprises", -> + enterprise = enterprises[0] + expect(OfnMap.enterprises[0].enterprises[enterprise.id]).toEqual { id: enterprise.id, name: enterprise.name, icon: enterprise.icon_font } + + it "the MapMarkers will bunch up enterprises with the same coordinates", -> + enterprise1 = enterprises[1] + enterprise2 = enterprises[3] + hash = {} + hash[enterprise1.id] = { id: enterprise1.id, name: enterprise1.name, icon: enterprise1.icon_font } + hash[enterprise2.id] = { id: enterprise2.id, name: enterprise2.name, icon: enterprise2.icon_font } + expect(OfnMap.enterprises[2].enterprises).toEqual hash diff --git a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee index d1b9f38bb6..62c8c0a071 100644 --- a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee @@ -3,6 +3,7 @@ describe 'Products service', -> Products = null OrderCycle = {} Shopfront = null + RailsFlashLoader = null Variants = null Cart = null shopfront = null @@ -43,6 +44,8 @@ describe 'Products service', -> OrderCycle = order_cycle: order_cycle_id: 1 + RailsFlashLoader = + loadFlash: (arg) -> module 'Darkswarm' module ($provide)-> @@ -52,12 +55,14 @@ describe 'Products service', -> $provide.value "properties", properties $provide.value "Geo", Geo $provide.value "OrderCycle", OrderCycle + $provide.value "railsFlash", null null - inject ($injector, _$httpBackend_)-> + inject ($injector, _$httpBackend_, _RailsFlashLoader_)-> Products = $injector.get("Products") Shopfront = $injector.get("Shopfront") Properties = $injector.get("Properties") + RailsFlashLoader = _RailsFlashLoader_ Variants = $injector.get("Variants") Cart = $injector.get("Cart") $httpBackend = _$httpBackend_ diff --git a/spec/jobs/job_logger_spec.rb b/spec/jobs/job_logger_spec.rb new file mode 100644 index 0000000000..768f050150 --- /dev/null +++ b/spec/jobs/job_logger_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe JobLogger do + describe '.logger' do + it 'passes the message to the Logger instance' do + job_logger = instance_double(::Logger) + allow(job_logger).to receive(:formatter=) + allow(job_logger).to receive(:info) + + worker_logger = instance_double(::Logger, clone: job_logger) + allow(Delayed::Worker).to receive(:logger) { worker_logger } + + JobLogger.logger.info('log message') + + expect(job_logger).to have_received(:info).with('log message') + end + end + + describe JobLogger::Formatter do + describe '#call' do + it 'outputs timestamps, progname and message' do + timestamp = DateTime.new(2020,5,6, 22, 36, 0) + log_line = JobLogger::Formatter.new.call(nil, timestamp, 'progname', 'message') + expect(log_line).to eq("2020-05-06T22:36:00+0000: message\n") + end + end + end +end diff --git a/spec/lib/open_food_network/order_and_distributor_report_spec.rb b/spec/lib/open_food_network/order_and_distributor_report_spec.rb index 9843aa81ac..5b9e564b21 100644 --- a/spec/lib/open_food_network/order_and_distributor_report_spec.rb +++ b/spec/lib/open_food_network/order_and_distributor_report_spec.rb @@ -38,7 +38,7 @@ module OpenFoodNetwork table = subject.table expect(table[0]).to eq([ - order.reload.created_at, + order.reload.completed_at.strftime("%F %T"), order.id, bill_address.full_name, order.email, diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index fb92fa8036..7f198e29ea 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -227,6 +227,14 @@ describe OrderCycle do expect(oc.variants_distributed_by(d2)).not_to include p1_v_hidden, p1_v_deleted expect(oc.variants_distributed_by(d1)).to include p2_v end + + context "with soft-deleted variants" do + it "does not consider soft-deleted variants to be currently distributed in the oc" do + p2_v.delete + + expect(oc.variants_distributed_by(d1)).to_not include p2_v + end + end end context "when hub prefers product selection from inventory only" do diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index 7bbc506a0f..94501eaa4e 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -109,5 +109,15 @@ module Spree expect(shipping_method.include?(address)).to be true end end + + describe "touches" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:shipping_method) { create(:shipping_method) } + let(:add_distributor) { shipping_method.distributors << distributor } + + it "is touched when applied to a distributor" do + expect{ add_distributor }.to change { shipping_method.reload.updated_at} + end + end end end diff --git a/spec/models/spree/stock/quantifier_spec.rb b/spec/models/spree/stock/quantifier_spec.rb new file mode 100644 index 0000000000..2a06f74344 --- /dev/null +++ b/spec/models/spree/stock/quantifier_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Spree + module Stock + describe Quantifier do + let(:quantifier) { Spree::Stock::Quantifier.new(variant) } + let(:variant) { create(:variant, on_hand: 99) } + + describe "#total_on_hand" do + context "with a soft-deleted variant" do + before do + variant.delete + end + + it "returns zero stock for the variant" do + expect(quantifier.total_on_hand).to eq 0 + end + end + end + end + end +end diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 5a565c5146..54a0069da9 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -28,5 +28,23 @@ module Spree 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!(:taxon3) { create(:taxon) } + let!(:product) { create(:simple_product, primary_taxon: taxon1, taxons: [taxon1, taxon2]) } + + it "is touched when a taxon is applied to a product" do + expect{ product.taxons << taxon3 }.to change { taxon3.reload.updated_at } + end + + it "is touched when assignment of primary_taxon on a product changes" do + expect do + product.primary_taxon = taxon2 + product.save + end.to change { taxon2.reload.updated_at } + end + end end end diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index 4c5e5e71af..22b93a06d4 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -32,6 +32,11 @@ describe VariantOverride do expect(VariantOverride.indexed(hub1)).to eq( variant => vo1 ) expect(VariantOverride.indexed(hub2)).to eq( variant => vo2 ) end + + it "does not include overrides for soft-deleted variants" do + variant.delete + expect(VariantOverride.indexed(hub1)).to eq( nil => vo1 ) + end end end diff --git a/spec/serializers/api/admin/order_serializer_spec.rb b/spec/serializers/api/admin/order_serializer_spec.rb new file mode 100644 index 0000000000..513e764e54 --- /dev/null +++ b/spec/serializers/api/admin/order_serializer_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +describe Api::Admin::OrderSerializer do + let(:serializer) { described_class.new order } + + describe "#display_outstanding_balance" do + let(:order) { create(:order) } + + it "returns empty string" do + expect(serializer.display_outstanding_balance).to eql("") + end + + context "with outstanding payments" do + let(:order) { create(:order_without_full_payment, unpaid_amount: 10) } + + it "generates the outstanding balance" do + expect(serializer.display_outstanding_balance).to eql("$10.00") + end + end + + context "with credit owed" do + let(:order) { create(:order_with_credit_payment, credit_amount: 20) } + + it "generates the outstanding balance" do + expect(serializer.display_outstanding_balance).to eql("$-20.00") + end + end + end +end diff --git a/spec/serializers/api/cached_enterprise_serializer_spec.rb b/spec/serializers/api/cached_enterprise_serializer_spec.rb index 6bcaeffdc3..c1046fb7b2 100644 --- a/spec/serializers/api/cached_enterprise_serializer_spec.rb +++ b/spec/serializers/api/cached_enterprise_serializer_spec.rb @@ -9,18 +9,35 @@ describe Api::CachedEnterpriseSerializer do let(:duplicate_property) { create(:property, presentation: 'One') } let(:different_property) { create(:property, presentation: 'Two') } - let(:enterprise) do - create(:enterprise, properties: [duplicate_property, different_property]) - end - before do product = create(:product, properties: [property]) enterprise.supplied_products << product end - it "removes duplicate product and producer properties" do - properties = cached_enterprise_serializer.supplied_properties - expect(properties).to eq([property, different_property]) + context "when the enterprise is a producer" do + let(:enterprise) do + create(:enterprise, + is_primary_producer: true, + properties: [duplicate_property, different_property]) + end + + it "serializes combined product and producer properties without duplicates" do + properties = cached_enterprise_serializer.supplied_properties + expect(properties).to eq([property, different_property]) + end + end + + context "when the enterprise is not a producer" do + let(:enterprise) do + create(:enterprise, + is_primary_producer: false, + properties: [duplicate_property, different_property]) + end + + it "does not serialize supplied properties" do + properties = cached_enterprise_serializer.supplied_properties + expect(properties).to eq([]) + end end end diff --git a/spec/serializers/api/enterprise_serializer_spec.rb b/spec/serializers/api/enterprise_serializer_spec.rb index 5eaa3d3e48..a388fe98d1 100644 --- a/spec/serializers/api/enterprise_serializer_spec.rb +++ b/spec/serializers/api/enterprise_serializer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Api::EnterpriseSerializer do let(:serializer) { Api::EnterpriseSerializer.new enterprise, data: data } - let(:enterprise) { create(:distributor_enterprise) } + let(:enterprise) { create(:distributor_enterprise, is_primary_producer: true) } let(:taxon) { create(:taxon) } let(:data) { OpenStruct.new(earliest_closing_times: {}, diff --git a/spec/services/cache_service_spec.rb b/spec/services/cache_service_spec.rb new file mode 100644 index 0000000000..68d77b7f1e --- /dev/null +++ b/spec/services/cache_service_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe CacheService do + let(:rails_cache) { Rails.cache } + + describe "#cache" do + before do + allow(rails_cache).to receive(:fetch) + end + + it "provides a wrapper for basic #fetch calls to Rails.cache" do + CacheService.cache("test-cache-key", expires_in: 10.seconds) do + "TEST" + end + + expect(rails_cache).to have_received(:fetch).with("test-cache-key", expires_in: 10.seconds) + end + end + + describe "#cached_data_by_class" do + let(:timestamp) { Time.now.to_i } + + before do + allow(rails_cache).to receive(:fetch) + allow(Enterprise).to receive(:maximum).with(:updated_at).and_return(timestamp) + end + + it "caches data by timestamp for last record of that class" do + CacheService.cached_data_by_class("test-cache-key", Enterprise) do + "TEST" + end + + expect(rails_cache).to have_received(:fetch).with("test-cache-key-Enterprise-#{timestamp}") + end + end + + describe "#latest_timestamp_by_class" do + let!(:taxon1) { create(:taxon) } + let!(:taxon2) { create(:taxon) } + + it "gets the :updated_at value of the last record for a given class and returns a timestamp" do + taxon1.touch + expect(CacheService.latest_timestamp_by_class(Spree::Taxon)).to eq taxon1.updated_at.to_i + + taxon2.touch + expect(CacheService.latest_timestamp_by_class(Spree::Taxon)).to eq taxon2.updated_at.to_i + end + end +end diff --git a/spec/services/cart_service_spec.rb b/spec/services/cart_service_spec.rb index 0baa3abe26..397014e08a 100644 --- a/spec/services/cart_service_spec.rb +++ b/spec/services/cart_service_spec.rb @@ -15,14 +15,18 @@ describe CartService do context "end-to-end" do let(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) } let(:distributor) { create(:distributor_enterprise) } - let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], variants: [v]) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], + variants: [variant]) } let(:cart_service) { CartService.new(order) } - let(:v) { create(:variant) } + let(:variant) { create(:variant) } - describe "populate" do + describe "#populate" do it "adds a variant" do - cart_service.populate({ variants: { v.id.to_s => { quantity: '1', max_quantity: '2' } } }, true) - li = order.find_line_item_by_variant(v) + cart_service.populate( + { variants: { variant.id.to_s => { quantity: '1', max_quantity: '2' } } }, + true + ) + li = order.find_line_item_by_variant(variant) expect(li).to be expect(li.quantity).to eq(1) expect(li.max_quantity).to eq(2) @@ -30,10 +34,13 @@ describe CartService do end it "updates a variant's quantity, max quantity and final_weight_volume" do - order.add_variant v, 1, 2 + order.add_variant variant, 1, 2 - cart_service.populate({ variants: { v.id.to_s => { quantity: '2', max_quantity: '3' } } }, true) - li = order.find_line_item_by_variant(v) + cart_service.populate( + { variants: { variant.id.to_s => { quantity: '2', max_quantity: '3' } } }, + true + ) + li = order.find_line_item_by_variant(variant) expect(li).to be expect(li.quantity).to eq(2) expect(li.max_quantity).to eq(3) @@ -41,13 +48,43 @@ describe CartService do end it "removes a variant" do - order.add_variant v, 1, 2 + order.add_variant variant, 1, 2 cart_service.populate({ variants: {} }, true) order.line_items(:reload) - li = order.find_line_item_by_variant(v) + li = order.find_line_item_by_variant(variant) expect(li).not_to be end + + context "when a variant has been soft-deleted" do + let(:relevant_line_item) { order.reload.find_line_item_by_variant(variant) } + + describe "when the soft-deleted variant is not in the cart yet" do + it "does not add the deleted variant to the cart" do + variant.delete + + cart_service.populate({ variants: { variant.id.to_s => { quantity: '2' } } }, true) + + expect(relevant_line_item).to be_nil + expect(cart_service.errors.count).to be 0 + end + end + + describe "when the soft-deleted variant is already in the cart" do + let!(:existing_line_item) { + create(:line_item, variant: variant, quantity: 2, order: order) + } + + it "removes the line_item from the cart" do + variant.delete + + cart_service.populate({ variants: { variant.id.to_s => { quantity: '3' } } }, true) + + expect(Spree::LineItem.where(id: relevant_line_item).first).to be_nil + expect(cart_service.errors.count).to be 0 + end + end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 72f4627785..55da5d8aa8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -122,6 +122,15 @@ RSpec.configure do |config| .each { |s| s.driver.reset! } end + # Enable caching in any specs tagged with `caching: true`. Usage is exactly the same as the + # well-known `js: true` tag used to enable javascript in feature specs. + config.around(:each, :caching) do |example| + caching = ActionController::Base.perform_caching + ActionController::Base.perform_caching = example.metadata[:caching] + example.run + ActionController::Base.perform_caching = caching + end + config.before(:all) { restart_phantomjs } # Geocoding diff --git a/spec/support/api_helper.rb b/spec/support/api_helper.rb index 2dd217117d..5059a94e80 100644 --- a/spec/support/api_helper.rb +++ b/spec/support/api_helper.rb @@ -13,7 +13,7 @@ module OpenFoodNetwork end def current_api_user - @current_api_user ||= Spree::LegacyUser.new(email: "spree@example.com", enterprises: []) + @current_api_user ||= Spree::LegacyUser.new(email: "ofn@example.com", enterprises: []) end def assert_unauthorized! diff --git a/spec/support/request/shop_workflow.rb b/spec/support/request/shop_workflow.rb index ff5909f267..cde1437b04 100644 --- a/spec/support/request/shop_workflow.rb +++ b/spec/support/request/shop_workflow.rb @@ -1,7 +1,17 @@ module ShopWorkflow - def add_to_cart - wait_until_enabled 'input.add_to_cart' - first("input.add_to_cart:not([disabled='disabled'])").click + def wait_for_cart + first("#cart").click + within '.cart-dropdown' do + expect(page).to_not have_link "Updating cart..." + end + end + + def edit_cart + wait_for_cart + within '.cart-dropdown' do + expect(page).to have_link "Edit your cart" + end + first("a.add_to_cart").click end def have_price(price)