diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000000..d737c0b3c9 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,13 @@ +engines: + rubocop: + enabled: true + scss-lint: + enabled: true +ratings: + paths: + - app/** + - lib/** + - "**.rb" +exclude_paths: +- spec/**/* +- vendor/**/* diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..2e70cbc53b --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,29 @@ +AllCops: + Include: + - '**/Rakefile' + - '**/config.ru' + Exclude: + - 'db/**/*' + - 'config/**/*' + - 'script/**/*' + - 'spec/**/*' + - !ruby/regexp /old_and_unused\.rb$/ + +Documentation: + Enabled: false + +Style/EmptyLinesAroundClassBody: + Enabled: false + +Style/BracesAroundHashParameters: + Enabled: false + +Metrics/LineLength: + Enabled: false + Max: 120 + +MethodLength: + Enabled: false + +StringLiterals: + Enabled: false diff --git a/.scss-lint.yml b/.scss-lint.yml new file mode 100644 index 0000000000..d90daf1407 --- /dev/null +++ b/.scss-lint.yml @@ -0,0 +1,3 @@ +scss_files: 'app/assets/stylesheets/**/*.css.scss' + +exclude: 'app/assets/stylesheets/shared/**' diff --git a/.travis.yml b/.travis.yml index 952804f011..0ba99dc9e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,12 +14,13 @@ env: global: - TZ="Australia/Melbourne" - TIMEZONE="Australia/Melbourne" + - CI_NODE_TOTAL=5 matrix: - - TEST_CASES="./spec/features/admin" GITHUB_DEPLOY="true" - - TEST_CASES="./spec/features/consumer ./spec/serializers ./spec/performance" - - TEST_CASES="./spec/models" - - TEST_CASES="./spec/controllers ./spec/views ./spec/jobs" - - TEST_CASES="./spec/requests ./spec/helpers ./spec/mailers ./spec/lib" KARMA="true" + - CI_NODE_INDEX=0 + - CI_NODE_INDEX=1 + - CI_NODE_INDEX=2 + - CI_NODE_INDEX=3 + - CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true" before_script: - cp config/database.travis.yml config/database.yml @@ -35,8 +36,9 @@ before_script: fi script: - - '[ "$KARMA" = "true" ] && bundle exec rake karma:run || echo "Skipping karma run"' - - "bundle exec rspec $TEST_CASES" + - 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi' + #- "KNAPSACK_GENERATE_REPORT=true bundle exec rspec spec" + - "bundle exec rake knapsack:rspec" after_success: - > diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31ccc06991..6a43ed5f48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing We love pull requests from everyone. Here are some instructions for -contributing code to Open Food Network. +contributing code to Open Food Network. See the [developer wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki) for more information. Fork, then clone the repo: diff --git a/Gemfile b/Gemfile index 00fd2c8908..8230c6a15f 100644 --- a/Gemfile +++ b/Gemfile @@ -105,6 +105,7 @@ group :test, :development do gem 'json_spec' gem 'unicorn-rails' gem 'atomic' + gem 'knapsack' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index bbcbfde415..37677c043c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,7 +122,7 @@ GEM rack-test (~> 0.6.1) sprockets (~> 2.2.1) active_link_to (1.0.0) - active_model_serializers (0.8.1) + active_model_serializers (0.8.3) activemodel (>= 3.0) activemerchant (1.48.0) activesupport (>= 3.2.14, < 5.0.0) @@ -431,6 +431,9 @@ GEM actionpack (>= 3.0.0) activesupport (>= 3.0.0) kgio (2.9.3) + knapsack (1.5.1) + rake + timecop (>= 0.1.0) launchy (2.1.2) addressable (~> 2.3) letter_opener (1.0.0) @@ -684,6 +687,7 @@ DEPENDENCIES immigrant jquery-rails json_spec + knapsack letter_opener momentjs-rails newrelic_rpm diff --git a/README.markdown b/README.md similarity index 78% rename from README.markdown rename to README.md index 122e49c946..c572aae193 100644 --- a/README.markdown +++ b/README.md @@ -1,3 +1,4 @@ +[![Build Status](https://travis-ci.org/openfoodfoundation/openfoodnetwork.svg?branch=master)](https://travis-ci.org/openfoodfoundation/openfoodnetwork) [![Code Climate](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork.png)](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork) # Open Food Network @@ -14,10 +15,12 @@ We're part of global movement - get involved! ## Getting started -Below are instructions for setting up a development environment for Open Food Network. If you're interested in provisioning a server, see [the project's Ansible playbooks](https://github.com/openfoodfoundation/ofn_deployment). +Below are instructions for setting up a development environment for Open Food Network. More information is in the [developer wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki). + +If you're interested in provisioning a server, see [the project's Ansible playbooks](https://github.com/openfoodfoundation/ofn_deployment). -## Dependencies +### Dependencies * Rails 3.2.x * Ruby 2.1.5 @@ -26,7 +29,7 @@ Below are instructions for setting up a development environment for Open Food Ne * See Gemfile for a list of gems required -## Get it +### Get it The source code is managed with Git (a version control system) and hosted at GitHub. @@ -40,7 +43,7 @@ You can download the source with the command: git clone https://github.com/openfoodfoundation/openfoodnetwork.git -## Get it running +### Get it running For those new to Rails, the following tutorial will help get you up to speed with configuring a Rails environment: http://guides.rubyonrails.org/getting_started.html . @@ -58,6 +61,15 @@ Configure the site: cp config/application.yml.example config/application.yml edit config/application.yml +Create a PostgreSQL user: + +* Login as your system postrgresql priviledged user: `sudo -i -u postgres` (this may vary on your OS). Now your prompt looks like: `[postgres@your_host ~]$` +* Create the `ofn` database superuser and give it the password `f00d`: + +``` +createuser -s -P ofn +``` + Create the development and test databases, using the settings specified in `config/database.yml`, and populate them with a schema and seed data: rake db:setup @@ -71,7 +83,7 @@ At long last, your dreams of spinning up a development server can be realised: rails server -## Testing +### Testing Tests, both unit and integration, are based on RSpec. To run the test suite, first prepare the test database: @@ -97,6 +109,10 @@ usage instructions. * Will Marshall (http://soundcloud.com/willmarshall) * Laura Summers (https://github.com/summerscope) * Maikel Linke (https://github.com/mkllnk) +* Lynne Davis (https://github.com/lin-d-hop) +* Paul Mackay (https://github.com/pmackay) +* Steve Petitt (https://github.com/stveep) + ## Licence diff --git a/Rakefile b/Rakefile index 699faf6e9d..9ed55e022e 100644 --- a/Rakefile +++ b/Rakefile @@ -5,3 +5,5 @@ require File.expand_path('../config/application', __FILE__) Openfoodnetwork::Application.load_tasks + +Knapsack.load_tasks if defined?(Knapsack) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index c0fa530626..7d3e94194d 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -39,6 +39,7 @@ //= require ./taxons/taxons //= require ./utils/utils //= require ./users/users +//= require ./variant_overrides/variant_overrides //= require textAngular.min.js //= require textAngular-sanitize.min.js //= require ../shared/bindonce.min.js diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index b3d24f00c0..6c85a4fd54 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -352,6 +352,9 @@ filterSubmitVariant = (variant) -> filteredVariant = {} if not variant.deleted_at? and variant.hasOwnProperty("id") filteredVariant.id = variant.id unless variant.id <= 0 + if variant.hasOwnProperty("sku") + filteredVariant.sku = variant.sku + hasUpdatableProperty = true if variant.hasOwnProperty("on_hand") filteredVariant.on_hand = variant.on_hand hasUpdatableProperty = true diff --git a/app/assets/javascripts/admin/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/controllers/variant_overrides_controller.js.coffee deleted file mode 100644 index bcc633805f..0000000000 --- a/app/assets/javascripts/admin/controllers/variant_overrides_controller.js.coffee +++ /dev/null @@ -1,67 +0,0 @@ -angular.module("ofn.admin").controller "AdminVariantOverridesCtrl", ($scope, $timeout, Indexer, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) -> - $scope.hubs = hubs - $scope.hub = null - $scope.products = [] - $scope.producers = Indexer.index producers - $scope.hubPermissions = hubPermissions - $scope.variantOverrides = VariantOverrides.variantOverrides - $scope.StatusMessage = StatusMessage - - $scope.initialise = -> - SpreeApiAuth.authorise() - .then -> - $scope.spree_api_key_ok = true - $scope.fetchProducts() - .catch (message) -> - $scope.api_error_msg = message - - - $scope.fetchProducts = -> - url = "/api/products/overridable?page=::page::;per_page=100" - PagedFetcher.fetch url, (data) => $scope.addProducts data.products - - - $scope.addProducts = (products) -> - $scope.products = $scope.products.concat products - VariantOverrides.ensureDataFor hubs, products - - - $scope.selectHub = -> - $scope.hub = (hub for hub in hubs when hub.id == $scope.hub_id)[0] - - - $scope.displayDirty = -> - if DirtyVariantOverrides.count() > 0 - num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides" - StatusMessage.display 'notice', "Changes to #{num} remain unsaved." - else - StatusMessage.clear() - - - $scope.update = -> - if DirtyVariantOverrides.count() == 0 - StatusMessage.display 'alert', 'No changes to save.' - else - StatusMessage.display 'progress', 'Saving...' - DirtyVariantOverrides.save() - .success (updatedVos) -> - DirtyVariantOverrides.clear() - VariantOverrides.updateIds updatedVos - $timeout -> StatusMessage.display 'success', 'Changes saved.' - .error (data, status) -> - $timeout -> StatusMessage.display 'failure', $scope.updateError(data, status) - - - $scope.updateError = (data, status) -> - if status == 401 - "I couldn't get authorisation to save those changes, so they remain unsaved." - - else if status == 400 && data.errors? - errors = [] - for field, field_errors of data.errors - errors = errors.concat field_errors - errors = errors.join ', ' - "I had some trouble saving: #{errors}" - - else - "Oh no! I was unable to save your changes." diff --git a/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee index 560598d23e..b4ca2869d7 100644 --- a/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee +++ b/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee @@ -1,8 +1,9 @@ angular.module("admin.dropdown").directive "ofnDropDown", ($document) -> + restrict: 'C' link: (scope, element, attrs) -> outsideClickListener = (event) -> - unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") || - $(event.target).parents("div.ofn_drop_down##{attrs.id} div.menu").length > 0 + unless $(event.target).is("div.ofn-drop-down##{attrs.id} div.menu") || + $(event.target).parents("div.ofn-drop-down##{attrs.id} div.menu").length > 0 scope.$emit "offClick" element.click (event) -> diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee new file mode 100644 index 0000000000..ba7a4b54df --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee @@ -0,0 +1,28 @@ +angular.module("admin.indexUtils").directive "ofnSelect2", ($timeout, blankOption) -> + require: 'ngModel' + restrict: 'C' + scope: + data: "=" + minSearch: "@?" + text: "@?" + blank: "=?" + link: (scope, element, attrs, ngModel) -> + $timeout -> + scope.text ||= 'name' + scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object" + element.select2 + minimumResultsForSearch: scope.minSearch || 0 + data: { results: scope.data, text: scope.text } + initSelection: (element, callback) -> + callback scope.data[0] + formatSelection: (item) -> + item[scope.text] + formatResult: (item) -> + item[scope.text] + + attrs.$observe 'disabled', (value) -> + element.select2('enable', !value) + + ngModel.$formatters.push (value) -> + element.select2('val', value) + value diff --git a/app/assets/javascripts/admin/index_utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/index_utils/directives/save_bar.js.coffee deleted file mode 100644 index a991592d42..0000000000 --- a/app/assets/javascripts/admin/index_utils/directives/save_bar.js.coffee +++ /dev/null @@ -1,7 +0,0 @@ -angular.module("admin.indexUtils").directive "saveBar", -> - restrict: "E" - scope: - save: "&" - saving: "&" - dirty: "&" - templateUrl: "admin/save_bar.html" diff --git a/app/assets/javascripts/admin/index_utils/filters/attr_filter.js.coffee b/app/assets/javascripts/admin/index_utils/filters/attr_filter.js.coffee new file mode 100644 index 0000000000..c645b507f1 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/filters/attr_filter.js.coffee @@ -0,0 +1,12 @@ +# Used like a regular angular filter where an object is passed +# Adds the additional special case that a value of 0 for the filter +# acts as a bypass for that particular attribute +angular.module("admin.indexUtils").filter "attrFilter", ($filter) -> + return (objects, filters) -> + Object.keys(filters).reduce (filtered, attr) -> + filter = filters[attr] + return filtered if !filter? || filter == 0 + return $filter('filter')(filtered, (object) -> + object[attr] == filter + ) + , objects diff --git a/app/assets/javascripts/admin/services/data_fetcher.js.coffee b/app/assets/javascripts/admin/index_utils/services/data_fetcher.js.coffee similarity index 79% rename from app/assets/javascripts/admin/services/data_fetcher.js.coffee rename to app/assets/javascripts/admin/index_utils/services/data_fetcher.js.coffee index 735e4cc6bb..bf5580a3b2 100644 --- a/app/assets/javascripts/admin/services/data_fetcher.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/data_fetcher.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "dataFetcher", [ +angular.module("admin.indexUtils").factory "dataFetcher", [ "$http", "$q" ($http, $q) -> return (dataLocation) -> @@ -9,4 +9,4 @@ angular.module("ofn.admin").factory "dataFetcher", [ deferred.reject() deferred.promise -] \ No newline at end of file +] diff --git a/app/assets/javascripts/admin/services/indexer.js.coffee b/app/assets/javascripts/admin/index_utils/services/indexer.js.coffee similarity index 85% rename from app/assets/javascripts/admin/services/indexer.js.coffee rename to app/assets/javascripts/admin/index_utils/services/indexer.js.coffee index f9a9688a2f..295df46be3 100644 --- a/app/assets/javascripts/admin/services/indexer.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/indexer.js.coffee @@ -4,7 +4,7 @@ # Indexer.index producers # -> {1: {id: 1, name: 'one'}, 2: {id: 2, name: 'two'}} -angular.module("ofn.admin").factory 'Indexer', -> +angular.module("admin.indexUtils").factory 'Indexer', -> new class Indexer index: (data, key='id') -> index = {} diff --git a/app/assets/javascripts/admin/services/paged_fetcher.js.coffee b/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee similarity index 83% rename from app/assets/javascripts/admin/services/paged_fetcher.js.coffee rename to app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee index 9281ed6a42..d65887bb2c 100644 --- a/app/assets/javascripts/admin/services/paged_fetcher.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) -> +angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) -> new class PagedFetcher # Given a URL like http://example.com/foo?page=::page::&per_page=20 # And the response includes an attribute pages with the number of pages to fetch @@ -13,4 +13,4 @@ angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) -> processData data urlForPage: (url, page) -> - url.replace("::page::", page) \ No newline at end of file + url.replace("::page::", page) diff --git a/app/assets/javascripts/admin/services/spree_api_auth.js.coffee b/app/assets/javascripts/admin/index_utils/services/spree_api_auth.js.coffee similarity index 84% rename from app/assets/javascripts/admin/services/spree_api_auth.js.coffee rename to app/assets/javascripts/admin/index_utils/services/spree_api_auth.js.coffee index e606882bc5..3ed4dd9bf7 100644 --- a/app/assets/javascripts/admin/services/spree_api_auth.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/spree_api_auth.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) -> +angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) -> new class SpreeApiAuth authorise: -> deferred = $q.defer() diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index 38c7f0ba33..c6709eb3b4 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -1,7 +1,6 @@ -angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) -> +angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) -> $scope.initialized = false $scope.RequestMonitor = RequestMonitor - $scope.saving = false $scope.filteredLineItems = [] $scope.confirmDelete = true $scope.startDate = formatDate daysFromToday -7 @@ -55,6 +54,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.enterprisesByID Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.ordersByID $scope.bulk_order_form.$setPristine() + StatusMessage.clear() unless $scope.initialized $scope.initialized = true $timeout -> @@ -62,16 +62,20 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.refreshData() - $scope.submit = => + $scope.$watch 'bulk_order_form.$dirty', (newVal, oldVal) -> + if newVal == true + StatusMessage.display 'notice', "You have unsaved changes" + + $scope.submit = -> if $scope.bulk_order_form.$valid - $scope.saving = true + StatusMessage.display 'progress', "Saving..." $q.all(LineItems.saveAll()).then(-> + StatusMessage.display 'success', "All changes saved" $scope.bulk_order_form.$setPristine() - $scope.saving = false ).catch -> - alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors." + StatusMessage.display 'failure', "Fields with red borders contain errors." else - alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors." + StatusMessage.display 'failure', "Fields with red borders contain errors." $scope.deleteLineItem = (lineItem) -> if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete diff --git a/app/assets/javascripts/admin/line_items/line_items.js.coffee b/app/assets/javascripts/admin/line_items/line_items.js.coffee index a3328c572e..8128a50e8a 100644 --- a/app/assets/javascripts/admin/line_items/line_items.js.coffee +++ b/app/assets/javascripts/admin/line_items/line_items.js.coffee @@ -1 +1 @@ -angular.module("admin.lineItems", ["admin.indexUtils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"]) +angular.module("admin.lineItems", ["admin.indexUtils", "admin.utils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"]) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee new file mode 100644 index 0000000000..2c98d60f0e --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee @@ -0,0 +1,83 @@ +angular.module('admin.orderCycles') + .controller 'AdminCreateOrderCycleCtrl', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) -> + $scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id) + $scope.supplier_enterprises = Enterprise.producer_enterprises + $scope.distributor_enterprises = Enterprise.hub_enterprises + $scope.supplied_products = Enterprise.supplied_products + $scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id) + + $scope.OrderCycle = OrderCycle + $scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id}) + + $scope.StatusMessage = StatusMessage + + $scope.loaded = -> + Enterprise.loaded && EnterpriseFee.loaded + + $scope.suppliedVariants = (enterprise_id) -> + Enterprise.suppliedVariants(enterprise_id) + + $scope.exchangeSelectedVariants = (exchange) -> + OrderCycle.exchangeSelectedVariants(exchange) + + $scope.setExchangeVariants = (exchange, variants, selected) -> + OrderCycle.setExchangeVariants(exchange, variants, selected) + + $scope.enterpriseTotalVariants = (enterprise) -> + Enterprise.totalVariants(enterprise) + + $scope.productSuppliedToOrderCycle = (product) -> + OrderCycle.productSuppliedToOrderCycle(product) + + $scope.variantSuppliedToOrderCycle = (variant) -> + OrderCycle.variantSuppliedToOrderCycle(variant) + + $scope.incomingExchangeVariantsFor = (enterprise_id) -> + $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) + + $scope.exchangeDirection = (exchange) -> + OrderCycle.exchangeDirection(exchange) + + $scope.enterprisesWithFees = -> + $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 + + $scope.toggleProducts = ($event, exchange) -> + $event.preventDefault() + OrderCycle.toggleProducts(exchange) + + $scope.enterpriseFeesForEnterprise = (enterprise_id) -> + EnterpriseFee.forEnterprise(parseInt(enterprise_id)) + + $scope.addSupplier = ($event) -> + $event.preventDefault() + OrderCycle.addSupplier($scope.new_supplier_id) + + $scope.addDistributor = ($event) -> + $event.preventDefault() + OrderCycle.addDistributor($scope.new_distributor_id) + + $scope.removeExchange = ($event, exchange) -> + $event.preventDefault() + OrderCycle.removeExchange(exchange) + + $scope.addCoordinatorFee = ($event) -> + $event.preventDefault() + OrderCycle.addCoordinatorFee() + + $scope.removeCoordinatorFee = ($event, index) -> + $event.preventDefault() + OrderCycle.removeCoordinatorFee(index) + + $scope.addExchangeFee = ($event, exchange) -> + $event.preventDefault() + OrderCycle.addExchangeFee(exchange) + + $scope.removeExchangeFee = ($event, exchange, index) -> + $event.preventDefault() + OrderCycle.removeExchangeFee(exchange, index) + + $scope.removeDistributionOfVariant = (variant_id) -> + OrderCycle.removeDistributionOfVariant(variant_id) + + $scope.submit = (destination) -> + OrderCycle.create(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee new file mode 100644 index 0000000000..fd426eb455 --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -0,0 +1,84 @@ +angular.module('admin.orderCycles') + .controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> + order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] + $scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id) + $scope.supplier_enterprises = Enterprise.producer_enterprises + $scope.distributor_enterprises = Enterprise.hub_enterprises + $scope.supplied_products = Enterprise.supplied_products + $scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id) + + $scope.OrderCycle = OrderCycle + $scope.order_cycle = OrderCycle.load(order_cycle_id) + + $scope.StatusMessage = StatusMessage + + $scope.loaded = -> + Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded + + $scope.suppliedVariants = (enterprise_id) -> + Enterprise.suppliedVariants(enterprise_id) + + $scope.exchangeSelectedVariants = (exchange) -> + OrderCycle.exchangeSelectedVariants(exchange) + + $scope.setExchangeVariants = (exchange, variants, selected) -> + OrderCycle.setExchangeVariants(exchange, variants, selected) + + $scope.enterpriseTotalVariants = (enterprise) -> + Enterprise.totalVariants(enterprise) + + $scope.productSuppliedToOrderCycle = (product) -> + OrderCycle.productSuppliedToOrderCycle(product) + + $scope.variantSuppliedToOrderCycle = (variant) -> + OrderCycle.variantSuppliedToOrderCycle(variant) + + $scope.incomingExchangeVariantsFor = (enterprise_id) -> + $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) + + $scope.exchangeDirection = (exchange) -> + OrderCycle.exchangeDirection(exchange) + + $scope.enterprisesWithFees = -> + $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 + + $scope.toggleProducts = ($event, exchange) -> + $event.preventDefault() + OrderCycle.toggleProducts(exchange) + + $scope.enterpriseFeesForEnterprise = (enterprise_id) -> + EnterpriseFee.forEnterprise(parseInt(enterprise_id)) + + $scope.addSupplier = ($event) -> + $event.preventDefault() + OrderCycle.addSupplier($scope.new_supplier_id) + + $scope.addDistributor = ($event) -> + $event.preventDefault() + OrderCycle.addDistributor($scope.new_distributor_id) + + $scope.removeExchange = ($event, exchange) -> + $event.preventDefault() + OrderCycle.removeExchange(exchange) + + $scope.addCoordinatorFee = ($event) -> + $event.preventDefault() + OrderCycle.addCoordinatorFee() + + $scope.removeCoordinatorFee = ($event, index) -> + $event.preventDefault() + OrderCycle.removeCoordinatorFee(index) + + $scope.addExchangeFee = ($event, exchange) -> + $event.preventDefault() + OrderCycle.addExchangeFee(exchange) + + $scope.removeExchangeFee = ($event, exchange, index) -> + $event.preventDefault() + OrderCycle.removeExchangeFee(exchange, index) + + $scope.removeDistributionOfVariant = (variant_id) -> + OrderCycle.removeDistributionOfVariant(variant_id) + + $scope.submit = (destination) -> + OrderCycle.update(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_controller.js.coffee deleted file mode 100644 index 40428e1f73..0000000000 --- a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_controller.js.coffee +++ /dev/null @@ -1,204 +0,0 @@ -angular.module('admin.orderCycles', ['ngResource', 'admin.utils']) - .controller('AdminCreateOrderCycleCtrl', ['$scope', '$filter', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'ocInstance', 'StatusMessage', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) -> - $scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id) - $scope.supplier_enterprises = Enterprise.producer_enterprises - $scope.distributor_enterprises = Enterprise.hub_enterprises - $scope.supplied_products = Enterprise.supplied_products - $scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id) - - $scope.OrderCycle = OrderCycle - $scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id}) - - $scope.StatusMessage = StatusMessage - - $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded - - $scope.suppliedVariants = (enterprise_id) -> - Enterprise.suppliedVariants(enterprise_id) - - $scope.exchangeSelectedVariants = (exchange) -> - OrderCycle.exchangeSelectedVariants(exchange) - - $scope.setExchangeVariants = (exchange, variants, selected) -> - OrderCycle.setExchangeVariants(exchange, variants, selected) - - $scope.enterpriseTotalVariants = (enterprise) -> - Enterprise.totalVariants(enterprise) - - $scope.productSuppliedToOrderCycle = (product) -> - OrderCycle.productSuppliedToOrderCycle(product) - - $scope.variantSuppliedToOrderCycle = (variant) -> - OrderCycle.variantSuppliedToOrderCycle(variant) - - $scope.incomingExchangeVariantsFor = (enterprise_id) -> - $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) - - $scope.exchangeDirection = (exchange) -> - OrderCycle.exchangeDirection(exchange) - - $scope.enterprisesWithFees = -> - $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 - - $scope.toggleProducts = ($event, exchange) -> - $event.preventDefault() - OrderCycle.toggleProducts(exchange) - - $scope.enterpriseFeesForEnterprise = (enterprise_id) -> - EnterpriseFee.forEnterprise(parseInt(enterprise_id)) - - $scope.addSupplier = ($event) -> - $event.preventDefault() - OrderCycle.addSupplier($scope.new_supplier_id) - - $scope.addDistributor = ($event) -> - $event.preventDefault() - OrderCycle.addDistributor($scope.new_distributor_id) - - $scope.removeExchange = ($event, exchange) -> - $event.preventDefault() - OrderCycle.removeExchange(exchange) - - $scope.addCoordinatorFee = ($event) -> - $event.preventDefault() - OrderCycle.addCoordinatorFee() - - $scope.removeCoordinatorFee = ($event, index) -> - $event.preventDefault() - OrderCycle.removeCoordinatorFee(index) - - $scope.addExchangeFee = ($event, exchange) -> - $event.preventDefault() - OrderCycle.addExchangeFee(exchange) - - $scope.removeExchangeFee = ($event, exchange, index) -> - $event.preventDefault() - OrderCycle.removeExchangeFee(exchange, index) - - $scope.removeDistributionOfVariant = (variant_id) -> - OrderCycle.removeDistributionOfVariant(variant_id) - - $scope.submit = (destination) -> - OrderCycle.create(destination) - ]) - - .controller('AdminEditOrderCycleCtrl', ['$scope', '$filter', '$location', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'StatusMessage', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> - order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] - $scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id) - $scope.supplier_enterprises = Enterprise.producer_enterprises - $scope.distributor_enterprises = Enterprise.hub_enterprises - $scope.supplied_products = Enterprise.supplied_products - $scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id) - - $scope.OrderCycle = OrderCycle - $scope.order_cycle = OrderCycle.load(order_cycle_id) - - $scope.StatusMessage = StatusMessage - - $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded - - $scope.suppliedVariants = (enterprise_id) -> - Enterprise.suppliedVariants(enterprise_id) - - $scope.exchangeSelectedVariants = (exchange) -> - OrderCycle.exchangeSelectedVariants(exchange) - - $scope.setExchangeVariants = (exchange, variants, selected) -> - OrderCycle.setExchangeVariants(exchange, variants, selected) - - $scope.enterpriseTotalVariants = (enterprise) -> - Enterprise.totalVariants(enterprise) - - $scope.productSuppliedToOrderCycle = (product) -> - OrderCycle.productSuppliedToOrderCycle(product) - - $scope.variantSuppliedToOrderCycle = (variant) -> - OrderCycle.variantSuppliedToOrderCycle(variant) - - $scope.incomingExchangeVariantsFor = (enterprise_id) -> - $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) - - $scope.exchangeDirection = (exchange) -> - OrderCycle.exchangeDirection(exchange) - - $scope.enterprisesWithFees = -> - $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 - - $scope.toggleProducts = ($event, exchange) -> - $event.preventDefault() - OrderCycle.toggleProducts(exchange) - - $scope.enterpriseFeesForEnterprise = (enterprise_id) -> - EnterpriseFee.forEnterprise(parseInt(enterprise_id)) - - $scope.addSupplier = ($event) -> - $event.preventDefault() - OrderCycle.addSupplier($scope.new_supplier_id) - - $scope.addDistributor = ($event) -> - $event.preventDefault() - OrderCycle.addDistributor($scope.new_distributor_id) - - $scope.removeExchange = ($event, exchange) -> - $event.preventDefault() - OrderCycle.removeExchange(exchange) - - $scope.addCoordinatorFee = ($event) -> - $event.preventDefault() - OrderCycle.addCoordinatorFee() - - $scope.removeCoordinatorFee = ($event, index) -> - $event.preventDefault() - OrderCycle.removeCoordinatorFee(index) - - $scope.addExchangeFee = ($event, exchange) -> - $event.preventDefault() - OrderCycle.addExchangeFee(exchange) - - $scope.removeExchangeFee = ($event, exchange, index) -> - $event.preventDefault() - OrderCycle.removeExchangeFee(exchange, index) - - $scope.removeDistributionOfVariant = (variant_id) -> - OrderCycle.removeDistributionOfVariant(variant_id) - - $scope.submit = (destination) -> - OrderCycle.update(destination) - ]) - - .config(['$httpProvider', ($httpProvider) -> - $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') - ]) - - .directive('datetimepicker', ['$parse', ($parse) -> - (scope, element, attrs) -> - # using $parse instead of scope[attrs.datetimepicker] for cases - # where attrs.datetimepicker is 'foo.bar.lol' - $(element).datetimepicker - dateFormat: 'yy-mm-dd' - timeFormat: 'HH:mm:ss' - showOn: "button" - buttonImage: "<%= asset_path 'datepicker/cal.gif' %>" - buttonImageOnly: true - stepMinute: 15 - onSelect: (dateText, inst) -> - scope.$apply -> - parsed = $parse(attrs.datetimepicker) - parsed.assign(scope, dateText) - ]) - - .directive('ofnOnChange', -> - (scope, element, attrs) -> - element.bind 'change', -> - scope.$apply(attrs.ofnOnChange) - ) - - .directive('ofnSyncDistributions', -> - (scope, element, attrs) -> - element.bind 'change', -> - if !$(this).is(':checked') - scope.$apply -> - scope.removeDistributionOfVariant(attrs.ofnSyncDistributions) - ) diff --git a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee index 2f97440e71..a75fdad58c 100644 --- a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee @@ -1 +1,32 @@ -angular.module('admin.orderCycles', ['ngResource', 'admin.indexUtils']) +angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils']) + + .config ($httpProvider) -> + $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') + + .directive 'datetimepicker', ($parse) -> + (scope, element, attrs) -> + # using $parse instead of scope[attrs.datetimepicker] for cases + # where attrs.datetimepicker is 'foo.bar.lol' + $(element).datetimepicker + dateFormat: 'yy-mm-dd' + timeFormat: 'HH:mm:ss' + showOn: "button" + buttonImage: "<%= asset_path 'datepicker/cal.gif' %>" + buttonImageOnly: true + stepMinute: 15 + onSelect: (dateText, inst) -> + scope.$apply -> + parsed = $parse(attrs.datetimepicker) + parsed.assign(scope, dateText) + + .directive 'ofnOnChange', -> + (scope, element, attrs) -> + element.bind 'change', -> + scope.$apply(attrs.ofnOnChange) + + .directive 'ofnSyncDistributions', -> + (scope, element, attrs) -> + element.bind 'change', -> + if !$(this).is(':checked') + scope.$apply -> + scope.removeDistributionOfVariant(attrs.ofnSyncDistributions) diff --git a/app/assets/javascripts/admin/services/status_message.js.coffee b/app/assets/javascripts/admin/services/status_message.js.coffee deleted file mode 100644 index aaa55cf339..0000000000 --- a/app/assets/javascripts/admin/services/status_message.js.coffee +++ /dev/null @@ -1,26 +0,0 @@ -angular.module("ofn.admin").factory "StatusMessage", ($timeout) -> - new class StatusMessage - types: - progress: {timeout: false, style: {color: '#ff9906'}} - alert: {timeout: 5000, style: {color: 'grey'}} - notice: {timeout: false, style: {color: 'grey'}} - success: {timeout: 5000, style: {color: '#9fc820'}} - failure: {timeout: false, style: {color: '#da5354'}} - - statusMessage: - text: "" - style: {} - - display: (type, text) -> - @statusMessage.text = text - @statusMessage.style = @types[type].style - $timeout.cancel @statusMessage.timeout if @statusMessage.timeout - timeout = @types[type].timeout - if timeout - @statusMessage.timeout = $timeout => - @clear() - , timeout, true - - clear: -> - @statusMessage.text = '' - @statusMessage.style = {} diff --git a/app/assets/javascripts/admin/services/variant_overrides.js.coffee b/app/assets/javascripts/admin/services/variant_overrides.js.coffee deleted file mode 100644 index 28d65eab03..0000000000 --- a/app/assets/javascripts/admin/services/variant_overrides.js.coffee +++ /dev/null @@ -1,23 +0,0 @@ -angular.module("ofn.admin").factory "VariantOverrides", (variantOverrides, Indexer) -> - new class VariantOverrides - variantOverrides: {} - - constructor: -> - for vo in variantOverrides - @variantOverrides[vo.hub_id] ||= {} - @variantOverrides[vo.hub_id][vo.variant_id] = vo - - ensureDataFor: (hubs, products) -> - for hub in hubs - @variantOverrides[hub.id] ||= {} - for product in products - for variant in product.variants - @variantOverrides[hub.id][variant.id] ||= - variant_id: variant.id - hub_id: hub.id - price: '' - count_on_hand: '' - - updateIds: (updatedVos) -> - for vo in updatedVos - @variantOverrides[vo.hub_id][vo.variant_id].id = vo.id \ No newline at end of file diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee new file mode 100644 index 0000000000..13e4f84bc6 --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.utils").directive "saveBar", (StatusMessage) -> + restrict: "E" + scope: + save: "&" + form: "=" + templateUrl: "admin/save_bar.html" + link: (scope, element, attrs) -> + scope.StatusMessage = StatusMessage diff --git a/app/assets/javascripts/admin/utils/services/status_message.js.coffee b/app/assets/javascripts/admin/utils/services/status_message.js.coffee index d317269ca4..6aac046a7f 100644 --- a/app/assets/javascripts/admin/utils/services/status_message.js.coffee +++ b/app/assets/javascripts/admin/utils/services/status_message.js.coffee @@ -11,6 +11,9 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) -> text: "" style: {} + active: -> + @statusMessage.text != '' + display: (type, text) -> @statusMessage.text = text @statusMessage.style = @types[type].style @@ -20,6 +23,7 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) -> @statusMessage.timeout = $timeout => @clear() , timeout, true + null # So we don't return weird timeouts clear: -> @statusMessage.text = '' diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee new file mode 100644 index 0000000000..b65df80d66 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee @@ -0,0 +1,102 @@ +angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) -> + $scope.hubs = Indexer.index hubs + $scope.hub = null + $scope.products = [] + $scope.producers = producers + $scope.producersByID = Indexer.index producers + $scope.hubPermissions = hubPermissions + $scope.variantOverrides = VariantOverrides.variantOverrides + $scope.StatusMessage = StatusMessage + + $scope.columns = Columns.setColumns + producer: { name: "Producer", visible: true } + product: { name: "Product", visible: true } + sku: { name: "SKU", visible: false } + price: { name: "Price", visible: true } + on_hand: { name: "On Hand", visible: true } + on_demand: { name: "On Demand", visible: false } + reset: { name: "Reset Stock Level", visible: false } + inheritance: { name: "Inheritance", visible: false } + + $scope.resetSelectFilters = -> + $scope.producerFilter = 0 + $scope.query = '' + + $scope.resetSelectFilters() + + $scope.initialise = -> + SpreeApiAuth.authorise() + .then -> + $scope.spree_api_key_ok = true + $scope.fetchProducts() + .catch (message) -> + $scope.api_error_msg = message + + + $scope.fetchProducts = -> + url = "/api/products/overridable?page=::page::;per_page=100" + PagedFetcher.fetch url, (data) => $scope.addProducts data.products + + + $scope.addProducts = (products) -> + $scope.products = $scope.products.concat products + VariantOverrides.ensureDataFor hubs, products + + + $scope.selectHub = -> + $scope.hub = $scope.hubs[$scope.hub_id] + + $scope.displayDirty = -> + if DirtyVariantOverrides.count() > 0 + num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides" + StatusMessage.display 'notice', "Changes to #{num} remain unsaved." + else + StatusMessage.clear() + + $scope.update = -> + if DirtyVariantOverrides.count() == 0 + StatusMessage.display 'alert', 'No changes to save.' + else + StatusMessage.display 'progress', 'Saving...' + DirtyVariantOverrides.save() + .success (updatedVos) -> + DirtyVariantOverrides.clear() + VariantOverrides.updateIds updatedVos + $scope.variant_overrides_form.$setPristine() + StatusMessage.display 'success', 'Changes saved.' + VariantOverrides.updateData updatedVos # Refresh page data + .error (data, status) -> + StatusMessage.display 'failure', $scope.updateError(data, status) + + + $scope.updateError = (data, status) -> + if status == 401 + "I couldn't get authorisation to save those changes, so they remain unsaved." + + else if status == 400 && data.errors? + errors = [] + for field, field_errors of data.errors + errors = errors.concat field_errors + errors = errors.join ', ' + "I had some trouble saving: #{errors}" + else + "Oh no! I was unable to save your changes." + + $scope.resetStock = -> + if DirtyVariantOverrides.count() > 0 + StatusMessage.display 'alert', 'Save changes first.' + $timeout -> + $scope.displayDirty() + , 3000 # 3 second delay + else + return unless $scope.hub_id? + StatusMessage.display 'progress', 'Changing on hand stock levels...' + $http + method: "POST" + url: "/admin/variant_overrides/bulk_reset" + data: { hub_id: $scope.hub_id } + .success (updatedVos) -> + VariantOverrides.updateData updatedVos + StatusMessage.display 'success', 'Stocks reset to defaults.' + .error (data, status) -> + $timeout -> StatusMessage.display 'failure', $scope.updateError(data, status) diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee new file mode 100644 index 0000000000..20ac08c035 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee @@ -0,0 +1,12 @@ +angular.module("admin.variantOverrides").directive "trackInheritance", (VariantOverrides, DirtyVariantOverrides) -> + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + # This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it + scope.inherit = angular.equals scope.variantOverrides[scope.hub.id][scope.variant.id], VariantOverrides.newFor scope.hub.id, scope.variant.id + + ngModel.$parsers.push (viewValue) -> + if ngModel.$dirty && viewValue + variantOverride = VariantOverrides.inherit(scope.hub.id, scope.variant.id) + DirtyVariantOverrides.add variantOverride + scope.displayDirty() + viewValue diff --git a/app/assets/javascripts/admin/directives/track_variant_override.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee similarity index 69% rename from app/assets/javascripts/admin/directives/track_variant_override.js.coffee rename to app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee index bb8117a757..919533967c 100644 --- a/app/assets/javascripts/admin/directives/track_variant_override.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee @@ -1,9 +1,10 @@ -angular.module("ofn.admin").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) -> +angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) -> require: "ngModel" link: (scope, element, attrs, ngModel) -> ngModel.$parsers.push (viewValue) -> if ngModel.$dirty variantOverride = scope.variantOverrides[scope.hub.id][scope.variant.id] + scope.inherit = false DirtyVariantOverrides.add variantOverride scope.displayDirty() viewValue diff --git a/app/assets/javascripts/admin/filters/hub_permissions_filter.js.coffee b/app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee similarity index 70% rename from app/assets/javascripts/admin/filters/hub_permissions_filter.js.coffee rename to app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee index 5db7a6d40e..39b5e77839 100644 --- a/app/assets/javascripts/admin/filters/hub_permissions_filter.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").filter "hubPermissions", ($filter) -> +angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) -> return (products, hubPermissions, hub_id) -> return [] if !hub_id return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true) diff --git a/app/assets/javascripts/admin/services/dirty_variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee similarity index 88% rename from app/assets/javascripts/admin/services/dirty_variant_overrides.js.coffee rename to app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee index 82e7772982..053c6cbfa1 100644 --- a/app/assets/javascripts/admin/services/dirty_variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "DirtyVariantOverrides", ($http) -> +angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http) -> new class DirtyVariantOverrides dirtyVariantOverrides: {} diff --git a/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee new file mode 100644 index 0000000000..4e1128572e --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee @@ -0,0 +1,40 @@ + +angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOverrides) -> + new class VariantOverrides + variantOverrides: {} + + constructor: -> + for vo in variantOverrides + @variantOverrides[vo.hub_id] ||= {} + @variantOverrides[vo.hub_id][vo.variant_id] = vo + + ensureDataFor: (hubs, products) -> + for hub_id, hub of hubs + @variantOverrides[hub.id] ||= {} + for product in products + for variant in product.variants + @inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id] + + inherit: (hub_id, variant_id) -> + # This method is called from the trackInheritance directive, to reinstate inheritance + @variantOverrides[hub_id][variant_id] ||= {} + angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id + + newFor: (hub_id, variant_id) -> + # These properties need to match those checked in VariantOverrideSet.deletable? + hub_id: hub_id + variant_id: variant_id + sku: null + price: null + count_on_hand: null + on_demand: null + default_stock: null + resettable: false + + updateIds: (updatedVos) -> + for vo in updatedVos + @variantOverrides[vo.hub_id][vo.variant_id].id = vo.id + + updateData: (updatedVos) -> + for vo in updatedVos + @variantOverrides[vo.hub_id][vo.variant_id] = vo diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee new file mode 100644 index 0000000000..ae46cd14c7 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee @@ -0,0 +1 @@ +angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown"]) diff --git a/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee index 3883eca8d5..db995170dd 100644 --- a/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee @@ -1,9 +1,7 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterprises, Search, $document, HashNavigation, FilterSelectorsService, EnterpriseModal, enterpriseMatchesNameQueryFilter, distanceWithinKmFilter) -> $scope.Enterprises = Enterprises - $scope.totalActive = FilterSelectorsService.totalActive - $scope.clearAll = FilterSelectorsService.clearAll - $scope.filterText = FilterSelectorsService.filterText - $scope.FilterSelectorsService = FilterSelectorsService + $scope.producers_to_filter = Enterprises.producers + $scope.filterSelectors = FilterSelectorsService.createSelectors() $scope.query = Search.search() $scope.openModal = EnterpriseModal.open $scope.activeTaxons = [] diff --git a/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee index ea652b8105..13c1017386 100644 --- a/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee @@ -1,8 +1,5 @@ Darkswarm.controller "GroupEnterprisesCtrl", ($scope, Search, FilterSelectorsService, EnterpriseModal) -> - $scope.totalActive = FilterSelectorsService.totalActive - $scope.clearAll = FilterSelectorsService.clearAll - $scope.filterText = FilterSelectorsService.filterText - $scope.FilterSelectorsService = FilterSelectorsService + $scope.filterSelectors = FilterSelectorsService.createSelectors() $scope.query = Search.search() $scope.openModal = EnterpriseModal.open $scope.activeTaxons = [] diff --git a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee index 4e16b4d062..ed52f46bb7 100644 --- a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee @@ -15,6 +15,8 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M $scope.group_hubs = visible_enterprises.filter (enterprise) -> enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"] + $scope.producers_to_filter = $scope.group_producers + $scope.map = angular.copy MapConfiguration.options $scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee index 8443301765..4f149db1e5 100644 --- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -1,10 +1,8 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) -> $scope.Products = Products $scope.Cart = Cart - $scope.totalActive = FilterSelectorsService.totalActive - $scope.clearAll = FilterSelectorsService.clearAll - $scope.filterText = FilterSelectorsService.filterText - $scope.FilterSelectorsService = FilterSelectorsService + $scope.taxonSelectors = FilterSelectorsService.createSelectors() + $scope.propertySelectors = FilterSelectorsService.createSelectors() $scope.filtersActive = true $scope.limit = 3 $scope.order_cycle = OrderCycle.order_cycle @@ -33,4 +31,5 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle, $scope.clearAll = -> $scope.query = "" - FilterSelectorsService.clearAll() + $scope.taxonSelectors.clearAll() + $scope.propertySelectors.clearAll() diff --git a/app/assets/javascripts/darkswarm/directives/filter_selector.js.coffee b/app/assets/javascripts/darkswarm/directives/filter_selector.js.coffee index 818af3b785..2090c481f7 100644 --- a/app/assets/javascripts/darkswarm/directives/filter_selector.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/filter_selector.js.coffee @@ -1,9 +1,10 @@ -Darkswarm.directive "filterSelector", (FilterSelectorsService)-> +Darkswarm.directive "filterSelector", -> # Automatically builds activeSelectors for taxons # Lots of magic here restrict: 'E' replace: true scope: + selectorSet: '=' objects: "&" activeSelectors: "=?" allSelectors: "=?" # Optional @@ -36,7 +37,7 @@ Darkswarm.directive "filterSelector", (FilterSelectorsService)-> if selector = selectors_by_id[id] selectors.push selector else - selector = selectors_by_id[id] = FilterSelectorsService.new + selector = selectors_by_id[id] = scope.selectorSet.new object: object selectors.push selector selectors diff --git a/app/assets/javascripts/darkswarm/directives/shipping_type_selector.js.coffee b/app/assets/javascripts/darkswarm/directives/shipping_type_selector.js.coffee index 07656d84b7..15fda75820 100644 --- a/app/assets/javascripts/darkswarm/directives/shipping_type_selector.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/shipping_type_selector.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)-> +Darkswarm.directive "shippingTypeSelector", -> # Builds selector for shipping types restrict: 'E' replace: true @@ -8,10 +8,10 @@ Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)-> pickup: false delivery: false - scope.selectors = - delivery: FilterSelectorsService.new + scope.selectors = + delivery: scope.filterSelectors.new icon: "ofn-i_039-delivery" - pickup: FilterSelectorsService.new + pickup: scope.filterSelectors.new icon: "ofn-i_038-takeaway" scope.emit = -> diff --git a/app/assets/javascripts/darkswarm/directives/single_line_selectors.coffee b/app/assets/javascripts/darkswarm/directives/single_line_selectors.coffee index 93896a41f9..ea5d8568d2 100644 --- a/app/assets/javascripts/darkswarm/directives/single_line_selectors.coffee +++ b/app/assets/javascripts/darkswarm/directives/single_line_selectors.coffee @@ -2,6 +2,7 @@ Darkswarm.directive 'singleLineSelectors', ($timeout, $filter) -> restrict: 'E' templateUrl: "single_line_selectors.html" scope: + selectors: "=" objects: "&" activeSelectors: "=" selectorName: "@activeSelectors" diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index ed7d9ac273..f1c9421a15 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -2,8 +2,11 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> # Handles syncing of current cart/order state to server new class Cart dirty: false + update_running: false + update_enqueued: false order: CurrentOrder.order line_items: CurrentOrder.order?.line_items || [] + constructor: -> for line_item in @line_items line_item.variant.line_item = line_item @@ -12,15 +15,31 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> orderChanged: => @unsaved() + + if !@update_running + @scheduleUpdate() + else + @update_enqueued = true + + scheduleUpdate: => if @promise $timeout.cancel(@promise) @promise = $timeout @update, 1000 update: => + @update_running = true $http.post('/orders/populate', @data()).success (data, status)=> @saved() + @update_running = false + @popQueue() if @update_enqueued + .error (response, status)=> - @scheduleRetry() + @scheduleRetry(status) + @update_running = false + + popQueue: => + @update_enqueued = false + @scheduleUpdate() data: => variants = {} @@ -30,7 +49,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> max_quantity: li.max_quantity {variants: variants} - scheduleRetry: => + scheduleRetry: (status) => console.log "Error updating cart: #{status}. Retrying in 3 seconds..." $timeout => console.log "Retrying cart update" diff --git a/app/assets/javascripts/darkswarm/services/filter_selectors.js.coffee b/app/assets/javascripts/darkswarm/services/filter_selectors.js.coffee index ca80ae344c..e94a098b3a 100644 --- a/app/assets/javascripts/darkswarm/services/filter_selectors.js.coffee +++ b/app/assets/javascripts/darkswarm/services/filter_selectors.js.coffee @@ -1,8 +1,11 @@ +# Returns a factory with the only function `createSelectors()`. +# That function creates objects managing a list of filter selectors. Darkswarm.factory "FilterSelectorsService", -> # This stores all filters so we can access in-use counts etc - # Accessed via activeSelector Directive - new class FilterSelectorsService - selectors: [] + class FilterSelectors + constructor: -> + @selectors = [] + new: (obj = {})-> obj.active = false @selectors.push obj @@ -26,3 +29,8 @@ Darkswarm.factory "FilterSelectorsService", -> for selector in @selectors selector.active = false selector.emit() + + # Creates instances of `FilterSelectors` + new class FilterSelectorsService + createSelectors: -> + new FilterSelectors diff --git a/app/assets/javascripts/templates/admin/links_dropdown.html.haml b/app/assets/javascripts/templates/admin/links_dropdown.html.haml index 4f85ed1319..1f44f2418c 100644 --- a/app/assets/javascripts/templates/admin/links_dropdown.html.haml +++ b/app/assets/javascripts/templates/admin/links_dropdown.html.haml @@ -1,4 +1,4 @@ -.ofn_drop_down{ "ofn-drop-down" => true } +.ofn-drop-down %span %i.icon-check Actions diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 62842f0bfb..452e81f6e3 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,10 +1,6 @@ -#save-bar.animate-show{ ng: { show: 'dirty()' } } +#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } .twelve.columns.alpha - %h5{ ng: { show: "dirty() && !saving()" } } - You have unsaved changes - %h5{ ng: { hide: "dirty() || saving()" } } - All changes saved - %h5{ ng: { show: "saving()" } } - Saving... + %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } + {{ StatusMessage.statusMessage.text || " " }} .four.columns.omega.text-right - %input.red{type: "button", value: "Save Changes", ng: { click: "save()" } } + %input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } } diff --git a/app/assets/javascripts/templates/shipping_type_selector.html.haml b/app/assets/javascripts/templates/shipping_type_selector.html.haml index 8feb23e59f..7774ab62c0 100644 --- a/app/assets/javascripts/templates/shipping_type_selector.html.haml +++ b/app/assets/javascripts/templates/shipping_type_selector.html.haml @@ -1,3 +1,4 @@ -%active-selector{"ng-repeat" => "(name, selector) in selectors"} - %i{"ng-class" => "selector.icon"} - {{ name | capitalize }} +%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2 + %active-selector{"ng-repeat" => "(name, selector) in selectors"} + %i{"ng-class" => "selector.icon"} + {{ name | capitalize }} diff --git a/app/assets/javascripts/templates/single_line_selectors.html.haml b/app/assets/javascripts/templates/single_line_selectors.html.haml index d8d5ae6f3a..0f5cdf0fa6 100644 --- a/app/assets/javascripts/templates/single_line_selectors.html.haml +++ b/app/assets/javascripts/templates/single_line_selectors.html.haml @@ -1,5 +1,5 @@ -# In order for the single-line-selector scope to have access to the available selectors, -%filter-selector{objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" } +%filter-selector{"selector-set" => "selectors", objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" } %ul{ ng: { if: "overFlowSelectors().length > 0 || fitting" } } %li.more diff --git a/app/assets/stylesheets/admin/disabled.css.scss b/app/assets/stylesheets/admin/disabled.css.scss new file mode 100644 index 0000000000..a393c5d80b --- /dev/null +++ b/app/assets/stylesheets/admin/disabled.css.scss @@ -0,0 +1,13 @@ +label.disabled { + color: #c3c3c3; + pointer-events: none; +} + +input[type='button']:disabled { + background-color: #c3c3c3; + color: #ffffff; +} + +.select2-container-disabled { + pointer-events: none; +} diff --git a/app/assets/stylesheets/admin/dropdown.css.scss b/app/assets/stylesheets/admin/dropdown.css.scss new file mode 100644 index 0000000000..2dfa369c87 --- /dev/null +++ b/app/assets/stylesheets/admin/dropdown.css.scss @@ -0,0 +1,70 @@ +#content-header .ofn-drop-down { + border: none; + background-color: #5498da; + color: #fff; + float: none; + margin-left: 3px; +} + +.ofn-drop-down:hover, .ofn-drop-down.expanded { + border: 1px solid #adadad; + color: #575757; +} + +.ofn-drop-down { + padding: 7px 15px; + border-radius: 3px; + border: 1px solid #d4d4d4; + background-color: #f5f5f5; + position: relative; + display: block; + float: left; + color: #828282; + cursor: pointer; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + text-align: center; + + &.right { + float: right; + } + + &:hover, &.expanded { + border: 1px solid #adadad; + color: #575757; + } + + > span { + width: auto; + text-transform: uppercase; + font-size: 85%; + font-weight: 600; + } + + .menu { + margin-top: 1px; + position: absolute; + float: none; + top:100%; + left: 0px; + padding: 5px 0px; + border: 1px solid #adadad; + background-color: #ffffff; + box-shadow: 1px 3px 10px #888888; + z-index: 100; + + .menu_item { + margin: 0px; + padding: 2px 0px; + color: #454545; + text-align: left; + } + + .menu_item:hover { + background-color: #ededed; + } + } +} diff --git a/app/assets/stylesheets/admin/filters_and_controls.css.scss b/app/assets/stylesheets/admin/filters_and_controls.css.scss new file mode 100644 index 0000000000..8dd188d9cd --- /dev/null +++ b/app/assets/stylesheets/admin/filters_and_controls.css.scss @@ -0,0 +1,3 @@ +.filters, .controls, .divider { + margin-bottom: 15px; +} diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index d5a53e11c4..e0916b4e79 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -184,68 +184,6 @@ table#listing_enterprise_groups { } } -#content-header .ofn_drop_down { - border: none; - background-color: #5498da; - color: #fff; - float: none; - margin-left: 3px; -} - -.ofn_drop_down { - padding: 6px 15px; - border-radius: 3px; - border: 1px solid #d4d4d4; - background-color: #f5f5f5; - position: relative; - display: block; - float: left; - color: #828282; - cursor: pointer; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - text-align: center; - - > span { - width: auto; - text-transform: uppercase; - font-size: 85%; - font-weight: 600; - } - - .menu { - margin-top: 1px; - position: absolute; - float: none; - top:100%; - left: 0px; - padding: 5px 0px; - border: 1px solid #adadad; - background-color: #ffffff; - box-shadow: 1px 3px 10px #888888; - z-index: 100; - - .menu_item { - margin: 0px; - padding: 2px 0px; - color: #454545; - text-align: left; - } - - .menu_item:hover { - background-color: #ededed; - } - } -} - -.ofn_drop_down:hover, .ofn_drop_down.expanded { - border: 1px solid #adadad; - color: #575757; -} - .field_with_errors > input { border-color: red; } diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 4676d93b1a..544abfa899 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -1,7 +1,3 @@ -.filter_select, .date_filter { - margin-bottom: 10px; -} - input, div { &.update-pending { border: solid 1px orange; diff --git a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass index b5dca200c5..701e8005b0 100644 --- a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass +++ b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass @@ -13,7 +13,7 @@ height: 7rem float: left display: block - z-index: 999999 + z-index: 1 background-color: white overflow: hidden i @@ -56,4 +56,4 @@ width: 0rem height: 0rem - \ No newline at end of file + diff --git a/app/assets/stylesheets/darkswarm/groups.css.sass b/app/assets/stylesheets/darkswarm/groups.css.sass index 97b984eb34..765689049d 100644 --- a/app/assets/stylesheets/darkswarm/groups.css.sass +++ b/app/assets/stylesheets/darkswarm/groups.css.sass @@ -11,6 +11,10 @@ background-size: 922px 922px @include sidepaddingSm @include panepadding + h1, p.text + font-weight: 300 + h1 + font-size: 350% a > .group-name &:hover, &:focus, &:active text-decoration: underline @@ -97,11 +101,13 @@ // Producers tab .producers background-image: none + background-color: initial .active_table .active_table_node a.is_distributor, .active_table .active_table_node a.is_distributor i.ofn-i_059-producer color: $clr-turquoise + padding: 0 // Hubs tab .hubs background-image: none padding-top: 0 padding-bottom: 0 - \ No newline at end of file + diff --git a/app/assets/stylesheets/darkswarm/modals.css.sass b/app/assets/stylesheets/darkswarm/modals.css.sass index 560153c5b1..5844a8b600 100644 --- a/app/assets/stylesheets/darkswarm/modals.css.sass +++ b/app/assets/stylesheets/darkswarm/modals.css.sass @@ -48,3 +48,7 @@ dialog .close-reveal-modal, .reveal-modal .close-reveal-modal &:hover, &:active, &:focus background-color: rgba(205,205,205,1) color: #333 + +// Prevent body from scrolling when a modal is open +body.modal-open + overflow: hidden diff --git a/app/assets/stylesheets/darkswarm/shopping-cart.css.sass b/app/assets/stylesheets/darkswarm/shopping-cart.css.sass index 74cec02ce0..6fd48e0970 100644 --- a/app/assets/stylesheets/darkswarm/shopping-cart.css.sass +++ b/app/assets/stylesheets/darkswarm/shopping-cart.css.sass @@ -13,6 +13,11 @@ right: 10px top: 55px width: 480px + + @media screen and (min-width: 641px) + overflow-y: auto + max-height: calc(95vh - 55px) + @media screen and (max-width: 640px) width: 96% @@ -48,7 +53,7 @@ .cart-item-delete a.delete font-size: 1.125em - + .item-thumb-image display: none @media screen and (min-width: 640px) diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index b8ea46689f..866c05ea54 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -89,7 +89,7 @@ module Admin end def collection_actions - [:index, :for_order_cycle] + [:index, :for_order_cycle, :bulk_update] end def current_enterprise diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 3880858e5b..300a2ca24e 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -175,5 +175,9 @@ module Admin def ams_prefix_whitelist [:basic] end + + def collection_actions + [:index, :bulk_update] + end end end diff --git a/app/controllers/admin/variant_overrides_controller.rb b/app/controllers/admin/variant_overrides_controller.rb index 9425565f6e..c886f80652 100644 --- a/app/controllers/admin/variant_overrides_controller.rb +++ b/app/controllers/admin/variant_overrides_controller.rb @@ -4,32 +4,43 @@ module Admin class VariantOverridesController < ResourceController include OpenFoodNetwork::SpreeApiKeyLoader + prepend_before_filter :load_data + before_filter :load_collection, only: [:bulk_update] before_filter :load_spree_api_key, only: :index - before_filter :load_data + def index end def bulk_update - collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }] - vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash - # Ensure we're authorised to update all variant overrides - vo_set.collection.each { |vo| authorize! :update, vo } + @vo_set.collection.each { |vo| authorize! :update, vo } - if vo_set.save + if @vo_set.save # Return saved VOs with IDs - render json: vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer + render json: @vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer else - if vo_set.errors.present? - render json: { errors: vo_set.errors }, status: 400 + if @vo_set.errors.present? + render json: { errors: @vo_set.errors }, status: 400 else render nothing: true, status: 500 end end end + def bulk_reset + # Ensure we're authorised to update all variant overrides. + @collection.each { |vo| authorize! :bulk_reset, vo } + @collection.each(&:reset_stock!) + + if collection_errors.present? + render json: { errors: collection_errors }, status: 400 + else + render json: @collection, each_serializer: Api::Admin::VariantOverrideSerializer + end + end + private @@ -43,10 +54,28 @@ module Admin @hub_permissions = OpenFoodNetwork::Permissions.new(spree_current_user). variant_override_enterprises_per_hub - @variant_overrides = VariantOverride.for_hubs(@hubs) + end + + def load_collection + collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }] + @vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash end def collection + @variant_overrides = VariantOverride.for_hubs(params[:hub_id] || @hubs) + end + + def collection_actions + [:index, :bulk_update, :bulk_reset] + end + + # This has been pulled from ModelSet as it is useful for compiling a list of errors on any generic collection (not necessarily a ModelSet) + # Could be pulled down into a lower level controller if it is useful in other high level controllers + def collection_errors + errors = ActiveModel::Errors.new self + full_messages = @collection.map { |element| element.errors.full_messages }.flatten + full_messages.each { |fm| errors.add(:base, fm) } + errors end end end diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index fd5d3b634e..c6d325d0f8 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -23,13 +23,23 @@ Spree::OrdersController.class_eval do end def populate - populator = Spree::OrderPopulator.new(current_order(true), current_currency) - if populator.populate(params.slice(:products, :variants, :quantity), true) - fire_event('spree.cart.add') - fire_event('spree.order.contents_changed') - render json: true, status: 200 - else - render json: false, status: 402 + # Without intervention, the Spree::Adjustment#update_adjustable callback is called many times + # during cart population, for both taxation and enterprise fees. This operation triggers a + # costly Spree::Order#update!, which only needs to be run once. We avoid this by disabling + # callbacks on Spree::Adjustment and then manually invoke Spree::Order#update! on success. + + Spree::Adjustment.without_callbacks do + populator = Spree::OrderPopulator.new(current_order(true), current_currency) + if populator.populate(params.slice(:products, :variants, :quantity), true) + fire_event('spree.cart.add') + fire_event('spree.order.contents_changed') + + current_order.update! + + render json: true, status: 200 + else + render json: false, status: 402 + end end end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 6036447d9b..343e15ef29 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -31,12 +31,12 @@ module Admin admin_inject_json_ams_array ngModule, "shops", @shops, Api::Admin::IdNameSerializer end - def admin_inject_hubs - admin_inject_json_ams_array "ofn.admin", "hubs", @hubs, Api::Admin::IdNameSerializer + def admin_inject_hubs(opts={module: 'ofn.admin'}) + admin_inject_json_ams_array opts[:module], "hubs", @hubs, Api::Admin::IdNameSerializer end - def admin_inject_producers - admin_inject_json_ams_array "ofn.admin", "producers", @producers, Api::Admin::IdNameSerializer + def admin_inject_producers(opts={module: 'ofn.admin'}) + admin_inject_json_ams_array opts[:module], "producers", @producers, Api::Admin::IdNameSerializer end def admin_inject_enterprise_permissions @@ -49,7 +49,7 @@ module Admin end def admin_inject_hub_permissions - render partial: "admin/json/injection_ams", locals: {ngModule: "ofn.admin", name: "hubPermissions", json: @hub_permissions.to_json} + render partial: "admin/json/injection_ams", locals: {ngModule: "admin.variantOverrides", name: "hubPermissions", json: @hub_permissions.to_json} end def admin_inject_products @@ -69,7 +69,7 @@ module Admin end def admin_inject_variant_overrides - admin_inject_json_ams_array "ofn.admin", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer + admin_inject_json_ams_array "admin.variantOverrides", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer end def admin_inject_order_cycle_instance @@ -85,7 +85,7 @@ module Admin end def admin_inject_spree_api_key - render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} + render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} end def admin_inject_json_ams(ngModule, name, data, serializer, opts = {}) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 05057c136b..df67261fd7 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -6,7 +6,7 @@ module InjectionHelper end def inject_group_enterprises - inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data + inject_json_ams "group_enterprises", @group.enterprises.activated.all, Api::EnterpriseSerializer, enterprise_injection_data end def inject_current_hub diff --git a/app/mailers/spree/base_mailer_decorator.rb b/app/mailers/spree/base_mailer_decorator.rb index 4f78c1fe1e..339abd8901 100644 --- a/app/mailers/spree/base_mailer_decorator.rb +++ b/app/mailers/spree/base_mailer_decorator.rb @@ -10,4 +10,4 @@ Spree::BaseMailer.class_eval do # This lets us specify assets using relative paths in email templates super.merge(url_options: {host: URI(spree.root_url).host }) end -end \ No newline at end of file +end diff --git a/app/models/model_set.rb b/app/models/model_set.rb index c9e1456f1a..0c0d156179 100644 --- a/app/models/model_set.rb +++ b/app/models/model_set.rb @@ -16,8 +16,8 @@ class ModelSet end end - def collection_attributes=(attributes) - attributes.each do |k, attributes| + def collection_attributes=(collection_attributes) + collection_attributes.each do |k, attributes| # attributes == {:id => 123, :next_collection_at => '...'} e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? } if e.nil? @@ -41,7 +41,11 @@ class ModelSet end def collection_to_delete - collection.select { |e| @delete_if.andand.call(e.attributes) } + # Remove all elements to be deleted from collection and return them + # Allows us to render @model_set.collection without deleted elements + deleted = [] + collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes) } + deleted end def collection_to_keep @@ -51,5 +55,4 @@ class ModelSet def persisted? false end - end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 33851cfd65..03979220d6 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -16,7 +16,7 @@ class OrderCycle < ActiveRecord::Base scope :inactive, lambda { where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?', Time.zone.now, Time.zone.now) } scope :upcoming, lambda { where('order_cycles.orders_open_at > ?', Time.zone.now) } scope :closed, lambda { where('order_cycles.orders_close_at < ?', Time.zone.now).order("order_cycles.orders_close_at DESC") } - scope :undated, where(orders_open_at: nil, orders_close_at: nil) + scope :undated, where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL') scope :soonest_closing, lambda { active.order('order_cycles.orders_close_at ASC') } # TODO This method returns all the closed orders. So maybe we can replace it with :recently_closed. @@ -182,7 +182,7 @@ class OrderCycle < ActiveRecord::Base end def undated? - self.orders_open_at.nil? && self.orders_close_at.nil? + self.orders_open_at.nil? || self.orders_close_at.nil? end def upcoming? diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 25e0ced2cc..9bc303116b 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -66,7 +66,7 @@ class AbilityDecorator def add_enterprise_management_abilities(user) # Spree performs authorize! on (:create, nil) when creating a new order from admin, and also (:search, nil) # when searching for variants to add to the order - can [:create, :search, :bulk_update], nil + can [:create, :search], nil can [:admin, :index], :overview @@ -111,7 +111,9 @@ class AbilityDecorator OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier end - can [:admin, :index, :read, :update, :bulk_update], VariantOverride do |vo| + can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo| + next false unless vo.hub.present? && vo.variant.andand.product.andand.supplier.present? + hub_auth = OpenFoodNetwork::Permissions.new(user). variant_override_hubs. include? vo.hub diff --git a/app/models/spree/adjustment_decorator.rb b/app/models/spree/adjustment_decorator.rb index fb3b190394..0d1bc941f6 100644 --- a/app/models/spree/adjustment_decorator.rb +++ b/app/models/spree/adjustment_decorator.rb @@ -35,5 +35,19 @@ module Spree def display_included_tax Spree::Money.new(included_tax, { :currency => currency }) end + + def self.without_callbacks + skip_callback :save, :after, :update_adjustable + skip_callback :destroy, :after, :update_adjustable + + result = yield + + ensure + set_callback :save, :after, :update_adjustable + set_callback :destroy, :after, :update_adjustable + + result + end + end end diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index 55afd99321..21820ce0db 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -3,6 +3,8 @@ class VariantOverride < ActiveRecord::Base belongs_to :variant, class_name: 'Spree::Variant' validates_presence_of :hub_id, :variant_id + # Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user. + validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true scope :for_hubs, lambda { |hubs| where(hub_id: hubs) @@ -49,6 +51,21 @@ class VariantOverride < ActiveRecord::Base end end + def default_stock? + default_stock.present? + end + + def reset_stock! + if resettable + if default_stock? + self.attributes = { count_on_hand: default_stock } + self.save + else + Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant with no default stock level." + end + end + self + end private diff --git a/app/models/variant_override_set.rb b/app/models/variant_override_set.rb index 985190095b..730246cebe 100644 --- a/app/models/variant_override_set.rb +++ b/app/models/variant_override_set.rb @@ -1,6 +1,16 @@ class VariantOverrideSet < ModelSet def initialize(collection, attributes={}) - super(VariantOverride, collection, attributes, nil, - proc { |attrs| attrs['price'].blank? && attrs['count_on_hand'].blank? } ) + super(VariantOverride, collection, attributes, nil, proc { |attrs| deletable?(attrs) } ) + end + + private + + def deletable?(attrs) + attrs['price'].blank? && + attrs['count_on_hand'].blank? && + attrs['default_stock'].blank? && + attrs['resettable'].blank? && + attrs['sku'].nil? && + attrs['on_demand'].nil? end end diff --git a/app/serializers/api/admin/exchange_serializer.rb b/app/serializers/api/admin/exchange_serializer.rb index 64c2c08cd4..cb49d94fc8 100644 --- a/app/serializers/api/admin/exchange_serializer.rb +++ b/app/serializers/api/admin/exchange_serializer.rb @@ -1,5 +1,5 @@ class Api::Admin::ExchangeSerializer < ActiveModel::Serializer - attributes :id, :sender_id, :receiver_id, :incoming, :variants, :pickup_time, :pickup_instructions + attributes :id, :sender_id, :receiver_id, :incoming, :variants, :receival_instructions, :pickup_time, :pickup_instructions has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer diff --git a/app/serializers/api/admin/variant_override_serializer.rb b/app/serializers/api/admin/variant_override_serializer.rb index ebe76a1049..c1e6e0038c 100644 --- a/app/serializers/api/admin/variant_override_serializer.rb +++ b/app/serializers/api/admin/variant_override_serializer.rb @@ -1,3 +1,3 @@ class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer - attributes :id, :hub_id, :variant_id, :price, :count_on_hand + attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable end diff --git a/app/serializers/api/admin/variant_serializer.rb b/app/serializers/api/admin/variant_serializer.rb index 510f7af333..66acfe8ece 100644 --- a/app/serializers/api/admin/variant_serializer.rb +++ b/app/serializers/api/admin/variant_serializer.rb @@ -1,5 +1,5 @@ class Api::Admin::VariantSerializer < ActiveModel::Serializer - attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display + attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display, :sku attributes :on_hand, :price has_many :variant_overrides diff --git a/app/serializers/api/line_item_serializer.rb b/app/serializers/api/line_item_serializer.rb index d791febdfc..35d3f9c540 100644 --- a/app/serializers/api/line_item_serializer.rb +++ b/app/serializers/api/line_item_serializer.rb @@ -1,5 +1,5 @@ class Api::LineItemSerializer < ActiveModel::Serializer - attributes :id, :quantity, :price + attributes :id, :quantity, :max_quantity, :price has_one :variant, serializer: Api::VariantSerializer end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 66790a34df..5647821bf5 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -12,25 +12,13 @@ .seven.columns.omega   .row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' } - .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } + .controls.sixteen.columns.alpha.omega .five.columns.alpha - %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } + %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } .five.columns   - -# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } - -# %span{ :class => 'icon-check' }   Actions - -# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - -# %div.menu{ 'ng-show' => "expanded" } - -# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true } - -# %span{ :class => 'three columns omega' } {{action.name }} + -# =render 'admin/shared/bulk_actions_dropdown' .three.columns   - .three.columns.omega - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + = render 'admin/shared/columns_dropdown' .row{ 'ng-if' => 'shop && !loaded()' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index 666ea4604d..1fb35e595c 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -4,21 +4,9 @@ .four.columns.alpha %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Search By Name' } .six.columns   - -# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } - -# %span{ :class => 'icon-check' }   Actions - -# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - -# %div.menu{ 'ng-show' => "expanded" } - -# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredEnterprises)", 'ofn-close-on-click' => true } - -# %span{ :class => 'three columns omega' } {{action.name }} + -# = render 'admin/shared/bulk_actions_dropdown' .three.columns   - .three.columns.omega - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + = render 'admin/shared/columns_dropdown' .row{ 'ng-if' => '!loaded' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/admin/shared/_bulk_actions_dropdown.html.haml b/app/views/admin/shared/_bulk_actions_dropdown.html.haml new file mode 100644 index 0000000000..912fe6662a --- /dev/null +++ b/app/views/admin/shared/_bulk_actions_dropdown.html.haml @@ -0,0 +1,7 @@ +.three.columns + .ofn-drop-down#bulk-actions-dropdown{ 'ng-controller' => "DropDownCtrl" } + %span.icon-check   Actions + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + .three.columns.alpha.menu_item{ 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true } + %span.three.columns.omega {{action.name }} diff --git a/app/views/admin/shared/_columns_dropdown.html.haml b/app/views/admin/shared/_columns_dropdown.html.haml new file mode 100644 index 0000000000..b16d388c1d --- /dev/null +++ b/app/views/admin/shared/_columns_dropdown.html.haml @@ -0,0 +1,8 @@ +%div.three.columns.omega + %div.ofn-drop-down.right#columns-dropdown{ 'ng-controller' => "DropDownCtrl" } + %span{ :class => 'icon-reorder' }   Columns + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + %div.menu_item.three.columns.alpha.omega{ 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } + %span.one.column.alpha.text-center {{ column.visible && "✓" || !column.visible && " " }} + %span.two.columns.omega {{column.name }} diff --git a/app/views/admin/variant_overrides/_actions.html.haml b/app/views/admin/variant_overrides/_actions.html.haml deleted file mode 100644 index 0ae6f8b96b..0000000000 --- a/app/views/admin/variant_overrides/_actions.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -.row - %input.four.columns.alpha{type: 'button', value: 'Save Changes', 'ng-click' => 'update()'} - .twelve.columns.omega - = render 'spree/admin/shared/status_message' diff --git a/app/views/admin/variant_overrides/_data.html.haml b/app/views/admin/variant_overrides/_data.html.haml index 3b5f7f125c..64a7619ea7 100644 --- a/app/views/admin/variant_overrides/_data.html.haml +++ b/app/views/admin/variant_overrides/_data.html.haml @@ -1,5 +1,5 @@ = admin_inject_spree_api_key -= admin_inject_hubs += admin_inject_hubs module: 'admin.variantOverrides' = admin_inject_hub_permissions -= admin_inject_producers += admin_inject_producers module: 'admin.variantOverrides' = admin_inject_variant_overrides diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml new file mode 100644 index 0000000000..9dc90d7ad3 --- /dev/null +++ b/app/views/admin/variant_overrides/_filters.html.haml @@ -0,0 +1,26 @@ +.filters.sixteen.columns.alpha + .filter.four.columns.alpha + %label{ :for => 'query', ng: {class: '{disabled: !hub.id}'} }Quick Search + %br + %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub.id'} } + .two.columns   + .filter_select.four.columns + %label{ :for => 'hub_id', ng: { bind: 'hub_id ? "Shop" : "Select a shop"' } } + %br + %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs', change: 'selectHub()' } } + .filter_select.four.columns + %label{ :for => 'producer_filter', ng: {class: '{disabled: !hub.id}'} }Producer + %br + %input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', style: 'display:none', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub.id' } } + -# .filter_select{ :class => "three columns" } + -# %label{ :for => 'distributor_filter' }Hub + -# %br + -# %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'} + -# .filter_select{ :class => "three columns" } + -# %label{ :for => 'order_cycle_filter' }Order Cycle + -# %br + -# %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'} + .filter_clear.two.columns.omega + %label{ :for => 'clear_all_filters' } + %br + %input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", ng: { click: "resetSelectFilters()", disabled: '!hub.id'} } diff --git a/app/views/admin/variant_overrides/_hub_choice.html.haml b/app/views/admin/variant_overrides/_hub_choice.html.haml deleted file mode 100644 index aa0f7ab738..0000000000 --- a/app/views/admin/variant_overrides/_hub_choice.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.row - .two.columns.alpha - Hub - .four.columns - %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', 'ng-options' => 'hub.id as hub.name for hub in hubs' } - .ten.columns.omega - %input{ type: 'button', value: 'Go', 'ng-click' => 'selectHub()' } diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index cf11e8ac5d..ed3354de9a 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,10 +1,23 @@ -%table.index.bulk{ng: {show: 'hub'}} +%table.index.bulk{ ng: {show: 'hub'}} + %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } + %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } + %col.sku{ width: "20%", ng: { show: 'columns.sku.visible' } } + %col.price{ width: "10%", ng: { show: 'columns.price.visible' } } + %col.on_hand{ width: "10%", ng: { show: 'columns.on_hand.visible' } } + %col.on_demand{ width: "10%", ng: { show: 'columns.on_demand.visible' } } + %col.reset{ width: "1%", ng: { show: 'columns.reset.visible' } } + %col.reset{ width: "15%", ng: { show: 'columns.reset.visible' } } + %col.inheritance{ width: "5%", ng: { show: 'columns.inheritance.visible' } } %thead - %tr - %th Producer - %th Product - %th Price - %th On hand - %tbody{ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id'}} + %tr{ ng: { controller: "ColumnsCtrl" } } + %th.producer{ ng: { show: 'columns.producer.visible' } } Producer + %th.product{ ng: { show: 'columns.product.visible' } } Product + %th.sku{ ng: { show: 'columns.sku.visible' } } SKU + %th.price{ ng: { show: 'columns.price.visible' } } Price + %th.on_hand{ ng: { show: 'columns.on_hand.visible' } } On hand + %th.on_demand{ ng: { show: 'columns.on_demand.visible' } } On Demand? + %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } Enable Stock Level Reset? + %th.inheritance{ ng: { show: 'columns.inheritance.visible' } } Inherit? + %tbody{bindonce: true, ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id | attrFilter:{producer_id:producerFilter} | filter:query' } } = render 'admin/variant_overrides/products_product' = render 'admin/variant_overrides/products_variants' diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index 52551e8e0b..b7cb11041b 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -1,5 +1,9 @@ %tr.product.even - %td {{ producers[product.producer_id].name }} - %td {{ product.name }} - %td - %td + %td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} } + %td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} } + %td.sku{ ng: { show: 'columns.sku.visible' } } + %td.price{ ng: { show: 'columns.price.visible' } } + %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %td.on_demand{ ng: { show: 'columns.on_demand.visible' } } + %td.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } + %td.inheritance{ ng: { show: 'columns.inheritance.visible' } } diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index bd48ea343c..87ec1709e4 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -1,10 +1,19 @@ -%tr.variant{ng: {repeat: 'variant in product.variants'}} - %td - %td - {{ variant.display_name }} - .variant-override-unit {{ variant.unit_to_display }} - %td +%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants'}} + %td.producer{ ng: { show: 'columns.producer.visible' } } + %td.product{ ng: { show: 'columns.product.visible' } } + %span{ bo: { bind: 'variant.display_name || ""'} } + .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %td.sku{ ng: { show: 'columns.sku.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'} + %td.price{ ng: { show: 'columns.price.visible' } } %input{name: 'variant-overrides-{{ variant.id }}-price', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].price'}, placeholder: '{{ variant.price }}', 'ofn-track-variant-override' => 'price'} - - %td - %input{name: 'variant-overrides-{{ variant.id }}-count-on-hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'price'} + %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-count_on_hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'count_on_hand'} + %td.on_demand{ ng: { show: 'columns.on_demand.visible' } } + %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-on_demand', ng: { model: 'variantOverrides[hub.id][variant.id].on_demand' }, 'ofn-track-variant-override' => 'on_demand' } + %td.reset{ ng: { show: 'columns.reset.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub.id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'} + %td.reset{ ng: { show: 'columns.reset.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'} + %td.inheritance{ ng: { show: 'columns.inheritance.visible' } } + %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true } diff --git a/app/views/admin/variant_overrides/index.html.haml b/app/views/admin/variant_overrides/index.html.haml index 8d7fc4e0b1..9027445e62 100644 --- a/app/views/admin/variant_overrides/index.html.haml +++ b/app/views/admin/variant_overrides/index.html.haml @@ -1,11 +1,14 @@ = render 'admin/variant_overrides/header' = render 'admin/variant_overrides/data' -%div{ ng: { app: 'ofn.admin', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } } - = render 'admin/variant_overrides/hub_choice' +%div{ ng: { app: 'admin.variantOverrides', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } } + = render 'admin/variant_overrides/filters' + %hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'hub' } } + .controls.sixteen.columns.alpha.omega{ ng: { show: 'hub' } } + %input.four.columns.alpha{ type: 'button', value: 'Reset Stock to Defaults', 'ng-click' => 'resetStock()' } + %div.nine.columns.alpha   + = render 'admin/shared/columns_dropdown' - %div{ng: {show: 'hub'}} - %h2 {{ hub.name }} - = render 'admin/variant_overrides/actions' - - = render 'admin/variant_overrides/products' + %form{ name: 'variant_overrides_form' } + %save-bar{ save: "update()", form: "variant_overrides_form" } + = render 'admin/variant_overrides/products' diff --git a/app/views/enterprises/shop.html.haml b/app/views/enterprises/shop.html.haml index 35937629fc..f2063494b4 100644 --- a/app/views/enterprises/shop.html.haml +++ b/app/views/enterprises/shop.html.haml @@ -1,5 +1,9 @@ - content_for(:title) do = current_distributor.name +- content_for(:description) do + = current_distributor.description +- content_for(:image) do + = current_distributor.logo.url = inject_enterprises diff --git a/app/views/groups/_hub_filters.html.haml b/app/views/groups/_hub_filters.html.haml new file mode 100644 index 0000000000..71924d5e85 --- /dev/null +++ b/app/views/groups/_hub_filters.html.haml @@ -0,0 +1,21 @@ +.row + = render partial: 'shared/components/filter_controls' + = render partial: 'shared/components/show_profiles' + +.row.animate-show{"ng-show" => "filtersActive"} + .small-12.columns + .row.filter-box + .small-12.large-9.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_type + %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{"selector-set" => "filterSelectors", objects: "group_hubs | searchEnterprises:query | shipping:shippingTypes | showHubProfiles:show_profiles | taxonsOf", "active-selectors" => "activeTaxons"} + .small-12.large-3.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_delivery + %shipping-type-selector + += render partial: 'shared/components/filter_box' diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml index 19159bc63f..afb19e1b91 100644 --- a/app/views/groups/index.html.haml +++ b/app/views/groups/index.html.haml @@ -7,10 +7,14 @@ angular.module('Darkswarm').value('groups', #{render partial: "json/groups", object: @groups}) #groups.pad-top.footer-pad{"ng-controller" => "GroupsCtrl"} - #active-table-search.row.pad-top - .small-12.columns + .row + .small-12.medium-6.medium-offset-3.columns.text-center %h1 = t :groups_headline + %p.text + = t :groups_text + #active-table-search.row.pad-top + .small-12.columns %p %input{type: :text, "ng-model" => "query", diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index b4e6f7586c..74588176b9 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,5 +1,9 @@ - content_for(:title) do = @group.name +- content_for(:description) do + = @group.description +- content_for(:image) do + = @group.logo.url -# inject all enterprises as "enterprises" -# it could be more efficient to inject only the enterprises that are related to the group @@ -55,14 +59,13 @@ %h1 = t :groups_producers = render partial: "shared/components/enterprise_search" - -# TODO: find out why this is not working - -#= render partial: "producers/filters" + = render partial: "producers/filters" .row{bindonce: true} .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", - "ng-repeat" => "producer in filteredEnterprises = (group_producers | visible | searchEnterprises:query | taxons:activeTaxons)", + "ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons)", "ng-controller" => "GroupEnterpriseNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} @@ -83,17 +86,13 @@ = t :groups_hubs = render partial: "shared/components/enterprise_search" - -# TODO: find out why this is not working - -#= render partial: "home/filters" - .small-12.medium-6.columns - %span   - = render partial: 'shared/components/show_profiles' + = render partial: "hub_filters" .row{bindonce: true} .small-12.columns .active_table %hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}", - "ng-repeat" => "hub in filteredEnterprises = (group_hubs | visible | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])", + "ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])", "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", "ng-controller" => "GroupEnterpriseNodeCtrl"} .small-12.columns diff --git a/app/views/home/_filters.html.haml b/app/views/home/_filters.html.haml index bf4b1f8266..bfd13fdf54 100644 --- a/app/views/home/_filters.html.haml +++ b/app/views/home/_filters.html.haml @@ -11,13 +11,12 @@ .light = t :hubs_filter_by = t :hubs_filter_type - %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" } + %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ "selector-set" => "filterSelectors", objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" } .small-12.large-3.columns %h5.tdhead .light = t :hubs_filter_by = t :hubs_filter_delivery - %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2 - %shipping-type-selector{results: "shippingTypes"} + %shipping-type-selector = render partial: 'shared/components/filter_box' diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index a933858049..c758e4836e 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -2,7 +2,9 @@ %head %meta{charset: 'utf-8'}/ %meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/ - + %meta{property: "og:title", content: content_for?(:title) ? yield(:title) : t(:title)} + %meta{property: "og:description", content: content_for?(:description) ? yield(:description) : t(:site_meta_description)} + %meta{property: "og:image", content: content_for?(:image) ? yield(:image) : ContentConfig.logo.url} %title= content_for?(:title) ? "#{yield(:title)} - #{t(:title)}".html_safe : "#{t(:welcome_to)} #{t(:title)}" - if Rails.env.production? = favicon_link_tag diff --git a/app/views/producers/_filters.html.haml b/app/views/producers/_filters.html.haml index 40cf519450..97cc204cab 100644 --- a/app/views/producers/_filters.html.haml +++ b/app/views/producers/_filters.html.haml @@ -11,5 +11,5 @@ .light = t :producers_filter = t :producers_filter_type - %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-6{objects: "Enterprises.producers | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"} + %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-6{"selector-set" => "filterSelectors", objects: "producers_to_filter | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"} = render partial: 'shared/components/filter_box' diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml index 22f1d426cc..fd7f5faf4e 100644 --- a/app/views/shared/_footer.html.haml +++ b/app/views/shared/_footer.html.haml @@ -150,7 +150,7 @@ = t :footer_legal_tos | = t :footer_legal_visit - %a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} Github + %a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} GitHub %p.text-small = t :footer_legal_text_html, {content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)')} diff --git a/app/views/shared/components/_filter_box.html.haml b/app/views/shared/components/_filter_box.html.haml index e36d754fbd..564edd4150 100644 --- a/app/views/shared/components/_filter_box.html.haml +++ b/app/views/shared/components/_filter_box.html.haml @@ -1,5 +1,5 @@ -.row.filter-box.clear-filters.animate-show{"ng-show" => "filtersActive && totalActive() > 0"} +.row.filter-box.clear-filters.animate-show{"ng-show" => "filtersActive && filterSelectors.totalActive() > 0"} .small-12.columns - %a.button.secondary.small.expand{"ng-click" => "clearAll()"} + %a.button.secondary.small.expand{"ng-click" => "filterSelectors.clearAll()"} %i.ofn-i_009-close = t :components_filters_clearfilters diff --git a/app/views/shared/components/_filter_box_shopfront.html.haml b/app/views/shared/components/_filter_box_shopfront.html.haml deleted file mode 100644 index faf5930019..0000000000 --- a/app/views/shared/components/_filter_box_shopfront.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%span.animate-show{"ng-show" => "filtersActive && totalActive() > 0"} - %a.button.secondary.tiny{"ng-click" => "clearAll()"} - %i.ofn-i_009-close - = t :components_filters_clearfilters diff --git a/app/views/shared/components/_filter_controls.html.haml b/app/views/shared/components/_filter_controls.html.haml index 813c4920b6..d3141a3ea0 100644 --- a/app/views/shared/components/_filter_controls.html.haml +++ b/app/views/shared/components/_filter_controls.html.haml @@ -1,9 +1,9 @@ .small-12.medium-6.columns %a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", - "ng-show" => "FilterSelectorsService.selectors.length > 0"} - {{ filterText(filtersActive) }} + "ng-show" => "filterSelectors.selectors.length > 0"} + {{ filterSelectors.filterText(filtersActive) }} %i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"} %i.ofn-i_006-caret-up{"ng-show" => "filtersActive"} - %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} + %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "filterSelectors.selectors.length == 0"} = t :components_filters_nofilters diff --git a/app/views/shared/components/_filter_controls_shopfront.html.haml b/app/views/shared/components/_filter_controls_shopfront.html.haml deleted file mode 100644 index 4bf7766a5c..0000000000 --- a/app/views/shared/components/_filter_controls_shopfront.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -%a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", -"ng-show" => "FilterSelectorsService.selectors.length > 0"} - {{ filterText(filtersActive) }} - %i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"} - %i.ofn-i_006-caret-up{"ng-show" => "filtersActive"} - -%a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} - = t :components_filters_nofilters diff --git a/app/views/shared/mailers/_social_and_contact.html.haml b/app/views/shared/mailers/_social_and_contact.html.haml index a0f8e125e5..4f5222f77d 100644 --- a/app/views/shared/mailers/_social_and_contact.html.haml +++ b/app/views/shared/mailers/_social_and_contact.html.haml @@ -7,20 +7,23 @@ %td %h5 = t :email_social - %p - %a.soc-btn.fb{:href => "https://www.facebook.com/OpenFoodNet", :target => "_blank"} - Facebook - %a.soc-btn.tw{:href => "https://twitter.com/OpenFoodNet", :target => "_blank"} - Twitter - %a.soc-btn.li{:href => "http://www.linkedin.com/groups/Open-Food-Foundation-4743336", :target => "_blank"} - LinkedIn + %p.social-icons + - if ContentConfig.footer_facebook_url.present? + %a.soc-btn.fb{href: ContentConfig.footer_facebook_url} + Facebook + - if ContentConfig.footer_twitter_url.present? + %a.soc-btn.tw{href: ContentConfig.footer_twitter_url} + Twitter + - if ContentConfig.footer_linkedin_url.present? + %a.soc-btn.li{href: ContentConfig.footer_linkedin_url} + LinkedIn %table.column{:align => "left"} %tr %td - %h5 - = t :email_contact - %p + - if ContentConfig.footer_email.present? + %h5 + = t :email_contact %strong - %a{:href => "mailto:hello@openfoodnetwork.org"} - hello@openfoodnetwork.org + %a{href: ContentConfig.footer_email.reverse, mailto: true, target: '_blank'} + #{ContentConfig.footer_email} %span.clear diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 786cd48662..7efaba6cc9 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 - %single-line-selectors{ objects: "Products.products | products:query | properties: activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} + %single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties: activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} .filter-shopfront.property-selectors.text-right - %single-line-selectors{ objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"} + %single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"} diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index dbcf9c97fd..0ab782023d 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -5,7 +5,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { app: 'admin.lineItems', controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", saving: 'saving', dirty: "bulk_order_form.$dirty" } + %save-bar{ save: "submit()", form: "bulk_order_form" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' }Start Date @@ -32,8 +32,10 @@ %label{ :for => 'clear_all_filters' } %br %input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", 'ng-click' => "resetSelectFilters()" } - %hr{ :class => "sixteen columns alpha", 'ng-show' => 'unitsVariantSelected()' } - %div#group_buy_calculation{ :class => "sixteen columns alpha", 'ng-show' => 'unitsVariantSelected()' } + + %hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'unitsVariantSelected()' } } + + %div.sixteen.columns.alpha.omega#group_buy_calculation{ ng: { show: 'unitsVariantSelected()' } } %div.shared_resource{ :class => "four columns alpha" } %span{ :class => 'three columns alpha' } %input{ type: 'checkbox', :id => 'shared_resource', 'ng-model' => 'sharedResource'} @@ -70,32 +72,23 @@ %div{ :class => "eight columns alpha", 'ng-hide' => 'allFinalWeightVolumesPresent()' } %span{ :class => "eight columns alpha", style: 'color:red' } WARNING: Some variants do not have a unit value - %hr{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px" } - %div{ 'ng-hide' => 'RequestMonitor.loading || lineItems.length == 0' } - .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } - %div{ :class => "three columns alpha" } - %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } - %div{ :class => "three columns" } - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } - %span{ :class => 'icon-check' }   Actions - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true } - %span{ :class => 'three columns omega' } {{action.name }} - %div{ :class => "seven columns" }   - %div{ :class => "three columns omega" } - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + + %hr.divider.sixteen.columns.alpha.omega + + .controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || lineItems.length == 0' } } + %div.three.columns.alpha + %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } + = render 'admin/shared/bulk_actions_dropdown' + %div.seven.columns   + = render 'admin/shared/columns_dropdown' + %div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 LOADING ORDERS + %div{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredLineItems.length == 0'} %h1#no_results No orders found. + %div{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' } %form{ name: 'bulk_order_form' } %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } @@ -129,6 +122,7 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true } diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 22b0a195da..235544cb8a 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -4,7 +4,7 @@ %div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } } = render 'spree/admin/products/bulk_edit/filters' - %hr.sixteen.columns.alpha + %hr.divider.sixteen.columns.alpha.omega = render 'spree/admin/products/bulk_edit/actions' = render 'spree/admin/products/bulk_edit/indicators' = render 'spree/admin/products/bulk_edit/products' diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml index 337f1d469b..7ea29bdb4c 100644 --- a/app/views/spree/admin/products/bulk_edit/_actions.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml @@ -1,13 +1,6 @@ -%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" } - %div.four.columns.alpha +.controls.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0' } + .four.columns.alpha %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} - %div.nine.columns + .nine.columns = render 'spree/admin/shared/status_message' - %div.three.columns.omega - %div.ofn_drop_down.three.columns.omega{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + = render 'admin/shared/columns_dropdown' diff --git a/app/views/spree/admin/products/bulk_edit/_filters.html.haml b/app/views/spree/admin/products/bulk_edit/_filters.html.haml index 99c1067663..3e676ede6d 100644 --- a/app/views/spree/admin/products/bulk_edit/_filters.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_filters.html.haml @@ -1,18 +1,18 @@ -%div.sixteen.columns.alpha - %div.quick_search{ :class => "four columns alpha" } +.filters.sixteen.columns.alpha.omega + .quick_search.four.columns.alpha %label{ :for => 'quick_filter' } %br - %input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } - .filter_select{ :class => "four columns" } + %input.quick-search.fullwidth{ 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } + .filter_select.four.columns %label{ :for => 'producer_filter' }Producer %br - %select{ :class => "four columns alpha", :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } - .filter_select{ :class => "four columns" } + %select.fullwidth{ :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } + .filter_select.four.columns %label{ :for => 'category_filter' }Category %br - %select{ :class => "four columns alpha", :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} + %select.fullwidth{ :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} %div{ :class => "one column" }   - .filter_clear{ :class => "three columns omega" } + .filter_clear.three.columns.omega %label{ :for => 'clear_all_filters' } %br %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index cc85566577..ff345cb259 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -4,6 +4,7 @@ %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" } %td{ 'ng-show' => 'columns.producer.visible' } %td{ 'ng-show' => 'columns.sku.visible' } + %input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' } %td{ 'ng-show' => 'columns.name.visible' } %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } %td.unit_value{ 'ng-show' => 'columns.unit.visible' } diff --git a/app/views/spree/order_mailer/_shipping.html.haml b/app/views/spree/order_mailer/_shipping.html.haml index ba4829b171..4b73f9c970 100644 --- a/app/views/spree/order_mailer/_shipping.html.haml +++ b/app/views/spree/order_mailer/_shipping.html.haml @@ -18,7 +18,7 @@ - if @order.ship_address %h4 - = t :email_shipping_delivery_time + = t :email_shipping_delivery_address %p #{@order.ship_address.full_name} %br diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 25582aac8f..d0a7e99ddf 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -62,7 +62,7 @@ %strong= order.shipping_method.name .pad .text-big - Ready for collection + = t :order_pickup_time %strong #{order.order_cycle.pickup_time_for(order.distributor)} %p.text-small.text-skinny.pre-line %em= order.shipping_method.description.andand.html_safe || "" @@ -140,7 +140,7 @@ %tr.total %td.text-right{colspan: "3"} %h5 - = t :order_produce + = t :order_total_price %td.text-right.total %h5#order_total= order.display_total.to_html diff --git a/config/application.rb b/config/application.rb index 8a492f5121..ba75a097ec 100644 --- a/config/application.rb +++ b/config/application.rb @@ -66,6 +66,7 @@ module Openfoodnetwork # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] config.i18n.default_locale = ENV["LOCALE"] + I18n.locale = config.i18n.locale = config.i18n.default_locale # Setting this to true causes a performance regression in Rails 3.2.17 # When we're on a version with the fix below, we can set it to true diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 96dc12f9b8..7301ef4f2c 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -129,7 +129,7 @@ en-GB: footer_join_partners: "Food systems partners" footer_legal_call: "Read our" - footer_legal_tos: "Terms & conditions" + footer_legal_tos: "Terms and conditions" footer_legal_visit: "Find us on" footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." @@ -344,19 +344,19 @@ See the %{link} to find out more about %{sitename}'s features and to start using login_invalid: "Invalid email or password" modal_hubs: "Food Hubs" - modal_hubs_abstract: Our food hubs are the point of contact between you and the people who make your food! - modal_hubs_content1: You can search for a convenient hub by location or name. Some hubs have multiple points where you can pick-up your purchases, and some will also provide delivery options. Each food hub is a sales point with independent business operations and logistics - so variations between hubs are to be expected. + modal_hubs_abstract: Our food hubs are the point of contact between you and the people who make your food! + modal_hubs_content1: You can search for a convenient hub by location or name. Some hubs have multiple points where you can pick-up your purchases, and some will also provide delivery options. Each food hub is a sales point with independent business operations and logistics - so variations between hubs are to be expected. modal_hubs_content2: You can only shop at one food hub at a time. modal_groups: "Groups / Regions" - modal_groups_content1: These are the organisations and relationships between hubs which make up the Open Food Network. - modal_groups_content2: Some groups are clustered by location or council, others by non-geographic similarities. + modal_groups_content1: These are the organisations and relationships between hubs which make up the Open Food Network. + modal_groups_content2: Some groups are clustered by location or council, others by non-geographic similarities. modal_how: "How it works" modal_how_shop: Shop the Open Food Network modal_how_shop_explained: Search for a food hub near you to start shopping! You can expand each food hub to see what kinds of goodies are available, and click through to start shopping. (You can only shop one food hub at a time.) - modal_how_pickup: Pick-ups, delivery & shipping costs - modal_how_pickup_explained: Some food hubs deliver to your door, while others require you to pick-up your purchases. You can see which options are available on the homepage, and select which you'd like at the shopping and check-out pages. Delivery will cost more, and pricing differs from hub-to-hub. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected. + modal_how_pickup: Pick-ups, delivery and shipping costs + modal_how_pickup_explained: Some food hubs deliver to your door, while others require you to pick-up your purchases. You can see which options are available on the homepage, and select which you'd like at the shopping and check-out pages. Delivery will cost more, and pricing differs from hub-to-hub. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected. modal_how_more: Learn more modal_how_more_explained: "If you want to learn more about the Open Food Network, how it works, and get involved, check out:" @@ -432,7 +432,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using orders_edit_checkout: Checkout orders_form_empty_cart: "Empty cart" orders_form_subtotal: Produce subtotal - orders_form_admin: Admin & handling + orders_form_admin: Admin and handling orders_form_total: Total orders_oc_expired_headline: Orders have closed for this order cycle orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders." diff --git a/config/locales/en.yml b/config/locales/en.yml index 062c4c2a29..a5c5ba7903 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -29,6 +29,7 @@ en: home: "OFN" title: Open Food Network welcome_to: 'Welcome to ' + site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers: Aussie Producers producers_join: Australian producers are now welcome to join the Open Food Network. @@ -52,6 +53,8 @@ en: free: "free" plus_tax: "plus GST" total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)" + say_no: "No" + say_yes: "Yes" sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" @@ -166,7 +169,7 @@ en: footer_join_partners: "Food systems partners" footer_legal_call: "Read our" - footer_legal_tos: "Terms & conditions" + footer_legal_tos: "Terms and conditions" footer_legal_visit: "Find us on" footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." @@ -228,6 +231,7 @@ en: order_delivery_on: Delivery on order_delivery_address: Delivery address order_special_instructions: "Your notes:" + order_pickup_time: Ready for collection order_pickup_instructions: Collection Instructions order_produce: Produce order_total_price: Total @@ -348,6 +352,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using groups_title: Groups groups_headline: Groups / regions + groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you." groups_search: "Search name or keyword" groups_no_groups: "No groups found" groups_about: "About Us" @@ -392,7 +397,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using modal_how: "How it works" modal_how_shop: Shop the Open Food Network modal_how_shop_explained: Search for a food hub near you to start shopping! You can expand each food hub to see what kinds of goodies are available, and click through to start shopping. (You can only shop one food hub at a time.) - modal_how_pickup: Pick-ups, delivery & shipping costs + modal_how_pickup: Pick-ups, delivery and shipping costs modal_how_pickup_explained: Some food hubs deliver to your door, while others require you to pick-up your purchases. You can see which options are available on the homepage, and select which you'd like at the shopping and check-out pages. Delivery will cost more, and pricing differs from hub-to-hub. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected. modal_how_more: Learn more modal_how_more_explained: "If you want to learn more about the Open Food Network, how it works, and get involved, check out:" @@ -469,7 +474,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using orders_edit_checkout: Checkout orders_form_empty_cart: "Empty cart" orders_form_subtotal: Produce subtotal - orders_form_admin: Admin & handling + orders_form_admin: Admin and handling orders_form_total: Total orders_oc_expired_headline: Orders have closed for this order cycle orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders." @@ -627,7 +632,7 @@ Please follow the instructions there to make your enterprise visible on the Open registration_type_producer: "Yes, I'm a producer" registration_type_no_producer: "No, I'm not a producer" registration_type_error: "Please choose one. Are you are producer?" - registration_type_producer_help: "Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." + registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." create_profile: "Create Profile" registration_images_headline: "Thanks!" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b0613efff4..c4d0939e18 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -14,10 +14,33 @@ fr: title: Open Food France welcome_to: 'Bienvenue sur ' search_by_name: Recherche pas nom ou région... - producers: Producteurs & hubs + producers: Producteurs et hubs producers_join: Les producteurs et autres hubs basés en France sont invités à rejoindre Open Food France. charges_sales_tax: Soumis à la TVA? - print: "Imprimer" + print_invoice: "Imprimer la facture" + send_invoice: "Envoyer la facture" + resend_confirmation: "Renvoyer la confirmation" + view_order: "Voir la commande" + edit_order: "Editer la commande" + ship_order: "Envoyer la commande" + cancel_order: "Annuler la commande" + confirm_send_invoice: "La facture de cette commande va être transmise au client. Etes-vous sûr de vouloir continuer ?" + confirm_resend_order_confirmation: "Etes-vous sûr de vouloir renvoyer le mail de confirmation de commande ?" + must_have_valid_business_number: "%{enterprise_name} doit avoir un SIRET valide avant que les factures puissent être envoyées." + invoice: "Facture" + percentage_of_sales: "%{percentage} des ventes" + percentage_of_turnover: "Pourcentage du chiffre d'affaire" + monthly_cap_excl_tax: "Cumul mensuel (sans TVA)" + capped_at_cap: "plafonné à %{cap}" + per_month: "par mois" + free: "gratuit" + plus_tax: "plus TVA" + total_monthly_bill_incl_tax: "Facture mensuelle totale (taxes incluses)" + sort_order_cycles_on_shopfront_by: "Trier les cycles de vente par" + invoice_column_item: "Produit" + invoice_column_qty: "Qté" + invoice_column_tax: "TVA" + invoice_column_price: "Prix" logo: "Logo (640x130)" logo_mobile: "Logo smartphone (75x26)" logo_mobile_svg: "Logo smartphone (SVG)" @@ -34,7 +57,6 @@ fr: footer_links_md: "Liens" footer_about_url: "A propos URL" footer_tos_url: "Conditions d'utilisation URL" - invoice: "Facture" name: Nom first_name: Prénom last_name: Nom de famille @@ -111,7 +133,7 @@ fr: footer_join_groups: "Inscription groupes" footer_join_partners: "Partenaires" footer_legal_call: "Lire nos" - footer_legal_tos: "Termes & conditions" + footer_legal_tos: "Termes et conditions" footer_legal_visit: "Nous trouver sur" footer_legal_text_html: "Open Food Network est une plateforme logicielle open source, libre et gratuite. Nos données sont protégées sous licence %{content_license} et notre code sous %{code_license}." home_shop: Faire mes courses @@ -166,6 +188,7 @@ fr: order_delivery_on: Livraison prévue order_delivery_address: Adresse de livraison order_special_instructions: "Vos commentaires:" + order_pickup_time: Disponible pour retrait order_pickup_instructions: Instructions de retrait order_produce: Produit order_total_price: Total @@ -209,7 +232,7 @@ fr: email_payment_paid: RÉGLÉ email_payment_not_paid: NON RÉGLÉ email_payment_summary: Résumé du paiement - email_payment_method: "Payé via:" + email_payment_method: "Payer via :" email_shipping_delivery_details: Détails de livraison email_shipping_delivery_time: "Livré le:" email_shipping_delivery_address: "Adresse de livraison:" @@ -378,7 +401,7 @@ fr: orders_edit_checkout: Régler ma commande orders_form_empty_cart: "Vider le panier" orders_form_subtotal: Sous-total - orders_form_admin: Admin & opérations + orders_form_admin: Admin et opérations orders_form_total: Total orders_oc_expired_headline: Les commandes ne sont plus possibles pour ce cycle de vente. orders_oc_expired_text: "Désolé, les commandes pour ce cycle de vente ont été clôturées il y a %{time}! Veuillez contacter directement le hub pour voir s'il accepte les commandes tardives." @@ -575,4 +598,4 @@ fr: fundraising_fee: "Frais recherche de financement" price_graph: "Légende détail du prix" included_tax: "Inclut TVA" - remove_tax: "Afficher prix HT" + remove_tax: "Retirer TVA" diff --git a/config/locales/nb.yml b/config/locales/nb.yml index 079ef67eb9..59fa27ffa8 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -17,7 +17,30 @@ nb: producers: Norske Produsenter producers_join: Norske produsenter er nÃ¥ velkommen til Ã¥ bli med i Open Food Network. charges_sales_tax: MVA-pliktig? - print: "Print" + print_invoice: "Skriv ut Faktura" + send_invoice: "Send Faktura" + resend_confirmation: "Send Bekreftelse pÃ¥ nytt" + view_order: "Se Bestilling" + edit_order: "Rediger Bestilling" + ship_order: "Send Bestilling" + cancel_order: "Avbryt Bestilling" + confirm_send_invoice: "En faktura for denne bestillingen vil bli sendt til kunden. Er du sikker pÃ¥ at du vil fortsette?" + confirm_resend_order_confirmation: "Er du sikker pÃ¥ at du vil sende ordrebekreftelse via epost pÃ¥ nytt?" + must_have_valid_business_number: "%{enterprise_name} mÃ¥ ha et gyldig ORG nr. før fakturaer kan sendes." + invoice: "Faktura" + percentage_of_sales: "%{percentage} av handel" + percentage_of_turnover: "Prosent av omsetning" + monthly_cap_excl_tax: "mÃ¥nedlig tak (eks. MVA)" + capped_at_cap: "tak pÃ¥ %{cap}" + per_month: "pr. mÃ¥ned" + free: "gratis" + plus_tax: "pluss MVA" + total_monthly_bill_incl_tax: "Total mÃ¥nedlig regning (Inkl. Avgift)" + sort_order_cycles_on_shopfront_by: "Sorter Bestillingsrunder i Nettbutikk etter" + invoice_column_item: "Vare" + invoice_column_qty: "Mengde" + invoice_column_tax: "MVA" + invoice_column_price: "Pris" logo: "Logo (640x130)" logo_mobile: "Mobil logo (75x26)" logo_mobile_svg: "Mobil logo (SVG)" @@ -34,7 +57,6 @@ nb: footer_links_md: "Linker" footer_about_url: "Om URL" footer_tos_url: "VilkÃ¥r URL" - invoice: "Faktura" name: Navn first_name: Fornavn last_name: Etternavn @@ -111,7 +133,7 @@ nb: footer_join_groups: "Bli med som Gruppe" footer_join_partners: "Samarbeidspartnere" footer_legal_call: "Les vÃ¥re" - footer_legal_tos: "VilkÃ¥r & betingelser" + footer_legal_tos: "VilkÃ¥r og betingelser" footer_legal_visit: "Finn oss pÃ¥" footer_legal_text_html: "Open Food Network er en plattform med fri og Ã¥pen kildekode. VÃ¥rt innhold er lisensiert med %{content_license} og vÃ¥r kode med %{code_license}." home_shop: Handle nÃ¥ @@ -575,4 +597,4 @@ nb: fundraising_fee: "Pengeinnsamlingsgebyr" price_graph: "Prisgraf" included_tax: "inkludert avgift" - remove_tax: "Fjern MVA" + remove_tax: "Fjern avgift" diff --git a/config/routes.rb b/config/routes.rb index a5ceb35874..62c4153d14 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -106,6 +106,7 @@ Openfoodnetwork::Application.routes.draw do resources :variant_overrides do post :bulk_update, on: :collection + post :bulk_reset, on: :collection end resources :customers, only: [:index, :update] diff --git a/db/migrate/20150719111807_add_default_stock_to_variant_overrides.rb b/db/migrate/20150719111807_add_default_stock_to_variant_overrides.rb new file mode 100644 index 0000000000..880a1b8349 --- /dev/null +++ b/db/migrate/20150719111807_add_default_stock_to_variant_overrides.rb @@ -0,0 +1,5 @@ +class AddDefaultStockToVariantOverrides < ActiveRecord::Migration + def change + add_column :variant_overrides, :default_stock, :integer + end +end diff --git a/db/migrate/20150827194622_add_enable_reset_to_variant_overrides.rb b/db/migrate/20150827194622_add_enable_reset_to_variant_overrides.rb new file mode 100644 index 0000000000..172cce6588 --- /dev/null +++ b/db/migrate/20150827194622_add_enable_reset_to_variant_overrides.rb @@ -0,0 +1,5 @@ +class AddEnableResetToVariantOverrides < ActiveRecord::Migration + def change + add_column :variant_overrides, :enable_reset, :boolean + end +end diff --git a/db/migrate/20151126235409_add_on_demand_and_sku_to_variant_overrides.rb b/db/migrate/20151126235409_add_on_demand_and_sku_to_variant_overrides.rb new file mode 100644 index 0000000000..9c47bfbc27 --- /dev/null +++ b/db/migrate/20151126235409_add_on_demand_and_sku_to_variant_overrides.rb @@ -0,0 +1,6 @@ +class AddOnDemandAndSkuToVariantOverrides < ActiveRecord::Migration + def change + add_column :variant_overrides, :sku, :string, :default => nil, :after => :hub_id + add_column :variant_overrides, :on_demand, :boolean, :default => nil, :after => :count_on_hand + end +end diff --git a/db/migrate/20151128185900_rename_enable_reset_to_resettable.rb b/db/migrate/20151128185900_rename_enable_reset_to_resettable.rb new file mode 100644 index 0000000000..461e2efe25 --- /dev/null +++ b/db/migrate/20151128185900_rename_enable_reset_to_resettable.rb @@ -0,0 +1,3 @@ +class RenameEnableResetToResettable < ActiveRecord::Migration + rename_column :variant_overrides, :enable_reset, :resettable +end diff --git a/db/schema.rb b/db/schema.rb index fb50bec0a1..465530b949 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20151125051510) do +ActiveRecord::Schema.define(:version => 20151128185900) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false @@ -1159,6 +1159,10 @@ ActiveRecord::Schema.define(:version => 20151125051510) do t.integer "hub_id", :null => false t.decimal "price", :precision => 8, :scale => 2 t.integer "count_on_hand" + t.integer "default_stock" + t.boolean "resettable" + t.string "sku" + t.boolean "on_demand" end add_index "variant_overrides", ["variant_id", "hub_id"], :name => "index_variant_overrides_on_variant_id_and_hub_id" diff --git a/knapsack_rspec_report.json b/knapsack_rspec_report.json new file mode 100644 index 0000000000..9578580ffd --- /dev/null +++ b/knapsack_rspec_report.json @@ -0,0 +1,173 @@ +{ + "spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb": 5.547292709350586, + "spec/controllers/admin/business_model_configuration_controller_spec.rb": 0.3683593273162842, + "spec/controllers/admin/customers_controller_spec.rb": 0.8933048248291016, + "spec/controllers/admin/enterprises_controller_spec.rb": 5.984264850616455, + "spec/controllers/admin/order_cycles_controller_spec.rb": 2.839667558670044, + "spec/controllers/api/enterprises_controller_spec.rb": 0.2780017852783203, + "spec/controllers/api/order_cycles_controller_spec.rb": 1.8730568885803223, + "spec/controllers/base_controller_spec.rb": 0.02932429313659668, + "spec/controllers/cart_controller_spec.rb": 1.062530517578125, + "spec/controllers/checkout_controller_spec.rb": 1.6658811569213867, + "spec/controllers/enterprise_confirmations_controller_spec.rb": 1.1228001117706299, + "spec/controllers/enterprises_controller_spec.rb": 2.2625372409820557, + "spec/controllers/groups_controller_spec.rb": 0.40616846084594727, + "spec/controllers/registration_controller_spec.rb": 0.2145981788635254, + "spec/controllers/shop_controller_spec.rb": 5.298644304275513, + "spec/controllers/shops_controller_spec.rb": 0.2002561092376709, + "spec/controllers/spree/admin/adjustments_controller_spec.rb": 1.023233413696289, + "spec/controllers/spree/admin/base_controller_spec.rb": 0.28871917724609375, + "spec/controllers/spree/admin/line_items_controller_spec.rb": 14.042466402053833, + "spec/controllers/spree/admin/orders_controller_spec.rb": 12.639750480651855, + "spec/controllers/spree/admin/overview_controller_spec.rb": 0.691641092300415, + "spec/controllers/spree/admin/payment_methods_controller_spec.rb": 0.7098217010498047, + "spec/controllers/spree/admin/products_controller_spec.rb": 1.4383087158203125, + "spec/controllers/spree/admin/reports_controller_spec.rb": 47.79633665084839, + "spec/controllers/spree/admin/search_controller_spec.rb": 0.9386723041534424, + "spec/controllers/spree/admin/variants_controller_spec.rb": 2.0663084983825684, + "spec/controllers/spree/api/line_items_controller_spec.rb": 0.4743325710296631, + "spec/controllers/spree/api/products_controller_spec.rb": 8.339523792266846, + "spec/controllers/spree/api/variants_controller_spec.rb": 4.835069179534912, + "spec/controllers/spree/checkout_controller_spec.rb": 0.687798023223877, + "spec/controllers/spree/orders_controller_spec.rb": 1.7623963356018066, + "spec/controllers/spree/paypal_controller_spec.rb": 0.437147855758667, + "spec/controllers/spree/store_controller_spec.rb": 0.03699040412902832, + "spec/controllers/spree/user_sessions_controller_spec.rb": 0.09967947006225586, + "spec/controllers/user_passwords_controller_spec.rb": 0.31070899963378906, + "spec/controllers/user_registrations_controller_spec.rb": 0.36581993103027344, + "spec/features/admin/account_spec.rb": 0.32449865341186523, + "spec/features/admin/accounts_and_billing_settings_spec.rb": 15.864763259887695, + "spec/features/admin/adjustments_spec.rb": 6.825028896331787, + "spec/features/admin/authentication_spec.rb": 22.29801869392395, + "spec/features/admin/bulk_order_management_spec.rb": 112.38913011550903, + "spec/features/admin/bulk_product_update_spec.rb": 59.00568914413452, + "spec/features/admin/business_model_configuration_spec.rb": 2.5152199268341064, + "spec/features/admin/cms_spec.rb": 2.5085999965667725, + "spec/features/admin/content_spec.rb": 1.2907540798187256, + "spec/features/admin/customers_spec.rb": 33.99929761886597, + "spec/features/admin/enterprise_fees_spec.rb": 13.33712100982666, + "spec/features/admin/enterprise_groups_spec.rb": 8.689672231674194, + "spec/features/admin/enterprise_relationships_spec.rb": 7.257282733917236, + "spec/features/admin/enterprise_roles_spec.rb": 5.535412788391113, + "spec/features/admin/enterprise_user_spec.rb": 2.5493221282958984, + "spec/features/admin/enterprises/index_spec.rb": 5.77092719078064, + "spec/features/admin/enterprises_spec.rb": 34.78606820106506, + "spec/features/admin/image_settings_spec.rb": 0.4501008987426758, + "spec/features/admin/order_cycles_spec.rb": 64.186044216156, + "spec/features/admin/orders_spec.rb": 49.190918922424316, + "spec/features/admin/overview_spec.rb": 5.788672208786011, + "spec/features/admin/payment_method_spec.rb": 15.959310531616211, + "spec/features/admin/products_spec.rb": 21.46337914466858, + "spec/features/admin/reports_spec.rb": 150.51152086257935, + "spec/features/admin/shipping_methods_spec.rb": 8.671862363815308, + "spec/features/admin/tax_settings_spec.rb": 0.7941949367523193, + "spec/features/admin/variant_overrides_spec.rb": 29.70982050895691, + "spec/features/admin/variants_spec.rb": 5.565031290054321, + "spec/features/consumer/authentication_spec.rb": 12.449390649795532, + "spec/features/consumer/groups_spec.rb": 1.545715093612671, + "spec/features/consumer/producers_spec.rb": 3.3242862224578857, + "spec/features/consumer/registration_spec.rb": 2.421873092651367, + "spec/features/consumer/shopping/cart_spec.rb": 1.6924467086791992, + "spec/features/consumer/shopping/checkout_auth_spec.rb": 8.496914863586426, + "spec/features/consumer/shopping/checkout_spec.rb": 39.204933881759644, + "spec/features/consumer/shopping/shopping_spec.rb": 23.358332633972168, + "spec/features/consumer/shopping/variant_overrides_spec.rb": 58.16736888885498, + "spec/features/consumer/shops_spec.rb": 6.636866092681885, + "spec/helpers/admin/business_model_configuration_helper_spec.rb": 0.2595028877258301, + "spec/helpers/checkout_helper_spec.rb": 0.10617446899414062, + "spec/helpers/groups_helper_spec.rb": 0.007729053497314453, + "spec/helpers/html_helper_spec.rb": 0.05157279968261719, + "spec/helpers/injection_helper_spec.rb": 0.6142556667327881, + "spec/helpers/navigation_helper_spec.rb": 0.02951979637145996, + "spec/helpers/order_cycles_helper_spec.rb": 0.5953588485717773, + "spec/helpers/products_helper_spec.rb": 0.009511232376098633, + "spec/helpers/shared_helper_spec.rb": 0.017564058303833008, + "spec/helpers/shop_helper_spec.rb": 0.05760025978088379, + "spec/jobs/confirm_order_job_spec.rb": 0.0458524227142334, + "spec/jobs/confirm_signup_job_spec.rb": 0.021564006805419922, + "spec/jobs/finalize_account_invoices_spec.rb": 4.505181312561035, + "spec/jobs/order_cycle_notification_job_spec.rb": 2.0606272220611572, + "spec/jobs/update_account_invoices_spec.rb": 18.434475898742676, + "spec/jobs/update_billable_periods_spec.rb": 4.850176572799683, + "spec/jobs/welcome_enterprise_job_spec.rb": 0.07065534591674805, + "spec/lib/open_food_network/bulk_coop_report_spec.rb": 4.789663553237915, + "spec/lib/open_food_network/customers_report_spec.rb": 2.419727325439453, + "spec/lib/open_food_network/distribution_change_validator_spec.rb": 0.10607743263244629, + "spec/lib/open_food_network/enterprise_fee_applicator_spec.rb": 0.7333858013153076, + "spec/lib/open_food_network/enterprise_fee_calculator_spec.rb": 7.406745195388794, + "spec/lib/open_food_network/enterprise_injection_data_spec.rb": 0.291548490524292, + "spec/lib/open_food_network/enterprise_issue_validator_spec.rb": 0.09764814376831055, + "spec/lib/open_food_network/feature_toggle_spec.rb": 0.010193109512329102, + "spec/lib/open_food_network/group_buy_report_spec.rb": 3.708569049835205, + "spec/lib/open_food_network/last_used_address_spec.rb": 0.0254666805267334, + "spec/lib/open_food_network/lettuce_share_report_spec.rb": 2.3206725120544434, + "spec/lib/open_food_network/option_value_namer_spec.rb": 0.06185555458068848, + "spec/lib/open_food_network/order_and_distributor_report_spec.rb": 1.0406858921051025, + "spec/lib/open_food_network/order_cycle_form_applicator_spec.rb": 4.533008337020874, + "spec/lib/open_food_network/order_cycle_management_report_spec.rb": 2.036308526992798, + "spec/lib/open_food_network/order_cycle_permissions_spec.rb": 23.74185061454773, + "spec/lib/open_food_network/order_grouper_spec.rb": 0.029039621353149414, + "spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb": 5.135573148727417, + "spec/lib/open_food_network/packing_report_spec.rb": 5.088447093963623, + "spec/lib/open_food_network/permissions_spec.rb": 8.881855249404907, + "spec/lib/open_food_network/products_and_inventory_report_spec.rb": 3.55375337600708, + "spec/lib/open_food_network/referer_parser_spec.rb": 0.014271259307861328, + "spec/lib/open_food_network/reports/report_spec.rb": 0.02238297462463379, + "spec/lib/open_food_network/reports/row_spec.rb": 0.0031762123107910156, + "spec/lib/open_food_network/reports/rule_spec.rb": 0.013959169387817383, + "spec/lib/open_food_network/sales_tax_report_spec.rb": 0.10717129707336426, + "spec/lib/open_food_network/scope_variant_to_hub_spec.rb": 2.4846229553222656, + "spec/lib/open_food_network/user_balance_calculator_spec.rb": 3.4277901649475098, + "spec/lib/open_food_network/users_and_enterprises_report_spec.rb": 0.40532779693603516, + "spec/lib/open_food_network/xero_invoices_report_spec.rb": 1.1586685180664062, + "spec/lib/spree/product_filters_spec.rb": 0.13163042068481445, + "spec/mailers/enterprise_mailer_spec.rb": 0.4537942409515381, + "spec/mailers/order_mailer_spec.rb": 1.452355146408081, + "spec/mailers/producer_mailer_spec.rb": 8.775528192520142, + "spec/mailers/user_mailer_spec.rb": 0.057527780532836914, + "spec/models/adjustment_metadata_spec.rb": 0.22016620635986328, + "spec/models/billable_period_spec.rb": 2.06524658203125, + "spec/models/calculator/weight_spec.rb": 0.009344100952148438, + "spec/models/cart_spec.rb": 4.099429130554199, + "spec/models/customer_spec.rb": 0.07328605651855469, + "spec/models/enterprise_caching_spec.rb": 0.8475983142852783, + "spec/models/enterprise_fee_spec.rb": 3.1999905109405518, + "spec/models/enterprise_group_spec.rb": 0.30861926078796387, + "spec/models/enterprise_relationship_spec.rb": 2.1849746704101562, + "spec/models/enterprise_spec.rb": 17.679611682891846, + "spec/models/exchange_spec.rb": 13.899227857589722, + "spec/models/model_set_spec.rb": 0.22760748863220215, + "spec/models/order_cycle_spec.rb": 10.680967569351196, + "spec/models/product_distribution_spec.rb": 2.227938413619995, + "spec/models/spree/ability_spec.rb": 15.278357028961182, + "spec/models/spree/addresses_spec.rb": 0.055602312088012695, + "spec/models/spree/adjustment_spec.rb": 9.196375846862793, + "spec/models/spree/classification_spec.rb": 0.161299467086792, + "spec/models/spree/image_spec.rb": 0.007464408874511719, + "spec/models/spree/line_item_spec.rb": 13.545411586761475, + "spec/models/spree/order_populator_spec.rb": 1.635932207107544, + "spec/models/spree/order_spec.rb": 10.645411968231201, + "spec/models/spree/payment_method_spec.rb": 0.0733034610748291, + "spec/models/spree/payment_spec.rb": 1.691227912902832, + "spec/models/spree/preferences/file_configuration_spec.rb": 0.03429675102233887, + "spec/models/spree/product_spec.rb": 17.406191110610962, + "spec/models/spree/shipping_method_spec.rb": 3.0447566509246826, + "spec/models/spree/tax_rate_spec.rb": 0.44750261306762695, + "spec/models/spree/taxon_spec.rb": 0.553098201751709, + "spec/models/spree/user_spec.rb": 1.2693369388580322, + "spec/models/spree/variant_spec.rb": 13.75825023651123, + "spec/models/variant_override_spec.rb": 4.086935520172119, + "spec/performance/injection_helper_spec.rb": 6.890667676925659, + "spec/performance/orders_controller_spec.rb": 0.031180143356323242, + "spec/performance/shop_controller_spec.rb": 18.19426918029785, + "spec/requests/large_request_spec.rb": 0.02229022979736328, + "spec/requests/shop_spec.rb": 1.0012562274932861, + "spec/serializers/admin/enterprise_serializer_spec.rb": 0.10484433174133301, + "spec/serializers/admin/exchange_serializer_spec.rb": 0.7569985389709473, + "spec/serializers/admin/for_order_cycle/enterprise_serializer_spec.rb": 0.4293792247772217, + "spec/serializers/admin/index_enterprise_serializer_spec.rb": 1.2506742477416992, + "spec/serializers/admin/variant_override_serializer_spec.rb": 0.38981151580810547, + "spec/serializers/enterprise_serializer_spec.rb": 0.3511006832122803, + "spec/serializers/spree/product_serializer_spec.rb": 0.26622653007507324, + "spec/serializers/spree/variant_serializer_spec.rb": 0.30304574966430664 +} diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index 80396ffa17..37a2455616 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -26,12 +26,16 @@ module OpenFoodNetwork end def on_demand - if @variant_override.andand.count_on_hand.present? - # If we're overriding the stock level of an on_demand variant, show it as not - # on_demand, so our stock control can take effect. - false + if @variant_override.andand.on_demand.nil? + if @variant_override.andand.count_on_hand.present? + # If we're overriding the stock level of an on_demand variant, show it as not + # on_demand, so our stock control can take effect. + false + else + super + end else - super + @variant_override.andand.on_demand end end @@ -42,7 +46,10 @@ module OpenFoodNetwork super end end - end + def sku + @variant_override.andand.sku || super + end + end end end diff --git a/script/ci/check_github_status.sh b/script/ci/check_github_status.sh index c4ce47c6fa..0a4775d268 100755 --- a/script/ci/check_github_status.sh +++ b/script/ci/check_github_status.sh @@ -15,5 +15,11 @@ echo "--- Checking environment variables" require_env_vars OFN_COMMIT BUILDKITE_REPO echo "--- Checking GitHub status" +if [ -n "$1" ]; then + REQUIRED_STATUS="$1" +else + REQUIRED_STATUS="success" +fi +echo "Require status '$REQUIRED_STATUS'" echo "Visiting $GITHUB_API_URL" -curl -s "$GITHUB_API_URL" | head -n 2 | grep '^ *"state": "success",$' +curl -s "$GITHUB_API_URL" | head -n 2 | grep '^ *"state":' | egrep "\"$REQUIRED_STATUS\",\$" diff --git a/script/mirror_db.sh b/script/mirror_db.sh index 85e6693b68..4c6142b701 100755 --- a/script/mirror_db.sh +++ b/script/mirror_db.sh @@ -10,12 +10,20 @@ else RAILS_RUN='bundle exec rails runner' fi +if [[ $1 != 'ofn-no' ]]; then + DB_USER='openfoodweb' + DB_DATABASE='openfoodweb_production' +else + DB_USER='ofn_user' + DB_DATABASE='openfoodnetwork' +fi + # -- Mirror database echo "Mirroring database..." echo "drop database open_food_network_dev" | psql -h localhost -U ofn open_food_network_test echo "create database open_food_network_dev" | psql -h localhost -U ofn open_food_network_test -ssh $1 "pg_dump -h localhost -U openfoodweb openfoodweb_production |gzip" |gunzip |psql -h localhost -U ofn open_food_network_dev +ssh $1 "pg_dump -h localhost -U $DB_USER $DB_DATABASE |gzip" |gunzip |psql -h localhost -U ofn open_food_network_dev # -- Disable S3 diff --git a/spec/controllers/admin/variant_overrides_controller_spec.rb b/spec/controllers/admin/variant_overrides_controller_spec.rb new file mode 100644 index 0000000000..d796f2d52f --- /dev/null +++ b/spec/controllers/admin/variant_overrides_controller_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' + +describe Admin::VariantOverridesController, type: :controller do + # include AuthenticationWorkflow + + describe "bulk_update" do + context "json" do + let(:format) { :json } + + let(:hub) { create(:distributor_enterprise) } + let(:variant) { create(:variant) } + let!(:variant_override) { create(:variant_override, hub: hub, variant: variant) } + let(:variant_override_params) { [ { id: variant_override.id, price: 123.45, count_on_hand: 321, sku: "MySKU", on_demand: false } ] } + + context "where I don't manage the variant override hub" do + before do + user = create(:user) + user.owned_enterprises << create(:enterprise) + allow(controller).to receive(:spree_current_user) { user } + end + + it "redirects to unauthorized" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "where I manage the variant override hub" do + before do + allow(controller).to receive(:spree_current_user) { hub.owner } + end + + context "but the producer has not granted VO permission" do + it "redirects to unauthorized" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "and the producer has granted VO permission" do + before do + create(:enterprise_relationship, parent: variant.product.supplier, child: hub, permissions_list: [:create_variant_overrides]) + end + + it "allows me to update the variant override" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + variant_override.reload + expect(variant_override.price).to eq 123.45 + expect(variant_override.count_on_hand).to eq 321 + expect(variant_override.sku).to eq "MySKU" + expect(variant_override.on_demand).to eq false + end + + context "where params for a variant override are blank" do + let(:variant_override_params) { [ { id: variant_override.id, price: "", count_on_hand: "", default_stock: nil, resettable: nil, sku: nil, on_demand: nil } ] } + + it "destroys the variant override" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + expect(VariantOverride.find_by_id(variant_override.id)).to be_nil + end + end + end + end + end + end + + describe "bulk_reset" do + context "json" do + let(:format) { :json } + + let(:hub) { create(:distributor_enterprise) } + let(:producer) { create(:supplier_enterprise) } + let(:product) { create(:product, supplier: producer) } + let(:variant1) { create(:variant, product: product) } + let(:variant2) { create(:variant, product: product) } + let!(:variant_override1) { create(:variant_override, hub: hub, variant: variant1, count_on_hand: 5, default_stock: 7, resettable: true) } + let!(:variant_override2) { create(:variant_override, hub: hub, variant: variant2, count_on_hand: 2, default_stock: 1, resettable: false) } + + let(:params) { { format: format, hub_id: hub.id } } + + context "where I don't manage the variant override hub" do + before do + user = create(:user) + user.owned_enterprises << create(:enterprise) + allow(controller).to receive(:spree_current_user) { user } + end + + it "redirects to unauthorized" do + spree_put :bulk_reset, params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "where I manage the variant override hub" do + before do + allow(controller).to receive(:spree_current_user) { hub.owner } + end + + context "where the producer has not granted create_variant_overrides permission to the hub" do + it "restricts access" do + spree_put :bulk_reset, params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "where the producer has granted create_variant_overrides permission to the hub" do + let!(:er1) { create(:enterprise_relationship, parent: producer, child: hub, permissions_list: [:create_variant_overrides]) } + + it "updates stock to default values where reset is enabled" do + expect(variant_override1.reload.count_on_hand).to eq 5 # reset enabled + expect(variant_override2.reload.count_on_hand).to eq 2 # reset disabled + spree_put :bulk_reset, params + expect(variant_override1.reload.count_on_hand).to eq 7 # reset enabled + expect(variant_override2.reload.count_on_hand).to eq 2 # reset disabled + end + + context "and the producer has granted create_variant_overrides permission to another hub I manage" do + before { hub.owner.update_attribute(:enterprise_limit, 2) } + let(:hub2) { create(:distributor_enterprise, owner: hub.owner) } + let(:product) { create(:product, supplier: producer) } + let(:variant3) { create(:variant, product: product) } + let!(:variant_override3) { create(:variant_override, hub: hub2, variant: variant3, count_on_hand: 1, default_stock: 13, resettable: true) } + let!(:er2) { create(:enterprise_relationship, parent: producer, child: hub2, permissions_list: [:create_variant_overrides]) } + + it "does not reset count_on_hand for variant_overrides not in params" do + expect { + spree_put :bulk_reset, params + }.to_not change{variant_override3.reload.count_on_hand} + end + end + end + end + end + end +end diff --git a/spec/controllers/api/enterprises_controller_spec.rb b/spec/controllers/api/enterprises_controller_spec.rb index 77e16368d9..0e8f2ef606 100644 --- a/spec/controllers/api/enterprises_controller_spec.rb +++ b/spec/controllers/api/enterprises_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Api - describe EnterprisesController do + describe EnterprisesController, :type => :controller do include AuthenticationWorkflow render_views diff --git a/spec/controllers/api/order_cycles_controller_spec.rb b/spec/controllers/api/order_cycles_controller_spec.rb index 3bb9a76602..a9a86608e7 100644 --- a/spec/controllers/api/order_cycles_controller_spec.rb +++ b/spec/controllers/api/order_cycles_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'spree/api/testing_support/helpers' module Api - describe OrderCyclesController do + describe OrderCyclesController, :type => :controller do include Spree::Api::TestingSupport::Helpers include AuthenticationWorkflow render_views diff --git a/spec/controllers/base_controller_spec.rb b/spec/controllers/base_controller_spec.rb index b5ef006c5b..4ea2e31ff2 100644 --- a/spec/controllers/base_controller_spec.rb +++ b/spec/controllers/base_controller_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe BaseController do +describe BaseController, :type => :controller do let(:oc) { mock_model(OrderCycle) } let(:hub) { mock_model(Enterprise, ready_for_checkout?: true) } let(:order) { mock_model(Spree::Order, distributor: hub) } diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index d95012a6fb..73c72f3386 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -58,21 +58,21 @@ describe Spree::OrdersController do end it "returns HTTP success when successful" do - Spree::OrderPopulator.stub(:new).and_return(populator = mock()) + Spree::OrderPopulator.stub(:new).and_return(populator = double()) populator.stub(:populate).and_return true xhr :post, :populate, use_route: :spree, format: :json response.status.should == 200 end it "returns failure when unsuccessful" do - Spree::OrderPopulator.stub(:new).and_return(populator = mock()) + Spree::OrderPopulator.stub(:new).and_return(populator = double()) populator.stub(:populate).and_return false xhr :post, :populate, use_route: :spree, format: :json response.status.should == 402 end it "tells populator to overwrite" do - Spree::OrderPopulator.stub(:new).and_return(populator = mock()) + Spree::OrderPopulator.stub(:new).and_return(populator = double()) populator.should_receive(:populate).with({}, true) xhr :post, :populate, use_route: :spree, format: :json end diff --git a/spec/factories.rb b/spec/factories.rb index 8ae6a094c2..dd8d02e7bf 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -94,6 +94,8 @@ FactoryGirl.define do factory :variant_override, :class => VariantOverride do price 77.77 count_on_hand 11111 + default_stock 2000 + resettable false end factory :enterprise, :class => Enterprise do diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 5a51f396b3..1e011b5939 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -119,10 +119,9 @@ feature %q{ expect(page).to_not have_selector "#save-bar" fill_in "quantity", :with => 2 expect(page).to have_selector "input[name='quantity'].ng-dirty" - expect(page).to have_selector "#save-bar" - expect(page).to have_button "Save Changes" + expect(page).to have_selector "#save-bar", text: "You have unsaved changes" click_button "Save Changes" - expect(page).to_not have_selector "#save-bar" + expect(page).to have_selector "#save-bar", text: "All changes saved" expect(page).to_not have_selector "input[name='quantity'].ng-dirty" end end @@ -132,10 +131,9 @@ feature %q{ expect(page).to_not have_selector "#save-bar" fill_in "quantity", :with => li1.variant.on_hand + li1.quantity + 10 expect(page).to have_selector "input[name='quantity'].ng-dirty" - expect(page).to have_selector "#save-bar" - expect(page).to have_button "Save Changes" + expect(page).to have_selector "#save-bar", text: "You have unsaved changes" click_button "Save Changes" - expect(page).to have_selector "#save-bar" + expect(page).to have_selector "#save-bar", text: "Fields with red borders contain errors." expect(page).to have_selector "input[name='quantity'].ng-dirty.update-error" expect(page).to have_content "exceeds available stock. Please ensure line items have a valid quantity." end @@ -158,9 +156,9 @@ feature %q{ context "modifying the weight/volume of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click - first("div#columns_dropdown div.menu div.menu_item", text: "Price").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click + first("div#columns-dropdown div.menu div.menu_item", text: "Price").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$50.00" fill_in "final_weight_volume", :with => 2000 @@ -177,8 +175,8 @@ feature %q{ context "modifying the quantity of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Price").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Price").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 5)}" fill_in "quantity", :with => 6 @@ -190,8 +188,8 @@ feature %q{ context "modifying the quantity of a line item" do it "weight/volume is altered" do visit '/admin/orders/bulk_management' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click within "tr#li_#{li1.id}" do expect(page).to have_field "final_weight_volume", with: "#{li1.final_weight_volume.round}" fill_in "quantity", :with => 6 @@ -211,8 +209,8 @@ feature %q{ expect(page).to have_selector "th", :text => "QUANTITY" expect(page).to have_selector "th", :text => "MAX" - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Producer").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Producer").click expect(page).to_not have_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" @@ -501,8 +499,8 @@ feature %q{ it "displays a bulk action select box with a list of actions" do list_of_actions = ['Delete Selected'] - find("div#bulk_actions_dropdown").click - within("div#bulk_actions_dropdown") do + find("div#bulk-actions-dropdown").click + within("div#bulk-actions-dropdown") do list_of_actions.each { |action_name| expect(page).to have_selector "div.menu_item", text: action_name } end end @@ -514,8 +512,8 @@ feature %q{ within("tr#li_#{li2.id} td.bulk") do check "bulk" end - find("div#bulk_actions_dropdown").click - find("div#bulk_actions_dropdown div.menu_item", :text => "Delete Selected" ).click + find("div#bulk-actions-dropdown").click + find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click expect(page).to have_selector "tr#li_#{li1.id}", visible: true expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true end @@ -534,8 +532,8 @@ feature %q{ it "only applies the delete action to filteredLineItems" do check "toggle_bulk" fill_in "quick_search", with: o1.number - find("div#bulk_actions_dropdown").click - find("div#bulk_actions_dropdown div.menu_item", :text => "Delete Selected" ).click + find("div#bulk-actions-dropdown").click + find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click fill_in "quick_search", with: '' expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true expect(page).to have_selector "tr#li_#{li2.id}", visible: true diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 91f476d16d..9dabf93028 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -46,8 +46,8 @@ feature %q{ p2 = FactoryGirl.create(:product, available_on: Date.current-1) visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click expect(page).to have_field "available_on", with: p1.available_on.strftime("%F %T") expect(page).to have_field "available_on", with: p2.available_on.strftime("%F %T") @@ -243,11 +243,11 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click - first("div#columns_dropdown div.menu div.menu_item", text: "Category").click - first("div#columns_dropdown div.menu div.menu_item", text: "Inherits Properties?").click - first("div#columns_dropdown div.menu div.menu_item", text: "SKU").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown div.menu div.menu_item", text: "Category").click + first("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click + first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name @@ -308,6 +308,7 @@ feature %q{ p = FactoryGirl.create(:product, supplier: s1, available_on: Date.current, variant_unit: 'volume', variant_unit_scale: 0.001, price: 3.0, on_hand: 9, unit_value: 0.25, unit_description: '(bottle)' ) v = p.variants.first + v.update_column(:sku, "VARIANTSKU") login_to_admin_section @@ -315,12 +316,18 @@ feature %q{ expect(page).to have_selector "a.view-variants" first("a.view-variants").trigger('click') + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + first("div#columns-dropdown", :text => "COLUMNS").click + + expect(page).to have_field "variant_sku", with: "VARIANTSKU" expect(page).to have_field "variant_price", with: "3.0" expect(page).to have_field "variant_unit_value_with_description", with: "250 (bottle)" expect(page).to have_field "variant_on_hand", with: "9" expect(page).to have_selector "span[name='on_hand']", "9" select "Volume (L)", from: "variant_unit_with_scale" + fill_in "variant_sku", with: "NEWSKU" fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" fill_in "variant_unit_value_with_description", with: "2 (8x250 mL bottles)" @@ -331,6 +338,7 @@ feature %q{ expect(page.find("#status-message")).to have_content "Changes saved." v.reload + expect(v.sku).to eq "NEWSKU" expect(v.price).to eq 4.0 expect(v.on_hand).to eq 10 expect(v.unit_value).to eq 2 # 2L in L @@ -556,8 +564,8 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click expect(page).to have_selector "th", :text => "NAME" expect(page).to have_selector "th", :text => "PRODUCER" @@ -565,7 +573,7 @@ feature %q{ expect(page).to have_selector "th", :text => "ON HAND" expect(page).to have_selector "th", :text => "AV. ON" - first("div#columns_dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click + first("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click expect(page).to have_no_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" @@ -688,8 +696,8 @@ feature %q{ v = p.variants.first visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index c84f8f35ed..5d2ffedb4e 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -39,8 +39,8 @@ feature 'Customers' do # Toggling columns expect(page).to have_selector "th.email" expect(page).to have_content customer1.email - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Email").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Email").click expect(page).to_not have_selector "th.email" expect(page).to_not have_content customer1.email end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 2aba4f1a71..aafaa5ee77 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -102,6 +102,7 @@ feature %q{ # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' + fill_in 'order_cycle_incoming_exchange_0_receival_instructions', with: 'receival instructions' page.find('table.exchanges tr.supplier td.products input').click check "order_cycle_incoming_exchange_0_variants_#{v1.id}" check "order_cycle_incoming_exchange_0_variants_#{v2.id}" @@ -157,8 +158,11 @@ feature %q{ oc.exchanges.first.variants.count.should == 2 oc.exchanges.last.variants.count.should == 2 - # And my pickup time and instructions should have been saved - exchange = oc.exchanges.where(:sender_id => oc.coordinator_id).first + # And my receival and pickup time and instructions should have been saved + exchange = oc.exchanges.incoming.first + exchange.receival_instructions.should == 'receival instructions' + + exchange = oc.exchanges.outgoing.first exchange.pickup_time.should == 'pickup time' exchange.pickup_instructions.should == 'pickup instructions' end @@ -188,6 +192,9 @@ feature %q{ page.should have_selector 'td.supplier_name', :text => oc.suppliers.first.name page.should have_selector 'td.supplier_name', :text => oc.suppliers.last.name + page.should have_field 'order_cycle_incoming_exchange_0_receival_instructions', with: 'instructions 0' + page.should have_field 'order_cycle_incoming_exchange_1_receival_instructions', with: 'instructions 1' + # And the suppliers should have products page.all('table.exchanges tbody tr.supplier').each do |row| row.find('td.products input').click diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index ce0dea54c8..725495b74b 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -192,7 +192,7 @@ feature %q{ order = create(:completed_order_with_totals, distributor: distributor1) visit spree.admin_order_path(order) - find("#links-dropdown .ofn_drop_down").click + find("#links-dropdown .ofn-drop-down").click within "#links-dropdown" do expect(page).to have_link "Edit", href: spree.edit_admin_order_path(order) expect(page).to have_link "Resend Confirmation", href: spree.resend_admin_order_path(order) diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index ee615f44f5..378afa3626 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -26,14 +26,6 @@ feature %q{ page.should have_select2 'hub_id', options: ['', hub.name, hub2.name] end - - it "displays the hub" do - visit '/admin/variant_overrides' - select2_select hub.name, from: 'hub_id' - click_button 'Go' - - page.should have_selector 'h2', text: hub.name - end end context "when a hub is selected" do @@ -59,29 +51,60 @@ feature %q{ before do visit '/admin/variant_overrides' select2_select hub.name, from: 'hub_id' - click_button 'Go' end it "displays the list of products with variants" do page.should have_table_row ['PRODUCER', 'PRODUCT', 'PRICE', 'ON HAND'] - page.should have_table_row [producer.name, product.name, '', ''] page.should have_input "variant-overrides-#{variant.id}-price", placeholder: '1.23' - page.should have_input "variant-overrides-#{variant.id}-count-on-hand", placeholder: '12' + page.should have_input "variant-overrides-#{variant.id}-count_on_hand", placeholder: '12' page.should have_table_row [producer_related.name, product_related.name, '', ''] page.should have_input "variant-overrides-#{variant_related.id}-price", placeholder: '2.34' - page.should have_input "variant-overrides-#{variant_related.id}-count-on-hand", placeholder: '23' - end + page.should have_input "variant-overrides-#{variant_related.id}-count_on_hand", placeholder: '23' - it "filters the products to those the hub can override" do + # filters the products to those the hub can override page.should_not have_content producer_unrelated.name page.should_not have_content product_unrelated.name + + # Filters based on the producer select filter + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" + select2_select producer.name, from: 'producer_filter' + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to_not have_selector "#v_#{variant_related.id}" + select2_select 'All', from: 'producer_filter' + + # Filters based on the quick search box + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" + fill_in 'query', with: product.name + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to_not have_selector "#v_#{variant_related.id}" + fill_in 'query', with: '' + + # Clears the filters + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" + select2_select producer.name, from: 'producer_filter' + fill_in 'query', with: product_related.name + expect(page).to_not have_selector "#v_#{variant.id}" + expect(page).to_not have_selector "#v_#{variant_related.id}" + click_button 'Clear All' + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" end it "creates new overrides" do + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click + first("div#columns-dropdown", :text => "COLUMNS").click + + fill_in "variant-overrides-#{variant.id}-sku", with: 'NEWSKU' fill_in "variant-overrides-#{variant.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' + check "variant-overrides-#{variant.id}-on_demand" page.should have_content "Changes to one override remain unsaved." expect do @@ -92,15 +115,17 @@ feature %q{ vo = VariantOverride.last vo.variant_id.should == variant.id vo.hub_id.should == hub.id + vo.sku.should == "NEWSKU" vo.price.should == 777.77 vo.count_on_hand.should == 123 + vo.on_demand.should == true end describe "creating and then updating the new override" do it "updates the same override instead of creating a duplicate" do # When I create a new override fill_in "variant-overrides-#{variant.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." expect do @@ -110,7 +135,7 @@ feature %q{ # And I update its settings without reloading the page fill_in "variant-overrides-#{variant.id}-price", with: '111.11' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '111' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '111' page.should have_content "Changes to one override remain unsaved." # Then I shouldn't see a new override @@ -130,7 +155,7 @@ feature %q{ it "displays an error when unauthorised to access the page" do fill_in "variant-overrides-#{variant.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." user.enterprises.clear @@ -143,7 +168,7 @@ feature %q{ it "displays an error when unauthorised to update a particular override" do fill_in "variant-overrides-#{variant_related.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant_related.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant_related.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." er2.destroy @@ -156,23 +181,27 @@ feature %q{ end context "with overrides" do - let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111) } + let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111, default_stock: 1000, resettable: true) } let!(:vo_no_auth) { create(:variant_override, variant: variant, hub: hub3, price: 1, count_on_hand: 2) } + let!(:product2) { create(:simple_product, supplier: producer, variant_unit: 'weight', variant_unit_scale: 1) } + let!(:variant2) { create(:variant, product: product2, unit_value: 8, price: 1.00, on_hand: 12) } + let!(:vo_no_reset) { create(:variant_override, variant: variant2, hub: hub, price: 3.99, count_on_hand: 40, default_stock: 100, resettable: false) } + let!(:variant3) { create(:variant, product: product, unit_value: 2, price: 5.00, on_hand: 6) } + let!(:vo3) { create(:variant_override, variant: variant3, hub: hub, price: 6, count_on_hand: 7, sku: "SOMESKU", default_stock: 100, resettable: false) } before do visit '/admin/variant_overrides' select2_select hub.name, from: 'hub_id' - click_button 'Go' end it "product values are affected by overrides" do page.should have_input "variant-overrides-#{variant.id}-price", with: '77.77', placeholder: '1.23' - page.should have_input "variant-overrides-#{variant.id}-count-on-hand", with: '11111', placeholder: '12' + page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '11111', placeholder: '12' end it "updates existing overrides" do fill_in "variant-overrides-#{variant.id}-price", with: '22.22' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '8888' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '8888' page.should have_content "Changes to one override remain unsaved." expect do @@ -187,17 +216,54 @@ feature %q{ vo.count_on_hand.should == 8888 end + # Any new fields added to the VO model need to be added to this test it "deletes overrides when values are cleared" do + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click + first("div#columns-dropdown div.menu div.menu_item", text: "Reset Stock Level").click + first("div#columns-dropdown", :text => "COLUMNS").click + + # Clearing values manually fill_in "variant-overrides-#{variant.id}-price", with: '' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '' + fill_in "variant-overrides-#{variant.id}-default_stock", with: '' + page.uncheck "variant-overrides-#{variant.id}-resettable" page.should have_content "Changes to one override remain unsaved." + # Clearing values by 'inheriting' + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Inheritance").click + first("div#columns-dropdown", :text => "COLUMNS").click + page.check "variant-overrides-#{variant3.id}-inherit" + expect do click_button 'Save Changes' page.should have_content "Changes saved." - end.to change(VariantOverride, :count).by(-1) + end.to change(VariantOverride, :count).by(-2) VariantOverride.where(id: vo.id).should be_empty + VariantOverride.where(id: vo3.id).should be_empty + end + + it "resets stock to defaults" do + click_button 'Reset Stock to Defaults' + page.should have_content 'Stocks reset to defaults.' + vo.reload + page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '1000', placeholder: '12' + vo.count_on_hand.should == 1000 + end + + it "doesn't reset stock levels if the behaviour is disabled" do + click_button 'Reset Stock to Defaults' + vo_no_reset.reload + page.should have_input "variant-overrides-#{variant2.id}-count_on_hand", with: '40', placeholder: '12' + vo_no_reset.count_on_hand.should == 40 + end + + it "prompts to save changes before reset if any are pending" do + fill_in "variant-overrides-#{variant.id}-price", with: '200' + click_button 'Reset Stock to Defaults' + page.should have_content "Save changes first" end end end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 12e9d8323b..e81a792881 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -187,7 +187,7 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path end - it "should save group buy data to the cart" do + it "should save group buy data to the cart and display it on shopfront reload" do # -- Quantity fill_in "variants[#{variant.id}]", with: 6 page.should have_in_cart product.name @@ -202,6 +202,11 @@ feature "As a consumer I want to shop with a distributor", js: true do li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last li.max_quantity.should == 7 + + # -- Reload + visit shop_path + page.should have_field "variants[#{variant.id}]", with: 6 + page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: 7 end end end diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index f850436e52..92945a60be 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -22,12 +22,12 @@ feature "shopping with variant overrides defined", js: true do let(:v4) { create(:variant, product: p1, price: 44.44, unit_value: 4) } let(:v5) { create(:variant, product: p3, price: 55.55, unit_value: 5, on_demand: true) } let(:v6) { create(:variant, product: p3, price: 66.66, unit_value: 6, on_demand: true) } - let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil) } - let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0) } - let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0) } - let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3) } - let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0) } - let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6) } + let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil, default_stock: nil, resettable: false) } + let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0, default_stock: nil, resettable: false) } + let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0, default_stock: nil, resettable: false) } + let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3, default_stock: nil, resettable: false) } + let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0, default_stock: nil, resettable: false) } + let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6, default_stock: nil, resettable: false) } let(:ef) { create(:enterprise_fee, enterprise: hub, fee_type: 'packing', calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)) } before do @@ -143,7 +143,6 @@ feature "shopping with variant overrides defined", js: true do it "does not subtract stock from overrides that do not override count_on_hand" do fill_in "variants[#{v1.id}]", with: "2" click_checkout - expect do complete_checkout end.to change { v1.reload.count_on_hand }.by(-2) @@ -192,7 +191,7 @@ feature "shopping with variant overrides defined", js: true do within "#payment" do choose pm.name end - + place_order page.should have_content "Your order has been processed successfully" end diff --git a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee index bdb62e8d37..699e1bc4ab 100644 --- a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee @@ -1,29 +1,39 @@ describe "VariantOverridesCtrl", -> ctrl = null - scope = null + scope = {} hubs = [{id: 1, name: 'Hub'}] producers = [{id: 2, name: 'Producer'}] products = [{id: 1, name: 'Product'}] hubPermissions = {} VariantOverrides = null variantOverrides = {} + DirtyVariantOverrides = null + dirtyVariantOverrides = {} + StatusMessage = null + statusMessage = {} beforeEach -> - module 'ofn.admin' + module 'admin.variantOverrides' module ($provide) -> $provide.value 'SpreeApiKey', 'API_KEY' $provide.value 'variantOverrides', variantOverrides + $provide.value 'dirtyVariantOverrides', dirtyVariantOverrides null - scope = {} - inject ($controller, Indexer, _VariantOverrides_) -> + inject ($controller, _VariantOverrides_, _DirtyVariantOverrides_, _StatusMessage_) -> VariantOverrides = _VariantOverrides_ - ctrl = $controller 'AdminVariantOverridesCtrl', {$scope: scope, Indexer: Indexer, hubs: hubs, producers: producers, products: products, hubPermissions: hubPermissions, VariantOverrides: _VariantOverrides_} + DirtyVariantOverrides = _DirtyVariantOverrides_ + StatusMessage = _StatusMessage_ + ctrl = $controller 'AdminVariantOverridesCtrl', { $scope: scope, hubs: hubs, producers: producers, products: products, hubPermissions: hubPermissions, VariantOverrides: VariantOverrides, DirtyVariantOverrides: DirtyVariantOverrides, StatusMessage: StatusMessage} it "initialises the hub list and the chosen hub", -> - expect(scope.hubs).toEqual hubs + expect(scope.hubs).toEqual { 1: {id: 1, name: 'Hub'} } expect(scope.hub).toBeNull() + it "initialises select filters", -> + expect(scope.producerFilter).toEqual 0 + expect(scope.query).toEqual '' + it "adds products", -> spyOn(VariantOverrides, "ensureDataFor") expect(scope.products).toEqual [] @@ -54,4 +64,23 @@ describe "VariantOverridesCtrl", -> expect(scope.updateError(data, 400)).toEqual "I had some trouble saving: Hub can't be blank, Variant can't be blank" it "returns a generic message otherwise", -> - expect(scope.updateError({}, 500)).toEqual "Oh no! I was unable to save your changes." \ No newline at end of file + expect(scope.updateError({}, 500)).toEqual "Oh no! I was unable to save your changes." + + describe "setting stock to defaults", -> + it "prompts to save changes if there are any pending", -> + spyOn(StatusMessage, "display") + DirtyVariantOverrides.add {hub_id: 1, variant_id: 1} + scope.resetStock() + expect(StatusMessage.display).toHaveBeenCalledWith 'alert', 'Save changes first.' + + it "updates and refreshes on hand value for variant overrides with a default stock level", inject ($httpBackend) -> + scope.hub_id = 123 + variant_overrides_mock = "mock object" + spyOn(StatusMessage, "display") + spyOn(VariantOverrides, "updateData") + $httpBackend.expectPOST("/admin/variant_overrides/bulk_reset", hub_id: 123).respond 200, variant_overrides_mock + scope.resetStock() + expect(StatusMessage.display).toHaveBeenCalledWith 'progress', 'Changing on hand stock levels...' + $httpBackend.flush() + expect(VariantOverrides.updateData).toHaveBeenCalledWith variant_overrides_mock + expect(StatusMessage.display).toHaveBeenCalledWith 'success', 'Stocks reset to defaults.' diff --git a/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee b/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee index 653560a989..553bba5713 100644 --- a/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee @@ -7,7 +7,7 @@ describe "maintaining a list of dirty variant overrides", -> count_on_hand: 4 beforeEach -> - module "ofn.admin" + module "admin.variantOverrides" beforeEach inject (_DirtyVariantOverrides_) -> DirtyVariantOverrides = _DirtyVariantOverrides_ diff --git a/spec/javascripts/unit/admin/services/indexer_spec.js.coffee b/spec/javascripts/unit/admin/services/indexer_spec.js.coffee index f17f8bd83c..22f263e02b 100644 --- a/spec/javascripts/unit/admin/services/indexer_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/indexer_spec.js.coffee @@ -2,7 +2,7 @@ describe "indexer", -> Indexer = null beforeEach -> - module "ofn.admin" + module "admin.indexUtils" beforeEach inject (_Indexer_) -> Indexer = _Indexer_ diff --git a/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee b/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee index 532bb1d65c..73dbdabee8 100644 --- a/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee @@ -1,27 +1,28 @@ describe "VariantOverrides service", -> - VariantOverrides = null + VariantOverrides = $httpBackend = null variantOverrides = [ - {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1} - {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2} - {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3} + {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } + {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false} + {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false} ] beforeEach -> - module "ofn.admin" + module "admin.variantOverrides" module ($provide) -> $provide.value "variantOverrides", variantOverrides null - beforeEach inject (_VariantOverrides_) -> + beforeEach inject (_VariantOverrides_, _$httpBackend_) -> VariantOverrides = _VariantOverrides_ + $httpBackend = _$httpBackend_ it "indexes variant overrides by hub_id -> variant_id", -> expect(VariantOverrides.variantOverrides).toEqual 10: - 100: {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1} - 200: {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2} + 100: {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } + 200: {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false } 20: - 300: {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3} + 300: {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false } it "ensures blank data available for some products", -> hubs = [{id: 10}, {id: 20}, {id: 30}] @@ -32,37 +33,50 @@ describe "VariantOverrides service", -> } ] VariantOverrides.ensureDataFor hubs, products - expect(VariantOverrides.variantOverrides).toEqual - 10: - 100: {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1} - 200: {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2} - 300: { hub_id: 10, variant_id: 300, price: '', count_on_hand: ''} - 400: { hub_id: 10, variant_id: 400, price: '', count_on_hand: ''} - 500: { hub_id: 10, variant_id: 500, price: '', count_on_hand: ''} - 20: - 100: { hub_id: 20, variant_id: 100, price: '', count_on_hand: ''} - 200: { hub_id: 20, variant_id: 200, price: '', count_on_hand: ''} - 300: {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3} - 400: { hub_id: 20, variant_id: 400, price: '', count_on_hand: ''} - 500: { hub_id: 20, variant_id: 500, price: '', count_on_hand: ''} - 30: - 100: { hub_id: 30, variant_id: 100, price: '', count_on_hand: ''} - 200: { hub_id: 30, variant_id: 200, price: '', count_on_hand: ''} - 300: { hub_id: 30, variant_id: 300, price: '', count_on_hand: ''} - 400: { hub_id: 30, variant_id: 400, price: '', count_on_hand: ''} - 500: { hub_id: 30, variant_id: 500, price: '', count_on_hand: ''} + expect(VariantOverrides.variantOverrides[10]).toEqual + 100: { id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } + 200: { id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false } + 300: { hub_id: 10, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 400: { hub_id: 10, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 500: { hub_id: 10, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + expect(VariantOverrides.variantOverrides[20]).toEqual + 100: { hub_id: 20, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 200: { hub_id: 20, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 300: { id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false } + 400: { hub_id: 20, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 500: { hub_id: 20, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + expect(VariantOverrides.variantOverrides[30]).toEqual + 100: { hub_id: 30, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 200: { hub_id: 30, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 300: { hub_id: 30, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 400: { hub_id: 30, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 500: { hub_id: 30, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } it "updates the IDs of variant overrides", -> VariantOverrides.variantOverrides[2] = {} - VariantOverrides.variantOverrides[2][3] = {hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5} - VariantOverrides.variantOverrides[2][8] = {hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10} + VariantOverrides.variantOverrides[2][3] = {hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: '', resettable: false} + VariantOverrides.variantOverrides[2][8] = {hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} updatedVos = [ - {id: 1, hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5} - {id: 6, hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10} + {id: 1, hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: '', resettable: false} + {id: 6, hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} ] VariantOverrides.updateIds updatedVos expect(VariantOverrides.variantOverrides[2][3].id).toEqual 1 expect(VariantOverrides.variantOverrides[2][8].id).toEqual 6 + + it "updates the variant overrides on the page with new data", -> + VariantOverrides.variantOverrides[1] = + 3: {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: 3, resettable: true} + 8: {id: 2, hub_id: 1, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} + # Updated count on hand to 3 + updatedVos = [ + {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 3, default_stock: 3, resettable: true} + ] + + VariantOverrides.updateData(updatedVos) + expect(VariantOverrides.variantOverrides[1]).toEqual + 3: {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 3, default_stock: 3, resettable: true} + 8: {id: 2, hub_id: 1, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 0519b59763..518a524010 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -48,9 +48,52 @@ describe 'Cart service', -> order.line_items[0].quantity = 2 expect(Cart.total_item_count()).toEqual 2 + describe "triggering cart updates", -> + it "schedules an update when there's no update running", -> + Cart.update_running = false + Cart.update_enqueued = false + spyOn(Cart, 'scheduleUpdate') + spyOn(Cart, 'unsaved') + Cart.orderChanged() + expect(Cart.scheduleUpdate).toHaveBeenCalled() + + it "enqueues an update when there's already an update running", -> + Cart.update_running = true + Cart.update_enqueued = false + spyOn(Cart, 'scheduleUpdate') + spyOn(Cart, 'unsaved') + Cart.orderChanged() + expect(Cart.scheduleUpdate).not.toHaveBeenCalled() + expect(Cart.update_enqueued).toBe(true) + + it "does nothing when there's already an update enqueued", -> + Cart.update_running = true + Cart.update_enqueued = true + spyOn(Cart, 'scheduleUpdate') + spyOn(Cart, 'unsaved') + Cart.orderChanged() + expect(Cart.scheduleUpdate).not.toHaveBeenCalled() + expect(Cart.update_enqueued).toBe(true) + describe "updating the cart", -> data = {variants: {}} + it "sets update_running during the update, and clears it on success", -> + $httpBackend.expectPOST("/orders/populate", data).respond 200, {} + expect(Cart.update_running).toBe(false) + Cart.update() + expect(Cart.update_running).toBe(true) + $httpBackend.flush() + expect(Cart.update_running).toBe(false) + + it "sets update_running during the update, and clears it on failure", -> + $httpBackend.expectPOST("/orders/populate", data).respond 404, {} + expect(Cart.update_running).toBe(false) + Cart.update() + expect(Cart.update_running).toBe(true) + $httpBackend.flush() + expect(Cart.update_running).toBe(false) + it "marks the form as saved on success", -> spyOn(Cart, 'saved') $httpBackend.expectPOST("/orders/populate", data).respond 200, {} @@ -58,6 +101,24 @@ describe 'Cart service', -> $httpBackend.flush() expect(Cart.saved).toHaveBeenCalled() + it "runs enqueued updates after success", -> + Cart.update_enqueued = true + spyOn(Cart, 'saved') + spyOn(Cart, 'popQueue') + $httpBackend.expectPOST("/orders/populate", data).respond 200, {} + Cart.update() + $httpBackend.flush() + expect(Cart.popQueue).toHaveBeenCalled() + + it "doesn't run an update if it's not enqueued", -> + Cart.update_enqueued = false + spyOn(Cart, 'saved') + spyOn(Cart, 'popQueue') + $httpBackend.expectPOST("/orders/populate", data).respond 200, {} + Cart.update() + $httpBackend.flush() + expect(Cart.popQueue).not.toHaveBeenCalled() + it "retries the update on failure", -> spyOn(Cart, 'scheduleRetry') $httpBackend.expectPOST("/orders/populate", data).respond 404, {} @@ -65,6 +126,13 @@ describe 'Cart service', -> $httpBackend.flush() expect(Cart.scheduleRetry).toHaveBeenCalled() + it "pops the queue", -> + Cart.update_enqueued = true + spyOn(Cart, 'scheduleUpdate') + Cart.popQueue() + expect(Cart.update_enqueued).toBe(false) + expect(Cart.scheduleUpdate).toHaveBeenCalled() + it "schedules retries of updates", -> spyOn(Cart, 'orderChanged') Cart.scheduleRetry() diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index 2a249f9c7a..82b24c7e02 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -3,8 +3,8 @@ require 'open_food_network/scope_variant_to_hub' module OpenFoodNetwork describe ScopeVariantToHub do let(:hub) { create(:distributor_enterprise) } - let(:v) { create(:variant, price: 11.11, count_on_hand: 1) } - let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2) } + let(:v) { create(:variant, price: 11.11, count_on_hand: 1, on_demand: true, sku: "VARIANTSKU") } + let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2, on_demand: false, sku: "VOSKU") } let(:vo_price_only) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: nil) } let(:scoper) { ScopeVariantToHub.new(hub) } @@ -66,6 +66,75 @@ module OpenFoodNetwork v.on_demand.should be_true end end + + describe "overriding on_demand" do + context "when an override exists" do + before { vo } + + context "with an on_demand set" do + it "returns the overridden on_demand" do + scoper.scope v + expect(v.on_demand).to be_false + end + end + + context "without an on_demand set" do + before { vo.update_column(:on_demand, nil) } + + context "when count_on_hand is set" do + it "returns false" do + scoper.scope v + expect(v.on_demand).to be_false + end + end + + context "when count_on_hand is not set" do + before { vo.update_column(:count_on_hand, nil) } + + it "returns the variant's on_demand" do + scoper.scope v + expect(v.on_demand).to be_true + end + end + end + end + + context "when no override exists" do + it "returns the variant's on_demand" do + scoper.scope v + expect(v.on_demand).to be_true + end + end + end + + describe "overriding sku" do + context "when an override exists" do + before { vo } + + context "with an sku set" do + it "returns the overridden sku" do + scoper.scope v + expect(v.sku).to eq "VOSKU" + end + end + + context "without an sku set" do + before { vo.update_column(:sku, nil) } + + it "returns the variant's sku" do + scoper.scope v + expect(v.sku).to eq "VARIANTSKU" + end + end + end + + context "when no override exists" do + it "returns the variant's sku" do + scoper.scope v + expect(v.sku).to eq "VARIANTSKU" + end + end + end end end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 554d427377..90465fb80e 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -35,12 +35,14 @@ describe OrderCycle do oc_not_yet_open = create(:simple_order_cycle, orders_open_at: 1.week.from_now, orders_close_at: 2.weeks.from_now) oc_already_closed = create(:simple_order_cycle, orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago) oc_undated = create(:simple_order_cycle, orders_open_at: nil, orders_close_at: nil) + oc_undated_open = create(:simple_order_cycle, orders_open_at: 1.week.ago, orders_close_at: nil) + oc_undated_close = create(:simple_order_cycle, orders_open_at: nil, orders_close_at: 1.week.from_now) OrderCycle.active.should == [oc_active] OrderCycle.inactive.should match_array [oc_not_yet_open, oc_already_closed] OrderCycle.upcoming.should == [oc_not_yet_open] OrderCycle.closed.should == [oc_already_closed] - OrderCycle.undated.should == [oc_undated] + OrderCycle.undated.should == [oc_undated, oc_undated_open, oc_undated_close] end it "finds order cycles accessible by a user" do @@ -356,6 +358,24 @@ describe OrderCycle do oc.should_not be_open oc.should_not be_closed end + + it "reports status when an order cycle is partially dated - opening time only" do + oc.update_attributes!(orders_close_at: nil) + + oc.should be_undated + oc.should_not be_upcoming + oc.should_not be_open + oc.should_not be_closed + end + + it "reports status when an order cycle is partially dated - closing time only" do + oc.update_attributes!(orders_open_at: nil) + + oc.should be_undated + oc.should_not be_upcoming + oc.should_not be_open + oc.should_not be_closed + end end it "clones itself" do diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 6b60707775..4af3d233b1 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -323,7 +323,7 @@ module Spree let!(:er1) { create(:enterprise_relationship, parent: s1, child: d1, permissions_list: [:create_variant_overrides]) } it "should be able to access variant overrides page" do - should have_ability([:admin, :index, :bulk_update], for: VariantOverride) + should have_ability([:admin, :index, :bulk_update, :bulk_reset], for: VariantOverride) end it "should be able to read/write their own variant overrides" do diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index e50a64a4d9..4d681e01cc 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -54,6 +54,7 @@ describe Spree::Order do product_distribution.should_receive(:create_adjustment_for).with(line_item) subject.stub(:product_distribution_for) { product_distribution } + subject.update_distribution_charge! end diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index e9f22a33be..ed2f2c00f4 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -49,16 +49,16 @@ describe VariantOverride do describe "checking if stock levels have been overriden" do it "returns true when stock level has been overridden" do create(:variant_override, variant: variant, hub: hub, count_on_hand: 12) - VariantOverride.stock_overridden?(hub, variant).should be_true + VariantOverride.stock_overridden?(hub, variant).should be true end it "returns false when the override has no stock level" do create(:variant_override, variant: variant, hub: hub, count_on_hand: nil) - VariantOverride.stock_overridden?(hub, variant).should be_false + VariantOverride.stock_overridden?(hub, variant).should be false end it "returns false when there is no override for the hub/variant" do - VariantOverride.stock_overridden?(hub, variant).should be_false + VariantOverride.stock_overridden?(hub, variant).should be false end end @@ -69,16 +69,40 @@ describe VariantOverride do vo.reload.count_on_hand.should == 10 end - it "silently logs an error if the variant override doesn't have a stock level" do - vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: nil) - Bugsnag.should_receive(:notify) - VariantOverride.decrement_stock! hub, variant, 2 - vo.reload.count_on_hand.should be_nil - end - it "silently logs an error if the variant override does not exist" do Bugsnag.should_receive(:notify) VariantOverride.decrement_stock! hub, variant, 2 end end + + describe "checking default stock value is present" do + it "returns true when a default stock level has been set" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20) + vo.default_stock?.should be true + end + + it "returns false when the override has no default stock level" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock:nil) + vo.default_stock?.should be false + end + end + + describe "resetting stock levels" do + it "resets the on hand level to the value in the default_stock field" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20, resettable: true) + vo.reset_stock! + vo.reload.count_on_hand.should == 20 + end + it "silently logs an error if the variant override doesn't have a default stock level" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock:nil, resettable: true) + Bugsnag.should_receive(:notify) + vo.reset_stock! + vo.reload.count_on_hand.should == 12 + end + it "doesn't reset the level if the behaviour is disabled" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 10, resettable: false) + vo.reset_stock! + vo.reload.count_on_hand.should == 12 + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d9a1e1b262..80d7c2eeb7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,10 @@ require 'rubygems' # Require pry when we're not inside Travis-CI -require 'pry' unless ENV['HAS_JOSH_K_SEAL_OF_APPROVAL'] +require 'pry' unless ENV['CI'] + +require 'knapsack' +Knapsack::Adapters::RSpecAdapter.bind ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) @@ -39,7 +42,7 @@ Capybara.register_driver :poltergeist do |app| Capybara::Poltergeist::Driver.new(app, options) end -Capybara.default_wait_time = 30 +Capybara.default_max_wait_time = 30 require "paperclip/matchers" diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 15c586561c..1dd5adc7a3 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -119,7 +119,7 @@ module WebHelper # Do not use this without good reason. Capybara's built-in waiting is very effective. def wait_until(secs=nil) require "timeout" - Timeout.timeout(secs || Capybara.default_wait_time) do + Timeout.timeout(secs || Capybara.default_max_wait_time) do sleep(0.1) until value = yield value end