From 3f62d82afce5d6fe786dc39fe21d565636d59fa1 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Thu, 20 Dec 2018 16:50:01 +0000 Subject: [PATCH 001/108] Add more inventory testing for visibility in the inventory UI --- spec/models/product_importer_spec.rb | 124 +++++++++++++++++++-------- 1 file changed, 90 insertions(+), 34 deletions(-) diff --git a/spec/models/product_importer_spec.rb b/spec/models/product_importer_spec.rb index f05816e538..b2d1a12d0d 100644 --- a/spec/models/product_importer_spec.rb +++ b/spec/models/product_importer_spec.rb @@ -465,50 +465,106 @@ describe ProductImport::ProductImporter do end describe "importing items into inventory" do - before do - csv_data = CSV.generate do |csv| - csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type"] - csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g"] - csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g"] - csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "500", "g"] + describe "creating and updating inventory" do + before do + csv_data = CSV.generate do |csv| + csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type"] + csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g"] + csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g"] + csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "500", "g"] + end + File.write('/tmp/test-m.csv', csv_data) + file = File.new('/tmp/test-m.csv') + settings = {'import_into' => 'inventories'} + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end - File.write('/tmp/test-m.csv', csv_data) - file = File.new('/tmp/test-m.csv') - settings = {'import_into' => 'inventories'} - @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) - end - after { File.delete('/tmp/test-m.csv') } + after { File.delete('/tmp/test-m.csv') } - it "validates entries" do - @importer.validate_entries - entries = JSON.parse(@importer.entries_json) + it "validates entries" do + @importer.validate_entries + entries = JSON.parse(@importer.entries_json) - expect(filter('valid', entries)).to eq 3 - expect(filter('invalid', entries)).to eq 0 - expect(filter('create_inventory', entries)).to eq 2 - expect(filter('update_inventory', entries)).to eq 1 + expect(filter('valid', entries)).to eq 3 + expect(filter('invalid', entries)).to eq 0 + expect(filter('create_inventory', entries)).to eq 2 + expect(filter('update_inventory', entries)).to eq 1 + end + + it "saves and updates inventory" do + @importer.save_entries + + expect(@importer.inventory_created_count).to eq 2 + expect(@importer.inventory_updated_count).to eq 1 + expect(@importer.updated_ids).to be_a(Array) + expect(@importer.updated_ids.count).to eq 3 + + beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first + sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first + cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first + + expect(Float(beans_override.price)).to eq 3.20 + expect(beans_override.count_on_hand).to eq 5 + + expect(Float(sprouts_override.price)).to eq 6.50 + expect(sprouts_override.count_on_hand).to eq 6 + + expect(Float(cabbage_override.price)).to eq 1.50 + expect(cabbage_override.count_on_hand).to eq 2001 + end end - it "saves and updates inventory" do - @importer.save_entries + describe "updating existing inventory referenced by display_name" do + before do + csv_data = CSV.generate do |csv| + csv << ["name", "display_name", "distributor", "producer", "on_hand", "price", "units"] + csv << ["Oats", "Porridge Oats", "Another Enterprise", "User Enterprise", "900", "", "500"] + end + File.write('/tmp/test-m.csv', csv_data) + file = File.new('/tmp/test-m.csv') + settings = {'import_into' => 'inventories'} + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) + end + after { File.delete('/tmp/test-m.csv') } - expect(@importer.inventory_created_count).to eq 2 - expect(@importer.inventory_updated_count).to eq 1 - expect(@importer.updated_ids).to be_a(Array) - expect(@importer.updated_ids.count).to eq 3 + it "updates inventory item correctly" do + @importer.save_entries - beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first - sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first - cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first + expect(@importer.inventory_created_count).to eq 1 - expect(Float(beans_override.price)).to eq 3.20 - expect(beans_override.count_on_hand).to eq 5 + override = VariantOverride.where(variant_id: variant2.id, hub_id: enterprise2.id).first + visible = InventoryItem.where(variant_id: variant2.id, enterprise_id: enterprise2.id).first.visible - expect(Float(sprouts_override.price)).to eq 6.50 - expect(sprouts_override.count_on_hand).to eq 6 + expect(override.count_on_hand).to eq 900 + expect(visible).to be_truthy + end + end - expect(Float(cabbage_override.price)).to eq 1.50 - expect(cabbage_override.count_on_hand).to eq 2001 + describe "updating existing item that was set to hidden in inventory" do + before do + InventoryItem.create(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id, visible: false) + + csv_data = CSV.generate do |csv| + csv << ["name", "distributor", "producer", "on_hand", "price", "units"] + csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "500"] + end + File.write('/tmp/test-m.csv', csv_data) + file = File.new('/tmp/test-m.csv') + settings = {'import_into' => 'inventories'} + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) + end + after { File.delete('/tmp/test-m.csv') } + + it "sets the item to visible in inventory when the item is updated" do + @importer.save_entries + + expect(@importer.inventory_updated_count).to eq 1 + + override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first + visible = InventoryItem.where(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id).first.visible + + expect(override.count_on_hand).to eq 900 + expect(visible).to be_truthy + end end end From 7dc7ffe1b7265def9969757908680619e8118211 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Thu, 20 Dec 2018 15:35:57 +0000 Subject: [PATCH 002/108] Update inventory template --- public/inventory_template.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/inventory_template.csv b/public/inventory_template.csv index d52cecded6..dc2f10e3be 100644 --- a/public/inventory_template.csv +++ b/public/inventory_template.csv @@ -1 +1 @@ -producer,distributor,name,display_name,units,unit_type,price,on_hand +producer,distributor,name,display_name,variant_unit_name,sku,units,unit_type,price,on_hand,on_demand From 79dce01aef197ab32a78b2c7e0db46c3aec3db96 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Fri, 11 Jan 2019 13:07:49 +0000 Subject: [PATCH 003/108] Add spec for new on_demand logic --- spec/features/admin/product_import_spec.rb | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/spec/features/admin/product_import_spec.rb b/spec/features/admin/product_import_spec.rb index 3a337d93dd..edfc738602 100644 --- a/spec/features/admin/product_import_spec.rb +++ b/spec/features/admin/product_import_spec.rb @@ -293,6 +293,49 @@ feature "Product Import", js: true do expect(page).to have_content 'Cabbage' end end + + it "handles on_demand and on_hand validations with inventory" do + csv_data = CSV.generate do |csv| + csv << ["name", "distributor", "producer", "category", "on_hand", "price", "units", "on_demand"] + csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", nil, "3.20", "500", "true"] + csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "6", "6.50", "500", "false"] + csv << ["Cabbage", "Another Enterprise", "User Enterprise", "Vegetables", nil, "1.50", "500", nil] + end + File.write('/tmp/test.csv', csv_data) + + visit main_app.admin_product_import_path + select2_select I18n.t('admin.product_import.index.inventories'), from: "settings_import_into" + attach_file 'file', '/tmp/test.csv' + click_button 'Upload' + + import_data + + expect(page).to have_selector '.item-count', text: "3" + expect(page).to have_no_selector '.invalid-count' + expect(page).to have_selector '.inv-create-count', text: '2' + expect(page).to have_selector '.inv-update-count', text: '1' + + save_data + + expect(page).to have_selector '.inv-created-count', text: '2' + expect(page).to have_selector '.inv-updated-count', text: '1' + + beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first + sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first + cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first + + expect(Float(beans_override.price)).to eq 3.20 + expect(beans_override.count_on_hand).to be_nil + expect(beans_override.on_demand).to be_truthy + + expect(Float(sprouts_override.price)).to eq 6.50 + expect(sprouts_override.count_on_hand).to eq 6 + expect(sprouts_override.on_demand).to eq false + + expect(Float(cabbage_override.price)).to eq 1.50 + expect(cabbage_override.count_on_hand).to be_nil + expect(cabbage_override.on_demand).to be_nil + end end describe "when dealing with uploaded files" do From a3b9936f3736efed6711dc1fe6bc7f41cb988e66 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 13 Jan 2019 21:42:29 +0000 Subject: [PATCH 004/108] Improve matching inventory using 'items' unit type --- app/models/product_import/entry_validator.rb | 2 +- spec/models/product_importer_spec.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/product_import/entry_validator.rb b/app/models/product_import/entry_validator.rb index 11c0aab7dd..7582330a0a 100644 --- a/app/models/product_import/entry_validator.rb +++ b/app/models/product_import/entry_validator.rb @@ -189,7 +189,7 @@ module ProductImport products.flat_map(&:variants).each do |existing_variant| unit_scale = existing_variant.product.variant_unit_scale unscaled_units = entry.unscaled_units || 0 - entry.unit_value = unscaled_units * unit_scale + entry.unit_value = unscaled_units * unit_scale unless unit_scale.nil? if entry_matches_existing_variant?(entry, existing_variant) variant_override = create_inventory_item(entry, existing_variant) diff --git a/spec/models/product_importer_spec.rb b/spec/models/product_importer_spec.rb index b2d1a12d0d..7a259f53fe 100644 --- a/spec/models/product_importer_spec.rb +++ b/spec/models/product_importer_spec.rb @@ -26,7 +26,7 @@ describe ProductImport::ProductImporter do let!(:variant) { create(:variant, product_id: product.id, price: '8.50', on_hand: '100', unit_value: '500', display_name: 'Preexisting Banana') } let!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans', unit_value: '500', primary_taxon_id: category.id, description: nil) } let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts', unit_value: '500', primary_taxon_id: category.id) } - let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '500', primary_taxon_id: category.id) } + let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '1', variant_unit_scale: nil, variant_unit: "items", variant_unit_name: "Whole", primary_taxon_id: category.id) } let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500', primary_taxon_id: category.id) } let!(:product6) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Beetroot', unit_value: '500', on_demand: true, variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) } let!(:product7) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Tomato', unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) } @@ -468,10 +468,10 @@ describe ProductImport::ProductImporter do describe "creating and updating inventory" do before do csv_data = CSV.generate do |csv| - csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type"] - csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g"] - csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g"] - csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "500", "g"] + csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type", "variant_unit_name"] + csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g", ""] + csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g", ""] + csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "1", "", "Whole"] end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') @@ -544,8 +544,8 @@ describe ProductImport::ProductImporter do InventoryItem.create(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id, visible: false) csv_data = CSV.generate do |csv| - csv << ["name", "distributor", "producer", "on_hand", "price", "units"] - csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "500"] + csv << ["name", "distributor", "producer", "on_hand", "price", "units", "variant_unit_name"] + csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "1", "Whole"] end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') From 990b90009eb2e9ce5f2c94c964f1ed5a986dcaec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 18 Jan 2019 19:17:05 +0000 Subject: [PATCH 005/108] Bump stripe from 3.3.2 to 4.5.0 Bumps [stripe](https://github.com/stripe/stripe-ruby) from 3.3.2 to 4.5.0. - [Release notes](https://github.com/stripe/stripe-ruby/releases) - [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/stripe/stripe-ruby/compare/v3.3.2...v4.5.0) Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index e36e5cf274..89c5c2dc4b 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ gem 'spree_auth_devise', github: 'openfoodfoundation/spree_auth_devise', branch: # - Change type of password from string to password to hide it in the form gem 'spree_paypal_express', github: "openfoodfoundation/better_spree_paypal_express", branch: "spree-upgrade-intermediate" #gem 'spree_paypal_express', github: "spree-contrib/better_spree_paypal_express", branch: "1-3-stable" -gem 'stripe', '~> 3.3.2' +gem 'stripe', '~> 4.5.0' # We need at least this version to have Digicert's root certificate # which is needed for Pin Payments (and possibly others). gem 'activemerchant', '~> 1.78' diff --git a/Gemfile.lock b/Gemfile.lock index 36c333e36d..d0b4e986f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -252,6 +252,7 @@ GEM compass (~> 1.0.0) sass-rails (< 5.1) sprockets (< 4.0) + connection_pool (2.2.2) crack (0.4.3) safe_yaml (~> 1.0.0) css_parser (1.6.0) @@ -540,6 +541,8 @@ GEM multi_xml (0.6.0) multipart-post (2.0.0) nenv (0.3.0) + net-http-persistent (3.0.0) + connection_pool (~> 2.2) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) notiffany (0.1.1) @@ -716,8 +719,9 @@ GEM tilt (~> 1.1, != 1.3.0) state_machine (1.2.0) stringex (1.3.3) - stripe (3.3.2) - faraday (~> 0.9) + stripe (4.5.0) + faraday (~> 0.13) + net-http-persistent (~> 3.0) therubyracer (0.12.0) libv8 (~> 3.16.14.0) ref @@ -853,7 +857,7 @@ DEPENDENCIES spree_paypal_express! spring (= 1.7.2) spring-commands-rspec - stripe (~> 3.3.2) + stripe (~> 4.5.0) therubyracer (= 0.12.0) timecop truncate_html From ea7f62123bd4ed0599ec1e9c2a66b210e912a90f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 21 Jan 2019 19:14:44 +0000 Subject: [PATCH 006/108] Bump jwt from 1.5.6 to 2.1.0 Bumps [jwt](https://github.com/jwt/ruby-jwt) from 1.5.6 to 2.1.0. - [Release notes](https://github.com/jwt/ruby-jwt/releases) - [Changelog](https://github.com/jwt/ruby-jwt/blob/master/CHANGELOG.md) - [Commits](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.1.0) Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e36e5cf274..29be48a8de 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'stripe', '~> 3.3.2' gem 'activemerchant', '~> 1.78' gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect -gem 'jwt', '~> 1.5' +gem 'jwt', '~> 2.1' gem 'delayed_job_active_record' gem 'daemons' diff --git a/Gemfile.lock b/Gemfile.lock index d79b08ea13..09c2a0a311 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -508,7 +508,7 @@ GEM json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) - jwt (1.5.6) + jwt (2.1.0) kaminari (0.13.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -813,7 +813,7 @@ DEPENDENCIES jquery-migrate-rails jquery-rails json_spec (~> 1.1.4) - jwt (~> 1.5) + jwt (~> 2.1) knapsack letter_opener (>= 1.4.1) listen (= 3.0.8) From dfa11834e331b22060c5c26f0727d6a3eb2d64d9 Mon Sep 17 00:00:00 2001 From: leandroalemao Date: Thu, 24 Jan 2019 11:16:31 +0000 Subject: [PATCH 007/108] [ISSUE-3348] LC: Fix moment.js deprecation warning --- app/assets/javascripts/darkswarm/filters/dates.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/darkswarm/filters/dates.js.coffee b/app/assets/javascripts/darkswarm/filters/dates.js.coffee index 7b5dd861d5..91efe89d1e 100644 --- a/app/assets/javascripts/darkswarm/filters/dates.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/dates.js.coffee @@ -4,7 +4,7 @@ Darkswarm.filter "date_in_words", -> Darkswarm.filter "sensible_timeframe", (date_in_wordsFilter)-> (date) -> - if moment().add('days', 2) < moment(date) + if moment().add(2, 'days') < moment(date) t 'orders_open' else t('closing') + date_in_wordsFilter(date) From 5610f64c7a859f403da4075f40f6f998727e5717 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Thu, 24 Jan 2019 19:14:52 +0000 Subject: [PATCH 008/108] Bump redcarpet from 3.2.3 to 3.4.0 Bumps [redcarpet](https://github.com/vmg/redcarpet) from 3.2.3 to 3.4.0. - [Release notes](https://github.com/vmg/redcarpet/releases) - [Changelog](https://github.com/vmg/redcarpet/blob/master/CHANGELOG.md) - [Commits](https://github.com/vmg/redcarpet/compare/v3.2.3...v3.4.0) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 41df6b00f7..aba685f8c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -629,7 +629,7 @@ GEM trollop (~> 2.1) rdoc (3.12.2) json (~> 1.4) - redcarpet (3.2.3) + redcarpet (3.4.0) ref (2.0.0) request_store (1.4.1) rack (>= 1.4) From b9492d6483ee251306201f437d22eae1a64e83ca Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 12 Sep 2018 03:36:23 +1000 Subject: [PATCH 009/108] Fix setup in tests for SubscriptionValidator --- spec/services/subscription_validator_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/services/subscription_validator_spec.rb b/spec/services/subscription_validator_spec.rb index 6670d14fa4..64dbc2637b 100644 --- a/spec/services/subscription_validator_spec.rb +++ b/spec/services/subscription_validator_spec.rb @@ -1,3 +1,5 @@ +require "spec_helper" + describe SubscriptionValidator do let(:shop) { instance_double(Enterprise, name: "Shop") } From 28c70c7719f6abd7c987a1d5a7482c8fce213a85 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 18 Jan 2019 19:04:32 +0800 Subject: [PATCH 010/108] Fix nesting of specs for editing subscriptions --- spec/features/admin/subscriptions_spec.rb | 234 +++++++++++----------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index 27bfaa1bfc..6610c006ee 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -285,137 +285,137 @@ feature 'Subscriptions' do expect(subscription_line_item.variant).to eq variant2 expect(subscription_line_item.quantity).to eq 3 end + end - context 'editing an existing subscription' do - let!(:customer) { create(:customer, enterprise: shop) } - let!(:product1) { create(:product, supplier: shop) } - let!(:product2) { create(:product, supplier: shop) } - let!(:product3) { create(:product, supplier: shop) } - let!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) } - let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) } - let!(:variant3) { create(:variant, product: product3, unit_value: '10000', price: 22.00, option_values: []) } - let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) } - let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } - let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant1, variant2], enterprise_fees: [enterprise_fee]) } - let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } - let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } - let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) } - let!(:payment_method) { create(:payment_method, distributors: [shop]) } - let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) } - let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } - let!(:subscription) { - create(:subscription, - shop: shop, - customer: customer, - schedule: schedule, - payment_method: payment_method, - shipping_method: shipping_method, - subscription_line_items: [create(:subscription_line_item, variant: variant1, quantity: 2, price_estimate: 13.75)], - with_proxy_orders: true) - } + context 'editing an existing subscription' do + let!(:customer) { create(:customer, enterprise: shop) } + let!(:product1) { create(:product, supplier: shop) } + let!(:product2) { create(:product, supplier: shop) } + let!(:product3) { create(:product, supplier: shop) } + let!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) } + let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) } + let!(:variant3) { create(:variant, product: product3, unit_value: '10000', price: 22.00, option_values: []) } + let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) } + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant1, variant2], enterprise_fees: [enterprise_fee]) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } + let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) } + let!(:payment_method) { create(:payment_method, distributors: [shop]) } + let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) } + let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } + let!(:subscription) { + create(:subscription, + shop: shop, + customer: customer, + schedule: schedule, + payment_method: payment_method, + shipping_method: shipping_method, + subscription_line_items: [create(:subscription_line_item, variant: variant1, quantity: 2, price_estimate: 13.75)], + with_proxy_orders: true) + } - it "passes the smoke test" do - visit edit_admin_subscription_path(subscription) + it "passes the smoke test" do + visit edit_admin_subscription_path(subscription) - # Customer and Schedule cannot be edited - click_button 'edit-details' - expect(page).to have_selector '#s2id_customer_id.select2-container-disabled' - expect(page).to have_selector '#s2id_schedule_id.select2-container-disabled' + # Customer and Schedule cannot be edited + click_button 'edit-details' + expect(page).to have_selector '#s2id_customer_id.select2-container-disabled' + expect(page).to have_selector '#s2id_schedule_id.select2-container-disabled' - # Can't use a Stripe payment method because customer does not allow it - select2_select stripe_payment_method.name, from: 'payment_method_id' - expect(page).to have_content I18n.t('admin.subscriptions.details.charges_not_allowed') - click_button 'Save Changes' - expect(page).to have_content 'Credit card charges are not allowed by this customer' - select2_select payment_method.name, from: 'payment_method_id' - click_button 'Review' + # Can't use a Stripe payment method because customer does not allow it + select2_select stripe_payment_method.name, from: 'payment_method_id' + expect(page).to have_content I18n.t('admin.subscriptions.details.charges_not_allowed') + click_button 'Save Changes' + expect(page).to have_content 'Credit card charges are not allowed by this customer' + select2_select payment_method.name, from: 'payment_method_id' + click_button 'Review' - # Existing products should be visible - click_button 'edit-products' - within "#sli_0" do - expect(page).to have_selector 'td.description', text: "#{product1.name} - #{variant1.full_name}" - expect(page).to have_selector 'td.price', text: "$13.75" - expect(page).to have_input 'quantity', with: "2" - expect(page).to have_selector 'td.total', text: "$27.50" + # Existing products should be visible + click_button 'edit-products' + within "#sli_0" do + expect(page).to have_selector 'td.description', text: "#{product1.name} - #{variant1.full_name}" + expect(page).to have_selector 'td.price', text: "$13.75" + expect(page).to have_input 'quantity', with: "2" + expect(page).to have_selector 'td.total', text: "$27.50" - # Remove variant1 from the subscription - find("a.delete-item").click - end - - # Attempting to submit without a product - click_button 'Save Changes' - expect(page).to have_content 'Please add at least one product' - - # Add variant2 to the subscription - select2_search_async product2.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' - fill_in 'add_quantity', with: 1 - click_link 'Add' - within "#sli_0" do - expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}" - expect(page).to have_selector 'td.price', text: "$7.75" - expect(page).to have_input 'quantity', with: "1" - expect(page).to have_selector 'td.total', text: "$7.75" - end - - # Total should be $7.75 - expect(page).to have_selector '#order_form_total', text: "$7.75" - - # Add variant3 to the subscription (even though it is not available) - select2_search_async product3.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' - fill_in 'add_quantity', with: 1 - click_link 'Add' - within "#sli_1" do - expect(page).to have_selector 'td.description', text: "#{product3.name} - #{variant3.full_name}" - expect(page).to have_selector 'td.price', text: "$22.00" - expect(page).to have_input 'quantity', with: "1" - expect(page).to have_selector 'td.total', text: "$22.00" - end - - # Total should be $29.75 - expect(page).to have_selector '#order_form_total', text: "$29.75" - - click_button 'Save Changes' - expect(page).to have_content "#{product3.name} - #{variant3.full_name} is not available from the selected schedule" - - # Remove variant3 from the subscription - within '#sli_1' do - find("a.delete-item").click - end - - click_button 'Save Changes' - expect(page).to have_current_path admin_subscriptions_path - - select2_select shop.name, from: "shop_id" - expect(page).to have_selector "td.items.panel-toggle" - first("td.items.panel-toggle").click - - # Total should be $7.75 - expect(page).to have_selector '#order_form_total', text: "$7.75" - expect(page).to have_selector 'tr.item', count: 1 - expect(subscription.reload.subscription_line_items.length).to eq 1 - expect(subscription.subscription_line_items.first.variant).to eq variant2 + # Remove variant1 from the subscription + find("a.delete-item").click end - context "with initialised order that has been changed" do - let(:proxy_order) { subscription.proxy_orders.first } - let(:order) { proxy_order.initialise_order! } - let(:line_item) { order.line_items.first } + # Attempting to submit without a product + click_button 'Save Changes' + expect(page).to have_content 'Please add at least one product' - before { line_item.update_attributes(quantity: 3) } + # Add variant2 to the subscription + select2_search_async product2.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' + fill_in 'add_quantity', with: 1 + click_link 'Add' + within "#sli_0" do + expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}" + expect(page).to have_selector 'td.price', text: "$7.75" + expect(page).to have_input 'quantity', with: "1" + expect(page).to have_selector 'td.total', text: "$7.75" + end - it "reports issues encountered during the update" do - visit edit_admin_subscription_path(subscription) - click_button 'edit-products' + # Total should be $7.75 + expect(page).to have_selector '#order_form_total', text: "$7.75" - within "#sli_0" do - fill_in 'quantity', with: "1" - end + # Add variant3 to the subscription (even though it is not available) + select2_search_async product3.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' + fill_in 'add_quantity', with: 1 + click_link 'Add' + within "#sli_1" do + expect(page).to have_selector 'td.description', text: "#{product3.name} - #{variant3.full_name}" + expect(page).to have_selector 'td.price', text: "$22.00" + expect(page).to have_input 'quantity', with: "1" + expect(page).to have_selector 'td.total', text: "$22.00" + end - click_button 'Save Changes' - expect(page).to have_content 'Saved' + # Total should be $29.75 + expect(page).to have_selector '#order_form_total', text: "$29.75" - expect(page).to have_selector "#order_update_issues_dialog .message", text: I18n.t("admin.subscriptions.order_update_issues_msg") + click_button 'Save Changes' + expect(page).to have_content "#{product3.name} - #{variant3.full_name} is not available from the selected schedule" + + # Remove variant3 from the subscription + within '#sli_1' do + find("a.delete-item").click + end + + click_button 'Save Changes' + expect(page).to have_current_path admin_subscriptions_path + + select2_select shop.name, from: "shop_id" + expect(page).to have_selector "td.items.panel-toggle" + first("td.items.panel-toggle").click + + # Total should be $7.75 + expect(page).to have_selector '#order_form_total', text: "$7.75" + expect(page).to have_selector 'tr.item', count: 1 + expect(subscription.reload.subscription_line_items.length).to eq 1 + expect(subscription.subscription_line_items.first.variant).to eq variant2 + end + + context "with initialised order that has been changed" do + let(:proxy_order) { subscription.proxy_orders.first } + let(:order) { proxy_order.initialise_order! } + let(:line_item) { order.line_items.first } + + before { line_item.update_attributes(quantity: 3) } + + it "reports issues encountered during the update" do + visit edit_admin_subscription_path(subscription) + click_button 'edit-products' + + within "#sli_0" do + fill_in 'quantity', with: "1" end + + click_button 'Save Changes' + expect(page).to have_content 'Saved' + + expect(page).to have_selector "#order_update_issues_dialog .message", text: I18n.t("admin.subscriptions.order_update_issues_msg") end end end From 35c0bcb3df65d4604b3b95beb5fb712583d60098 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 12 Sep 2018 18:35:24 +0800 Subject: [PATCH 011/108] Add tests for eligible variants for a subscription --- spec/services/subscription_validator_spec.rb | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/spec/services/subscription_validator_spec.rb b/spec/services/subscription_validator_spec.rb index 64dbc2637b..c4878414a5 100644 --- a/spec/services/subscription_validator_spec.rb +++ b/spec/services/subscription_validator_spec.rb @@ -463,4 +463,81 @@ describe SubscriptionValidator do end end end + + describe "variant eligibility for subscription" do + let!(:shop) { create(:distributor_enterprise) } + let!(:producer) { create(:supplier_enterprise) } + let!(:product) { create(:product, supplier: producer) } + let!(:variant) { product.variants.first } + + let!(:order_cycle) { current_order_cycle } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + let!(:subscription) { create(:subscription, shop: shop, schedule: schedule) } + let!(:subscription_line_item) do + create(:subscription_line_item, subscription: subscription, variant: variant) + end + + let(:current_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.ago, + orders_close_at: 1.week.from_now) + end + + let(:future_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.from_now, + orders_close_at: 2.weeks.from_now) + end + + let(:past_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.weeks.ago, + orders_close_at: 1.week.ago) + end + + let(:validator) { SubscriptionValidator.new(subscription) } + + context "if it is not in an exchange" do + it "is not eligible" do + expect(validator.__send__(:available_variant_ids)).to_not include(variant.id) + end + end + + context "if it is only in an incoming exchange" do + let!(:incoming_exchange) do + create(:exchange, order_cycle: order_cycle, sender: producer, receiver: shop, + incoming: true, variants: [variant]) + end + + it "is not eligible" do + expect(validator.__send__(:available_variant_ids)).to_not include(variant.id) + end + end + + context "if is an outgoing exchange where the shop is the receiver" do + let!(:outgoing_exchange) do + create(:exchange, order_cycle: order_cycle, sender: shop, receiver: shop, + incoming: false, variants: [variant]) + end + + context "if the order cycle is currently open" do + it "is eligible" do + expect(validator.__send__(:available_variant_ids)).to include(variant.id) + end + end + + context "if the order cycle opens in the future" do + let!(:order_cycle) { future_order_cycle } + + it "is eligible" do + expect(validator.__send__(:available_variant_ids)).to include(variant.id) + end + end + + context "if the order cycle closed in the past" do + let!(:order_cycle) { past_order_cycle } + + it "is not eligible" do + expect(validator.__send__(:available_variant_ids)).to_not include(variant.id) + end + end + end + end end From d3f20289f85e1fd3f4240af3fc9d03a8201768f9 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 12 Sep 2018 22:40:33 +0800 Subject: [PATCH 012/108] Move setup of feature specs for creating subscriptions --- spec/features/admin/subscriptions_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index 6610c006ee..be2eaa6ba5 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -156,12 +156,14 @@ feature 'Subscriptions' do let!(:payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) } let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } - it "passes the smoke test" do + before do visit admin_subscriptions_path - click_link 'New Subscription' - select2_select shop.name, from: 'new_subscription_shop_id' - click_button 'Continue' + click_link "New Subscription" + select2_select shop.name, from: "new_subscription_shop_id" + click_button "Continue" + end + it "passes the smoke test" do select2_select customer.email, from: 'customer_id' select2_select schedule.name, from: 'schedule_id' select2_select payment_method.name, from: 'payment_method_id' From 57f6a7a3b9dffb7a4b3cf7eabf72a111f4804d9c Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 19 Sep 2018 19:01:32 +0800 Subject: [PATCH 013/108] Support clicking different text for select2 helper Interaction with the variant autocomplete is not precise. The specs only search for the product name, then click the first result that matches the product name which they see. This could have been the case because searching using the full variant name does not match the variant. For example, searching "Some Product - 1kg" would not have results, while searching only "Some Product" (the product name) would list "Some Product - 1kg". Clicking the first match does not work in all scenarios. This allows using a separate text for searching and for clicking. --- spec/support/request/web_helper.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index c37014e637..912c19e445 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -105,6 +105,16 @@ module WebHelper targetted_select2(value, options) end + # Support having different texts to search for and to click in the select2 + # field. + # + # This overrides the method in Spree. + def targetted_select2_search(value, options) + page.execute_script %Q{$('#{options[:from]}').select2('open')} + page.execute_script "$('#{options[:dropdown_css]} input.select2-input').val('#{value}').trigger('keyup-change');" + select_select2_result(options[:select_text] || value) + end + def multi_select2_select(value, options) find("#s2id_#{options[:from]}").find('ul li.select2-search-field').click select_select2_result(value) From 929290fc77c34b5f4205a75d9ab8ee09e76158e4 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 20 Jan 2019 17:51:37 +0800 Subject: [PATCH 014/108] Reduce restrictions for creating subscriptions Allow the following variants: * Variants of permitted producers * Variants of hub * Variants that are in outgoing exchanges where the hub is receiver --- .../subscription_controller.js.coffee | 6 +- .../directives/variant_autocomplete.js.coffee | 1 + .../subscription_line_items_controller.rb | 7 +- app/services/subscription_validator.rb | 15 +- .../scope_variants_for_search.rb | 27 +++- lib/open_food_network/subscription_service.rb | 28 ++++ ...subscription_line_items_controller_spec.rb | 4 +- .../admin/subscriptions_controller_spec.rb | 2 +- spec/features/admin/subscriptions_spec.rb | 141 ++++++++++++++---- .../subscription_service_spec.rb | 99 ++++++++++++ spec/services/subscription_validator_spec.rb | 87 +---------- 11 files changed, 296 insertions(+), 121 deletions(-) create mode 100644 lib/open_food_network/subscription_service.rb create mode 100644 spec/lib/open_food_network/subscription_service_spec.rb diff --git a/app/assets/javascripts/admin/subscriptions/controllers/subscription_controller.js.coffee b/app/assets/javascripts/admin/subscriptions/controllers/subscription_controller.js.coffee index 3b26866125..aa635da8cc 100644 --- a/app/assets/javascripts/admin/subscriptions/controllers/subscription_controller.js.coffee +++ b/app/assets/javascripts/admin/subscriptions/controllers/subscription_controller.js.coffee @@ -6,7 +6,11 @@ angular.module("admin.subscriptions").controller "SubscriptionController", ($sco $scope.schedules = Schedules.all $scope.paymentMethods = PaymentMethods.all $scope.shippingMethods = ShippingMethods.all - $scope.distributor_id = $scope.subscription.shop_id # variant selector requires distributor_id + + # Variant selector requires these + $scope.distributor_id = $scope.subscription.shop_id + $scope.eligible_for_subscriptions = true + $scope.view = if $scope.subscription.id? then 'review' else 'details' $scope.nextCallbacks = {} $scope.backCallbacks = {} diff --git a/app/assets/javascripts/admin/utils/directives/variant_autocomplete.js.coffee b/app/assets/javascripts/admin/utils/directives/variant_autocomplete.js.coffee index 9b8f12d3dc..17a467e463 100644 --- a/app/assets/javascripts/admin/utils/directives/variant_autocomplete.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/variant_autocomplete.js.coffee @@ -22,6 +22,7 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) -> q: term distributor_id: scope.distributor_id order_cycle_id: scope.order_cycle_id + eligible_for_subscriptions: scope.eligible_for_subscriptions results: (data, page) -> results: data formatResult: (variant) -> diff --git a/app/controllers/admin/subscription_line_items_controller.rb b/app/controllers/admin/subscription_line_items_controller.rb index d981b71fe1..8c0432578d 100644 --- a/app/controllers/admin/subscription_line_items_controller.rb +++ b/app/controllers/admin/subscription_line_items_controller.rb @@ -1,6 +1,7 @@ require 'open_food_network/permissions' require 'open_food_network/order_cycle_permissions' require 'open_food_network/scope_variant_to_hub' +require "open_food_network/subscription_service" module Admin class SubscriptionLineItemsController < ResourceController @@ -26,7 +27,7 @@ module Admin @shop = Enterprise.managed_by(spree_current_user).find_by_id(params[:shop_id]) @schedule = permissions.editable_schedules.find_by_id(params[:schedule_id]) @order_cycle = @schedule.andand.current_or_next_order_cycle - @variant = Spree::Variant.stockable_by(@shop).find_by_id(params[:subscription_line_item][:variant_id]) + @variant = variant_if_eligible(params[:subscription_line_item][:variant_id]) if @shop.present? end def new_actions @@ -50,5 +51,9 @@ module Admin OpenFoodNetwork::ScopeVariantToHub.new(@shop).scope(@variant) @variant.price + fee_calculator.indexed_fees_for(@variant) end + + def variant_if_eligible(variant_id) + OpenFoodNetwork::SubscriptionService.eligible_variants(@shop).find_by_id(variant_id) + end end end diff --git a/app/services/subscription_validator.rb b/app/services/subscription_validator.rb index 33fc2baf77..8ee43b9c4f 100644 --- a/app/services/subscription_validator.rb +++ b/app/services/subscription_validator.rb @@ -2,6 +2,8 @@ # Public interface consists of #valid? method provided by ActiveModel::Validations # and #json_errors which compiles a serializable hash of errors +require "open_food_network/subscription_service" + class SubscriptionValidator include ActiveModel::Naming include ActiveModel::Conversion @@ -97,15 +99,12 @@ class SubscriptionValidator errors.add(:subscription_line_items, :not_available, name: name) end - # TODO: Extract this into a separate class def available_variant_ids - @available_variant_ids ||= - Spree::Variant.joins(exchanges: { order_cycle: :schedules }) - .where(id: subscription_line_items.map(&:variant_id)) - .where(schedules: { id: schedule }, exchanges: { incoming: false, receiver_id: shop }) - .merge(OrderCycle.not_closed) - .select('DISTINCT spree_variants.id') - .pluck(:id) + return @available_variant_ids if @available_variant_ids.present? + + subscription_variant_ids = subscription_line_items.map(&:variant_id) + @available_variant_ids = OpenFoodNetwork::SubscriptionService.eligible_variants(shop) + .where(id: subscription_variant_ids).pluck(:id) end def build_msg_from(k, msg) diff --git a/lib/open_food_network/scope_variants_for_search.rb b/lib/open_food_network/scope_variants_for_search.rb index 9befef5304..c1f90df7d1 100644 --- a/lib/open_food_network/scope_variants_for_search.rb +++ b/lib/open_food_network/scope_variants_for_search.rb @@ -5,6 +5,8 @@ require 'open_food_network/scope_variant_to_hub' # Further restrictions on the schedule, order_cycle or distributor through which the # products are available are also possible +require "open_food_network/subscription_service" + module OpenFoodNetwork class ScopeVariantsForSearch def initialize(params) @@ -33,6 +35,10 @@ module OpenFoodNetwork Spree::Variant.where(is_master: false).ransack(search_params.merge(m: 'or')).result end + def distributor + Enterprise.find params[:distributor_id] + end + def scope_to_schedule @variants = @variants.in_schedule(params[:schedule_id]) end @@ -42,12 +48,29 @@ module OpenFoodNetwork end def scope_to_distributor - distributor = Enterprise.find params[:distributor_id] + if params[:eligible_for_subscriptions] + scope_to_eligible_for_subscriptions_in_distributor + else + scope_to_available_for_orders_in_distributor + end + end + + def scope_to_available_for_orders_in_distributor @variants = @variants.in_distributor(distributor) + scope_variants_to_distributor(@variants, distributor) + end + + def scope_to_eligible_for_subscriptions_in_distributor + eligible_variants_scope = OpenFoodNetwork::SubscriptionService.eligible_variants(distributor) + @variants = @variants.merge(eligible_variants_scope) + scope_variants_to_distributor(@variants, distributor) + end + + def scope_variants_to_distributor(variants, distributor) scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor) # Perform scoping after all filtering is done. # Filtering could be a problem on scoped variants. - @variants.each { |v| scoper.scope(v) } + variants.each { |v| scoper.scope(v) } end end end diff --git a/lib/open_food_network/subscription_service.rb b/lib/open_food_network/subscription_service.rb new file mode 100644 index 0000000000..e3479275c1 --- /dev/null +++ b/lib/open_food_network/subscription_service.rb @@ -0,0 +1,28 @@ +module OpenFoodNetwork + class SubscriptionService + # Includes the following variants: + # - Variants of permitted producers + # - Variants of hub + # - Variants that are in outgoing exchanges where the hub is receiver + def self.eligible_variants(distributor) + permitted_order_cycle_enterprise_ids = EnterpriseRelationship.permitting(distributor) + .with_permission(:add_to_order_cycle).pluck(:parent_id) + permitted_producer_ids = Enterprise.is_primary_producer + .where('enterprises.id IN (?)', permitted_order_cycle_enterprise_ids).pluck(:id) + + outgoing_exchange_variant_ids = ExchangeVariant + .select("DISTINCT exchange_variants.variant_id") + .joins(:exchange) + .where(exchanges: { incoming: false, receiver_id: distributor.id }) + .pluck(:variant_id) + + variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids | [distributor.id]] + if outgoing_exchange_variant_ids.present? + variant_conditions[0] << " OR spree_variants.id IN (?)" + variant_conditions << outgoing_exchange_variant_ids + end + + Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions) + end + end +end diff --git a/spec/controllers/admin/subscription_line_items_controller_spec.rb b/spec/controllers/admin/subscription_line_items_controller_spec.rb index b2f3a0f432..2b42a20df7 100644 --- a/spec/controllers/admin/subscription_line_items_controller_spec.rb +++ b/spec/controllers/admin/subscription_line_items_controller_spec.rb @@ -10,9 +10,9 @@ describe Admin::SubscriptionLineItemsController, type: :controller do let(:unmanaged_shop) { create(:enterprise) } let!(:product) { create(:product) } let!(:variant) { create(:variant, product: product, unit_value: '100', price: 15.00, option_values: []) } + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant], enterprise_fees: [enterprise_fee]) } let!(:enterprise_fee) { create(:enterprise_fee, amount: 3.50) } let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } - let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant], enterprise_fees: [enterprise_fee]) } let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } let(:unmanaged_schedule) { create(:schedule, order_cycles: [create(:simple_order_cycle, coordinator: unmanaged_shop)]) } @@ -42,6 +42,8 @@ describe Admin::SubscriptionLineItemsController, type: :controller do before { params.merge!(shop_id: shop.id) } context "but the shop doesn't have permission to sell product in question" do + let!(:outgoing_exchange) { } + it "returns an error" do spree_post :build, params json_response = JSON.parse(response.body) diff --git a/spec/controllers/admin/subscriptions_controller_spec.rb b/spec/controllers/admin/subscriptions_controller_spec.rb index 5f83bf0529..9385d453f3 100644 --- a/spec/controllers/admin/subscriptions_controller_spec.rb +++ b/spec/controllers/admin/subscriptions_controller_spec.rb @@ -341,7 +341,7 @@ describe Admin::SubscriptionsController, type: :controller do end context 'with subscription_line_items params' do - let!(:product2) { create(:product, supplier: shop) } + let!(:product2) { create(:product) } let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) } before do diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index be2eaa6ba5..0a03d629f4 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -145,13 +145,13 @@ feature 'Subscriptions' do let!(:customer_user) { create(:user) } let!(:credit_card1) { create(:credit_card, user: customer_user, cc_type: 'visa', last_digits: 1111, month: 10, year: 2030) } let!(:customer) { create(:customer, enterprise: shop, bill_address: address, user: customer_user, allow_charges: true) } - let!(:product1) { create(:product, supplier: shop) } - let!(:product2) { create(:product, supplier: shop) } - let!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) } - let!(:variant2) { create(:variant, product: product2, unit_value: '1000', price: 6.00, option_values: []) } + let!(:test_product) { create(:product, supplier: shop, distributors: []) } + let!(:test_variant) { create(:variant, product: test_product, unit_value: "100", price: 12.00, option_values: []) } + let!(:shop_product) { create(:product, supplier: shop, distributors: [shop]) } + let!(:shop_variant) { create(:variant, product: shop_product, unit_value: "1000", price: 6.00, option_values: []) } let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) } let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } - let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant1, variant2], enterprise_fees: [enterprise_fee]) } + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [test_variant, shop_variant], enterprise_fees: [enterprise_fee]) } let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } let!(:payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) } let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } @@ -217,11 +217,9 @@ feature 'Subscriptions' do expect(page).to have_content 'Please add at least one product' # Adding a product and getting a price estimate - select2_search_async product1.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' - fill_in 'add_quantity', with: 2 - click_link 'Add' + add_variant_to_subscription test_variant, 2 within 'table#subscription-line-items tr.item', match: :first do - expect(page).to have_selector 'td.description', text: "#{product1.name} - #{variant1.full_name}" + expect(page).to have_selector '.description', text: "#{test_product.name} - #{test_variant.full_name}" expect(page).to have_selector 'td.price', text: "$13.75" expect(page).to have_input 'quantity', with: "2" expect(page).to have_selector 'td.total', text: "$27.50" @@ -243,11 +241,9 @@ feature 'Subscriptions' do click_button('edit-products') # Adding a new product - select2_search_async product2.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' - fill_in 'add_quantity', with: 3 - click_link 'Add' + add_variant_to_subscription shop_variant, 3 within 'table#subscription-line-items tr.item', match: :first do - expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}" + expect(page).to have_selector '.description', text: "#{shop_product.name} - #{shop_variant.full_name}" expect(page).to have_selector 'td.price', text: "$7.75" expect(page).to have_input 'quantity', with: "3" expect(page).to have_selector 'td.total', text: "$23.25" @@ -266,7 +262,7 @@ feature 'Subscriptions' do # Prices are shown in the index within 'table#subscription-line-items tr.item', match: :first do - expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}" + expect(page).to have_selector '.description', text: "#{shop_product.name} - #{shop_variant.full_name}" expect(page).to have_selector 'td.price', text: "$7.75" expect(page).to have_input 'quantity', with: "3" expect(page).to have_selector 'td.total', text: "$23.25" @@ -284,7 +280,7 @@ feature 'Subscriptions' do # Standing Line Items are created expect(subscription.subscription_line_items.count).to eq 1 subscription_line_item = subscription.subscription_line_items.first - expect(subscription_line_item.variant).to eq variant2 + expect(subscription_line_item.variant).to eq shop_variant expect(subscription_line_item.quantity).to eq 3 end end @@ -336,7 +332,7 @@ feature 'Subscriptions' do # Existing products should be visible click_button 'edit-products' within "#sli_0" do - expect(page).to have_selector 'td.description', text: "#{product1.name} - #{variant1.full_name}" + expect(page).to have_selector '.description', text: "#{product1.name} - #{variant1.full_name}" expect(page).to have_selector 'td.price', text: "$13.75" expect(page).to have_input 'quantity', with: "2" expect(page).to have_selector 'td.total', text: "$27.50" @@ -350,11 +346,9 @@ feature 'Subscriptions' do expect(page).to have_content 'Please add at least one product' # Add variant2 to the subscription - select2_search_async product2.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' - fill_in 'add_quantity', with: 1 - click_link 'Add' + add_variant_to_subscription(variant2, 1) within "#sli_0" do - expect(page).to have_selector 'td.description', text: "#{product2.name} - #{variant2.full_name}" + expect(page).to have_selector '.description', text: "#{product2.name} - #{variant2.full_name}" expect(page).to have_selector 'td.price', text: "$7.75" expect(page).to have_input 'quantity', with: "1" expect(page).to have_selector 'td.total', text: "$7.75" @@ -364,11 +358,9 @@ feature 'Subscriptions' do expect(page).to have_selector '#order_form_total', text: "$7.75" # Add variant3 to the subscription (even though it is not available) - select2_search_async product3.name, from: I18n.t(:name_or_sku), dropdown_css: '.select2-drop' - fill_in 'add_quantity', with: 1 - click_link 'Add' + add_variant_to_subscription(variant3, 1) within "#sli_1" do - expect(page).to have_selector 'td.description', text: "#{product3.name} - #{variant3.full_name}" + expect(page).to have_selector '.description', text: "#{product3.name} - #{variant3.full_name}" expect(page).to have_selector 'td.price', text: "$22.00" expect(page).to have_input 'quantity', with: "1" expect(page).to have_selector 'td.total', text: "$22.00" @@ -377,9 +369,6 @@ feature 'Subscriptions' do # Total should be $29.75 expect(page).to have_selector '#order_form_total', text: "$29.75" - click_button 'Save Changes' - expect(page).to have_content "#{product3.name} - #{variant3.full_name} is not available from the selected schedule" - # Remove variant3 from the subscription within '#sli_1' do find("a.delete-item").click @@ -421,5 +410,103 @@ feature 'Subscriptions' do end end end + + describe "allowed variants" do + let!(:customer) { create(:customer, enterprise: shop, allow_charges: true) } + let!(:credit_card) { create(:credit_card, user: customer.user) } + let!(:shop_product) { create(:product, supplier: shop, distributors: [shop]) } + let!(:shop_variant) { create(:variant, product: shop_product, unit_value: "2000") } + let!(:permitted_supplier) do + create(:supplier_enterprise).tap do |supplier| + create(:enterprise_relationship, child: shop, parent: supplier, permissions_list: [:add_to_order_cycle]) + end + end + let!(:permitted_supplier_product) { create(:product, supplier: permitted_supplier, distributors: [shop]) } + let!(:permitted_supplier_variant) { create(:variant, product: permitted_supplier_product, unit_value: "2000") } + let!(:incoming_exchange_product) { create(:product, distributors: [shop]) } + let!(:incoming_exchange_variant) do + create(:variant, product: incoming_exchange_product, unit_value: "2000").tap do |variant| + create(:exchange, order_cycle: order_cycle, incoming: true, receiver: shop, variants: [variant]) + end + end + let!(:outgoing_exchange_product) { create(:product, distributors: [shop]) } + let!(:outgoing_exchange_variant) do + create(:variant, product: outgoing_exchange_product, unit_value: "2000").tap do |variant| + create(:exchange, order_cycle: order_cycle, incoming: false, receiver: shop, variants: [variant]) + end + end + let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) } + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + let!(:payment_method) { create(:stripe_payment_method, distributors: [shop], preferred_enterprise_id: shop.id) } + let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } + + before do + visit admin_subscriptions_path + click_link "New Subscription" + select2_select shop.name, from: "new_subscription_shop_id" + click_button "Continue" + end + + it "permit creating and editing of the subscription" do + select2_select customer.email, from: "customer_id" + select2_select schedule.name, from: "schedule_id" + select2_select payment_method.name, from: "payment_method_id" + select2_select shipping_method.name, from: "shipping_method_id" + find_field("begins_at").click + within(".ui-datepicker-calendar") do + find(".ui-datepicker-today").click + end + click_button "Next" + + expect(page).to have_content "BILLING ADDRESS" + click_button "Next" + + # Add products + expect(page).to have_content "NAME OR SKU" + add_variant_to_subscription shop_variant, 3 + add_variant_to_subscription permitted_supplier_variant, 4 + add_variant_to_subscription incoming_exchange_variant, 5 + add_variant_to_subscription outgoing_exchange_variant, 6 + click_button "Next" + + # Submit form + expect { + click_button "Create Subscription" + expect(page).to have_current_path admin_subscriptions_path + }.to change(Subscription, :count).by(1) + + # Subscription line items are created + subscription = Subscription.last + expect(subscription.subscription_line_items.count).to eq 4 + + # Edit the subscription + visit edit_admin_subscription_path(subscription) + + # Remove shop_variant from the subscription + click_button "edit-products" + within "#sli_0" do + expect(page).to have_selector ".description", text: shop_variant.name + find("a.delete-item").click + end + + # Submit form + click_button "Save Changes" + expect(page).to have_current_path admin_subscriptions_path + + # Subscription is saved + visit edit_admin_subscription_path(subscription) + expect(page).to have_selector "#subscription-line-items .item", count: 3 + end + end + end + + def add_variant_to_subscription(variant, quantity) + row_count = all("#subscription-line-items .item").length + variant_name = variant.full_name.present? ? "#{variant.name} - #{variant.full_name}" : variant.name + select2_search variant.name, from: I18n.t(:name_or_sku), dropdown_css: ".select2-drop", select_text: variant_name + fill_in "add_quantity", with: quantity + click_link "Add" + expect(page).to have_selector("#subscription-line-items .item", count: row_count + 1) end end diff --git a/spec/lib/open_food_network/subscription_service_spec.rb b/spec/lib/open_food_network/subscription_service_spec.rb new file mode 100644 index 0000000000..7df860cf03 --- /dev/null +++ b/spec/lib/open_food_network/subscription_service_spec.rb @@ -0,0 +1,99 @@ +require "spec_helper" +require "open_food_network/subscription_service" + +module OpenFoodNetwork + describe SubscriptionService do + describe "variant eligibility for subscription" do + let!(:shop) { create(:distributor_enterprise) } + let!(:producer) { create(:supplier_enterprise) } + let!(:product) { create(:product, supplier: producer) } + let!(:variant) { product.variants.first } + + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + let!(:subscription) { create(:subscription, shop: shop, schedule: schedule) } + let!(:subscription_line_item) do + create(:subscription_line_item, subscription: subscription, variant: variant) + end + + let(:current_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.ago, + orders_close_at: 1.week.from_now) + end + + let(:future_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.from_now, + orders_close_at: 2.weeks.from_now) + end + + let(:past_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.weeks.ago, + orders_close_at: 1.week.ago) + end + + let!(:order_cycle) { current_order_cycle } + + context "if the shop is the supplier for the product" do + let!(:producer) { shop } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the supplier is permitted for the shop" do + let!(:enterprise_relationship) { create(:enterprise_relationship, child: shop, parent: product.supplier, permissions_list: [:add_to_order_cycle]) } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the variant is involved in an exchange" do + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + + context "if it is an incoming exchange where the shop is the receiver" do + let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) } + + it "is not eligible" do + expect(described_class.eligible_variants(shop)).to_not include(variant) + end + end + + context "if it is an outgoing exchange where the shop is the receiver" do + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) } + + context "if the order cycle is currently open" do + let!(:order_cycle) { current_order_cycle } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the order cycle opens in the future" do + let!(:order_cycle) { future_order_cycle } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the order cycle closed in the past" do + let!(:order_cycle) { past_order_cycle } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + end + end + + context "if the variant is unrelated" do + it "is not eligible" do + expect(described_class.eligible_variants(shop)).to_not include(variant) + end + end + end + end +end diff --git a/spec/services/subscription_validator_spec.rb b/spec/services/subscription_validator_spec.rb index c4878414a5..bdcd14bea5 100644 --- a/spec/services/subscription_validator_spec.rb +++ b/spec/services/subscription_validator_spec.rb @@ -1,10 +1,11 @@ require "spec_helper" describe SubscriptionValidator do - let(:shop) { instance_double(Enterprise, name: "Shop") } + let(:owner) { create(:user) } + let(:shop) { create(:enterprise, name: "Shop", owner: owner) } describe "delegation" do - let(:subscription) { create(:subscription) } + let(:subscription) { create(:subscription, shop: shop) } let(:validator) { SubscriptionValidator.new(subscription) } it "delegates to subscription" do @@ -440,6 +441,7 @@ describe SubscriptionValidator do context "but some variants are unavailable" do let(:product) { instance_double(Spree::Product, name: "some_name") } + before do allow(validator).to receive(:available_variant_ids) { [variant2.id] } allow(variant1).to receive(:product) { product } @@ -453,7 +455,9 @@ describe SubscriptionValidator do end context "and all requested variants are available" do - before { allow(validator).to receive(:available_variant_ids) { [variant1.id, variant2.id] } } + before do + allow(validator).to receive(:available_variant_ids) { [variant1.id, variant2.id] } + end it "returns true" do expect(validator.valid?).to be true @@ -463,81 +467,4 @@ describe SubscriptionValidator do end end end - - describe "variant eligibility for subscription" do - let!(:shop) { create(:distributor_enterprise) } - let!(:producer) { create(:supplier_enterprise) } - let!(:product) { create(:product, supplier: producer) } - let!(:variant) { product.variants.first } - - let!(:order_cycle) { current_order_cycle } - let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } - let!(:subscription) { create(:subscription, shop: shop, schedule: schedule) } - let!(:subscription_line_item) do - create(:subscription_line_item, subscription: subscription, variant: variant) - end - - let(:current_order_cycle) do - create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.ago, - orders_close_at: 1.week.from_now) - end - - let(:future_order_cycle) do - create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.from_now, - orders_close_at: 2.weeks.from_now) - end - - let(:past_order_cycle) do - create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.weeks.ago, - orders_close_at: 1.week.ago) - end - - let(:validator) { SubscriptionValidator.new(subscription) } - - context "if it is not in an exchange" do - it "is not eligible" do - expect(validator.__send__(:available_variant_ids)).to_not include(variant.id) - end - end - - context "if it is only in an incoming exchange" do - let!(:incoming_exchange) do - create(:exchange, order_cycle: order_cycle, sender: producer, receiver: shop, - incoming: true, variants: [variant]) - end - - it "is not eligible" do - expect(validator.__send__(:available_variant_ids)).to_not include(variant.id) - end - end - - context "if is an outgoing exchange where the shop is the receiver" do - let!(:outgoing_exchange) do - create(:exchange, order_cycle: order_cycle, sender: shop, receiver: shop, - incoming: false, variants: [variant]) - end - - context "if the order cycle is currently open" do - it "is eligible" do - expect(validator.__send__(:available_variant_ids)).to include(variant.id) - end - end - - context "if the order cycle opens in the future" do - let!(:order_cycle) { future_order_cycle } - - it "is eligible" do - expect(validator.__send__(:available_variant_ids)).to include(variant.id) - end - end - - context "if the order cycle closed in the past" do - let!(:order_cycle) { past_order_cycle } - - it "is not eligible" do - expect(validator.__send__(:available_variant_ids)).to_not include(variant.id) - end - end - end - end end From c23002102cc4b7b1593cf4e86023a435e4e03199 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 17 Sep 2018 01:14:40 +1000 Subject: [PATCH 015/108] Add warning for unavailable subscription items --- .../admin/pages/subscription_form.css.scss | 5 +++ .../admin/pages/subscription_review.css.scss | 5 +++ .../subscription_line_items_controller.rb | 3 +- .../subscription_line_item_serializer.rb | 19 ++++++++++- .../admin/subscriptions/_review.html.haml | 7 ++-- .../_subscription_line_items.html.haml | 7 ++-- config/locales/en.yml | 2 ++ lib/open_food_network/subscription_service.rb | 8 +++++ spec/features/admin/subscriptions_spec.rb | 9 +++++ .../subscription_service_spec.rb | 34 +++++++++++++++++++ 10 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 app/assets/stylesheets/admin/pages/subscription_form.css.scss create mode 100644 app/assets/stylesheets/admin/pages/subscription_review.css.scss diff --git a/app/assets/stylesheets/admin/pages/subscription_form.css.scss b/app/assets/stylesheets/admin/pages/subscription_form.css.scss new file mode 100644 index 0000000000..02c9fd783e --- /dev/null +++ b/app/assets/stylesheets/admin/pages/subscription_form.css.scss @@ -0,0 +1,5 @@ +.admin-subscription-form-subscription-line-items { + .not-in-open-and-upcoming-order-cycles-warning { + color: $warning-red; + } +} diff --git a/app/assets/stylesheets/admin/pages/subscription_review.css.scss b/app/assets/stylesheets/admin/pages/subscription_review.css.scss new file mode 100644 index 0000000000..ba3280aa2b --- /dev/null +++ b/app/assets/stylesheets/admin/pages/subscription_review.css.scss @@ -0,0 +1,5 @@ +.admin-subscription-review-subscription-line-items { + .not-in-open-and-upcoming-order-cycles-warning { + color: $warning-red; + } +} diff --git a/app/controllers/admin/subscription_line_items_controller.rb b/app/controllers/admin/subscription_line_items_controller.rb index 8c0432578d..378b73b990 100644 --- a/app/controllers/admin/subscription_line_items_controller.rb +++ b/app/controllers/admin/subscription_line_items_controller.rb @@ -14,7 +14,8 @@ module Admin def build @subscription_line_item.assign_attributes(params[:subscription_line_item]) @subscription_line_item.price_estimate = price_estimate - render json: @subscription_line_item, serializer: Api::Admin::SubscriptionLineItemSerializer + render json: @subscription_line_item, serializer: Api::Admin::SubscriptionLineItemSerializer, + shop: @shop, schedule: @schedule end private diff --git a/app/serializers/api/admin/subscription_line_item_serializer.rb b/app/serializers/api/admin/subscription_line_item_serializer.rb index 23f4c6bc52..ca8028d7b5 100644 --- a/app/serializers/api/admin/subscription_line_item_serializer.rb +++ b/app/serializers/api/admin/subscription_line_item_serializer.rb @@ -1,7 +1,8 @@ module Api module Admin class SubscriptionLineItemSerializer < ActiveModel::Serializer - attributes :id, :variant_id, :quantity, :description, :price_estimate + attributes :id, :variant_id, :quantity, :description, :price_estimate, + :in_open_and_upcoming_order_cycles def description "#{object.variant.product.name} - #{object.variant.full_name}" @@ -10,6 +11,22 @@ module Api def price_estimate object.price_estimate.andand.to_f || "?" end + + def in_open_and_upcoming_order_cycles + OpenFoodNetwork::SubscriptionService + .in_open_and_upcoming_order_cycles?(option_or_assigned_shop, option_or_assigned_schedule, + object.variant) + end + + private + + def option_or_assigned_shop + @options[:shop] || object.subscription.andand.shop + end + + def option_or_assigned_schedule + @options[:schedule] || object.subscription.andand.schedule + end end end end diff --git a/app/views/admin/subscriptions/_review.html.haml b/app/views/admin/subscriptions/_review.html.haml index e1591e6210..7e97bd02aa 100644 --- a/app/views/admin/subscriptions/_review.html.haml +++ b/app/views/admin/subscriptions/_review.html.haml @@ -56,7 +56,7 @@ %input#edit-products{ type: "button", value: t(:edit), ng: { click: "setView('products')" } } .row .seven.columns.alpha.omega - %table#subscription-line-items + %table#subscription-line-items.admin-subscription-review-subscription-line-items %colgroup %col{:style => "width: 62%;"}/ %col{:style => "width: 14%;"}/ @@ -71,7 +71,10 @@ %span= t(:total) %tbody %tr.item{ id: "sli_{{$index}}", ng: { repeat: "item in subscription.subscription_line_items | filter:{ _destroy: '!true' }", class: { even: 'even', odd: 'odd' } } } - %td.description {{ item.description }} + %td + .description {{ item.description }} + .not-in-open-and-upcoming-order-cycles-warning{ ng: { if: '!item.in_open_and_upcoming_order_cycles' } } + = t(".no_open_or_upcoming_order_cycle") %td.price.align-center {{ item.price_estimate | currency }} %td.quantity {{ item.quantity }} %td.total.align-center {{ (item.price_estimate * item.quantity) | currency }} diff --git a/app/views/admin/subscriptions/_subscription_line_items.html.haml b/app/views/admin/subscriptions/_subscription_line_items.html.haml index 0be91f2c93..b80b95a85b 100644 --- a/app/views/admin/subscriptions/_subscription_line_items.html.haml +++ b/app/views/admin/subscriptions/_subscription_line_items.html.haml @@ -1,4 +1,4 @@ -%table#subscription-line-items +%table#subscription-line-items.admin-subscription-form-subscription-line-items %colgroup %col{:style => "width: 49%;"}/ %col{:style => "width: 14%;"}/ @@ -15,7 +15,10 @@ %th.orders-actions.actions %tbody %tr.item{ id: "sli_{{$index}}", ng: { repeat: "item in subscription.subscription_line_items | filter:{ _destroy: '!true' }", class: { even: 'even', odd: 'odd' } } } - %td.description {{ item.description }} + %td + .description {{ item.description }} + .not-in-open-and-upcoming-order-cycles-warning{ ng: { if: '!item.in_open_and_upcoming_order_cycles' } } + = t(".not_in_open_and_upcoming_order_cycles_warning") %td.price.align-center {{ item.price_estimate | currency }} %td.quantity %input{ name: 'quantity', type: 'number', min: 0, ng: { model: 'item.quantity' } } diff --git a/config/locales/en.yml b/config/locales/en.yml index 0bde4fb005..366a481bac 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1088,6 +1088,7 @@ en: this_is_an_estimate: | The displayed prices are only an estimate and calculated at the time the subscription is changed. If you change prices or fees, orders will be updated, but the subscription will still display the old values. + not_in_open_and_upcoming_order_cycles_warning: "There are no open or upcoming order cycles for this product." details: details: Details invalid_error: Oops! Please fill in all of the required fields... @@ -1102,6 +1103,7 @@ en: details: Details address: Address products: Products + no_open_or_upcoming_order_cycle: "No Upcoming OC" product_already_in_order: This product has already been added to the order. Please edit the quantity directly. orders: number: Number diff --git a/lib/open_food_network/subscription_service.rb b/lib/open_food_network/subscription_service.rb index e3479275c1..e4f48aa67a 100644 --- a/lib/open_food_network/subscription_service.rb +++ b/lib/open_food_network/subscription_service.rb @@ -24,5 +24,13 @@ module OpenFoodNetwork Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions) end + + def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant) + scope = ExchangeVariant.joins(exchange: { order_cycle: :schedules }) + .where(variant_id: variant, exchanges: { incoming: false, receiver_id: distributor }) + .merge(OrderCycle.not_closed) + scope = scope.where(schedules: { id: schedule }) + scope.any? + end end end diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index 0a03d629f4..f51de02e5f 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -465,9 +465,13 @@ feature 'Subscriptions' do # Add products expect(page).to have_content "NAME OR SKU" add_variant_to_subscription shop_variant, 3 + expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 1 add_variant_to_subscription permitted_supplier_variant, 4 + expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 2 add_variant_to_subscription incoming_exchange_variant, 5 + expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 3 add_variant_to_subscription outgoing_exchange_variant, 6 + expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 3 click_button "Next" # Submit form @@ -509,4 +513,9 @@ feature 'Subscriptions' do click_link "Add" expect(page).to have_selector("#subscription-line-items .item", count: row_count + 1) end + + def variant_not_in_open_or_upcoming_order_cycle_warning + I18n.t("not_in_open_and_upcoming_order_cycles_warning", + scope: "admin.subscriptions.subscription_line_items") + end end diff --git a/spec/lib/open_food_network/subscription_service_spec.rb b/spec/lib/open_food_network/subscription_service_spec.rb index 7df860cf03..63ef72707d 100644 --- a/spec/lib/open_food_network/subscription_service_spec.rb +++ b/spec/lib/open_food_network/subscription_service_spec.rb @@ -95,5 +95,39 @@ module OpenFoodNetwork end end end + + describe "checking if variant in open and upcoming order cycles" do + let!(:shop) { create(:enterprise) } + let!(:product) { create(:product) } + let!(:variant) { product.variants.first } + let!(:schedule) { create(:schedule) } + + context "if the variant is involved in an exchange" do + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + + context "if it is an incoming exchange where the shop is the receiver" do + let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) } + + it "is is false" do + expect(described_class).not_to be_in_open_and_upcoming_order_cycles(shop, schedule, variant) + end + end + + context "if it is an outgoing exchange where the shop is the receiver" do + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) } + + it "is true" do + expect(described_class).to be_in_open_and_upcoming_order_cycles(shop, schedule, variant) + end + end + end + + context "if the variant is unrelated" do + it "is false" do + expect(described_class).to_not be_in_open_and_upcoming_order_cycles(shop, schedule, variant) + end + end + end end end From b691d727a780da4f78103f1eb67e77e2e70827bc Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 20 Jan 2019 23:18:52 +1100 Subject: [PATCH 016/108] Move OFN::SubscriptionService to SubscriptionVariantsService --- .../subscription_line_items_controller.rb | 3 +- .../subscription_line_item_serializer.rb | 6 +- app/services/subscription_validator.rb | 4 +- app/services/subscription_variants_service.rb | 34 +++++ .../scope_variants_for_search.rb | 4 +- lib/open_food_network/subscription_service.rb | 36 ----- .../subscription_service_spec.rb | 133 ------------------ .../subscription_variants_service_spec.rb | 130 +++++++++++++++++ 8 files changed, 170 insertions(+), 180 deletions(-) create mode 100644 app/services/subscription_variants_service.rb delete mode 100644 lib/open_food_network/subscription_service.rb delete mode 100644 spec/lib/open_food_network/subscription_service_spec.rb create mode 100644 spec/services/subscription_variants_service_spec.rb diff --git a/app/controllers/admin/subscription_line_items_controller.rb b/app/controllers/admin/subscription_line_items_controller.rb index 378b73b990..5267374539 100644 --- a/app/controllers/admin/subscription_line_items_controller.rb +++ b/app/controllers/admin/subscription_line_items_controller.rb @@ -1,7 +1,6 @@ require 'open_food_network/permissions' require 'open_food_network/order_cycle_permissions' require 'open_food_network/scope_variant_to_hub' -require "open_food_network/subscription_service" module Admin class SubscriptionLineItemsController < ResourceController @@ -54,7 +53,7 @@ module Admin end def variant_if_eligible(variant_id) - OpenFoodNetwork::SubscriptionService.eligible_variants(@shop).find_by_id(variant_id) + SubscriptionVariantsService.eligible_variants(@shop).find_by_id(variant_id) end end end diff --git a/app/serializers/api/admin/subscription_line_item_serializer.rb b/app/serializers/api/admin/subscription_line_item_serializer.rb index ca8028d7b5..34bc00c6c0 100644 --- a/app/serializers/api/admin/subscription_line_item_serializer.rb +++ b/app/serializers/api/admin/subscription_line_item_serializer.rb @@ -13,9 +13,9 @@ module Api end def in_open_and_upcoming_order_cycles - OpenFoodNetwork::SubscriptionService - .in_open_and_upcoming_order_cycles?(option_or_assigned_shop, option_or_assigned_schedule, - object.variant) + SubscriptionVariantsService.in_open_and_upcoming_order_cycles?(option_or_assigned_shop, + option_or_assigned_schedule, + object.variant) end private diff --git a/app/services/subscription_validator.rb b/app/services/subscription_validator.rb index 8ee43b9c4f..051033dd9f 100644 --- a/app/services/subscription_validator.rb +++ b/app/services/subscription_validator.rb @@ -2,8 +2,6 @@ # Public interface consists of #valid? method provided by ActiveModel::Validations # and #json_errors which compiles a serializable hash of errors -require "open_food_network/subscription_service" - class SubscriptionValidator include ActiveModel::Naming include ActiveModel::Conversion @@ -103,7 +101,7 @@ class SubscriptionValidator return @available_variant_ids if @available_variant_ids.present? subscription_variant_ids = subscription_line_items.map(&:variant_id) - @available_variant_ids = OpenFoodNetwork::SubscriptionService.eligible_variants(shop) + @available_variant_ids = SubscriptionVariantsService.eligible_variants(shop) .where(id: subscription_variant_ids).pluck(:id) end diff --git a/app/services/subscription_variants_service.rb b/app/services/subscription_variants_service.rb new file mode 100644 index 0000000000..eb6dcf0cf2 --- /dev/null +++ b/app/services/subscription_variants_service.rb @@ -0,0 +1,34 @@ +class SubscriptionVariantsService + # Includes the following variants: + # - Variants of permitted producers + # - Variants of hub + # - Variants that are in outgoing exchanges where the hub is receiver + def self.eligible_variants(distributor) + permitted_order_cycle_enterprise_ids = EnterpriseRelationship.permitting(distributor) + .with_permission(:add_to_order_cycle).pluck(:parent_id) + permitted_producer_ids = Enterprise.is_primary_producer + .where('enterprises.id IN (?)', permitted_order_cycle_enterprise_ids).pluck(:id) + + outgoing_exchange_variant_ids = ExchangeVariant + .select("DISTINCT exchange_variants.variant_id") + .joins(:exchange) + .where(exchanges: { incoming: false, receiver_id: distributor.id }) + .pluck(:variant_id) + + variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids | [distributor.id]] + if outgoing_exchange_variant_ids.present? + variant_conditions[0] << " OR spree_variants.id IN (?)" + variant_conditions << outgoing_exchange_variant_ids + end + + Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions) + end + + def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant) + scope = ExchangeVariant.joins(exchange: { order_cycle: :schedules }) + .where(variant_id: variant, exchanges: { incoming: false, receiver_id: distributor }) + .merge(OrderCycle.not_closed) + scope = scope.where(schedules: { id: schedule }) + scope.any? + end +end diff --git a/lib/open_food_network/scope_variants_for_search.rb b/lib/open_food_network/scope_variants_for_search.rb index c1f90df7d1..24b110fb6e 100644 --- a/lib/open_food_network/scope_variants_for_search.rb +++ b/lib/open_food_network/scope_variants_for_search.rb @@ -5,8 +5,6 @@ require 'open_food_network/scope_variant_to_hub' # Further restrictions on the schedule, order_cycle or distributor through which the # products are available are also possible -require "open_food_network/subscription_service" - module OpenFoodNetwork class ScopeVariantsForSearch def initialize(params) @@ -61,7 +59,7 @@ module OpenFoodNetwork end def scope_to_eligible_for_subscriptions_in_distributor - eligible_variants_scope = OpenFoodNetwork::SubscriptionService.eligible_variants(distributor) + eligible_variants_scope = SubscriptionVariantsService.eligible_variants(distributor) @variants = @variants.merge(eligible_variants_scope) scope_variants_to_distributor(@variants, distributor) end diff --git a/lib/open_food_network/subscription_service.rb b/lib/open_food_network/subscription_service.rb deleted file mode 100644 index e4f48aa67a..0000000000 --- a/lib/open_food_network/subscription_service.rb +++ /dev/null @@ -1,36 +0,0 @@ -module OpenFoodNetwork - class SubscriptionService - # Includes the following variants: - # - Variants of permitted producers - # - Variants of hub - # - Variants that are in outgoing exchanges where the hub is receiver - def self.eligible_variants(distributor) - permitted_order_cycle_enterprise_ids = EnterpriseRelationship.permitting(distributor) - .with_permission(:add_to_order_cycle).pluck(:parent_id) - permitted_producer_ids = Enterprise.is_primary_producer - .where('enterprises.id IN (?)', permitted_order_cycle_enterprise_ids).pluck(:id) - - outgoing_exchange_variant_ids = ExchangeVariant - .select("DISTINCT exchange_variants.variant_id") - .joins(:exchange) - .where(exchanges: { incoming: false, receiver_id: distributor.id }) - .pluck(:variant_id) - - variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids | [distributor.id]] - if outgoing_exchange_variant_ids.present? - variant_conditions[0] << " OR spree_variants.id IN (?)" - variant_conditions << outgoing_exchange_variant_ids - end - - Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions) - end - - def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant) - scope = ExchangeVariant.joins(exchange: { order_cycle: :schedules }) - .where(variant_id: variant, exchanges: { incoming: false, receiver_id: distributor }) - .merge(OrderCycle.not_closed) - scope = scope.where(schedules: { id: schedule }) - scope.any? - end - end -end diff --git a/spec/lib/open_food_network/subscription_service_spec.rb b/spec/lib/open_food_network/subscription_service_spec.rb deleted file mode 100644 index 63ef72707d..0000000000 --- a/spec/lib/open_food_network/subscription_service_spec.rb +++ /dev/null @@ -1,133 +0,0 @@ -require "spec_helper" -require "open_food_network/subscription_service" - -module OpenFoodNetwork - describe SubscriptionService do - describe "variant eligibility for subscription" do - let!(:shop) { create(:distributor_enterprise) } - let!(:producer) { create(:supplier_enterprise) } - let!(:product) { create(:product, supplier: producer) } - let!(:variant) { product.variants.first } - - let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } - let!(:subscription) { create(:subscription, shop: shop, schedule: schedule) } - let!(:subscription_line_item) do - create(:subscription_line_item, subscription: subscription, variant: variant) - end - - let(:current_order_cycle) do - create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.ago, - orders_close_at: 1.week.from_now) - end - - let(:future_order_cycle) do - create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.from_now, - orders_close_at: 2.weeks.from_now) - end - - let(:past_order_cycle) do - create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.weeks.ago, - orders_close_at: 1.week.ago) - end - - let!(:order_cycle) { current_order_cycle } - - context "if the shop is the supplier for the product" do - let!(:producer) { shop } - - it "is eligible" do - expect(described_class.eligible_variants(shop)).to include(variant) - end - end - - context "if the supplier is permitted for the shop" do - let!(:enterprise_relationship) { create(:enterprise_relationship, child: shop, parent: product.supplier, permissions_list: [:add_to_order_cycle]) } - - it "is eligible" do - expect(described_class.eligible_variants(shop)).to include(variant) - end - end - - context "if the variant is involved in an exchange" do - let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } - let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } - - context "if it is an incoming exchange where the shop is the receiver" do - let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) } - - it "is not eligible" do - expect(described_class.eligible_variants(shop)).to_not include(variant) - end - end - - context "if it is an outgoing exchange where the shop is the receiver" do - let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) } - - context "if the order cycle is currently open" do - let!(:order_cycle) { current_order_cycle } - - it "is eligible" do - expect(described_class.eligible_variants(shop)).to include(variant) - end - end - - context "if the order cycle opens in the future" do - let!(:order_cycle) { future_order_cycle } - - it "is eligible" do - expect(described_class.eligible_variants(shop)).to include(variant) - end - end - - context "if the order cycle closed in the past" do - let!(:order_cycle) { past_order_cycle } - - it "is eligible" do - expect(described_class.eligible_variants(shop)).to include(variant) - end - end - end - end - - context "if the variant is unrelated" do - it "is not eligible" do - expect(described_class.eligible_variants(shop)).to_not include(variant) - end - end - end - - describe "checking if variant in open and upcoming order cycles" do - let!(:shop) { create(:enterprise) } - let!(:product) { create(:product) } - let!(:variant) { product.variants.first } - let!(:schedule) { create(:schedule) } - - context "if the variant is involved in an exchange" do - let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } - let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } - - context "if it is an incoming exchange where the shop is the receiver" do - let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) } - - it "is is false" do - expect(described_class).not_to be_in_open_and_upcoming_order_cycles(shop, schedule, variant) - end - end - - context "if it is an outgoing exchange where the shop is the receiver" do - let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) } - - it "is true" do - expect(described_class).to be_in_open_and_upcoming_order_cycles(shop, schedule, variant) - end - end - end - - context "if the variant is unrelated" do - it "is false" do - expect(described_class).to_not be_in_open_and_upcoming_order_cycles(shop, schedule, variant) - end - end - end - end -end diff --git a/spec/services/subscription_variants_service_spec.rb b/spec/services/subscription_variants_service_spec.rb new file mode 100644 index 0000000000..31d0ff4ca7 --- /dev/null +++ b/spec/services/subscription_variants_service_spec.rb @@ -0,0 +1,130 @@ +require "spec_helper" + +describe SubscriptionVariantsService do + describe "variant eligibility for subscription" do + let!(:shop) { create(:distributor_enterprise) } + let!(:producer) { create(:supplier_enterprise) } + let!(:product) { create(:product, supplier: producer) } + let!(:variant) { product.variants.first } + + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + let!(:subscription) { create(:subscription, shop: shop, schedule: schedule) } + let!(:subscription_line_item) do + create(:subscription_line_item, subscription: subscription, variant: variant) + end + + let(:current_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.ago, + orders_close_at: 1.week.from_now) + end + + let(:future_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.week.from_now, + orders_close_at: 2.weeks.from_now) + end + + let(:past_order_cycle) do + create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.weeks.ago, + orders_close_at: 1.week.ago) + end + + let!(:order_cycle) { current_order_cycle } + + context "if the shop is the supplier for the product" do + let!(:producer) { shop } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the supplier is permitted for the shop" do + let!(:enterprise_relationship) { create(:enterprise_relationship, child: shop, parent: product.supplier, permissions_list: [:add_to_order_cycle]) } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the variant is involved in an exchange" do + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + + context "if it is an incoming exchange where the shop is the receiver" do + let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) } + + it "is not eligible" do + expect(described_class.eligible_variants(shop)).to_not include(variant) + end + end + + context "if it is an outgoing exchange where the shop is the receiver" do + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) } + + context "if the order cycle is currently open" do + let!(:order_cycle) { current_order_cycle } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the order cycle opens in the future" do + let!(:order_cycle) { future_order_cycle } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + + context "if the order cycle closed in the past" do + let!(:order_cycle) { past_order_cycle } + + it "is eligible" do + expect(described_class.eligible_variants(shop)).to include(variant) + end + end + end + end + + context "if the variant is unrelated" do + it "is not eligible" do + expect(described_class.eligible_variants(shop)).to_not include(variant) + end + end + end + + describe "checking if variant in open and upcoming order cycles" do + let!(:shop) { create(:enterprise) } + let!(:product) { create(:product) } + let!(:variant) { product.variants.first } + let!(:schedule) { create(:schedule) } + + context "if the variant is involved in an exchange" do + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } + + context "if it is an incoming exchange where the shop is the receiver" do + let!(:incoming_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: true, variants: [variant]) } + + it "is is false" do + expect(described_class).not_to be_in_open_and_upcoming_order_cycles(shop, schedule, variant) + end + end + + context "if it is an outgoing exchange where the shop is the receiver" do + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: product.supplier, receiver: shop, incoming: false, variants: [variant]) } + + it "is true" do + expect(described_class).to be_in_open_and_upcoming_order_cycles(shop, schedule, variant) + end + end + end + + context "if the variant is unrelated" do + it "is false" do + expect(described_class).to_not be_in_open_and_upcoming_order_cycles(shop, schedule, variant) + end + end + end +end From 9965e95c65a29c6989f1d9abb0e7693f404c7837 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 20 Jan 2019 23:46:56 +1100 Subject: [PATCH 017/108] Refactor methods in SubscriptionVariantsService --- app/services/subscription_variants_service.rb | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/app/services/subscription_variants_service.rb b/app/services/subscription_variants_service.rb index eb6dcf0cf2..855c200303 100644 --- a/app/services/subscription_variants_service.rb +++ b/app/services/subscription_variants_service.rb @@ -4,21 +4,11 @@ class SubscriptionVariantsService # - Variants of hub # - Variants that are in outgoing exchanges where the hub is receiver def self.eligible_variants(distributor) - permitted_order_cycle_enterprise_ids = EnterpriseRelationship.permitting(distributor) - .with_permission(:add_to_order_cycle).pluck(:parent_id) - permitted_producer_ids = Enterprise.is_primary_producer - .where('enterprises.id IN (?)', permitted_order_cycle_enterprise_ids).pluck(:id) - - outgoing_exchange_variant_ids = ExchangeVariant - .select("DISTINCT exchange_variants.variant_id") - .joins(:exchange) - .where(exchanges: { incoming: false, receiver_id: distributor.id }) - .pluck(:variant_id) - - variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids | [distributor.id]] - if outgoing_exchange_variant_ids.present? + variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids(distributor)] + exchange_variant_ids = outgoing_exchange_variant_ids(distributor) + if exchange_variant_ids.present? variant_conditions[0] << " OR spree_variants.id IN (?)" - variant_conditions << outgoing_exchange_variant_ids + variant_conditions << exchange_variant_ids end Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions) @@ -31,4 +21,19 @@ class SubscriptionVariantsService scope = scope.where(schedules: { id: schedule }) scope.any? end + + def self.permitted_producer_ids(distributor) + other_permitted_producer_ids = EnterpriseRelationship.joins(:parent) + .permitting(distributor).with_permission(:add_to_order_cycle) + .merge(Enterprise.is_primary_producer) + .pluck(:parent_id) + + other_permitted_producer_ids | [distributor.id] + end + + def self.outgoing_exchange_variant_ids(distributor) + ExchangeVariant.select("DISTINCT exchange_variants.variant_id").joins(:exchange) + .where(exchanges: { incoming: false, receiver_id: distributor.id }) + .pluck(:variant_id) + end end From da4d6a092ac52b21fe7e95b83c201869952d8b97 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 21 Jan 2019 00:02:38 +1100 Subject: [PATCH 018/108] Add spec helper for choosing today from datepicker --- spec/features/admin/subscriptions_spec.rb | 4 +--- spec/spec_helper.rb | 1 + spec/support/features/datepicker_helper.rb | 9 +++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 spec/support/features/datepicker_helper.rb diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index f51de02e5f..7626329ef2 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -454,9 +454,7 @@ feature 'Subscriptions' do select2_select payment_method.name, from: "payment_method_id" select2_select shipping_method.name, from: "shipping_method_id" find_field("begins_at").click - within(".ui-datepicker-calendar") do - find(".ui-datepicker-today").click - end + choose_today_from_datepicker click_button "Next" expect(page).to have_content "BILLING ADDRESS" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 25dac67716..f404f4c95a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -136,6 +136,7 @@ RSpec.configure do |config| config.extend Spree::Api::TestingSupport::Setup, :type => :controller config.include Spree::Api::TestingSupport::Helpers, :type => :controller config.include OpenFoodNetwork::ControllerHelper, :type => :controller + config.include Features::DatepickerHelper, type: :feature config.include OpenFoodNetwork::FeatureToggleHelper config.include OpenFoodNetwork::FiltersHelper config.include OpenFoodNetwork::EnterpriseGroupsHelper diff --git a/spec/support/features/datepicker_helper.rb b/spec/support/features/datepicker_helper.rb new file mode 100644 index 0000000000..df966cfa05 --- /dev/null +++ b/spec/support/features/datepicker_helper.rb @@ -0,0 +1,9 @@ +module Features + module DatepickerHelper + def choose_today_from_datepicker + within(".ui-datepicker-calendar") do + find(".ui-datepicker-today").click + end + end + end +end From c33808e8f5659796339f8db0712e330ea112ada6 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 21 Jan 2019 00:14:12 +1100 Subject: [PATCH 019/108] Extract some actions in tests into methods --- spec/features/admin/subscriptions_spec.rb | 31 +++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index 7626329ef2..99f32162f6 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -449,27 +449,22 @@ feature 'Subscriptions' do end it "permit creating and editing of the subscription" do - select2_select customer.email, from: "customer_id" - select2_select schedule.name, from: "schedule_id" - select2_select payment_method.name, from: "payment_method_id" - select2_select shipping_method.name, from: "shipping_method_id" - find_field("begins_at").click - choose_today_from_datepicker + # Fill in other details + fill_in_subscription_basic_details click_button "Next" - expect(page).to have_content "BILLING ADDRESS" click_button "Next" # Add products expect(page).to have_content "NAME OR SKU" add_variant_to_subscription shop_variant, 3 - expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 1 + expect_not_in_open_or_upcoming_order_cycle_warning 1 add_variant_to_subscription permitted_supplier_variant, 4 - expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 2 + expect_not_in_open_or_upcoming_order_cycle_warning 2 add_variant_to_subscription incoming_exchange_variant, 5 - expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 3 + expect_not_in_open_or_upcoming_order_cycle_warning 3 add_variant_to_subscription outgoing_exchange_variant, 6 - expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: 3 + expect_not_in_open_or_upcoming_order_cycle_warning 3 click_button "Next" # Submit form @@ -503,6 +498,20 @@ feature 'Subscriptions' do end end + def fill_in_subscription_basic_details + select2_select customer.email, from: "customer_id" + select2_select schedule.name, from: "schedule_id" + select2_select payment_method.name, from: "payment_method_id" + select2_select shipping_method.name, from: "shipping_method_id" + + find_field("begins_at").click + choose_today_from_datepicker + end + + def expect_not_in_open_or_upcoming_order_cycle_warning(count) + expect(page).to have_content variant_not_in_open_or_upcoming_order_cycle_warning, count: count + end + def add_variant_to_subscription(variant, quantity) row_count = all("#subscription-line-items .item").length variant_name = variant.full_name.present? ? "#{variant.name} - #{variant.full_name}" : variant.name From 4a58aedf09b898a175132386bf82af1f22fbc6fb Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 22 Jan 2019 16:09:20 +0800 Subject: [PATCH 020/108] Use clearer translation for "No Upcoming OC" --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 366a481bac..f0c72f289a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1103,7 +1103,7 @@ en: details: Details address: Address products: Products - no_open_or_upcoming_order_cycle: "No Upcoming OC" + no_open_or_upcoming_order_cycle: "No Upcoming Order Cycle" product_already_in_order: This product has already been added to the order. Please edit the quantity directly. orders: number: Number From 5992bba97b80afa7264763c5ba5a92d647ed2c4f Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sat, 26 Jan 2019 03:29:15 +0800 Subject: [PATCH 021/108] Import color variables in new page SCSS partials "admin/all.css" which imports these SCSS partials already imports the color variables in "admin/variables", so actually there should be no need to import the variables again. However, "application.css" calls "require_tree", which means asset precompilation through Sprockets would attempt to compile each of the SCSS partials individually. When compiled individually, the color variables are not available to these partials. This is a quick solution to allow precompilation of "application.css" to complete. --- app/assets/stylesheets/admin/pages/subscription_form.css.scss | 2 ++ app/assets/stylesheets/admin/pages/subscription_review.css.scss | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/assets/stylesheets/admin/pages/subscription_form.css.scss b/app/assets/stylesheets/admin/pages/subscription_form.css.scss index 02c9fd783e..b892c4f17e 100644 --- a/app/assets/stylesheets/admin/pages/subscription_form.css.scss +++ b/app/assets/stylesheets/admin/pages/subscription_form.css.scss @@ -1,3 +1,5 @@ +@import '../variables'; + .admin-subscription-form-subscription-line-items { .not-in-open-and-upcoming-order-cycles-warning { color: $warning-red; diff --git a/app/assets/stylesheets/admin/pages/subscription_review.css.scss b/app/assets/stylesheets/admin/pages/subscription_review.css.scss index ba3280aa2b..76008afc0f 100644 --- a/app/assets/stylesheets/admin/pages/subscription_review.css.scss +++ b/app/assets/stylesheets/admin/pages/subscription_review.css.scss @@ -1,3 +1,5 @@ +@import '../variables'; + .admin-subscription-review-subscription-line-items { .not-in-open-and-upcoming-order-cycles-warning { color: $warning-red; From 8543bb7cfb101afbc8cde0c963ae1e150ffecdb8 Mon Sep 17 00:00:00 2001 From: Isaac Haseley Date: Wed, 23 Jan 2019 12:23:35 -0600 Subject: [PATCH 022/108] Add missing 'active products' translation on dashboard --- app/views/spree/admin/overview/_products.html.haml | 2 +- config/locales/en.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/spree/admin/overview/_products.html.haml b/app/views/spree/admin/overview/_products.html.haml index 37de2f11d4..459aa3483c 100644 --- a/app/views/spree/admin/overview/_products.html.haml +++ b/app/views/spree/admin/overview/_products.html.haml @@ -12,7 +12,7 @@ - if @product_count > 0 %div.seven.columns.alpha.list-item %span.six.columns.alpha - = "You have #{@product_count} active product#{@product_count > 1 ? "s" : ""}." + = t(".you_have") + " #{@product_count} #{@product_count > 1 ? t(".active_products") : t(".active_product")}." %span.one.column.omega %span.icon-ok-sign %a.seven.columns.alpha.button.bottom.blue{ href: "#{admin_products_path}" } diff --git a/config/locales/en.yml b/config/locales/en.yml index 0bde4fb005..7557acbb84 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2716,6 +2716,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using distributor: "Distributor:" order_cycle: "Order cycle:" overview: + products: + you_have: "You have" + active_products: "active products" + active_product: "active product" order_cycles: order_cycles: "Order Cycles" order_cycles_tip: "Order cycles determine when and where your products are available to customers." From a18c98191a8503a8f6c74c26593002ea76dd92fc Mon Sep 17 00:00:00 2001 From: Isaac Haseley Date: Fri, 25 Jan 2019 14:51:19 -0600 Subject: [PATCH 023/108] Better accomodate different sentence structures --- app/views/spree/admin/overview/_products.html.haml | 5 ++++- config/locales/en.yml | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/views/spree/admin/overview/_products.html.haml b/app/views/spree/admin/overview/_products.html.haml index 459aa3483c..010bcfcf77 100644 --- a/app/views/spree/admin/overview/_products.html.haml +++ b/app/views/spree/admin/overview/_products.html.haml @@ -12,7 +12,10 @@ - if @product_count > 0 %div.seven.columns.alpha.list-item %span.six.columns.alpha - = t(".you_have") + " #{@product_count} #{@product_count > 1 ? t(".active_products") : t(".active_product")}." + - if @product_count == 1 + = t(".active_products_count_single", product_count: @product_count ) + - else + = t(".active_products_count_multiple", product_count: @product_count ) %span.one.column.omega %span.icon-ok-sign %a.seven.columns.alpha.button.bottom.blue{ href: "#{admin_products_path}" } diff --git a/config/locales/en.yml b/config/locales/en.yml index 7557acbb84..7c14048cdd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2717,9 +2717,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using order_cycle: "Order cycle:" overview: products: - you_have: "You have" - active_products: "active products" - active_product: "active product" + active_products_count_single: "You have %{product_count} active product" + active_products_count_multiple: "You have %{product_count} active products" order_cycles: order_cycles: "Order Cycles" order_cycles_tip: "Order cycles determine when and where your products are available to customers." From 73a58435a8f76c06ad58092165fc57e803ed3ff6 Mon Sep 17 00:00:00 2001 From: Isaac Haseley Date: Thu, 31 Jan 2019 21:45:12 -0600 Subject: [PATCH 024/108] Adopt one/other pluralization --- app/views/spree/admin/overview/_products.html.haml | 5 +---- config/locales/en.yml | 5 +++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/spree/admin/overview/_products.html.haml b/app/views/spree/admin/overview/_products.html.haml index 010bcfcf77..effc671162 100644 --- a/app/views/spree/admin/overview/_products.html.haml +++ b/app/views/spree/admin/overview/_products.html.haml @@ -12,10 +12,7 @@ - if @product_count > 0 %div.seven.columns.alpha.list-item %span.six.columns.alpha - - if @product_count == 1 - = t(".active_products_count_single", product_count: @product_count ) - - else - = t(".active_products_count_multiple", product_count: @product_count ) + = t(".active_products", count: @product_count ) %span.one.column.omega %span.icon-ok-sign %a.seven.columns.alpha.button.bottom.blue{ href: "#{admin_products_path}" } diff --git a/config/locales/en.yml b/config/locales/en.yml index 7c14048cdd..59b519de8f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2717,8 +2717,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using order_cycle: "Order cycle:" overview: products: - active_products_count_single: "You have %{product_count} active product" - active_products_count_multiple: "You have %{product_count} active products" + active_products: + one: "You have one active product" + other: "You have %{count} active products" order_cycles: order_cycles: "Order Cycles" order_cycles_tip: "Order cycles determine when and where your products are available to customers." From 09d3cea83097e5d9102b12cb1054fa72fdf9da4f Mon Sep 17 00:00:00 2001 From: Isaac Haseley Date: Fri, 1 Feb 2019 09:22:26 -0600 Subject: [PATCH 025/108] Lazy-load one more translation --- app/views/spree/admin/overview/_products.html.haml | 2 +- config/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/spree/admin/overview/_products.html.haml b/app/views/spree/admin/overview/_products.html.haml index effc671162..539eecd943 100644 --- a/app/views/spree/admin/overview/_products.html.haml +++ b/app/views/spree/admin/overview/_products.html.haml @@ -21,7 +21,7 @@ - else %div.seven.columns.alpha.list-item.red %span.six.columns.alpha - = t "spree_admin_enterprises_any_active_products_text" + = t(".active_products", count: @product_count ) %span.one.column.omega %span.icon-remove-sign %a.seven.columns.alpha.button.bottom.red{ href: "#{new_admin_product_path}" } diff --git a/config/locales/en.yml b/config/locales/en.yml index 59b519de8f..4c76f68854 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2149,7 +2149,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using spree_admin_enterprises_none_text: "You don't have any enterprises yet" spree_admin_enterprises_tabs_hubs: "HUBS" spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" - spree_admin_enterprises_any_active_products_text: "You don't have any active products." spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" @@ -2718,6 +2717,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using overview: products: active_products: + zero: "You don't have any active products." one: "You have one active product" other: "You have %{count} active products" order_cycles: From 2c3eeec2b9e4e2b54788f4f73f236c7053b337bf Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Fri, 1 Feb 2019 21:44:30 +0000 Subject: [PATCH 026/108] Update cancan permissions for second iteration of bulk invoices --- app/controllers/spree/admin/invoices_controller.rb | 1 + app/models/spree/ability_decorator.rb | 3 ++- spec/controllers/spree/admin/invoices_controller_spec.rb | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/admin/invoices_controller.rb b/app/controllers/spree/admin/invoices_controller.rb index 230d01322b..710fda1a3a 100644 --- a/app/controllers/spree/admin/invoices_controller.rb +++ b/app/controllers/spree/admin/invoices_controller.rb @@ -2,6 +2,7 @@ module Spree module Admin class InvoicesController < Spree::Admin::BaseController respond_to :json + authorize_resource class: false def create invoice_service = BulkInvoiceService.new diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 2c68470840..f6f37560ce 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -210,9 +210,10 @@ class AbilityDecorator # during the order creation process from the admin backend order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user) end - can [:admin, :bulk_management, :managed, :bulk_invoice], Spree::Order do + can [:admin, :bulk_management, :managed], Spree::Order do user.admin? || user.enterprises.any?(&:is_distributor) end + can [:admin, :create, :show, :poll], :invoice can [:admin, :visible], Enterprise can [:admin, :index, :create, :update, :destroy], :line_item can [:admin, :index, :create], Spree::LineItem diff --git a/spec/controllers/spree/admin/invoices_controller_spec.rb b/spec/controllers/spree/admin/invoices_controller_spec.rb index 1eff65ba2c..9a7453fb70 100644 --- a/spec/controllers/spree/admin/invoices_controller_spec.rb +++ b/spec/controllers/spree/admin/invoices_controller_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe Spree::Admin::InvoicesController, type: :controller do let(:order) { create(:order_with_totals_and_distribution) } - let(:user) { create(:admin_user) } + let(:enterprise_user) { create(:user) } + let!(:enterprise) { create(:enterprise, owner: enterprise_user) } before do - allow(controller).to receive(:spree_current_user) { user } + allow(controller).to receive(:spree_current_user) { enterprise_user } end describe "#create" do From 3eedee313e352fb2a677f72ddab23faf91a07c49 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 31 Jan 2019 21:21:30 +1100 Subject: [PATCH 027/108] Wait for datepicker to associate and open before selecting date --- spec/features/admin/bulk_order_management_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index d261405a4d..063500800f 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -743,6 +743,9 @@ feature %q{ current_month = Time.zone.today.strftime("%B") target_month = date.strftime("%B") + # Wait for datepicker to open and be associated to the datepicker trigger. + expect(page).to have_selector("#ui-datepicker-div") + find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-prev').click if current_month != target_month find('#ui-datepicker-div .ui-datepicker-calendar .ui-state-default', text: date.strftime("%e").to_s.strip, exact_text: true).click end From bb51f7e36b4d8264bb5b1a53a147cc776b9a79f2 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 31 Jan 2019 21:24:11 +1100 Subject: [PATCH 028/108] Improve sync between keyword filter and selecting all --- spec/features/admin/bulk_order_management_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 063500800f..70caed9190 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -567,6 +567,7 @@ feature %q{ context "when a filter has been applied" do it "only toggles checkboxes which are in filteredLineItems" do fill_in "quick_search", with: o1.number + expect(page).to have_no_selector "tr#li_#{li2.id}" check "toggle_bulk" fill_in "quick_search", with: '' expect(find("tr#li_#{li1.id} input[type='checkbox'][name='bulk']").checked?).to be true @@ -577,11 +578,13 @@ feature %q{ it "only applies the delete action to filteredLineItems" do check "toggle_bulk" fill_in "quick_search", with: o1.number + expect(page).to have_no_selector "tr#li_#{li2.id}" 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 have_no_selector "tr#li_#{li1.id}" + fill_in "quick_search", with: '' expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li1.id}" end end end From fdede83086f35b1ec9677f14e9fc5c5e1160ad14 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 31 Jan 2019 19:24:22 +0800 Subject: [PATCH 029/108] Support selecting date in next months This was causing failures when selecting tomorrow when running tests on the last day of the month. --- .../admin/bulk_order_management_spec.rb | 6 ++--- spec/support/features/datepicker_helper.rb | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 70caed9190..b04acb29e0 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -743,13 +743,11 @@ feature %q{ end def select_date(date) - current_month = Time.zone.today.strftime("%B") - target_month = date.strftime("%B") - # Wait for datepicker to open and be associated to the datepicker trigger. expect(page).to have_selector("#ui-datepicker-div") - find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-prev').click if current_month != target_month + navigate_datepicker_to_month date + find('#ui-datepicker-div .ui-datepicker-calendar .ui-state-default', text: date.strftime("%e").to_s.strip, exact_text: true).click end end diff --git a/spec/support/features/datepicker_helper.rb b/spec/support/features/datepicker_helper.rb index df966cfa05..60221c6635 100644 --- a/spec/support/features/datepicker_helper.rb +++ b/spec/support/features/datepicker_helper.rb @@ -5,5 +5,29 @@ module Features find(".ui-datepicker-today").click end end + + def navigate_datepicker_to_month(date, reference_date = Time.zone.today) + month_and_year = date.strftime("%B %Y") + + until datepicker_month_and_year == month_and_year.upcase + if date < reference_date + navigate_datepicker_to_previous_month + elsif date > reference_date + navigate_datepicker_to_next_month + end + end + end + + def navigate_datepicker_to_previous_month + find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-prev').click + end + + def navigate_datepicker_to_next_month + find('#ui-datepicker-div .ui-datepicker-header .ui-datepicker-next').click + end + + def datepicker_month_and_year + find("#ui-datepicker-div .ui-datepicker-title").text + end end end From ed5856afa4ed5385007d1d95b5937fb75c3bccfd Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 31 Jan 2019 23:12:03 +0800 Subject: [PATCH 030/108] Compile edit link with higher priority --- app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 50aa10634f..fd7cd1dc37 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -181,6 +181,6 @@ %input.show-dirty{ :type => 'text', :name => 'price', :id => 'price', :ng => { value: 'line_item.price * line_item.quantity | currency:""', readonly: "true", class: '{"update-error": line_item.errors.price}' } } %span.error{ ng: { bind: 'line_item.errors.price' } } %td.actions - %a{ href: "/admin/orders/{{line_item.order.number}}/edit", :class => "edit-order icon-edit no-text", 'confirm-link-click' => 'confirmRefresh()' } + %a{ ng: { href: "/admin/orders/{{line_item.order.number}}/edit" }, :class => "edit-order icon-edit no-text", 'confirm-link-click' => 'confirmRefresh()' } %td.actions %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } From 35ecbe1584ca59d36259153b809da5035099aa2f Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 1 Feb 2019 00:17:53 +0800 Subject: [PATCH 031/108] Compile row ID with higher priority --- app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index fd7cd1dc37..32d164adc1 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -157,7 +157,7 @@ = t("admin.orders.bulk_management.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:sorting.predicate:sorting.reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", ng: { attr: { id: "li_{{line_item.id}}" } } } %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true } %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} From 428e58f8f7dc047a74bf7444aa9d0f3c49bdfaa0 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 1 Feb 2019 00:36:32 +0800 Subject: [PATCH 032/108] Remove unused have_no_selector argument in feature test --- spec/features/admin/bulk_order_management_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index b04acb29e0..26dbc2aab0 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -439,7 +439,7 @@ feature %q{ expect(page).to have_selector "tr#li_#{li3.id}" fill_in "quick_search", :with => o1.email expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to have_no_selector "tr#li_#{li2.id}", true + expect(page).to have_no_selector "tr#li_#{li2.id}" expect(page).to have_no_selector "tr#li_#{li3.id}" end end From 00304286478d82ed9a10cff68f04dcd95dda39b5 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 1 Feb 2019 01:45:27 +0800 Subject: [PATCH 033/108] Do not show table until first time dereferencing is done --- app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 32d164adc1..950ec2a45a 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -112,7 +112,7 @@ .margin-bottom-50{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' } %form{ name: 'bulk_order_form' } - %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } + %table.index#listing_orders.bulk{ :class => "sixteen columns alpha", ng: { show: "initialized" } } %thead %tr{ ng: { controller: "ColumnsCtrl" } } %th.bulk From 93aabf674152d157415a5a3ac867f81bceccb86c Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 7 Feb 2019 19:26:00 +0000 Subject: [PATCH 034/108] Update all locales with the latest Transifex translations --- config/locales/fr_BE.yml | 2 +- config/locales/it.yml | 45 ++++++++++++++++++++++++++++++++++++++++ config/locales/nb.yml | 2 ++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/config/locales/fr_BE.yml b/config/locales/fr_BE.yml index c5cfc0054f..94291967f1 100644 --- a/config/locales/fr_BE.yml +++ b/config/locales/fr_BE.yml @@ -1105,7 +1105,7 @@ fr_BE: footer_legal_call: "Lire nos" 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}." + footer_legal_text_html: "Open Food Network est une plateforme logicielle open source et libre. Nos données sont protégées sous licence %{content_license} et notre code sous %{code_license}." footer_data_text_with_privacy_policy_html: "Nous prenons soin de vos données. Voir notre %{privacy_policy} et %{cookies_policy}." footer_data_text_without_privacy_policy_html: "Nous prenons soin de vos données. Voir notre %{cookies_policy}." footer_data_privacy_policy: "politique de confidentialité" diff --git a/config/locales/it.yml b/config/locales/it.yml index ddb3185edc..5a123f6480 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -602,6 +602,7 @@ it: desc_long_placeholder: Racconta di te ai consumatori. Questa informazione comparirà nel tuo profilo pubblico. business_details: abn: ABN + abn_placeholder: es. 99 123 456 789 acn: ACN acn_placeholder: es. 123 456 789 display_invoice_logo: Mostra logo nelle fatture @@ -1131,6 +1132,7 @@ it: total_excl_tax: "Totale (Tasse escl.):" total_incl_tax: "Totale (Tasse incl.):" abn: "CF:" + acn: "ACN:" invoice_issued_on: "Fattura emessa il" order_number: "Numero fattura:" date_of_transaction: "Data della transazione:" @@ -1149,7 +1151,9 @@ it: menu_5_title: "About" menu_5_url: "http://www.openfoodnetwork.org/" menu_6_title: "Connetti" + menu_6_url: "https://openfoodnetwork.org/au/connect/" menu_7_title: "Impara" + menu_7_url: "https://openfoodnetwork.org/au/learn/" logo: "Logo (640x130)" logo_mobile: "Mobile logo (75x26)" logo_mobile_svg: "Mobile logo (SVG)" @@ -2216,6 +2220,9 @@ it: validation_msg_tax: "^Categoria d'imposta richiesta" validation_msg_tax_category_cant_be_blank: "^Categoria di tassa non può essere vuoto" validation_msg_is_associated_with_an_exising_customer: "è associato con un cliente esistente" + content_configuration_pricing_table: "(TODO: tabella dei prezzi)" + content_configuration_case_studies: "(TODO: Casi studio)" + content_configuration_detail: "(TODO: Dettaglio)" enterprise_name_error: "è già stato utilizzato. Se questo è il nome della tua azienda e vorresti reclamarne la proprietà, o se vuoi contattare questa azienda, puoi contattare l'attuale referente di questo profilo a %{email}." enterprise_owner_error: "^%{email}non può gestire altre aziende (il limite è %{enterprise_limit})." enterprise_role_uniqueness_error: "^Questo ruolo è già presente." @@ -2253,6 +2260,7 @@ it: back_to_orders_list: "Indietro alla lista delle gentili richieste" no_orders_found: "Nessuna gentile richiesta trovata" order_information: "Informazioni Gentile Richiesta" + date_completed: "Data di completamento" amount: "Quantità" state_names: ready: Pronto @@ -2275,6 +2283,7 @@ it: choose: Scegli resolve_errors: 'Per favore risolvi i seguenti errori:' more_items: "+ %{count} ancora" + default_card_updated: Carta predefinita aggiornata admin: enterprise_limit_reached: "Hai raggiunto il limite standard di aziende per account. Scrivi a %{contact_email} se hai bisogno di aumentarlo." modals: @@ -2368,14 +2377,18 @@ it: non_producer_example: es. Botteghe, Food Coop, GAS enterprise_status: status_title: "%{name} è impostato e pronto a partire!" + severity: Gravità description: Descrizione resolve: Risolvi new_tag_rule_dialog: select_rule_type: "Seleziona un tipo di regola:" orders: index: + per_page: "%{results} per pagina" view_file: Vedi file compiling_invoices: Compilazione fatture + bulk_invoice_created: Fattura all'ingrosso creata + bulk_invoice_failed: Creazione fattura all'ingrosso fallita please_wait: Si prega di attendere che il PDF sia pronto prima di chiudere questo modale resend_user_email_confirmation: resend: "Invia nuovamente" @@ -2394,6 +2407,7 @@ it: 'yes': "A richiesta" variant_overrides: on_demand: + use_producer_settings: "Usa le impostazioni dello stock del produttore" 'yes': "Sì" 'no': "No" inventory_products: "Inventario Prodotti" @@ -2401,6 +2415,9 @@ it: new_products: "Nuovi Prodotti" reset_stock_levels: Resetta le quantità disponibili alla quantità predefinita changes_to: Cambia in + one_override: una si è sovrascritta + overrides: sovrascrive + remain_unsaved: restano da memorizzare no_changes_to_save: Nessuna modifica da salvare. no_authorisation: "Non abbiamo l'autorizzazione per salvare queste modifiche. " some_trouble: "Abbiamo avuto problemi durante il salvataggio: %{errors}" @@ -2449,16 +2466,23 @@ it: admin: orders: index: + listing_orders: "Listino Ordini" new_order: "Nuovo ordine" + capture: "Cattura" ship: "Spedizione" edit: "Modifica" + note: "Nota" + first: "Primo" + last: "Ultimo" previous: "Precedente" next: "Prossimo" loading: "Caricamento" no_orders_found: "Nessuna gentile richiesta trovata" results_found: "%{number} Risultati trovati." + viewing: "Guardando da %{start} a %{end}" print_invoices: "Stampa fatture" invoice: + issued_on: Emesso il tax_invoice: FATTURA DELLE TASSE code: Codice from: Da @@ -2490,12 +2514,18 @@ it: stripe_disabled_msg: I pagamenti Stripe sono stati disabilitati dall'amministratore di sistema. request_failed_msg: Spiacenti, qualcosa è andato storto mentre cercavamo di verificare i dettagli dell'account con Stripe... account_missing_msg: Non esiste un account Stripe per questa azienda. + connect_one: Connetti One access_revoked_msg: L'accesso a questo account Stripe è stato revocato, per favore ricollega il tuo account. status: Stato connected: Connesso account_id: Account ID business_name: Ragione sociale charges_enabled: Cambi Consentiti + payments: + source_forms: + stripe: + error_saving_payment: Errore memorizzando il pagamento + submitting_payment: Eseguendo il pagamento products: new: title: 'Nuovo prodotto' @@ -2514,7 +2544,9 @@ it: inherits_properties?: Eredita proprietà? available_on: Disponibile il av_on: "Disp. il" + import_date: "Data di importazione" products_variant: + variant_has_n_overrides: "Questa variante ha %{n} sostituzione/i" new_variant: "Nuova variante" product_name: Nome Prodotto primary_taxon_form: @@ -2523,14 +2555,19 @@ it: group_buy: "Acquisto di gruppo?" display_as: display_as: Visualizza come + reports: + table: + select_and_search: "Seleziona filtri e clicca su %{option} per accedere al tuo dato" users: index: + listing_users: "Elenco Utenti" new_user: "Nuovo utente" user: "Utente" enterprise_limit: "Limite azienda" search: "Cerca" email: "Email" edit: + editing_user: "Modificando Utente" back_to_users_list: "Torna all'elenco degli utenti" general_settings: "Impostazioni generali" form: @@ -2548,6 +2585,7 @@ it: edit: legal_settings: "Impostazioni Legali" cookies_consent_banner_toggle: "Mostra banner di consenso per i cookie" + privacy_policy_url: "Privacy Policy URL" enterprises_require_tos: "Le aziende devono accettare i Termini di Servizio" cookies_policy_matomo_section: "Visualizza la sezione di Matomo nella pagina della cookie policy" cookies_policy_ga_section: "Visualizza la sezione di Google Analytics nella pagina della cookie policy" @@ -2556,7 +2594,12 @@ it: payment: stripe: choose_one: Scegli uno + enter_new_card: Inserire dettagli per una nuova carta + used_saved_card: "usare la carta salvata" + or_enter_new_card: "Oppure, inserire dettagli di una nuova carta" + remember_this_card: Ricordare questa Carta? date_picker: + format: '%Y-%m-%d' js_format: 'aa-mm-gg' inventory: Inventario orders: @@ -2567,6 +2610,7 @@ it: order_mailer: invoice_email: hi: "Ciao %{name}" + invoice_attached_text: 'Aggiunge una fattura per il tuo recente ordine di ' order_state: address: indirizzo adjustments: aggiustamenti @@ -2659,5 +2703,6 @@ it: authorised_shops: Negozi autorizzati authorised_shops: shop_name: "Nome del negozio" + allow_charges?: "Consentire ricarichi" localized_number: invalid_format: 'Formato non valido: inserire un numero.' diff --git a/config/locales/nb.yml b/config/locales/nb.yml index 9a19f953d9..8490c91c8f 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -1027,6 +1027,7 @@ nb: this_is_an_estimate: | De viste prisene er bare et estimat og beregnet på det tidspunktet abonnementet endres. Hvis du endrer priser eller avgifter, vil ordrer bli oppdatert, men abonnementet vil fortsatt vise de gamle verdiene. + not_in_open_and_upcoming_order_cycles_warning: "Det er ingen åpne eller kommende bestillingsrunder for dette produktet." details: details: Detaljer invalid_error: Oops! Vennligst fyll inn alle obligatoriske felter... @@ -1041,6 +1042,7 @@ nb: details: Detaljer address: Adresse products: Produkter + no_open_or_upcoming_order_cycle: "Ingen kommende bestillingsrunde" product_already_in_order: Dette produktet er allerede lagt til i bestillingen. Vennligst rediger mengden direkte. orders: number: Antall From 8ff5f9055b70de7ae229f2588e194001ee6c8ff5 Mon Sep 17 00:00:00 2001 From: Danni M Date: Fri, 8 Feb 2019 16:58:32 +1100 Subject: [PATCH 035/108] Update issue templates I've created 3 types of templates based on those in the wiki and the default (bug) issue template. These can be edited or additional ones added at your leisure. I also added the OFN software instance and version to the bugs template --- .github/ISSUE_TEMPLATE/bug_report.md | 61 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature-template.md | 35 +++++++++++++ .github/ISSUE_TEMPLATE/story-template.md | 18 +++++++ 3 files changed, 114 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature-template.md create mode 100644 .github/ISSUE_TEMPLATE/story-template.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..76ce58fccf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,61 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +## Description + + + +## Expected Behavior + + + +## Actual Behaviour + + + +## Steps to Reproduce + + + +1. +2. +3. +4. + +## Animated Gif/Screenshot + + + +## Context + + + +## Severity + + +## Your Environment + + +* Version used: +* Browser name and version: +* Operating System and version (desktop or mobile): +* OFN Platform instance where you discovered the bug, and which version of the software they are using. + +## Possible Fix + diff --git a/.github/ISSUE_TEMPLATE/feature-template.md b/.github/ISSUE_TEMPLATE/feature-template.md new file mode 100644 index 0000000000..9fb6cc1741 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-template.md @@ -0,0 +1,35 @@ +--- +name: Feature template +about: Create feature epics that detail the larger feature or functionality to be + delivered. +title: '' +labels: '' +assignees: '' + +--- + +## What is the problem we are solving + + +## Success factors = expected outcome + + +## Useful information for inception + + +## Link to the "Product Development - Backlog" item in Discourse + + Add a custom footer + Pages 70 +Home +Development environment setup + +macOS (Sierra, HighSierra and Mojave) +OS X (El Capitan) +OS X (Mavericks) +Ubuntu +On Heroku +Rubocop +General guidelines + +Spree Commerce customisation diff --git a/.github/ISSUE_TEMPLATE/story-template.md b/.github/ISSUE_TEMPLATE/story-template.md new file mode 100644 index 0000000000..48a1c33493 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/story-template.md @@ -0,0 +1,18 @@ +--- +name: Story template +about: Create stories that are small chunks of work that devs will pick up and deliver +title: '' +labels: '' +assignees: '' + +--- + +**## Description** + + +**## Acceptance Criteria** + From 73597899038ffe0b049f42fb5ecc438e9672fa8a Mon Sep 17 00:00:00 2001 From: Pau Perez Date: Fri, 8 Feb 2019 15:22:59 +0100 Subject: [PATCH 036/108] Import Catalan translation from Transifex Although we've started translating to Catalan and focused on it recently, we never imported the file in the app thus the language menu showed "English" for the "ca" locale and clicking on it switched to English as well instead of Catalan. --- config/locales/ca.yml | 2649 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2649 insertions(+) create mode 100644 config/locales/ca.yml diff --git a/config/locales/ca.yml b/config/locales/ca.yml new file mode 100644 index 0000000000..4c2341161a --- /dev/null +++ b/config/locales/ca.yml @@ -0,0 +1,2649 @@ +ca: + language_name: "Català" + activerecord: + attributes: + spree/order: + payment_state: Estat del Pagament + shipment_state: Estat de la Tramesa + completed_at: Completat a + number: Número + state: Estat + email: Correu electrònic de la consumidora + spree/payment: + amount: Import + order_cycle: + orders_close_at: Data de tancament + errors: + models: + spree/user: + attributes: + email: + taken: "Ja hi ha un compte per a aquest correu electrònic. Si us plau, inicia sessió o restableix la contrasenya." + spree/order: + no_card: No hi ha targetes de crèdit autoritzades disponibles per carregar + order_cycle: + attributes: + orders_close_at: + after_orders_open_at: s'ha de fer amb el termini obert + variant_override: + count_on_hand: + using_producer_stock_settings_but_count_on_hand_set: "ha d'estar en blanc perquè s'utilitza la configuració de l'inventari de la productora" + on_demand_but_count_on_hand_set: "ha d'estar en blanc si és sota demanda" + limited_stock_but_no_count_on_hand: "cal especificar-se perquè força existències limitades" + activemodel: + errors: + models: + subscription_validator: + attributes: + subscription_line_items: + at_least_one_product: "^Afegiu com a mínim un producte" + not_available: "^ %{name} no està disponible a l'horari seleccionat" + ends_at: + after_begins_at: "ha de ser després de" + customer: + does_not_belong_to_shop: "no pertany a %{shop}" + schedule: + not_coordinated_by_shop: "no està coordinat per %{shop}" + payment_method: + not_available_to_shop: "no està disponible per %{shop}" + invalid_type: "el mètode ha de ser Efectiu o Stripe" + charges_not_allowed: "^ Els càrrecs de la targeta de crèdit no estan permesos per aquest consumidora" + no_default_card: "^ No hi ha cap targeta predeterminada disponible per a aquesta consumidora" + shipping_method: + not_available_to_shop: "no està disponible per %{shop}" + devise: + confirmations: + send_instructions: "Rebreu un correu electrònic amb instruccions sobre com confirmar el vostre compte en pocs minuts." + failed_to_send: "S'ha produït un error en enviar el correu electrònic de confirmació." + resend_confirmation_email: "Reenvia el correu electrònic de confirmació." + confirmed: "Gràcies per confirmar el vostre correu electrònic. Ara podeu iniciar sessió." + not_confirmed: "No s'ha pogut confirmar la vostra adreça de correu electrònic. Potser ja heu completat aquest pas?" + user_registrations: + spree_user: + signed_up_but_unconfirmed: "S'ha enviat un missatge amb un enllaç de confirmació a la teva adreça de correu electrònic. Obre l'enllaç per activar el teu compte." + unknown_error: "S'ha produït un error en crear el teu compte. Comprova la teva adreça de correu electrònic i torna-ho a provar." + failure: + invalid: | + Correu electrònic o contrasenya no vàlids. + Va ser un convitat la última vegada? Potser vostè necessita crear un compte o restablir la contrasenya. + unconfirmed: "Heu de confirmar el vostre compte abans de continuar." + already_registered: "Aquesta adreça electrònica ja està registrada. Inicieu sessió per continuar o torneu endarrere per utilitzar una altra adreça de correu electrònic." + user_passwords: + spree_user: + updated_not_active: "La vostra contrasenya s'ha restablert, però el vostre correu electrònic encara no s'ha confirmat." + models: + order_cycle: + cloned_order_cycle_name: "CÒPIA DE %{order_cycle}" + enterprise_mailer: + confirmation_instructions: + subject: "Sisplau, confirma l'adreça electrònica d'%{enterprise}" + welcome: + subject: "%{enterprise} és ara %{sitename}" + invite_manager: + subject: "%{enterprise} t'ha convidat a ser administrador" + order_mailer: + cancel_email: + dear_customer: "Benvolguda consumidora:" + instructions: "S'ha cancel·lat la vostra comanda. Conserveu aquesta informació com a comprovant de la cancel·lació." + order_summary_canceled: "Resum de comanda [CANCEL·LAT]" + subject: "Cancel·lació de la comanda" + subtotal: "Total parcial: %{subtotal}" + total: "Total comanda: %{total}" + producer_mailer: + order_cycle: + subject: "Informe del cicle de comanda per %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Benvolguda consumidora:" + instructions: "La vostra comanda s'ha enviat" + shipment_summary: "Detalls de l'enviament" + subject: "Notificació d'enviament" + thanks: "Gràcies per la teva compra." + track_information: "Informació del seguiment: %{tracking}" + track_link: "Enllaç del seguiment: %{url}" + subscription_mailer: + placement_summary_email: + subject: Un resum de les comandes per subscripció recentment establertes + greeting: "Hola %{name}," + intro: "A continuació es mostra un resum de les comandes per subscripció que s'acaben de col·locar per %{shop}." + confirmation_summary_email: + subject: Un resum de les comandes per subscripció confirmades recentment + greeting: "Hola %{name}," + intro: "A continuació es mostra un resum de les comandes per subscripció que acaben de finalitzar per %{shop}." + summary_overview: + total: S'ha marcat un total de subscripcions %{count} per al processament automàtic. + success_zero: D'aquestes, cap d'elles s'ha processat correctament. + success_some: D'aquests, %{count} s'han processat correctament. + success_all: Tots s'han processat correctament. + issues: A continuació es detallen els problemes trobats. + summary_detail: + no_message_provided: No s'ha proporcionat cap missatge d'error + changes: + title: Stock insuficient (%{count} comandes) + explainer: Aquestes comandes es van processar, però no hi havia suficients estocs per a alguns articles sol·licitats + empty: + title: Sense Stock (%{count} comandes) + explainer: Aquestes comandes no s'han pogut processar perquè no hi ha disponible cap estoc per a cap dels elements sol·licitats + complete: + title: Ja està processat (%{count} comandes) + explainer: Aquestes comandes ja estaven marcades com a completes i, per tant, no es modificarán + processing: + title: S'ha trobat un error (%{count} comandes) + explainer: El processament automàtic d'aquestes comandes ha fallat a causa d'un error. L'error es mostra sempre que sigui possible. + failed_payment: + title: Pagament erroni (%{count} comandes) + explainer: La tramitació automàtica del pagament d'aquestes comandes va fallar a causa d'un error. L'error es mostra sempre que sigui possible. + other: + title: Altres errors (%{count} comandes) + explainer: El processament automàtic d'aquestes comandes va fallar per un motiu desconegut. Això no hauria de passar, si us plau, contacteu amb nosaltres si esteu veient això. + home: "OFN" + title: Open Food Network + welcome_to: 'Benvingut a' + site_meta_description: "Comencem des de baix. Amb agricultors i productors disposats a explicar les seves històries amb orgull i sinceritat. Amb distribuïdors connectant persones i productes de manera justa i honesta. Amb compradors que creuen que millors decisions de compra setmanal poden ..." + search_by_name: Cercar per nom o barri ... + producers_join: Els productors australians son ara benvinguts a unir-se a la Open Food Network. + charges_sales_tax: Càrrecs GST? + print_invoice: "Imprimir Factura" + print_ticket: "Imprimir Ticket" + select_ticket_printer: "Seleccionar la impressora de tickets" + send_invoice: "Enviar Factura" + resend_confirmation: "Reenviar Confirmació" + view_order: "Veure Comanda" + edit_order: "Editar Comanda" + ship_order: "Enviar Comanda" + cancel_order: "Cancel·lar Comanda" + confirm_send_invoice: "Una factura per aquesta comanda s'enviarà al client. Està segur de continuar?" + confirm_resend_order_confirmation: "Segur que vol reenviar el correu de confirmació de la comanda?" + must_have_valid_business_number: "%{enterprise_name} ha de tenir un ABN vàlid abans de les factures es poden enviar." + invoice: "Factura" + percentage_of_sales: "%{percentage} de vendes" + capped_at_cap: "llimitat a %{cap}" + per_month: "al mes" + free: "gratuït" + free_trial: "prova gratuïta" + plus_tax: "més GST" + min_bill_turnover_desc: "una vegada que el volum de negocis supere %{mbt_amount}" + more: "Més" + say_no: "No" + say_yes: "Si" + then: llavors + ongoing: En marxa + bill_address: Adreça de facturació + ship_address: Adreça d'enviament + sort_order_cycles_on_shopfront_by: "Ordenar Cicles de Comanda de la Botiga per" + required_fields: Els camps obligatoris estan marcats amb un asterisc + select_continue: Seleccionar i Continuar + remove: Eliminar + or: o + collapse_all: Col·lapsar tot + expand_all: Expandir tot + loading: Carregant... + show_more: Mostrar més + show_all: Mostrar tots + show_all_with_more: "Mostrar tot (%{num} més)" + cancel: Cancel·lar + edit: Editar + clone: Clonar + distributors: Distribuïdors + distribution: Distribució + bulk_order_management: Gestió de comandes en bloc + enterprises: Organitzacions + enterprise_groups: Grups + reports: Informes + variant_overrides: Inventari + spree_products: Productes Spree + all: Tots + current: Actual + available: Disponible + dashboard: Panell + undefined: indefinit + unused: no utilitzat + admin_and_handling: Administració i manipulació + profile: Perfil + supplier_only: Només proveïdor + has_shopfront: Té botiga + weight: Pes + volume: Volum + items: Articles + summary: Resum + detailed: Detallada + updated: Actualitzat + 'yes': "Sí" + 'no': "No" + y: 'S' + n: 'N' + powered_by: Impulsat per + blocked_cookies_alert: "El vostre navegador pot estar bloquejant les galetes necessàries per utilitzar aquesta botiga. Feu clic a continuació per permetre les galetes i tornar a carregar la pàgina." + allow_cookies: "Permet les galetes" + notes: Notes + error: Error + processing_payment: S'està processant el pagament ... + show_only_unfulfilled_orders: Mostra només comandes no realitzades + filter_results: Resultats del filtre + quantity: Quantitat + pick_up: Recollida + copy: Còpia + password_confirmation: Confirmació de la contrassenya + reset_password_token: Reinicia el token de contrasenya + expired: ha caducat, si us plau, sol·liciteu-ne un de nou + back_to_payments_list: "Torna a la llista de pagaments" + actions: + create_and_add_another: "Crea i afegeix-ne una altra" + admin: + begins_at: Comença a + begins_on: Comença + customer: Consumidora + date: Data + email: E-mail + ends_at: Acaba a + ends_on: Finalitza + name: Nom + on_hand: Disponibles + on_demand: Sota demanda + on_demand?: Sota demanda? + order_cycle: Cicle de Comanda + payment: Pagament + payment_method: Mètode de pagament + phone: Telèfon + price: Preu + producer: Productor + image: Imatge + product: Producte + quantity: Quantitat + schedule: Horari + shipping: Enviament + shipping_method: Mètode d'enviament + shop: Botiga + sku: SKU + status_state: Estat + tags: Etiquetes + variant: Variant + weight: Pes + volume: Volum + items: Articles + select_all: Seleccionar tot + obsolete_master: Mestre obsolet + quick_search: Cerca Ràpida + clear_all: Esborrar tot + start_date: "Data d'inici" + end_date: "Data de finalització" + form_invalid: "El formulari conté camps que falten o no son vàlids" + clear_filters: Netejar Filtres + clear: Netejar + save: Desa + cancel: Cancel·lar + back: Enrere + show_more: Mostra més + show_n_more: Mostra %{num} més + choose: "Tria ..." + please_select: Seleccioneu ... + columns: Columnes + actions: Accions + viewing: "Veient: %{current_view_name}" + description: Descripció + whats_this: Què és això? + tag_has_rules: "Regles existents per a aquesta etiqueta: %{num}" + has_one_rule: "té una regla" + has_n_rules: "té%{num} regles" + unsaved_confirm_leave: "Hi ha canvis no desats canviats en aquesta pàgina. Continua sense desar?" + unsaved_changes: "Teniu canvis sense desar" + accounts_and_billing_settings: + method_settings: + default_accounts_payment_method: "Mètode de pagament de comptes predeterminats" + default_accounts_shipping_method: "Mètode d'enviament de comptes predeterminades" + edit: + accounts_and_billing: "Comptes i facturació" + accounts_administration_distributor: "'administració de comptes de distribuïdora" + admin_settings: "Configuració" + update_invoice: "Actualitza les factures" + auto_update_invoices: "Actualitzeu les factures cada dia a la 1:00 a.m." + finalise_invoice: "Finalitzar les factures" + auto_finalise_invoices: "Finalitzar automàticament les factures mensualment el dia 2 a la 1:30 a.m." + manually_run_task: "Executa la tasca manualment" + update_user_invoice_explained: "Utilitzeu aquest botó per actualitzar immediatament les factures del mes actual per a cada usuari d'organització del sistema. Aquesta tasca es pot configurar per executar-se automàticament cada nit." + finalise_user_invoices: "Finalitza les factures d'usuari" + finalise_user_invoice_explained: "Utilitzeu aquest botó per finalitzar totes les factures del sistema per al mes natural anterior. Aquesta tasca es pot configurar per executar-se automàticament un cop al mes." + update_user_invoices: "Actualitzeu factures d'usuari" + errors: + accounts_distributor: s'ha d'establir si voleu crear factures per a usuàries de la organització. + default_payment_method: s'ha d'establir si voleu crear factures per a usuàries de la organització. + default_shipping_method: s'ha d'establir si voleu crear factures per a usuàries de la organització. + shopfront_settings: + embedded_shopfront_settings: "Configuració de botiga incrustada" + enable_embedded_shopfronts: "Habilita les botigues incrustades" + embedded_shopfronts_whitelist: "Llista blanca de dominis externs" + number_localization: + number_localization_settings: "Configuració de localització numèrica" + enable_localized_number: "Utilitzeu l'estàndard internacional per separar milers/decimals" + business_model_configuration: + edit: + business_model_configuration: "Model de sostenibilitat" + business_model_configuration_tip: "Configureu la tarifa què es cobrarà a les botigues cada mes per utilitzar la Xarxa Open Food." + bill_calculation_settings: "Configuració del càlcul de ticket" + bill_calculation_settings_tip: "Ajusteu la quantitat que es facturarà a les organitzacions cada mes per utilitzar l'OFN." + shop_trial_length: "Durada de la prova de la botiga (dies)" + shop_trial_length_tip: "El temps (en dies) que les organitzacions que es configuren com a botigues poden funcionar amb període de prova." + fixed_monthly_charge: "Càrrec mensual fixat" + fixed_monthly_charge_tip: "Un càrrec mensual fix per a totes les organitzacions que es configuren com a botiga i que hagin superat la facturació mínima (si s'estableix)." + percentage_of_turnover: "Percentatge de la facturació" + percentage_of_turnover_tip: "Quan sigui superior a zero, aquesta taxa (0,0 - 1,0) s'aplicarà a la facturació total de cada botiga i s'afegeix a qualsevol càrrec fixat (a l'esquerra) per calcular la factura mensual." + monthly_cap_excl_tax: "Límit mensual (exclòs GST)" + monthly_cap_excl_tax_tip: "Quan sigui superior a zero, aquest valor s'utilitzarà com a límit en la quantitat que es cobrarà a les botigues cada mes." + tax_rate: "Impost" + tax_rate_tip: "Impost que s'aplica a la factura mensual que es cobra a les organitzacions per utilitzar el sistema." + minimum_monthly_billable_turnover: "Mínim volum de vendes facturable al mes" + minimum_monthly_billable_turnover_tip: "La facturació mensual mínima abans que a una botiga se li cobri per l'ús d'OFN. Les organitzacions que facturin menys d'aquest import en un mes no es cobraran, ja sigui per percentatge o per tipus fix." + example_bill_calculator: "Exemple de calculadora de rebut" + example_bill_calculator_legend: "Després de l'exemple de volum de ventes per visualitzar l'efecte de la configuració a l'esquerra." + example_monthly_turnover: "Exemple de facturació mensual" + example_monthly_turnover_tip: "Un exemple de volum de ventes mensual per a una organització que es farà servir per calcular un exemple de factura mensual més a baix." + cap_reached?: "S'ha arribat a la capacitat?" + cap_reached?_tip: "Si s'ha arribat al límit (especificat a l'esquerra), tenint en compte la configuració i el volum de ventes proporcionat." + included_tax: "Impostos incloses" + included_tax_tip: "Els impostos totals inclosos a la factura mensual d'exemple, tenint en compte la configuració i la facturació proporcionada." + total_monthly_bill_incl_tax: "Factura mensual total (impostos inclosos)" + total_monthly_bill_incl_tax_tip: "L'exemple total de la factura mensual amb impostos inclosos, tenint en compte la configuració i la facturació proporcionada." + cache_settings: + show: + title: Emmagatzematge ocult + distributor: Distribuïdora + order_cycle: Cicle de Comanda + status: Estat + diff: Diferència + error: Error + invoice_settings: + edit: + title: Configuració de la factura + invoice_style2?: Utilitzeu el model de factura alternatiu que inclou el desglossament dels càrregs totals per tarifa i la informació sobre la taxa impositiva per article (encara no està disponible per als països sense recàrregs en concpte d'impostos) + enable_receipt_printing?: Mostra les opcions per imprimir rebuts amb impressores tèrmiques en el menú desplegable de la comanda? + stripe_connect_settings: + edit: + title: "Stripe Connect" + settings: "Configuració" + stripe_connect_enabled: Permetre que les botigues acceptin pagaments mitjançant Stripe Connect? + no_api_key_msg: No hi ha cap compte de Stripe per a aquesta organització. + configuration_explanation_html: Per obtenir instruccions detallades sobre la configuració de la integració de Stripe Connect, consulteu aquesta guia . + status: Estat + ok: Correcte + instance_secret_key: Exemple de clau secreta + account_id: Identificador del compte + business_name: Nom de l'empresa + charges_enabled: Càrrecs habilitats + charges_enabled_warning: "Advertència: els càrrecs no estan habilitats per al vostre compte" + auth_fail_error: La clau API que heu proporcionat no és vàlida + empty_api_key_error_html: No s'ha proporcionat cap clau d'API de Stripe. Per configurar la clau API, seguiu aquestes instruccions < / a> + matomo_settings: + edit: + title: "Configuració de Matomo" + matomo_url: "URL de Matomo" + matomo_site_id: "Identificador de Matomo" + info_html: "Matomo és un analitzador de webs i mòbils. Podeu allotjar Matomo de manera local o utilitzar un servei al núvol. Vegeu matomo.org per obtenir més informació." + config_instructions_html: "Aquí podeu configurar la integració Matomo OFN. L'URL de Matomo que us apareix a continuació ha d'indicar la instància de Matomo en la qual s'enviarà la informació de seguiment de l'usuari; si es deixa buit, el seguiment de l'usuari de Matomo estarà desactivat. El camp d'identificació del lloc no és obligatori, però és útil si fa un seguiment de més d'un lloc web en una sola instància de Matomo; es pot trobar a la consola d'instància de Matomo." + customers: + index: + add_customer: "Afegeix consumidora" + new_customer: "Nova consumidora" + customer_placeholder: "consumidora@example.org" + valid_email_error: siusplau, introduïu una adreça de correu electrònic vàlida + add_a_new_customer_for: Afegiu una consumidora nova per %{shop_name} + code: Codi + duplicate_code: "Aquest codi ja s'ha utilitzat." + bill_address: "Adreça de facturació" + ship_address: "Adreça d'enviament" + update_address_success: 'L''adreça s''ha actualitzat correctament.' + update_address_error: 'Ho sentim! Introduïu tots els camps obligatoris.' + edit_bill_address: 'Edita l''adreça de facturació' + edit_ship_address: 'Edita l''adreça d''enviament' + required_fileds: 'Els camps obligatoris es denoten amb un asterisc' + select_country: 'Selecciona país' + select_state: 'Selecciona província' + edit: 'Editar' + update_address: 'Actualitza l''adreça' + confirm_delete: 'Estàs segur que vols suprimir?' + search_by_email: "Cerca per correu electrònic/codi ..." + guest_label: 'Fer comanda com a convidat' + destroy: + has_associated_orders: 'S''ha produït un error en suprimir: la consumidora té comandes associades amb la seva botiga' + contents: + edit: + title: Contingut + header: Capçalera + home_page: Pàgina d'inici + producer_signup_page: Pàgina d'inscripció de productora + hub_signup_page: Pàgina d'inscripció de grup + group_signup_page: Pàgina d'inscripció de grup + main_links: Enllaços del menú principal + footer_and_external_links: Peu de pàgina i enllaços externs + your_content: El vostre contingut + user_guide: Guia de l'usuari + enterprise_fees: + index: + title: Tarifes de l'organització + enterprise: Organització + fee_type: Tipus de tarifa + name: Nom + tax_category: Categoria d'impostos + calculator: Calculadora + calculator_values: Valors de la calculadora + enterprise_groups: + index: + new_button: Grup d'organització nou + enterprise_roles: + form: + manages: gestiona + enterprise_role: + manages: gestiona + products: + unit_name_placeholder: 'exemple: grapats' + index: + unit: Unitat + display_as: Mostra com + category: Categoria + tax_category: Categoria d'impostos + inherits_properties?: Hereda propietats? + available_on: Disponible el + import_date: S'ha importat + upload_an_image: Penja una imatge + product_search_keywords: Paraules clau de cerca de producte + product_search_tip: Escriviu paraules per ajudar-vos a cercar els vostres productes a les botigues. Utilitzeu espai per separar cada paraula clau. + SEO_keywords: Paraules clau de SEO + seo_tip: Escriviu paraules per ajudar-vos a cercar els vostres productes a la web. Utilitzeu espai per separar cada paraula clau. + Search: Cerca + properties: + property_name: Nom de la propietat + inherited_property: Propietat heretada + variants: + to_order_tip: "Els articles preparats per encàrrec no tenen un nivell fixat d'existències, com ara pa fet sota comanda." + product_distributions: "Distribucions de productes" + group_buy_options: "Opcions de compra en grup" + back_to_products_list: "Torna a la llista de productes" + product_import: + title: Importació de productes + file_not_found: No s'ha trobat el fitxer o no s'ha pogut obrir + no_data: No s'ha trobat cap dada al full de càlcul + confirm_reset: "Això farà que el nivell d'existències sigui zero en tots els productes d'aquesta\n organització que no estan presents en el fitxer carregat" + model: + no_file: "error: no s'ha carregat cap fitxer" + could_not_process: "no s'ha pogut processar el fitxer: tipus de fitxer no vàlid" + incorrect_value: valor incorrecte + conditional_blank: no es pot deixar en blanc si el tipus d'unitat està en blanc + no_product: no s'ha trobat cap producte a la base de dades + not_found: no es troba a la base de dades + not_updatable: No es poden actualitzar els productes existents mitjançant la importació de productes + blank: no es pot deixar en blanc + products_no_permission: no tens el permís per gestionar els productes d'aquesta organització + inventory_no_permission: no tens el permís per crear inventari per a aquesta productora + none_saved: no s'han desat cap producte amb èxit + line_number: "Línia %{number}:" + index: + select_file: Selecciona un full de càlcul per pujar-lo + spreadsheet: Full de càlcul + choose_import_type: Selecciona el tipus d'importació + import_into: Tipus d'importació + product_list: Llista de productes + inventories: Inventari + import: Importa + upload: Carrega + csv_templates: Plantilles de CSV + product_list_template: Descarrega la plantilla de la llista de productes + inventory_template: Descarrega la plantilla d'inventari + category_values: Valors de la categoria disponibles + product_categories: Tipus de productes + tax_categories: Tipus d'impostos + shipping_categories: Tipus d'enviament + import: + review: Revisa + import: Importa + save: Desa + results: Resultats + save_imported: Desa els productes importats + no_valid_entries: No s'han trobat entrades vàlides + none_to_save: No hi ha entrades que es puguin desar + some_invalid_entries: El fitxer importat conté entrades no vàlides + fix_before_import: Si us plau, corregeix aquests errors i torna a importar el fitxer + save_valid?: Vols desar per ara les entrades vàlides i descartar les altres? + no_errors: No s'ha detectat cap error! + save_all_imported?: Vols desar tots els productes importats? + options_and_defaults: Importa opcions i valors predeterminats + no_permission: no tens permís per gestionar aquesta organització + not_found: No s'ha pogut trobar l'organització a la base de dades + no_name: Sense nom + blank_enterprise: alguns productes no tenen una organització definida + reset_absent?: Restabliu els productes absents + reset_absent_tip: Establiu valors a zero per a tots els productes existents que no figurin al fitxer + overwrite_all: Sobreescriu-ho tot + overwrite_empty: Sobreescriu si està buit + default_stock: Estableix el nivell d'existències + default_tax_cat: Estableix la categoria d'impostos + default_shipping_cat: Estableix la categoria d'enviament + default_available_date: Estableix data de disponibilitat + validation_overview: Importa la descripció general de la validació + entries_found: S'han trobat entrades al fitxer importat + entries_with_errors: Els articles contenen errors i no s'importaran + products_to_create: Els productes es crearan + products_to_update: Els productes s'actualitzaran + inventory_to_create: Els articles de l'inventari es crearan + inventory_to_update: Els articles de l'inventari s'actualitzaran + products_to_reset: Els productes existents reajustaran el nivell d'existències a zero + inventory_to_reset: Els articles de l'inventari existents tindran el restabliment d'existències a zero + line: Línia + item_line: Línia d'article + import_review: + not_updatable_tip: "Els següents camps no es poden actualitzar mitjançant importació en bloc per a productes existents:" + fields_ignored: Aquests camps s'ignoraran quan es guardin els productes importats. + entries_table: + not_updatable: Aquest camp no es pot actualitzar mitjançant la importació en bloc per a productes existents + save_results: + final_results: Importa els resultats finals + products_created: Productes creats + products_updated: Productes actualitzats + inventory_created: S'han creat articles d'inventari + inventory_updated: 'S''han actualitzat articles d''inventari ' + products_reset: S'ha reestablert el nivell d'existències dels productes a zero + inventory_reset: S'ha reestablert el nivell d'estoc dels articles de l'inventari a zero + all_saved: "Tots els articles s'han desat correctament" + some_saved: "els articles s'han desat correctament" + save_errors: Desa els errors + import_again: Penja un altre fitxer + view_products: Anar a la pàgina de Productes + view_inventory: Anar a la pàgina d'Inventari + variant_overrides: + loading_flash: + loading_inventory: CARREGANT INVENTARI + index: + title: Inventari + description: Utilitzeu aquesta pàgina per administrar els inventaris de la vostres organitzacions. Tots els detalls del producte aquí establerts substituiran els establerts a la pàgina "Productes" + enable_reset?: Habilitar la restauració de valors de stock? + inherit?: Heredar? + add: Afegeix + hide: Amaga + import_date: S'ha importat + select_a_shop: Seleccioneu una botiga + review_now: Reviseu ara + new_products_alert_message: Hi ha productes nous %{new_product_count} disponibles per afegir al vostre inventari. + currently_empty: El vostre inventari està buit + no_matching_products: No es troben productes coincidents al vostre inventari + no_hidden_products: No s'ha amagat cap producte d'aquest inventari + no_matching_hidden_products: Els productes ocults no coincideixen amb els criteris de cerca + no_new_products: No hi ha productes nous disponibles per afegir-los a aquest inventari + no_matching_new_products: Cap producte nou coincideix amb els criteris de cerca + inventory_powertip: Aquest és el vostre inventari de productes. Per afegir productes al vostre inventari, seleccioneu "Nous productes" al menú desplegable Visualització. + hidden_powertip: Aquests productes s'han ocultat al vostre inventari i no estaran disponibles per afegir-los a la vostra botiga. Podeu fer clic a "Afegeix" per afegir un producte a l'inventari. + new_powertip: Aquests productes estan disponibles per afegir al vostre inventari. Feu clic a "Afegeix" per afegir un producte al vostre inventari o "Oculta" per ocultar-lo de la vista. Sempre podreu canviar aquestes opcions després. + controls: + back_to_my_inventory: Torna al meu inventari + orders: + invoice_email_sent: 'S''ha enviat el correu electrònic de la factura' + order_email_resent: 'S''ha reenviat el correu electrònic de la comanda' + bulk_management: + tip: "Utilitzeu aquesta pàgina per alterar les quantitats de productes en diverses comandes. Els productes també es poden eliminar de les comandes completament, si es requereix." + shared: "Recurs compartit?" + order_no: "Nº de comanda." + order_date: "Completada a" + max: "Màx" + product_unit: "Producte: Unitat" + weight_volume: "Pes/Volum" + ask: "Preguntar?" + page_title: "Gestió de les comandes en bloc" + actions_delete: "Suprimeix seleccionats" + loading: "Carregant comandes" + no_results: "No s'han trobat comandes." + group_buy_unit_size: "Mida d'unitat de grup de compra" + total_qtt_ordered: "Quantitat total demanada" + max_qtt_ordered: "Quantitat màxima demanada" + current_fulfilled_units: "Unitats actuals fetes" + max_fulfilled_units: "Unitats màximes fetes" + order_error: "S'han de resoldre alguns errors abans de poder actualitzar les comandes.\nQualsevol camp amb vora vermella conté errors." + variants_without_unit_value: "Atenció: algunes variants no tenen cap unitat assignada." + select_variant: "Selecciona un paràmetre" + enterprise: + select_outgoing_oc_products_from: Selecciona els productes sortints del Cicle de Comandes + enterprises: + index: + title: Organitzacions + new_enterprise: Nova organització + producer?: "Productora?" + package: Perfil + status: Estat + manage: Gestiona + form: + about_us: + desc_short: Descripció breu + desc_short_placeholder: Explica'ns sobre la teva organització en una o dues frases + desc_long: Sobre nosaltres + desc_long_placeholder: Explica coses sobre tu als clients. Aquesta informació apareixerà al teu perfil públic. + business_details: + abn: NIF + abn_placeholder: p. ex. F987654321 + acn: NIF + acn_placeholder: p. ex. 123 456 789 + display_invoice_logo: Mostreu el logotip a les factures + invoice_text: Afegeix text personalitzat al final de les factures + contact: + name: Nom + name_placeholder: 'p. ex: Josep Ribes' + email_address: Adreça electrònica pública + email_address_placeholder: 'p. ex: contacte@hortajosepribes.com' + email_address_tip: "Aquesta adreça de correu electrònic es mostrarà al vostre perfil públic" + phone: Telèfon + phone_placeholder: p. ex. 98 765 43 21 + website: Lloc web + website_placeholder: 'p. ex.: www.hortajosepribes.com' + enterprise_fees: + name: Nom + fee_type: Tipus de tarifa + manage_fees: Gestioneu les tarifes de l'organització + no_fees_yet: Encara no tens cap tipus de comissió de l'organització + create_button: Crea'n una ara + images: + logo: Logotip + promo_image_placeholder: 'Aquesta imatge es mostra a "Sobre Nosaltres"' + promo_image_note1: 'ATENCIÓ:' + promo_image_note2: Qualsevol imatge promocional que es carregui aquí es tallarà a 1200 x 260. + promo_image_note3: 'La imatge promocional es mostra a la part superior de la pàgina de perfil i finestres emergents d''una organització ' + inventory_settings: + text1: 'Pots optar per gestionar els nivells d''existències i els preus a través del teu ' + inventory: inventari + text2: > + Si utilitzes l'eina d'inventari, pots seleccionar si els nous productes + afegits pels teus proveïdors han de ser afegits al teu inventari primerament, + abans de poder emmagatzemar-los a la botiga. Si no estàs utilitzant + l'inventari per gestionar els teus productes, has de seleccionar l'opció + "recomanada" següent: + preferred_product_selection_from_inventory_only_yes: Es poden introduir nous productes directament a la meva botiga (recomanat) + preferred_product_selection_from_inventory_only_no: He d'afegir els nous productes a l'inventari abans de poder-los mostrar a la meva botiga. + payment_methods: + name: Nom + applies: Aplicar? + manage: Gestiona els mètodes de pagament + not_method_yet: Encara no tens cap mètode de pagament. + create_button: Crea un nou mètode de pagament + create_one_button: Crea'n un ara + primary_details: + name: Nom + name_placeholder: p. ex. Horta Josep Ribes + groups: Grups + groups_tip: Seleccioneu grups o xarxes de la quals sou membres. Això ajudarà les consumidores a trobar la vostra organització o empresa. + groups_placeholder: Comenceu a escriure per cercar xarxes disponibles... + primary_producer: Productora principal? + primary_producer_tip: Selecciona "Productora" si ets productora principal d'aliments. + producer: Productora + any: Cap + none: No productora + own: Propi + sells: Ven + sells_tip: "Cap: l'organització no ven als clients directament.
Propietari: l'organització ven productes propis als clients.
Qualsevol: l'organització pot vendre productes propis o d'altres empreses.
" + visible_in_search: Visible a la cerca? + visible_in_search_tip: Determina si aquesta organització serà visible per a les consumidores en cercar el lloc. + visible: Visible + not_visible: No visible + permalink: Permalink (sense espais) + permalink_tip: "Aquest enllaç permanent s'utilitza per crear l'url a la vostra botiga: %{link}your-shop-name / shop" + link_to_front: Enllaç a la botiga + link_to_front_tip: Un enllaç directe a la vostra botiga a l'Open Food Netwok. + shipping_methods: + name: Nom + applies: 'Aplicar? ' + manage: Gestiona els mètodes d'enviament + create_button: Crea un nou mètode d'enviament + create_one_button: Crea'n un ara + no_method_yet: Encara no teniu cap mètode d'enviament. + shop_preferences: + shopfront_requires_login: "Botiga visible públicament?" + shopfront_requires_login_tip: "Trieu si les consumidores han d'iniciar sessió per veure la botiga o si és visible per a tothom." + shopfront_requires_login_false: "Públic" + shopfront_requires_login_true: "Només visible per a usuaris registrats" + recommend_require_login: "Recomanem que es requeireixi inici de sessió quan l'opció de poder modificar les comandes estigui habilitada." + allow_guest_orders: "Comandes de convidats" + allow_guest_orders_tip: "Permet fer comanda com a convidat o requereix un usuari registrat." + allow_guest_orders_false: "Demana un inici de sessió per realitzar la comanda" + allow_guest_orders_true: "Permet comandes de convidats" + allow_order_changes: "Canviar les comandes" + allow_order_changes_tip: "Permetre que les consumidores canviïn o cancel·lin la seva comanda mentre el cicle de comanda estigui obert." + allow_order_changes_false: "Les comandes realitzades no es poden canviar / cancel·lar." + allow_order_changes_true: "Les consumidores poden canviar o cancel·lar les comandes mentre el cicle de comanda està obert." + enable_subscriptions: "Subscripcions" + enable_subscriptions_tip: "Habilitar la funcionalitat de subscripcions?" + enable_subscriptions_false: "Deshabilitat" + enable_subscriptions_true: "Habilitat" + shopfront_message: Missatge de la botiga + shopfront_message_placeholder: > + Una explicació opcional per a les consumidores que detalla com funciona + la vostra botiga, que es mostrarà a sobre de la llista de productes. + shopfront_closed_message: Missatge de tancament de la botiga + shopfront_closed_message_placeholder: > + Un missatge que proporciona una explicació més detallada sobre per què + la vostra botiga està tancada i / o quan poden esperar les consumidores + que es tornarà a obrir. Això només es mostrarà a la vostra botiga quan + no hi hagi cicles de comanda actius (és a dir, quan la botiga estigui + tancada). + shopfront_category_ordering: Ordre de les categories de la botiga + open_date: Data d'obertura + close_date: Data de tancament + social: + twitter_placeholder: 'p. ex: @horta_josepribes' + instagram_placeholder: 'p. ex: horta_josepribes' + facebook_placeholder: 'p .ex: www.facebook.com/NomDeLaPàgina' + linkedin_placeholder: 'p. ex: www.linkedin.com/in/ElTeuNom' + stripe_connect: + connect_with_stripe: "Connectar amb Stripe" + stripe_connect_intro: "Per acceptar pagaments amb targeta de crèdit haureu de connectar el vostre compte de Stripe a Open Food Network. Utilitzeu el botó de la dreta per començar. " + stripe_account_connected: "Compte de Stripe connectat." + disconnect: "Desconnecta el compte" + confirm_modal: + title: Connectar amb Stripe + part1: Stripe és un servei de processament de pagaments que permet que les botigues de l'OFN acceptin els pagaments amb targeta de crèdit de les consumidores. + part2: Per utilitzar aquesta funció heu de connectar el vostre compte Stripe a l'OFN. Si feu clic a "Accepto", us redirigirem al lloc web de Stripe on podeu connectar un compte Stripe existent o bé crear-ne un si encara no en teniu cap. + part3: Això permetrà que Open Food Network accepti pagaments amb targeta de crèdit de consumidores en nom vostre. Tingueu en compte que haureu de mantenir el vostre propi compte de Stripe, pagar-ne les tarifes i mantenir el servei a les consumidores pel teu compte. + i_agree: Accepto + cancel: Cancel·lar + tag_rules: + default_rules: + by_default: Per defecte + no_rules_yet: Encara no s'aplica cap regla per defecte + add_new_button: '+ Afegeix una nova regla per defecte' + no_tags_yet: Encara no hi ha cap etiqueta aplicada a aquesta organització + no_rules_yet: Encara no hi ha cap regla aplicada a aquesta etiqueta + for_customers_tagged: 'Per als clients etiquetats:' + add_new_rule: '+ Afegeix una nova regla' + add_new_tag: '+ Afegeix una nova etiqueta' + users: + email_confirmation_notice_html: "La confirmació de correu electrònic està pendent. Hem enviat un correu electrònic de confirmació a %{email}." + resend: Reenviar + owner: 'Propietària' + contact: "Contacte" + contact_tip: "El gestor que rebrà els missatges de correu electrònic de l'organització per a comandes i notificacions. Ha de tenir una adreça electrònica confirmada." + owner_tip: La usuària principal responsable d'aquesta organització. + notifications: Notificacions + notifications_tip: Les notificacions sobre les comandes s'enviaran a aquesta adreça de correu electrònic. + notifications_placeholder: 'p. ex: contacte@hortajosepribes.com' + notifications_note: 'Nota: és possible que hagi de confirmar una nova adreça de correu electrònic abans d''utilitzar-la' + managers: Gestors + managers_tip: Altres usuàries amb permisos per gestionar aquesta organització + invite_manager: "Convida un gestor" + invite_manager_tip: "Convida un usuari no registrat a registrar-se i convertir-se en gestor d'aquesta organització." + add_unregistered_user: "Afegeix un usuari no registrat" + email_confirmed: "S'ha confirmat el correu electrònic" + email_not_confirmed: "Correu electrònic no confirmat" + actions: + edit_profile: Configuració + properties: Propietats + payment_methods: Mètodes de Pagament + payment_methods_tip: Aquesta organització no té mètodes de pagament + shipping_methods: Mètodes d'enviament + shipping_methods_tip: 'Aquesta organització té mètodes d''enviament ' + enterprise_fees: Honoraris de l'organització + enterprise_fees_tip: Aquesta organització no té comissions + admin_index: + name: Nom + role: Rol + sells: Ven + visible: Visible? + owner: Propietària + producer: Productor + change_type_form: + producer_profile: Perfil de productora + connect_ofn: Connecta mitjançant OFN + always_free: SEMPRE GRATUÏT + producer_description_text: Afegeix els teus productes a Katuma i permet a grups de consum i altres organitzacions emmagatzemar els teus productes a les seves botigues. + producer_shop: Botiga d'una productora + sell_your_produce: Ven els teus propis productes + producer_shop_description_text: Ven els teus productes directament les consumidores a través de la teva pròpia botiga a Katuma. + producer_shop_description_text2: Una productora amb botiga només és per als teus productes; si vols vendre productes produïts / cultivats fora del lloc, selecciona "Grup de productores". + producer_hub: Grup de productores + producer_hub_text: Ven productes propis i d'altres productores + producer_hub_description_text: La vostra organització és la columna vertebral del sistema alimentari local. Podeu vendre els vostres propis productes i o afegir els productes d'altres organitzacions a través de la vostra botiga a la Katuma. + profile: Només perfil + get_listing: Apareix en els directoris + profile_description_text: La gent pot trobar-vos i contactar-vos a Katuma. La vostra organització serà visible al mapa i es podrà cercar als directoris. + hub_shop: Botiga + hub_shop_text: Veneu productes d'altres + hub_shop_description_text: La vostra organització la columna vertebral del vostre sistema alimentari local. Afegeix productes d'altres organitzacions i els podreu vendre a través de la vostra botiga a Katuma. + choose_option: Si us plau, trieu una de les opcions anteriors. + change_now: Canvia ara + enterprise_user_index: + loading_enterprises: CARREGANT ORGANITZACIONS + no_enterprises_found: No s'han trobat organitzacions. + search_placeholder: Cerca pel nom + manage: Gestiona + manage_link: Configuració + producer?: "Productora?" + package: "Perfil" + status: "Estat" + new_form: + owner: Propietària + owner_tip: La usuària principal responsable d'aquesta organització. + i_am_producer: Sóc una productora + contact_name: Nom de contacte + edit: + editing: 'Configuració:' + back_link: Tornar a la llista d'organitzacions + new: + title: Nova organització + back_link: 'Tornar a la llista d''organitzacions ' + remove_logo: + remove: "Elimina la imatge" + removed_successfully: "Logotip eliminat correctament" + immediate_removal_warning: "El logotip s'eliminarà immediatament després de confirmar." + remove_promo_image: + remove: "Elimina la imatge " + removed_successfully: "Imatge promocional eliminada correctament" + immediate_removal_warning: "La imatge promocional s'eliminarà immediatament després de confirmar." + welcome: + welcome_title: Benvingut a la Katuma - Open Food Network! + welcome_text: Heu creat correctament un + next_step: Següent pas + choose_starting_point: 'Escull el teu perfil:' + invite_manager: + user_already_exists: "L'usuari ja existeix" + error: "Alguna cosa ha anat malament" + order_cycles: + edit: + advanced_settings: Configuració avançada + update_and_close: Actualitza i tanca + choose_products_from: 'Trieu Productes des de:' + exchange_form: + pickup_time_tip: Quan les comandes d'aquest cicle de comandes estiguin preparades per a les consumidores + pickup_instructions_placeholder: "Instruccions de recollida" + pickup_instructions_tip: Aquestes instruccions es mostraran a les consumidores després d'haver completat una comanda + pickup_time_placeholder: "Preparat per (és a dir, data / hora)" + receival_instructions_placeholder: "Instruccions de recepció" + add_fee: 'Afegeix una comissió' + selected: 'seleccionat' + add_exchange_form: + add_supplier: 'Afegeix proveïdora' + add_distributor: 'Afegeix distribuïdora' + advanced_settings: + title: Configuració avançada + choose_product_tip: Podeu optar per restringir tots els productes disponibles (tant d'entrada com de sortida), per només als que pertanyen a l'inventari de %{inventory}. + preferred_product_selection_from_coordinator_inventory_only_here: Només inventari del coordinador + preferred_product_selection_from_coordinator_inventory_only_all: Tots els productes disponibles + save_reload: Desa i recarrega la pàgina + coordinator_fees: + add: Afegeix una comissió del coordinador + filters: + search_by_order_cycle_name: "Cerca pel nom del Cicle de Comanda..." + involving: "Implica" + any_enterprise: "Qualsevol organització" + any_schedule: "Qualsevol horari" + form: + incoming: Entrant + supplier: Proveïdora + receival_details: Detalls de recepció + fees: Tarifes + outgoing: Sortint + distributor: Distribuïdora + products: Productes + tags: Etiquetes + add_a_tag: Afegeix una etiqueta + delivery_details: Detalls de recollida / lliurament + debug_info: Informació de depuració + index: + schedule: Horari + schedules: Horaris + adding_a_new_schedule: Afegir una nova programació + updating_a_schedule: Actualització una programació + new_schedule: Nova programació + create_schedule: Crear una programació + update_schedule: Actualitza la programació + delete_schedule: Suprimeix la programació + created_schedule: Programació creada + updated_schedule: Programació actualitzada + deleted_schedule: Programació eliminada + schedule_name_placeholder: Nom de la programació + name_required_error: Si us plau introdueix un nom per a aquesta programació + no_order_cycles_error: Si us plau selecciona com a mínim un cicle de comanda (arrossega i deixa anar) + name_and_timing_form: + name: Nom + orders_open: Les comandes s'obren a + coordinator: Coordinador + orders_close: Les comandes tanquen + row: + suppliers: proveïdores + distributors: distribuïdores + variants: variants + simple_form: + ready_for: Preparat per + ready_for_placeholder: Data / hora + customer_instructions: Instruccions de la consumidora + customer_instructions_placeholder: Notes de recollida o de lliurament + products: Productes + fees: Tarifes + destroy_errors: + orders_present: Una consumidora ha seleccionat aquest Cicle de Comanda i no es pot esborrar. Per evitar que les consumidores hi accedeixin, tanqueu-lo. + schedule_present: Aquest cicle de comanda està vinculat a una programació i no es pot esborrar. Desenllaça o suprimeix primer la programació. + bulk_update: + no_data: Hm, alguna cosa ha sortit malament. No s'ha trobat cap cicle de comanda. + date_warning: + msg: Aquest cicle de comanda està enllaçat amb %{n}comandes de subscripció obertes . Si canvieu aquesta data ara això no afectarà comandes que ja s'hagin realitzat però cal evitar-ho si és possible. Esteu segur que voleu continuar? + cancel: Cancel·lar + proceed: Procedeix + producer_properties: + index: + title: Propietats de la productora + proxy_orders: + cancel: + could_not_cancel_the_order: No s'ha pogut cancel·lar la comanda + resume: + could_not_resume_the_order: No es pot reprendre la comanda + shared: + user_guide_link: + user_guide: Guia de l'usuari + overview: + enterprises_header: + ofn_with_tip: Les organitzacions són productores i/o grups de consum i són la unitat bàsica d'organització dins d'Open Food Network. + enterprises_hubs_tabs: + has_no_payment_methods: "%{enterprise} no té mètodes de pagament" + has_no_shipping_methods: "%{enterprise} no té mètodes d'enviament" + has_no_enterprise_fees: "%{enterprise} no té comissions de l'organització" + enterprise_issues: + create_new: Crear nou + resend_email: Reenvia el correu electrònic + has_no_payment_methods: "%{enterprise} actualment no té mètodes de pagament" + has_no_shipping_methods: "%{enterprise} actualment no té mètodes d'enviament" + email_confirmation: "La confirmació de correu electrònic està pendent. Hem enviat un correu electrònic de confirmació a %{email}." + not_visible: "%{enterprise} no és visible i, per tant, no es pot trobar al mapa ni a les cerques" + reports: + hidden: OCULT + unitsize: UNITAT DE MESURA + total: TOTAL + total_items: ARTICLES TOTALS + supplier_totals: Total de de proveïdores del Cicle de Comanda + supplier_totals_by_distributor: Total de proveïdores del Cicle de Comanda - per distribuïdora + totals_by_supplier: Totals de la distribuïdora del Cicle de Comanda - per proveïdora + customer_totals: Totals de consumidores del Cicle de Comanda + all_products: Tots els productes + inventory: Inventari (disponible) + lettuce_share: LettuceShare + mailing_list: Llista de correus electrònics + addresses: Adreces + payment_methods: Informe de Mètodes de pagament + delivery: Informe de lliurament + tax_types: Tipus d'impostos + tax_rates: Tarifes fiscals + pack_by_customer: Paquets per consumidora + pack_by_supplier: Paquets per proveïdora + orders_and_distributors: + name: Comandes i distribuïdores + description: Comandes amb detalls de la distribuïdora + payments: + name: Informes de pagament + description: Informes per a pagaments + orders_and_fulfillment: + name: Informes de comandes i compliment + customers: + name: Consumidores + products_and_inventory: + name: Productes & Inventari + sales_total: + name: Total de vendes + description: Total de vendes per a totes les comandes + users_and_enterprises: + name: Usuaris & Organitzacions + description: Propietat i estatus de l'organització + order_cycle_management: + name: Gestió del Cicle de Comanda + sales_tax: + name: Impostos sobre la venda + xero_invoices: + name: Factures Xero + description: Factures per a la importació a Xero + packing: + name: Informes d'embalatge + subscriptions: + subscriptions: Subscripcions + new: Nova subscripció + create: Crea una subscripció + index: + please_select_a_shop: Si us plau, seleccioneu una botiga + edit_subscription: Edita la subscripció + pause_subscription: Pausa la subscripció + unpause_subscription: Reprèn la subscripció + cancel_subscription: Cancel·la la subscripció + setup_explanation: + just_a_few_more_steps: 'Només uns quants passos més abans de començar:' + enable_subscriptions: "Activa les subscripcions d'almenys una de les teves botigues" + enable_subscriptions_step_1_html: 1. Aneu a la pàgina %{enterprises_link}, cerqueu la vostra botiga i feu clic a "Gestionar" + enable_subscriptions_step_2: 2. A "Preferències de la botiga", activeu l'opció Subscripcions + set_up_shipping_and_payment_methods_html: Configureu els mètodes %{shipping_link} i %{payment_link} + set_up_shipping_and_payment_methods_note_html: Tingueu en compte que només es poden utilitzar mètodes de pagament en efectiu i Stripe amb les subscripcions + ensure_at_least_one_customer_html: Assegureu-vos que hi hagi almenys un %{customer_link} + create_at_least_one_schedule: Crea almenys una programació + create_at_least_one_schedule_step_1_html: 1. Aneu a la pàgina %{order_cycles_link} + create_at_least_one_schedule_step_2: 2. Creeu un Cicle de Comanda si encara no ho heu fet + create_at_least_one_schedule_step_3: 3. Fes clic a '+ Nova programació' i omple el formulari + once_you_are_done_you_can_html: Un cop hagueu acabat, podeu %{reload_this_page_link} + reload_this_page: tornar a carregar aquesta pàgina + steps: + details: 1. Detalls bàsics + address: 2. Adreça + products: 3. Afegeix productes + review: 4. Revisa i desa + subscription_line_items: + this_is_an_estimate: | + Els preus que es mostren són només una estimació i es calculen en el moment en què es canvia la subscripció. + Si canvieu els preus o les comissions, les comandes s'actualitzaran, però la subscripció continuarà mostrant els valors anteriors. + details: + details: Detalls + invalid_error: Oops! Si us plau ompliu tots els camps obligatoris ... + allowed_payment_method_types_tip: Actualment només es poden utilitzar els mètodes de pagament en efectiu i Stripe + credit_card: Targeta de crèdit + charges_not_allowed: Els càrrecs no estan permesos per aquesta consumidora + no_default_card: La consumidora no té targetes disponibles per cobrar + card_ok: La consumidora té una targeta disponible per cobrar + loading_flash: + loading: CARREGANT SUBSCRIPCIONS + review: + details: Detalls + address: Adreça + products: 'Productes ' + product_already_in_order: Aquest producte ja s'ha afegit a la comanda. Editeu-ne la quantitat directament. + orders: + number: Número + confirm_edit: Estàs segur que voleu editar aquesta comanda? Si ho fas és més difícil que es sincronitzin automàticament els canvis a la subscripció en el futur. + confirm_cancel_msg: Estàs segur que vols cancel·lar aquesta subscripció? Aquesta acció no es pot desfer. + cancel_failure_msg: 'Ho sentim, la cancel·lació ha fallat!' + confirm_pause_msg: Estàs segur que vols pausar aquesta subscripció? + pause_failure_msg: 'Ho sentim, la pausa ha fallat!' + confirm_unpause_msg: Estàs segur que vols reprendre aquesta subscripció? + unpause_failure_msg: 'Ho sentim, la represa ha fallat!' + confirm_cancel_open_orders_msg: "Actualment hi ha algunes comandes obertes per a aquesta subscripció. Ja s'ha notificat a les consumidores que les comandes serà atesa. Voleu cancel·lar aquestes comandes o conservar-les?" + resume_canceled_orders_msg: "Algunes comandes d'aquesta subscripció es poden reprendre ara mateix. Podeu reprendre-les des del menú desplegable de comandes." + yes_cancel_them: Cancel·lar-les + no_keep_them: Conservar-les + yes_i_am_sure: Sí, n'estic segur + order_update_issues_msg: Algunes comandes no s'han pogut actualitzar automàticament, probablement perquè s'han editat manualment. Reviseu els problemes que es detallen a continuació i realitzeu els ajustaments a comandes individuals si és necessari. + no_results: + no_subscriptions: Encara no hi ha cap subscripció... + why_dont_you_add_one: Per què no n'afegiu un? :) + no_matching_subscriptions: No s'han trobat subscripcions coincidents + schedules: + destroy: + associated_subscriptions_error: Aquesta programació no es pot suprimir perquè té subscripcions associades + controllers: + enterprises: + stripe_connect_cancelled: "S'ha cancel·lat la connexió a Stripe" + stripe_connect_success: "S'ha connectat correctament el compte de Stripe" + stripe_connect_fail: Ho sentim, s'ha produït un error en la connexió del vostre compte de Stripe + stripe_connect_settings: + resource: 'Configuració de la connexió amb Stripe ' + api: + enterprise_logo: + destroy_attachment_does_not_exist: "El logotip no existeix" + enterprise_promo_image: + destroy_attachment_does_not_exist: "La imatge promocional no existeix" + checkout: + already_ordered: + cart: "cistella" + message_html: "Ja teniu una comanda per a aquest cicle de comanda. Consulteu %{cart} per veure els articles que heu demanat anteriorment. També podeu cancel·lar articles sempre que el cicle de comanda estigui obert." + shops: + hubs: + show_closed_shops: "Mostra les botigues tancades" + hide_closed_shops: "Amaga les botigues tancades" + show_on_map: "Mostra-ho tot al mapa" + shared: + menu: + cart: + checkout: "Validar ara" + already_ordered_products: "Ja està demanat en aquest cicle de comanda" + register_call: + selling_on_ofn: "Estàs interessat en formar part d'Open Food Network?" + register: "Registra't aquí" + footer: + footer_global_headline: "OFN Global" + footer_global_home: "Inici" + footer_global_news: "Notícies" + footer_global_about: "Sobre" + footer_global_contact: "Contacte" + footer_sites_headline: "Pàgines d'OFN" + footer_sites_developer: "Desenvolupador" + footer_sites_community: "Comunitat" + footer_sites_userguide: "Guia de l'usuari" + footer_secure: "Segur i de confiança." + footer_secure_text: "Open Food Network utilitza el xifrat SSL (RSA de 2048 bits) a tot arreu per mantenir les vostres dades comercials i de pagament privades. Els nostres servidors no emmagatzemen els detalls de la targeta de crèdit i els pagaments es processen mitjançant serveis compatibles amb PCI." + footer_contact_headline: "Mantén el contacte" + footer_contact_email: "Envia'ns un correu electrònic" + footer_nav_headline: "Navega" + footer_join_headline: "Uneix-te a nosaltres" + footer_join_body: "Crea un directori de botigues a grups a Open Food Network." + footer_join_cta: "Vull saber-ne més!" + footer_legal_call: "Llegiu el nostre" + footer_legal_tos: "Termes i condicions" + footer_legal_visit: "Troba'ns a" + footer_legal_text_html: "Open Food Network és una plataforma de programari lliure i de codi obert. El nostre contingut està llicenciat amb %{content_license} i el nostre codi amb %{code_license}." + footer_data_text_with_privacy_policy_html: "Tenim cura de les vostres dades. Vegeu les nostres %{privacy_policy} i %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "Tenim cura de les vostres dades. Vegeu la nostra %{cookies_policy}" + footer_data_privacy_policy: "política de privacitat" + footer_data_cookies_policy: "política de cookies" + footer_skylight_dashboard_html: Les dades de rendiment estan disponibles a %{dashboard}. + shop: + messages: + login: "Inicia sessió" + register: "Registra't" + contact: "contacta" + require_customer_login: "Aquesta botiga només és per a consumidores registrades." + require_login_html: "Si us plau%{login} si ja teniu un compte. En cas contrari, %{register} per convertir-vos en una consumidora." + require_customer_html: "Si us plau%{contact} %{enterprise} per convertir-vos en consumidora." + card_could_not_be_updated: No s'ha pogut actualitzar la targeta + card_could_not_be_saved: no s'ha pogut desar la targeta + spree_gateway_error_flash_for_checkout: "Hi ha hagut un problema amb la vostra informació de pagament: %{error}" + invoice_billing_address: "Adreça de facturació:" + invoice_column_price: "Preu" + invoice_column_item: "Article" + invoice_column_qty: "Quantitat" + invoice_column_unit_price_with_taxes: "Preu unitari (IVA inclòs)" + invoice_column_unit_price_without_taxes: "Preu unitari (impost exclòs)" + invoice_column_price_with_taxes: "Preu total (IVA inclòs)" + invoice_column_price_without_taxes: "Preu total (sense impostos)" + invoice_column_tax_rate: "Taxa d'impost" + tax_invoice: "FACTURA D'IMPOSTOS" + tax_total: "Impost total (%{rate}):" + total_excl_tax: "Total (impostos exclòs):" + total_incl_tax: "Total (impost inclòs):" + invoice_issued_on: "Factura emesa el:" + order_number: "Nombre de factura:" + date_of_transaction: "Data de la transacció:" + ticket_column_qty: "Quantitat" + ticket_column_item: "Article" + ticket_column_unit_price: "Preu unitari" + ticket_column_total_price: "Preu total" + menu_1_title: "Botigues" + menu_1_url: "/shops" + menu_2_title: "Mapa" + menu_2_url: "/map" + menu_3_title: "Productors" + menu_3_url: "/producers" + menu_4_title: "Grups" + menu_4_url: "/groups" + menu_5_title: "Sobre" + menu_5_url: " " + menu_6_title: "Connecta" + menu_6_url: " " + menu_7_title: "Aprèn" + menu_7_url: " " + logo: "Logotip (640x130)" + logo_mobile: "Logotip mòbil (75x26)" + logo_mobile_svg: "Logotip per mòbil (SVG)" + home_hero: " " + home_show_stats: "Mostra estadístiques" + footer_logo: "Logotip (220x76)" + footer_facebook_url: "URL de Facebook" + footer_twitter_url: "URL de Twitter" + footer_instagram_url: "URL d'Instagram" + footer_linkedin_url: "URL de LinkedIn" + footer_googleplus_url: "URL de Google Plus" + footer_pinterest_url: "URL de Pinterest" + footer_email: "Correu electrònic" + footer_links_md: "Enllaços" + footer_about_url: "Quant a l'URL" + user_guide_link: "Enllaç a la guia de l'usuari" + name: Nom + first_name: Nom + last_name: Cognoms + email: Correu electrònic + phone: Telèfon + next: Següent + address: Adreça + address_placeholder: 'p. ex: Carrer Ample, 123' + address2: Adreça (continua) + city: Municipi + city_placeholder: 'p. ex: Sitges' + postcode: Codi postal + postcode_placeholder: 'p. ex: 08870' + state: Estat + country: País + unauthorized: No autoritzat + terms_of_service: "Termes del servei" + on_demand: Sota demanda + none: No productora + not_allowed: No permès + no_shipping: sense mètodes d'enviament + no_payment: sense mètodes de pagament + no_shipping_or_payment: sense mètodes d'enviament o pagament + unconfirmed: no confirmat + days: dies + label_shop: "Botiga" + label_shops: "Botigues" + label_map: "Mapa" + label_producer: "Productora" + label_producers: "Productors" + label_groups: "Grups" + label_about: "Sobre" + label_connect: "Connecta" + label_learn: "Aprèn" + label_blog: "Blog" + label_support: "Suport" + label_shopping: "Compres" + label_login: "Inicia sessió" + label_logout: "Tanca sessió" + label_signup: "Registra't" + label_administration: "Administració" + label_admin: "Administradora" + label_account: "Compte" + label_more: "Mostrar més" + label_less: "Mostra menys" + label_notices: "Avisos" + cart_items: "articles" + cart_headline: "La teva cistella" + total: "Total" + cart_updating: "Actualitzant la cistella..." + cart_empty: "Cistella buida" + cart_edit: "Edita la teva cistella" + card_number: Número de targeta + card_securitycode: "Codi de seguretat" + card_expiry_date: Data de caducitat + card_masked_digit: "X" + new_credit_card: "Nova targeta de crèdit" + my_credit_cards: Les meves targetes de crèdit + add_new_credit_card: 'Afegeix una nova targeta de crèdit ' + saved_cards: Targetes desades + add_a_card: Afegeix una targeta + add_card: Afegeix targeta + you_have_no_saved_cards: Encara no heu guardat cap targeta + saving_credit_card: Desant la targeta de crèdit... + card_has_been_removed: "S'ha eliminat la teva targeta (número: %{number})" + card_could_not_be_removed: Ho sentim, no s'ha pogut eliminar la targeta + ie_warning_headline: "El vostre navegador no està actualitzat :-(" + ie_warning_text: "Per obtenir la millor experiència a Open Food Network et recomanem que actualitzis el teu navegador:" + ie_warning_chrome: Descarrega Chrome + ie_warning_firefox: Descarrega Firefox + ie_warning_ie: Actualitza Internet Explorer + ie_warning_other: "No pots actualitzar el navegador? Prova Open Food Network al telèfon mòbil :-)" + legal: + cookies_policy: + header: "Com utilitzem les cookies" + desc_part_1: "Les cookies són fitxers de text molt petits que s'emmagatzemen a l'ordinador quan visites alguns llocs web." + desc_part_2: "A OFN som plenament respectuosos amb la teva privadesa. Utilitzem només les cookies que són necessàries per oferir-te el servei de compra/venda d'aliments en línia. No venem cap de les vostres dades. En el futur, podríem proposar que compartiu algunes de les vostres dades per crear serveis públics nous que poguessin ser útils per a l'ecosistema (com ara serveis de logística per als sistemes d'alimentació curts), però encara no hi estem treballant i no ho farem sense la vostra autorització :-)" + desc_part_3: "Utilitzem cookies principalment per recordar qui ets si inicies la sessió al servei o per recordar els elements que heu introduït a la vostra cistella encara que no hàgiu iniciat la sessió. Si continues navegant al lloc web sense fer clic \"Acceptar cookies\", suposem que ens estàs donant el consentiment per emmagatzemar les cookies que són essencials per al funcionament del lloc web. Aquí tens la llista de les cookies que fem servir." + essential_cookies: "Cookies essencials" + essential_cookies_desc: "Les cookies següents són estrictament necessàries per al funcionament del nostre lloc web." + essential_cookies_note: "La majoria de les cookies només contenen un identificador únic, però no hi ha altres dades, de manera que la teva adreça de correu electrònic i contrasenya, per exemple, mai no estan contingudes ni exposades." + cookie_domain: "Establert per:" + cookie_session_desc: "S'utilitza per permetre que el lloc web recordi usuaris entre visites a la pàgina, per exemple, recordar els articles de la vostra cistella." + cookie_consent_desc: "S'utilitza per mantenir l'estat del consentiment de l'usuari per emmagatzemar cookies" + cookie_remember_me_desc: "S'utilitza si l'usuari ha demanat que el lloc web el recordi. Aquesta cookie s'elimina automàticament després de 12 dies. Si com a usuari voleu que se suprimeixi aquesta cookie, només heu de tancar la sessió. Si no voleu que aquesta cookie s'instal·li a l'ordinador, no haureu de marcar la casella \"Recorda'm\" quan inicieu la sessió." + cookie_openstreemap_desc: "Utilitzat pel nostre proveïdor de confiança d'emmagatzematge de codi obert (OpenStreetMap) per garantir que no rebis massa sol·licituds durant un període de temps determinat, per evitar l'abús dels seus serveis." + cookie_stripe_desc: "Dades recollides pel nostre processador de pagaments Stripe per a la detecció de frau https://stripe.com/cookies-policy/legal. No totes les botigues utilitzen Stripe com a mètode de pagament, però és una bona pràctica per evitar que el frau s'apliqui a totes les pàgines. Probablement Stripe construeixi una imatge de quines de les nostres pàgines solen interactuar amb la seva API i, a continuació, marca qualsevol cosa inusual. Així, configurar les cookies d'Stripe té una funció més àmplia que simplement proporcionar un mètode de pagament a un usuari. Eliminant-lo podria afectar la seguretat del propi servei. Pots obtenir més informació sobre Stripe i llegir la seva política de privadesa a https://stripe.com/privacy." + statistics_cookies: "Cookies d'estadístiques" + statistics_cookies_desc: "Les següents no són estrictament necessàries, però ajuden a proporcionar-vos la millor experiència d'usuari, permetent-nos analitzar el comportament de l'usuari, identificar quines funcions s'utilitzen més o quines no es fan servir, comprendre problemes d'experiència d'usuari, etc." + statistics_cookies_analytics_desc_html: "Per recopilar i analitzar les dades d'ús de la plataforma utilitzem Google Analytics ja que era el servei predeterminat connectat amb Spree (el programari de codi obert de comerç en línia que hem construït), però la nostra visió és canviar a Matomo (ex Piwik, eina d'anàlisi de codi obert compatible amb GDPR i protegeix la vostra privadesa) tan aviat com puguem. " + statistics_cookies_matomo_desc_html: "Per recopilar i analitzar les dades d'ús de la plataforma, utilitzem Matomo (ex Piwik), una eina d'anàlisi de codi obert que és compatible amb GDPR i protegeix la vostra privadesa." + statistics_cookies_matomo_optout: "Vols desactivar l'anàlisi de dades de Matomo? No recopilem cap dada personal i Matomo ens ajuda a millorar el nostre servei però respectem la teva elecció :-)" + cookie_analytics_utma_desc: "S'utilitza per distingir usuaris i sessions. La cookie es crea quan s'executa la biblioteca javascript i no existeixen cookies __utma existents. La cookie s'actualitza cada cop que s'envien dades a Google Analytics." + cookie_analytics_utmt_desc: "S'utilitza per accelerar la quantitat de sol·licituds." + cookie_analytics_utmb_desc: "S'utilitza per determinar noves sessions / visites. La cookie es crea quan s'executa la biblioteca javascript i no existeixen cookies __utmb existents. La cookie s'actualitza cada cop que s'envien dades a Google Analytics." + cookie_analytics_utmc_desc: "No s'utilitza a ga.js. Estableix la interoperabilitat amb urchin.js. Històricament, aquesta cookie funcionava juntament amb la cookie __utmb per determinar si l'usuari estava en una nova sessió / visita." + cookie_analytics_utmz_desc: "Emmagatzema l'origen del codi o la campanya que explica com l'usuari ha arribat al vostre lloc. La cookie es crea quan s'executa i s'actualitza la biblioteca javascript cada vegada que s'envien dades a Google Analytics." + cookie_matomo_basics_desc: "Les primeres cookies de Matomo per recollir estadístiques." + cookie_matomo_ignore_desc: "La cookie usada per excloure l'usuari de ser seguit." + disabling_cookies_header: "Advertència sobre la desactivació de cookies" + disabling_cookies_desc: "Com a usuari sempre podeu permetre, bloquejar o eliminar les cookies Open Food Network o qualsevol altra pàgina web sempre que vulgueu mitjançant el control de configuració del vostre navegador. Cada navegador té una operativa diferent. Aquests són els enllaços:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" + disabling_cookies_note: "Però tingueu en compte que si suprimiu o modifiqueu les cookies essencials que utilitza Open Food Network el lloc web no funcionarà; no podreu afegir res a la vostra cistella ni per validar la compra, per exemple." + cookies_banner: + cookies_usage: "Aquest lloc utilitza cookies per fer que la teva navegació no sigui problemàtica i sigui segura i ens ajuda a comprendre com navegues per millorar les funcions que oferim." + cookies_definition: "Les cookies són fitxers de text molt petits que s'emmagatzemen a l'ordinador quan visites alguns llocs web." + cookies_desc: "Utilitzem només les cookies que són necessàries per oferir-te el servei de compra/venda d'aliments en línia. No venem cap de les teves dades. Utilitzem cookies principalment per recordar qui ets si inicies la sessió al servei o per recordar els elements que has introduït a la teva cistella encara que no hagis iniciat la sessió. Si continues navegant al lloc web sense fer clic \"Acceptar cookies\", suposem que ens estàs donant el consentiment per emmagatzemar les cookies que són essencials per al funcionament del lloc web." + cookies_policy_link_desc: "Si vols obtenir més informació consulta la nostra" + cookies_policy_link: "política de cookies" + cookies_accept_button: "Acceptar les cookies" + home_shop: Compra ara + brandstory_headline: "Aliments de proximitat." + brandstory_intro: "De vegades la millor manera de solucionar el sistema és començar una nova ..." + brandstory_part1: "Comencem des del principi. Amb agricultores i productores disposades a explicar les seves històries amb orgull i veritat. Amb les distribuïdores disposades a connectar persones amb productes de manera justa i honesta. Amb les consumidores que creuen que millors decisions de compra setmanals poden seriosament canviar el món." + brandstory_part2: "A continuació, necessitem una manera de fer-ho real. Una forma d'empoderar a totes les que cultiven, venen i compren aliments. Una manera d'explicar totes les històries, de gestionar tota la logística. Una manera de convertir la transacció en transformació cada dia." + brandstory_part3: "Així que construïm un mercat en línia que anivella el camp de joc. És transparent, de manera que crea relacions reals. És de codi obert, de manera que és propietat de tothom. Escala a regions i nacions, de manera que les persones comencin versions a tot el món." + brandstory_part4: "Funciona a tot arreu. Ho canvia tot." + brandstory_part5_strong: "L'anomenem Open Food Network." + brandstory_part6: "A totes ens agrada menjar. Ara també podem estimar el nostre sistema alimentari." + learn_body: "Explora models, històries i recursos per ajudar-te a desenvolupar el teu negoci o organització de menjar just. Troba coneixements, esdeveniments i altres oportunitats d'aprendre dels companys." + learn_cta: "Inspira't" + connect_body: "Busca els nostres directoris complets de productores, organitzacions o xarxes per trobar menjar de proximitat i just a prop teu. Indica i llista la teva empresa o organització a l'OFN perquè les consumidores et trobin. Uneix-te a la comunitat per obtenir consells i resoldre problemes juntes." + connect_cta: "Explorar" + system_headline: "Compres - aquí t'expliquem com funcionen." + system_step1: "1. Cerca" + system_step1_text: "Cerca a les nostres botigues diverses i independents per menjar producte local de temporada. Cerca per població i categoria d'aliments, o bé si prefereixes lliurament o recollida." + system_step2: "2. Compra" + system_step2_text: "Transforma les teves transaccions amb aliments locals assequibles de diverses productores i grups. Coneix les històries del teu menjar i de les persones que el fan!" + system_step3: "3. Recollida / lliurament" + system_step3_text: "Espereu per al vostre lliurament o visiteu la vostra productora o grup de consum per tenir una connexió més personal amb el vostre menjar. La compra d'aliments és tan diversa com ens proposem." + cta_headline: "Compres que fan del món un lloc millor." + cta_label: "Estic preparada" + stats_headline: "Estem creant un nou sistema alimentari." + stats_producers: "productores d'aliments" + stats_shops: "botigues d'alimentació" + stats_shoppers: "consumidores d'aliments" + stats_orders: "comandes de menjar" + checkout_title: Realitza la comanda + checkout_now: Validar ara + checkout_order_ready: Comanda preparada per + checkout_hide: Amaga + checkout_expand: Expandeix + checkout_headline: "Estàs preparada per validar la compra?" + checkout_as_guest: "Realitzar comanda com a convidat" + checkout_details: "Els teus detalls" + checkout_billing: "Informació de facturació" + checkout_default_bill_address: "Desa per defecte com a adreça de facturació " + checkout_shipping: Informació d'enviament + checkout_default_ship_address: "Desa per defecte com a adreça d'enviament " + checkout_method_free: Gratuït + checkout_address_same: L'adreça d'enviament és igual que l'adreça de facturació? + checkout_ready_for: "Preparat per:" + checkout_instructions: "Alguns comentaris o instruccions especials?" + checkout_payment: Pagament + checkout_send: Realitza una comanda ara + checkout_your_order: La teva comanda + checkout_cart_total: Total de la cistella + checkout_shipping_price: Enviament + checkout_total_price: Total + checkout_back_to_cart: "Tornar a la cistella" + cost_currency: "Moneda del cost" + order_paid: PAGAT + order_not_paid: NO PAGAT + order_total: Total de la comanda + order_payment: "Pagament a través de:" + order_billing_address: Adreça de facturació + order_delivery_on: Lliurament a + order_delivery_address: Adreça de lliurament + order_delivery_time: Hora de lliurament + order_special_instructions: "Les teves notes:" + order_pickup_time: Llest per a la recollida + order_pickup_instructions: Instruccions de recollida + order_produce: Productes + order_total_price: Total + order_includes_tax: (inclou impostos) + order_payment_paypal_successful: El teu pagament mitjançant PayPal s'ha processat correctament. + order_hub_info: Informació del grup + order_back_to_store: Tornar a la botiga + order_back_to_cart: Tornar a la cistella + bom_tip: "Utilitzeu aquesta pàgina per alterar les quantitats de productes en diverses comandes. Els productes també es poden eliminar de les comandes completament, si es requereix." + unsaved_changes_warning: "Hi ha canvis sense desar i es perdran si continueu." + unsaved_changes_error: "Els camps amb vora vermella contenen errors." + products: "Productes" + products_in: "en %{oc}" + products_at: "a %{distributor}" + products_elsewhere: "Productes trobats en altres llocs" + email_welcome: "Benvinguda" + email_confirmed: "Gràcies per confirmar la teva adreça de correu electrònic." + email_registered: "ara forma part de" + email_userguide_html: "La Guia d'usuari amb suport detallat per configurar una productora o grup de consum és aquí: %{link}" + email_admin_html: "Pots gestionar el teu compte iniciant sessió a l'%{link} o fent clic a la pestanya a la part superior dreta de la pàgina d'inici i seleccionant Administració." + email_community_html: "També tenim un fòrum en línia per debats de la comunitat relacionada amb el programari OFN i els desafiaments únics de triar endavant una organització alimentària. T'animem a unir-t'hi. Estem en constant evolució i les teves contribucions en aquest fòrum donaran forma al que passi a en el futur. %{link}" + join_community: "Uneix-te a la comunitat" + email_confirmation_activate_account: "Abans de poder activar el compte nou hem de confirmar la teva adreça de correu electrònic." + email_confirmation_greeting: "Hola, %{contact}!" + email_confirmation_profile_created: "S'ha creat exitosament un perfil per %{name}. Per activar el teu perfil hem de confirmar aquesta adreça de correu electrònic." + email_confirmation_click_link: "Si us plau fes clic a l'enllaç següent per confirmar el teu correu electrònic i continuar configurant el teu perfil." + email_confirmation_link_label: "Confirma aquesta adreça de correu electrònic »" + email_confirmation_help_html: "Després de confirmar el teu correu electrònic podràs accedir al teu compte d'administració per a aquesta organització. Consulta l'%{link} per obtenir més informació sobre les funcions d'%{sitename} i començar a utilitzar el teu perfil o botiga en línia." + email_confirmation_notice_unexpected: "Has rebut aquest missatge perquè t'has inscrit a l'%{sitename} o perquè has estat convidada a registrar-te per algú que probablement coneixes. Si no entens per què estàs rebent aquest correu electrònic, escriu a %{contact}." + email_social: "Connecta't amb nosaltres:" + email_contact: "Envia'ns un correu electrònic:" + email_signoff: "Salut," + email_signature: "Equip %{sitename}" + email_confirm_customer_greeting: "Hola %{name}," + email_confirm_customer_intro_html: "Gràcies per comprar a %{distributor} ." + email_confirm_customer_number_html: "Confirmació de la comanda # %{number} " + email_confirm_customer_details_html: "Aquests són els detalls de la teva comanda de %{distributor} :" + email_confirm_customer_signoff: "Salutacions cordials," + email_confirm_shop_greeting: "Hola %{name}," + email_confirm_shop_order_html: "Que bé! Tens una nova comanda per %{distributor} ." + email_confirm_shop_number_html: "Confirmació de la comanda # %{number} " + email_order_summary_item: "Article" + email_order_summary_quantity: "quant." + email_order_summary_price: "Preu" + email_order_summary_subtotal: "Subtotal:" + email_order_summary_total: "Total:" + email_order_summary_includes_tax: "(inclou impostos):" + email_payment_paid: PAGAT + email_payment_not_paid: 'NO PAGAT ' + email_payment_summary: Resum del pagament + email_payment_method: "Pagament a través de:" + email_so_placement_intro_html: "Tens una nova comanda amb %{distributor} " + email_so_placement_details_html: "Aquests són els detalls de la comanda de %{distributor} :" + email_so_placement_changes: "Malauradament, no tots els productes que has demanat estaven disponibles. Les quantitats originals que has sol·licitat apareixen ratllades a sota." + email_so_payment_success_intro_html: "S'ha processat un pagament automàtic per a la vostra comanda des de %{distributor} ." + email_so_placement_explainer_html: "Aquesta comanda s'ha creat automàticament per tu." + email_so_edit_true_html: "Potd fer canvis fins que les comandes es tanquin el %{orders_close_at}." + email_so_edit_false_html: "Pots veure detalls d'aquesta comanda en qualsevol moment." + email_so_contact_distributor_html: "Si tens alguna pregunta pots contactar amb %{distributor} a través d'%{email}." + email_so_contact_distributor_to_change_order_html: "Aquesta comanda s'ha creat automàticament per a vostè. Podeu fer canvis fins que les comandes es tanquin a %{orders_close_at} contactant a %{distributor} a través d'%{email}." + email_so_confirmation_intro_html: "La teva comanda amb %{distributor} ja està confirmada" + email_so_confirmation_explainer_html: "Vas realitzar aquesta comanda automàticament i ara s'ha finalitzat." + email_so_confirmation_details_html: "A continuació trobareu tot el que necessiteu saber sobre la comanda de %{distributor} :" + email_so_empty_intro_html: "Hem intentat fer una nova comanda amb %{distributor} , però hem tingut alguns problemes..." + email_so_empty_explainer_html: "Malauradament, cap dels productes que heu demanat estava disponible, de manera que no s'ha realitzat cap comanda. Les quantitats originals que heu sol·licitat apareixen ratllades a sota." + email_so_empty_details_html: "Aquests són els detalls de la comanda sense confirmar per %{distributor} :" + email_so_failed_payment_intro_html: "Hem intentat processar un pagament però hem tingut alguns problemes..." + email_so_failed_payment_explainer_html: "El pagament de la vostra subscripció amb %{distributor} ha fallat a causa d'un problema amb la vostra targeta de crèdit. S'ha notificat a %{distributor} d'aquest pagament fallit." + email_so_failed_payment_details_html: "Aquests són els detalls de l'error proporcionats per la passarel·la de pagament:" + email_shipping_delivery_details: Detalls de lliurament + email_shipping_delivery_time: "Lliurament a:" + email_shipping_delivery_address: "Adreça de lliurament:" + email_shipping_collection_details: Detalls de la recollida + email_shipping_collection_time: "Llest per a la recollida:" + email_shipping_collection_instructions: "Instruccions de recollida:" + email_special_instructions: "Les teves notes: " + email_signup_greeting: Hola! + email_signup_welcome: "Benvinguda a %{sitename}!" + email_signup_confirmed_email: "Gràcies per confirmar el teu correu electrònic." + email_signup_shop_html: "Ara pots iniciar sessió a %{link}." + email_signup_text: "Gràcies per unir-te a la xarxa. Si ets una consumidora, esperem presentar-te a moltes agricultores fantàstiques, meravellosos grups de consum i aliments deliciosos. Si ets una productora o organització alimentària, ens complau tenir-te com a part de la xarxa." + email_signup_help_html: "Donem la benvinguda a totes les teves preguntes i comentaris; pots utilitzar el botó Enviar comentaris del lloc web o enviar-nos un correu electrònic a %{email}" + invite_email: + greeting: "Hola! " + invited_to_manage: "Has estat convidada a gestionar %{enterprise} a %{instance}." + confirm_your_email: "Hauries d'haver rebut o aviat rebràs un correu electrònic amb un enllaç de confirmació. No podràs accedir al perfil de%{enterprise} fins que no hagis confirmat el teu correu electrònic." + set_a_password: "A continuació se us demanarà que configureu una contrasenya abans de poder administrar l'organització." + mistakenly_sent: "No esteu segur de perquè heu rebut aquest correu electrònic? Poseu-vos en contacte amb %{owner_email} per obtenir més informació." + producer_mail_greeting: "Benvolguda" + producer_mail_text_before: "Ara tenim totes les comandes per al proper repartiment." + producer_mail_order_text: "Aquí tens un resum de les comandes dels teus productes:" + producer_mail_delivery_instructions: "Instruccions de recollida / lliurament d'estoc:" + producer_mail_signoff: "Gràcies i els millors desitjos" + shopping_oc_closed: Les comandes estan tancades + shopping_oc_closed_description: "Si us plau espera fins que s'obri el pròxim cicle (o posa't en contacte amb nosaltres directament per veure si podem acceptar alguna comanda fora de temps)" + shopping_oc_last_closed: "L'últim cicle va tancar fa %{distance_of_time} " + shopping_oc_next_open: "El següent cicle s'obre en %{distance_of_time}" + shopping_tabs_about: "Sobre %{distributor}" + shopping_tabs_contact: "Contacte" + shopping_contact_address: "Adreça" + shopping_contact_web: "Contacte" + shopping_contact_social: "Segueix" + shopping_groups_part_of: "forma part de:" + shopping_producers_of_hub: "Productores de%{hub}:" + enterprises_next_closing: "Tancament de la comanda següent" + enterprises_ready_for: "Preparat per" + enterprises_choose: "Escull quan vols la teva comanda:" + maps_open: "Obert" + maps_closed: "Tancat" + hubs_buy: "Compreu per:" + hubs_shopping_here: "Compra aquí" + hubs_orders_closed: "Comandes tancades" + hubs_profile_only: "Només perfil" + hubs_delivery_options: "Opcions de lliurament" + hubs_pickup: "Recollida" + hubs_delivery: "Lliurament" + hubs_producers: "Les nostres productores" + hubs_filter_by: "Filtra per" + hubs_filter_type: "Tipus" + hubs_filter_delivery: "Lliurament " + hubs_filter_property: "Propietat" + hubs_matches: "Volies dir?" + hubs_intro: 'Compreu a la vostra zona ' + hubs_distance: El més proper a + hubs_distance_filter: "Mostra'm botigues properes a%{location}" + shop_changeable_orders_alert_html: + one: El vostre ordre amb %{shop} / %{order} està obert per a la seva revisió. Podeu fer canvis fins %{oc_close}. + other: Tens %{count} comandes amb %{shop} actualment obertes per a la seva revisió. Pots fer canvis fins %{oc_close}. + orders_changeable_orders_alert_html: S'ha confirmat aquesta comanda però pots fer-hi canvis fins a %{oc_close} . + products_clear_all: Esborra-ho tot + products_showing: "S'està mostrant:" + products_with: amb + products_search: "Cerca per producte o productora" + products_loading: "S'estan carregant els productes..." + products_updating_cart: "Actualitzant la cistella..." + products_cart_empty: "Cistella buida" + products_edit_cart: "Edita la teva cistella" + products_from: de + products_change: "No hi ha canvis per desar." + products_update_error: "No s'ha pogut desar pel(s) següent(s) error(s):" + products_update_error_msg: "S'ha produït un error en desar." + products_update_error_data: "S'ha produït un error en desar a causa de dades no vàlides:" + products_changes_saved: "S'han desat els canvis." + search_no_results_html: "Ho sentim, no s'ha trobat cap resultat per %{query}. Intentar una altra cerca?" + components_profiles_popover: "Els perfils no tenen una botiga a l'OFN però poden tenir la seva pròpia botiga física o en línia en altres llocs" + components_profiles_show: "Mostra els perfils" + components_filters_nofilters: "Sense filtres" + components_filters_clearfilters: "Esborra tots els filtres" + groups_title: Grups + groups_headline: Xarxes / regions + groups_text: "Cada productora és única. Tots els negocis tenen alguna cosa diferent per oferir. Els nostres grups són col·lectius o xarxes de productores, grups de consum o distribuïdores que comparteixen alguna cosa comú com la ubicació, la parada en un mercat de pagès o la filosofia. Això fa que la teva experiència de compra sigui més fàcil. Explora els nostres grups." + groups_search: "Cerca nom o paraula clau" + groups_no_groups: "No s'ha trobat cap xarxa" + groups_about: "Sobre nosaltres" + groups_producers: "Les nostres productores" + groups_hubs: "Els nostres grups" + groups_contact_web: Contacte + groups_contact_social: Segueix + groups_contact_address: Adreça + groups_contact_email: Envieu-nos un correu electrònic + groups_contact_website: Visita el nostre lloc web + groups_contact_facebook: Segueix-nos a Facebook + groups_signup_title: Registra't com a grup + groups_signup_headline: Inscripció de xarxes + groups_signup_intro: "Som una plataforma sorprenent per a la comercialització col·laborativa, la forma més senzilla perquè els vostres membres i persones interessades arribin a nous mercats. Som sense ànim de lucre, assequibles i senzills." + groups_signup_email: Envieu-nos un correu electrònic + groups_signup_motivation1: Transformem els sistemes alimentaris de manera justa. + groups_signup_motivation2: És per això que sortim del llit cada dia. Som un globals i sense ànim de lucre, basats en codi font obert. Juguem just. Sempre podràs confiar en nosaltres. + groups_signup_motivation3: Sabem que tens grans idees i et volem ajudar. Compartirem els nostres coneixements, xarxes i recursos. Sabem que l'aïllament no genera canvis, així que ens associarem amb tu. + groups_signup_motivation4: Ens trobem on ets. + groups_signup_motivation5: És possible que formis part d'una xarxa de consumidores, grup de consum, productora o distribuïdora, un organisme industrial o un govern local. + groups_signup_motivation6: Independentment del teu paper en el moviment d'aliments local, estem preparades per ajudar-te. No obstant això, si vens a preguntar-se com és Open Food Network o que està fent a la teva part del món, comencem la conversa. + groups_signup_motivation7: Fem que els moviments dels aliments tinguin més sentit. + groups_signup_motivation8: Cal activar i habilitar les xarxes, oferim una plataforma de conversa i d'acció. Necessites un compromís real. Volem ajudar totes les jugadores, totes les parts interessades, tots els sectors. + groups_signup_motivation9: Necessites recursos. Et brindarem tota la nostra experiència. Necessites cooperació. Et connectarem millor amb una xarxa global d'iguals. + groups_signup_pricing: Compte de grup + groups_signup_studies: Casos d'estudi + groups_signup_contact: Preparada per debatre? + groups_signup_contact_text: "Posa't en contacte per descobrir què pot fer OFN per tu:" + groups_signup_detail: "Aquest és el detall." + login_invalid: "Correu electrònic o contrasenya no vàlids" + modal_hubs: "Grups" + modal_hubs_abstract: Els nostres grups són el punt de contacte entre tu i les persones que fan els teus aliments. + modal_hubs_content1: Pots cercar un grup convenient per ubicació o nom. Alguns grups tenen diversos punts on pots recollir les vostres comandes i alguns també proporcionen opcions de lliurament. Cada grup és un punt de venda amb operacions comercials i logística independents, per la qual cosa és normal que existeixin variacions d'un grup a un altre. + modal_hubs_content2: Només pots comprar en un grup de consum a la vegada. + modal_groups: "Xarxes / regions" + modal_groups_content1: Aquestes són les organitzacions i les relacions entre els grups que conformen l'OFN + modal_groups_content2: Alguns grups estan agrupats per localització o Ajuntament, altres per similituds no geogràfiques. + modal_how: "Com funciona" + modal_how_shop: Compreu a Open Food Network + modal_how_shop_explained: Cerca un grup de consum a prop teu per començar a comprar. Pots expandir cada grup per veure quins tipus de productes estan disponibles i fer clic per començar a comprar. (Només pots comprar en un grup alhora). + modal_how_pickup: Costes de recollida, lliurament i enviament + modal_how_pickup_explained: Algunes organitzacións o grups lliuren a la vostra porta, mentre que altres requereixen que aneu a buscar les vostres comandes. Podeu veure quines opcions hi ha disponibles a la pàgina d'inici i seleccionar el que vulgueu a les pàgines de confirmació de la compra. L'enviament costarà més, i els preus difereixen de l'organització. Cada grup és un punt de venda amb operacions comercials i logística independents, per la qual cosa es existeixen variacions entre grups. + modal_how_more: Aprèn-ne més + modal_how_more_explained: "Si vols saber-ne més sobre l'Open Food Network, com funciona i participar-hi, consulta:" + modal_producers: "Productors" + modal_producers_explained: "Les nostres productores elaboren tot el menjar deliciós que pots adquirir a l'Open Food Network." + producers_about: Sobre nosaltres + producers_buy: 'Compreu ' + producers_contact: Contacte + producers_contact_phone: Truca + producers_contact_social: Segueix + producers_buy_at_html: "Compra productes de %{enterprise} a:" + producers_filter: Filtra per + producers_filter_type: Tipus + producers_filter_property: Propietat + producers_title: Productors + producers_headline: Troba productores locals + producers_signup_title: Registra't com a productora + producers_signup_headline: Productores d'aliments, empoderades. + producers_signup_motivation: Ven el teu menjar i explica la teva història a diversos nous mercats. Estalvia temps i diners en totes les despeses generals. Done suport la innovació sense el risc. Hem aplanat el terreny de joc. + producers_signup_send: Uneix-te ara + producers_signup_enterprise: Comptes de l'organització + producers_signup_studies: Històries de les nostres productores. + producers_signup_cta_headline: Uneix-te ara! + producers_signup_cta_action: 'Uneix-te ara ' + producers_signup_detail: Aquest és el detall. + products_item: Article + products_description: Descripció + products_variant: Variant + products_quantity: Quantitat + products_available: Disponible? + products_producer: "Productor" + products_price: "Preu" + register_title: Registra't + sell_title: "Registra't" + sell_headline: "Afegiu-vos a Katuma!" + sell_motivation: "Mostra el teu bonic menjar." + sell_producers: "Productors" + sell_hubs: "Grups" + sell_groups: "Grups" + sell_producers_detail: "Configura un perfil per a la vostra empresa a Katuma en qüestió de minuts. En qualsevol moment pots convertir el teu perfil en una botiga en línia i vendre els teus productes directament a les consumidores." + sell_hubs_detail: "Configura un perfil per a la teva organització alimentària a Katuma. En qualsevol moment, pots modificar i convertir el teu perfil en una botiga de diverses productores." + sell_groups_detail: "Configura un directori personalitzat d'empreses (productores i altres empreses alimentàries) per a la teva regió o per a la teva xarxa, organització." + sell_user_guide: "Troba més informació a la nostra Guia d'usuari." + sell_embed: "També podem incrustar una botiga OFN al vostre web personalitzat o crear un lloc web personalitzat de la xarxa alimentària per a la teva regió." + sell_ask_services: "Pregunta'ns sobre els serveis d'OFN." + shops_title: Botigues + shops_headline: Compres, transformades. + shops_text: Els aliments creixen en cicles, les agricultores cullen en cicles, i nosaltres fem les comandes de menjar en cicles. Si trobes un cicle de comandes tancat, torna a consultar properament. + shops_signup_title: Inscriu-te com a grup + shops_signup_headline: Grups, il·limitats. + shops_signup_motivation: Sigui quin sigui el teu model, et donem suport. Tot i que canvieu, estarem amb tu. Som sense ànim de lucre, independents i obertes. Som les sòcies de programari que has somiat. + shops_signup_action: Uneix-te ara + shops_signup_pricing: Comptes de l'organització + shops_signup_stories: Històries dels nostres grups. + shops_signup_help: Estem preparadess per ajudar. + shops_signup_help_text: Necessites un millor retorn. Necessites noves compradores i sòcies logístiques. Necessites la teva història explicada a través de majoristes, minoristes i de la taula de la cuina. + shops_signup_detail: Aquest és el detall. + orders: Comandes + orders_fees: Comissions... + orders_edit_title: Cistella de la compra + orders_edit_headline: El teu cistell de la compra + orders_edit_time: Comanda preparada per + orders_edit_continue: Continuar comprant + orders_edit_checkout: Realitza la compra + orders_form_empty_cart: "Cistella buida" + orders_form_admin: Administració i manipulació + orders_form_total: Total + orders_oc_expired_headline: S'han tancat les comandes per a aquest cicle de comanda + orders_oc_expired_text: "Ho sentim, les comandes d'aquest cicle de comanda es van tancar fa %{time} ! Posa't en contacte amb el teu grup directament per veure si poden acceptar comandes fora de temps." + orders_oc_expired_text_others_html: "Ho sentim, les comandes d'aquest cicle de comanda es van tancar fa %{time} ! Posa't en contacte amb el teu grup directament per veure si poden acceptar comandes fora de temps %{link} ." + orders_oc_expired_text_link: "o consulta els altres cicles de comanda disponibles en aquest grup" + orders_oc_expired_email: "Correu electrònic:" + orders_oc_expired_phone: "Telèfon:" + orders_show_title: Confirmació de la comanda + orders_show_time: Comanda preparada + orders_show_order_number: "Comanda # %{number}" + orders_show_cancelled: Cancel·lada + orders_show_confirmed: Confirmada + orders_your_order_has_been_cancelled: "S'ha cancel·lat la teva comanda" + orders_could_not_cancel: "Disculpa, no s'ha pogut cancel·lar la teva comanda " + orders_cannot_remove_the_final_item: "No es pot eliminar l'article final d'una comanda, si us plau, en comptes d'això cancel·leu la comanda." + orders_bought_items_notice: + one: "Ja s'ha confirmat un element addicional per al cicle d'aquest ordre" + other: "%{count}articles addicionals confirmats per a aquest cicle de comandes" + orders_bought_edit_button: Edita articles confirmats + orders_bought_already_confirmed: "* ja confirmat" + orders_confirm_cancel: Estàs segur que vols cancel·lar aquesta comanda? + products_cart_distributor_choice: "Distribuïdora de la teva comanda:" + products_cart_distributor_change: "La teva distribuïdora d'aquesta comanda canviarà a %{name} si afegeixes aquest producte a la teva cistella." + products_cart_distributor_is: "La distribuïdora d'aquesta comanda és %{name}." + products_distributor_error: "Si us plau, completa la comanda a: %{link} abans de comprar amb una altra distribuïdora." + products_oc: "Cicle de comanda per a la teva comanda:" + products_oc_change: "El cicle de comandes d'aquesta comanda canviarà a %{name} si afegeixes aquest producte a la cistella." + products_oc_is: "El cicle de comandes per a aquesta comanda és %{name}." + products_oc_error: "Si us plau valideu la comanda de%{link} abans de comprar en un cicle de comanda diferent." + products_oc_current: "el vostre cicle de comanda actual" + products_max_quantity: Quantitat màxima + products_distributor: Distribuïdora + products_distributor_info: Quan seleccionis una distribuïdora per a la teva comanda, aquí es mostraran la seva d'adreça i hores de recollida. + products_distribution_adjustment_label: "Distribució del producte per part de %{distributor} per a %{product}" + shop_trial_expires_in: "El període de prova de la vostra botiga caduca en" + shop_trial_expired_notice: "Bones notícies! Hem decidit ampliar els període de prova de la botiga fins a un altre avís." + password: Contrasenya + remember_me: Recorda'm + are_you_sure: "Estàs segur?" + orders_open: Comandes obertes + closing: "Tancant" + going_back_to_home_page: "Tornant a la pàgina d'inici" + creating: Creant + updating: Actualitzant + failed_to_create_enterprise: "No s'ha pogut crear la vostra organització." + failed_to_create_enterprise_unknown: "No s'ha pogut crear la teva organització.\nAssegura't que tots els camps estan completament emplenats." + failed_to_update_enterprise_unknown: "No s'ha pogut actualitzar la teva organització.\nAssegura't que tots els camps estan completament emplenats." + enterprise_confirm_delete_message: "Això també eliminarà el %{product} que subministra aquesta organització. Estàs segura que vols continuar?" + order_not_saved_yet: "Encara no s'ha desat la teva comanda. Dona'ns uns segons per acabar!" + filter_by: "Filtra per" + hide_filters: "Amaga els filtres" + one_filter_applied: "S'ha aplicat 1 filtre" + x_filters_applied: " filtres aplicats" + submitting_order: "Enviant la teva comanda: espereu si us plau" + confirm_hub_change: "Estàs segura? Això canviarà el grup que has seleccionat i eliminarà els articles de la cistella de la compra." + confirm_oc_change: "N'estàs segur? Això canviarà el cicle de comanda seleccionat i eliminarà els articles de la cistella de la compra." + location_placeholder: "Escriu una ubicació..." + error_required: "no es pot deixar en blanc" + error_number: "ha de ser un nombre" + error_email: "ha de ser una adreça de correu electrònic" + error_not_found_in_database: "%{name} no s'ha trobat a la base de dades" + error_not_primary_producer: "%{name} no està habilitat com a productora" + error_no_permission_for_enterprise: "\"%{name}\": no tens permís per gestionar els productes d'aquesta organització" + january: "Gener" + february: "Febrer" + march: "Març" + april: "Abril" + may: "Maig" + june: "Juny" + july: "Juliol" + august: "Agost" + september: "Setembre" + october: "Octubre" + november: "Novembre" + december: "Desembre" + email_not_found: "No s'ha trobat l'adreça de correu electrònic " + email_unconfirmed: "Has de confirmar la teva adreça de correu electrònic abans de poder restablir la contrasenya." + email_required: "Has de proporcionar una adreça de correu electrònic" + logging_in: "Espereu-vos un moment, estem iniciant-vos la sessió" + signup_email: "El teu correu electrònic" + choose_password: "Escull una contrasenya" + confirm_password: "Confirma la contrassenya" + action_signup: "Registra't ara" + welcome_to_ofn: "Benvingut a la xarxa Open Food Network!" + have_an_account: "Ja tens un compte?" + action_login: "Inicia la sessió ara." + forgot_password: "Has oblidat la contrasenya?" + password_reset_sent: "S'ha enviat un correu electrònic amb instruccions per restablir la teva contrasenya." + reset_password: "Restablir la contrasenya" + who_is_managing_enterprise: "Qui és responsable de gestionar %{enterprise}?" + update_and_recalculate_fees: "Actualitza i recalcula les comissions" + registration: + steps: + images: + continue: "Continua" + back: "Enrere" + headline: "Gràcies!" + description: "Som-hi, pugem unes imatges perquè el teu perfil es vegi bonic. :)" + type: + headline: "El darrer pas per afegir %{enterprise}!" + question: "Ets productora?" + yes_producer: "Sí, sóc productora" + no_producer: "No, no sóc productora" + producer_field_error: "Si us plau, escull una de les opcions. Ets productora?" + yes_producer_help: "Les productores fan coses delicioses per menjar i/o beure. Ets productora si cultives, cries, elabores, cous, fermentes..." + no_producer_help: "Si no ets productora, probablement ets algú que ven i distribueix aliments. Pot ser un grup de consum, cooperativa, minorista, majorista o altres." + create_profile: "Crea un perfil" + enterprise: + registration: + modal: + steps: + details: + title: 'Detalls' + headline: "Comencem!" + enterprise: "Ep! Primer necessitem saber una mica sobre la vostra organització:" + producer: "Ep! Primer necessitem saber una mica sobre la teva granja:" + enterprise_name_field: "Nom de l'organització:" + producer_name_field: "Nom de la granja:" + producer_name_field_placeholder: "p. ex: Horta Josep Ribes" + producer_name_field_error: "Si us plau, selecciona un únic nom per a la teva organització" + address1_field: "Adreça línia 1:" + address1_field_placeholder: "p. ex: Carrer Ample, 123" + address1_field_error: "Si us plau, introdueix una adreça" + address2_field: "Adreça (línia 2):" + suburb_field: "Barri:" + suburb_field_placeholder: "p. ex: Les Corts" + suburb_field_error: "Si us plau, introdueix un barri" + postcode_field: "Codi postal:" + postcode_field_placeholder: "p. ex: 08870" + postcode_field_error: "El codi postal és obligatori" + state_field: "Estat:" + state_field_error: "Estat obligatori" + country_field: "País:" + country_field_error: "Si us plau, selecciona un país" + contact: + title: 'Contacte' + contact_field: 'Contacte principal' + contact_field_placeholder: 'Nom de contacte' + contact_field_required: "Has d'introduir un contacte principal." + email_field: 'Correu electrònic' + email_field_placeholder: 'ex: josep@hortajosepribes.com' + phone_field: 'Número de telèfon' + phone_field_placeholder: 'p. ex. 012 345 678' + type: + title: 'Tipus' + about: + title: 'Sobre' + images: + title: 'Imatges' + social: + title: 'Social' + enterprise_contact: "Contacte principal" + enterprise_contact_placeholder: "Nom de contacte" + enterprise_contact_required: "Has d'introduir un contacte principal." + enterprise_email_address: "Correu electrònic" + enterprise_email_placeholder: "ex: josep@hortajosepribes.com" + enterprise_phone: "Número de telèfon " + enterprise_phone_placeholder: "p. ex. 012 345 678 " + back: "Enrere" + continue: "Continua" + limit_reached_headline: "Oh, no!" + limit_reached_message: "Has arribat al límit!" + limit_reached_text: "Has arribat al límit de la quantitat de les organitzacions de les quals pots ser propietari a" + limit_reached_action: "Torna a la pàgina d'inici" + select_promo_image: "Pas 3. Selecciona una imatge promocional" + promo_image_tip: "Consell: es mostra com a banner, la mida recomanada és de 1200 × 260 píxels" + promo_image_label: "Escull una imatge promocional" + action_or: "O" + promo_image_drag: "Arrossegueu i deixeu anar la vostra imatge promocional aquí" + review_promo_image: "Pas 4. Reviseu el vostre banner promocional" + review_promo_image_tip: "Consell: per obtenir millors resultats, la teva imatge de promoció hauria d'omplir l'espai disponible" + promo_image_placeholder: "El teu logotip apareixerà aquí per a la seva revisió una vegada que s'hagi carregat" + uploading: "Carregant..." + select_logo: "Pas 1. Selecciona imatge del logotip" + logo_tip: "Consell: les imatges quadrades funcionaran millor, preferiblement com a mínim 300×300 px" + logo_label: "Escull una imatge de logotip" + logo_drag: "Arrossega i deixa anar el teu logotip aquí" + review_logo: "Pas 2. Revisa el teu logotip" + review_logo_tip: "Consell: per obtenir millors resultats, el teu logotip ha d'omplir l'espai disponible" + logo_placeholder: "El teu logotip apareixerà aquí per a la seva revisió una vegada que s'hagi carregat " + enterprise_about_headline: "Bona!" + enterprise_about_message: "Ara expliqueu-ne els detalls" + enterprise_success: "Enhorabona! %{enterprise} s'ha afegit a Open Food Network" + enterprise_registration_exit_message: "Si surts de l'assistent en qualsevol moment, podràs continuar creant el teu perfil anant a la interfície d'administració." + enterprise_description: "Descripció breu" + enterprise_description_placeholder: "Una frase breu que descrigui la teva organització" + enterprise_long_desc: "Descripció llarga" + enterprise_long_desc_placeholder: "Aquesta és la teva oportunitat per explicar la història de la vostra organització: què et fa diferent i meravellós? Et suggerim que la descripció sigui inferior a 600 caràcters o 150 paraules." + enterprise_long_desc_length: "%{num} caràcters / fins a 600 recomanats" + enterprise_abn: "NIF" + enterprise_abn_placeholder: "ex. 99 123 456 789" + enterprise_acn: "IVA" + enterprise_acn_placeholder: "p. ex. 123 456 789" + enterprise_tax_required: "Has de fer una selecció." + enterprise_final_step: "Pas final!" + enterprise_social_text: "Com poden la gent trobar %{enterprise}en línia?" + website: "Lloc web" + facebook: "Facebook" + facebook_placeholder: "p .ex: www.facebook.com/NomDeLaPàgina" + linkedin: "LinkedIn" + linkedin_placeholder: "p. ex: www.linkedin.com/YourNameHere" + twitter: "Twitter" + twitter_placeholder: "p. ex: @twitter_hortajosepribes" + instagram: "Instagram" + instagram_placeholder: "p. ex: @instagram_hortajosepribes" + registration_greeting: "Hola!" + registration_intro: "Ara pots crear un perfil per productora o grup de consum" + registration_action: "Comencem!" + registration_checklist: "Necessitaràs" + registration_time: "5-10 minuts" + registration_enterprise_address: "Adreça de l'organització" + registration_contact_details: "Dades de contacte principals" + registration_logo: "Imatge del logotip" + registration_promo_image: "Imatge promocional per al vostre perfil" + registration_about_us: "Text \"Sobre nosaltres\"" + registration_outcome_headline: "Què aconsegueixo?" + registration_outcome1_html: "El vostre perfil ajuda els usuaris a trobar i contactar a l'Open Food Network." + registration_outcome2: "Utilitza aquest espai per explicar la història de la teva organització, per ajudar a connectar-te amb la vostra presència social i a les xarxes." + registration_outcome3: "També és el primer pas cap al comenrç a través d'Open Food Network o per obrir una botiga en línia." + registration_finished_headline: "Acabat!" + registration_finished_thanks: "Gràcies per omplir els detalls de%{enterprise}." + registration_finished_login: "Pots canviar o actualitzar la teva organització en qualsevol moment accedint a Katuma i anant a Admin." + registration_contact_name: 'Nom de contacte' + registration_type_headline: "El darrer pas per afegir %{enterprise}!" + registration_type_question: "Ets productora?" + registration_type_producer: "Sí, sóc productora" + registration_type_no_producer: "No, no sóc productora" + registration_type_error: "Si us plau, escull una de les opcions. Ets productora?" + registration_type_producer_help: "Les productores fan coses delicioses per menjar i/o beure. Ets productora si cultives, cries, elabores, cous, fermentes..." + registration_type_no_producer_help: "Si no ets productora, probablement ets algú que ven i distribueix aliments. Pot ser un grup de consum, cooperativa, minorista, majorista o altres." + registration_detail_headline: "Comencem!" + registration_detail_enterprise: "Ep! Primer necessitem saber una mica sobre la vostra organització:" + registration_detail_producer: "Ep! Primer necessitem saber una mica sobre la teva granja:" + registration_detail_name_enterprise: "Nom de l'organització:" + registration_detail_name_producer: "Nom de la granja:" + registration_detail_name_placeholder: "p. ex: Horta Josep Ribes " + registration_detail_name_error: "Si us plau, selecciona un únic nom per a la teva organització" + registration_detail_address1: "Adreça línia 1: " + registration_detail_address1_placeholder: "p. ex: Carrer Ample, 123 " + registration_detail_address1_error: "Si us plau, introdueix una adreça" + registration_detail_address2: "Adreça (línia 2):" + registration_detail_suburb: "Barri: " + registration_detail_suburb_placeholder: "p. ex: Les Corts " + registration_detail_suburb_error: "Si us plau, introdueix un barri " + registration_detail_postcode: "Codi postal:" + registration_detail_postcode_placeholder: "p. ex: 08870" + registration_detail_postcode_error: "El codi postal és obligatori" + registration_detail_state: "Estat:" + registration_detail_state_error: "Estat obligatori" + registration_detail_country: "País:" + registration_detail_country_error: "Si us plau, selecciona un país" + shipping_method_destroy_error: "Aquest mètode d'enviament no es pot esborrar perquè s'hi fa referència en una comanda: %{number}." + accounts_and_billing_task_already_running_error: "Ja s'està executant una tasca, espera fins que hagi acabat" + accounts_and_billing_start_task_notice: "Tasca en cua" + fees: "Tarifes" + item_cost: "Cost de l'article" + shop_variant_quantity_min: "min" + shop_variant_quantity_max: "màx" + follow: "Segueix" + shop_for_products_html: "Compra productes de %{enterprise} a:" + shop_at: "Compra ara a:" + admin_fee: "Comissió d'administració" + sales_fee: "Comissió de venda" + packing_fee: "Comissió d'embalatge" + transport_fee: "Comissió de transport" + fundraising_fee: "Comissió d'autogestió" + price_graph: "Gràfic de preus" + included_tax: "Impostos incloses" + transaction: "Transacció" + transaction_date: "Data" + payment_state: "Estat del pagament" + shipping_state: "Estat d'enviament" + value: "Valor" + credit: "Crèdit" + Paid: "Pagat" + Ready: "Llest" + ok: D'acord + not_visible: no visible + you_have_no_orders_yet: "Encara no tens comandes" + admin_enterprise_relationships: "Permisos de l'organització" + admin_enterprise_relationships_everything: "Marcar tots" + admin_enterprise_relationships_permits: "Permet" + admin_enterprise_relationships_seach_placeholder: "Cerca" + admin_enterprise_relationships_button_create: "Crear" + admin_enterprise_groups: "Grups d'organització" + admin_enterprise_groups_name: "Nom" + admin_enterprise_groups_owner: "Propietària" + admin_enterprise_groups_enterprise: "Organitzacions" + admin_enterprise_groups_data_powertip: "La usuària principal responsable d'aquest grup." + admin_enterprise_groups_data_powertip_logo: "Això és el logotip del grup" + admin_enterprise_groups_data_powertip_promo_image: "Aquesta imatge es mostra a la part superior del perfil del grup" + admin_enterprise_groups_contact: "Contacte" + admin_enterprise_groups_contact_phone_placeholder: "p. ex.: 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "p. ex: Carrer Ample, 23" + admin_enterprise_groups_contact_city: "Barri" + admin_enterprise_groups_contact_city_placeholder: "p. ex: Sitges" + admin_enterprise_groups_contact_zipcode: "Codi postal" + admin_enterprise_groups_contact_zipcode_placeholder: "p. ex: 08870" + admin_enterprise_groups_contact_state_id: "Estat" + admin_enterprise_groups_contact_country_id: "País" + admin_enterprise_groups_web: "Recursos web" + admin_enterprise_groups_web_twitter: "p. ex: @horta_josepribes" + admin_enterprise_groups_web_website_placeholder: "p. ex.: www.hortajosepribes.coop" + admin_order_cycles: "Cicles de comanda de l'Admin" + open: "Obert" + close: "Tanca" + create: "Crear" + search: "Cerca" + supplier: "Proveïdora" + product_name: "Nom del producte" + product_description: "Descripció del producte" + coordinator: "Coordinador" + distributor: "Distribuïdora" + enterprise_fees: "Honoraris de l'organització" + process_my_order: "Processa la meva comanda" + delivery_instructions: Instruccions de lliurament + delivery_method: Mètode de lliurament + fee_type: "Tipus de tarifa" + tax_category: "Categoria d'impostos" + calculator: "Calculadora" + calculator_values: "Valors de la calculadora" + flat_percent_per_item: "Percentatge fixe (per article)" + flat_rate_per_item: "Tarifa fixa (per article)" + flat_rate_per_order: "Tarifa fixa (per comanda)" + flexible_rate: "Tarifa Flexible" + new_order_cycles: "Nou cicle de comanda" + new_order_cycle: "Nou cicle de comanda" + select_a_coordinator_for_your_order_cycle: "Selecciona una coordinadora per al vostre cicle de comanda" + notify_producers: 'Notifica les productores' + edit_order_cycle: "Edita el cicle de comanda" + roles: "Rols" + update: "Actualitzar" + delete: Suprimir + add_producer_property: "Afegeix propietats de la productora" + in_progress: "En progrés" + started_at: "Va començar a" + queued: "En cua" + scheduled_for: "Programat per a" + customers: "Consumidores" + please_select_hub: "Si us plau selecciona un grup" + loading_customers: "Carregant consumidores" + no_customers_found: "No s'han trobat consumidores" + go: "Anar" + hub: "Grup" + producer: "Productor" + product: "Producte" + price: "Preu" + on_hand: "Disponible" + save_changes: "Desa els canvis" + order_saved: "Comanda desada" + no_products: Sense productes + spree_admin_overview_enterprises_header: "Les meves organitzacions" + spree_admin_overview_enterprises_footer: "GESTIONAR LES MEVES ORGANITZACIONS" + spree_admin_enterprises_hubs_name: "Nom" + spree_admin_enterprises_create_new: "CREA'N UNA DE NOVA" + spree_admin_enterprises_shipping_methods: "Mètodes d'enviament" + spree_admin_enterprises_fees: "Honoraris de l'organització" + spree_admin_enterprises_none_create_a_new_enterprise: "CREA UNA NOVA ORGANITZACIÓ" + spree_admin_enterprises_none_text: "Encara no tens cap organització" + spree_admin_enterprises_tabs_hubs: "GRUPS" + spree_admin_enterprises_producers_manage_products: "GESTIONA ELS PRODUCTES" + spree_admin_enterprises_any_active_products_text: "No tens cap producte actiu." + spree_admin_enterprises_create_new_product: "CREA UN NOU PRODUCTE" + spree_admin_single_enterprise_alert_mail_confirmation: "Si us plau confirma l'adreça de correu electrònic de" + spree_admin_single_enterprise_alert_mail_sent: "Hem enviat un correu electrònic a" + spree_admin_overview_action_required: "Acció requerida" + spree_admin_overview_check_your_inbox: "Si us plat comproveu la vostra safata d'entrada per obtenir més instruccions. Gràcies!" + spree_admin_unit_value: Valor de la unitat + spree_admin_unit_description: Descripció de la unitat + spree_admin_supplier: Proveïdora + spree_admin_product_category: Categoria del producte + change_package: "Canvia el perfil" + spree_admin_single_enterprise_hint: "Suggeriment: per permetre que la gent us trobi, activeu la vostra visibilitat" + spree_admin_eg_pickup_from_school: "p. ex: 'Recollida al local del grup de consum'" + spree_admin_eg_collect_your_order: "p. ex: \"Recolliu la vostra comanda al c/Ample, n. 123'" + spree_order_availability_error: "La distribuïdora o el cicle de comanda no pot subministrar els productes de la vostra cistella" + spree_order_populator_error: "Aquesta distribuïdora o cicle de comanda no pot subministrar tots els productes de la vostra cistella. Si us plau trieu-ne d'altres." + spree_order_populator_availability_error: "Aquest producte no està disponible des de la distribuïdora o cicle de comanda seleccionat." + spree_distributors_error: "Cal seleccionar almenys un grup" + spree_user_enterprise_limit_error: "^ %{email} no està autoritzat a tenir més organitzacions (el límit és %{enterprise_limit})." + spree_variant_product_error: ha de tenir com a mínim una variant + on_ofn_map: "al mapa d'Open Food Network" + manage: "Gestiona" + resend: "Reenviar" + add_and_manage_products: "Afegeix & gestiona productes" + add_and_manage_order_cycles: "Afegeix & gestiona cicles de comanda" + manage_order_cycles: "Gestiona els cicles de comanda" + manage_products: "Gestiona els productes" + edit_profile_details: "Edita els detalls del perfil" + edit_profile_details_etc: "Canvia la descripció del perfil, les imatges, etc." + order_cycle: "Cicle de Comanda" + order_cycles: "Cicles de comanda" + remove_tax: "Suprimeix comissions" + enterprise_tos_message: "Volem treballar amb persones que comparteixen els nostres objectius i valors. Com a tal, demanem a les noves organitzacions que acceptin la nostra" + enterprise_tos_link_text: "Termes del servei." + enterprise_tos_agree: "Accepto els Termes i condicions anteriors" + admin_shared_address_1: "Adreça" + admin_shared_address_2: "Adreça (cont.)" + admin_share_city: "Municipi" + admin_share_zipcode: "Codi postal" + admin_share_country: "País" + admin_share_state: "Estat" + hub_sidebar_hubs: "Grups" + hub_sidebar_none_available: "Cap disponible" + hub_sidebar_manage: "Gestiona" + hub_sidebar_at_least: "Cal seleccionar almenys un grup " + hub_sidebar_blue: "blau" + hub_sidebar_red: "vermell" + shop_trial_in_progress: "El període de prova de la vostra botiga caduca en %{days}." + report_customers_distributor: "Distribuïdora" + report_customers_supplier: "Proveïdora" + report_customers_cycle: "Cicle de Comanda" + report_customers_type: "Tipus d'informe" + report_customers_csv: "Descarrega com a csv" + report_producers: "Productores:" + report_type: "Tipus d'informe:" + report_hubs: "Grups:" + report_payment: "Mètodes de pagament:" + report_distributor: "Distribuïdora:" + report_payment_by: 'Pagaments per tipus' + report_itemised_payment: 'Totals de pagament especificats' + report_payment_totals: 'Totals de pagament' + report_all: 'tot' + report_order_cycle: "Cicle de comanda:" + report_enterprises: "Organitzacions:" + report_users: "Usuàries:" + report_tax_rates: Taxes d'impostos + report_tax_types: Tipus d'impostos + report_header_order_cycle: Cicle de comanda + report_header_user: Usuàries + report_header_email: Correu electrònic + report_header_status: Estat + report_header_comments: Comentaris + report_header_first_name: Nom + report_header_last_name: Cognoms + report_header_phone: Telèfon + report_header_suburb: Barri + report_header_address: Adreça + report_header_billing_address: Adreça de facturació + report_header_relationship: Relació + report_header_hub: Grup + report_header_hub_address: Adreça del grup + report_header_to_hub: Per al grup + report_header_hub_code: Codi del grup + report_header_code: Codi + report_header_paid: Pagat? + report_header_delivery: Lliurament? + report_header_shipping: Enviament + report_header_shipping_method: Mètode d'enviament + report_header_shipping_instructions: Instruccions d'enviament + report_header_ship_street: Carrer d'enviament + report_header_ship_street_2: Carrer d'enviament 2 + report_header_ship_city: Ciutat d'enviament + report_header_ship_postcode: Codi postal d'enviament + report_header_ship_state: Estat d'enviament + report_header_billing_street: Carrer de facturació + report_header_billing_street_2: Carrer de facturació 2 + report_header_billing_street_3: Carrer de facturació 3 + report_header_billing_street_4: Carrer de facturació 4 + report_header_billing_city: Ciutat de facturació + report_header_billing_postcode: Codi postal de facturació + report_header_billing_state: Estat de facturació + report_header_incoming_transport: Transport entrant + report_header_special_instructions: Instruccions especials + report_header_order_number: Número de comanda + report_header_date: Data + report_header_tags: Etiquetes + report_header_items: Articles + report_header_items_total: "Total d'articles %{currency_symbol}" + report_header_delivery_charge: "Càrrec de lliurament (%{currency_symbol})" + report_header_tax_on_delivery: "Impost sobre el lliurament (%{currency_symbol})" + report_header_enterprise: Organització + report_header_customer: Consumidora + report_header_customer_code: Codi de la consumidora + report_header_product: Producte + report_header_product_properties: Propietats del producte + report_header_quantity: Quantitat + report_header_max_quantity: Quantitat màxima + report_header_variant: Variant + report_header_variant_value: Valor de la variant + report_header_variant_unit: Unitat de la variant + report_header_total_available: Total disponible + report_header_unallocated: Sense assignar + report_header_supplier: Proveïdora + report_header_producer: Productora + report_header_unit: Unitat + report_header_cost: Cost + report_header_shipping_cost: Despeses d'enviament + report_header_total_shipping_cost: Cost total d'enviament + report_header_payment_method: Mètode de pagament + report_header_sells: Ven + report_header_visible: Visible + report_header_price: Preu + report_header_distributor: Distribuïdora + report_header_distributor_address: Adreça de la distribuïdora + report_header_distributor_city: Ciutat de la distribuïdora + report_header_distributor_postcode: Codi postal de la distribuïdora + report_header_delivery_address: Adreça de lliurament + report_header_delivery_postcode: Codi postal de lliurament + report_header_weight: Pes + report_header_amount_owing: Import adeutat + report_header_amount_paid: Import pagat + report_header_units_required: Unitats necessàries + report_header_remainder: Restant + report_header_order_date: Data de comanda + report_header_order_id: Identificació de comanda + report_header_item_name: Nom de l'article + report_header_temp_controlled_items: Articles amb control de temperatura? + report_header_customer_name: Nom de la consumidora + report_header_customer_email: Correu electrònic de la consumidora + report_header_customer_phone: Telèfon de la consumidora + report_header_customer_city: Ciutat de la consumidora + report_header_payment_state: Estat del pagament + report_header_payment_type: Tipus de pagament + report_header_item_price: "Article (%{currency})" + report_header_total_price: "Total (%{currency})" + report_header_product_total_price: "Total del producte (%{currency})" + report_header_shipping_total_price: "Enviament total (%{currency})" + report_header_paypal_price: "PayPal (%{currency})" + report_header_sku: Número de referència (SKU) + report_header_amount: Import + report_header_total_excl_vat: "Total excl. impostos (%{currency_symbol})" + report_header_total_incl_vat: "Total incl. impostos (%{currency_symbol})" + report_header_temp_controlled: Control de temperatura? + report_header_is_producer: Productor? + initial_invoice_number: "Número de la comanda inicial:" + invoice_date: "Data del comprovant de compra:" + contains: "conté" + discount: "Descompte" + delete_product_variant: "L'última variant no es pot esborrar!" + progress: "progressió" + saving: "Desant..." + success: "èxit" + failure: "error" + unsaved_changes_confirmation: "Es perdran els canvis sense desar. Vols continuar de totes maneres?" + one_product_unsaved: "Els canvis d'un producte romanen sense desar." + products_unsaved: "Els canvis a %{n} productes romanen sense desar." + is_already_manager: "ja és gestor!" + no_change_to_save: " No hi ha cap canvi per desar" + user_invited: "%{email} ha estat convidada a gestionar aquesta organització" + add_manager: "Afegeix una usuària existent" + users: "Usuàries" + about: "Sobre" + images: "Imatges" + web: "Web" + primary_details: "Detalls principals" + adrdress: "Adreça" + contact: "Contacte" + social: "Social" + business_details: "Dades comercials" + properties: "Propietats" + shipping: "Enviament" + shipping_methods: "Mètodes d'enviament" + payment_methods: "Mètodes de Pagament" + payment_method_fee: "Tarifa de transacció" + inventory_settings: "Configuració de l'inventari" + tag_rules: "Regles d'etiqueta" + shop_preferences: "Preferències de la botiga" + enterprise_fee_whole_order: Comanda sencera + validation_msg_relationship_already_established: "^ Aquesta relació ja està establerta." + validation_msg_at_least_one_hub: "^ Cal seleccionar com a mínim un grup" + validation_msg_product_category_cant_be_blank: "^ La categoria de producte no pot estar en blanc" + validation_msg_is_associated_with_an_exising_customer: "Està associada amb una consumidora existent" + enterprise_name_error: "ja ha estat agafat. Si aquesta és la vostra organització i voleu reclamar-ne la propietat o si voleu comerciar amb aquesta organització, poseu-vos en contacte amb l'administradora actual d'aquest perfil %{email}." + enterprise_owner_error: "^ %{email} no està autoritzat a tenir més organitzacions (el límit és %{enterprise_limit})." + inventory_item_visibility_error: ha de ser veritable o falsa + product_importer_file_error: "error: no s'ha carregat cap fitxer" + product_importer_spreadsheet_error: "no s'ha pogut processar el fitxer: tipus de fitxer no vàlid" + product_importer_products_save_error: no s'han desat cap producte amb èxit + product_import_file_not_found_notice: 'No s''ha trobat el fitxer o no s''ha pogut obrir' + product_import_no_data_in_spreadsheet_notice: 'No s''ha trobat cap dada al full de càlcul' + order_choosing_hub_notice: El teu grup ha estat seleccionat. + order_cycle_selecting_notice: S'ha seleccionat el teu cicle de comanda. + enterprise_fees_update_notice: S'han actualitzat les comissions de l'organització. + enterprise_register_package_error: "Si us plau, selecciona un perfil" + enterprise_register_error: "No es pot completar el registre de %{enterprise}" + enterprise_register_success_notice: "Enhorabona! El registre de %{enterprise} s'ha completat!" + enterprise_bulk_update_success_notice: "Les organitzacions s'han actualitzat correctament" + enterprise_bulk_update_error: 'No s''ha pogut actualitzar' + order_cycles_create_notice: 'S''ha creat el cicle de comanda.' + order_cycles_update_notice: 'S''ha actualitzat el cicle de comanda.' + order_cycles_bulk_update_notice: 'S''han actualitzat els cicles de comanda.' + order_cycles_clone_notice: "S'ha clonat el teu cicle de comanda %{name}." + order_cycles_email_to_producers_notice: 'Els correus electrònics per enviar a les productores estan en cua.' + order_cycles_no_permission_to_coordinate_error: "Cap de les vostres organitzacions té permís per coordinar un cicle de comanda" + order_cycles_no_permission_to_create_error: "No teniu permís per crear un cicle de comandes coordinat per aquesta organització" + back_to_orders_list: "Torna a la llista de comandes" + no_orders_found: "No s'han trobat comandes" + order_information: "Informació de la comanda" + date_completed: "Data finalitzada" + amount: "Import" + state_names: + ready: Llest + pending: Pendents + shipped: Enviat + js: + saving: 'Desant...' + changes_saved: 'S''han desat els canvis.' + save_changes_first: Desa els canvis en primer lloc. + all_changes_saved: S'han desat tots els canvis + unsaved_changes: Teniu canvis sense desar + all_changes_saved_successfully: Tots els canvis s'han desat correctament + oh_no: "Ah no! No he pogut desar els canvis." + unauthorized: "No tens autorització per accedir a aquesta pàgina." + error: Error + unavailable: No disponible + profile: Perfil + hub: Grup + shop: Botiga + choose: Escull + resolve_errors: Si us plau, resol els errors següents + more_items: "+ %{count} Més" + admin: + enterprise_limit_reached: "Has assolit el límit estàndard d'organitzacions per compte. Escriu a %{contact_email} si necessites augmentar-lo." + modals: + got_it: Ho tinc + close: "Tanca" + invite: "Convida" + invite_title: "Convida un usuari no registrat" + tag_rule_help: + title: Regles d'etiqueta + overview: Visió general + overview_text: > + Les regles d'etiquetes proporcionen una manera de descriure quins elements + són visibles o pel contrari s'amaguen a segons quins clients. Els elements + poden ser mètodes d'enviament, mètodes de pagament, productes i cicles + de comanda. + by_default_rules: "Regles 'per defecte...'" + by_default_rules_text: > + Les regles predeterminades o per defecte permeten ocultar elements perquè + no siguin visibles de manera predeterminada. Aquest comportament es + pot reemplaçar per regles no predeterminades per a clients amb etiquetes + particulars. + customer_tagged_rules: "Regles de 'clients etiquetats...'" + customer_tagged_rules_text: > + En crear regles relacionades amb una etiqueta de client específica, + podeu anul·lar el comportament predeterminat (ja sigui per mostrar o + ocultar elements) per als clients amb l'etiqueta especificada. + panels: + save: DESA + saved: DESAT + saving: DESANT + enterprise_package: + hub_profile: Perfil de grup + hub_profile_cost: "COST: SEMPRE GRATUÏT" + hub_profile_text1: > + La gent podrà trobar-vos i posar-se en contacte amb vosaltres a Open + Food Network. La vostra organització serà visible al mapa i es podrà + cercar a les llistes. + hub_profile_text2: > + Tenir un perfil i fer contactes amb vostre moviment alimentari local + a través d'Open Food Network sempre serà gratuït. + hub_shop: Botiga + hub_shop_text1: > + La teva organització és la columna vertebral del vostre sistema alimentari + local. Afegeix productes d'altres organitzacions productores i ven-los + a través de la vostra botiga a la Katuma. + hub_shop_text2: > + Els grups poden prendre moltes formes, ja siguin una cooperativa d'aliments, + un grup de compra, o una botiga de queviures local, un supermercat cooperatiu. + hub_shop_text3: > + Si també vols vendre els teus propis productes, hauràs de canviar d'organització + per ser productora. + choose_package: Si us plau tria un perfil + choose_package_text1: > + La teva organització no s'activarà completament fins que es seleccioni + un perfil d'entre les opcions de l'esquerra. + choose_package_text2: > + Fes clic a una opció per veure informació més detallada sobre cada perfil + i prem el botó vermell "DESA" quan hagis acabat. + profile_only: Només perfil + profile_only_cost: "COST: SEMPRE GRATUÏT" + profile_only_text1: > + Un perfil et fa visible i contactablede cara als altres i és una forma + de compartir la teva història. + profile_only_text2: > + Si prefereixes concentrar-te en produir menjar i vols deixar la feina + de vendre'l a una altra persona, no necessitaràs una botiga Katuma. + profile_only_text3: > + Afegeix els teus productes a Katuma, permetent que els grups de consum + o altres puguin emmagatzemar-los a les seves botigues. + producer_shop: Productora amb botiga + producer_shop_text1: > + Ven els teus productes directament a les consumidores a través de la + teva pròpia botiga a Katuma. + producer_shop_text2: > + Una botiga de productores només és per als teus productes, si vols vendre + productes produïts / cultivats per un altre, selecciona "Grup de productores" + producer_hub: Grup de productores + producer_hub_text1: > + La teva organització és la columna vertebral del vostre sistema alimentari + local. Pots vendre els vostres propis productes i productes agregats + d'altres organitzacions a través de la vostra botiga a Katuma. + producer_hub_text2: > + Un grup pot prendre moltes formes, ja siguin una cooperativa de consum, + un programa de cistelles o un hort comunitari que produeix per als seus + socis. + producer_hub_text3: > + L'Open Food Network té com a objectiu donar suport a tots els models + de grups possibles, de manera que, independentment de la teva situació, + volem proporcionar les eines que necessites per executar la teva organització + o empresa alimentària local. + get_listing: Obtenir una llista + always_free: SEMPRE GRATUÏT + sell_produce_others: Ven productes d'altres + sell_own_produce: Ven els teus propis productes + sell_both: Ven productes d'un mateix i d'altres + enterprise_producer: + producer: Productor + producer_text1: > + Les productores fan coses delicioses per menjar o beure. Ets productora + si cultives, cries, fermentes, cous pa, vens llet, fas formatges... + producer_text2: > + Les productores també poden realitzar altres funcions, com ara agregar + productes d'altres productores i vendre-la a través d'una botiga a Katuma. + non_producer: No productora + non_producer_text1: > + Les no-productores no produeixen cap aliment, per la qual cosa no poden + crear productes propis per vendre a través de Katuma. + non_producer_text2: > + En canvi, les no-productores s'especialitzen en vincular les productores + amb el consumidor final, ja sigui classificant, envasant, venent o lliurant + aliments. + producer_desc: Productores d'aliments + producer_example: 'p. ex: AGRICULTORES, FORNERES, CERVESERES, TRANSFORMADORES D''ALIMENTS...' + non_producer_desc: Totes les altres organitzacions alimentàries + non_producer_example: 'p. ex: botigues de queviures, cooperatives d''aliments, grups de compra...' + enterprise_status: + description: Descripció + resolve: Resoldre + new_tag_rule_dialog: + select_rule_type: "Selecciona un tipus de regla:" + orders: + index: + per_page: "%{results} per pàgina" + resend_user_email_confirmation: + resend: "Reenviar" + sending: "Reenviar..." + done: "Reenviament fet ✓" + failed: "Reenviament fallit ✗" + out_of_stock: + reduced_stock_available: Estoc reduït disponible + out_of_stock_text: > + Mentre heu estat comprant, s'han reduït els nivells d'existències d'un o + més dels productes de la cistella. Aquí podeu veure el que ha canviat: + now_out_of_stock: ara està fora d'estoc. + only_n_remainging: "ara només n'hi ha %{num}restants." + variants: + on_demand: + 'yes': "Sota demanda" + variant_overrides: + on_demand: + use_producer_settings: "Utilitzeu la configuració d'inventari de la productora" + 'yes': "Sí" + 'no': "No" + inventory_products: "Productes de l'inventari" + hidden_products: "Productes ocults" + new_products: "Nous productes" + reset_stock_levels: Restablir els nivells d'existències a valors predeterminats + remain_unsaved: romanen sense desar. + no_changes_to_save: No hi ha canvis per desar. + no_authorisation: "No he pogut obtenir l'autorització per guardar aquests canvis, de manera que romanen sense desar." + some_trouble: "He tingut problemes per desar: %{errors}" + changing_on_hand_stock: Canviant els nivells de disponibilitat d'existències... + stock_reset: Existències restablertes als valors predeterminats. + tag_rules: + show_hide_variants: 'Mostra o amaga variants a la meva botiga' + show_hide_shipping: 'Mostra o amaga mètodes d''enviament en la validació de la comanda' + show_hide_payment: 'Mostra o amaga mètodes de pagament en la validació de la comanda' + show_hide_order_cycles: 'Mostra o amaga els cicles de comanda a la meva botiga' + visible: VISIBLE + not_visible: NO VISIBLE + services: + unsaved_changes_message: Actualment hi ha canvis sense desar, vols desar-los o ignorar-los? + save: DESA + ignore: IGNORA + add_to_order_cycle: "afegeix al cicle de comanda" + manage_products: "gestiona els productes" + edit_profile: "edita el perfil" + add_products_to_inventory: "afegeix productes a l'inventari" + resources: + could_not_delete_customer: 'No s''ha pogut eliminar la consumidora' + order_cycles: + create_failure: "No s'ha pogut crear el cicle de comanda" + update_success: 'S''ha actualitzat el cicle de comanda.' + update_failure: "No s'ha pogut actualitzar el cicle de comanda" + no_distributors: 'No hi ha distribuïdores en aquest cicle de comanda. Aquest cicle de comanda no serà visible per a les consumidores fins que no n''afegiu un. Voleu continuar desant aquest cicle de comanda? ' + enterprises: + producer: "Productora" + non_producer: "No-productora" + customers: + select_shop: 'Si us plau seleccioneu primer una botiga' + could_not_create: Ho sentim! No s'ha pogut crear + subscriptions: + closes: tanca + closed: tancat + close_date_not_set: La data de tancament no està establerta + producers: + signup: + start_free_profile: "Comença amb un perfil gratuït i amplia'l quan estiguis preparada." + spree: + email: Correu electrònic + account_updated: "Compte actualitzat!" + my_account: "El meu compte" + date: "Data" + time: "Hora" + layouts: + admin: + header: + store: Botiga + admin: + orders: + index: + new_order: "Nova comanda" + capture: "Captura" + ship: "Enviament" + edit: "Editar" + note: "Nota" + first: "Primer" + last: "Últim" + next: "Següent" + loading: "S'està carregant" + no_orders_found: "No s'ha trobat cap comanda" + results_found: "%{number} Resultats trobats." + viewing: "Veient %{start} a %{end}." + invoice: + issued_on: Publicat a + tax_invoice: FACTURA D'IMPOSTOS + code: Codi + from: De + to: Per a + form: + distribution_fields: + title: Distribució + distributor: "Distribuïdora:" + order_cycle: "Cicle de comanda:" + overview: + order_cycles: + order_cycles: "Cicles de comanda" + order_cycles_tip: "Els cicles de comanda determinen quan i on els teus productes estan disponibles per a les consumidores." + you_have_active: + zero: "No tens cicles de comanda actius." + one: "Tens un cicle de comanda actiu." + other: "Tens %{count}cicles de comanda actius" + manage_order_cycles: "GESTIONA ELS CICLES DE COMANDA" + payment_methods: + new: + new_payment_method: "Nou mètode de pagament" + back_to_payment_methods_list: "Tornar a la llista de mètodes de pagament" + edit: + editing_payment_method: "Edició del mètode de pagament" + back_to_payment_methods_list: "Tornar a la llista de mètodes de pagament" + stripe_connect: + enterprise_select_placeholder: Tria ... + loading_account_information_msg: S'està carregant la informació del compte de Stripe, si us plau espera... + stripe_disabled_msg: Els pagaments de Stripe han estat inhabilitat per l'administrador del sistema. + request_failed_msg: Ho sentim. S'ha produït un error en provar de verificar els detalls del compte amb Stripe... + account_missing_msg: No hi ha cap compte de Stripe per a aquesta organització. + access_revoked_msg: S'ha revocat l'accés a aquest compte de Stripe, si us plau torna a connectar el teu compte. + status: Estat + connected: Connectat + account_id: Identificador del compte + business_name: Nom de l'empresa + charges_enabled: Càrrecs habilitats + payments: + source_forms: + stripe: + error_saving_payment: Error en desar el pagament + submitting_payment: S'està lliurant el pagament... + products: + new: + title: 'Nou producte' + index: + header: + title: Edició de productes en bloc + indicators: + title: CARREGANT PRODUCTES + no_products: "Encara no hi ha productes. Per què no afegeixes alguna cosa?" + no_results: "Ho sentim, no hi ha coincidència de resultats" + products_head: + name: Nom + unit: Unitat + display_as: Mostra com + category: Categoria + tax_category: Categoria d'impostos + inherits_properties?: Hereda propietats? + available_on: Disponible el + products_variant: + new_variant: "Nova variant" + product_name: Nom del producte + primary_taxon_form: + product_category: Categoria del producte + group_buy_form: + group_buy: "Compra en grup?" + display_as: + display_as: Mostra com + users: + index: + listing_users: "Llistat d'usuàries" + new_user: "Nova usuària" + user: "Usuàries" + enterprise_limit: "Límit d'organitzacions" + search: "Cerca" + email: "E-mail" + edit: + editing_user: "S'està editant una usuària" + back_to_users_list: "Torna a la llista d'usuàries" + general_settings: "Configuració general" + form: + email: "E-mail" + roles: "Rols" + enterprise_limit: "Límit d'organitzacions" + confirm_password: "Confirma la contrassenya" + password: "Contrasenya" + email_confirmation: + confirmation_pending: "La confirmació de correu electrònic està pendent. Hem enviat un correu electrònic de confirmació a %{address}." + variants: + autocomplete: + producer_name: Productor + general_settings: + edit: + cookies_consent_banner_toggle: "Mostra banner de consentiment de cookies" + privacy_policy_url: "URL de la política de privacitat " + cookies_policy_matomo_section: "Mostra la secció de Matomo a la pàgina de política de cookies" + cookies_policy_ga_section: "Mostra la secció de Google Analytics a la pàgina de política de cookies" + checkout: + payment: + stripe: + choose_one: Escull-ne un + enter_new_card: Introdueix els detalls d'una targeta nova + used_saved_card: "Utilitza una targeta desada:" + or_enter_new_card: "O bé introdueix els detalls d'una nova targeta:" + remember_this_card: Recordar aquesta targeta? + inventory: Inventari + orders: + edit: + login_to_view_order: "Si us plau inicia sessió per veure la teva comanda." + bought: + item: "Ja està demanat en aquest cicle de comandes" + order_mailer: + invoice_email: + hi: "Hola %{name}" + invoice_attached_text: Trobareu adjunta un comprovant de la compra per a la vostra comanda recent + order_state: + address: adreça + adjustments: ajustaments + awaiting_return: esperant el retorn + canceled: cancel·lat + cart: cistella + complete: completa + confirm: confirma + delivery: lliurament + paused: en pausa + payment: pagament + pending: pendents + resumed: reprès + returned: retornat + skrill: habilitat + subscription_state: + active: actiu + pending: pendents + ended: acabat + paused: en pausa + canceled: cancel·lat + payment_states: + completed: completat + credit_owed: crèdit a deure + failed: error + paid: pagat + pending: pendents + processing: processant + void: buit + invalid: invàlid + shipment_states: + backorder: per tornar + partial: parcial + pending: pendents + ready: llest + shipped: enviat + user_mailer: + reset_password_instructions: + request_sent_text: | + S'ha fet una sol·licitud per restablir la teva contrasenya. + Si no has fet aquesta sol·licitud, simplement ignora aquest correu electrònic. + link_text: > + Si has fet aquesta sol·licitud, fes clic a l'enllaç següent: + issue_text: | + Si l'URL anterior no funciona, prova de copiar-lo i enganxar-lo al navegador. + Si continues tenint problemes, no dubtis en contactar-nos. + confirmation_instructions: + subject: Si us plau confirma el teu compte d'OFN + weight: Pes (per kg) + zipcode: Codi postal + users: + form: + account_settings: Configuració del compte + show: + tabs: + orders: Comandes + cards: Targetes de crèdit + transactions: Transaccions + settings: Configuració del compte + unconfirmed_email: "Confirmació pendent de correu electrònic per a: %{unconfirmed_email}. La vostra adreça de correu electrònic s'actualitzarà un cop confirmat el nou correu electrònic." + orders: + open_orders: Comandes obertes + past_orders: Comandes anteriors + transactions: + transaction_history: Historial de transaccions + open_orders: + order: Comanda + shop: Botiga + changes_allowed_until: Canvis permesos fins a + items: Articles + total: Total + edit: Editar + cancel: Cancel·lar + closed: Tancat + until: Fins a + past_orders: + order: Comanda + shop: Botiga + completed_at: Completat a + items: Articles + total: Total + paid?: Pagat? + view: Veure + saved_cards: + default?: Per defecte? + delete?: Suprimeix? + cards: + authorised_shops: Botigues autoritzades + authorised_shops_popover: Aquesta és la llista de botigues que tenen permís per carregar la teva targeta de crèdit predeterminada per a qualsevol subscripció (és a dir, comandes de repetició) que puguis tenir. Les dades de la targeta es mantindran segures i no es compartiran amb els propietaris de botigues. Sempre se us notificarà quan se us faci un càrrec. + saved_cards_popover: Aquesta és la llista de targetes que heu optat per guardar per a un ús posterior. El vostre "valor predeterminat" es seleccionarà automàticament quan valideu una comanda i es pot carregar per qualsevol botiga a la que li hagueu permès fer-ho (vegeu a la dreta). + authorised_shops: + shop_name: "Nom de la botiga" + allow_charges?: "Permetre càrrecs?" + localized_number: + invalid_format: té un format no vàlid. Si us plau introdueix un número. From de9cff6fc2d8c48a4cf13342f22f0811cdc55a0d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 20 Sep 2018 14:12:34 +0800 Subject: [PATCH 037/108] Add validator for datetime string Example usage: validates :start_at, date_time_string: true --- app/validators/date_time_string_validator.rb | 63 +++++++++++++++++++ config/locales/en.yml | 5 ++ .../date_time_string_validator_spec.rb | 58 +++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 app/validators/date_time_string_validator.rb create mode 100644 spec/validators/date_time_string_validator_spec.rb diff --git a/app/validators/date_time_string_validator.rb b/app/validators/date_time_string_validator.rb new file mode 100644 index 0000000000..f1a4eccbfb --- /dev/null +++ b/app/validators/date_time_string_validator.rb @@ -0,0 +1,63 @@ +# Validates a datetime string with relaxed rules +# +# This uses ActiveSupport::TimeZone.parse behind the scenes. +# +# https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-parse +# +# === Example +# +# class Post +# include ActiveModel::Validations +# +# attr_accessor :published_at +# validates :published_at, date_time_string: true +# end +# +# post = Post.new +# +# post.published_at = nil +# post.valid? # => true +# +# post.published_at = "" +# post.valid? # => true +# +# post.published_at = [] +# post.valid? # => false +# post.errors[:published_at] # => ["must be a string"] +# +# post.published_at = 1 +# post.valid? # => false +# post.errors[:published_at] # => ["must be a string"] +# +# post.published_at = "2018-09-20 01:02:00 +10:00" +# post.valid? # => true +# +# post.published_at = "Not Valid" +# post.valid? # => false +# post.errors[:published_at] # => ["must be valid"] +class DateTimeStringValidator < ActiveModel::EachValidator + NOT_STRING_ERROR = I18n.t("validators.date_time_string_validator.not_string_error") + INVALID_FORMAT_ERROR = I18n.t("validators.date_time_string_validator.invalid_format_error") + + def validate_each(record, attribute, value) + return if value.nil? || value == "" + + validate_attribute_is_string(record, attribute, value) + validate_attribute_is_datetime_string(record, attribute, value) + end + + protected + + def validate_attribute_is_string(record, attribute, value) + return if value.is_a?(String) + + record.errors.add(attribute, NOT_STRING_ERROR) + end + + def validate_attribute_is_datetime_string(record, attribute, value) + return unless value.is_a?(String) + + datetime = Time.zone.parse(value) + record.errors.add(attribute, INVALID_FORMAT_ERROR) if datetime.blank? + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index f0c72f289a..cea3d34d6b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -100,6 +100,11 @@ en: order_cycle: cloned_order_cycle_name: "COPY OF %{order_cycle}" + validators: + date_time_string_validator: + not_string_error: "must be a string" + invalid_format_error: "must be valid" + enterprise_mailer: confirmation_instructions: subject: "Please confirm the email address for %{enterprise}" diff --git a/spec/validators/date_time_string_validator_spec.rb b/spec/validators/date_time_string_validator_spec.rb new file mode 100644 index 0000000000..d5151fc7c5 --- /dev/null +++ b/spec/validators/date_time_string_validator_spec.rb @@ -0,0 +1,58 @@ +require "spec_helper" + +describe DateTimeStringValidator do + class TestModel + include ActiveModel::Validations + + attr_accessor :timestamp + + validates :timestamp, date_time_string: true + end + + describe "internationalization" do + it "has translation for NOT_STRING_ERROR" do + expect(described_class::NOT_STRING_ERROR).not_to be_blank + end + + it "has translation for INVALID_FORMAT_ERROR" do + expect(described_class::INVALID_FORMAT_ERROR).not_to be_blank + end + end + + describe "validation" do + let(:instance) { TestModel.new } + + it "does not add error when nil" do + instance.timestamp = nil + expect(instance).to be_valid + end + + it "does not add error when blank string" do + instance.timestamp = nil + expect(instance).to be_valid + end + + it "adds error NOT_STRING_ERROR when blank but neither nil nor a string" do + instance.timestamp = [] + expect(instance).not_to be_valid + expect(instance.errors[:timestamp]).to eq([described_class::NOT_STRING_ERROR]) + end + + it "adds error NOT_STRING_ERROR when not a string" do + instance.timestamp = 1 + expect(instance).not_to be_valid + expect(instance.errors[:timestamp]).to eq([described_class::NOT_STRING_ERROR]) + end + + it "does not add error when value can be parsed" do + instance.timestamp = "2018-09-20 01:02:00 +10:00" + expect(instance).to be_valid + end + + it "adds error INVALID_FORMAT_ERROR when value cannot be parsed" do + instance.timestamp = "Not Valid" + expect(instance).not_to be_valid + expect(instance.errors[:timestamp]).to eq([described_class::INVALID_FORMAT_ERROR]) + end + end +end From f6e8f18d89c7ad1aac3429941b5f71ab25b5e6c3 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 21 Sep 2018 12:16:06 +0800 Subject: [PATCH 038/108] Add validator for integer arrays Example usage: validates :related_post_ids, integer_array: true --- app/validators/integer_array_validator.rb | 63 +++++++++++++++++++ config/locales/en.yml | 3 + .../integer_array_validator_spec.rb | 57 +++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 app/validators/integer_array_validator.rb create mode 100644 spec/validators/integer_array_validator_spec.rb diff --git a/app/validators/integer_array_validator.rb b/app/validators/integer_array_validator.rb new file mode 100644 index 0000000000..27042c972b --- /dev/null +++ b/app/validators/integer_array_validator.rb @@ -0,0 +1,63 @@ +# Validates an integer array +# +# This uses Integer() behind the scenes. +# +# === Example +# +# class Post +# include ActiveModel::Validations +# +# attr_accessor :related_post_ids +# validates :related_post_ids, integer_array: true +# end +# +# post = Post.new +# +# post.related_post_ids = nil +# post.valid? # => true +# +# post.related_post_ids = [] +# post.valid? # => true +# +# post.related_post_ids = 1 +# post.valid? # => false +# post.errors[:related_post_ids] # => ["must be an array"] +# +# post.related_post_ids = [1, 2, 3] +# post.valid? # => true +# +# post.related_post_ids = ["1", "2", "3"] +# post.valid? # => true +# +# post.related_post_ids = [1, "2", "Not Integer", 3] +# post.valid? # => false +# post.errors[:related_post_ids] # => ["must contain only valid integers"] +class IntegerArrayValidator < ActiveModel::EachValidator + NOT_ARRAY_ERROR = I18n.t("validators.integer_array_validator.not_array_error") + INVALID_ELEMENT_ERROR = I18n.t("validators.integer_array_validator.invalid_element_error") + + def validate_each(record, attribute, value) + return if value.nil? + + validate_attribute_is_array(record, attribute, value) + validate_attribute_elements_are_integer(record, attribute, value) + end + + protected + + def validate_attribute_is_array(record, attribute, value) + return if value.is_a?(Array) + + record.errors.add(attribute, NOT_ARRAY_ERROR) + end + + def validate_attribute_elements_are_integer(record, attribute, array) + return unless array.is_a?(Array) + + array.each do |element| + Integer(element) + end + rescue ArgumentError + record.errors.add(attribute, INVALID_ELEMENT_ERROR) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index cea3d34d6b..f59b6d4bba 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -104,6 +104,9 @@ en: date_time_string_validator: not_string_error: "must be a string" invalid_format_error: "must be valid" + integer_array_validator: + not_array_error: "must be an array" + invalid_element_error: "must contain only valid integers" enterprise_mailer: confirmation_instructions: diff --git a/spec/validators/integer_array_validator_spec.rb b/spec/validators/integer_array_validator_spec.rb new file mode 100644 index 0000000000..156409f0d0 --- /dev/null +++ b/spec/validators/integer_array_validator_spec.rb @@ -0,0 +1,57 @@ +require "spec_helper" + +describe IntegerArrayValidator do + class TestModel + include ActiveModel::Validations + + attr_accessor :ids + + validates :ids, integer_array: true + end + + describe "internationalization" do + it "has translation for NOT_ARRAY_ERROR" do + expect(described_class::NOT_ARRAY_ERROR).not_to be_blank + end + + it "has translation for INVALID_ELEMENT_ERROR" do + expect(described_class::INVALID_ELEMENT_ERROR).not_to be_blank + end + end + + describe "validation" do + let(:instance) { TestModel.new } + + it "does not add error when nil" do + instance.ids = nil + expect(instance).to be_valid + end + + it "does not add error when blank array" do + instance.ids = [] + expect(instance).to be_valid + end + + it "adds error NOT_ARRAY_ERROR when neither nil nor an array" do + instance.ids = 1 + expect(instance).not_to be_valid + expect(instance.errors[:ids]).to include(described_class::NOT_ARRAY_ERROR) + end + + it "does not add error when array of integers" do + instance.ids = [1, 2, 3] + expect(instance).to be_valid + end + + it "does not add error when array of integers as String" do + instance.ids = ["1", "2", "3"] + expect(instance).to be_valid + end + + it "adds error INVALID_ELEMENT_ERROR when an element cannot be parsed as Integer" do + instance.ids = [1, "2", "Not Integer", 3] + expect(instance).not_to be_valid + expect(instance.errors[:ids]).to include(described_class::INVALID_ELEMENT_ERROR) + end + end +end From 519a7d2ee6e40a856a5d692e35f3065ab54c9da3 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 21 Sep 2018 17:29:27 +0800 Subject: [PATCH 039/108] Add datetime and integer array validator matchers --- .../matchers/date_time_validator_matchers.rb | 30 +++++++++++++++++++ .../integer_array_validator_matchers.rb | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 spec/support/matchers/date_time_validator_matchers.rb create mode 100644 spec/support/matchers/integer_array_validator_matchers.rb diff --git a/spec/support/matchers/date_time_validator_matchers.rb b/spec/support/matchers/date_time_validator_matchers.rb new file mode 100644 index 0000000000..f0fdaf678c --- /dev/null +++ b/spec/support/matchers/date_time_validator_matchers.rb @@ -0,0 +1,30 @@ +# RSpec matcher for DateTimeValidator +# +# Usage: +# +# describe Post do +# it { should validate_date_time_format_of(:start_at) } +# end +RSpec::Matchers.define :validate_date_time_format_of do |attribute| + match do |instance| + @instance, @attribute = instance, attribute + + invalid_format_message = I18n.t("validators.date_time_string_validator.invalid_format_error") + + allow(instance).to receive(attribute) { "Invalid Format" } + instance.valid? + (instance.errors[attribute] || []).include?(invalid_format_message) + end + + description do + "validates :#{@attribute} has datetime format" + end + + failure_message do + "expected #{@instance} to validate format of :#{@attribute} is datetime" + end + + failure_message_when_negated do + "expected #{@instance} not to validate format of :#{@attribute} is datetime" + end +end diff --git a/spec/support/matchers/integer_array_validator_matchers.rb b/spec/support/matchers/integer_array_validator_matchers.rb new file mode 100644 index 0000000000..44cff638aa --- /dev/null +++ b/spec/support/matchers/integer_array_validator_matchers.rb @@ -0,0 +1,30 @@ +# RSpec matcher for IntegerArrayValidator +# +# Usage: +# +# describe Post do +# it { should validate_integer_array(:related_post_ids) } +# end +RSpec::Matchers.define :validate_integer_array do |attribute| + match do |instance| + @instance, @attribute = instance, attribute + + invalid_format_message = I18n.t("validators.integer_array_validator.invalid_element_error") + + allow(instance).to receive(attribute) { [1, "2", "Not Integer", 3] } + instance.valid? + (instance.errors[attribute] || []).include?(invalid_format_message) + end + + description do + "validates :#{@attribute} is integer array" + end + + failure_message do + "expected #{@instance} to validate :#{@attribute} is integer array" + end + + failure_message_when_negated do + "expected #{@instance} not to validate :#{@attribute} is integer array" + end +end From a0976404644b240350cdfaae2d69493025787a4b Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 21 Sep 2018 17:52:09 +0800 Subject: [PATCH 040/108] Add basic form object for enterprise fee summary --- config/locales/en.yml | 5 ++ .../enterprise_fee_summary/parameters.rb | 46 +++++++++++++++++++ .../enterprise_fee_summary/parameters_spec.rb | 44 ++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 lib/order_management/reports/enterprise_fee_summary/parameters.rb create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb diff --git a/config/locales/en.yml b/config/locales/en.yml index f59b6d4bba..01fd3aaec4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2681,6 +2681,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using signup: start_free_profile: "Start with a free profile, and expand when you're ready!" + order_management: + reports: + enterprise_fee_summary: + date_end_before_start_error: "must be after start" + spree: # TODO: remove `email` key once we get to Spree 2.0 email: Email diff --git a/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/lib/order_management/reports/enterprise_fee_summary/parameters.rb new file mode 100644 index 0000000000..62b10f308e --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/parameters.rb @@ -0,0 +1,46 @@ +module OrderManagement + module Reports + module EnterpriseFeeSummary + class Parameters + @i18n_scope = "order_management.reports.enterprise_fee_summary" + + DATE_END_BEFORE_START_ERROR = I18n.t("date_end_before_start_error", scope: @i18n_scope) + + extend ActiveModel::Naming + include ActiveModel::Validations + + attr_accessor :start_at, :end_at, :distributor_ids, :producer_ids, :order_cycle_ids, + :enterprise_fee_ids, :shipping_method_ids, :payment_method_ids + + validates :start_at, :end_at, date_time_string: true + validates :distributor_ids, :producer_ids, integer_array: true + validates :order_cycle_ids, integer_array: true + validates :enterprise_fee_ids, integer_array: true + validates :shipping_method_ids, :payment_method_ids, integer_array: true + + validate :require_valid_datetime_range + + def initialize(attributes = {}) + self.distributor_ids = [] + self.producer_ids = [] + self.order_cycle_ids = [] + self.enterprise_fee_ids = [] + self.shipping_method_ids = [] + self.payment_method_ids = [] + + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + + protected + + def require_valid_datetime_range + return if start_at.blank? || end_at.blank? + + errors.add(:end_at, DATE_END_BEFORE_START_ERROR) unless start_at < end_at + end + end + end + end +end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb new file mode 100644 index 0000000000..94aac5e323 --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -0,0 +1,44 @@ +require "spec_helper" + +require "date_time_string_validator" +require "order_management/reports/enterprise_fee_summary/parameters" + +describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do + describe "validation" do + let(:parameters) { described_class.new } + + it "allows all parameters to be blank" do + expect(parameters).to be_valid + end + + context "for type of parameters" do + it { expect(subject).to validate_date_time_format_of(:start_at) } + it { expect(subject).to validate_date_time_format_of(:end_at) } + it { expect(subject).to validate_integer_array(:distributor_ids) } + it { expect(subject).to validate_integer_array(:producer_ids) } + it { expect(subject).to validate_integer_array(:order_cycle_ids) } + it { expect(subject).to validate_integer_array(:enterprise_fee_ids) } + it { expect(subject).to validate_integer_array(:shipping_method_ids) } + it { expect(subject).to validate_integer_array(:payment_method_ids) } + + describe "requiring start_at to be before end_at" do + let(:now) { Time.zone.now } + + it "adds error when start_at is after end_at" do + allow(subject).to receive(:start_at) { now.to_s } + allow(subject).to receive(:end_at) { (now - 1.hour).to_s } + + expect(subject).not_to be_valid + expect(subject.errors[:end_at]).to eq([described_class::DATE_END_BEFORE_START_ERROR]) + end + + it "does not add error when start_at is before end_at" do + allow(subject).to receive(:start_at) { now.to_s } + allow(subject).to receive(:end_at) { (now + 1.hour).to_s } + + expect(subject).to be_valid + end + end + end + end +end From 3763cb98a3b9ab640750e718a775727f46501afa Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 26 Sep 2018 14:00:53 +0800 Subject: [PATCH 041/108] Add data classes for Enterprise Fee Summary report --- .../reports/report_data/base.rb | 13 +++++++++++++ .../report_data/enterprise_fee_type_total.rb | 14 ++++++++++++++ .../report_data/enterprise_fee_type_totals.rb | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 lib/open_food_network/reports/report_data/base.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb diff --git a/lib/open_food_network/reports/report_data/base.rb b/lib/open_food_network/reports/report_data/base.rb new file mode 100644 index 0000000000..b29ced4684 --- /dev/null +++ b/lib/open_food_network/reports/report_data/base.rb @@ -0,0 +1,13 @@ +module OpenFoodNetwork + module Reports + module ReportData + class Base + def initialize(attributes = {}) + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb new file mode 100644 index 0000000000..4a931a8b46 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb @@ -0,0 +1,14 @@ +require "open_food_network/reports/report_data/base" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module ReportData + class EnterpriseFeeTypeTotal < OpenFoodNetwork::Reports::ReportData::Base + attr_accessor :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, + :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb new file mode 100644 index 0000000000..b88e4475e1 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb @@ -0,0 +1,19 @@ +require "open_food_network/reports/report_data/base" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module ReportData + class EnterpriseFeeTypeTotals < OpenFoodNetwork::Reports::ReportData::Base + attr_accessor :list + + def initialize(*args) + @list = [] + + super(*args) + end + end + end + end + end +end From faf7079780ccf93bd6357dfeb8c1971887ab534d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 1 Oct 2018 13:58:51 +0800 Subject: [PATCH 042/108] Specify sort order of enterprise fee totals data --- .../report_data/enterprise_fee_type_total.rb | 17 +++++++ .../enterprise_fee_type_total_spec.rb | 48 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb index 4a931a8b46..2a15ab8a8e 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb @@ -7,6 +7,23 @@ module OrderManagement class EnterpriseFeeTypeTotal < OpenFoodNetwork::Reports::ReportData::Base attr_accessor :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount + + def <=>(other) + self.class.sortable_data(self) <=> self.class.sortable_data(other) + end + + def self.sortable_data(instance) + [ + instance.fee_type, + instance.enterprise_name, + instance.fee_name, + instance.customer_name, + instance.fee_placement, + instance.fee_calculated_on_transfer_through_name, + instance.tax_category_name, + instance.total_amount + ] + end end end end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb new file mode 100644 index 0000000000..85fd826613 --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb @@ -0,0 +1,48 @@ +require "spec_helper" + +require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total" + +describe OrderManagement::Reports::EnterpriseFeeSummary::ReportData::EnterpriseFeeTypeTotal do + it "sorts instances according to their attributes" do + instance_a = described_class.new( + fee_type: "sales", + enterprise_name: "Enterprise A", + fee_name: "A Sales", + customer_name: "Customer A", + fee_placement: "Incoming", + fee_calculated_on_transfer_through_name: "Transfer Enterprise B", + tax_category_name: "Sales 4%", + total_amount: "12.00" + ) + + instance_b = described_class.new( + fee_type: "sales", + enterprise_name: "Enterprise A", + fee_name: "B Sales", + customer_name: "Customer A", + fee_placement: "Incoming", + fee_calculated_on_transfer_through_name: "Transfer Enterprise B", + tax_category_name: "Sales 4%", + total_amount: "12.00" + ) + + instance_c = described_class.new( + fee_type: "admin", + enterprise_name: "Enterprise A", + fee_name: "C Admin", + customer_name: "Customer B", + fee_placement: "Incoming", + fee_calculated_on_transfer_through_name: nil, + tax_category_name: "Sales 6%", + total_amount: "12.00" + ) + + list = [ + instance_a, + instance_b, + instance_c + ] + + expect(list.sort).to eq([instance_c, instance_a, instance_b]) + end +end From e7ed625d5a99c37b9f9245b686f2c98e3c99841b Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 2 Oct 2018 15:49:20 +0800 Subject: [PATCH 043/108] Add processing of enterprise fee summary from DB --- .rubocop_styleguide.yml | 4 + config/locales/en.yml | 6 +- .../enterprise_fee_type_total_summarizer.rb | 54 ++++ .../enterprise_fee_summary/report_service.rb | 47 ++++ .../reports/enterprise_fee_summary/scope.rb | 264 ++++++++++++++++++ .../report_service_spec.rb | 155 ++++++++++ 6 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/report_service.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/scope.rb create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb diff --git a/.rubocop_styleguide.yml b/.rubocop_styleguide.yml index 13214b0aed..3c37dd7274 100644 --- a/.rubocop_styleguide.yml +++ b/.rubocop_styleguide.yml @@ -194,6 +194,8 @@ Metrics/BlockNesting: Metrics/ClassLength: Max: 100 + Exclude: + - lib/order_management/reports/enterprise_fee_summary/scope.rb Metrics/ModuleLength: Max: 100 @@ -203,6 +205,8 @@ Metrics/CyclomaticComplexity: Metrics/MethodLength: Max: 10 + Exclude: + - lib/order_management/reports/enterprise_fee_summary/scope.rb Metrics/ParameterLists: Max: 5 diff --git a/config/locales/en.yml b/config/locales/en.yml index 01fd3aaec4..9f82e41894 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1357,7 +1357,6 @@ en: ie_warning_firefox: Download Firefox ie_warning_ie: Upgrade Internet Explorer ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)" - legal: cookies_policy: header: "How We Use Cookies" @@ -2685,6 +2684,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using reports: enterprise_fee_summary: date_end_before_start_error: "must be after start" + fee_calculated_on_transfer_through_all: "All" + fee_placements: + supplier: "Incoming" + distributor: "Outgoing" + coordinator: "Coordinator" spree: # TODO: remove `email` key once we get to Spree 2.0 diff --git a/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb b/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb new file mode 100644 index 0000000000..e3c5b8683a --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb @@ -0,0 +1,54 @@ +module OrderManagement + module Reports + module EnterpriseFeeSummary + class EnterpriseFeeTypeTotalSummarizer + attr_accessor :data + + def initialize(data) + @data = data + end + + def fee_type + data["fee_type"].capitalize if data["fee_type"] + end + + def enterprise_name + data["enterprise_name"] + end + + def fee_name + data["fee_name"] + end + + def customer_name + data["customer_name"] + end + + def fee_placement + i18n_translate("fee_placements.#{data['placement_enterprise_role']}") + end + + def fee_calculated_on_transfer_through_name + transfer_through_all_string = i18n_translate("fee_calculated_on_transfer_through_all") + + data["incoming_exchange_enterprise_name"] || data["outgoing_exchange_enterprise_name"] || + (transfer_through_all_string if data["placement_enterprise_role"] == "coordinator") + end + + def tax_category_name + data["tax_category_name"] || data["product_tax_category_name"] + end + + def total_amount + data["total_amount"] + end + + private + + def i18n_translate(translation_key) + I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}") + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/lib/order_management/reports/enterprise_fee_summary/report_service.rb new file mode 100644 index 0000000000..a5668a7d69 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -0,0 +1,47 @@ +require "order_management/reports/enterprise_fee_summary/scope" +require "order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer" +require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals" +require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + class ReportService + attr_accessor :parameters + + def initialize(parameters) + @parameters = parameters + end + + def enterprise_fees_by_customer + Scope.new.all + end + + def enterprise_fee_type_totals + ReportData::EnterpriseFeeTypeTotals.new(list: enterprise_fee_type_total_list.sort) + end + + private + + def enterprise_fee_type_total_list + enterprise_fees_by_customer.map do |total_data| + summarizer = EnterpriseFeeTypeTotalSummarizer.new(total_data) + + ReportData::EnterpriseFeeTypeTotal.new.tap do |total| + enterprise_fee_type_summarizer_to_total_attributes.each do |attribute| + total.public_send("#{attribute}=", summarizer.public_send(attribute)) + end + end + end + end + + def enterprise_fee_type_summarizer_to_total_attributes + [ + :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, + :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount + ] + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/scope.rb b/lib/order_management/reports/enterprise_fee_summary/scope.rb new file mode 100644 index 0000000000..e6557fd772 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/scope.rb @@ -0,0 +1,264 @@ +module OrderManagement + module Reports + module EnterpriseFeeSummary + class Scope + def initialize + setup_default_scope + end + + def all + @scope.all + end + + protected + + def setup_default_scope + find_adjustments_for_enterprise_fees + + include_adjustment_metadata + include_enterprise_fee_details + include_order_details + include_line_item_details + include_incoming_exchange_details + include_outgoing_exchange_details + + group_data + select_attributes + end + + def find_adjustments_for_enterprise_fees + find_adjustments.for_orders.for_enterprise_fees + end + + def find_adjustments + chain_to_scope do + Spree::Adjustment + end + end + + def for_orders + chain_to_scope do + where(adjustable_type: "Spree::Order") + end + end + + def for_enterprise_fees + chain_to_scope do + where(originator_type: "EnterpriseFee") + end + end + + def include_adjustment_metadata + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN adjustment_metadata + ON (adjustment_metadata.adjustment_id = spree_adjustments.id) + JOIN_STRING + ) + end + + # Includes: + # * Enterprise fee + # * Enterprise + # * Enterprise fee tax category + def include_enterprise_fee_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprise_fees + ON ( + spree_adjustments.originator_type = 'EnterpriseFee' + AND enterprise_fees.id = spree_adjustments.originator_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprises + ON (enterprises.id = enterprise_fees.enterprise_id) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_tax_categories + ON (spree_tax_categories.id = enterprise_fees.tax_category_id) + JOIN_STRING + ) + end + + # Includes: + # * Order + # * Customer + def include_order_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_orders + ON ( + spree_adjustments.adjustable_type = 'Spree::Order' + AND spree_orders.id = spree_adjustments.adjustable_id + ) + JOIN_STRING + ) + + join_scope("LEFT OUTER JOIN customers ON (customers.id = spree_orders.customer_id)") + end + + # If for line item - Use data only if spree_line_items.id is present + # + # Includes: + # * Line item + # * Variant + # * Product + # * Tax category of product, if enterprise fee tells to inherit + def include_line_item_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_line_items + ON ( + spree_adjustments.source_type = 'Spree::LineItem' + AND spree_line_items.id = spree_adjustments.source_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_variants + ON ( + spree_adjustments.source_type = 'Spree::LineItem' + AND spree_variants.id = spree_line_items.variant_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_products + ON (spree_products.id = spree_variants.product_id) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_tax_categories AS product_tax_categories + ON ( + enterprise_fees.inherits_tax_category IS TRUE + AND product_tax_categories.id = spree_products.tax_category_id + ) + JOIN_STRING + ) + end + + # If incoming exchange - Use data only if incoming_exchange_variants.id is present + # + # Includes: + # * Incoming exchange + # * Incoming exchange enterprise + # * Incoming exchange variant + def include_incoming_exchange_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN + ( + exchange_variants AS incoming_exchange_variants + LEFT OUTER JOIN exchanges AS incoming_exchanges + ON ( + incoming_exchanges.incoming IS TRUE + AND incoming_exchange_variants.exchange_id = incoming_exchanges.id + ) + ) + ON ( + spree_adjustments.source_type = 'Spree::LineItem' + AND adjustment_metadata.enterprise_role = 'supplier' + AND incoming_exchanges.order_cycle_id = spree_orders.order_cycle_id + AND incoming_exchange_variants.id IS NOT NULL + AND incoming_exchange_variants.variant_id = spree_line_items.variant_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprises AS incoming_exchange_enterprises + ON (incoming_exchange_enterprises.id = incoming_exchanges.sender_id) + JOIN_STRING + ) + end + + # If outgoing exchange - Use data only if outgoing_exchange_variants.id is present + # + # Includes: + # * Outgoing exchange + # * Outgoing exchange enterprise + # * Outgoing exchange variant + def include_outgoing_exchange_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN + ( + exchange_variants AS outgoing_exchange_variants + LEFT OUTER JOIN exchanges AS outgoing_exchanges + ON ( + outgoing_exchanges.incoming IS NOT TRUE + AND outgoing_exchange_variants.exchange_id = outgoing_exchanges.id + ) + ) + ON ( + spree_adjustments.source_type = 'Spree::LineItem' + AND adjustment_metadata.enterprise_role = 'distributor' + AND outgoing_exchanges.order_cycle_id = spree_orders.order_cycle_id + AND outgoing_exchange_variants.id IS NOT NULL + AND outgoing_exchange_variants.variant_id = spree_line_items.variant_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprises AS outgoing_exchange_enterprises + ON (outgoing_exchange_enterprises.id = outgoing_exchanges.sender_id) + JOIN_STRING + ) + end + + def group_data + chain_to_scope do + group("enterprise_fees.id", "enterprises.id", "customers.id", + "adjustment_metadata.enterprise_role", "spree_tax_categories.id", + "product_tax_categories.id", "incoming_exchange_enterprises.id", + "outgoing_exchange_enterprises.id") + end + end + + def select_attributes + chain_to_scope do + select( + <<-JOIN_STRING.strip_heredoc + SUM(spree_adjustments.amount) AS total_amount, enterprises.name AS enterprise_name, + enterprise_fees.fee_type AS fee_type, customers.name AS customer_name, + customers.email AS customer_email, enterprise_fees.fee_type AS fee_type, + enterprise_fees.name AS fee_name, spree_tax_categories.name AS tax_category_name, + product_tax_categories.name AS product_tax_category_name, + adjustment_metadata.enterprise_role AS placement_enterprise_role, + incoming_exchange_enterprises.name AS incoming_exchange_enterprise_name, + outgoing_exchange_enterprises.name AS outgoing_exchange_enterprise_name + JOIN_STRING + ) + end + end + + def chain_to_scope(&block) + @scope = @scope.instance_eval(&block) + self + end + + def join_scope(join_string) + chain_to_scope do + joins(join_string) + end + end + end + end + end +end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb new file mode 100644 index 0000000000..e8056a0087 --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -0,0 +1,155 @@ +require "spec_helper" + +require "order_management/reports/enterprise_fee_summary/report_service" +require "order_management/reports/enterprise_fee_summary/parameters" + +describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do + let!(:shipping_method) { create(:shipping_method) } + let!(:payment_method) { create(:payment_method) } + + let!(:distributor) do + create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| + payment_method.distributors << enterprise + shipping_method.distributors << enterprise + end + end + let!(:distributor_fees) do + [ + create(:enterprise_fee, name: "Included Distributor Fee 1", enterprise: distributor, + fee_type: "admin", calculator: per_item_calculator(4.0), + tax_category: prepare_tax_category("Sample Distributor Tax")), + create(:enterprise_fee, name: "Included Distributor Fee 2", enterprise: distributor, + fee_type: "sales", calculator: per_item_calculator(8.0), + inherits_tax_category: true), + create(:enterprise_fee, name: "Excluded Distributor Fee", enterprise: distributor, + fee_type: "sales", calculator: per_item_calculator(16.0)) + ] + end + + let!(:producer) { create(:supplier_enterprise, name: "Sample Producer") } + let!(:producer_fees) do + [ + create(:enterprise_fee, name: "Excluded Producer Fee", enterprise: producer, + fee_type: "admin", calculator: per_item_calculator(32.0)), + create(:enterprise_fee, name: "Included Producer Fee 1", enterprise: producer, + fee_type: "sales", calculator: per_item_calculator(64.0), + tax_category: prepare_tax_category("Sample Producer Tax")), + create(:enterprise_fee, name: "Included Producer Fee 2", enterprise: producer, + fee_type: "sales", calculator: per_item_calculator(128.0), + inherits_tax_category: true) + ] + end + + let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } + let!(:coordinator_fees) do + [ + create(:enterprise_fee, name: "Excluded Coordinator Fee", enterprise: coordinator, + fee_type: "admin", calculator: per_item_calculator(256.0)), + create(:enterprise_fee, name: "Included Coordinator Fee 1", enterprise: coordinator, + fee_type: "admin", calculator: per_item_calculator(512.0), + tax_category: prepare_tax_category("Sample Coordinator Tax")), + create(:enterprise_fee, name: "Included Coordinator Fee 2", enterprise: coordinator, + fee_type: "sales", calculator: per_item_calculator(1024.0), + inherits_tax_category: true) + ] + end + + let!(:order_cycle) do + create(:simple_order_cycle, coordinator: coordinator, + coordinator_fees: [coordinator_fees[1], coordinator_fees[2]]) + end + + let!(:product) { create(:product, tax_category: prepare_tax_category("Sample Product Tax")) } + let!(:variant) { create(:variant, product: product, is_master: false) } + let!(:incoming_exchange) do + create(:exchange, order_cycle: order_cycle, incoming: true, sender: producer, + receiver: coordinator, variants: [variant]).tap do |exchange| + exchange.enterprise_fees << producer_fees[1] + exchange.enterprise_fees << producer_fees[2] + end + end + let!(:outgoing_exchange) do + create(:exchange, order_cycle: order_cycle, incoming: false, sender: coordinator, + receiver: distributor, variants: [variant]).tap do |exchange| + exchange.enterprise_fees << distributor_fees[0] + exchange.enterprise_fees << distributor_fees[1] + end + end + + let!(:customer) { create(:customer, name: "Sample Customer") } + let!(:customer_order) { prepare_completed_order(customer) } + + it "groups and sorts entries correctly" do + parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new + service = described_class.new(parameters) + + totals = service.enterprise_fee_type_totals + + expect(totals.list.length).to eq(6) + + # Data is sorted by the following, in order: + # * fee_type + # * enterprise_name + # * fee_name + # * customer_name + # * fee_placement + # * fee_calculated_on_transfer_through_name + # * tax_category_name + # * total_amount + + expected_result = [ + ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Sample Customer", + "Coordinator", "All", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", + "Coordinator", "All", "Sample Product Tax", "1024.00"], + ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Product Tax", "8.00"], + ["Sales", "Sample Producer", "Included Producer Fee 1", "Sample Customer", + "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], + ["Sales", "Sample Producer", "Included Producer Fee 2", "Sample Customer", + "Incoming", "Sample Producer", "Sample Product Tax", "128.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals.list[row_index], expected_attributes) + end + end + + # Helper methods for example group + + def expect_total_attributes(total, expected_attribute_list) + actual_attribute_list = [total.fee_type, total.enterprise_name, total.fee_name, + total.customer_name, total.fee_placement, + total.fee_calculated_on_transfer_through_name, total.tax_category_name, + total.total_amount] + expect(actual_attribute_list).to eq(expected_attribute_list) + end + + def prepare_tax_category(name) + create(:tax_category, name: name) + end + + def prepare_completed_order(customer) + order = create(:order, customer: customer, distributor: distributor, order_cycle: order_cycle, + shipping_method: shipping_method) + create(:line_item, order: order, variant: variant) + + complete_order(order) + + order.reload + end + + def complete_order(order) + order.create_shipment! + create(:payment, state: "checkout", order: order, amount: order.total, + payment_method: payment_method) + order.update_distribution_charge! + while !order.completed? do break unless order.next! end + end + + def per_item_calculator(amount) + Spree::Calculator::PerItem.new(preferred_amount: amount) + end +end From 00e70733b0fd32da4ca24aed51977881b1b94b38 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 3 Oct 2018 14:09:50 +0800 Subject: [PATCH 044/108] Add test data for enterprise fee summary report --- .../report_service_spec.rb | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index e8056a0087..09f7d86441 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -77,7 +77,13 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end let!(:customer) { create(:customer, name: "Sample Customer") } + let!(:another_customer) { create(:customer, name: "Another Customer") } + let!(:customer_order) { prepare_completed_order(customer) } + let!(:second_customer_order) { prepare_completed_order(customer) } + + let!(:another_customer) { create(:customer, name: "Another Customer") } + let!(:other_customer_order) { prepare_completed_order(another_customer) } it "groups and sorts entries correctly" do parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new @@ -85,7 +91,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do totals = service.enterprise_fee_type_totals - expect(totals.list.length).to eq(6) + expect(totals.list.length).to eq(12) # Data is sorted by the following, in order: # * fee_type @@ -98,18 +104,30 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do # * total_amount expected_result = [ - ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Sample Customer", + ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Another Customer", "Coordinator", "All", "Sample Coordinator Tax", "512.00"], - ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", + ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Sample Customer", + "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], + ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Another Customer", "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", + ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Another Customer", "Coordinator", "All", "Sample Product Tax", "1024.00"], - ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Sample Customer", + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", + "Coordinator", "All", "Sample Product Tax", "2048.00"], + ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Another Customer", "Outgoing", "Sample Coordinator", "Sample Product Tax", "8.00"], - ["Sales", "Sample Producer", "Included Producer Fee 1", "Sample Customer", + ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Product Tax", "16.00"], + ["Sales", "Sample Producer", "Included Producer Fee 1", "Another Customer", "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], + ["Sales", "Sample Producer", "Included Producer Fee 1", "Sample Customer", + "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], + ["Sales", "Sample Producer", "Included Producer Fee 2", "Another Customer", + "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], ["Sales", "Sample Producer", "Included Producer Fee 2", "Sample Customer", - "Incoming", "Sample Producer", "Sample Product Tax", "128.00"] + "Incoming", "Sample Producer", "Sample Product Tax", "256.00"] ] expected_result.each_with_index do |expected_attributes, row_index| From ed2b8e9b9f1e93fe9b3200ebbc5cf8cc455d267b Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 3 Oct 2018 18:40:29 +0800 Subject: [PATCH 045/108] Add CSV renderer for enterprise fee summary report --- config/locales/en.yml | 11 +++ .../reports/renderers/base.rb | 13 ++++ .../renderers/csv_renderer.rb | 57 +++++++++++++++ .../enterprise_fee_summary/report_service.rb | 10 ++- .../renderers/csv_renderer_spec.rb | 73 +++++++++++++++++++ .../report_service_spec.rb | 2 +- 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 lib/open_food_network/reports/renderers/base.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb diff --git a/config/locales/en.yml b/config/locales/en.yml index 9f82e41894..119a0fe200 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2689,6 +2689,17 @@ See the %{link} to find out more about %{sitename}'s features and to start using supplier: "Incoming" distributor: "Outgoing" coordinator: "Coordinator" + formats: + csv: + header: + fee_type: "Fee Type" + enterprise_name: "Enterprise Owner" + fee_name: "Fee Name" + customer_name: "Customer" + fee_placement: "Fee Placement" + fee_calculated_on_transfer_through_name: "Fee Calc on Transfer Through" + tax_category_name: "Tax Category" + total_amount: "$$ SUM" spree: # TODO: remove `email` key once we get to Spree 2.0 diff --git a/lib/open_food_network/reports/renderers/base.rb b/lib/open_food_network/reports/renderers/base.rb new file mode 100644 index 0000000000..be720366a6 --- /dev/null +++ b/lib/open_food_network/reports/renderers/base.rb @@ -0,0 +1,13 @@ +module OpenFoodNetwork + module Reports + module Renderers + class Base + attr_accessor :report_data + + def initialize(report_data) + @report_data = report_data + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb new file mode 100644 index 0000000000..a06bcf634b --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb @@ -0,0 +1,57 @@ +require "open_food_network/reports/renderers/base" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module Renderers + class CsvRenderer < OpenFoodNetwork::Reports::Renderers::Base + def render + CSV.generate do |csv| + render_header(csv) + + report_data.enterprise_fee_type_totals.list.each do |data| + render_data_row(csv, data) + end + end + end + + private + + def render_header(csv) + csv << [ + header_label(:fee_type), + header_label(:enterprise_name), + header_label(:fee_name), + header_label(:customer_name), + header_label(:fee_placement), + header_label(:fee_calculated_on_transfer_through_name), + header_label(:tax_category_name), + header_label(:total_amount) + ] + end + + def render_data_row(csv, data) + csv << [ + data.fee_type, + data.enterprise_name, + data.fee_name, + data.customer_name, + data.fee_placement, + data.fee_calculated_on_transfer_through_name, + data.tax_category_name, + data.total_amount + ] + end + + def header_label(attribute) + I18n.t("header.#{attribute}", scope: i18n_scope) + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary.formats.csv" + end + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/lib/order_management/reports/enterprise_fee_summary/report_service.rb index a5668a7d69..15ab2a64f0 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -7,10 +7,11 @@ module OrderManagement module Reports module EnterpriseFeeSummary class ReportService - attr_accessor :parameters + attr_accessor :parameters, :renderer_klass - def initialize(parameters) + def initialize(parameters, renderer_klass) @parameters = parameters + @renderer_klass = renderer_klass end def enterprise_fees_by_customer @@ -21,6 +22,11 @@ module OrderManagement ReportData::EnterpriseFeeTypeTotals.new(list: enterprise_fee_type_total_list.sort) end + def render + renderer = renderer_klass.new(self) + renderer.render + end + private def enterprise_fee_type_total_list diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb new file mode 100644 index 0000000000..4ed3e9ba21 --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -0,0 +1,73 @@ +require "spec_helper" + +require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/report_service" +require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" + +describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do + let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + + let!(:parameters) { report_klass::Parameters.new } + let!(:service) { report_klass::ReportService.new(parameters, described_class) } + + let!(:enterprise_fee_type_totals) do + instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new + instance.tap do |totals| + totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + fee_type: "Fee Type A", + enterprise_name: "Enterprise A", + fee_name: "Fee A", + customer_name: "Custoemr A", + fee_placement: "Fee Placement A", + fee_calculated_on_transfer_through_name: "Transfer Enterprise A", + tax_category_name: "Tax Category A", + total_amount: "1.00" + ) + + totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + fee_type: "Fee Type B", + enterprise_name: "Enterprise B", + fee_name: "Fee C", + customer_name: "Custoemr D", + fee_placement: "Fee Placement E", + fee_calculated_on_transfer_through_name: "Transfer Enterprise F", + tax_category_name: "Tax Category G", + total_amount: "2.00" + ) + end + end + + before do + allow(service).to receive(:enterprise_fee_type_totals) { enterprise_fee_type_totals } + end + + it "generates CSV header" do + result = service.render + csv = CSV.parse(result) + header_row = csv[0] + + # Test all header cells have values + expect(header_row.length).to eq(8) + expect(header_row.all?(&:present?)).to be_truthy + end + + it "generates CSV data rows" do + result = service.render + csv = CSV.parse(result, headers: true) + + expect(csv.length).to eq(2) + + # Test random cells + expect(csv[0][i18n_translate("header.fee_type")]).to eq("Fee Type A") + expect(csv[0][i18n_translate("header.total_amount")]).to eq("1.00") + expect(csv[1][i18n_translate("header.total_amount")]).to eq("2.00") + end + + def i18n_translate(key) + I18n.t(key, scope: i18n_scope) + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary.formats.csv" + end +end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 09f7d86441..31d1262ca7 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -87,7 +87,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do it "groups and sorts entries correctly" do parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new - service = described_class.new(parameters) + service = described_class.new(parameters, nil) totals = service.enterprise_fee_type_totals From 5b6bbc3920bcc36904d7ce589847186b3335131a Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 9 Oct 2018 07:38:08 +1100 Subject: [PATCH 046/108] Add generation of CSV renderer filename --- .../enterprise_fee_summary/renderers/csv_renderer.rb | 5 +++++ .../reports/enterprise_fee_summary/report_service.rb | 11 ++++++----- .../renderers/csv_renderer_spec.rb | 7 +++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb index a06bcf634b..388cac91eb 100644 --- a/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb @@ -15,6 +15,11 @@ module OrderManagement end end + def filename + timestamp = Time.zone.now.strftime("%Y%m%d") + "enterprise_fee_summary_#{timestamp}.csv" + end + private def render_header(csv) diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/lib/order_management/reports/enterprise_fee_summary/report_service.rb index 15ab2a64f0..953bea06ee 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -7,6 +7,8 @@ module OrderManagement module Reports module EnterpriseFeeSummary class ReportService + delegate :render, :filename, to: :renderer + attr_accessor :parameters, :renderer_klass def initialize(parameters, renderer_klass) @@ -22,13 +24,12 @@ module OrderManagement ReportData::EnterpriseFeeTypeTotals.new(list: enterprise_fee_type_total_list.sort) end - def render - renderer = renderer_klass.new(self) - renderer.render - end - private + def renderer + @renderer ||= renderer_klass.new(self) + end + def enterprise_fee_type_total_list enterprise_fees_by_customer.map do |total_data| summarizer = EnterpriseFeeTypeTotalSummarizer.new(total_data) diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index 4ed3e9ba21..21854e29ce 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -63,6 +63,13 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer expect(csv[1][i18n_translate("header.total_amount")]).to eq("2.00") end + it "generates filename correctly" do + Timecop.freeze(Time.zone.local(2018, 10, 9, 7, 30, 0)) do + filename = service.filename + expect(filename).to eq("enterprise_fee_summary_20181009.csv") + end + end + def i18n_translate(key) I18n.t(key, scope: i18n_scope) end From b7439d257432bc63b3a5c97df28dd1ae0035cca4 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 10 Oct 2018 16:18:16 +0800 Subject: [PATCH 047/108] Fix sort for nil cells in enterprise fee summary --- .../report_data/enterprise_fee_type_total.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb index 2a15ab8a8e..9f1561824d 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb @@ -9,20 +9,20 @@ module OrderManagement :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount def <=>(other) - self.class.sortable_data(self) <=> self.class.sortable_data(other) + sortable_data <=> other.sortable_data end - def self.sortable_data(instance) + def sortable_data [ - instance.fee_type, - instance.enterprise_name, - instance.fee_name, - instance.customer_name, - instance.fee_placement, - instance.fee_calculated_on_transfer_through_name, - instance.tax_category_name, - instance.total_amount - ] + fee_type, + enterprise_name, + fee_name, + customer_name, + fee_placement, + fee_calculated_on_transfer_through_name, + tax_category_name, + total_amount + ].map { |attribute| attribute || "" } end end end From ec81e4221f8df26dec04aa1e52f52956df80a1c5 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 9 Oct 2018 14:36:56 +0800 Subject: [PATCH 048/108] Add basic action for enterprise fee summary --- ...nterprise_fee_summary_report_controller.rb | 52 +++++++++++++++++++ .../_filters.html.haml | 0 .../index.html.haml | 1 + config/locales/en.yml | 1 + config/routes/spree.rb | 1 + lib/open_food_network/reports.rb | 5 ++ ...rise_fee_summary_report_controller_spec.rb | 49 +++++++++++++++++ 7 files changed, 109 insertions(+) create mode 100644 app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb create mode 100644 app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml create mode 100644 app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml create mode 100644 lib/open_food_network/reports.rb create mode 100644 spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb new file mode 100644 index 0000000000..3f6f9e957e --- /dev/null +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -0,0 +1,52 @@ +require "open_food_network/reports" +require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/report_service" +require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" + +module Spree + module Admin + module Reports + class EnterpriseFeeSummaryReportController < BaseController + def index + return render_report_form if params[:report].blank? + return respond_to_invalid_parameters unless report_parameters.valid? + + service = report_klass::ReportService.new(report_parameters, report_renderer_klass) + send_data service.render, filename: service.filename + end + + private + + def respond_to_invalid_parameters + flash[:error] = I18n.t("invalid_filter_parameters", scope: i18n_scope) + render_report_form + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary" + end + + def render_report_form + render action: :index + end + + def report_klass + OrderManagement::Reports::EnterpriseFeeSummary + end + + def report_parameters + @report_parameters ||= report_klass::Parameters.new(params[:report]) + end + + def report_renderer_klass + case params[:report_format] + when "csv" + report_klass::Renderers::CsvRenderer + else + raise OpenFoodNetwork::Reports::UnsupportedReportFormatException + end + end + end + end + end +end diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml new file mode 100644 index 0000000000..790853ca1f --- /dev/null +++ b/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml @@ -0,0 +1 @@ += render "filters" diff --git a/config/locales/en.yml b/config/locales/en.yml index 119a0fe200..19e5295b8d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2700,6 +2700,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using fee_calculated_on_transfer_through_name: "Fee Calc on Transfer Through" tax_category_name: "Tax Category" total_amount: "$$ SUM" + invalid_filter_parameters: "The filters you selected for this report are invalid." spree: # TODO: remove `email` key once we get to Spree 2.0 diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 2e9dc0b5c8..ecba823fa1 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -25,6 +25,7 @@ Spree::Core::Engine.routes.prepend do match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post] match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post] match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post] + get "/admin/reports/enterprise_fee_summary", to: "admin/reports/enterprise_fee_summary_report#index", as: :enterprise_fee_summary_admin_reports match '/admin', :to => 'admin/overview#index', :as => :admin match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get put 'credit_cards/new_from_token', to: 'credit_cards#new_from_token' diff --git a/lib/open_food_network/reports.rb b/lib/open_food_network/reports.rb new file mode 100644 index 0000000000..5e6886b178 --- /dev/null +++ b/lib/open_food_network/reports.rb @@ -0,0 +1,5 @@ +module OpenFoodNetwork + module Reports + class UnsupportedReportFormatException < StandardError; end + end +end diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb new file mode 100644 index 0000000000..7b058d233e --- /dev/null +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb @@ -0,0 +1,49 @@ +require "spec_helper" + +describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :controller do + let!(:admin) { create(:admin_user) } + + let(:current_user) { admin } + + before do + allow(controller).to receive(:spree_current_user) { admin } + end + + describe "#index" do + context "when there are no parameters" do + it "renders the report form" do + get :index + + expect(response).to be_success + expect(response).to render_template(view_template_path) + end + end + + context "when the parameters are valid" do + it "sends the generated report in the correct format" do + get :index, report: { start_at: "2018-10-09 07:30:00" }, report_format: "csv" + + expect(response).to be_success + expect(response.body).not_to be_blank + expect(response.header["Content-Type"]).to eq("text/csv") + end + end + + context "when the parameters are invalid" do + it "renders the report form with an error" do + get :index, report: { start_at: "invalid date" }, report_format: "csv" + + expect(flash[:error]).to eq(I18n.t("invalid_filter_parameters", scope: i18n_scope)) + expect(response).to render_template(view_template_path) + end + end + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary" + end + + def view_template_path + "spree/admin/reports/enterprise_fee_summary_report/index" + end +end From bd2b4c0134d5acaf7aa4d65493add5ac79ecde35 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 10 Oct 2018 18:52:34 +0800 Subject: [PATCH 049/108] Add enterprise fee summary form without options The objects will be filled in another commit. This simply sets up the filters UI for now. --- ...nterprise_fee_summary_report_controller.rb | 10 ++-- .../_filters.html.haml | 50 +++++++++++++++++++ config/locales/en.yml | 15 ++++++ .../enterprise_fee_summary/parameters.rb | 4 ++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb index 3f6f9e957e..c990a5199d 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -7,11 +7,13 @@ module Spree module Admin module Reports class EnterpriseFeeSummaryReportController < BaseController + before_filter :load_report_parameters, only: [:index] + def index return render_report_form if params[:report].blank? - return respond_to_invalid_parameters unless report_parameters.valid? + return respond_to_invalid_parameters unless @report_parameters.valid? - service = report_klass::ReportService.new(report_parameters, report_renderer_klass) + service = report_klass::ReportService.new(@report_parameters, report_renderer_klass) send_data service.render, filename: service.filename end @@ -34,8 +36,8 @@ module Spree OrderManagement::Reports::EnterpriseFeeSummary end - def report_parameters - @report_parameters ||= report_klass::Parameters.new(params[:report]) + def load_report_parameters + @report_parameters = report_klass::Parameters.new(params[:report] || {}) end def report_renderer_klass diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml index e69de29bb2..f4c5db1f56 100644 --- a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml +++ b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml @@ -0,0 +1,50 @@ += form_for @report_parameters, as: :report, url: spree.enterprise_fee_summary_admin_reports_path, method: :get do |f| + .row.date-range-filter + .sixteen.columns.alpha + = label_tag nil, t(".date_range") + %br + + = f.label :start_at, class: "inline" + = f.text_field :start_at, class: "datetimepicker datepicker-from" + + %span.range-divider + %i.icon-arrow-right + + = f.text_field :end_at, class: "datetimepicker datepicker-to" + = f.label :end_at, class: "inline" + + .row + .sixteen.columns.alpha + = f.label :distributor_ids + = f.collection_select(:distributor_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .sixteen.columns.alpha + = f.label :producer_ids + = f.collection_select(:producer_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .sixteen.columns.alpha + = f.label :order_cycle_ids + = f.collection_select(:order_cycle_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .eight.columns.alpha + = f.label :enterprise_fee_ids + = f.collection_select(:enterprise_fee_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + .eight.columns.omega + = f.label :shipping_method_ids + = f.collection_select(:shipping_method_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .eight.columns.alpha   + .eight.columns.omega + = f.label :payment_method_ids + = f.collection_select(:payment_method_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .sixteen.columns.alpha + = check_box_tag :report_format, "csv", false, id: "report_format_csv" + = label_tag :report_format_csv, t(".report_format_csv") + + = button t(".generate_report") diff --git a/config/locales/en.yml b/config/locales/en.yml index 19e5295b8d..19eede5447 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -54,6 +54,16 @@ en: on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" activemodel: + attributes: + order_management/reports/enterprise_fee_summary/parameters: + start_at: "Start" + end_at: "End" + distributor_ids: "Hubs" + producer_ids: "Producers" + order_cycle_ids: "Order Cycles" + enterprise_fee_ids: "Fees Names" + shipping_method_ids: "Shipping Methods" + payment_method_ids: "Payment Methods" errors: models: subscription_validator: @@ -2820,6 +2830,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using bulk_coop_allocation: 'Bulk Co-op - Allocation' bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets' bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' + enterprise_fee_summary_report: + filters: + date_range: "Date Range" + report_format_csv: "Download as CSV" + generate_report: "Generate Report" users: index: listing_users: "Listing Users" diff --git a/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/lib/order_management/reports/enterprise_fee_summary/parameters.rb index 62b10f308e..70f8d89c78 100644 --- a/lib/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/lib/order_management/reports/enterprise_fee_summary/parameters.rb @@ -7,6 +7,7 @@ module OrderManagement DATE_END_BEFORE_START_ERROR = I18n.t("date_end_before_start_error", scope: @i18n_scope) extend ActiveModel::Naming + extend ActiveModel::Translation include ActiveModel::Validations attr_accessor :start_at, :end_at, :distributor_ids, :producer_ids, :order_cycle_ids, @@ -33,6 +34,9 @@ module OrderManagement end end + # The parameters are never persisted. + def to_key; end + protected def require_valid_datetime_range From 34dc16f8c9ddb2b310fe8cdf8ab3de5b0b2ad2c2 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 11 Oct 2018 02:36:59 +0800 Subject: [PATCH 050/108] Base filter options from initial authorizer object --- ...nterprise_fee_summary_report_controller.rb | 6 ++++ .../_filters.html.haml | 12 +++---- .../reports/report_authorizer.rb | 11 +++++++ .../report_authorizer.rb | 33 +++++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 lib/open_food_network/reports/report_authorizer.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb index c990a5199d..cd443f8b23 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -1,5 +1,6 @@ require "open_food_network/reports" require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/report_authorizer" require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" @@ -8,6 +9,7 @@ module Spree module Reports class EnterpriseFeeSummaryReportController < BaseController before_filter :load_report_parameters, only: [:index] + before_filter :load_report_authorizer, only: [:index] def index return render_report_form if params[:report].blank? @@ -40,6 +42,10 @@ module Spree @report_parameters = report_klass::Parameters.new(params[:report] || {}) end + def load_report_authorizer + @report_authorizer = report_klass::ReportAuthorizer.new(spree_current_user) + end + def report_renderer_klass case params[:report_format] when "csv" diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml index f4c5db1f56..25c9a2eed4 100644 --- a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml +++ b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml @@ -16,31 +16,31 @@ .row .sixteen.columns.alpha = f.label :distributor_ids - = f.collection_select(:distributor_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:distributor_ids, @report_authorizer.allowed_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .sixteen.columns.alpha = f.label :producer_ids - = f.collection_select(:producer_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:producer_ids, @report_authorizer.allowed_producers, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .sixteen.columns.alpha = f.label :order_cycle_ids - = f.collection_select(:order_cycle_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:order_cycle_ids, @report_authorizer.allowed_order_cycles, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .eight.columns.alpha = f.label :enterprise_fee_ids - = f.collection_select(:enterprise_fee_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:enterprise_fee_ids, @report_authorizer.allowed_enterprise_fees, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .eight.columns.omega = f.label :shipping_method_ids - = f.collection_select(:shipping_method_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:shipping_method_ids, @report_authorizer.allowed_shipping_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .eight.columns.alpha   .eight.columns.omega = f.label :payment_method_ids - = f.collection_select(:payment_method_ids, [], :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:payment_method_ids, @report_authorizer.allowed_payment_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .sixteen.columns.alpha diff --git a/lib/open_food_network/reports/report_authorizer.rb b/lib/open_food_network/reports/report_authorizer.rb new file mode 100644 index 0000000000..88225a26f5 --- /dev/null +++ b/lib/open_food_network/reports/report_authorizer.rb @@ -0,0 +1,11 @@ +module OpenFoodNetwork + module Reports + class ReportAuthorizer + attr_accessor :user + + def initialize(user) + @user = user + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb b/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb new file mode 100644 index 0000000000..74ce7288b9 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb @@ -0,0 +1,33 @@ +require "open_food_network/reports/report_authorizer" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + class ReportAuthorizer < OpenFoodNetwork::Reports::ReportAuthorizer + def allowed_distributors + [] + end + + def allowed_producers + [] + end + + def allowed_order_cycles + [] + end + + def allowed_enterprise_fees + [] + end + + def allowed_shipping_methods + [] + end + + def allowed_payment_methods + [] + end + end + end + end +end From 6e03ab03f5860fb0fc52639708e52509a9e7bbb8 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 12 Oct 2018 01:07:46 +0800 Subject: [PATCH 051/108] Extract general behaviour for report parameters --- .../reports/parameters/base.rb | 20 +++++++++++++++++++ .../enterprise_fee_summary/parameters.rb | 11 ++++------ 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 lib/open_food_network/reports/parameters/base.rb diff --git a/lib/open_food_network/reports/parameters/base.rb b/lib/open_food_network/reports/parameters/base.rb new file mode 100644 index 0000000000..22e2d5ac89 --- /dev/null +++ b/lib/open_food_network/reports/parameters/base.rb @@ -0,0 +1,20 @@ +module OpenFoodNetwork + module Reports + module Parameters + class Base + extend ActiveModel::Naming + extend ActiveModel::Translation + include ActiveModel::Validations + + def initialize(attributes = {}) + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + + # The parameters are never persisted. + def to_key; end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/lib/order_management/reports/enterprise_fee_summary/parameters.rb index 70f8d89c78..418038967c 100644 --- a/lib/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/lib/order_management/reports/enterprise_fee_summary/parameters.rb @@ -1,7 +1,9 @@ +require "open_food_network/reports/parameters/base" + module OrderManagement module Reports module EnterpriseFeeSummary - class Parameters + class Parameters < OpenFoodNetwork::Reports::Parameters::Base @i18n_scope = "order_management.reports.enterprise_fee_summary" DATE_END_BEFORE_START_ERROR = I18n.t("date_end_before_start_error", scope: @i18n_scope) @@ -29,14 +31,9 @@ module OrderManagement self.shipping_method_ids = [] self.payment_method_ids = [] - attributes.each do |key, value| - public_send("#{key}=", value) - end + super(attributes) end - # The parameters are never persisted. - def to_key; end - protected def require_valid_datetime_range From 418050c1b8f90dbd01baa9470d5f26314f4efbcb Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 11 Oct 2018 11:31:08 +0800 Subject: [PATCH 052/108] Remove blank strings from array report parameters Remove the blank strings that Rails multiple selects add by default to make sure that blank lists are still submitted to the server as arrays instead of nil. https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select --- .../reports/parameters/base.rb | 1 + .../enterprise_fee_summary/parameters.rb | 16 ++++++++++++++++ .../enterprise_fee_summary/parameters_spec.rb | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/lib/open_food_network/reports/parameters/base.rb b/lib/open_food_network/reports/parameters/base.rb index 22e2d5ac89..9b97558ad3 100644 --- a/lib/open_food_network/reports/parameters/base.rb +++ b/lib/open_food_network/reports/parameters/base.rb @@ -5,6 +5,7 @@ module OpenFoodNetwork extend ActiveModel::Naming extend ActiveModel::Translation include ActiveModel::Validations + include ActiveModel::Validations::Callbacks def initialize(attributes = {}) attributes.each do |key, value| diff --git a/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/lib/order_management/reports/enterprise_fee_summary/parameters.rb index 418038967c..0404326826 100644 --- a/lib/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/lib/order_management/reports/enterprise_fee_summary/parameters.rb @@ -15,6 +15,8 @@ module OrderManagement attr_accessor :start_at, :end_at, :distributor_ids, :producer_ids, :order_cycle_ids, :enterprise_fee_ids, :shipping_method_ids, :payment_method_ids + before_validation :cleanup_arrays + validates :start_at, :end_at, date_time_string: true validates :distributor_ids, :producer_ids, integer_array: true validates :order_cycle_ids, integer_array: true @@ -41,6 +43,20 @@ module OrderManagement errors.add(:end_at, DATE_END_BEFORE_START_ERROR) unless start_at < end_at end + + # Remove the blank strings that Rails multiple selects add by default to + # make sure that blank lists are still submitted to the server as arrays + # instead of nil. + # + # https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select + def cleanup_arrays + distributor_ids.reject!(&:blank?) + producer_ids.reject!(&:blank?) + order_cycle_ids.reject!(&:blank?) + enterprise_fee_ids.reject!(&:blank?) + shipping_method_ids.reject!(&:blank?) + payment_method_ids.reject!(&:blank?) + end end end end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb index 94aac5e323..148e32214f 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -21,6 +21,24 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do it { expect(subject).to validate_integer_array(:shipping_method_ids) } it { expect(subject).to validate_integer_array(:payment_method_ids) } + it "allows integer arrays to include blank string and cleans it up" do + subject.distributor_ids = ["", "1"] + subject.producer_ids = ["", "1"] + subject.order_cycle_ids = ["", "1"] + subject.enterprise_fee_ids = ["", "1"] + subject.shipping_method_ids = ["", "1"] + subject.payment_method_ids = ["", "1"] + + expect(subject).to be_valid + + expect(subject.distributor_ids).to eq(["1"]) + expect(subject.producer_ids).to eq(["1"]) + expect(subject.order_cycle_ids).to eq(["1"]) + expect(subject.enterprise_fee_ids).to eq(["1"]) + expect(subject.shipping_method_ids).to eq(["1"]) + expect(subject.payment_method_ids).to eq(["1"]) + end + describe "requiring start_at to be before end_at" do let(:now) { Time.zone.now } From 7d74ddc85b00090362f9b1e2cc95dea2731b1d47 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 11 Oct 2018 12:25:04 +0800 Subject: [PATCH 053/108] Add HTML view for enterprise fee summary report --- ...nterprise_fee_summary_report_controller.rb | 16 ++++- .../_report.html.haml | 19 +++++ .../index.html.haml | 1 + config/locales/en.yml | 13 ++++ .../reports/renderers/base.rb | 4 ++ .../reports/renderers/independent_file.rb | 11 +++ .../renderers/csv_renderer.rb | 3 + .../renderers/html_renderer.rb | 48 +++++++++++++ .../enterprise_fee_summary/report_service.rb | 4 +- .../renderers/html_renderer_spec.rb | 71 +++++++++++++++++++ 10 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 app/views/spree/admin/reports/enterprise_fee_summary_report/_report.html.haml create mode 100644 lib/open_food_network/reports/renderers/independent_file.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb index cd443f8b23..ce749ce882 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -3,6 +3,7 @@ require "order_management/reports/enterprise_fee_summary/parameters" require "order_management/reports/enterprise_fee_summary/report_authorizer" require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" +require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" module Spree module Admin @@ -15,8 +16,8 @@ module Spree return render_report_form if params[:report].blank? return respond_to_invalid_parameters unless @report_parameters.valid? - service = report_klass::ReportService.new(@report_parameters, report_renderer_klass) - send_data service.render, filename: service.filename + @report = report_klass::ReportService.new(@report_parameters, report_renderer_klass) + render_report end private @@ -46,10 +47,21 @@ module Spree @report_authorizer = report_klass::ReportAuthorizer.new(spree_current_user) end + def render_report + return render_html_report unless @report.renderer.independent_file? + send_data(@report.render, filename: @report.filename) + end + + def render_html_report + render action: :index + end + def report_renderer_klass case params[:report_format] when "csv" report_klass::Renderers::CsvRenderer + when nil, "", "html" + report_klass::Renderers::HtmlRenderer else raise OpenFoodNetwork::Reports::UnsupportedReportFormatException end diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_report.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/_report.html.haml new file mode 100644 index 0000000000..2877778d08 --- /dev/null +++ b/app/views/spree/admin/reports/enterprise_fee_summary_report/_report.html.haml @@ -0,0 +1,19 @@ +- if @report.present? + %table#enterprise_fee_summary_report.report__table + %thead + %tr + - @report.renderer.header.each do |heading| + %th= heading + + %tbody + - @report.renderer.data_rows.each do |row| + %tr + - row.each do |cell_value| + %td= cell_value + + - if @report.renderer.data_rows.empty? + %tr + %td{colspan: @report.renderer.header.length}= t(:none) +- else + %p.report__message + = t(".select_and_search") diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml index 790853ca1f..6d8f0c79ab 100644 --- a/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml +++ b/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml @@ -1 +1,2 @@ = render "filters" += render "report" diff --git a/config/locales/en.yml b/config/locales/en.yml index 19eede5447..1a0ae8547a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2710,6 +2710,16 @@ See the %{link} to find out more about %{sitename}'s features and to start using fee_calculated_on_transfer_through_name: "Fee Calc on Transfer Through" tax_category_name: "Tax Category" total_amount: "$$ SUM" + html: + header: + fee_type: "Fee Type" + enterprise_name: "Enterprise Owner" + fee_name: "Fee Name" + customer_name: "Customer" + fee_placement: "Fee Placement" + fee_calculated_on_transfer_through_name: "Fee Calc on Transfer Through" + tax_category_name: "Tax Category" + total_amount: "$$ SUM" invalid_filter_parameters: "The filters you selected for this report are invalid." spree: @@ -2835,6 +2845,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using date_range: "Date Range" report_format_csv: "Download as CSV" generate_report: "Generate Report" + report: + none: "None" + select_and_search: "Select filters and click on GENERATE REPORT to access your data." users: index: listing_users: "Listing Users" diff --git a/lib/open_food_network/reports/renderers/base.rb b/lib/open_food_network/reports/renderers/base.rb index be720366a6..6a60b94c46 100644 --- a/lib/open_food_network/reports/renderers/base.rb +++ b/lib/open_food_network/reports/renderers/base.rb @@ -7,6 +7,10 @@ module OpenFoodNetwork def initialize(report_data) @report_data = report_data end + + def independent_file? + false + end end end end diff --git a/lib/open_food_network/reports/renderers/independent_file.rb b/lib/open_food_network/reports/renderers/independent_file.rb new file mode 100644 index 0000000000..3c5322a04e --- /dev/null +++ b/lib/open_food_network/reports/renderers/independent_file.rb @@ -0,0 +1,11 @@ +module OpenFoodNetwork + module Reports + module Renderers + module IndependentFile + def independent_file? + true + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb index 388cac91eb..9af42fbfc4 100644 --- a/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb @@ -1,10 +1,13 @@ require "open_food_network/reports/renderers/base" +require "open_food_network/reports/renderers/independent_file" module OrderManagement module Reports module EnterpriseFeeSummary module Renderers class CsvRenderer < OpenFoodNetwork::Reports::Renderers::Base + include OpenFoodNetwork::Reports::Renderers::IndependentFile + def render CSV.generate do |csv| render_header(csv) diff --git a/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb new file mode 100644 index 0000000000..1a280ba667 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb @@ -0,0 +1,48 @@ +require "open_food_network/reports/renderers/base" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module Renderers + class HtmlRenderer < OpenFoodNetwork::Reports::Renderers::Base + def header + data_row_attributes.map do |attribute| + header_label(attribute) + end + end + + def data_rows + report_data.enterprise_fee_type_totals.list.map do |data| + data_row_attributes.map do |attribute| + data.public_send(attribute) + end + end + end + + private + + def data_row_attributes + [ + :fee_type, + :enterprise_name, + :fee_name, + :customer_name, + :fee_placement, + :fee_calculated_on_transfer_through_name, + :tax_category_name, + :total_amount + ] + end + + def header_label(attribute) + I18n.t("header.#{attribute}", scope: i18n_scope) + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary.formats.csv" + end + end + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/lib/order_management/reports/enterprise_fee_summary/report_service.rb index 953bea06ee..43f193e804 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -24,12 +24,12 @@ module OrderManagement ReportData::EnterpriseFeeTypeTotals.new(list: enterprise_fee_type_total_list.sort) end - private - def renderer @renderer ||= renderer_klass.new(self) end + private + def enterprise_fee_type_total_list enterprise_fees_by_customer.map do |total_data| summarizer = EnterpriseFeeTypeTotalSummarizer.new(total_data) diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb new file mode 100644 index 0000000000..8afc535c1d --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -0,0 +1,71 @@ +require "spec_helper" + +require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/report_service" +require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" + +describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do + let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + + let!(:parameters) { report_klass::Parameters.new } + let!(:service) { report_klass::ReportService.new(parameters, described_class) } + + let!(:enterprise_fee_type_totals) do + instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new + instance.tap do |totals| + totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + fee_type: "Fee Type A", + enterprise_name: "Enterprise A", + fee_name: "Fee A", + customer_name: "Custoemr A", + fee_placement: "Fee Placement A", + fee_calculated_on_transfer_through_name: "Transfer Enterprise A", + tax_category_name: "Tax Category A", + total_amount: "1.00" + ) + + totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + fee_type: "Fee Type B", + enterprise_name: "Enterprise B", + fee_name: "Fee C", + customer_name: "Custoemr D", + fee_placement: "Fee Placement E", + fee_calculated_on_transfer_through_name: "Transfer Enterprise F", + tax_category_name: "Tax Category G", + total_amount: "2.00" + ) + end + end + + before do + allow(service).to receive(:enterprise_fee_type_totals) { enterprise_fee_type_totals } + end + + it "generates header values" do + header_row = service.renderer.header + + # Test all header cells have values + expect(header_row.length).to eq(8) + expect(header_row.all?(&:present?)).to be_truthy + end + + it "generates data rows" do + header_row = service.renderer.header + result = service.renderer.data_rows + + expect(result.length).to eq(2) + + # Test random cells + expect(result[0][header_row.index(i18n_translate("header.fee_type"))]).to eq("Fee Type A") + expect(result[0][header_row.index(i18n_translate("header.total_amount"))]).to eq("1.00") + expect(result[1][header_row.index(i18n_translate("header.total_amount"))]).to eq("2.00") + end + + def i18n_translate(key) + I18n.t(key, scope: i18n_scope) + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary.formats.csv" + end +end From 1273cc085bb5de58f61a1af59103be2cfb382551 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 12 Oct 2018 17:13:03 +0800 Subject: [PATCH 054/108] Include payment fees in enterprise fee summary --- config/locales/en.yml | 2 + .../enterprise_fee_type_total_summarizer.rb | 28 +++++- .../reports/enterprise_fee_summary/scope.rb | 86 +++++++++++++------ .../report_service_spec.rb | 14 ++- 4 files changed, 95 insertions(+), 35 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 1a0ae8547a..a209afda26 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2695,6 +2695,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using enterprise_fee_summary: date_end_before_start_error: "must be after start" fee_calculated_on_transfer_through_all: "All" + fee_type: + payment_method: "Payment Transaction" fee_placements: supplier: "Incoming" distributor: "Outgoing" diff --git a/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb b/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb index e3c5b8683a..0c81ee1f3a 100644 --- a/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb @@ -9,15 +9,27 @@ module OrderManagement end def fee_type - data["fee_type"].capitalize if data["fee_type"] + if for_payment_method? + i18n_translate("fee_type.payment_method") + else + data["fee_type"].try(:capitalize) + end end def enterprise_name - data["enterprise_name"] + if for_payment_method? + data["payment_hub_name"] + else + data["enterprise_name"] + end end def fee_name - data["fee_name"] + if for_payment_method? + data["payment_method_name"] + else + data["fee_name"] + end end def customer_name @@ -25,10 +37,14 @@ module OrderManagement end def fee_placement + return if for_payment_method? + i18n_translate("fee_placements.#{data['placement_enterprise_role']}") end def fee_calculated_on_transfer_through_name + return if for_payment_method? + transfer_through_all_string = i18n_translate("fee_calculated_on_transfer_through_all") data["incoming_exchange_enterprise_name"] || data["outgoing_exchange_enterprise_name"] || @@ -36,6 +52,8 @@ module OrderManagement end def tax_category_name + return if for_payment_method? + data["tax_category_name"] || data["product_tax_category_name"] end @@ -45,6 +63,10 @@ module OrderManagement private + def for_payment_method? + data["payment_method_name"].present? + end + def i18n_translate(translation_key) I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}") end diff --git a/lib/order_management/reports/enterprise_fee_summary/scope.rb b/lib/order_management/reports/enterprise_fee_summary/scope.rb index e6557fd772..c9b160b8b0 100644 --- a/lib/order_management/reports/enterprise_fee_summary/scope.rb +++ b/lib/order_management/reports/enterprise_fee_summary/scope.rb @@ -13,11 +13,12 @@ module OrderManagement protected def setup_default_scope - find_adjustments_for_enterprise_fees + find_adjustments_for_enterprise_fees_and_payment_methods include_adjustment_metadata - include_enterprise_fee_details include_order_details + include_payment_fee_details + include_enterprise_fee_details include_line_item_details include_incoming_exchange_details include_outgoing_exchange_details @@ -26,8 +27,8 @@ module OrderManagement select_attributes end - def find_adjustments_for_enterprise_fees - find_adjustments.for_orders.for_enterprise_fees + def find_adjustments_for_enterprise_fees_and_payment_methods + find_adjustments.for_orders.for_enterprise_fees_and_payment_methods end def find_adjustments @@ -42,9 +43,9 @@ module OrderManagement end end - def for_enterprise_fees + def for_enterprise_fees_and_payment_methods chain_to_scope do - where(originator_type: "EnterpriseFee") + where(originator_type: ["EnterpriseFee", "Spree::PaymentMethod"]) end end @@ -57,6 +58,50 @@ module OrderManagement ) end + # Includes: + # * Order + # * Customer + def include_order_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_orders + ON ( + spree_adjustments.adjustable_type = 'Spree::Order' + AND spree_orders.id = spree_adjustments.adjustable_id + ) + JOIN_STRING + ) + + join_scope("LEFT OUTER JOIN customers ON (customers.id = spree_orders.customer_id)") + end + + # If for payment fee + # + # Includes: + # * Payment method + # * Hub + def include_payment_fee_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_payment_methods + ON ( + spree_adjustments.originator_type = 'Spree::PaymentMethod' + AND spree_payment_methods.id = spree_adjustments.originator_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprises AS payment_hubs + ON ( + spree_payment_methods.id IS NOT NULL + AND payment_hubs.id = spree_orders.distributor_id + ) + JOIN_STRING + ) + end + # Includes: # * Enterprise fee # * Enterprise @@ -87,23 +132,6 @@ module OrderManagement ) end - # Includes: - # * Order - # * Customer - def include_order_details - join_scope( - <<-JOIN_STRING.strip_heredoc - LEFT OUTER JOIN spree_orders - ON ( - spree_adjustments.adjustable_type = 'Spree::Order' - AND spree_orders.id = spree_adjustments.adjustable_id - ) - JOIN_STRING - ) - - join_scope("LEFT OUTER JOIN customers ON (customers.id = spree_orders.customer_id)") - end - # If for line item - Use data only if spree_line_items.id is present # # Includes: @@ -225,6 +253,7 @@ module OrderManagement def group_data chain_to_scope do group("enterprise_fees.id", "enterprises.id", "customers.id", + "spree_payment_methods.id", "payment_hubs.id", "adjustment_metadata.enterprise_role", "spree_tax_categories.id", "product_tax_categories.id", "incoming_exchange_enterprises.id", "outgoing_exchange_enterprises.id") @@ -235,11 +264,12 @@ module OrderManagement chain_to_scope do select( <<-JOIN_STRING.strip_heredoc - SUM(spree_adjustments.amount) AS total_amount, enterprises.name AS enterprise_name, - enterprise_fees.fee_type AS fee_type, customers.name AS customer_name, - customers.email AS customer_email, enterprise_fees.fee_type AS fee_type, - enterprise_fees.name AS fee_name, spree_tax_categories.name AS tax_category_name, - product_tax_categories.name AS product_tax_category_name, + SUM(spree_adjustments.amount) AS total_amount, spree_payment_methods.name AS + payment_method_name, payment_hubs.name AS payment_hub_name, enterprises.name AS + enterprise_name, enterprise_fees.fee_type AS fee_type, customers.name AS + customer_name, customers.email AS customer_email, enterprise_fees.fee_type AS + fee_type, enterprise_fees.name AS fee_name, spree_tax_categories.name AS + tax_category_name, product_tax_categories.name AS product_tax_category_name, adjustment_metadata.enterprise_role AS placement_enterprise_role, incoming_exchange_enterprises.name AS incoming_exchange_enterprise_name, outgoing_exchange_enterprises.name AS outgoing_exchange_enterprise_name diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 31d1262ca7..9f9345b276 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -5,7 +5,9 @@ require "order_management/reports/enterprise_fee_summary/parameters" describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:shipping_method) { create(:shipping_method) } - let!(:payment_method) { create(:payment_method) } + let!(:payment_method) do + create(:payment_method, name: "Sample Payment Method", calculator: per_item_calculator(2.0)) + end let!(:distributor) do create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| @@ -91,7 +93,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do totals = service.enterprise_fee_type_totals - expect(totals.list.length).to eq(12) + expect(totals.list.length).to eq(14) # Data is sorted by the following, in order: # * fee_type @@ -112,8 +114,12 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Another Customer", - "Coordinator", "All", "Sample Product Tax", "1024.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", + "Another Customer", nil, nil, nil, "2.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", + "Sample Customer", nil, nil, nil, "4.00"], + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", + "Another Customer", "Coordinator", "All", "Sample Product Tax", "1024.00"], ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", "Coordinator", "All", "Sample Product Tax", "2048.00"], ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Another Customer", From 4a00aceba250ec9f01e49892a34d7e4e11c1c277 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 12 Oct 2018 17:55:48 +0800 Subject: [PATCH 055/108] Include shipping fees in enterprise fee summary --- config/locales/en.yml | 3 ++ .../enterprise_fee_type_total_summarizer.rb | 17 ++++-- .../reports/enterprise_fee_summary/scope.rb | 52 ++++++++++++++----- .../report_service_spec.rb | 25 +++++---- 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index a209afda26..7136e06dab 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2697,10 +2697,13 @@ See the %{link} to find out more about %{sitename}'s features and to start using fee_calculated_on_transfer_through_all: "All" fee_type: payment_method: "Payment Transaction" + shipping_method: "Shipment" fee_placements: supplier: "Incoming" distributor: "Outgoing" coordinator: "Coordinator" + tax_category_name: + shipping_instance_rate: "Platform Rate" formats: csv: header: diff --git a/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb b/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb index 0c81ee1f3a..cd7875f758 100644 --- a/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb @@ -11,6 +11,8 @@ module OrderManagement def fee_type if for_payment_method? i18n_translate("fee_type.payment_method") + elsif for_shipping_method? + i18n_translate("fee_type.shipping_method") else data["fee_type"].try(:capitalize) end @@ -18,7 +20,9 @@ module OrderManagement def enterprise_name if for_payment_method? - data["payment_hub_name"] + data["hub_name"] + elsif for_shipping_method? + data["hub_name"] else data["enterprise_name"] end @@ -27,6 +31,8 @@ module OrderManagement def fee_name if for_payment_method? data["payment_method_name"] + elsif for_shipping_method? + data["shipping_method_name"] else data["fee_name"] end @@ -37,13 +43,13 @@ module OrderManagement end def fee_placement - return if for_payment_method? + return if for_payment_method? || for_shipping_method? i18n_translate("fee_placements.#{data['placement_enterprise_role']}") end def fee_calculated_on_transfer_through_name - return if for_payment_method? + return if for_payment_method? || for_shipping_method? transfer_through_all_string = i18n_translate("fee_calculated_on_transfer_through_all") @@ -53,6 +59,7 @@ module OrderManagement def tax_category_name return if for_payment_method? + return i18n_translate("tax_category_name.shipping_instance_rate") if for_shipping_method? data["tax_category_name"] || data["product_tax_category_name"] end @@ -67,6 +74,10 @@ module OrderManagement data["payment_method_name"].present? end + def for_shipping_method? + data["shipping_method_name"].present? + end + def i18n_translate(translation_key) I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}") end diff --git a/lib/order_management/reports/enterprise_fee_summary/scope.rb b/lib/order_management/reports/enterprise_fee_summary/scope.rb index c9b160b8b0..533536b3b4 100644 --- a/lib/order_management/reports/enterprise_fee_summary/scope.rb +++ b/lib/order_management/reports/enterprise_fee_summary/scope.rb @@ -13,11 +13,12 @@ module OrderManagement protected def setup_default_scope - find_adjustments_for_enterprise_fees_and_payment_methods + find_supported_adjustments include_adjustment_metadata include_order_details include_payment_fee_details + include_shipping_fee_details include_enterprise_fee_details include_line_item_details include_incoming_exchange_details @@ -27,8 +28,8 @@ module OrderManagement select_attributes end - def find_adjustments_for_enterprise_fees_and_payment_methods - find_adjustments.for_orders.for_enterprise_fees_and_payment_methods + def find_supported_adjustments + find_adjustments.for_orders.for_supported_adjustments end def find_adjustments @@ -43,9 +44,10 @@ module OrderManagement end end - def for_enterprise_fees_and_payment_methods + def for_supported_adjustments chain_to_scope do - where(originator_type: ["EnterpriseFee", "Spree::PaymentMethod"]) + where(originator_type: ["EnterpriseFee", "Spree::PaymentMethod", + "Spree::ShippingMethod"]) end end @@ -61,6 +63,7 @@ module OrderManagement # Includes: # * Order # * Customer + # * Hub def include_order_details join_scope( <<-JOIN_STRING.strip_heredoc @@ -73,13 +76,19 @@ module OrderManagement ) join_scope("LEFT OUTER JOIN customers ON (customers.id = spree_orders.customer_id)") + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprises AS hubs + ON (hubs.id = spree_orders.distributor_id) + JOIN_STRING + ) end # If for payment fee # # Includes: # * Payment method - # * Hub def include_payment_fee_details join_scope( <<-JOIN_STRING.strip_heredoc @@ -102,6 +111,22 @@ module OrderManagement ) end + # If for shipping fee + # + # Includes: + # * Shipping method + def include_shipping_fee_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_shipping_methods + ON ( + spree_adjustments.originator_type = 'Spree::ShippingMethod' + AND spree_shipping_methods.id = spree_adjustments.originator_id + ) + JOIN_STRING + ) + end + # Includes: # * Enterprise fee # * Enterprise @@ -252,8 +277,8 @@ module OrderManagement def group_data chain_to_scope do - group("enterprise_fees.id", "enterprises.id", "customers.id", - "spree_payment_methods.id", "payment_hubs.id", + group("enterprise_fees.id", "enterprises.id", "customers.id", "hubs.id", + "spree_payment_methods.id", "spree_shipping_methods.id", "adjustment_metadata.enterprise_role", "spree_tax_categories.id", "product_tax_categories.id", "incoming_exchange_enterprises.id", "outgoing_exchange_enterprises.id") @@ -265,11 +290,12 @@ module OrderManagement select( <<-JOIN_STRING.strip_heredoc SUM(spree_adjustments.amount) AS total_amount, spree_payment_methods.name AS - payment_method_name, payment_hubs.name AS payment_hub_name, enterprises.name AS - enterprise_name, enterprise_fees.fee_type AS fee_type, customers.name AS - customer_name, customers.email AS customer_email, enterprise_fees.fee_type AS - fee_type, enterprise_fees.name AS fee_name, spree_tax_categories.name AS - tax_category_name, product_tax_categories.name AS product_tax_category_name, + payment_method_name, spree_shipping_methods.name AS shipping_method_name, + hubs.name AS hub_name, enterprises.name AS enterprise_name, + enterprise_fees.fee_type AS fee_type, customers.name AS customer_name, + customers.email AS customer_email, enterprise_fees.fee_type AS fee_type, + enterprise_fees.name AS fee_name, spree_tax_categories.name AS tax_category_name, + product_tax_categories.name AS product_tax_category_name, adjustment_metadata.enterprise_role AS placement_enterprise_role, incoming_exchange_enterprises.name AS incoming_exchange_enterprise_name, outgoing_exchange_enterprises.name AS outgoing_exchange_enterprise_name diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 9f9345b276..e9e253170a 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -4,7 +4,10 @@ require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/parameters" describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do - let!(:shipping_method) { create(:shipping_method) } + let!(:shipping_method) do + create(:shipping_method, name: "Sample Shipping Method", calculator: per_item_calculator(1.0)) + end + let!(:payment_method) do create(:payment_method, name: "Sample Payment Method", calculator: per_item_calculator(2.0)) end @@ -93,7 +96,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do totals = service.enterprise_fee_type_totals - expect(totals.list.length).to eq(14) + expect(totals.list.length).to eq(16) # Data is sorted by the following, in order: # * fee_type @@ -114,12 +117,12 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", - "Another Customer", nil, nil, nil, "2.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", - "Sample Customer", nil, nil, nil, "4.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", - "Another Customer", "Coordinator", "All", "Sample Product Tax", "1024.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", + nil, nil, nil, "2.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "4.00"], + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Another Customer", + "Coordinator", "All", "Sample Product Tax", "1024.00"], ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", "Coordinator", "All", "Sample Product Tax", "2048.00"], ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Another Customer", @@ -133,7 +136,11 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do ["Sales", "Sample Producer", "Included Producer Fee 2", "Another Customer", "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], ["Sales", "Sample Producer", "Included Producer Fee 2", "Sample Customer", - "Incoming", "Sample Producer", "Sample Product Tax", "256.00"] + "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", + nil, nil, "Platform Rate", "1.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "2.00"] ] expected_result.each_with_index do |expected_attributes, row_index| From d573dc5023ad3ead188f5ceeaf5a487e444d5a27 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 14 Oct 2018 11:24:43 +0800 Subject: [PATCH 056/108] Make order builder for enterprise fee summary flexible This is in preparation for tests for filtering the report. --- .../report_service_spec.rb | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index e9e253170a..df9b009ff0 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -82,13 +82,11 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end let!(:customer) { create(:customer, name: "Sample Customer") } - let!(:another_customer) { create(:customer, name: "Another Customer") } - - let!(:customer_order) { prepare_completed_order(customer) } - let!(:second_customer_order) { prepare_completed_order(customer) } + let!(:customer_order) { prepare_completed_order(customer: customer) } + let!(:second_customer_order) { prepare_completed_order(customer: customer) } let!(:another_customer) { create(:customer, name: "Another Customer") } - let!(:other_customer_order) { prepare_completed_order(another_customer) } + let!(:other_customer_order) { prepare_completed_order(customer: another_customer) } it "groups and sorts entries correctly" do parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new @@ -162,20 +160,29 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do create(:tax_category, name: name) end - def prepare_completed_order(customer) - order = create(:order, customer: customer, distributor: distributor, order_cycle: order_cycle, - shipping_method: shipping_method) - create(:line_item, order: order, variant: variant) - - complete_order(order) - - order.reload + def default_order_options + { customer: customer, distributor: distributor, order_cycle: order_cycle, + shipping_method: shipping_method } end - def complete_order(order) + def prepare_completed_order(options = {}) + target = default_order_options.merge(options) + + create(:order, customer: target[:customer], distributor: target[:distributor], + order_cycle: target[:order_cycle], + shipping_method: target[:shipping_method]).tap do |order| + create(:line_item, order: order, variant: options[:variant] || variant) + + complete_order(order, options) + + order.reload + end + end + + def complete_order(order, options) order.create_shipment! create(:payment, state: "checkout", order: order, amount: order.total, - payment_method: payment_method) + payment_method: options[:payment_method] || payment_method) order.update_distribution_charge! while !order.completed? do break unless order.next! end end From 2407531957a208b49828bb4b5c686274695c5252 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 15 Oct 2018 09:51:53 +0800 Subject: [PATCH 057/108] Make more code for report tests reusable --- .../report_service_spec.rb | 161 ++++++++++-------- 1 file changed, 87 insertions(+), 74 deletions(-) diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index df9b009ff0..14a7b49929 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -65,84 +65,76 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end let!(:product) { create(:product, tax_category: prepare_tax_category("Sample Product Tax")) } - let!(:variant) { create(:variant, product: product, is_master: false) } - let!(:incoming_exchange) do - create(:exchange, order_cycle: order_cycle, incoming: true, sender: producer, - receiver: coordinator, variants: [variant]).tap do |exchange| - exchange.enterprise_fees << producer_fees[1] - exchange.enterprise_fees << producer_fees[2] - end - end - let!(:outgoing_exchange) do - create(:exchange, order_cycle: order_cycle, incoming: false, sender: coordinator, - receiver: distributor, variants: [variant]).tap do |exchange| - exchange.enterprise_fees << distributor_fees[0] - exchange.enterprise_fees << distributor_fees[1] - end + + let!(:variant) do + prepare_variant(incoming_exchange_fees: [producer_fees[1], producer_fees[2]], + outgoing_exchange_fees: [distributor_fees[0], distributor_fees[1]]) end let!(:customer) { create(:customer, name: "Sample Customer") } - let!(:customer_order) { prepare_completed_order(customer: customer) } - let!(:second_customer_order) { prepare_completed_order(customer: customer) } - let!(:another_customer) { create(:customer, name: "Another Customer") } - let!(:other_customer_order) { prepare_completed_order(customer: another_customer) } - it "groups and sorts entries correctly" do - parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new - service = described_class.new(parameters, nil) + describe "grouping and sorting of entries" do + let!(:customer_order) { prepare_completed_order(customer: customer) } + let!(:second_customer_order) { prepare_completed_order(customer: customer) } + let!(:other_customer_order) { prepare_completed_order(customer: another_customer) } - totals = service.enterprise_fee_type_totals + let(:parameters) { OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new } + let(:service) { described_class.new(parameters, nil) } - expect(totals.list.length).to eq(16) + it "groups and sorts entries correctly" do + totals = service.enterprise_fee_type_totals - # Data is sorted by the following, in order: - # * fee_type - # * enterprise_name - # * fee_name - # * customer_name - # * fee_placement - # * fee_calculated_on_transfer_through_name - # * tax_category_name - # * total_amount + expect(totals.list.length).to eq(16) - expected_result = [ - ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Another Customer", - "Coordinator", "All", "Sample Coordinator Tax", "512.00"], - ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Sample Customer", - "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], - ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Another Customer", - "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], - ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", - "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", - nil, nil, nil, "2.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", - nil, nil, nil, "4.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Another Customer", - "Coordinator", "All", "Sample Product Tax", "1024.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", - "Coordinator", "All", "Sample Product Tax", "2048.00"], - ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Another Customer", - "Outgoing", "Sample Coordinator", "Sample Product Tax", "8.00"], - ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Sample Customer", - "Outgoing", "Sample Coordinator", "Sample Product Tax", "16.00"], - ["Sales", "Sample Producer", "Included Producer Fee 1", "Another Customer", - "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], - ["Sales", "Sample Producer", "Included Producer Fee 1", "Sample Customer", - "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], - ["Sales", "Sample Producer", "Included Producer Fee 2", "Another Customer", - "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], - ["Sales", "Sample Producer", "Included Producer Fee 2", "Sample Customer", - "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", - nil, nil, "Platform Rate", "1.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "2.00"] - ] + # Data is sorted by the following, in order: + # * fee_type + # * enterprise_name + # * fee_name + # * customer_name + # * fee_placement + # * fee_calculated_on_transfer_through_name + # * tax_category_name + # * total_amount - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals.list[row_index], expected_attributes) + expected_result = [ + ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Another Customer", + "Coordinator", "All", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Sample Customer", + "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], + ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Another Customer", + "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], + ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", + nil, nil, nil, "2.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "4.00"], + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Another Customer", + "Coordinator", "All", "Sample Product Tax", "1024.00"], + ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", + "Coordinator", "All", "Sample Product Tax", "2048.00"], + ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Another Customer", + "Outgoing", "Sample Coordinator", "Sample Product Tax", "8.00"], + ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Product Tax", "16.00"], + ["Sales", "Sample Producer", "Included Producer Fee 1", "Another Customer", + "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], + ["Sales", "Sample Producer", "Included Producer Fee 1", "Sample Customer", + "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], + ["Sales", "Sample Producer", "Included Producer Fee 2", "Another Customer", + "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], + ["Sales", "Sample Producer", "Included Producer Fee 2", "Sample Customer", + "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", + nil, nil, "Platform Rate", "1.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "2.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals.list[row_index], expected_attributes) + end end end @@ -162,23 +154,26 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do def default_order_options { customer: customer, distributor: distributor, order_cycle: order_cycle, - shipping_method: shipping_method } + shipping_method: shipping_method, variant: variant } end - def prepare_completed_order(options = {}) + def prepare_order(options = {}) target = default_order_options.merge(options) create(:order, customer: target[:customer], distributor: target[:distributor], order_cycle: target[:order_cycle], shipping_method: target[:shipping_method]).tap do |order| - create(:line_item, order: order, variant: options[:variant] || variant) - - complete_order(order, options) - + create(:line_item, order: order, variant: target[:variant]) order.reload end end + def prepare_completed_order(options = {}) + order = prepare_order(options) + complete_order(order, options) + order.reload + end + def complete_order(order, options) order.create_shipment! create(:payment, state: "checkout", order: order, amount: order.total, @@ -187,6 +182,24 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do while !order.completed? do break unless order.next! end end + def prepare_variant(options = {}) + variant = create(:variant, product: product, is_master: false) + exchange = create(:exchange, incoming: true, order_cycle: order_cycle, sender: producer, + receiver: coordinator, variants: [variant]) + attach_enterprise_fees(exchange, options[:incoming_exchange_fees] || []) + + exchange = create(:exchange, incoming: false, order_cycle: order_cycle, sender: coordinator, + receiver: distributor, variants: [variant]) + attach_enterprise_fees(exchange, options[:outgoing_exchange_fees] || []) + variant + end + + def attach_enterprise_fees(exchange, enterprise_fees) + enterprise_fees.each do |enterprise_fee| + exchange.enterprise_fees << enterprise_fee + end + end + def per_item_calculator(amount) Spree::Calculator::PerItem.new(preferred_amount: amount) end From 24ab2e7fb0a3be206996bb954a22439dadfff458 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 14 Oct 2018 17:35:28 +0800 Subject: [PATCH 058/108] Add filtering to enterprise fee summary --- .../enterprise_fee_summary/report_service.rb | 2 +- .../reports/enterprise_fee_summary/scope.rb | 43 ++- .../report_service_spec.rb | 301 +++++++++++++++++- 3 files changed, 329 insertions(+), 17 deletions(-) diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/lib/order_management/reports/enterprise_fee_summary/report_service.rb index 43f193e804..3b57be7b6c 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -17,7 +17,7 @@ module OrderManagement end def enterprise_fees_by_customer - Scope.new.all + Scope.new.apply_filters(parameters).result end def enterprise_fee_type_totals diff --git a/lib/order_management/reports/enterprise_fee_summary/scope.rb b/lib/order_management/reports/enterprise_fee_summary/scope.rb index 533536b3b4..b218c1f677 100644 --- a/lib/order_management/reports/enterprise_fee_summary/scope.rb +++ b/lib/order_management/reports/enterprise_fee_summary/scope.rb @@ -2,11 +2,22 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Scope + attr_accessor :parameters + def initialize setup_default_scope end - def all + def apply_filters(params) + filter_by_date(params) + filter_by_distribution(params) + filter_by_fee(params) + + self + end + + def result + group_data.select_attributes @scope.all end @@ -275,6 +286,30 @@ module OrderManagement ) end + def filter_by_date(params) + filter_scope("spree_orders.completed_at >= ?", params.start_at) \ + if params.start_at.present? + filter_scope("spree_orders.completed_at <= ?", params.end_at) if params.end_at.present? + end + + def filter_by_distribution(params) + filter_scope(spree_orders: { distributor_id: params.distributor_ids }) \ + if params.distributor_ids.present? + filter_scope(spree_products: { supplier_id: params.producer_ids }) \ + if params.producer_ids.present? + filter_scope(spree_orders: { order_cycle_id: params.order_cycle_ids }) \ + if params.order_cycle_ids.present? + end + + def filter_by_fee(params) + filter_scope(enterprise_fees: { id: params.enterprise_fee_ids }) \ + if params.enterprise_fee_ids.present? + filter_scope(spree_shipping_methods: { id: params.shipping_method_ids }) \ + if params.shipping_method_ids.present? + filter_scope(spree_payment_methods: { id: params.payment_method_ids }) \ + if params.payment_method_ids.present? + end + def group_data chain_to_scope do group("enterprise_fees.id", "enterprises.id", "customers.id", "hubs.id", @@ -314,6 +349,12 @@ module OrderManagement joins(join_string) end end + + def filter_scope(*args) + chain_to_scope do + where(*args) + end + end end end end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 14a7b49929..94effc1f11 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -4,6 +4,8 @@ require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/parameters" describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do + let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + let!(:shipping_method) do create(:shipping_method, name: "Sample Shipping Method", calculator: per_item_calculator(1.0)) end @@ -75,9 +77,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:another_customer) { create(:customer, name: "Another Customer") } describe "grouping and sorting of entries" do - let!(:customer_order) { prepare_completed_order(customer: customer) } - let!(:second_customer_order) { prepare_completed_order(customer: customer) } - let!(:other_customer_order) { prepare_completed_order(customer: another_customer) } + let!(:customer_order) { prepare_order(customer: customer) } + let!(:second_customer_order) { prepare_order(customer: customer) } + let!(:other_customer_order) { prepare_order(customer: another_customer) } let(:parameters) { OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new } let(:service) { described_class.new(parameters, nil) } @@ -138,6 +140,241 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end end + describe "filters entries correctly" do + let(:parameters) { report_klass::Parameters.new(parameters_attributes) } + let(:service) { described_class.new(parameters, nil) } + + context "filtering by completion date" do + let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } + + let!(:customer_a) { create(:customer, name: "Customer A") } + let!(:customer_b) { create(:customer, name: "Customer B") } + let!(:customer_c) { create(:customer, name: "Customer C") } + + let!(:order_placed_before_timestamp) do + prepare_order(customer: customer_a).tap do |order| + order.update_column(:completed_at, timestamp - 1.second) + end + end + + let!(:order_placed_during_timestamp) do + prepare_order(customer: customer_b).tap do |order| + order.update_column(:completed_at, timestamp) + end + end + + let!(:order_placed_after_timestamp) do + prepare_order(customer: customer_c).tap do |order| + order.update_column(:completed_at, timestamp + 1.second) + end + end + + context "on or after start_at" do + let(:parameters_attributes) { { start_at: timestamp } } + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer A") + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer C") + end + end + + context "on or before end_at" do + let(:parameters_attributes) { { end_at: timestamp } } + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer A") + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") + expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer C") + end + end + end + + describe "for specified shops" do + let!(:distributor_a) do + create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_b) do + create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_c) do + create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + + let!(:order_a) { prepare_order(distributor: distributor_a) } + let!(:order_b) { prepare_order(distributor: distributor_b) } + let!(:order_c) { prepare_order(distributor: distributor_c) } + + let(:parameters_attributes) { { distributor_ids: [distributor_a.id, distributor_b.id] } } + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") + expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") + end + end + + describe "for specified suppliers" do + let!(:producer_a) { create(:supplier_enterprise, name: "Producer A") } + let!(:producer_b) { create(:supplier_enterprise, name: "Producer B") } + let!(:producer_c) { create(:supplier_enterprise, name: "Producer C") } + + let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: producer_a) } + let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: producer_b) } + let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: producer_c) } + + let!(:product_a) { create(:product, supplier: producer_a) } + let!(:product_b) { create(:product, supplier: producer_b) } + let!(:product_c) { create(:product, supplier: producer_c) } + + let!(:variant_a) do + prepare_variant(product: product_a, producer: producer_a, incoming_exchange_fees: [fee_a]) + end + let!(:variant_b) do + prepare_variant(product: product_b, producer: producer_b, incoming_exchange_fees: [fee_b]) + end + let!(:variant_c) do + prepare_variant(product: product_c, producer: producer_c, incoming_exchange_fees: [fee_c]) + end + + let!(:order_a) { prepare_order(variant: variant_a) } + let!(:order_b) { prepare_order(variant: variant_b) } + let!(:order_c) { prepare_order(variant: variant_c) } + + let(:parameters_attributes) { { producer_ids: [producer_a.id, producer_b.id] } } + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_name: "Fee A", enterprise_name: "Producer A") + expect_total_matches(totals, 1, fee_name: "Fee B", enterprise_name: "Producer B") + expect_total_matches(totals, 0, fee_name: "Fee C", enterprise_name: "Producer C") + end + end + + describe "for specified order cycles" do + let!(:distributor_a) do + create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_b) do + create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_c) do + create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + + let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order_cycle_c) { create(:simple_order_cycle, coordinator: coordinator) } + + let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } + let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } + let!(:variant_c) { prepare_variant(distributor: distributor_c, order_cycle: order_cycle_c) } + + let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } + let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } + let!(:order_c) { prepare_order(order_cycle: order_cycle_c, distributor: distributor_c) } + + let(:parameters_attributes) { { order_cycle_ids: [order_cycle_a.id, order_cycle_b.id] } } + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") + expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") + end + end + + describe "for specified enterprise fees" do + let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: distributor) } + let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: distributor) } + let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: distributor) } + + let!(:variant) { prepare_variant(outgoing_exchange_fees: [fee_a, fee_b, fee_c]) } + + let!(:order) { prepare_order(variant: variant) } + + let(:parameters_attributes) { { enterprise_fee_ids: [fee_a.id, fee_b.id] } } + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_name: "Fee A") + expect_total_matches(totals, 1, fee_name: "Fee B") + expect_total_matches(totals, 0, fee_name: "Fee C") + end + end + + describe "for specified shipping methods" do + let!(:shipping_method_a) do + create(:shipping_method, name: "Shipping A", distributors: [distributor]) + end + let!(:shipping_method_b) do + create(:shipping_method, name: "Shipping B", distributors: [distributor]) + end + let!(:shipping_method_c) do + create(:shipping_method, name: "Shipping C", distributors: [distributor]) + end + + let!(:order_a) { prepare_order(shipping_method: shipping_method_a) } + let!(:order_b) { prepare_order(shipping_method: shipping_method_b) } + let!(:order_c) { prepare_order(shipping_method: shipping_method_c) } + + let(:parameters_attributes) do + { shipping_method_ids: [shipping_method_a.id, shipping_method_b.id] } + end + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_name: "Shipping A") + expect_total_matches(totals, 1, fee_name: "Shipping B") + expect_total_matches(totals, 0, fee_name: "Shipping C") + end + end + + describe "for specified payment methods" do + let!(:payment_method_a) do + create(:payment_method, name: "Payment A", distributors: [distributor]) + end + let!(:payment_method_b) do + create(:payment_method, name: "Payment B", distributors: [distributor]) + end + let!(:payment_method_c) do + create(:payment_method, name: "Payment C", distributors: [distributor]) + end + + let!(:order_a) { prepare_order(payment_method: payment_method_a) } + let!(:order_b) { prepare_order(payment_method: payment_method_b) } + let!(:order_c) { prepare_order(payment_method: payment_method_c) } + + let(:parameters_attributes) do + { payment_method_ids: [payment_method_a.id, payment_method_b.id] } + end + + it "filters entries" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_name: "Payment A") + expect_total_matches(totals, 1, fee_name: "Payment B") + expect_total_matches(totals, 0, fee_name: "Payment C") + end + end + end + # Helper methods for example group def expect_total_attributes(total, expected_attribute_list) @@ -148,6 +385,10 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do expect(actual_attribute_list).to eq(expected_attribute_list) end + def expect_total_matches(totals, count, attributes) + expect(count_totals(totals, attributes)).to eq(count) + end + def prepare_tax_category(name) create(:tax_category, name: name) end @@ -157,7 +398,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do shipping_method: shipping_method, variant: variant } end - def prepare_order(options = {}) + def setup_order(options = {}) target = default_order_options.merge(options) create(:order, customer: target[:customer], distributor: target[:distributor], @@ -168,8 +409,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end end - def prepare_completed_order(options = {}) - order = prepare_order(options) + def prepare_order(options = {}) + order = setup_order(options) complete_order(order, options) order.reload end @@ -182,16 +423,38 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do while !order.completed? do break unless order.next! end end - def prepare_variant(options = {}) - variant = create(:variant, product: product, is_master: false) - exchange = create(:exchange, incoming: true, order_cycle: order_cycle, sender: producer, - receiver: coordinator, variants: [variant]) - attach_enterprise_fees(exchange, options[:incoming_exchange_fees] || []) + def default_variant_options + { product: product, producer: producer, coordinator: coordinator, distributor: distributor, + order_cycle: order_cycle } + end - exchange = create(:exchange, incoming: false, order_cycle: order_cycle, sender: coordinator, - receiver: distributor, variants: [variant]) - attach_enterprise_fees(exchange, options[:outgoing_exchange_fees] || []) - variant + def prepare_variant(options = {}) + target = default_variant_options.merge(options) + + create(:variant, product: target[:product], is_master: false).tap do |variant| + exchange_options = { producer: target[:producer], coordinator: target[:coordinator], + distributor: target[:distributor], + incoming_exchange_fees: target[:incoming_exchange_fees], + outgoing_exchange_fees: target[:outgoing_exchange_fees] } + setup_exchanges(target[:order_cycle], variant, exchange_options) + end + end + + def setup_exchanges(order_cycle, variant, options) + setup_exchange(order_cycle, variant, true, sender: options[:producer], + receiver: options[:coordinator], + enterprise_fees: options[:incoming_exchange_fees]) + setup_exchange(order_cycle, variant, false, sender: options[:coordinator], + receiver: options[:distributor], + enterprise_fees: options[:outgoing_exchange_fees]) + end + + def setup_exchange(order_cycle, variant, incoming, options) + exchange_attributes = { order_cycle_id: order_cycle.id, incoming: incoming, + sender_id: options[:sender].id, receiver_id: options[:receiver].id } + exchange = Exchange.where(exchange_attributes).first || create(:exchange, exchange_attributes) + exchange.variants << variant + attach_enterprise_fees(exchange, options[:enterprise_fees] || []) end def attach_enterprise_fees(exchange, enterprise_fees) @@ -203,4 +466,12 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do def per_item_calculator(amount) Spree::Calculator::PerItem.new(preferred_amount: amount) end + + def count_totals(totals, attributes) + totals.count do |data| + attributes.all? do |attribute_name, attribute_value| + data.public_send(attribute_name) == attribute_value + end + end + end end From 5fce9d0a7d630c32b577b18d52004a7d836fe768 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 16 Oct 2018 16:47:21 +0800 Subject: [PATCH 059/108] Add multi-distributor scope to shipping and payment methods --- app/models/spree/payment_method_decorator.rb | 5 +++ app/models/spree/shipping_method_decorator.rb | 4 +++ spec/models/spree/payment_method_spec.rb | 19 ++++++++++++ spec/models/spree/shipping_method_spec.rb | 31 +++++++++++++++---- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb index 6eb845895f..6a42f81055 100644 --- a/app/models/spree/payment_method_decorator.rb +++ b/app/models/spree/payment_method_decorator.rb @@ -23,6 +23,11 @@ Spree::PaymentMethod.class_eval do end } + scope :for_distributors, ->(distributors) { + non_unique_matches = unscoped.joins(:distributors).where(enterprises: { id: distributors }) + where(id: non_unique_matches.map(&:id)) + } + scope :for_distributor, lambda { |distributor| joins(:distributors). where('enterprises.id = ?', distributor) diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb index a900f14936..430579bd42 100644 --- a/app/models/spree/shipping_method_decorator.rb +++ b/app/models/spree/shipping_method_decorator.rb @@ -20,6 +20,10 @@ Spree::ShippingMethod.class_eval do end } + scope :for_distributors, ->(distributors) { + non_unique_matches = unscoped.joins(:distributors).where(enterprises: { id: distributors }) + where(id: non_unique_matches.map(&:id)) + } scope :for_distributor, lambda { |distributor| joins(:distributors). where('enterprises.id = ?', distributor) diff --git a/spec/models/spree/payment_method_spec.rb b/spec/models/spree/payment_method_spec.rb index 28707876d3..286c6788f5 100644 --- a/spec/models/spree/payment_method_spec.rb +++ b/spec/models/spree/payment_method_spec.rb @@ -39,5 +39,24 @@ module Spree order.add_variant(product.master) expect(transaction.compute_amount(order)).to eq 2.0 end + + describe "scope" do + describe "filtering to specified distributors" do + let!(:distributor_a) { create(:distributor_enterprise) } + let!(:distributor_b) { create(:distributor_enterprise) } + let!(:distributor_c) { create(:distributor_enterprise) } + + let!(:payment_method_a) { create(:payment_method, distributors: [distributor_a, distributor_b]) } + let!(:payment_method_b) { create(:payment_method, distributors: [distributor_b]) } + let!(:payment_method_c) { create(:payment_method, distributors: [distributor_c]) } + + it "includes only unique records under specified distributors" do + result = described_class.for_distributors([distributor_a, distributor_b]) + expect(result.length).to eq(2) + expect(result).to include(payment_method_a) + expect(result).to include(payment_method_b) + end + end + end end end diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index bd759edcc7..dafd6788e4 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -18,13 +18,32 @@ module Spree sm.reload.distributors.should match_array [d1, d2] end - it "finds shipping methods for a particular distributor" do - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - sm1 = create(:shipping_method, distributors: [d1]) - sm2 = create(:shipping_method, distributors: [d2]) + describe "scope" do + describe "filtering to specified distributors" do + let!(:distributor_a) { create(:distributor_enterprise) } + let!(:distributor_b) { create(:distributor_enterprise) } + let!(:distributor_c) { create(:distributor_enterprise) } - ShippingMethod.for_distributor(d1).should == [sm1] + let!(:shipping_method_a) { create(:shipping_method, distributors: [distributor_a, distributor_b]) } + let!(:shipping_method_b) { create(:shipping_method, distributors: [distributor_b]) } + let!(:shipping_method_c) { create(:shipping_method, distributors: [distributor_c]) } + + it "includes only unique records under specified distributors" do + result = described_class.for_distributors([distributor_a, distributor_b]) + expect(result.length).to eq(2) + expect(result).to include(shipping_method_a) + expect(result).to include(shipping_method_b) + end + end + + it "finds shipping methods for a particular distributor" do + d1 = create(:distributor_enterprise) + d2 = create(:distributor_enterprise) + sm1 = create(:shipping_method, distributors: [d1]) + sm2 = create(:shipping_method, distributors: [d2]) + + ShippingMethod.for_distributor(d1).should == [sm1] + end end it "orders shipping methods by name" do From 469988856dd37a2c9f3b48359f7a0540637aed38 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 17 Oct 2018 04:16:53 +0800 Subject: [PATCH 060/108] Define permissions for enterprise fee summary --- .../report_authorizer.rb | 32 ++- .../report_authorizer_spec.rb | 208 ++++++++++++++++++ 2 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb b/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb index 74ce7288b9..f49e28c88d 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb @@ -4,28 +4,42 @@ module OrderManagement module Reports module EnterpriseFeeSummary class ReportAuthorizer < OpenFoodNetwork::Reports::ReportAuthorizer + def allowed_order_cycles + @allowed_order_cycles ||= OrderCycle.accessible_by(user) + end + def allowed_distributors - [] + outgoing_exchanges = Exchange.outgoing.where(order_cycle_id: allowed_order_cycle_ids) + @allowed_distributors ||= Enterprise.where(id: outgoing_exchanges.pluck(:receiver_id)) end def allowed_producers - [] - end - - def allowed_order_cycles - [] + incoming_exchanges = Exchange.incoming.where(order_cycle_id: allowed_order_cycle_ids) + @allowed_producers ||= Enterprise.where(id: incoming_exchanges.pluck(:sender_id)) end def allowed_enterprise_fees - [] + return EnterpriseFee.where("1=0") if allowed_order_cycles.blank? + + coordinator_enterprise_fees = EnterpriseFee.joins(:coordinator_fees) + .where(coordinator_fees: { order_cycle_id: allowed_order_cycle_ids }) + exchange_enterprise_fees = EnterpriseFee.joins(exchange_fees: :exchange) + .where(exchanges: { order_cycle_id: allowed_order_cycle_ids }) + @allowed_enterprise_fees ||= (coordinator_enterprise_fees | exchange_enterprise_fees).uniq end def allowed_shipping_methods - [] + @allowed_shipping_methods ||= Spree::ShippingMethod.for_distributors(allowed_distributors) end def allowed_payment_methods - [] + @allowed_payment_methods ||= Spree::PaymentMethod.for_distributors(allowed_distributors) + end + + private + + def allowed_order_cycle_ids + @allowed_order_cycle_ids ||= allowed_order_cycles.map(&:id) end end end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb new file mode 100644 index 0000000000..33c02ce76e --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb @@ -0,0 +1,208 @@ +require "spec_helper" + +require "order_management/reports/enterprise_fee_summary/report_authorizer" + +describe OrderManagement::Reports::EnterpriseFeeSummary::ReportAuthorizer do + let!(:order_cycle) { create(:simple_order_cycle) } + let!(:incoming_exchange) { create(:exchange, incoming: true, order_cycle: order_cycle) } + let!(:outgoing_exchange) { create(:exchange, incoming: false, order_cycle: order_cycle) } + + # The factory for order cycle uses the first distributor it finds in the database, if it exists. + # However, for this example group, we need to make sure that the coordinator for the second order + # cycle is not the same as the one in the first. + let!(:another_coordinator) { create(:distributor_enterprise) } + + let!(:another_order_cycle) { create(:simple_order_cycle, coordinator: another_coordinator) } + let!(:another_incoming_exchange) do + create(:exchange, incoming: true, order_cycle: another_order_cycle) + end + let!(:another_outgoing_exchange) do + create(:exchange, incoming: false, order_cycle: another_order_cycle) + end + + describe "permissions for order cycles" do + it "allows admin" do + user = create(:admin_user) + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).to include(order_cycle) + end + + it "allows coordinator of the order cycle" do + user = order_cycle.coordinator.owner + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).to include(order_cycle) + end + + it "allows sender of incoming exchange" do + user = incoming_exchange.sender.owner + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).to include(order_cycle) + end + + it "allows receiver of outgoing exchange" do + user = outgoing_exchange.receiver.owner + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).to include(order_cycle) + end + + it "does not allow coordinator of another order cycle" do + user = another_order_cycle.coordinator.owner + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).not_to include(order_cycle) + end + + it "does not allow sender of incoming exchange of another order cycle" do + user = another_incoming_exchange.sender.owner + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).not_to include(order_cycle) + end + + it "does not allow receiver of outgoing exchange of another order cycle" do + user = another_outgoing_exchange.receiver.owner + authorizer = described_class.new(user) + expect(authorizer.allowed_order_cycles).not_to include(order_cycle) + end + end + + describe "permissions for properties related to the order cycle" do + let(:user) { create(:user) } + let(:authorizer) do + described_class.new(user).tap do |instance| + allow(instance).to receive(:allowed_order_cycles) { [order_cycle] } + end + end + + describe "allowed distributors" do + it "includes distributor of allowed order cycle" do + expect(authorizer.allowed_distributors).to include(outgoing_exchange.receiver) + end + + it "does not include distributor of order cycle that is not allowed" do + expect(authorizer.allowed_distributors).not_to include(another_outgoing_exchange.receiver) + end + end + + describe "allowed producers" do + it "includes supplier of allowed order cycle" do + expect(authorizer.allowed_producers).to include(incoming_exchange.sender) + end + + it "does not include supplier of order cycle that is not allowed" do + expect(authorizer.allowed_producers).not_to include(another_incoming_exchange.sender) + end + end + + describe "allowed enterprise fees" do + context "when coordinator fee for order cycle" do + let!(:coordinator_fee) do + create(:enterprise_fee, enterprise: order_cycle.coordinator).tap do |fee| + order_cycle.coordinator_fees << fee + end + end + + let!(:another_coordinator_fee) do + create(:enterprise_fee, enterprise: another_order_cycle.coordinator).tap do |fee| + another_order_cycle.coordinator_fees << fee + end + end + + it "includes enterprise fee in allowed order cycle" do + expect(authorizer.allowed_enterprise_fees).to include(coordinator_fee) + end + + it "does not include enterprise fee in order cycle that is not allowed" do + expect(authorizer.allowed_enterprise_fees).not_to include(another_coordinator_fee) + end + end + + context "when enterprise fee for incoming exchange" do + let!(:exchange_fee) do + create(:enterprise_fee, enterprise: incoming_exchange.sender).tap do |fee| + incoming_exchange.enterprise_fees << fee + end + end + + let!(:another_exchange_fee) do + create(:enterprise_fee, enterprise: another_incoming_exchange.sender).tap do |fee| + another_incoming_exchange.enterprise_fees << fee + end + end + + it "includes enterprise fee in allowed order cycle" do + expect(authorizer.allowed_enterprise_fees).to include(exchange_fee) + end + + it "does not include enterprise fee in order cycle that is not allowed" do + expect(authorizer.allowed_enterprise_fees).not_to include(another_exchange_fee) + end + end + + context "when enterprise fee for outgoing exchange" do + let!(:exchange_fee) do + create(:enterprise_fee, enterprise: outgoing_exchange.receiver).tap do |fee| + outgoing_exchange.enterprise_fees << fee + end + end + + let!(:another_exchange_fee) do + create(:enterprise_fee, enterprise: another_outgoing_exchange.receiver).tap do |fee| + another_outgoing_exchange.enterprise_fees << fee + end + end + + it "includes enterprise fee in allowed order cycle" do + expect(authorizer.allowed_enterprise_fees).to include(exchange_fee) + end + + it "does not include enterprise fee in order cycle that is not allowed" do + expect(authorizer.allowed_enterprise_fees).not_to include(another_exchange_fee) + end + end + end + + describe "allowed shipping methods" do + it "includes shipping methods of distributors in allowed order cycle" do + shipping_method = create(:shipping_method, distributors: [outgoing_exchange.receiver]) + expect(authorizer.allowed_shipping_methods).to include(shipping_method) + end + + it "does not include shipping methods of suppliers in allowed order cycle" do + shipping_method = create(:shipping_method, distributors: [incoming_exchange.sender]) + expect(authorizer.allowed_shipping_methods).not_to include(shipping_method) + end + + it "does not include shipping methods of coordinator of allowed order cycle" do + shipping_method = create(:shipping_method, distributors: [order_cycle.coordinator]) + expect(authorizer.allowed_shipping_methods).not_to include(shipping_method) + end + + it "does not include shipping methods of distributors in order cycle that is not allowed" do + shipping_method = create(:shipping_method, + distributors: [another_outgoing_exchange.receiver]) + expect(authorizer.allowed_shipping_methods).not_to include(shipping_method) + end + end + + describe "allowed payment methods" do + it "includes payment methods of distributors in allowed order cycle" do + payment_method = create(:payment_method, distributors: [outgoing_exchange.receiver]) + expect(authorizer.allowed_payment_methods).to include(payment_method) + end + + it "does not include payment methods of suppliers in allowed order cycle" do + payment_method = create(:payment_method, distributors: [incoming_exchange.sender]) + expect(authorizer.allowed_payment_methods).not_to include(payment_method) + end + + it "does not include payment methods of coordinator of allowed order cycle" do + payment_method = create(:payment_method, distributors: [order_cycle.coordinator]) + expect(authorizer.allowed_payment_methods).not_to include(payment_method) + end + + it "does not include payment methods of distributors in order cycle that is not allowed" do + payment_method = create(:payment_method, distributors: [another_outgoing_exchange.receiver]) + expect(authorizer.allowed_payment_methods).not_to include(payment_method) + end + end + end +end From cd3e258b5947bac578eeb14650c3dab7bfd37da5 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sat, 20 Oct 2018 22:43:28 +0800 Subject: [PATCH 061/108] Rename report authorizers to reflect permissions --- .../enterprise_fee_summary_report_controller.rb | 8 ++++---- .../enterprise_fee_summary_report/_filters.html.haml | 12 ++++++------ .../reports/{report_authorizer.rb => permissions.rb} | 2 +- .../{report_authorizer.rb => permissions.rb} | 4 ++-- ...report_authorizer_spec.rb => permissions_spec.rb} | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) rename lib/open_food_network/reports/{report_authorizer.rb => permissions.rb} (84%) rename lib/order_management/reports/enterprise_fee_summary/{report_authorizer.rb => permissions.rb} (92%) rename spec/lib/order_management/reports/enterprise_fee_summary/{report_authorizer_spec.rb => permissions_spec.rb} (98%) diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb index ce749ce882..f0236c21a3 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -1,6 +1,6 @@ require "open_food_network/reports" require "order_management/reports/enterprise_fee_summary/parameters" -require "order_management/reports/enterprise_fee_summary/report_authorizer" +require "order_management/reports/enterprise_fee_summary/permissions" require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" @@ -10,7 +10,7 @@ module Spree module Reports class EnterpriseFeeSummaryReportController < BaseController before_filter :load_report_parameters, only: [:index] - before_filter :load_report_authorizer, only: [:index] + before_filter :load_permissions, only: [:index] def index return render_report_form if params[:report].blank? @@ -43,8 +43,8 @@ module Spree @report_parameters = report_klass::Parameters.new(params[:report] || {}) end - def load_report_authorizer - @report_authorizer = report_klass::ReportAuthorizer.new(spree_current_user) + def load_permissions + @permissions = report_klass::Permissions.new(spree_current_user) end def render_report diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml index 25c9a2eed4..bb2135b2e9 100644 --- a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml +++ b/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml @@ -16,31 +16,31 @@ .row .sixteen.columns.alpha = f.label :distributor_ids - = f.collection_select(:distributor_ids, @report_authorizer.allowed_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:distributor_ids, @permissions.allowed_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .sixteen.columns.alpha = f.label :producer_ids - = f.collection_select(:producer_ids, @report_authorizer.allowed_producers, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:producer_ids, @permissions.allowed_producers, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .sixteen.columns.alpha = f.label :order_cycle_ids - = f.collection_select(:order_cycle_ids, @report_authorizer.allowed_order_cycles, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:order_cycle_ids, @permissions.allowed_order_cycles, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .eight.columns.alpha = f.label :enterprise_fee_ids - = f.collection_select(:enterprise_fee_ids, @report_authorizer.allowed_enterprise_fees, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:enterprise_fee_ids, @permissions.allowed_enterprise_fees, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .eight.columns.omega = f.label :shipping_method_ids - = f.collection_select(:shipping_method_ids, @report_authorizer.allowed_shipping_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:shipping_method_ids, @permissions.allowed_shipping_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .eight.columns.alpha   .eight.columns.omega = f.label :payment_method_ids - = f.collection_select(:payment_method_ids, @report_authorizer.allowed_payment_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + = f.collection_select(:payment_method_ids, @permissions.allowed_payment_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .sixteen.columns.alpha diff --git a/lib/open_food_network/reports/report_authorizer.rb b/lib/open_food_network/reports/permissions.rb similarity index 84% rename from lib/open_food_network/reports/report_authorizer.rb rename to lib/open_food_network/reports/permissions.rb index 88225a26f5..1e8a8e3978 100644 --- a/lib/open_food_network/reports/report_authorizer.rb +++ b/lib/open_food_network/reports/permissions.rb @@ -1,6 +1,6 @@ module OpenFoodNetwork module Reports - class ReportAuthorizer + class Permissions attr_accessor :user def initialize(user) diff --git a/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb b/lib/order_management/reports/enterprise_fee_summary/permissions.rb similarity index 92% rename from lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb rename to lib/order_management/reports/enterprise_fee_summary/permissions.rb index f49e28c88d..8dea4c7f6d 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_authorizer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/permissions.rb @@ -1,9 +1,9 @@ -require "open_food_network/reports/report_authorizer" +require "open_food_network/reports/permissions" module OrderManagement module Reports module EnterpriseFeeSummary - class ReportAuthorizer < OpenFoodNetwork::Reports::ReportAuthorizer + class Permissions < OpenFoodNetwork::Reports::Permissions def allowed_order_cycles @allowed_order_cycles ||= OrderCycle.accessible_by(user) end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb similarity index 98% rename from spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb rename to spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb index 33c02ce76e..3ec63e27ca 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_authorizer_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb @@ -1,8 +1,8 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/report_authorizer" +require "order_management/reports/enterprise_fee_summary/permissions" -describe OrderManagement::Reports::EnterpriseFeeSummary::ReportAuthorizer do +describe OrderManagement::Reports::EnterpriseFeeSummary::Permissions do let!(:order_cycle) { create(:simple_order_cycle) } let!(:incoming_exchange) { create(:exchange, incoming: true, order_cycle: order_cycle) } let!(:outgoing_exchange) { create(:exchange, incoming: false, order_cycle: order_cycle) } From 9ce313c7f59cc7c650785fade99fe5c16628664d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 22 Oct 2018 16:05:15 +0800 Subject: [PATCH 062/108] Define filter authorizer for enterprise fee report --- lib/open_food_network/reports/authorizer.rb | 14 ++ .../enterprise_fee_summary/authorizer.rb | 33 ++++ .../enterprise_fee_summary/authorizer_spec.rb | 177 ++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 lib/open_food_network/reports/authorizer.rb create mode 100644 lib/order_management/reports/enterprise_fee_summary/authorizer.rb create mode 100644 spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb diff --git a/lib/open_food_network/reports/authorizer.rb b/lib/open_food_network/reports/authorizer.rb new file mode 100644 index 0000000000..c8c77c6512 --- /dev/null +++ b/lib/open_food_network/reports/authorizer.rb @@ -0,0 +1,14 @@ +module OpenFoodNetwork + module Reports + class Authorizer + class ParameterNotAllowedError < StandardError; end + + attr_accessor :parameters, :permissions + + def initialize(parameters, permissions) + @parameters = parameters + @permissions = permissions + end + end + end +end diff --git a/lib/order_management/reports/enterprise_fee_summary/authorizer.rb b/lib/order_management/reports/enterprise_fee_summary/authorizer.rb new file mode 100644 index 0000000000..c2b7a36919 --- /dev/null +++ b/lib/order_management/reports/enterprise_fee_summary/authorizer.rb @@ -0,0 +1,33 @@ +require "open_food_network/reports/authorizer" + +module OrderManagement + module Reports + module EnterpriseFeeSummary + class Authorizer < OpenFoodNetwork::Reports::Authorizer + def authorize! + authorize_by_distribution! + authorize_by_fee! + end + + private + + def authorize_by_distribution! + require_ids_allowed(parameters.order_cycle_ids, permissions.allowed_order_cycles) + require_ids_allowed(parameters.distributor_ids, permissions.allowed_distributors) + require_ids_allowed(parameters.producer_ids, permissions.allowed_producers) + end + + def authorize_by_fee! + require_ids_allowed(parameters.enterprise_fee_ids, permissions.allowed_enterprise_fees) + require_ids_allowed(parameters.shipping_method_ids, permissions.allowed_shipping_methods) + require_ids_allowed(parameters.payment_method_ids, permissions.allowed_payment_methods) + end + + def require_ids_allowed(array, allowed_objects) + raise OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError \ + if (array - allowed_objects.map(&:id).map(&:to_s)).any? + end + end + end + end +end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb new file mode 100644 index 0000000000..334a0b4726 --- /dev/null +++ b/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb @@ -0,0 +1,177 @@ +require "spec_helper" + +require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/permissions" +require "order_management/reports/enterprise_fee_summary/authorizer" +require "open_food_network/reports/authorizer" + +describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do + let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + let(:user) { create(:user) } + + let(:parameters) { report_klass::Parameters.new(params) } + let(:permissions) { report_klass::Permissions.new(user) } + let(:authorizer) { described_class.new(parameters, permissions) } + + context "for distributors" do + before do + allow(permissions).to receive(:allowed_distributors) do + stub_model_collection(Enterprise, :id, ["1", "2", "3"]) + end + end + + context "when distributors are allowed" do + let(:params) { { distributor_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a distributor is not allowed" do + let(:params) { { distributor_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + end + + context "for producers" do + before do + allow(permissions).to receive(:allowed_producers) do + stub_model_collection(Enterprise, :id, ["1", "2", "3"]) + end + end + + context "when producers are allowed" do + let(:params) { { producer_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a producer is not allowed" do + let(:params) { { producer_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + end + + context "for order cycles" do + before do + allow(permissions).to receive(:allowed_order_cycles) do + stub_model_collection(OrderCycle, :id, ["1", "2", "3"]) + end + end + + context "when order cycles are allowed" do + let(:params) { { order_cycle_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when an order cycle is not allowed" do + let(:params) { { order_cycle_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + end + + context "for enterprise fees" do + before do + allow(permissions).to receive(:allowed_enterprise_fees) do + stub_model_collection(EnterpriseFee, :id, ["1", "2", "3"]) + end + end + + context "when enterprise fees are allowed" do + let(:params) { { enterprise_fee_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when an enterprise fee is not allowed" do + let(:params) { { enterprise_fee_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + end + + context "for shipping methods" do + before do + allow(permissions).to receive(:allowed_shipping_methods) do + stub_model_collection(Spree::ShippingMethod, :id, ["1", "2", "3"]) + end + end + + context "when shipping methods are allowed" do + let(:params) { { shipping_method_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a shipping method is not allowed" do + let(:params) { { shipping_method_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + end + + context "for payment methods" do + before do + allow(permissions).to receive(:allowed_payment_methods) do + stub_model_collection(Spree::PaymentMethod, :id, ["1", "2", "3"]) + end + end + + context "when payment methods are allowed" do + let(:params) { { payment_method_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a payment method is not allowed" do + let(:params) { { payment_method_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + end + + def stub_model_collection(model, attribute_name, attribute_list) + attribute_list.map do |attribute_value| + stub_model(model, attribute_name => attribute_value) + end + end + + def stub_model(model, params) + model.new.tap do |instance| + instance.stub(params) + end + end +end From f81f4b7e4a26988b580aa7fa51e0de2faacd54f2 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 1 Nov 2018 22:35:27 +0800 Subject: [PATCH 063/108] Authorize filters before generating enterprise fee report --- .../enterprise_fee_summary_report_controller.rb | 11 +++++++++++ config/locales/en.yml | 1 + .../enterprise_fee_summary/authorizer.rb | 11 +++++++++-- ...rprise_fee_summary_report_controller_spec.rb | 17 ++++++++++++++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb index f0236c21a3..948c7ce3f5 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -1,6 +1,7 @@ require "open_food_network/reports" require "order_management/reports/enterprise_fee_summary/parameters" require "order_management/reports/enterprise_fee_summary/permissions" +require "order_management/reports/enterprise_fee_summary/authorizer" require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" @@ -11,13 +12,19 @@ module Spree class EnterpriseFeeSummaryReportController < BaseController before_filter :load_report_parameters, only: [:index] before_filter :load_permissions, only: [:index] + before_filter :load_authorizer, only: [:index] def index return render_report_form if params[:report].blank? return respond_to_invalid_parameters unless @report_parameters.valid? + @authorizer.authorize! @report = report_klass::ReportService.new(@report_parameters, report_renderer_klass) + render_report + rescue OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError => e + flash[:error] = e.message + render_report_form end private @@ -47,6 +54,10 @@ module Spree @permissions = report_klass::Permissions.new(spree_current_user) end + def load_authorizer + @authorizer = report_klass::Authorizer.new(@report_parameters, @permissions) + end + def render_report return render_html_report unless @report.renderer.independent_file? send_data(@report.render, filename: @report.filename) diff --git a/config/locales/en.yml b/config/locales/en.yml index 7136e06dab..b309175379 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2694,6 +2694,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using reports: enterprise_fee_summary: date_end_before_start_error: "must be after start" + parameter_not_allowed_error: "You are not authorized to use one or more selected filters for this report." fee_calculated_on_transfer_through_all: "All" fee_type: payment_method: "Payment Transaction" diff --git a/lib/order_management/reports/enterprise_fee_summary/authorizer.rb b/lib/order_management/reports/enterprise_fee_summary/authorizer.rb index c2b7a36919..073c0282b5 100644 --- a/lib/order_management/reports/enterprise_fee_summary/authorizer.rb +++ b/lib/order_management/reports/enterprise_fee_summary/authorizer.rb @@ -4,6 +4,10 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Authorizer < OpenFoodNetwork::Reports::Authorizer + @i18n_scope = "order_management.reports.enterprise_fee_summary" + + PARAMETER_NOT_ALLOWED_ERROR = I18n.t("parameter_not_allowed_error", scope: @i18n_scope) + def authorize! authorize_by_distribution! authorize_by_fee! @@ -24,8 +28,11 @@ module OrderManagement end def require_ids_allowed(array, allowed_objects) - raise OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError \ - if (array - allowed_objects.map(&:id).map(&:to_s)).any? + error_klass = OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError + error_message = PARAMETER_NOT_ALLOWED_ERROR + ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? + + raise error_klass, error_message unless ids_allowed end end end diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb index 7b058d233e..7905a5354e 100644 --- a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb @@ -6,7 +6,7 @@ describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :con let(:current_user) { admin } before do - allow(controller).to receive(:spree_current_user) { admin } + allow(controller).to receive(:spree_current_user) { current_user } end describe "#index" do @@ -37,6 +37,21 @@ describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :con expect(response).to render_template(view_template_path) end end + + context "when some parameters are now allowed" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:other_distributor) { create(:distributor_enterprise) } + + let(:current_user) { distributor.owner } + + it "renders the report form with an error" do + get :index, report: { distributor_ids: [other_distributor.id] }, report_format: "csv" + + expect(flash[:error]).to eq(report_klass::Authorizer::PARAMETER_NOT_ALLOWED_ERROR) + expect(response) + .to render_template("spree/admin/reports/enterprise_fee_summary_report/index") + end + end end def i18n_scope From 8ad53055c760a9bf6d674f6f08fa927b548c1ba4 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 9 Nov 2018 06:03:34 +0800 Subject: [PATCH 064/108] Allow non-admins to see enterprise fee summary Relax controller permissions for enterprise fee summary. Even non-admin enterprise users should be able to see these reports. Filtering of data based on permissions is handled in: * OrderManagement::Reports::EnterpriseFeeSummary::Authorizer and * OrderManagement::Reports::EnterpriseFeeSummary::Permissions. --- app/models/spree/ability_decorator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index f6f37560ce..7aa9e697f8 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -185,6 +185,7 @@ class AbilityDecorator # Reports page can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report + can [:admin, :index], :enterprise_fee_summary_report end def add_order_cycle_management_abilities(user) @@ -258,6 +259,7 @@ class AbilityDecorator # Reports page can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], :report + can [:admin, :index], :enterprise_fee_summary_report can [:create], Customer can [:admin, :index, :update, :destroy, :show], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) From d3e9a53120ec00e1ed975325838f9a7279bd5fd1 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 7 Nov 2018 02:27:54 +0800 Subject: [PATCH 065/108] Filter enterprise fee summary for user permissions --- ...nterprise_fee_summary_report_controller.rb | 4 +- .../enterprise_fee_summary/report_service.rb | 11 +++- ...rise_fee_summary_report_controller_spec.rb | 18 ++++++ .../renderers/csv_renderer_spec.rb | 6 +- .../renderers/html_renderer_spec.rb | 6 +- .../report_service_spec.rb | 58 ++++++++++++++++++- 6 files changed, 93 insertions(+), 10 deletions(-) diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb index 948c7ce3f5..b6dac3bbef 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb @@ -19,8 +19,8 @@ module Spree return respond_to_invalid_parameters unless @report_parameters.valid? @authorizer.authorize! - @report = report_klass::ReportService.new(@report_parameters, report_renderer_klass) - + @report = report_klass::ReportService.new(@permissions, @report_parameters, + report_renderer_klass) render_report rescue OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError => e flash[:error] = e.message diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/lib/order_management/reports/enterprise_fee_summary/report_service.rb index 3b57be7b6c..d2b86d97e9 100644 --- a/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -9,15 +9,16 @@ module OrderManagement class ReportService delegate :render, :filename, to: :renderer - attr_accessor :parameters, :renderer_klass + attr_accessor :permissions, :parameters, :renderer_klass - def initialize(parameters, renderer_klass) + def initialize(permissions, parameters, renderer_klass) + @permissions = permissions @parameters = parameters @renderer_klass = renderer_klass end def enterprise_fees_by_customer - Scope.new.apply_filters(parameters).result + Scope.new.apply_filters(permission_filters).apply_filters(parameters).result end def enterprise_fee_type_totals @@ -30,6 +31,10 @@ module OrderManagement private + def permission_filters + Parameters.new(order_cycle_ids: permissions.allowed_order_cycles.map(&:id)) + end + def enterprise_fee_type_total_list enterprise_fees_by_customer.map do |total_data| summarizer = EnterpriseFeeTypeTotalSummarizer.new(total_data) diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb index 7905a5354e..6d01d3eb32 100644 --- a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb @@ -1,6 +1,8 @@ require "spec_helper" describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :controller do + let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + let!(:admin) { create(:admin_user) } let(:current_user) { admin } @@ -52,6 +54,22 @@ describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :con .to render_template("spree/admin/reports/enterprise_fee_summary_report/index") end end + + describe "filtering results based on permissions" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:other_distributor) { create(:distributor_enterprise) } + + let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } + let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } + + let(:current_user) { distributor.owner } + + it "applies permissions to report" do + get :index, report: {}, report_format: "csv" + + expect(assigns(:permissions).allowed_order_cycles.to_a).to eq([order_cycle]) + end + end end def i18n_scope diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index 21854e29ce..192e165bd9 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -1,14 +1,16 @@ require "spec_helper" require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/permissions" require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + let!(:permissions) { report_klass::Permissions.new(current_user) } let!(:parameters) { report_klass::Parameters.new } - let!(:service) { report_klass::ReportService.new(parameters, described_class) } + let!(:service) { report_klass::ReportService.new(permissions, parameters, described_class) } let!(:enterprise_fee_type_totals) do instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new @@ -37,6 +39,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer end end + let(:current_user) { nil } + before do allow(service).to receive(:enterprise_fee_type_totals) { enterprise_fee_type_totals } end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb index 8afc535c1d..39158b35cd 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -1,14 +1,16 @@ require "spec_helper" require "order_management/reports/enterprise_fee_summary/parameters" +require "order_management/reports/enterprise_fee_summary/permissions" require "order_management/reports/enterprise_fee_summary/report_service" require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + let!(:permissions) { report_klass::Permissions.new(current_user) } let!(:parameters) { report_klass::Parameters.new } - let!(:service) { report_klass::ReportService.new(parameters, described_class) } + let!(:service) { report_klass::ReportService.new(permissions, parameters, described_class) } let!(:enterprise_fee_type_totals) do instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new @@ -37,6 +39,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer end end + let(:current_user) { nil } + before do allow(service).to receive(:enterprise_fee_type_totals) { enterprise_fee_type_totals } end diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 94effc1f11..a9c7988592 100644 --- a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -1,6 +1,7 @@ require "spec_helper" require "order_management/reports/enterprise_fee_summary/report_service" +require "order_management/reports/enterprise_fee_summary/permissions" require "order_management/reports/enterprise_fee_summary/parameters" describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do @@ -76,13 +77,16 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:customer) { create(:customer, name: "Sample Customer") } let!(:another_customer) { create(:customer, name: "Another Customer") } + let!(:current_user) { create(:admin_user) } + describe "grouping and sorting of entries" do let!(:customer_order) { prepare_order(customer: customer) } let!(:second_customer_order) { prepare_order(customer: customer) } let!(:other_customer_order) { prepare_order(customer: another_customer) } - let(:parameters) { OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new } - let(:service) { described_class.new(parameters, nil) } + let(:permissions) { report_klass::Permissions.new(current_user) } + let(:parameters) { report_klass::Parameters.new } + let(:service) { described_class.new(permissions, parameters, nil) } it "groups and sorts entries correctly" do totals = service.enterprise_fee_type_totals @@ -140,9 +144,57 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end end + describe "filtering results based on permissions" do + let!(:distributor_a) do + create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_b) do + create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + + let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } + + let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } + let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } + + let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } + let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } + + let(:permissions) { report_klass::Permissions.new(current_user) } + let(:parameters) { report_klass::Parameters.new({}) } + let(:service) { described_class.new(permissions, parameters, nil) } + + context "when admin" do + let!(:current_user) { create(:admin_user) } + + it "includes all order cycles" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 2, fee_type: "Shipment") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") + end + end + + context "when enterprise owner for distributor" do + let!(:current_user) { distributor_a.owner } + + it "does not include unrelated order cycles" do + totals = service.enterprise_fee_type_totals.list + + expect_total_matches(totals, 1, fee_type: "Shipment") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + end + end + end + describe "filters entries correctly" do + let(:permissions) { report_klass::Permissions.new(current_user) } let(:parameters) { report_klass::Parameters.new(parameters_attributes) } - let(:service) { described_class.new(parameters, nil) } + let(:service) { described_class.new(permissions, parameters, nil) } context "filtering by completion date" do let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } From edf0b0df764307331edd923c91f64e2471b79a0d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 9 Nov 2018 09:30:11 +0800 Subject: [PATCH 066/108] Add navigation item for enterprise fee summary --- .../admin/reports_controller_decorator.rb | 4 +++ app/models/spree/ability_decorator.rb | 4 +-- config/locales/en.yml | 4 ++- .../enterprise_fee_summary_report_spec.rb | 32 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 spec/features/admin/reports/enterprise_fee_summary_report_spec.rb diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 657c6b6bb8..ba51b4692e 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -40,6 +40,9 @@ Spree::Admin::ReportsController.class_eval do [I18n.t('admin.reports.mailing_list'), :mailing_list], [I18n.t('admin.reports.addresses'), :addresses] ], + enterprise_fee_summary: [ + [I18n.t("admin.reports.enterprise_fee_summary"), :enterprise_fee_summary] + ], order_cycle_management: [ [I18n.t('admin.reports.payment_methods'), :payment_methods], [I18n.t('admin.reports.delivery'), :delivery] @@ -275,6 +278,7 @@ Spree::Admin::ReportsController.class_eval do :products_and_inventory, :sales_total, :users_and_enterprises, + :enterprise_fee_summary, :order_cycle_management, :sales_tax, :xero_invoices, diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 7aa9e697f8..e7ba9df1ef 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -184,7 +184,7 @@ class AbilityDecorator can [:admin, :index, :guide, :import, :save, :save_data, :validate_data, :reset_absent_products], ProductImport::ProductImporter # Reports page - can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report + can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, :enterprise_fee_summary], :report can [:admin, :index], :enterprise_fee_summary_report end @@ -258,7 +258,7 @@ class AbilityDecorator end # Reports page - can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], :report + can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices, :enterprise_fee_summary], :report can [:admin, :index], :enterprise_fee_summary_report can [:create], Customer diff --git a/config/locales/en.yml b/config/locales/en.yml index b309175379..ea17a0515b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1072,7 +1072,9 @@ en: description: Invoices for import into Xero packing: name: Packing Reports - + enterprise_fee_summary: + name: "Enterprise Fee Summary" + description: "Summary of Enterprise Fees collected" subscriptions: subscriptions: Subscriptions new: New Subscription diff --git a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb b/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb new file mode 100644 index 0000000000..391dcdf4a5 --- /dev/null +++ b/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb @@ -0,0 +1,32 @@ +require "spec_helper" + +feature "enterprise fee summary report" do + include AuthenticationWorkflow + include WebHelper + + let!(:distributor) { create(:distributor_enterprise) } + let!(:other_distributor) { create(:distributor_enterprise) } + + before do + login_as current_user + end + + describe "navigation" do + let(:current_user) { distributor.owner } + + before do + visit spree.admin_reports_path + click_on "Enterprise Fee Summary" + end + + context "when accessing the report as an enterprise user" do + it "allows access to the report" do + expect(page).to have_button(I18n.t("generate_report", scope: i18n_scope)) + end + end + end + + def i18n_scope + "spree.admin.reports.enterprise_fee_summary_report.filters" + end +end From dcb1d9fe25d1057571295ecb55a2174843ecf605 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 9 Nov 2018 09:32:59 +0800 Subject: [PATCH 067/108] Add feature tests for enterprise fee summary --- .../enterprise_fee_summary_report_spec.rb | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb b/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb index 391dcdf4a5..c2868288e8 100644 --- a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb @@ -7,6 +7,9 @@ feature "enterprise fee summary report" do let!(:distributor) { create(:distributor_enterprise) } let!(:other_distributor) { create(:distributor_enterprise) } + let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } + let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } + before do login_as current_user end @@ -24,6 +27,99 @@ feature "enterprise fee summary report" do expect(page).to have_button(I18n.t("generate_report", scope: i18n_scope)) end end + + context "when accessing the report as an enterprise user without sufficient permissions" do + let(:current_user) { create(:user) } + + before do + visit spree.enterprise_fee_summary_admin_reports_path + end + + it "does not allow access to the report" do + expect(page).to have_content(I18n.t("unauthorized")) + end + end + end + + describe "smoke test for filters" do + before do + visit spree.enterprise_fee_summary_admin_reports_path + end + + context "when logged in as admin" do + let(:current_user) { create(:admin_user) } + + it "shows all available options" do + expect(page).to have_select "report_order_cycle_ids", with_options: [order_cycle.name] + end + end + + context "when logged in as enterprise user" do + let!(:order) { create(:completed_order_with_fees, order_cycle: order_cycle, distributor: distributor) } + + let(:current_user) { distributor.owner } + + it "shows available options for the enterprise" do + expect(page).to have_select "report_order_cycle_ids", options: [order_cycle.name] + end + end + end + + describe "smoke test for generation of report based on permissions" do + before do + visit spree.enterprise_fee_summary_admin_reports_path + end + + context "when logged in as admin" do + let!(:order) { create(:completed_order_with_fees, order_cycle: order_cycle, distributor: distributor) } + + let(:current_user) { create(:admin_user) } + + it "generates file with data for all enterprises" do + check I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.report_format_csv") + click_on I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.generate_report") + expect(page.response_headers['Content-Type']).to eq "text/csv" + expect(page.body).to have_content(distributor.name) + end + end + + context "when logged in as enterprise user" do + let!(:order) { create(:completed_order_with_fees, order_cycle: order_cycle, distributor: distributor) } + let!(:other_order) { create(:completed_order_with_fees, order_cycle: other_order_cycle, distributor: other_distributor) } + + let(:current_user) { distributor.owner } + + it "generates file with data for the enterprise" do + check I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.report_format_csv") + click_on I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.generate_report") + expect(page.response_headers['Content-Type']).to eq "text/csv" + expect(page.body).to have_content(distributor.name) + expect(page.body).not_to have_content(other_distributor.name) + end + end + end + + describe "smoke test for filtering report based on filters" do + let!(:second_distributor) { create(:distributor_enterprise) } + let!(:second_order_cycle) { create(:simple_order_cycle, coordinator: second_distributor) } + + let!(:order) { create(:completed_order_with_fees, order_cycle: order_cycle, distributor: distributor) } + let!(:second_order) { create(:completed_order_with_fees, order_cycle: second_order_cycle, distributor: second_distributor) } + + let(:current_user) { create(:admin_user) } + + before do + visit spree.enterprise_fee_summary_admin_reports_path + end + + it "generates file with data for selected order cycle" do + select order_cycle.name, from: "report_order_cycle_ids" + check I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.report_format_csv") + click_on I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.generate_report") + expect(page.response_headers['Content-Type']).to eq "text/csv" + expect(page.body).to have_content(distributor.name) + expect(page.body).not_to have_content(second_distributor.name) + end end def i18n_scope From 0cdcd96bb56ad92442f88df84478cd5a56a5438d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 14 Nov 2018 16:57:49 +0800 Subject: [PATCH 068/108] Add skeleton for OrderManagement engine --- Gemfile | 1 + Gemfile.lock | 6 ++++++ config/routes.rb | 3 ++- engines/order_management/README.md | 5 +++++ .../app/assets/javascripts/order_management/all.js | 1 + .../order_management/order_management.js | 0 .../stylesheets/order_management/all.css.scss | 0 .../order_management/application_controller.rb | 5 +++++ engines/order_management/config/routes.rb | 2 ++ engines/order_management/lib/order_management.rb | 4 ++++ .../order_management/lib/order_management/engine.rb | 5 +++++ .../lib/order_management/version.rb | 3 +++ engines/order_management/order_management.gemspec | 13 +++++++++++++ engines/order_management/spec/spec_helper.rb | 9 +++++++++ 14 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 engines/order_management/README.md create mode 100644 engines/order_management/app/assets/javascripts/order_management/all.js create mode 100644 engines/order_management/app/assets/javascripts/order_management/order_management.js create mode 100644 engines/order_management/app/assets/stylesheets/order_management/all.css.scss create mode 100644 engines/order_management/app/controllers/order_management/application_controller.rb create mode 100644 engines/order_management/config/routes.rb create mode 100644 engines/order_management/lib/order_management.rb create mode 100644 engines/order_management/lib/order_management/engine.rb create mode 100644 engines/order_management/lib/order_management/version.rb create mode 100644 engines/order_management/order_management.gemspec create mode 100644 engines/order_management/spec/spec_helper.rb diff --git a/Gemfile b/Gemfile index 2865321201..9182e03c27 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,7 @@ gem 'i18n-js', '~> 3.1.0' # Patched version. See http://rubysec.com/advisories/CVE-2015-5312/. gem 'nokogiri', '>= 1.6.7.1' +gem "order_management", path: "./engines/order_management" gem 'web', path: './engines/web' gem 'pg' diff --git a/Gemfile.lock b/Gemfile.lock index be3639fd40..a69449b3b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -133,6 +133,11 @@ GIT activemodel (>= 3.0) railties (>= 3.0) +PATH + remote: engines/order_management + specs: + order_management (0.0.1) + PATH remote: engines/web specs: @@ -826,6 +831,7 @@ DEPENDENCIES oauth2 (~> 1.4.1) ofn-qz! oj + order_management! paper_trail (~> 5.2.3) paperclip pg diff --git a/config/routes.rb b/config/routes.rb index 4804e4cae2..34d681fe11 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -118,8 +118,9 @@ Openfoodnetwork::Application.routes.draw do get 'sitemap.xml', to: 'sitemap#index', defaults: { format: 'xml' } - # Mount Web engine routes + # Mount engine routes mount Web::Engine, :at => '/' + mount OrderManagement::Engine, :at => '/' # Mount Spree's routes mount Spree::Core::Engine, :at => '/' diff --git a/engines/order_management/README.md b/engines/order_management/README.md new file mode 100644 index 0000000000..179330e20a --- /dev/null +++ b/engines/order_management/README.md @@ -0,0 +1,5 @@ +# Order Management + +This is the rails engine for the Order Management domain. + +See our wiki for [more info about domains and engines in OFN](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Tech-Doc:-How-OFN-is-organized-in-Domains-using-Rails-Engines). diff --git a/engines/order_management/app/assets/javascripts/order_management/all.js b/engines/order_management/app/assets/javascripts/order_management/all.js new file mode 100644 index 0000000000..2c03f2d7ab --- /dev/null +++ b/engines/order_management/app/assets/javascripts/order_management/all.js @@ -0,0 +1 @@ +//= require_tree . diff --git a/engines/order_management/app/assets/javascripts/order_management/order_management.js b/engines/order_management/app/assets/javascripts/order_management/order_management.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/engines/order_management/app/assets/stylesheets/order_management/all.css.scss b/engines/order_management/app/assets/stylesheets/order_management/all.css.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/engines/order_management/app/controllers/order_management/application_controller.rb b/engines/order_management/app/controllers/order_management/application_controller.rb new file mode 100644 index 0000000000..ba61b2290b --- /dev/null +++ b/engines/order_management/app/controllers/order_management/application_controller.rb @@ -0,0 +1,5 @@ +module OrderManagement + class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + end +end diff --git a/engines/order_management/config/routes.rb b/engines/order_management/config/routes.rb new file mode 100644 index 0000000000..d366f73ef2 --- /dev/null +++ b/engines/order_management/config/routes.rb @@ -0,0 +1,2 @@ +OrderManagement::Engine.routes.draw do +end diff --git a/engines/order_management/lib/order_management.rb b/engines/order_management/lib/order_management.rb new file mode 100644 index 0000000000..816ef31b89 --- /dev/null +++ b/engines/order_management/lib/order_management.rb @@ -0,0 +1,4 @@ +require "order_management/engine" + +module OrderManagement +end diff --git a/engines/order_management/lib/order_management/engine.rb b/engines/order_management/lib/order_management/engine.rb new file mode 100644 index 0000000000..a79bb4d0c7 --- /dev/null +++ b/engines/order_management/lib/order_management/engine.rb @@ -0,0 +1,5 @@ +module OrderManagement + class Engine < ::Rails::Engine + isolate_namespace OrderManagement + end +end diff --git a/engines/order_management/lib/order_management/version.rb b/engines/order_management/lib/order_management/version.rb new file mode 100644 index 0000000000..ccdf0928b4 --- /dev/null +++ b/engines/order_management/lib/order_management/version.rb @@ -0,0 +1,3 @@ +module OrderManagement + VERSION = "0.0.1".freeze +end diff --git a/engines/order_management/order_management.gemspec b/engines/order_management/order_management.gemspec new file mode 100644 index 0000000000..0ab11f2800 --- /dev/null +++ b/engines/order_management/order_management.gemspec @@ -0,0 +1,13 @@ +$LOAD_PATH.push File.expand_path('lib', __dir__) + +require "order_management/version" + +Gem::Specification.new do |s| + s.name = "order_management" + s.version = OrderManagement::VERSION + s.authors = ["developers@ofn"] + s.summary = "Order Management domain of the OFN solution." + + s.files = Dir["{app,config,db,lib}/**/*"] + ["LICENSE.txt", "Rakefile", "README.rdoc"] + s.test_files = Dir["spec/**/*"] +end diff --git a/engines/order_management/spec/spec_helper.rb b/engines/order_management/spec/spec_helper.rb new file mode 100644 index 0000000000..c4ab08aaf9 --- /dev/null +++ b/engines/order_management/spec/spec_helper.rb @@ -0,0 +1,9 @@ +ENV["RAILS_ENV"] = "test" + +require "../../spec/spec_helper" + +# Require factories in Spree and main application. +require 'spree/testing_support/factories' +require '../../spec/factories' + +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } From b78e2b4720fd840fd8437c1c272f4a693d393e98 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 14 Nov 2018 17:40:18 +0800 Subject: [PATCH 069/108] Move lib files for enterprise fee summary to engine --- .rubocop_styleguide.yml | 4 ++-- .../reports/enterprise_fee_summary/authorizer.rb | 0 .../enterprise_fee_type_total_summarizer.rb | 0 .../reports/enterprise_fee_summary/parameters.rb | 0 .../reports/enterprise_fee_summary/permissions.rb | 0 .../reports/enterprise_fee_summary/renderers/csv_renderer.rb | 0 .../reports/enterprise_fee_summary/renderers/html_renderer.rb | 0 .../report_data/enterprise_fee_type_total.rb | 0 .../report_data/enterprise_fee_type_totals.rb | 0 .../reports/enterprise_fee_summary/report_service.rb | 0 .../order_management/reports/enterprise_fee_summary/scope.rb | 0 .../reports/enterprise_fee_summary/authorizer_spec.rb | 0 .../reports/enterprise_fee_summary/parameters_spec.rb | 0 .../reports/enterprise_fee_summary/permissions_spec.rb | 0 .../enterprise_fee_summary/renderers/csv_renderer_spec.rb | 0 .../enterprise_fee_summary/renderers/html_renderer_spec.rb | 0 .../report_data/enterprise_fee_type_total_spec.rb | 0 .../reports/enterprise_fee_summary/report_service_spec.rb | 0 18 files changed, 2 insertions(+), 2 deletions(-) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/authorizer.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/parameters.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/permissions.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/report_service.rb (100%) rename {lib => engines/order_management/lib}/order_management/reports/enterprise_fee_summary/scope.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb (100%) rename {spec => engines/order_management/spec}/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb (100%) diff --git a/.rubocop_styleguide.yml b/.rubocop_styleguide.yml index 3c37dd7274..ac4a0d1a45 100644 --- a/.rubocop_styleguide.yml +++ b/.rubocop_styleguide.yml @@ -195,7 +195,7 @@ Metrics/BlockNesting: Metrics/ClassLength: Max: 100 Exclude: - - lib/order_management/reports/enterprise_fee_summary/scope.rb + - engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb Metrics/ModuleLength: Max: 100 @@ -206,7 +206,7 @@ Metrics/CyclomaticComplexity: Metrics/MethodLength: Max: 10 Exclude: - - lib/order_management/reports/enterprise_fee_summary/scope.rb + - engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb Metrics/ParameterLists: Max: 5 diff --git a/lib/order_management/reports/enterprise_fee_summary/authorizer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/authorizer.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/parameters.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/permissions.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/permissions.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/report_service.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb diff --git a/lib/order_management/reports/enterprise_fee_summary/scope.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb similarity index 100% rename from lib/order_management/reports/enterprise_fee_summary/scope.rb rename to engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb diff --git a/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb similarity index 100% rename from spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb rename to engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb From 552bf1b9f7ba6452c32da8571cd0eaa02212eaea Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 26 Nov 2018 13:32:43 +0800 Subject: [PATCH 070/108] Move report list to own class --- .../admin/reports_controller_decorator.rb | 35 +------- lib/open_food_network/reports/list.rb | 79 +++++++++++++++++++ 2 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 lib/open_food_network/reports/list.rb diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index ba51b4692e..bfc5e458c1 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -1,4 +1,6 @@ require 'csv' + +require 'open_food_network/reports/list' require 'open_food_network/order_and_distributor_report' require 'open_food_network/products_and_inventory_report' require 'open_food_network/lettuce_share_report' @@ -24,38 +26,7 @@ Spree::Admin::ReportsController.class_eval do before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management, :packing] def report_types - { - orders_and_fulfillment: [ - [I18n.t('admin.reports.supplier_totals'), :order_cycle_supplier_totals], - [I18n.t('admin.reports.supplier_totals_by_distributor'), :order_cycle_supplier_totals_by_distributor], - [I18n.t('admin.reports.totals_by_supplier'), :order_cycle_distributor_totals_by_supplier], - [I18n.t('admin.reports.customer_totals'), :order_cycle_customer_totals] - ], - products_and_inventory: [ - [I18n.t('admin.reports.all_products'), :all_products], - [I18n.t('admin.reports.inventory'), :inventory], - [I18n.t('admin.reports.lettuce_share'), :lettuce_share] - ], - customers: [ - [I18n.t('admin.reports.mailing_list'), :mailing_list], - [I18n.t('admin.reports.addresses'), :addresses] - ], - enterprise_fee_summary: [ - [I18n.t("admin.reports.enterprise_fee_summary"), :enterprise_fee_summary] - ], - order_cycle_management: [ - [I18n.t('admin.reports.payment_methods'), :payment_methods], - [I18n.t('admin.reports.delivery'), :delivery] - ], - sales_tax: [ - [I18n.t('admin.reports.tax_types'), :tax_types], - [I18n.t('admin.reports.tax_rates'), :tax_rates] - ], - packing: [ - [I18n.t('admin.reports.pack_by_customer'), :pack_by_customer], - [I18n.t('admin.reports.pack_by_supplier'), :pack_by_supplier] - ] - } + OpenFoodNetwork::Reports::List.all end # Override spree reports list. diff --git a/lib/open_food_network/reports/list.rb b/lib/open_food_network/reports/list.rb new file mode 100644 index 0000000000..e4425ba936 --- /dev/null +++ b/lib/open_food_network/reports/list.rb @@ -0,0 +1,79 @@ +module OpenFoodNetwork + module Reports + class List + def self.all + new.all + end + + def all + { + orders_and_fulfillment: orders_and_fulfillment_report_types, + products_and_inventory: products_and_inventory_report_types, + customers: customers_report_types, + enterprise_fee_summary: enterprise_fee_summary_report_types, + order_cycle_management: order_cycle_management_report_types, + sales_tax: sales_tax_report_types, + packing: packing_report_types + } + end + + protected + + def orders_and_fulfillment_report_types + [ + [i18n_translate("supplier_totals"), :order_cycle_supplier_totals], + [i18n_translate("supplier_totals_by_distributor"), + :order_cycle_supplier_totals_by_distributor], + [i18n_translate("totals_by_supplier"), :order_cycle_distributor_totals_by_supplier], + [i18n_translate("customer_totals"), :order_cycle_customer_totals] + ] + end + + def products_and_inventory_report_types + [ + [i18n_translate("all_products"), :all_products], + [i18n_translate("inventory"), :inventory], + [i18n_translate("lettuce_share"), :lettuce_share] + ] + end + + def customers_report_types + [ + [i18n_translate("mailing_list"), :mailing_list], + [i18n_translate("addresses"), :addresses] + ] + end + + def enterprise_fee_summary_report_types + [ + [i18n_translate("enterprise_fee_summary"), :enterprise_fee_summary] + ] + end + + def order_cycle_management_report_types + [ + [i18n_translate("payment_methods"), :payment_methods], + [i18n_translate("delivery"), :delivery] + ] + end + + def sales_tax_report_types + [ + [i18n_translate("tax_types"), :tax_types], + [i18n_translate("tax_rates"), :tax_rates] + ] + end + + def packing_report_types + [ + [i18n_translate("pack_by_customer"), :pack_by_customer], + [i18n_translate("pack_by_supplier"), :pack_by_supplier] + ] + end + + def i18n_translate(key) + I18n.t(key, scope: "admin.reports") + end + end + end +end From 2bf38b3ea18ae0fe10f38c89864ae6d815121b0d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 26 Nov 2018 13:33:05 +0800 Subject: [PATCH 071/108] Improve code based on Rubocop recommendations --- app/models/spree/ability_decorator.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index e7ba9df1ef..7056586d46 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -184,7 +184,9 @@ class AbilityDecorator can [:admin, :index, :guide, :import, :save, :save_data, :validate_data, :reset_absent_products], ProductImport::ProductImporter # Reports page - can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, :enterprise_fee_summary], :report + can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, + :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, + :enterprise_fee_summary], :report can [:admin, :index], :enterprise_fee_summary_report end @@ -258,7 +260,9 @@ class AbilityDecorator end # Reports page - can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices, :enterprise_fee_summary], :report + can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, + :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, + :order_cycle_management, :xero_invoices, :enterprise_fee_summary], :report can [:admin, :index], :enterprise_fee_summary_report can [:create], Customer From 2063752b97d367c65da9407c64ac1fb69e5053cd Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 26 Nov 2018 18:24:21 +0800 Subject: [PATCH 072/108] Improve specs for Enterprise Fee Summary This also addresses a spec that has failed over the rebasing. --- .../enterprise_fee_summary_report_spec.rb | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb b/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb index c2868288e8..b29cc8ee7b 100644 --- a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb @@ -15,15 +15,12 @@ feature "enterprise fee summary report" do end describe "navigation" do - let(:current_user) { distributor.owner } - - before do - visit spree.admin_reports_path - click_on "Enterprise Fee Summary" - end - context "when accessing the report as an enterprise user" do + let(:current_user) { distributor.owner } + it "allows access to the report" do + visit spree.admin_reports_path + click_on I18n.t("admin.reports.enterprise_fee_summary.name") expect(page).to have_button(I18n.t("generate_report", scope: i18n_scope)) end end @@ -31,11 +28,10 @@ feature "enterprise fee summary report" do context "when accessing the report as an enterprise user without sufficient permissions" do let(:current_user) { create(:user) } - before do - visit spree.enterprise_fee_summary_admin_reports_path - end - it "does not allow access to the report" do + visit spree.admin_reports_path + expect(page).to have_no_link(I18n.t("admin.reports.enterprise_fee_summary.name")) + visit spree.enterprise_fee_summary_admin_reports_path expect(page).to have_content(I18n.t("unauthorized")) end end From 207b6c7f3098238052ed87190f37c7f0cb342f96 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Mon, 26 Nov 2018 18:28:53 +0800 Subject: [PATCH 073/108] Use shorthand for RSpec expect(subject).to --- .../enterprise_fee_summary/parameters_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb index 148e32214f..af8483422c 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -12,14 +12,14 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do end context "for type of parameters" do - it { expect(subject).to validate_date_time_format_of(:start_at) } - it { expect(subject).to validate_date_time_format_of(:end_at) } - it { expect(subject).to validate_integer_array(:distributor_ids) } - it { expect(subject).to validate_integer_array(:producer_ids) } - it { expect(subject).to validate_integer_array(:order_cycle_ids) } - it { expect(subject).to validate_integer_array(:enterprise_fee_ids) } - it { expect(subject).to validate_integer_array(:shipping_method_ids) } - it { expect(subject).to validate_integer_array(:payment_method_ids) } + it { is_expected.to validate_date_time_format_of(:start_at) } + it { is_expected.to validate_date_time_format_of(:end_at) } + it { is_expected.to validate_integer_array(:distributor_ids) } + it { is_expected.to validate_integer_array(:producer_ids) } + it { is_expected.to validate_integer_array(:order_cycle_ids) } + it { is_expected.to validate_integer_array(:enterprise_fee_ids) } + it { is_expected.to validate_integer_array(:shipping_method_ids) } + it { is_expected.to validate_integer_array(:payment_method_ids) } it "allows integer arrays to include blank string and cleans it up" do subject.distributor_ids = ["", "1"] From c0e6b70131bf49d7a5652f16945886c9065bf5a7 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 4 Dec 2018 05:10:25 +0800 Subject: [PATCH 074/108] Rename EnterpriseFeeSummaryReportController Remove "Report" because this is redundant, and pluralize the resource name. --- ...rb => enterprise_fee_summaries_controller.rb} | 2 +- app/models/spree/ability_decorator.rb | 4 ++-- .../_filters.html.haml | 0 .../_report.html.haml | 0 .../index.html.haml | 0 config/locales/en.yml | 2 +- config/routes/spree.rb | 2 +- ... enterprise_fee_summaries_controller_spec.rb} | 6 +++--- ..._spec.rb => enterprise_fee_summaries_spec.rb} | 16 ++++++++-------- 9 files changed, 16 insertions(+), 16 deletions(-) rename app/controllers/spree/admin/reports/{enterprise_fee_summary_report_controller.rb => enterprise_fee_summaries_controller.rb} (97%) rename app/views/spree/admin/reports/{enterprise_fee_summary_report => enterprise_fee_summaries}/_filters.html.haml (100%) rename app/views/spree/admin/reports/{enterprise_fee_summary_report => enterprise_fee_summaries}/_report.html.haml (100%) rename app/views/spree/admin/reports/{enterprise_fee_summary_report => enterprise_fee_summaries}/index.html.haml (100%) rename spec/controllers/spree/admin/reports/{enterprise_fee_summary_report_controller_spec.rb => enterprise_fee_summaries_controller_spec.rb} (93%) rename spec/features/admin/reports/{enterprise_fee_summary_report_spec.rb => enterprise_fee_summaries_spec.rb} (89%) diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb similarity index 97% rename from app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb rename to app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb index b6dac3bbef..1f42a2b85e 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summary_report_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb @@ -9,7 +9,7 @@ require "order_management/reports/enterprise_fee_summary/renderers/html_renderer module Spree module Admin module Reports - class EnterpriseFeeSummaryReportController < BaseController + class EnterpriseFeeSummariesController < BaseController before_filter :load_report_parameters, only: [:index] before_filter :load_permissions, only: [:index] before_filter :load_authorizer, only: [:index] diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 7056586d46..2e7f7d5343 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -187,7 +187,7 @@ class AbilityDecorator can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, :enterprise_fee_summary], :report - can [:admin, :index], :enterprise_fee_summary_report + can [:admin, :index], :enterprise_fee_summary end def add_order_cycle_management_abilities(user) @@ -263,7 +263,7 @@ class AbilityDecorator can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices, :enterprise_fee_summary], :report - can [:admin, :index], :enterprise_fee_summary_report + can [:admin, :index], :enterprise_fee_summary can [:create], Customer can [:admin, :index, :update, :destroy, :show], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/_filters.html.haml similarity index 100% rename from app/views/spree/admin/reports/enterprise_fee_summary_report/_filters.html.haml rename to app/views/spree/admin/reports/enterprise_fee_summaries/_filters.html.haml diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/_report.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/_report.html.haml similarity index 100% rename from app/views/spree/admin/reports/enterprise_fee_summary_report/_report.html.haml rename to app/views/spree/admin/reports/enterprise_fee_summaries/_report.html.haml diff --git a/app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/index.html.haml similarity index 100% rename from app/views/spree/admin/reports/enterprise_fee_summary_report/index.html.haml rename to app/views/spree/admin/reports/enterprise_fee_summaries/index.html.haml diff --git a/config/locales/en.yml b/config/locales/en.yml index ea17a0515b..14f763134b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2848,7 +2848,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using bulk_coop_allocation: 'Bulk Co-op - Allocation' bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets' bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' - enterprise_fee_summary_report: + enterprise_fee_summaries: filters: date_range: "Date Range" report_format_csv: "Download as CSV" diff --git a/config/routes/spree.rb b/config/routes/spree.rb index ecba823fa1..dac548cdde 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -25,7 +25,7 @@ Spree::Core::Engine.routes.prepend do match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post] match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post] match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post] - get "/admin/reports/enterprise_fee_summary", to: "admin/reports/enterprise_fee_summary_report#index", as: :enterprise_fee_summary_admin_reports + get "/admin/reports/enterprise_fee_summary", to: "admin/reports/enterprise_fee_summaries#index", as: :enterprise_fee_summary_admin_reports match '/admin', :to => 'admin/overview#index', :as => :admin match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get put 'credit_cards/new_from_token', to: 'credit_cards#new_from_token' diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb similarity index 93% rename from spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb rename to spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb index 6d01d3eb32..20d7608a4c 100644 --- a/spec/controllers/spree/admin/reports/enterprise_fee_summary_report_controller_spec.rb +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :controller do +describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :controller do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } let!(:admin) { create(:admin_user) } @@ -51,7 +51,7 @@ describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :con expect(flash[:error]).to eq(report_klass::Authorizer::PARAMETER_NOT_ALLOWED_ERROR) expect(response) - .to render_template("spree/admin/reports/enterprise_fee_summary_report/index") + .to render_template("spree/admin/reports/enterprise_fee_summaries/index") end end @@ -77,6 +77,6 @@ describe Spree::Admin::Reports::EnterpriseFeeSummaryReportController, type: :con end def view_template_path - "spree/admin/reports/enterprise_fee_summary_report/index" + "spree/admin/reports/enterprise_fee_summaries/index" end end diff --git a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb similarity index 89% rename from spec/features/admin/reports/enterprise_fee_summary_report_spec.rb rename to spec/features/admin/reports/enterprise_fee_summaries_spec.rb index b29cc8ee7b..a1eeb549c1 100644 --- a/spec/features/admin/reports/enterprise_fee_summary_report_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -feature "enterprise fee summary report" do +feature "enterprise fee summaries" do include AuthenticationWorkflow include WebHelper @@ -72,8 +72,8 @@ feature "enterprise fee summary report" do let(:current_user) { create(:admin_user) } it "generates file with data for all enterprises" do - check I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.report_format_csv") - click_on I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.generate_report") + check I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.report_format_csv") + click_on I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.generate_report") expect(page.response_headers['Content-Type']).to eq "text/csv" expect(page.body).to have_content(distributor.name) end @@ -86,8 +86,8 @@ feature "enterprise fee summary report" do let(:current_user) { distributor.owner } it "generates file with data for the enterprise" do - check I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.report_format_csv") - click_on I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.generate_report") + check I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.report_format_csv") + click_on I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.generate_report") expect(page.response_headers['Content-Type']).to eq "text/csv" expect(page.body).to have_content(distributor.name) expect(page.body).not_to have_content(other_distributor.name) @@ -110,8 +110,8 @@ feature "enterprise fee summary report" do it "generates file with data for selected order cycle" do select order_cycle.name, from: "report_order_cycle_ids" - check I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.report_format_csv") - click_on I18n.t("spree.admin.reports.enterprise_fee_summary_report.filters.generate_report") + check I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.report_format_csv") + click_on I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.generate_report") expect(page.response_headers['Content-Type']).to eq "text/csv" expect(page.body).to have_content(distributor.name) expect(page.body).not_to have_content(second_distributor.name) @@ -119,6 +119,6 @@ feature "enterprise fee summary report" do end def i18n_scope - "spree.admin.reports.enterprise_fee_summary_report.filters" + "spree.admin.reports.enterprise_fee_summaries.filters" end end From a17fa8013faf4d7888fd12b36533fd1138c0169d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 4 Dec 2018 19:39:47 +0800 Subject: [PATCH 075/108] Clean up I18n scope in feature specs for report --- .../reports/enterprise_fee_summaries_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb index a1eeb549c1..506a764c53 100644 --- a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb @@ -21,7 +21,7 @@ feature "enterprise fee summaries" do it "allows access to the report" do visit spree.admin_reports_path click_on I18n.t("admin.reports.enterprise_fee_summary.name") - expect(page).to have_button(I18n.t("generate_report", scope: i18n_scope)) + expect(page).to have_button(I18n.t("filters.generate_report", scope: i18n_scope)) end end @@ -72,8 +72,8 @@ feature "enterprise fee summaries" do let(:current_user) { create(:admin_user) } it "generates file with data for all enterprises" do - check I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.report_format_csv") - click_on I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.generate_report") + check I18n.t("filters.report_format_csv", scope: i18n_scope) + click_on I18n.t("filters.generate_report", scope: i18n_scope) expect(page.response_headers['Content-Type']).to eq "text/csv" expect(page.body).to have_content(distributor.name) end @@ -86,8 +86,8 @@ feature "enterprise fee summaries" do let(:current_user) { distributor.owner } it "generates file with data for the enterprise" do - check I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.report_format_csv") - click_on I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.generate_report") + check I18n.t("filters.report_format_csv", scope: i18n_scope) + click_on I18n.t("filters.generate_report", scope: i18n_scope) expect(page.response_headers['Content-Type']).to eq "text/csv" expect(page.body).to have_content(distributor.name) expect(page.body).not_to have_content(other_distributor.name) @@ -110,8 +110,8 @@ feature "enterprise fee summaries" do it "generates file with data for selected order cycle" do select order_cycle.name, from: "report_order_cycle_ids" - check I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.report_format_csv") - click_on I18n.t("spree.admin.reports.enterprise_fee_summaries.filters.generate_report") + check I18n.t("filters.report_format_csv", scope: i18n_scope) + click_on I18n.t("filters.generate_report", scope: i18n_scope) expect(page.response_headers['Content-Type']).to eq "text/csv" expect(page.body).to have_content(distributor.name) expect(page.body).not_to have_content(second_distributor.name) @@ -119,6 +119,6 @@ feature "enterprise fee summaries" do end def i18n_scope - "spree.admin.reports.enterprise_fee_summaries.filters" + "spree.admin.reports.enterprise_fee_summaries" end end From da914289eafd5c921a6dd1c50286d1cc13c5eb31 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 4 Dec 2018 14:12:16 +0800 Subject: [PATCH 076/108] Copy spree/admin/reports#index into application --- app/views/spree/admin/reports/index.html.erb | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/views/spree/admin/reports/index.html.erb diff --git a/app/views/spree/admin/reports/index.html.erb b/app/views/spree/admin/reports/index.html.erb new file mode 100644 index 0000000000..1ae8b4c17a --- /dev/null +++ b/app/views/spree/admin/reports/index.html.erb @@ -0,0 +1,21 @@ +<% content_for :page_title do %> + <%= t(:listing_reports) %> +<% end %> + + + + + + + + + + <% @reports.each do |key, value| %> + + + + + <% end %> + +
<%= t(:name) %><%= t(:description) %>
<%= link_to value[:name], send("#{key}_admin_reports_url".to_sym) %><%= value[:description] %>
+ From c8b154b12aa770e103b8f1f673eb394b2042146e Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Tue, 4 Dec 2018 13:38:15 +0800 Subject: [PATCH 077/108] Separate enterprise fee summary action Split the previous "index" action into "new" and "create" actions. --- .../enterprise_fee_summaries_controller.rb | 17 +++++----- .../admin/reports_controller_decorator.rb | 8 ++++- app/models/spree/ability_decorator.rb | 4 +-- .../_filters.html.haml | 2 +- .../{index.html.haml => create.html.haml} | 0 .../enterprise_fee_summaries/new.html.haml | 1 + app/views/spree/admin/reports/index.html.erb | 2 +- config/routes/spree.rb | 8 +++-- ...nterprise_fee_summaries_controller_spec.rb | 31 +++++++++---------- .../reports/enterprise_fee_summaries_spec.rb | 8 ++--- 10 files changed, 44 insertions(+), 37 deletions(-) rename app/views/spree/admin/reports/enterprise_fee_summaries/{index.html.haml => create.html.haml} (100%) create mode 100644 app/views/spree/admin/reports/enterprise_fee_summaries/new.html.haml diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb index 1f42a2b85e..7548c962b5 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb @@ -10,14 +10,15 @@ module Spree module Admin module Reports class EnterpriseFeeSummariesController < BaseController - before_filter :load_report_parameters, only: [:index] - before_filter :load_permissions, only: [:index] - before_filter :load_authorizer, only: [:index] + before_filter :load_report_parameters + before_filter :load_permissions - def index - return render_report_form if params[:report].blank? + def new; end + + def create return respond_to_invalid_parameters unless @report_parameters.valid? + @authorizer = report_klass::Authorizer.new(@report_parameters, @permissions) @authorizer.authorize! @report = report_klass::ReportService.new(@permissions, @report_parameters, report_renderer_klass) @@ -39,7 +40,7 @@ module Spree end def render_report_form - render action: :index + render action: :new end def report_klass @@ -54,10 +55,6 @@ module Spree @permissions = report_klass::Permissions.new(spree_current_user) end - def load_authorizer - @authorizer = report_klass::Authorizer.new(@report_parameters, @permissions) - end - def render_report return render_html_report unless @report.renderer.independent_file? send_data(@report.render, filename: @report.filename) diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index bfc5e458c1..ef9d906a74 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -270,7 +270,13 @@ Spree::Admin::ReportsController.class_eval do locals: { report_types: report_types[report] } ).html_safe end - { name: name, description: description } + { name: name, url: url_for_report(report), description: description } + end + + def url_for_report(report) + public_send("#{report}_admin_reports_url".to_sym) + rescue NoMethodError + url_for([:new, :admin, :reports, report.to_s.singularize]) end def timestamp diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 2e7f7d5343..6a68ae1476 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -187,7 +187,7 @@ class AbilityDecorator can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, :enterprise_fee_summary], :report - can [:admin, :index], :enterprise_fee_summary + can [:admin, :new, :create], :enterprise_fee_summary end def add_order_cycle_management_abilities(user) @@ -263,7 +263,7 @@ class AbilityDecorator can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices, :enterprise_fee_summary], :report - can [:admin, :index], :enterprise_fee_summary + can [:admin, :new, :create], :enterprise_fee_summary can [:create], Customer can [:admin, :index, :update, :destroy, :show], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) diff --git a/app/views/spree/admin/reports/enterprise_fee_summaries/_filters.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/_filters.html.haml index bb2135b2e9..744097e9a5 100644 --- a/app/views/spree/admin/reports/enterprise_fee_summaries/_filters.html.haml +++ b/app/views/spree/admin/reports/enterprise_fee_summaries/_filters.html.haml @@ -1,4 +1,4 @@ -= form_for @report_parameters, as: :report, url: spree.enterprise_fee_summary_admin_reports_path, method: :get do |f| += form_for @report_parameters, as: :report, url: spree.admin_reports_enterprise_fee_summary_path, method: :post do |f| .row.date-range-filter .sixteen.columns.alpha = label_tag nil, t(".date_range") diff --git a/app/views/spree/admin/reports/enterprise_fee_summaries/index.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/create.html.haml similarity index 100% rename from app/views/spree/admin/reports/enterprise_fee_summaries/index.html.haml rename to app/views/spree/admin/reports/enterprise_fee_summaries/create.html.haml diff --git a/app/views/spree/admin/reports/enterprise_fee_summaries/new.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/new.html.haml new file mode 100644 index 0000000000..790853ca1f --- /dev/null +++ b/app/views/spree/admin/reports/enterprise_fee_summaries/new.html.haml @@ -0,0 +1 @@ += render "filters" diff --git a/app/views/spree/admin/reports/index.html.erb b/app/views/spree/admin/reports/index.html.erb index 1ae8b4c17a..cb514a5291 100644 --- a/app/views/spree/admin/reports/index.html.erb +++ b/app/views/spree/admin/reports/index.html.erb @@ -12,7 +12,7 @@ <% @reports.each do |key, value| %> - <%= link_to value[:name], send("#{key}_admin_reports_url".to_sym) %> + <%= link_to value[:name], value[:url] %> <%= value[:description] %> <% end %> diff --git a/config/routes/spree.rb b/config/routes/spree.rb index dac548cdde..4e7165796c 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -25,13 +25,17 @@ Spree::Core::Engine.routes.prepend do match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post] match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post] match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post] - get "/admin/reports/enterprise_fee_summary", to: "admin/reports/enterprise_fee_summaries#index", as: :enterprise_fee_summary_admin_reports match '/admin', :to => 'admin/overview#index', :as => :admin match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get put 'credit_cards/new_from_token', to: 'credit_cards#new_from_token' - resources :credit_cards + namespace :admin do + namespace :reports do + resource :enterprise_fee_summary, only: [:new, :create] + end + end + resources :credit_cards namespace :api, :defaults => { :format => 'json' } do resources :users do diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb index 20d7608a4c..8cc159852e 100644 --- a/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb @@ -11,19 +11,19 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control allow(controller).to receive(:spree_current_user) { current_user } end - describe "#index" do - context "when there are no parameters" do - it "renders the report form" do - get :index + describe "#new" do + it "renders the report form" do + get :new - expect(response).to be_success - expect(response).to render_template(view_template_path) - end + expect(response).to be_success + expect(response).to render_template(new_template_path) end + end + describe "#create" do context "when the parameters are valid" do it "sends the generated report in the correct format" do - get :index, report: { start_at: "2018-10-09 07:30:00" }, report_format: "csv" + post :create, report: { start_at: "2018-10-09 07:30:00" }, report_format: "csv" expect(response).to be_success expect(response.body).not_to be_blank @@ -33,10 +33,10 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control context "when the parameters are invalid" do it "renders the report form with an error" do - get :index, report: { start_at: "invalid date" }, report_format: "csv" + post :create, report: { start_at: "invalid date" }, report_format: "csv" expect(flash[:error]).to eq(I18n.t("invalid_filter_parameters", scope: i18n_scope)) - expect(response).to render_template(view_template_path) + expect(response).to render_template(new_template_path) end end @@ -47,11 +47,10 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control let(:current_user) { distributor.owner } it "renders the report form with an error" do - get :index, report: { distributor_ids: [other_distributor.id] }, report_format: "csv" + post :create, report: { distributor_ids: [other_distributor.id] }, report_format: "csv" expect(flash[:error]).to eq(report_klass::Authorizer::PARAMETER_NOT_ALLOWED_ERROR) - expect(response) - .to render_template("spree/admin/reports/enterprise_fee_summaries/index") + expect(response).to render_template(new_template_path) end end @@ -65,7 +64,7 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control let(:current_user) { distributor.owner } it "applies permissions to report" do - get :index, report: {}, report_format: "csv" + post :create, report: {}, report_format: "csv" expect(assigns(:permissions).allowed_order_cycles.to_a).to eq([order_cycle]) end @@ -76,7 +75,7 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control "order_management.reports.enterprise_fee_summary" end - def view_template_path - "spree/admin/reports/enterprise_fee_summaries/index" + def new_template_path + "spree/admin/reports/enterprise_fee_summaries/new" end end diff --git a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb index 506a764c53..d4585ba291 100644 --- a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb @@ -31,7 +31,7 @@ feature "enterprise fee summaries" do it "does not allow access to the report" do visit spree.admin_reports_path expect(page).to have_no_link(I18n.t("admin.reports.enterprise_fee_summary.name")) - visit spree.enterprise_fee_summary_admin_reports_path + visit spree.new_admin_reports_enterprise_fee_summary_path expect(page).to have_content(I18n.t("unauthorized")) end end @@ -39,7 +39,7 @@ feature "enterprise fee summaries" do describe "smoke test for filters" do before do - visit spree.enterprise_fee_summary_admin_reports_path + visit spree.new_admin_reports_enterprise_fee_summary_path end context "when logged in as admin" do @@ -63,7 +63,7 @@ feature "enterprise fee summaries" do describe "smoke test for generation of report based on permissions" do before do - visit spree.enterprise_fee_summary_admin_reports_path + visit spree.new_admin_reports_enterprise_fee_summary_path end context "when logged in as admin" do @@ -105,7 +105,7 @@ feature "enterprise fee summaries" do let(:current_user) { create(:admin_user) } before do - visit spree.enterprise_fee_summary_admin_reports_path + visit spree.new_admin_reports_enterprise_fee_summary_path end it "generates file with data for selected order cycle" do From 9e035efd50eccfa2cea3a514f1a29eae15db9176 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 6 Dec 2018 09:37:58 +0800 Subject: [PATCH 078/108] Render enterprise fee report directly in renderer --- .../enterprise_fee_summaries_controller.rb | 18 ++++++----------- .../_report.html.haml | 8 ++++---- .../renderers/csv_renderer.rb | 11 +++++----- .../renderers/html_renderer.rb | 5 +++++ .../enterprise_fee_summary/report_service.rb | 11 ++-------- .../renderers/csv_renderer_spec.rb | 20 +++++++++++++++---- .../renderers/html_renderer_spec.rb | 10 ++++++---- .../report_service_spec.rb | 6 +++--- .../reports/renderers/base.rb | 6 +----- .../reports/renderers/independent_file.rb | 11 ---------- 10 files changed, 49 insertions(+), 57 deletions(-) delete mode 100644 lib/open_food_network/reports/renderers/independent_file.rb diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb index 7548c962b5..04346efa7a 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb @@ -20,9 +20,8 @@ module Spree @authorizer = report_klass::Authorizer.new(@report_parameters, @permissions) @authorizer.authorize! - @report = report_klass::ReportService.new(@permissions, @report_parameters, - report_renderer_klass) - render_report + @report = report_klass::ReportService.new(@permissions, @report_parameters) + renderer.render(self) rescue OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError => e flash[:error] = e.message render_report_form @@ -55,15 +54,6 @@ module Spree @permissions = report_klass::Permissions.new(spree_current_user) end - def render_report - return render_html_report unless @report.renderer.independent_file? - send_data(@report.render, filename: @report.filename) - end - - def render_html_report - render action: :index - end - def report_renderer_klass case params[:report_format] when "csv" @@ -74,6 +64,10 @@ module Spree raise OpenFoodNetwork::Reports::UnsupportedReportFormatException end end + + def renderer + @renderer ||= report_renderer_klass.new(@report) + end end end end diff --git a/app/views/spree/admin/reports/enterprise_fee_summaries/_report.html.haml b/app/views/spree/admin/reports/enterprise_fee_summaries/_report.html.haml index 2877778d08..4427c511ba 100644 --- a/app/views/spree/admin/reports/enterprise_fee_summaries/_report.html.haml +++ b/app/views/spree/admin/reports/enterprise_fee_summaries/_report.html.haml @@ -2,18 +2,18 @@ %table#enterprise_fee_summary_report.report__table %thead %tr - - @report.renderer.header.each do |heading| + - @renderer.header.each do |heading| %th= heading %tbody - - @report.renderer.data_rows.each do |row| + - @renderer.data_rows.each do |row| %tr - row.each do |cell_value| %td= cell_value - - if @report.renderer.data_rows.empty? + - if @renderer.data_rows.empty? %tr - %td{colspan: @report.renderer.header.length}= t(:none) + %td{colspan: @renderer.header.length}= t(:none) - else %p.report__message = t(".select_and_search") diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb index 9af42fbfc4..7479d940fd 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb @@ -1,14 +1,15 @@ require "open_food_network/reports/renderers/base" -require "open_food_network/reports/renderers/independent_file" module OrderManagement module Reports module EnterpriseFeeSummary module Renderers class CsvRenderer < OpenFoodNetwork::Reports::Renderers::Base - include OpenFoodNetwork::Reports::Renderers::IndependentFile + def render(context) + context.send_data(generate, filename: filename) + end - def render + def generate CSV.generate do |csv| render_header(csv) @@ -18,13 +19,13 @@ module OrderManagement end end + private + def filename timestamp = Time.zone.now.strftime("%Y%m%d") "enterprise_fee_summary_#{timestamp}.csv" end - private - def render_header(csv) csv << [ header_label(:fee_type), diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb index 1a280ba667..fd54febc2e 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb @@ -5,6 +5,11 @@ module OrderManagement module EnterpriseFeeSummary module Renderers class HtmlRenderer < OpenFoodNetwork::Reports::Renderers::Base + def render(context) + context.instance_variable_set :@renderer, self + context.render(action: :create, renderer: self) + end + def header data_row_attributes.map do |attribute| header_label(attribute) diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb index d2b86d97e9..c40cffd308 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -7,14 +7,11 @@ module OrderManagement module Reports module EnterpriseFeeSummary class ReportService - delegate :render, :filename, to: :renderer + attr_accessor :permissions, :parameters - attr_accessor :permissions, :parameters, :renderer_klass - - def initialize(permissions, parameters, renderer_klass) + def initialize(permissions, parameters) @permissions = permissions @parameters = parameters - @renderer_klass = renderer_klass end def enterprise_fees_by_customer @@ -25,10 +22,6 @@ module OrderManagement ReportData::EnterpriseFeeTypeTotals.new(list: enterprise_fee_type_total_list.sort) end - def renderer - @renderer ||= renderer_klass.new(self) - end - private def permission_filters diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index 192e165bd9..13823d61b2 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -10,7 +10,17 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer let!(:permissions) { report_klass::Permissions.new(current_user) } let!(:parameters) { report_klass::Parameters.new } - let!(:service) { report_klass::ReportService.new(permissions, parameters, described_class) } + let!(:service) { report_klass::ReportService.new(permissions, parameters) } + let!(:renderer) { described_class.new(service) } + + # Context which will be passed to the renderer. The response object is not automatically prepared, + # so this has to be assigned explicitly. + let!(:response) { ActionController::TestResponse.new } + let!(:controller) do + ActionController::Base.new.tap do |controller_mock| + controller_mock.instance_variable_set(:@_response, response) + end + end let!(:enterprise_fee_type_totals) do instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new @@ -46,7 +56,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer end it "generates CSV header" do - result = service.render + renderer.render(controller) + result = response.body csv = CSV.parse(result) header_row = csv[0] @@ -56,7 +67,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer end it "generates CSV data rows" do - result = service.render + renderer.render(controller) + result = response.body csv = CSV.parse(result, headers: true) expect(csv.length).to eq(2) @@ -69,7 +81,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer it "generates filename correctly" do Timecop.freeze(Time.zone.local(2018, 10, 9, 7, 30, 0)) do - filename = service.filename + filename = renderer.__send__(:filename) expect(filename).to eq("enterprise_fee_summary_20181009.csv") end end diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb index 39158b35cd..3cc30f6abb 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -10,7 +10,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer let!(:permissions) { report_klass::Permissions.new(current_user) } let!(:parameters) { report_klass::Parameters.new } - let!(:service) { report_klass::ReportService.new(permissions, parameters, described_class) } + let!(:controller) { Spree::Admin::Reports::EnterpriseFeeSummariesController.new } + let!(:service) { report_klass::ReportService.new(permissions, parameters) } + let!(:renderer) { described_class.new(service) } let!(:enterprise_fee_type_totals) do instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new @@ -46,7 +48,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer end it "generates header values" do - header_row = service.renderer.header + header_row = renderer.header # Test all header cells have values expect(header_row.length).to eq(8) @@ -54,8 +56,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer end it "generates data rows" do - header_row = service.renderer.header - result = service.renderer.data_rows + header_row = renderer.header + result = renderer.data_rows expect(result.length).to eq(2) diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index a9c7988592..8b3d522e34 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -86,7 +86,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:permissions) { report_klass::Permissions.new(current_user) } let(:parameters) { report_klass::Parameters.new } - let(:service) { described_class.new(permissions, parameters, nil) } + let(:service) { described_class.new(permissions, parameters) } it "groups and sorts entries correctly" do totals = service.enterprise_fee_type_totals @@ -165,7 +165,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:permissions) { report_klass::Permissions.new(current_user) } let(:parameters) { report_klass::Parameters.new({}) } - let(:service) { described_class.new(permissions, parameters, nil) } + let(:service) { described_class.new(permissions, parameters) } context "when admin" do let!(:current_user) { create(:admin_user) } @@ -194,7 +194,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do describe "filters entries correctly" do let(:permissions) { report_klass::Permissions.new(current_user) } let(:parameters) { report_klass::Parameters.new(parameters_attributes) } - let(:service) { described_class.new(permissions, parameters, nil) } + let(:service) { described_class.new(permissions, parameters) } context "filtering by completion date" do let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } diff --git a/lib/open_food_network/reports/renderers/base.rb b/lib/open_food_network/reports/renderers/base.rb index 6a60b94c46..9720202bc7 100644 --- a/lib/open_food_network/reports/renderers/base.rb +++ b/lib/open_food_network/reports/renderers/base.rb @@ -2,15 +2,11 @@ module OpenFoodNetwork module Reports module Renderers class Base - attr_accessor :report_data + attr_reader :report_data def initialize(report_data) @report_data = report_data end - - def independent_file? - false - end end end end diff --git a/lib/open_food_network/reports/renderers/independent_file.rb b/lib/open_food_network/reports/renderers/independent_file.rb deleted file mode 100644 index 3c5322a04e..0000000000 --- a/lib/open_food_network/reports/renderers/independent_file.rb +++ /dev/null @@ -1,11 +0,0 @@ -module OpenFoodNetwork - module Reports - module Renderers - module IndependentFile - def independent_file? - true - end - end - end - end -end From c61a83faca0a4aaabd413db277378fc5b6c0b9ec Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 06:36:16 +1100 Subject: [PATCH 079/108] Move routes for enterprise fee summary to engine --- config/routes/spree.rb | 6 ------ engines/order_management/config/routes.rb | 7 ++++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 4e7165796c..ad8973a84a 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -29,12 +29,6 @@ Spree::Core::Engine.routes.prepend do match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get put 'credit_cards/new_from_token', to: 'credit_cards#new_from_token' - namespace :admin do - namespace :reports do - resource :enterprise_fee_summary, only: [:new, :create] - end - end - resources :credit_cards namespace :api, :defaults => { :format => 'json' } do diff --git a/engines/order_management/config/routes.rb b/engines/order_management/config/routes.rb index d366f73ef2..e221867e7e 100644 --- a/engines/order_management/config/routes.rb +++ b/engines/order_management/config/routes.rb @@ -1,2 +1,7 @@ -OrderManagement::Engine.routes.draw do +Spree::Core::Engine.routes.prepend do + namespace :admin do + namespace :reports do + resource :enterprise_fee_summary, only: [:new, :create] + end + end end From 95e3a2f45d7d292829450a9fb8f2de6199571678 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 09:38:51 +0800 Subject: [PATCH 080/108] Move authorization to the ::Parameters class --- .../enterprise_fee_summaries_controller.rb | 4 +-- .../enterprise_fee_summary/parameters.rb | 5 ++++ .../enterprise_fee_summary/parameters_spec.rb | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb index 04346efa7a..d0d0cd89b6 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb @@ -18,8 +18,8 @@ module Spree def create return respond_to_invalid_parameters unless @report_parameters.valid? - @authorizer = report_klass::Authorizer.new(@report_parameters, @permissions) - @authorizer.authorize! + @report_parameters.authorize!(@permissions) + @report = report_klass::ReportService.new(@permissions, @report_parameters) renderer.render(self) rescue OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError => e diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb index 0404326826..7545f636e9 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb @@ -36,6 +36,11 @@ module OrderManagement super(attributes) end + def authorize!(permissions) + authorizer = Authorizer.new(self, permissions) + authorizer.authorize! + end + protected def require_valid_datetime_range diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb index af8483422c..e27508159d 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -59,4 +59,29 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do end end end + + describe "smoke authorization" do + let!(:order_cycle) { create(:order_cycle) } + let!(:user) { create(:user) } + + let(:permissions) do + report_klass::Permissions.new(nil).tap do |instance| + instance.stub(allowed_order_cycles: [order_cycle]) + end + end + + it "does not raise error when the parameters are allowed" do + parameters = described_class.new(order_cycle_ids: [order_cycle.id.to_s]) + expect { parameters.authorize!(permissions) }.not_to raise_error + end + + it "raises error when the parameters are not allowed" do + parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s]) + expect { parameters.authorize!(permissions) }.to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + end + end + + def report_klass + OrderManagement::Reports::EnterpriseFeeSummary + end end From 3f84d0008a8d8cc521db324a797f4de1cb97ba7d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 18:26:34 +1100 Subject: [PATCH 081/108] Move some in OpenFoodNetwork to engine services This removes the OpenFoodNetwork namespace from these too. --- .../enterprise_fee_summaries_controller.rb | 5 ++--- .../order_management/app/services/reports.rb | 3 +++ .../app/services/reports/authorizer.rb | 12 +++++++++++ .../app/services/reports/parameters/base.rb | 19 +++++++++++++++++ .../app/services/reports/permissions.rb | 9 ++++++++ .../app/services/reports/renderers/base.rb | 11 ++++++++++ .../app/services/reports/report_data/base.rb | 11 ++++++++++ .../enterprise_fee_summary/authorizer.rb | 6 ++---- .../enterprise_fee_summary/parameters.rb | 4 +--- .../enterprise_fee_summary/permissions.rb | 4 +--- .../renderers/csv_renderer.rb | 4 +--- .../renderers/html_renderer.rb | 4 +--- .../report_data/enterprise_fee_type_total.rb | 4 +--- .../report_data/enterprise_fee_type_totals.rb | 4 +--- .../enterprise_fee_summary/authorizer_spec.rb | 13 ++++++------ .../enterprise_fee_summary/parameters_spec.rb | 2 +- lib/open_food_network/reports.rb | 5 ----- lib/open_food_network/reports/authorizer.rb | 14 ------------- .../reports/parameters/base.rb | 21 ------------------- lib/open_food_network/reports/permissions.rb | 11 ---------- .../reports/renderers/base.rb | 13 ------------ .../reports/report_data/base.rb | 13 ------------ 22 files changed, 82 insertions(+), 110 deletions(-) create mode 100644 engines/order_management/app/services/reports.rb create mode 100644 engines/order_management/app/services/reports/authorizer.rb create mode 100644 engines/order_management/app/services/reports/parameters/base.rb create mode 100644 engines/order_management/app/services/reports/permissions.rb create mode 100644 engines/order_management/app/services/reports/renderers/base.rb create mode 100644 engines/order_management/app/services/reports/report_data/base.rb delete mode 100644 lib/open_food_network/reports.rb delete mode 100644 lib/open_food_network/reports/authorizer.rb delete mode 100644 lib/open_food_network/reports/parameters/base.rb delete mode 100644 lib/open_food_network/reports/permissions.rb delete mode 100644 lib/open_food_network/reports/renderers/base.rb delete mode 100644 lib/open_food_network/reports/report_data/base.rb diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb index d0d0cd89b6..db88a3bca0 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb @@ -1,4 +1,3 @@ -require "open_food_network/reports" require "order_management/reports/enterprise_fee_summary/parameters" require "order_management/reports/enterprise_fee_summary/permissions" require "order_management/reports/enterprise_fee_summary/authorizer" @@ -22,7 +21,7 @@ module Spree @report = report_klass::ReportService.new(@permissions, @report_parameters) renderer.render(self) - rescue OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError => e + rescue ::Reports::Authorizer::ParameterNotAllowedError => e flash[:error] = e.message render_report_form end @@ -61,7 +60,7 @@ module Spree when nil, "", "html" report_klass::Renderers::HtmlRenderer else - raise OpenFoodNetwork::Reports::UnsupportedReportFormatException + raise Reports::UnsupportedReportFormatException end end diff --git a/engines/order_management/app/services/reports.rb b/engines/order_management/app/services/reports.rb new file mode 100644 index 0000000000..ec85236122 --- /dev/null +++ b/engines/order_management/app/services/reports.rb @@ -0,0 +1,3 @@ +module Reports + class UnsupportedReportFormatException < StandardError; end +end diff --git a/engines/order_management/app/services/reports/authorizer.rb b/engines/order_management/app/services/reports/authorizer.rb new file mode 100644 index 0000000000..279952249a --- /dev/null +++ b/engines/order_management/app/services/reports/authorizer.rb @@ -0,0 +1,12 @@ +module Reports + class Authorizer + class ParameterNotAllowedError < StandardError; end + + attr_accessor :parameters, :permissions + + def initialize(parameters, permissions) + @parameters = parameters + @permissions = permissions + end + end +end diff --git a/engines/order_management/app/services/reports/parameters/base.rb b/engines/order_management/app/services/reports/parameters/base.rb new file mode 100644 index 0000000000..8debc06b1d --- /dev/null +++ b/engines/order_management/app/services/reports/parameters/base.rb @@ -0,0 +1,19 @@ +module Reports + module Parameters + class Base + extend ActiveModel::Naming + extend ActiveModel::Translation + include ActiveModel::Validations + include ActiveModel::Validations::Callbacks + + def initialize(attributes = {}) + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + + # The parameters are never persisted. + def to_key; end + end + end +end diff --git a/engines/order_management/app/services/reports/permissions.rb b/engines/order_management/app/services/reports/permissions.rb new file mode 100644 index 0000000000..df8588670f --- /dev/null +++ b/engines/order_management/app/services/reports/permissions.rb @@ -0,0 +1,9 @@ +module Reports + class Permissions + attr_accessor :user + + def initialize(user) + @user = user + end + end +end diff --git a/engines/order_management/app/services/reports/renderers/base.rb b/engines/order_management/app/services/reports/renderers/base.rb new file mode 100644 index 0000000000..73fda10231 --- /dev/null +++ b/engines/order_management/app/services/reports/renderers/base.rb @@ -0,0 +1,11 @@ +module Reports + module Renderers + class Base + attr_reader :report_data + + def initialize(report_data) + @report_data = report_data + end + end + end +end diff --git a/engines/order_management/app/services/reports/report_data/base.rb b/engines/order_management/app/services/reports/report_data/base.rb new file mode 100644 index 0000000000..a7d0595078 --- /dev/null +++ b/engines/order_management/app/services/reports/report_data/base.rb @@ -0,0 +1,11 @@ +module Reports + module ReportData + class Base + def initialize(attributes = {}) + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + end + end +end diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb index 073c0282b5..8b091577dd 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb @@ -1,9 +1,7 @@ -require "open_food_network/reports/authorizer" - module OrderManagement module Reports module EnterpriseFeeSummary - class Authorizer < OpenFoodNetwork::Reports::Authorizer + class Authorizer < ::Reports::Authorizer @i18n_scope = "order_management.reports.enterprise_fee_summary" PARAMETER_NOT_ALLOWED_ERROR = I18n.t("parameter_not_allowed_error", scope: @i18n_scope) @@ -28,7 +26,7 @@ module OrderManagement end def require_ids_allowed(array, allowed_objects) - error_klass = OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError + error_klass = ::Reports::Authorizer::ParameterNotAllowedError error_message = PARAMETER_NOT_ALLOWED_ERROR ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb index 7545f636e9..2082d64c9e 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb @@ -1,9 +1,7 @@ -require "open_food_network/reports/parameters/base" - module OrderManagement module Reports module EnterpriseFeeSummary - class Parameters < OpenFoodNetwork::Reports::Parameters::Base + class Parameters < ::Reports::Parameters::Base @i18n_scope = "order_management.reports.enterprise_fee_summary" DATE_END_BEFORE_START_ERROR = I18n.t("date_end_before_start_error", scope: @i18n_scope) diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb index 8dea4c7f6d..2077931627 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb @@ -1,9 +1,7 @@ -require "open_food_network/reports/permissions" - module OrderManagement module Reports module EnterpriseFeeSummary - class Permissions < OpenFoodNetwork::Reports::Permissions + class Permissions < ::Reports::Permissions def allowed_order_cycles @allowed_order_cycles ||= OrderCycle.accessible_by(user) end diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb index 7479d940fd..32ab1188f5 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb @@ -1,10 +1,8 @@ -require "open_food_network/reports/renderers/base" - module OrderManagement module Reports module EnterpriseFeeSummary module Renderers - class CsvRenderer < OpenFoodNetwork::Reports::Renderers::Base + class CsvRenderer < ::Reports::Renderers::Base def render(context) context.send_data(generate, filename: filename) end diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb index fd54febc2e..beace472ee 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb @@ -1,10 +1,8 @@ -require "open_food_network/reports/renderers/base" - module OrderManagement module Reports module EnterpriseFeeSummary module Renderers - class HtmlRenderer < OpenFoodNetwork::Reports::Renderers::Base + class HtmlRenderer < ::Reports::Renderers::Base def render(context) context.instance_variable_set :@renderer, self context.render(action: :create, renderer: self) diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb index 9f1561824d..ca781af4e4 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb @@ -1,10 +1,8 @@ -require "open_food_network/reports/report_data/base" - module OrderManagement module Reports module EnterpriseFeeSummary module ReportData - class EnterpriseFeeTypeTotal < OpenFoodNetwork::Reports::ReportData::Base + class EnterpriseFeeTypeTotal < ::Reports::ReportData::Base attr_accessor :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb index b88e4475e1..dcc54ccf29 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb @@ -1,10 +1,8 @@ -require "open_food_network/reports/report_data/base" - module OrderManagement module Reports module EnterpriseFeeSummary module ReportData - class EnterpriseFeeTypeTotals < OpenFoodNetwork::Reports::ReportData::Base + class EnterpriseFeeTypeTotals < ::Reports::ReportData::Base attr_accessor :list def initialize(*args) diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb index 334a0b4726..e1b857868e 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb @@ -3,7 +3,6 @@ require "spec_helper" require "order_management/reports/enterprise_fee_summary/parameters" require "order_management/reports/enterprise_fee_summary/permissions" require "order_management/reports/enterprise_fee_summary/authorizer" -require "open_food_network/reports/authorizer" describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } @@ -33,7 +32,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do it "raises ParameterNotAllowedError" do expect { authorizer.authorize! } - .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end end @@ -58,7 +57,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do it "raises ParameterNotAllowedError" do expect { authorizer.authorize! } - .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end end @@ -83,7 +82,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do it "raises ParameterNotAllowedError" do expect { authorizer.authorize! } - .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end end @@ -108,7 +107,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do it "raises ParameterNotAllowedError" do expect { authorizer.authorize! } - .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end end @@ -133,7 +132,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do it "raises ParameterNotAllowedError" do expect { authorizer.authorize! } - .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end end @@ -158,7 +157,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do it "raises ParameterNotAllowedError" do expect { authorizer.authorize! } - .to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end end diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb index e27508159d..d490393f7b 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -77,7 +77,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do it "raises error when the parameters are not allowed" do parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s]) - expect { parameters.authorize!(permissions) }.to raise_error(OpenFoodNetwork::Reports::Authorizer::ParameterNotAllowedError) + expect { parameters.authorize!(permissions) }.to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end diff --git a/lib/open_food_network/reports.rb b/lib/open_food_network/reports.rb deleted file mode 100644 index 5e6886b178..0000000000 --- a/lib/open_food_network/reports.rb +++ /dev/null @@ -1,5 +0,0 @@ -module OpenFoodNetwork - module Reports - class UnsupportedReportFormatException < StandardError; end - end -end diff --git a/lib/open_food_network/reports/authorizer.rb b/lib/open_food_network/reports/authorizer.rb deleted file mode 100644 index c8c77c6512..0000000000 --- a/lib/open_food_network/reports/authorizer.rb +++ /dev/null @@ -1,14 +0,0 @@ -module OpenFoodNetwork - module Reports - class Authorizer - class ParameterNotAllowedError < StandardError; end - - attr_accessor :parameters, :permissions - - def initialize(parameters, permissions) - @parameters = parameters - @permissions = permissions - end - end - end -end diff --git a/lib/open_food_network/reports/parameters/base.rb b/lib/open_food_network/reports/parameters/base.rb deleted file mode 100644 index 9b97558ad3..0000000000 --- a/lib/open_food_network/reports/parameters/base.rb +++ /dev/null @@ -1,21 +0,0 @@ -module OpenFoodNetwork - module Reports - module Parameters - class Base - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Validations::Callbacks - - def initialize(attributes = {}) - attributes.each do |key, value| - public_send("#{key}=", value) - end - end - - # The parameters are never persisted. - def to_key; end - end - end - end -end diff --git a/lib/open_food_network/reports/permissions.rb b/lib/open_food_network/reports/permissions.rb deleted file mode 100644 index 1e8a8e3978..0000000000 --- a/lib/open_food_network/reports/permissions.rb +++ /dev/null @@ -1,11 +0,0 @@ -module OpenFoodNetwork - module Reports - class Permissions - attr_accessor :user - - def initialize(user) - @user = user - end - end - end -end diff --git a/lib/open_food_network/reports/renderers/base.rb b/lib/open_food_network/reports/renderers/base.rb deleted file mode 100644 index 9720202bc7..0000000000 --- a/lib/open_food_network/reports/renderers/base.rb +++ /dev/null @@ -1,13 +0,0 @@ -module OpenFoodNetwork - module Reports - module Renderers - class Base - attr_reader :report_data - - def initialize(report_data) - @report_data = report_data - end - end - end - end -end diff --git a/lib/open_food_network/reports/report_data/base.rb b/lib/open_food_network/reports/report_data/base.rb deleted file mode 100644 index b29ced4684..0000000000 --- a/lib/open_food_network/reports/report_data/base.rb +++ /dev/null @@ -1,13 +0,0 @@ -module OpenFoodNetwork - module Reports - module ReportData - class Base - def initialize(attributes = {}) - attributes.each do |key, value| - public_send("#{key}=", value) - end - end - end - end - end -end From 3e499faa7c77f893da19064c71a5352bb6a145f9 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 20:02:20 +1100 Subject: [PATCH 082/108] Do not use ReportData::EnterpriseFeeTypeTotals --- .../renderers/csv_renderer.rb | 2 +- .../renderers/html_renderer.rb | 2 +- .../report_data/enterprise_fee_type_totals.rb | 17 ------------ .../enterprise_fee_summary/report_service.rb | 5 ++-- .../renderers/csv_renderer_spec.rb | 14 +++++----- .../renderers/html_renderer_spec.rb | 14 +++++----- .../report_service_spec.rb | 26 +++++++++---------- 7 files changed, 29 insertions(+), 51 deletions(-) delete mode 100644 engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb index 32ab1188f5..c4c8450485 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb @@ -11,7 +11,7 @@ module OrderManagement CSV.generate do |csv| render_header(csv) - report_data.enterprise_fee_type_totals.list.each do |data| + report_data.list.each do |data| render_data_row(csv, data) end end diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb index beace472ee..23ed8f26ba 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb @@ -15,7 +15,7 @@ module OrderManagement end def data_rows - report_data.enterprise_fee_type_totals.list.map do |data| + report_data.list.map do |data| data_row_attributes.map do |attribute| data.public_send(attribute) end diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb deleted file mode 100644 index dcc54ccf29..0000000000 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals.rb +++ /dev/null @@ -1,17 +0,0 @@ -module OrderManagement - module Reports - module EnterpriseFeeSummary - module ReportData - class EnterpriseFeeTypeTotals < ::Reports::ReportData::Base - attr_accessor :list - - def initialize(*args) - @list = [] - - super(*args) - end - end - end - end - end -end diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb index c40cffd308..b0ddc1c507 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb @@ -1,6 +1,5 @@ require "order_management/reports/enterprise_fee_summary/scope" require "order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer" -require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_totals" require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total" module OrderManagement @@ -18,8 +17,8 @@ module OrderManagement Scope.new.apply_filters(permission_filters).apply_filters(parameters).result end - def enterprise_fee_type_totals - ReportData::EnterpriseFeeTypeTotals.new(list: enterprise_fee_type_total_list.sort) + def list + enterprise_fee_type_total_list.sort end private diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index 13823d61b2..c12aa844bb 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -23,9 +23,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer end let!(:enterprise_fee_type_totals) do - instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new - instance.tap do |totals| - totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + [ + report_klass::ReportData::EnterpriseFeeTypeTotal.new( fee_type: "Fee Type A", enterprise_name: "Enterprise A", fee_name: "Fee A", @@ -34,9 +33,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer fee_calculated_on_transfer_through_name: "Transfer Enterprise A", tax_category_name: "Tax Category A", total_amount: "1.00" - ) - - totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + ), + report_klass::ReportData::EnterpriseFeeTypeTotal.new( fee_type: "Fee Type B", enterprise_name: "Enterprise B", fee_name: "Fee C", @@ -46,13 +44,13 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer tax_category_name: "Tax Category G", total_amount: "2.00" ) - end + ] end let(:current_user) { nil } before do - allow(service).to receive(:enterprise_fee_type_totals) { enterprise_fee_type_totals } + allow(service).to receive(:list) { enterprise_fee_type_totals } end it "generates CSV header" do diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb index 3cc30f6abb..733b3dec74 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -15,9 +15,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer let!(:renderer) { described_class.new(service) } let!(:enterprise_fee_type_totals) do - instance = report_klass::ReportData::EnterpriseFeeTypeTotals.new - instance.tap do |totals| - totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + [ + report_klass::ReportData::EnterpriseFeeTypeTotal.new( fee_type: "Fee Type A", enterprise_name: "Enterprise A", fee_name: "Fee A", @@ -26,9 +25,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer fee_calculated_on_transfer_through_name: "Transfer Enterprise A", tax_category_name: "Tax Category A", total_amount: "1.00" - ) - - totals.list << report_klass::ReportData::EnterpriseFeeTypeTotal.new( + ), + report_klass::ReportData::EnterpriseFeeTypeTotal.new( fee_type: "Fee Type B", enterprise_name: "Enterprise B", fee_name: "Fee C", @@ -38,13 +36,13 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer tax_category_name: "Tax Category G", total_amount: "2.00" ) - end + ] end let(:current_user) { nil } before do - allow(service).to receive(:enterprise_fee_type_totals) { enterprise_fee_type_totals } + allow(service).to receive(:list) { enterprise_fee_type_totals } end it "generates header values" do diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 8b3d522e34..9d196bbb11 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -89,9 +89,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:service) { described_class.new(permissions, parameters) } it "groups and sorts entries correctly" do - totals = service.enterprise_fee_type_totals + totals = service.list - expect(totals.list.length).to eq(16) + expect(totals.length).to eq(16) # Data is sorted by the following, in order: # * fee_type @@ -139,7 +139,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do ] expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals.list[row_index], expected_attributes) + expect_total_attributes(totals[row_index], expected_attributes) end end end @@ -171,7 +171,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:current_user) { create(:admin_user) } it "includes all order cycles" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 2, fee_type: "Shipment") expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") @@ -183,7 +183,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:current_user) { distributor_a.owner } it "does not include unrelated order cycles" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_type: "Shipment") expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") @@ -225,7 +225,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:parameters_attributes) { { start_at: timestamp } } it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer A") expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") @@ -237,7 +237,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:parameters_attributes) { { end_at: timestamp } } it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer A") expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") @@ -267,7 +267,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:parameters_attributes) { { distributor_ids: [distributor_a.id, distributor_b.id] } } it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") @@ -305,7 +305,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:parameters_attributes) { { producer_ids: [producer_a.id, producer_b.id] } } it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_name: "Fee A", enterprise_name: "Producer A") expect_total_matches(totals, 1, fee_name: "Fee B", enterprise_name: "Producer B") @@ -342,7 +342,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:parameters_attributes) { { order_cycle_ids: [order_cycle_a.id, order_cycle_b.id] } } it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") @@ -362,7 +362,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:parameters_attributes) { { enterprise_fee_ids: [fee_a.id, fee_b.id] } } it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_name: "Fee A") expect_total_matches(totals, 1, fee_name: "Fee B") @@ -390,7 +390,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_name: "Shipping A") expect_total_matches(totals, 1, fee_name: "Shipping B") @@ -418,7 +418,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end it "filters entries" do - totals = service.enterprise_fee_type_totals.list + totals = service.list expect_total_matches(totals, 1, fee_name: "Payment A") expect_total_matches(totals, 1, fee_name: "Payment B") From c81be75530929670aa45ce967920b7fe58f3f530 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 17:33:07 +0800 Subject: [PATCH 083/108] Move engine libraries except Engine and Version to services --- .rubocop_styleguide.yml | 4 ++-- .../admin/reports/enterprise_fee_summaries_controller.rb | 7 ------- .../reports/enterprise_fee_summary/authorizer.rb | 0 .../enterprise_fee_type_total_summarizer.rb | 0 .../reports/enterprise_fee_summary/parameters.rb | 0 .../reports/enterprise_fee_summary/permissions.rb | 0 .../enterprise_fee_summary/renderers/csv_renderer.rb | 0 .../enterprise_fee_summary/renderers/html_renderer.rb | 0 .../report_data/enterprise_fee_type_total.rb | 0 .../reports/enterprise_fee_summary/report_service.rb | 4 ---- .../reports/enterprise_fee_summary/scope.rb | 0 .../reports/enterprise_fee_summary/authorizer_spec.rb | 4 ---- .../reports/enterprise_fee_summary/parameters_spec.rb | 1 - .../reports/enterprise_fee_summary/permissions_spec.rb | 2 -- .../enterprise_fee_summary/renderers/csv_renderer_spec.rb | 5 ----- .../enterprise_fee_summary/renderers/html_renderer_spec.rb | 5 ----- .../report_data/enterprise_fee_type_total_spec.rb | 2 -- .../reports/enterprise_fee_summary/report_service_spec.rb | 4 ---- engines/order_management/spec/spec_helper.rb | 1 + 19 files changed, 3 insertions(+), 36 deletions(-) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/authorizer.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/parameters.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/permissions.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb (100%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/report_service.rb (84%) rename engines/order_management/{lib => app/services}/order_management/reports/enterprise_fee_summary/scope.rb (100%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/authorizer_spec.rb (95%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/parameters_spec.rb (97%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/permissions_spec.rb (99%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb (90%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb (88%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb (93%) rename engines/order_management/spec/{lib => services}/order_management/reports/enterprise_fee_summary/report_service_spec.rb (99%) diff --git a/.rubocop_styleguide.yml b/.rubocop_styleguide.yml index ac4a0d1a45..accf9e6314 100644 --- a/.rubocop_styleguide.yml +++ b/.rubocop_styleguide.yml @@ -195,7 +195,7 @@ Metrics/BlockNesting: Metrics/ClassLength: Max: 100 Exclude: - - engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb + - engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb Metrics/ModuleLength: Max: 100 @@ -206,7 +206,7 @@ Metrics/CyclomaticComplexity: Metrics/MethodLength: Max: 10 Exclude: - - engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb + - engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb Metrics/ParameterLists: Max: 5 diff --git a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb index db88a3bca0..e6b750b904 100644 --- a/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb +++ b/app/controllers/spree/admin/reports/enterprise_fee_summaries_controller.rb @@ -1,10 +1,3 @@ -require "order_management/reports/enterprise_fee_summary/parameters" -require "order_management/reports/enterprise_fee_summary/permissions" -require "order_management/reports/enterprise_fee_summary/authorizer" -require "order_management/reports/enterprise_fee_summary/report_service" -require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" -require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" - module Spree module Admin module Reports diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/authorizer.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/parameters.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/permissions.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb similarity index 84% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb index b0ddc1c507..4780629a45 100644 --- a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb @@ -1,7 +1,3 @@ -require "order_management/reports/enterprise_fee_summary/scope" -require "order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer" -require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total" - module OrderManagement module Reports module EnterpriseFeeSummary diff --git a/engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb similarity index 100% rename from engines/order_management/lib/order_management/reports/enterprise_fee_summary/scope.rb rename to engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb similarity index 95% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb index e1b857868e..62d6e74465 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/authorizer_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb @@ -1,9 +1,5 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/parameters" -require "order_management/reports/enterprise_fee_summary/permissions" -require "order_management/reports/enterprise_fee_summary/authorizer" - describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } let(:user) { create(:user) } diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb similarity index 97% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb index d490393f7b..5128c92c63 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -1,7 +1,6 @@ require "spec_helper" require "date_time_string_validator" -require "order_management/reports/enterprise_fee_summary/parameters" describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do describe "validation" do diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb similarity index 99% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb index 3ec63e27ca..2024da96c7 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/permissions_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb @@ -1,7 +1,5 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/permissions" - describe OrderManagement::Reports::EnterpriseFeeSummary::Permissions do let!(:order_cycle) { create(:simple_order_cycle) } let!(:incoming_exchange) { create(:exchange, incoming: true, order_cycle: order_cycle) } diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb similarity index 90% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index c12aa844bb..d265de4933 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -1,10 +1,5 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/parameters" -require "order_management/reports/enterprise_fee_summary/permissions" -require "order_management/reports/enterprise_fee_summary/report_service" -require "order_management/reports/enterprise_fee_summary/renderers/csv_renderer" - describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb similarity index 88% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb index 733b3dec74..5b9c62b03c 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -1,10 +1,5 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/parameters" -require "order_management/reports/enterprise_fee_summary/permissions" -require "order_management/reports/enterprise_fee_summary/report_service" -require "order_management/reports/enterprise_fee_summary/renderers/html_renderer" - describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb similarity index 93% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb index 85fd826613..9a30660692 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb @@ -1,7 +1,5 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total" - describe OrderManagement::Reports::EnterpriseFeeSummary::ReportData::EnterpriseFeeTypeTotal do it "sorts instances according to their attributes" do instance_a = described_class.new( diff --git a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb similarity index 99% rename from engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb rename to engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 9d196bbb11..b74d28c40d 100644 --- a/engines/order_management/spec/lib/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -1,9 +1,5 @@ require "spec_helper" -require "order_management/reports/enterprise_fee_summary/report_service" -require "order_management/reports/enterprise_fee_summary/permissions" -require "order_management/reports/enterprise_fee_summary/parameters" - describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } diff --git a/engines/order_management/spec/spec_helper.rb b/engines/order_management/spec/spec_helper.rb index c4ab08aaf9..1be4512650 100644 --- a/engines/order_management/spec/spec_helper.rb +++ b/engines/order_management/spec/spec_helper.rb @@ -1,5 +1,6 @@ ENV["RAILS_ENV"] = "test" +require "order_management" require "../../spec/spec_helper" # Require factories in Spree and main application. From 6378afdddb976cf8c2bda7b56fd3fcdd1468114d Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 17:42:05 +0800 Subject: [PATCH 084/108] Address Rubocop violation --- .../reports/enterprise_fee_summary/parameters_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb index 5128c92c63..22e54cfbcb 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -76,7 +76,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do it "raises error when the parameters are not allowed" do parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s]) - expect { parameters.authorize!(permissions) }.to raise_error(Reports::Authorizer::ParameterNotAllowedError) + expect { parameters.authorize!(permissions) } + .to raise_error(Reports::Authorizer::ParameterNotAllowedError) end end From eaa780042775d250adca4417bbf3ef11490d8d65 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 18:55:23 +0800 Subject: [PATCH 085/108] Do not assign I18n translation to constants The preferred language could change dynamically. --- .../reports/enterprise_fee_summary/authorizer.rb | 9 +++++---- .../reports/enterprise_fee_summary/parameters.rb | 12 +++++++----- .../enterprise_fee_summary/parameters_spec.rb | 3 ++- .../enterprise_fee_summaries_controller_spec.rb | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb index 8b091577dd..e129905dc6 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb @@ -2,9 +2,10 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Authorizer < ::Reports::Authorizer - @i18n_scope = "order_management.reports.enterprise_fee_summary" - - PARAMETER_NOT_ALLOWED_ERROR = I18n.t("parameter_not_allowed_error", scope: @i18n_scope) + def self.parameter_not_allowed_error_message + i18n_scope = "order_management.reports.enterprise_fee_summary" + I18n.t("parameter_not_allowed_error", scope: i18n_scope) + end def authorize! authorize_by_distribution! @@ -27,7 +28,7 @@ module OrderManagement def require_ids_allowed(array, allowed_objects) error_klass = ::Reports::Authorizer::ParameterNotAllowedError - error_message = PARAMETER_NOT_ALLOWED_ERROR + error_message = self.class.parameter_not_allowed_error_message ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? raise error_klass, error_message unless ids_allowed diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb index 2082d64c9e..cc12d9be1f 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb @@ -2,10 +2,6 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Parameters < ::Reports::Parameters::Base - @i18n_scope = "order_management.reports.enterprise_fee_summary" - - DATE_END_BEFORE_START_ERROR = I18n.t("date_end_before_start_error", scope: @i18n_scope) - extend ActiveModel::Naming extend ActiveModel::Translation include ActiveModel::Validations @@ -23,6 +19,11 @@ module OrderManagement validate :require_valid_datetime_range + def self.date_end_before_start_error_message + i18n_scope = "order_management.reports.enterprise_fee_summary" + I18n.t("date_end_before_start_error", scope: i18n_scope) + end + def initialize(attributes = {}) self.distributor_ids = [] self.producer_ids = [] @@ -44,7 +45,8 @@ module OrderManagement def require_valid_datetime_range return if start_at.blank? || end_at.blank? - errors.add(:end_at, DATE_END_BEFORE_START_ERROR) unless start_at < end_at + error_message = self.class.date_end_before_start_error_message + errors.add(:end_at, error_message) unless start_at < end_at end # Remove the blank strings that Rails multiple selects add by default to diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb index 22e54cfbcb..6fd3c8b9a7 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb @@ -46,7 +46,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do allow(subject).to receive(:end_at) { (now - 1.hour).to_s } expect(subject).not_to be_valid - expect(subject.errors[:end_at]).to eq([described_class::DATE_END_BEFORE_START_ERROR]) + error_message = described_class.date_end_before_start_error_message + expect(subject.errors[:end_at]).to eq([error_message]) end it "does not add error when start_at is before end_at" do diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb index 8cc159852e..5a5747041b 100644 --- a/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb @@ -49,7 +49,7 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control it "renders the report form with an error" do post :create, report: { distributor_ids: [other_distributor.id] }, report_format: "csv" - expect(flash[:error]).to eq(report_klass::Authorizer::PARAMETER_NOT_ALLOWED_ERROR) + expect(flash[:error]).to eq(report_klass::Authorizer.parameter_not_allowed_error_message) expect(response).to render_template(new_template_path) end end From 4a73b1d26c856ca1894d4769f31d72e585472162 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 14 Dec 2018 20:06:08 +0800 Subject: [PATCH 086/108] Do not include incomplete orders in report --- .../order_management/reports/enterprise_fee_summary/scope.rb | 1 + .../reports/enterprise_fee_summary/report_service_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb index b218c1f677..cf343063a9 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb @@ -82,6 +82,7 @@ module OrderManagement ON ( spree_adjustments.adjustable_type = 'Spree::Order' AND spree_orders.id = spree_adjustments.adjustable_id + AND spree_orders.completed_at IS NOT NULL ) JOIN_STRING ) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index b74d28c40d..23c2c093a2 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -77,6 +77,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do describe "grouping and sorting of entries" do let!(:customer_order) { prepare_order(customer: customer) } + let!(:customer_incomplete_order) { setup_order(customer: customer) } let!(:second_customer_order) { prepare_order(customer: customer) } let!(:other_customer_order) { prepare_order(customer: another_customer) } From 53c458f817b121705f66676b2a34a87be7e0c4c9 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 4 Jan 2019 14:53:12 +0800 Subject: [PATCH 087/108] Clean up setup data for enterprise fee summary tests --- .../report_service_spec.rb | 147 +++++++++--------- 1 file changed, 77 insertions(+), 70 deletions(-) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 23c2c093a2..af9ae38362 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + # Basic data. let!(:shipping_method) do create(:shipping_method, name: "Sample Shipping Method", calculator: per_item_calculator(1.0)) end @@ -11,80 +12,91 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do create(:payment_method, name: "Sample Payment Method", calculator: per_item_calculator(2.0)) end + # Create enterprises. let!(:distributor) do create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| payment_method.distributors << enterprise shipping_method.distributors << enterprise end end - let!(:distributor_fees) do - [ - create(:enterprise_fee, name: "Included Distributor Fee 1", enterprise: distributor, - fee_type: "admin", calculator: per_item_calculator(4.0), - tax_category: prepare_tax_category("Sample Distributor Tax")), - create(:enterprise_fee, name: "Included Distributor Fee 2", enterprise: distributor, - fee_type: "sales", calculator: per_item_calculator(8.0), - inherits_tax_category: true), - create(:enterprise_fee, name: "Excluded Distributor Fee", enterprise: distributor, - fee_type: "sales", calculator: per_item_calculator(16.0)) - ] - end - let!(:producer) { create(:supplier_enterprise, name: "Sample Producer") } - let!(:producer_fees) do - [ - create(:enterprise_fee, name: "Excluded Producer Fee", enterprise: producer, - fee_type: "admin", calculator: per_item_calculator(32.0)), - create(:enterprise_fee, name: "Included Producer Fee 1", enterprise: producer, - fee_type: "sales", calculator: per_item_calculator(64.0), - tax_category: prepare_tax_category("Sample Producer Tax")), - create(:enterprise_fee, name: "Included Producer Fee 2", enterprise: producer, - fee_type: "sales", calculator: per_item_calculator(128.0), - inherits_tax_category: true) - ] - end - let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } - let!(:coordinator_fees) do - [ - create(:enterprise_fee, name: "Excluded Coordinator Fee", enterprise: coordinator, - fee_type: "admin", calculator: per_item_calculator(256.0)), - create(:enterprise_fee, name: "Included Coordinator Fee 1", enterprise: coordinator, - fee_type: "admin", calculator: per_item_calculator(512.0), - tax_category: prepare_tax_category("Sample Coordinator Tax")), - create(:enterprise_fee, name: "Included Coordinator Fee 2", enterprise: coordinator, - fee_type: "sales", calculator: per_item_calculator(1024.0), - inherits_tax_category: true) - ] - end - - let!(:order_cycle) do - create(:simple_order_cycle, coordinator: coordinator, - coordinator_fees: [coordinator_fees[1], coordinator_fees[2]]) + + # Add some fee noise. + let!(:other_distributor_fee) do + create(:enterprise_fee, enterprise: distributor, calculator: per_item_calculator(1)) + end + let!(:other_producer_fee) do + create(:enterprise_fee, enterprise: producer, calculator: per_item_calculator(1)) + end + let!(:other_coordinator_fee) do + create(:enterprise_fee, enterprise: coordinator, calculator: per_item_calculator(1)) end + # Set up other requirements for ordering. + let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } let!(:product) { create(:product, tax_category: prepare_tax_category("Sample Product Tax")) } + let!(:variant) { prepare_variant } - let!(:variant) do - prepare_variant(incoming_exchange_fees: [producer_fees[1], producer_fees[2]], - outgoing_exchange_fees: [distributor_fees[0], distributor_fees[1]]) - end - + # Create customers. let!(:customer) { create(:customer, name: "Sample Customer") } let!(:another_customer) { create(:customer, name: "Another Customer") } + # Setup up permissions and report. let!(:current_user) { create(:admin_user) } + let(:permissions) { report_klass::Permissions.new(current_user) } + let(:parameters) { report_klass::Parameters.new } + let(:service) { described_class.new(permissions, parameters) } + describe "grouping and sorting of entries" do + let!(:order_cycle) do + create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: order_cycle_fees) + end + + let!(:variant) do + prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, + outgoing_exchange_fees: variant_outgoing_exchange_fees) + end + + let!(:order_cycle_fees) do + [ + create(:enterprise_fee, name: "Coordinator Fee 1", enterprise: coordinator, + fee_type: "admin", calculator: per_item_calculator(512.0), + tax_category: prepare_tax_category("Sample Coordinator Tax")), + create(:enterprise_fee, name: "Coordinator Fee 2", enterprise: coordinator, + fee_type: "sales", calculator: per_item_calculator(1024.0), + inherits_tax_category: true) + ] + end + + let!(:variant_incoming_exchange_fees) do + [ + create(:enterprise_fee, name: "Producer Fee 1", enterprise: producer, fee_type: "sales", + calculator: per_item_calculator(64.0), + tax_category: prepare_tax_category("Sample Producer Tax")), + create(:enterprise_fee, name: "Producer Fee 2", enterprise: producer, fee_type: "sales", + calculator: per_item_calculator(128.0), + inherits_tax_category: true) + ] + end + + let!(:variant_outgoing_exchange_fees) do + [ + create(:enterprise_fee, name: "Distributor Fee 1", enterprise: distributor, + fee_type: "admin", calculator: per_item_calculator(4.0), + tax_category: prepare_tax_category("Sample Distributor Tax")), + create(:enterprise_fee, name: "Distributor Fee 2", enterprise: distributor, + fee_type: "sales", calculator: per_item_calculator(8.0), + inherits_tax_category: true) + ] + end + let!(:customer_order) { prepare_order(customer: customer) } let!(:customer_incomplete_order) { setup_order(customer: customer) } let!(:second_customer_order) { prepare_order(customer: customer) } let!(:other_customer_order) { prepare_order(customer: another_customer) } - let(:permissions) { report_klass::Permissions.new(current_user) } - let(:parameters) { report_klass::Parameters.new } - let(:service) { described_class.new(permissions, parameters) } - it "groups and sorts entries correctly" do totals = service.list @@ -101,33 +113,33 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do # * total_amount expected_result = [ - ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Another Customer", + ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Another Customer", "Coordinator", "All", "Sample Coordinator Tax", "512.00"], - ["Admin", "Sample Coordinator", "Included Coordinator Fee 1", "Sample Customer", + ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Sample Customer", "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], - ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Another Customer", + ["Admin", "Sample Distributor", "Distributor Fee 1", "Another Customer", "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], - ["Admin", "Sample Distributor", "Included Distributor Fee 1", "Sample Customer", + ["Admin", "Sample Distributor", "Distributor Fee 1", "Sample Customer", "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", nil, nil, nil, "2.00"], ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", nil, nil, nil, "4.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Another Customer", + ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Another Customer", "Coordinator", "All", "Sample Product Tax", "1024.00"], - ["Sales", "Sample Coordinator", "Included Coordinator Fee 2", "Sample Customer", + ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", "Coordinator", "All", "Sample Product Tax", "2048.00"], - ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Another Customer", + ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", "Outgoing", "Sample Coordinator", "Sample Product Tax", "8.00"], - ["Sales", "Sample Distributor", "Included Distributor Fee 2", "Sample Customer", + ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", "Outgoing", "Sample Coordinator", "Sample Product Tax", "16.00"], - ["Sales", "Sample Producer", "Included Producer Fee 1", "Another Customer", + ["Sales", "Sample Producer", "Producer Fee 1", "Another Customer", "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], - ["Sales", "Sample Producer", "Included Producer Fee 1", "Sample Customer", + ["Sales", "Sample Producer", "Producer Fee 1", "Sample Customer", "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], - ["Sales", "Sample Producer", "Included Producer Fee 2", "Another Customer", + ["Sales", "Sample Producer", "Producer Fee 2", "Another Customer", "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], - ["Sales", "Sample Producer", "Included Producer Fee 2", "Sample Customer", + ["Sales", "Sample Producer", "Producer Fee 2", "Sample Customer", "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", nil, nil, "Platform Rate", "1.00"], @@ -160,10 +172,6 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } - let(:permissions) { report_klass::Permissions.new(current_user) } - let(:parameters) { report_klass::Parameters.new({}) } - let(:service) { described_class.new(permissions, parameters) } - context "when admin" do let!(:current_user) { create(:admin_user) } @@ -189,9 +197,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end describe "filters entries correctly" do - let(:permissions) { report_klass::Permissions.new(current_user) } let(:parameters) { report_klass::Parameters.new(parameters_attributes) } - let(:service) { described_class.new(permissions, parameters) } context "filtering by completion date" do let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } @@ -352,7 +358,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: distributor) } let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: distributor) } - let!(:variant) { prepare_variant(outgoing_exchange_fees: [fee_a, fee_b, fee_c]) } + let!(:variant) { prepare_variant(outgoing_exchange_fees: variant_outgoing_exchange_fees) } + let!(:variant_outgoing_exchange_fees) { [fee_a, fee_b, fee_c] } let!(:order) { prepare_order(variant: variant) } From 61ec43f868bf5b41f2d5bde1ecbb5d44bcb4fdbe Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 4 Jan 2019 16:19:10 +0800 Subject: [PATCH 088/108] Add enterprise fee factory trait for per item rate We should not presume what is not default for an enterprise fee object. --- spec/factories.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/factories.rb b/spec/factories.rb index eee3cb553f..931a727d7b 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -270,6 +270,12 @@ FactoryBot.define do calculator { build(:calculator_per_item, preferred_amount: amount) } after(:create) { |ef| ef.calculator.save! } + + trait :per_item do + transient { amount 1 } + + calculator { build(:calculator_per_item, preferred_amount: amount) } + end end factory :product_distribution, :class => ProductDistribution do From 5dda3b67eb22ab0ebc8df3ee3acbd1a28efc0896 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 4 Jan 2019 17:00:11 +0800 Subject: [PATCH 089/108] Use factory for tax category in entperprise fee summary tests --- .../report_service_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index af9ae38362..378c17444c 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -35,7 +35,8 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do # Set up other requirements for ordering. let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } - let!(:product) { create(:product, tax_category: prepare_tax_category("Sample Product Tax")) } + let!(:product) { create(:product, tax_category: product_tax_category) } + let!(:product_tax_category) { create(:tax_category, name: "Sample Product Tax") } let!(:variant) { prepare_variant } # Create customers. @@ -63,34 +64,37 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do [ create(:enterprise_fee, name: "Coordinator Fee 1", enterprise: coordinator, fee_type: "admin", calculator: per_item_calculator(512.0), - tax_category: prepare_tax_category("Sample Coordinator Tax")), + tax_category: coordinator_tax_category), create(:enterprise_fee, name: "Coordinator Fee 2", enterprise: coordinator, fee_type: "sales", calculator: per_item_calculator(1024.0), inherits_tax_category: true) ] end + let!(:coordinator_tax_category) { create(:tax_category, name: "Sample Coordinator Tax") } let!(:variant_incoming_exchange_fees) do [ create(:enterprise_fee, name: "Producer Fee 1", enterprise: producer, fee_type: "sales", calculator: per_item_calculator(64.0), - tax_category: prepare_tax_category("Sample Producer Tax")), + tax_category: producer_tax_category), create(:enterprise_fee, name: "Producer Fee 2", enterprise: producer, fee_type: "sales", calculator: per_item_calculator(128.0), inherits_tax_category: true) ] end + let!(:producer_tax_category) { create(:tax_category, name: "Sample Producer Tax") } let!(:variant_outgoing_exchange_fees) do [ create(:enterprise_fee, name: "Distributor Fee 1", enterprise: distributor, fee_type: "admin", calculator: per_item_calculator(4.0), - tax_category: prepare_tax_category("Sample Distributor Tax")), + tax_category: distributor_tax_category), create(:enterprise_fee, name: "Distributor Fee 2", enterprise: distributor, fee_type: "sales", calculator: per_item_calculator(8.0), inherits_tax_category: true) ] end + let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } let!(:customer_order) { prepare_order(customer: customer) } let!(:customer_incomplete_order) { setup_order(customer: customer) } @@ -445,10 +449,6 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do expect(count_totals(totals, attributes)).to eq(count) end - def prepare_tax_category(name) - create(:tax_category, name: name) - end - def default_order_options { customer: customer, distributor: distributor, order_cycle: order_cycle, shipping_method: shipping_method, variant: variant } From a3aa594c421393759ddd32cd00c697426e7550c4 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 4 Jan 2019 17:49:55 +0800 Subject: [PATCH 090/108] Use enterprise fee factory trait where applicable --- .../report_service_spec.rb | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 378c17444c..c6f32aac94 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -23,15 +23,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } # Add some fee noise. - let!(:other_distributor_fee) do - create(:enterprise_fee, enterprise: distributor, calculator: per_item_calculator(1)) - end - let!(:other_producer_fee) do - create(:enterprise_fee, enterprise: producer, calculator: per_item_calculator(1)) - end - let!(:other_coordinator_fee) do - create(:enterprise_fee, enterprise: coordinator, calculator: per_item_calculator(1)) - end + let!(:other_distributor_fee) { create(:enterprise_fee, :per_item, enterprise: distributor) } + let!(:other_producer_fee) { create(:enterprise_fee, :per_item, enterprise: producer) } + let!(:other_coordinator_fee) { create(:enterprise_fee, :per_item, enterprise: coordinator) } # Set up other requirements for ordering. let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } @@ -62,36 +56,36 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:order_cycle_fees) do [ - create(:enterprise_fee, name: "Coordinator Fee 1", enterprise: coordinator, - fee_type: "admin", calculator: per_item_calculator(512.0), - tax_category: coordinator_tax_category), - create(:enterprise_fee, name: "Coordinator Fee 2", enterprise: coordinator, - fee_type: "sales", calculator: per_item_calculator(1024.0), - inherits_tax_category: true) + create(:enterprise_fee, :per_item, name: "Coordinator Fee 1", enterprise: coordinator, + fee_type: "admin", amount: 512.0, + tax_category: coordinator_tax_category), + create(:enterprise_fee, :per_item, name: "Coordinator Fee 2", enterprise: coordinator, + fee_type: "sales", amount: 1024.0, + inherits_tax_category: true) ] end let!(:coordinator_tax_category) { create(:tax_category, name: "Sample Coordinator Tax") } let!(:variant_incoming_exchange_fees) do [ - create(:enterprise_fee, name: "Producer Fee 1", enterprise: producer, fee_type: "sales", - calculator: per_item_calculator(64.0), - tax_category: producer_tax_category), - create(:enterprise_fee, name: "Producer Fee 2", enterprise: producer, fee_type: "sales", - calculator: per_item_calculator(128.0), - inherits_tax_category: true) + create(:enterprise_fee, :per_item, name: "Producer Fee 1", enterprise: producer, + fee_type: "sales", amount: 64.0, + tax_category: producer_tax_category), + create(:enterprise_fee, :per_item, name: "Producer Fee 2", enterprise: producer, + fee_type: "sales", amount: 128.0, + inherits_tax_category: true) ] end let!(:producer_tax_category) { create(:tax_category, name: "Sample Producer Tax") } let!(:variant_outgoing_exchange_fees) do [ - create(:enterprise_fee, name: "Distributor Fee 1", enterprise: distributor, - fee_type: "admin", calculator: per_item_calculator(4.0), - tax_category: distributor_tax_category), - create(:enterprise_fee, name: "Distributor Fee 2", enterprise: distributor, - fee_type: "sales", calculator: per_item_calculator(8.0), - inherits_tax_category: true) + create(:enterprise_fee, :per_item, name: "Distributor Fee 1", enterprise: distributor, + fee_type: "admin", amount: 4.0, + tax_category: distributor_tax_category), + create(:enterprise_fee, :per_item, name: "Distributor Fee 2", enterprise: distributor, + fee_type: "sales", amount: 8.0, + inherits_tax_category: true) ] end let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } From ce030aeb07ed98e5371152ceba3c3ac777d217f0 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 11 Jan 2019 08:19:03 +0800 Subject: [PATCH 091/108] Move order setup to new factory traits --- .../report_service_spec.rb | 24 ++++------------- spec/factories/orders.rb | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 spec/factories/orders.rb diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index c6f32aac94..7d89c56a7b 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -449,28 +449,14 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end def setup_order(options = {}) - target = default_order_options.merge(options) - - create(:order, customer: target[:customer], distributor: target[:distributor], - order_cycle: target[:order_cycle], - shipping_method: target[:shipping_method]).tap do |order| - create(:line_item, order: order, variant: target[:variant]) - order.reload - end + target_options = default_order_options.merge(options) + create(:order, :with_line_item, target_options) end def prepare_order(options = {}) - order = setup_order(options) - complete_order(order, options) - order.reload - end - - def complete_order(order, options) - order.create_shipment! - create(:payment, state: "checkout", order: order, amount: order.total, - payment_method: options[:payment_method] || payment_method) - order.update_distribution_charge! - while !order.completed? do break unless order.next! end + factory_trait_options = { payment_method: payment_method } + target_options = default_order_options.merge(factory_trait_options).merge(options) + create(:order, :with_line_item, :completed, target_options) end def default_variant_options diff --git a/spec/factories/orders.rb b/spec/factories/orders.rb new file mode 100644 index 0000000000..4b2a33f3d0 --- /dev/null +++ b/spec/factories/orders.rb @@ -0,0 +1,27 @@ +FactoryBot.modify do + factory :order do + trait :with_line_item do + transient do + variant { FactoryGirl.create(:variant) } + end + + after(:create) do |order, evaluator| + create(:line_item, order: order, variant: evaluator.variant) + end + end + + trait :completed do + transient do + payment_method { create(:payment_method, distributors: [distributor]) } + end + + after(:create) do |order, evaluator| + order.create_shipment! + create(:payment, state: "checkout", order: order, amount: order.total, + payment_method: evaluator.payment_method) + order.update_distribution_charge! + while !order.completed? do break unless order.next! end + end + end + end +end From f36d13a736591bf2de3da73f9cb5ea60fea934e0 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 11 Jan 2019 08:23:31 +0800 Subject: [PATCH 092/108] Rename helper for setting up incomplete order --- .../reports/enterprise_fee_summary/report_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 7d89c56a7b..1edbd69449 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -91,7 +91,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } let!(:customer_order) { prepare_order(customer: customer) } - let!(:customer_incomplete_order) { setup_order(customer: customer) } + let!(:customer_incomplete_order) { prepare_incomplete_order(customer: customer) } let!(:second_customer_order) { prepare_order(customer: customer) } let!(:other_customer_order) { prepare_order(customer: another_customer) } @@ -448,7 +448,7 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do shipping_method: shipping_method, variant: variant } end - def setup_order(options = {}) + def prepare_incomplete_order(options = {}) target_options = default_order_options.merge(options) create(:order, :with_line_item, target_options) end From d2e49b8b49a2be270d273d0f2a746581269d62ec Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 11 Jan 2019 13:23:28 +0800 Subject: [PATCH 093/108] Move variant setup to new factory trait --- .../report_service_spec.rb | 38 ++----------------- spec/factories/variants.rb | 34 +++++++++++++++++ 2 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 spec/factories/variants.rb diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 1edbd69449..4b4d032b2d 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -460,43 +460,13 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end def default_variant_options - { product: product, producer: producer, coordinator: coordinator, distributor: distributor, - order_cycle: order_cycle } + { product: product, producer: producer, is_master: false, coordinator: coordinator, + distributor: distributor, order_cycle: order_cycle } end def prepare_variant(options = {}) - target = default_variant_options.merge(options) - - create(:variant, product: target[:product], is_master: false).tap do |variant| - exchange_options = { producer: target[:producer], coordinator: target[:coordinator], - distributor: target[:distributor], - incoming_exchange_fees: target[:incoming_exchange_fees], - outgoing_exchange_fees: target[:outgoing_exchange_fees] } - setup_exchanges(target[:order_cycle], variant, exchange_options) - end - end - - def setup_exchanges(order_cycle, variant, options) - setup_exchange(order_cycle, variant, true, sender: options[:producer], - receiver: options[:coordinator], - enterprise_fees: options[:incoming_exchange_fees]) - setup_exchange(order_cycle, variant, false, sender: options[:coordinator], - receiver: options[:distributor], - enterprise_fees: options[:outgoing_exchange_fees]) - end - - def setup_exchange(order_cycle, variant, incoming, options) - exchange_attributes = { order_cycle_id: order_cycle.id, incoming: incoming, - sender_id: options[:sender].id, receiver_id: options[:receiver].id } - exchange = Exchange.where(exchange_attributes).first || create(:exchange, exchange_attributes) - exchange.variants << variant - attach_enterprise_fees(exchange, options[:enterprise_fees] || []) - end - - def attach_enterprise_fees(exchange, enterprise_fees) - enterprise_fees.each do |enterprise_fee| - exchange.enterprise_fees << enterprise_fee - end + target_options = default_variant_options.merge(options) + create(:variant, :with_order_cycle, target_options) end def per_item_calculator(amount) diff --git a/spec/factories/variants.rb b/spec/factories/variants.rb new file mode 100644 index 0000000000..6eaeb9a26b --- /dev/null +++ b/spec/factories/variants.rb @@ -0,0 +1,34 @@ +FactoryBot.modify do + factory :variant do + trait :with_order_cycle do + transient do + order_cycle { create(:order_cycle) } + producer { product.supplier } + coordinator { create(:distributor_enterprise) } + distributor { create(:distributor_enterprise) } + incoming_exchange_fees { [] } + outgoing_exchange_fees { [] } + end + + after(:create) do |variant, evaluator| + exchange_attributes = { order_cycle_id: evaluator.order_cycle.id, incoming: true, + sender_id: evaluator.producer.id, + receiver_id: evaluator.coordinator.id } + exchange = Exchange.where(exchange_attributes).first_or_create!(exchange_attributes) + exchange.variants << variant + evaluator.incoming_exchange_fees.each do |enterprise_fee| + exchange.enterprise_fees << enterprise_fee + end + + exchange_attributes = { order_cycle_id: evaluator.order_cycle.id, incoming: false, + sender_id: evaluator.coordinator.id, + receiver_id: evaluator.distributor.id } + exchange = Exchange.where(exchange_attributes).first_or_create!(exchange_attributes) + exchange.variants << variant + (evaluator.outgoing_exchange_fees || []).each do |enterprise_fee| + exchange.enterprise_fees << enterprise_fee + end + end + end + end +end From 625069cb5ad15aad7142c2b83b48cd639185911b Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 11 Jan 2019 13:43:15 +0800 Subject: [PATCH 094/108] Add :per_item factory trait to shipping and payment methods --- .../enterprise_fee_summary/report_service_spec.rb | 8 ++------ spec/factories.rb | 6 ------ spec/factories/calculators.rb | 12 ++++++++++++ 3 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 spec/factories/calculators.rb diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 4b4d032b2d..d9cf0220ad 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -5,11 +5,11 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do # Basic data. let!(:shipping_method) do - create(:shipping_method, name: "Sample Shipping Method", calculator: per_item_calculator(1.0)) + create(:shipping_method, :per_item, amount: 1, name: "Sample Shipping Method") end let!(:payment_method) do - create(:payment_method, name: "Sample Payment Method", calculator: per_item_calculator(2.0)) + create(:payment_method, :per_item, amount: 2, name: "Sample Payment Method") end # Create enterprises. @@ -469,10 +469,6 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do create(:variant, :with_order_cycle, target_options) end - def per_item_calculator(amount) - Spree::Calculator::PerItem.new(preferred_amount: amount) - end - def count_totals(totals, attributes) totals.count do |data| attributes.all? do |attribute_name, attribute_value| diff --git a/spec/factories.rb b/spec/factories.rb index 931a727d7b..eee3cb553f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -270,12 +270,6 @@ FactoryBot.define do calculator { build(:calculator_per_item, preferred_amount: amount) } after(:create) { |ef| ef.calculator.save! } - - trait :per_item do - transient { amount 1 } - - calculator { build(:calculator_per_item, preferred_amount: amount) } - end end factory :product_distribution, :class => ProductDistribution do diff --git a/spec/factories/calculators.rb b/spec/factories/calculators.rb new file mode 100644 index 0000000000..50ae6f5fce --- /dev/null +++ b/spec/factories/calculators.rb @@ -0,0 +1,12 @@ +attach_per_item_trait = proc do + trait :per_item do + transient { amount 1 } + calculator { build(:calculator_per_item, preferred_amount: amount) } + end +end + +FactoryBot.modify do + factory :payment_method, &attach_per_item_trait + factory :shipping_method, &attach_per_item_trait + factory :enterprise_fee, &attach_per_item_trait +end From c92d202612412362b2022f89348d952148babe49 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 16 Jan 2019 15:40:27 +0800 Subject: [PATCH 095/108] Rename files for factories in spec/factories/ Make these follow the "#{SINGULAR_OBJECT}_factory.rb" convention. --- .../{calculators.rb => calculated_adjustment_factory.rb} | 0 spec/factories/{orders.rb => order_factory.rb} | 0 spec/factories/{variants.rb => variant_factory.rb} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename spec/factories/{calculators.rb => calculated_adjustment_factory.rb} (100%) rename spec/factories/{orders.rb => order_factory.rb} (100%) rename spec/factories/{variants.rb => variant_factory.rb} (100%) diff --git a/spec/factories/calculators.rb b/spec/factories/calculated_adjustment_factory.rb similarity index 100% rename from spec/factories/calculators.rb rename to spec/factories/calculated_adjustment_factory.rb diff --git a/spec/factories/orders.rb b/spec/factories/order_factory.rb similarity index 100% rename from spec/factories/orders.rb rename to spec/factories/order_factory.rb diff --git a/spec/factories/variants.rb b/spec/factories/variant_factory.rb similarity index 100% rename from spec/factories/variants.rb rename to spec/factories/variant_factory.rb From ddf15f1d3078f4da3df0ad3b0fdaaad7cf148c8a Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 6 Jan 2019 14:47:11 +0800 Subject: [PATCH 096/108] Add scenario test for enterprise fee summary Add test for more complex scenario where there is a coordinator and distributor fee for an incoming exchange, and a producer and coordinator fee for an outgoing exchange. --- .../report_service_spec.rb | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index d9cf0220ad..ccf03553f1 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -151,6 +151,63 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end end + describe "handling of more complex cases" do + context "with non-sender fee for incoming exchange and non-receiver fee for outgoing" do + let!(:variant) do + prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, + outgoing_exchange_fees: variant_outgoing_exchange_fees) + end + let!(:variant_incoming_exchange_fees) { [coordinator_fee, distributor_fee] } + let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee] } + + let!(:producer_fee) do + tax_category = create(:tax_category, name: "Sample Producer Tax") + create(:enterprise_fee, :per_item, name: "Sample Producer Fee", enterprise: producer, + fee_type: "sales", amount: 64.0, + tax_category: tax_category) + end + let!(:coordinator_fee) do + tax_category = create(:tax_category, name: "Sample Coordinator Tax") + create(:enterprise_fee, :per_item, name: "Sample Coordinator Fee", enterprise: coordinator, + fee_type: "admin", amount: 512.0, + tax_category: tax_category) + end + let!(:distributor_fee) do + tax_category = create(:tax_category, name: "Sample Distributor Tax") + create(:enterprise_fee, :per_item, name: "Sample Distributor Fee", enterprise: distributor, + fee_type: "admin", amount: 4.0, + tax_category: tax_category) + end + + let!(:customer_order) { prepare_order(customer: customer) } + + it "fetches data correctly" do + totals = service.list + + expect(totals.length).to eq(6) + + expected_result = [ + ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", + "Incoming", "Sample Producer", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Distributor", "Sample Distributor Fee", "Sample Customer", + "Incoming", "Sample Producer", "Sample Distributor Tax", "4.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "2.00"], + ["Sales", "Sample Producer", "Sample Producer Fee", "Sample Customer", + "Outgoing", "Sample Coordinator", "Sample Producer Tax", "64.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + end + describe "filtering results based on permissions" do let!(:distributor_a) do create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], From f59834ddc8692c860724816ef5a349aeef7fc9c2 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 11 Jan 2019 07:20:23 +0800 Subject: [PATCH 097/108] Use receiver as "Fee Calc on Transfer Through" for outgoing exchanges --- .../reports/enterprise_fee_summary/scope.rb | 2 +- .../enterprise_fee_summary/report_service_spec.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb index cf343063a9..b3f3168518 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb @@ -282,7 +282,7 @@ module OrderManagement join_scope( <<-JOIN_STRING.strip_heredoc LEFT OUTER JOIN enterprises AS outgoing_exchange_enterprises - ON (outgoing_exchange_enterprises.id = outgoing_exchanges.sender_id) + ON (outgoing_exchange_enterprises.id = outgoing_exchanges.receiver_id) JOIN_STRING ) end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index ccf03553f1..83a3f77576 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -116,9 +116,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Sample Customer", "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], ["Admin", "Sample Distributor", "Distributor Fee 1", "Another Customer", - "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "4.00"], + "Outgoing", "Sample Distributor", "Sample Distributor Tax", "4.00"], ["Admin", "Sample Distributor", "Distributor Fee 1", "Sample Customer", - "Outgoing", "Sample Coordinator", "Sample Distributor Tax", "8.00"], + "Outgoing", "Sample Distributor", "Sample Distributor Tax", "8.00"], ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", nil, nil, nil, "2.00"], ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", @@ -128,9 +128,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", "Coordinator", "All", "Sample Product Tax", "2048.00"], ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", - "Outgoing", "Sample Coordinator", "Sample Product Tax", "8.00"], + "Outgoing", "Sample Distributor", "Sample Product Tax", "8.00"], ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", - "Outgoing", "Sample Coordinator", "Sample Product Tax", "16.00"], + "Outgoing", "Sample Distributor", "Sample Product Tax", "16.00"], ["Sales", "Sample Producer", "Producer Fee 1", "Another Customer", "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], ["Sales", "Sample Producer", "Producer Fee 1", "Sample Customer", @@ -190,13 +190,13 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", "Incoming", "Sample Producer", "Sample Coordinator Tax", "512.00"], ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", - "Outgoing", "Sample Coordinator", "Sample Coordinator Tax", "512.00"], + "Outgoing", "Sample Distributor", "Sample Coordinator Tax", "512.00"], ["Admin", "Sample Distributor", "Sample Distributor Fee", "Sample Customer", "Incoming", "Sample Producer", "Sample Distributor Tax", "4.00"], ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", nil, nil, nil, "2.00"], ["Sales", "Sample Producer", "Sample Producer Fee", "Sample Customer", - "Outgoing", "Sample Coordinator", "Sample Producer Tax", "64.00"], + "Outgoing", "Sample Distributor", "Sample Producer Tax", "64.00"], ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", nil, nil, "Platform Rate", "1.00"] ] From 1d3f4aa28a1374113213a5d42c49e26be15b1f7b Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 16 Jan 2019 08:44:28 +0800 Subject: [PATCH 098/108] Add feature flag for enterprise fee summary --- app/models/feature_flags.rb | 7 +++++++ spec/models/feature_flags_spec.rb | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/models/feature_flags.rb b/app/models/feature_flags.rb index aaba0f4472..096ef44fcc 100644 --- a/app/models/feature_flags.rb +++ b/app/models/feature_flags.rb @@ -14,6 +14,13 @@ class FeatureFlags superadmin? end + # Checks whether the "Enterprise Fee Summary" is enabled for the specified user + # + # @return [Boolean] + def enterprise_fee_summary_enabled? + superadmin? + end + private attr_reader :user diff --git a/spec/models/feature_flags_spec.rb b/spec/models/feature_flags_spec.rb index 3a2d06da30..b8608fa1a3 100644 --- a/spec/models/feature_flags_spec.rb +++ b/spec/models/feature_flags_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' describe FeatureFlags do - describe '.product_import_enabled?' do - let(:user) { build_stubbed(:user) } - let(:feature_flags) { described_class.new(user) } + let(:user) { build_stubbed(:user) } + let(:feature_flags) { described_class.new(user) } + describe '.product_import_enabled?' do context 'when the user is superadmin' do before do allow(user).to receive(:has_spree_role?).with('admin') { true } @@ -25,4 +25,22 @@ describe FeatureFlags do end end end + + describe ".enterprise_fee_summary_enabled?" do + context "when the user is superadmin" do + let!(:user) { create(:admin_user) } + + it "returns true" do + expect(feature_flags).to be_enterprise_fee_summary_enabled + end + end + + context "when the user is not superadmin" do + let!(:user) { create(:user) } + + it "returns false" do + expect(feature_flags).not_to be_enterprise_fee_summary_enabled + end + end + end end From 461b5725d1e95b471f89f23c6a4523e64c065d68 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Wed, 16 Jan 2019 13:54:29 +0800 Subject: [PATCH 099/108] Hide report link and resource from non-superadmins --- app/models/spree/ability_decorator.rb | 20 ++++++--- ...nterprise_fee_summaries_controller_spec.rb | 25 ++++++++++- .../reports/enterprise_fee_summaries_spec.rb | 41 +++++++++++++++++-- spec/models/spree/ability_spec.rb | 8 +++- spec/support/ability_helper.rb | 28 +++++++++++++ 5 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 spec/support/ability_helper.rb diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 6a68ae1476..0b0a7f0466 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -185,9 +185,9 @@ class AbilityDecorator # Reports page can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, - :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing, - :enterprise_fee_summary], :report - can [:admin, :new, :create], :enterprise_fee_summary + :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], + :report + add_enterprise_fee_summary_abilities(user) end def add_order_cycle_management_abilities(user) @@ -262,8 +262,8 @@ class AbilityDecorator # Reports page can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, - :order_cycle_management, :xero_invoices, :enterprise_fee_summary], :report - can [:admin, :new, :create], :enterprise_fee_summary + :order_cycle_management, :xero_invoices], :report + add_enterprise_fee_summary_abilities(user) can [:create], Customer can [:admin, :index, :update, :destroy, :show], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) @@ -286,6 +286,16 @@ class AbilityDecorator user.enterprises.include? enterprise_relationship.parent end end + + def add_enterprise_fee_summary_abilities(user) + feature_enabled = FeatureFlags.new(user).enterprise_fee_summary_enabled? + return unless feature_enabled + + # Reveal the report link in spree/admin/reports#index + can [:enterprise_fee_summary], :report + # Allow direct access to the report resource + can [:admin, :new, :create], :enterprise_fee_summary + end end Spree::Ability.register_ability(AbilityDecorator) diff --git a/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb index 5a5747041b..8019035fa4 100644 --- a/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb +++ b/spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb @@ -3,11 +3,14 @@ require "spec_helper" describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :controller do let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } - let!(:admin) { create(:admin_user) } + let!(:distributor) { create(:distributor_enterprise) } - let(:current_user) { admin } + let(:current_user) { distributor.owner } before do + feature_flags = instance_double(FeatureFlags, enterprise_fee_summary_enabled?: true) + allow(FeatureFlags).to receive(:new).with(current_user) { feature_flags } + allow(controller).to receive(:spree_current_user) { current_user } end @@ -18,6 +21,15 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control expect(response).to be_success expect(response).to render_template(new_template_path) end + + context "when feature flag is in effect" do + before { allow(FeatureFlags).to receive(:new).with(current_user).and_call_original } + + it "is unauthorized" do + get :new + expect(response).to redirect_to spree.unauthorized_path + end + end end describe "#create" do @@ -29,6 +41,15 @@ describe Spree::Admin::Reports::EnterpriseFeeSummariesController, type: :control expect(response.body).not_to be_blank expect(response.header["Content-Type"]).to eq("text/csv") end + + context "when feature flag is in effect" do + before { allow(FeatureFlags).to receive(:new).with(current_user).and_call_original } + + it "is unauthorized" do + post :create, report: { start_at: "2018-10-09 07:30:00" }, report_format: "csv" + expect(response).to redirect_to spree.unauthorized_path + end + end end context "when the parameters are invalid" do diff --git a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb index d4585ba291..e63b04edc1 100644 --- a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb @@ -11,20 +11,44 @@ feature "enterprise fee summaries" do let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } before do + feature_flags = instance_double(FeatureFlags, enterprise_fee_summary_enabled?: true) + allow(FeatureFlags).to receive(:new).with(current_user) { feature_flags } + login_as current_user end describe "navigation" do - context "when accessing the report as an enterprise user" do - let(:current_user) { distributor.owner } + context "when accessing the report as an superadmin" do + let(:current_user) { create(:admin_user) } - it "allows access to the report" do + it "shows link and allows access to the report" do visit spree.admin_reports_path click_on I18n.t("admin.reports.enterprise_fee_summary.name") expect(page).to have_button(I18n.t("filters.generate_report", scope: i18n_scope)) end end + context "when accessing the report as an admin" do + let(:current_user) { distributor.owner } + + it "shows link and allows access to the report" do + visit spree.admin_reports_path + click_on I18n.t("admin.reports.enterprise_fee_summary.name") + expect(page).to have_button(I18n.t("filters.generate_report", scope: i18n_scope)) + end + + context "when feature flag is in effect" do + before { allow(FeatureFlags).to receive(:new).with(current_user).and_call_original } + + it "does not show link now allow direct access to the report" do + visit spree.admin_reports_path + expect(page).to have_no_link I18n.t("admin.reports.enterprise_fee_summary.name") + visit spree.new_admin_reports_enterprise_fee_summary_path + expect(page).to have_no_button(I18n.t("filters.generate_report", scope: i18n_scope)) + end + end + end + context "when accessing the report as an enterprise user without sufficient permissions" do let(:current_user) { create(:user) } @@ -34,6 +58,17 @@ feature "enterprise fee summaries" do visit spree.new_admin_reports_enterprise_fee_summary_path expect(page).to have_content(I18n.t("unauthorized")) end + + context "when feature flag is in effect" do + before { allow(FeatureFlags).to receive(:new).with(current_user).and_call_original } + + it "does not show link now allow direct access to the report" do + visit spree.admin_reports_path + expect(page).to have_no_link I18n.t("admin.reports.enterprise_fee_summary.name") + visit spree.new_admin_reports_enterprise_fee_summary_path + expect(page).to have_no_button(I18n.t("filters.generate_report", scope: i18n_scope)) + end + end end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index ab9946c0ff..0e83a9719a 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -4,9 +4,11 @@ require 'support/cancan_helper' module Spree describe User do - describe "broad permissions" do subject { AbilityDecorator.new(user) } + + include ::AbilityHelper + let(:user) { create(:user) } let(:enterprise_any) { create(:enterprise, sells: 'any') } let(:enterprise_own) { create(:enterprise, sells: 'own') } @@ -215,6 +217,8 @@ module Spree should have_ability([:admin, :index, :customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], for: :report) end + include_examples "allows access to Enterprise Fee Summary only if feature flag enabled" + it "should not be able to read other reports" do should_not have_ability([:sales_total, :group_buys, :payments, :orders_and_distributors, :users_and_enterprises, :xero_invoices], for: :report) end @@ -406,6 +410,8 @@ module Spree should have_ability([:admin, :index, :customers, :sales_tax, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], for: :report) end + include_examples "allows access to Enterprise Fee Summary only if feature flag enabled" + it "should not be able to read other reports" do should_not have_ability([:sales_total, :users_and_enterprises], for: :report) end diff --git a/spec/support/ability_helper.rb b/spec/support/ability_helper.rb new file mode 100644 index 0000000000..42c4418a80 --- /dev/null +++ b/spec/support/ability_helper.rb @@ -0,0 +1,28 @@ +module AbilityHelper + shared_examples "allows access to Enterprise Fee Summary only if feature flag enabled" do + it "should not be able to read Enterprise Fee Summary" do + is_expected.not_to have_link_to_enterprise_fee_summary + is_expected.not_to have_direct_access_to_enterprise_fee_summary + end + + context "when feature flag for Enterprise Fee Summary is enabled absolutely" do + before do + feature_flags = instance_double(FeatureFlags, enterprise_fee_summary_enabled?: true) + allow(FeatureFlags).to receive(:new).with(user) { feature_flags } + end + + it "should be able to see link and read report" do + is_expected.to have_link_to_enterprise_fee_summary + is_expected.to have_direct_access_to_enterprise_fee_summary + end + end + + def have_link_to_enterprise_fee_summary + have_ability([:enterprise_fee_summary], for: :report) + end + + def have_direct_access_to_enterprise_fee_summary + have_ability([:admin, :new, :create], for: :enterprise_fee_summary) + end + end +end From de30cf119886211293ef1ad24fa8d9e8e04e9eb1 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Thu, 17 Jan 2019 15:44:18 +0800 Subject: [PATCH 100/108] Fix description for feature flag example groups --- spec/models/feature_flags_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/feature_flags_spec.rb b/spec/models/feature_flags_spec.rb index b8608fa1a3..6b9380b879 100644 --- a/spec/models/feature_flags_spec.rb +++ b/spec/models/feature_flags_spec.rb @@ -4,7 +4,7 @@ describe FeatureFlags do let(:user) { build_stubbed(:user) } let(:feature_flags) { described_class.new(user) } - describe '.product_import_enabled?' do + describe '#product_import_enabled?' do context 'when the user is superadmin' do before do allow(user).to receive(:has_spree_role?).with('admin') { true } @@ -26,7 +26,7 @@ describe FeatureFlags do end end - describe ".enterprise_fee_summary_enabled?" do + describe "#enterprise_fee_summary_enabled?" do context "when the user is superadmin" do let!(:user) { create(:admin_user) } From b6be8c48d130419387c58a31f44163a1d065a3c0 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 1 Feb 2019 12:38:39 +0800 Subject: [PATCH 101/108] Fix layout violation in Spree::Ability decorator --- app/models/spree/ability_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 0b0a7f0466..90f393a161 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -186,7 +186,7 @@ class AbilityDecorator # Reports page can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], - :report + :report add_enterprise_fee_summary_abilities(user) end From f143540d0c6db23cce47bbe4ac9449e6dadf3de8 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sat, 9 Feb 2019 22:38:26 +0800 Subject: [PATCH 102/108] Do not expect modal open when checking spinner gone The element referenced in the following might no longer be visible: within "div.reveal-modal" --- spec/features/admin/bulk_product_update_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 96e3792163..1d9e2fb7b2 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -762,9 +762,9 @@ feature %q{ # Shows spinner whilst loading expect(page).to have_css "img.spinner", visible: true - expect(page).to have_no_css "img.spinner", visible: true end + expect(page).to have_no_css "img.spinner", visible: true expect(page).to have_no_selector "div.reveal-modal" within "table#listing_products tr#p_#{product.id}" do From e14c60c1c17e2b246e7c762a661bebfca06f179f Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 10 Feb 2019 18:59:36 +0800 Subject: [PATCH 103/108] Add RSpec matchers for flash messages --- .../matchers/flash_message_matchers.rb | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 spec/support/matchers/flash_message_matchers.rb diff --git a/spec/support/matchers/flash_message_matchers.rb b/spec/support/matchers/flash_message_matchers.rb new file mode 100644 index 0000000000..b781fc1811 --- /dev/null +++ b/spec/support/matchers/flash_message_matchers.rb @@ -0,0 +1,31 @@ +RSpec::Matchers.define :have_flash_message do |message| + match do |node| + @message, @node = message, node + + # Ignore leading and trailing whitespace. Later versions of Capybara have :exact_text option. + # The :exact option is not supported in has_selector?. + message_substring_regex = substring_match_regex(message) + node.has_selector?(".flash", text: message_substring_regex, visible: false) + end + + failure_message do |actual| + "expected to find flash message ##{@message}" + end + + match_when_negated do |node| + @message, @node = message, node + + # Ignore leading and trailing whitespace. Later versions of Capybara have :exact_text option. + # The :exact option is not supported in has_selector?. + message_substring_regex = substring_match_regex(message) + node.has_no_selector?(".flash", text: message_substring_regex, visible: false) + end + + failure_message_when_negated do |actual| + "expected not to find flash message ##{@message}" + end + + def substring_match_regex(text) + /\A\s*#{Regexp.escape(text)}\s*\Z/ + end +end From 175264f41f8ffea35d5c41afa3680809ecbe3004 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 10 Feb 2019 19:03:02 +0800 Subject: [PATCH 104/108] Use flash matcher in shipping method feature specs --- spec/features/admin/shipping_methods_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb index f6d4935a55..f8763a3c6b 100644 --- a/spec/features/admin/shipping_methods_spec.rb +++ b/spec/features/admin/shipping_methods_spec.rb @@ -32,7 +32,8 @@ feature 'shipping methods' do click_button 'Create' # Then the shipping method should have its distributor set - flash_message.should == 'Shipping method "Carrier Pidgeon" has been successfully created!' + message = "Shipping method \"Carrier Pidgeon\" has been successfully created!" + expect(page).to have_flash_message message sm = Spree::ShippingMethod.last sm.name.should == 'Carrier Pidgeon' @@ -100,7 +101,9 @@ feature 'shipping methods' do click_button 'Create' - flash_message.should == 'Shipping method "Teleport" has been successfully created!' + message = "Shipping method \"Teleport\" has been successfully created!" + expect(page).to have_flash_message message + expect(first('tags-input .tag-list ti-tag-item')).to have_content "local" shipping_method = Spree::ShippingMethod.find_by_name('Teleport') From 0032a237a284e05b11e921d41892db625dd77602 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Sun, 10 Feb 2019 22:06:35 +0800 Subject: [PATCH 105/108] Wait for button to disappear before checking flash --- spec/features/admin/shipping_methods_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb index f8763a3c6b..bb65e70255 100644 --- a/spec/features/admin/shipping_methods_spec.rb +++ b/spec/features/admin/shipping_methods_spec.rb @@ -29,7 +29,9 @@ feature 'shipping methods' do fill_in 'shipping_method_name', with: 'Carrier Pidgeon' check "shipping_method_distributor_ids_#{d1.id}" check "shipping_method_distributor_ids_#{d2.id}" - click_button 'Create' + click_button I18n.t("actions.create") + + expect(page).to have_no_button I18n.t("actions.create") # Then the shipping method should have its distributor set message = "Shipping method \"Carrier Pidgeon\" has been successfully created!" @@ -99,8 +101,9 @@ feature 'shipping methods' do expect(page).to have_css '.tag-item' end - click_button 'Create' + click_button I18n.t("actions.create") + expect(page).to have_no_button I18n.t("actions.create") message = "Shipping method \"Teleport\" has been successfully created!" expect(page).to have_flash_message message From 097ffba323a55010276c3be4a23ca4159ce94c01 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Mon, 11 Feb 2019 23:57:13 +0000 Subject: [PATCH 106/108] Fix broken spec from refactored method --- spec/features/admin/product_import_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/product_import_spec.rb b/spec/features/admin/product_import_spec.rb index 3cff57b16d..fd4bc1a13e 100644 --- a/spec/features/admin/product_import_spec.rb +++ b/spec/features/admin/product_import_spec.rb @@ -307,7 +307,7 @@ feature "Product Import", js: true do attach_file 'file', '/tmp/test.csv' click_button 'Upload' - import_data + proceed_to_validation expect(page).to have_selector '.item-count', text: "3" expect(page).to have_no_selector '.invalid-count' From ff2bf76e3875d38de2dc0043d80cf214f2fbd3d3 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Wed, 13 Feb 2019 09:05:35 +0000 Subject: [PATCH 107/108] Add missing translation to fix shipping methods spec --- config/locales/en.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index edc5c11ec7..3c4b35ebbd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -280,6 +280,7 @@ en: actions: create_and_add_another: "Create and Add Another" + create: "Create" admin: # Common properties / models begins_at: Begins At From 728ed2c20276b7db294dd871b0706b8c7f25c52e Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Wed, 13 Feb 2019 09:06:10 +0000 Subject: [PATCH 108/108] Move features/admin/reports/enterprise_fee_summaries_spec to xdescribe (will be fixed as part of spree upgrade phase 2) --- spec/features/admin/reports/enterprise_fee_summaries_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb index e63b04edc1..be03f7d393 100644 --- a/spec/features/admin/reports/enterprise_fee_summaries_spec.rb +++ b/spec/features/admin/reports/enterprise_fee_summaries_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -feature "enterprise fee summaries" do +xfeature "enterprise fee summaries", js: true do include AuthenticationWorkflow include WebHelper