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 @@
+
+
\ 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)