From bf44a1c8627257f02cff86afc9f1262e2312c052 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 4 Apr 2015 17:06:26 +0100 Subject: [PATCH 001/206] Update DB schema to store updatable weight on items sold --- .../api/line_items_controller_decorator.rb | 17 +++++++++++++++++ .../20150305004846_add_weight_to_line_items.rb | 5 +++++ db/schema.rb | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 app/controllers/spree/api/line_items_controller_decorator.rb create mode 100644 db/migrate/20150305004846_add_weight_to_line_items.rb diff --git a/app/controllers/spree/api/line_items_controller_decorator.rb b/app/controllers/spree/api/line_items_controller_decorator.rb new file mode 100644 index 0000000000..f9a025873d --- /dev/null +++ b/app/controllers/spree/api/line_items_controller_decorator.rb @@ -0,0 +1,17 @@ +Spree::Api::LineItemsController.class_eval do + after_filter :apply_enterprise_fees, :only => :update + + def apply_enterprise_fees + authorize! :read, order + order.update_distribution_charge! + end +end + + +#when we update a line item the .update_distribution_charge! is called +# order.should_receive .update_distribution_charge! +# check fails when absent + +# in order model check that .update_distribution_charge! is properly tested. +# think through use cases - existing completed order +# currently likely just used to complete orders so add test case that works on a completed order diff --git a/db/migrate/20150305004846_add_weight_to_line_items.rb b/db/migrate/20150305004846_add_weight_to_line_items.rb new file mode 100644 index 0000000000..a4df1a12f0 --- /dev/null +++ b/db/migrate/20150305004846_add_weight_to_line_items.rb @@ -0,0 +1,5 @@ +class AddWeightToLineItems < ActiveRecord::Migration + def change + add_column :spree_line_items, :unit_value, :decimal, :precision => 8, :scale => 2 + end +end diff --git a/db/schema.rb b/db/schema.rb index 7435fa4e30..bec31e61fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150225232938) do +ActiveRecord::Schema.define(:version => 20150305004846) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -532,6 +532,7 @@ ActiveRecord::Schema.define(:version => 20150225232938) do t.string "currency" t.decimal "distribution_fee", :precision => 10, :scale => 2 t.string "shipping_method_name" + t.decimal "unit_value", :precision => 8, :scale => 2 end add_index "spree_line_items", ["order_id"], :name => "index_line_items_on_order_id" From ffd850c761f16e14b7f3049c8cab8a3dca18db71 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 4 Apr 2015 17:44:02 +0100 Subject: [PATCH 002/206] Adding specs for variable weight adjustment via builk order management --- .../spree/api/line_items_controller_spec.rb | 31 +++++++++++++++++++ .../admin/bulk_order_management_spec.rb | 18 +++++++++-- .../unit/bulk_order_management_spec.js.coffee | 31 +++++++++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 spec/controllers/spree/api/line_items_controller_spec.rb diff --git a/spec/controllers/spree/api/line_items_controller_spec.rb b/spec/controllers/spree/api/line_items_controller_spec.rb new file mode 100644 index 0000000000..e3c9a262af --- /dev/null +++ b/spec/controllers/spree/api/line_items_controller_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +module Spree + describe Spree::Api::LineItemsController do + render_views + + before do + stub_authentication! + Spree.user_class.stub :find_by_spree_api_key => current_api_user + end + + def self.make_simple_data! + let!(:order) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now) } + let!(:line_item) { FactoryGirl.create(:line_item, order: order, unit_value: 500) } + end + + #test that when a line item is updated, an order's fees are updated too + context "as an admin user" do + sign_in_as_admin! + make_simple_data! + + context "as a line item is updated" do + it "apply enterprise fees on the item" do + line_item_params = { order_id: order.number, id: line_item.id, line_item: { id: line_item.id, unit_value: 520 }, format: :json} + controller.should_receive(:apply_enterprise_fees).and_return(true) + spree_post :update, line_item_params + end + end + end + end +end diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index c8ad230a00..7aa6449c1e 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -57,7 +57,7 @@ feature %q{ end it "displays a column for order date" do - page.should have_selector "th,date", text: "ORDER DATE", :visible => true + page.should have_selector "th.date", text: "ORDER DATE", :visible => true page.should have_selector "td.date", text: o1.completed_at.strftime("%F %T"), :visible => true page.should have_selector "td.date", text: o2.completed_at.strftime("%F %T"), :visible => true end @@ -141,8 +141,22 @@ feature %q{ admin_user = quick_login_as_admin end + let!(:p1) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } + let!(:v1) { p1.variants.first } let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } + let!(:li1) { FactoryGirl.create(:line_item, order: o1, variant: v1, :quantity => 5, :unit_value => 1000 ) } + + context "modifying the weight/volume of a line item" do + it "update-pending is added to variable 'price'" do + visit '/admin/orders/bulk_management' + first("div#columns_dropdown", :text => "COLUMNS").click + first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click + page.should_not have_css "input[name='price'].update-pending" + li1_unit_value_column = find("tr#li_#{li1.id} td.unit_value") + li1_unit_value_column.fill_in "unit_value", :with => 1200 + page.should have_css "input[name='price'].update-pending", :visible => false + end + end context "using column display toggle" do it "shows a column display toggle button, which shows a list of columns when clicked" do diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 35b9e13d61..76ae925785 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -33,8 +33,8 @@ describe "AdminOrderMgmtCtrl", -> httpBackend.flush() expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] - expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] - expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] + expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] + expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] expect(scope.initialiseVariables.calls.length).toBe 1 expect(scope.fetchOrders.calls.length).toBe 1 @@ -350,6 +350,33 @@ describe "AdminOrderMgmtCtrl", -> spyOn(VariantUnitManager, "getUnitName").andReturn "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" + describe "updating the price upon updating the weight of a line item", -> + + it "resets the weight if the weight is set to zero", -> + scope.filteredLineItems = [ + { units_variant: { unit_value: 100 }, price: 2, unit_value: 0 } + ] + expect(scope.weightAdjustedPrice(scope.filteredLineItems[0], 100)).toEqual scope.filteredLineItems[0].price + + it "updates the price if the weight is changed", -> + scope.filteredLineItems = [ + { units_variant: { unit_value: 100 }, price: 2, unit_value: 200 } + ] + old_value = scope.filteredLineItems[0].units_variant.unit_value + new_value = scope.filteredLineItems[0].unit_value + sp = scope.filteredLineItems[0].price * new_value / old_value + expect(scope.weightAdjustedPrice(scope.filteredLineItems[0], old_value)).toEqual sp + + it "doesn't update the price if the weight is not changed", -> + scope.filteredLineItems = [ + { units_variant: { unit_value: 100 }, price: 2, unit_value: 100 } + ] + old_value = scope.filteredLineItems[0].unit_value + new_value = scope.filteredLineItems[0].unit_value + sp = scope.filteredLineItems[0].price + expect(scope.weightAdjustedPrice(scope.filteredLineItems[0], old_value)).toEqual sp + + describe "managing pending changes", -> dataSubmitter = pendingChangesService = null From ff935af18bd4a2a459e945dafa1e35642ac5d198 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 4 Apr 2015 17:52:31 +0100 Subject: [PATCH 003/206] Variable Weights: Adding ability to update the weight/volume of a line_item after checkout. The price of the line_item is automatically updated to reflect the value of the new weight. --- .../admin/bulk_order_management.js.coffee | 9 +- .../directives/line_item_upd_attr.js.coffee | 6 +- app/models/spree/line_item_decorator.rb | 3 +- .../admin/orders/bulk_management.html.haml | 107 ++++++++++-------- .../spree/api/line_items/bulk_show.v1.rabl | 6 +- 5 files changed, 75 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 4c1a319c1a..a20cdd27e7 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -32,7 +32,8 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ variant: { name: "Variant", visible: true } quantity: { name: "Quantity", visible: true } max: { name: "Max", visible: true } - + unit_value: { name: "Weight/Volume", visible: false } + price: { name: "Price", visible: false } $scope.initialise = -> $scope.initialiseVariables() authorise_api_reponse = "" @@ -162,6 +163,12 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.supplierFilter = $scope.suppliers[0].id $scope.orderCycleFilter = $scope.orderCycles[0].id $scope.quickSearch = "" + + $scope.weightAdjustedPrice = (lineItem, oldValue) -> + if lineItem.unit_value <= 0 + lineItem.unit_value = lineItem.units_variant.unit_value + lineItem.price = lineItem.price * lineItem.unit_value / oldValue + #$scope.bulk_order_form.line_item.price.$setViewValue($scope.bulk_order_form.line_item.price.$viewValue) ] daysFromToday = (days) -> diff --git a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee index c83d7fdc0f..c5afce07a5 100644 --- a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee +++ b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee @@ -8,7 +8,9 @@ angular.module("ofn.admin").directive "ofnLineItemUpdAttr", [ scope.$watch -> scope.$eval(attrs.ngModel) , (value) -> - if ngModel.$dirty + #if ngModel.$dirty + # i think i can take this out, this directive is still only called + # on a change and only an updated value will create a db call. if value == element.dbValue pendingChanges.remove(scope.line_item.id, attrName) switchClass( element, "", ["update-pending", "update-error", "update-success"], false ) @@ -20,4 +22,4 @@ angular.module("ofn.admin").directive "ofnLineItemUpdAttr", [ url: "/api/orders/#{scope.line_item.order.number}/line_items/#{scope.line_item.id}?line_item[#{attrName}]=#{value}" pendingChanges.add(scope.line_item.id, attrName, changeObj) switchClass( element, "update-pending", ["update-error", "update-success"], false ) -] \ No newline at end of file +] diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index b2c0a583fc..4eec0bcd2b 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -1,5 +1,6 @@ Spree::LineItem.class_eval do - attr_accessible :max_quantity + attr_accessible :max_quantity, :unit_value + attr_accessible :unit_value, :price, :as => :api # -- Scopes scope :managed_by, lambda { |user| diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 61bf9fbb7e..a1f53826e1 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -100,53 +100,60 @@ %div{ :class => "sixteen columns alpha", 'ng-show' => '!loading && filteredLineItems.length == 0'} %h1#no_results No orders found. %div{ 'ng-hide' => 'loading || filteredLineItems.length == 0' } - %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } - %thead - %tr - %th.bulk - %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } - %th.order_no{ 'ng-show' => 'columns.order_no.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } Order No. - %th.full_name{ 'ng-show' => 'columns.full_name.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } Name - %th.email{ 'ng-show' => 'columns.email.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } Email - %th.phone{ 'ng-show' => 'columns.phone.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } Phone - %th.date{ 'ng-show' => 'columns.order_date.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } Order Date - %th.producer{ 'ng-show' => 'columns.producer.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } Producer - %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } Order Cycle - %th.hub{ 'ng-show' => 'columns.hub.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub - %th.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit - %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity - %th.max{ 'ng-show' => 'columns.max.visible' } Max - %th.actions - %th.actions - Ask?  - %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } - %td.bulk - %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } - %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} - %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} - %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} - %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} - %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} - %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} - %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} - %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} - %td.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} - %td.quantity{ 'ng-show' => 'columns.quantity.visible' } - %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } - %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} - %td.actions - %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } - %td.actions - %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } - %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } \ No newline at end of file + %form{ 'ng-model' => "bulk_order_form" } + %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } + %thead + %tr + %th.bulk + %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } + %th.order_no{ 'ng-show' => 'columns.order_no.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } Order No. + %th.full_name{ 'ng-show' => 'columns.full_name.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } Name + %th.email{ 'ng-show' => 'columns.email.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } Email + %th.phone{ 'ng-show' => 'columns.phone.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } Phone + %th.date{ 'ng-show' => 'columns.order_date.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } Order Date + %th.producer{ 'ng-show' => 'columns.producer.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } Producer + %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } Order Cycle + %th.hub{ 'ng-show' => 'columns.hub.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub + %th.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit + %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity + %th.max{ 'ng-show' => 'columns.max.visible' } Max + %th.unit_value{ 'ng-show' => 'columns.unit_value.visible' } Weight/Volume + %th.price{ 'ng-show' => 'columns.price.visible' } Price + %th.actions + %th.actions + Ask?  + %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } + %td.bulk + %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } + %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} + %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} + %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} + %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} + %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} + %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} + %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} + %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} + %td.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} + %td.quantity{ 'ng-show' => 'columns.quantity.visible' } + %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } + %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} + %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } + %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "unit_value" } + %td.price{ 'ng-show' => 'columns.price.visible' } + %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-model' => "line_item.price", 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "price" } + %td.actions + %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } + %td.actions + %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } + %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } diff --git a/app/views/spree/api/line_items/bulk_show.v1.rabl b/app/views/spree/api/line_items/bulk_show.v1.rabl index 8b53c42086..6b24ef4bf2 100644 --- a/app/views/spree/api/line_items/bulk_show.v1.rabl +++ b/app/views/spree/api/line_items/bulk_show.v1.rabl @@ -1,5 +1,7 @@ object @line_item -attributes :id, :quantity, :max_quantity +attributes :id, :quantity, :max_quantity, :price node( :supplier ) { |li| partial 'api/enterprises/bulk_show', :object => li.product.supplier } node( :units_product ) { |li| partial 'spree/api/products/units_show', :object => li.product } -node( :units_variant ) { |li| partial 'spree/api/variants/units_show', :object => li.variant } \ No newline at end of file +node( :units_variant ) { |li| partial 'spree/api/variants/units_show', :object => li.variant } +node( :unit_value ) { |li| li.unit_value.to_f } +node( :price ) { |li| li.price } From 83981fbb15e254cacf5c9cacefe0cec4d0f9f787 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Tue, 14 Apr 2015 17:29:56 +0100 Subject: [PATCH 004/206] Adding additional logic for if the line_item unit_value is nil --- app/assets/javascripts/admin/bulk_order_management.js.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index a20cdd27e7..a52b770831 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -165,6 +165,8 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.quickSearch = "" $scope.weightAdjustedPrice = (lineItem, oldValue) -> + if oldValue <= 0 + oldValue = lineItem.units_variant.unit_value if lineItem.unit_value <= 0 lineItem.unit_value = lineItem.units_variant.unit_value lineItem.price = lineItem.price * lineItem.unit_value / oldValue From 3f01a459ac317a5fef7ac6b753bfcf6eca074bf8 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 26 Sep 2014 12:17:55 +1000 Subject: [PATCH 005/206] Adding a standard variant upon initialisation of a new instance of Spree::Product --- app/models/spree/product_decorator.rb | 4 ++++ spec/models/spree/product_spec.rb | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index cf7d02bf46..034bbed043 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -32,6 +32,7 @@ Spree::Product.class_eval do validates_presence_of :variant_unit_name, if: -> p { p.variant_unit == 'items' } + after_initialize :ensure_first_standard_variant, :if => :new_record? after_initialize :set_available_on_to_now, :if => :new_record? after_save :update_units after_touch :touch_distributors @@ -205,4 +206,7 @@ Spree::Product.class_eval do Spree::OptionType.where('name LIKE ?', 'unit_%%') end + def ensure_first_standard_variant + self.variants << Spree::Variant.new + end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 400f92754b..73c6f2fe10 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -85,6 +85,14 @@ module Spree end end + context "instatiating a new product" do + let!(:product) { Spree::Product.new } + + it "creates a standard (non-master) variant when created" do + product.variants.should_not be_empty + end + end + context "when the unit is items" do it "is valid when unit name is set and unit scale is not" do product.variant_unit = 'items' @@ -122,7 +130,6 @@ module Spree end end end - describe "scopes" do describe "in_supplier" do From dfb513cce7943fcb59dab66d35daaeb0a405383d Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 26 Sep 2014 13:11:32 +1000 Subject: [PATCH 006/206] Use after_create callback to duplicate master variant --- app/models/spree/product_decorator.rb | 9 ++++++--- spec/models/spree/product_spec.rb | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 034bbed043..aae90f41c4 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -32,7 +32,7 @@ Spree::Product.class_eval do validates_presence_of :variant_unit_name, if: -> p { p.variant_unit == 'items' } - after_initialize :ensure_first_standard_variant, :if => :new_record? + after_create :ensure_one_standard_variant after_initialize :set_available_on_to_now, :if => :new_record? after_save :update_units after_touch :touch_distributors @@ -206,7 +206,10 @@ Spree::Product.class_eval do Spree::OptionType.where('name LIKE ?', 'unit_%%') end - def ensure_first_standard_variant - self.variants << Spree::Variant.new + def ensure_one_standard_variant + variant = self.master.dup + variant.product = self + variant.is_master = false + self.variants << variant end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 73c6f2fe10..e3a72f19c5 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -85,11 +85,21 @@ module Spree end end - context "instatiating a new product" do - let!(:product) { Spree::Product.new } + context "saving a new product" do + let!(:product){ Spree::Product.new } - it "creates a standard (non-master) variant when created" do - product.variants.should_not be_empty + before do + product.primary_taxon = create(:taxon) + product.supplier = create(:supplier_enterprise) + product.name = "Product1" + product.on_hand = 3 + product.price = 4.27 + product.save! + end + + it "copies the properties on master variant to the first standard variant" do + standard_variant = product.variants(:reload).first + expect(standard_variant.price).to eq product.master.price end end From 0a7b01ff07f36891f39a99fa8850ca143fa96b56 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 16:13:03 +1000 Subject: [PATCH 007/206] Product requires variant_unit and master requires unit_value and/or unit_desc --- app/models/spree/product_decorator.rb | 6 +- app/models/spree/variant_decorator.rb | 6 +- spec/factories.rb | 4 + spec/models/spree/product_spec.rb | 106 +++++--------------------- spec/models/spree/variant_spec.rb | 62 ++------------- 5 files changed, 35 insertions(+), 149 deletions(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index aae90f41c4..b6cd0bb221 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -25,14 +25,14 @@ Spree::Product.class_eval do validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } validates :tax_category_id, presence: { message: "^Tax Category can't be blank" }, if: "Spree::Config.products_require_tax_category" - - validates_presence_of :variant_unit, if: :has_variants? + + validates_presence_of :variant_unit validates_presence_of :variant_unit_scale, if: -> p { %w(weight volume).include? p.variant_unit } validates_presence_of :variant_unit_name, if: -> p { p.variant_unit == 'items' } - after_create :ensure_one_standard_variant + #after_create :ensure_one_standard_variant after_initialize :set_available_on_to_now, :if => :new_record? after_save :update_units after_touch :touch_distributors diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 86a0b5fd69..703111c884 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -12,12 +12,10 @@ Spree::Variant.class_eval do accepts_nested_attributes_for :images validates_presence_of :unit_value, - if: -> v { %w(weight volume).include? v.product.andand.variant_unit }, - unless: :is_master + if: -> v { %w(weight volume).include? v.product.andand.variant_unit } validates_presence_of :unit_description, - if: -> v { v.product.andand.variant_unit.present? && v.unit_value.nil? }, - unless: :is_master + if: -> v { v.product.andand.variant_unit.present? && v.unit_value.nil? } before_validation :update_weight_from_unit_value, if: -> v { v.product.present? } after_save :update_units diff --git a/spec/factories.rb b/spec/factories.rb index aa86af8563..7effe49a9d 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -214,6 +214,10 @@ end FactoryGirl.modify do + factory :base_product do + unit_value 1 + unit_description '' + end factory :product do primary_taxon { Spree::Taxon.first || FactoryGirl.create(:taxon) } end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index e3a72f19c5..aca9404fb7 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -85,23 +85,24 @@ module Spree end end - context "saving a new product" do - let!(:product){ Spree::Product.new } - - before do - product.primary_taxon = create(:taxon) - product.supplier = create(:supplier_enterprise) - product.name = "Product1" - product.on_hand = 3 - product.price = 4.27 - product.save! - end - - it "copies the properties on master variant to the first standard variant" do - standard_variant = product.variants(:reload).first - expect(standard_variant.price).to eq product.master.price - end - end + # context "saving a new product" do + # let!(:product){ Spree::Product.new } + # + # before do + # product.primary_taxon = create(:taxon) + # product.supplier = create(:supplier_enterprise) + # product.name = "Product1" + # product.variant_unit = "weight" + # product.on_hand = 3 + # product.price = 4.27 + # product.save! + # end + # + # it "copies the properties on master variant to the first standard variant" do + # standard_variant = product.variants(:reload).first + # expect(standard_variant.price).to eq product.master.price + # end + # end context "when the unit is items" do it "is valid when unit name is set and unit scale is not" do @@ -128,7 +129,7 @@ module Spree product.variant_unit_name = nil product.variant_unit_scale = nil - product.should be_valid + expect(product).to be_invalid end it "requires a unit scale when variant unit is weight" do @@ -412,63 +413,6 @@ module Spree describe "variant units" do - context "when the product initially has no variant unit" do - let!(:p) { create(:simple_product, - variant_unit: nil, - variant_unit_scale: nil, - variant_unit_name: nil) } - - context "when the required option type does not exist" do - it "creates the option type and assigns it to the product" do - expect { - p.update_attributes!(variant_unit: 'weight', variant_unit_scale: 1000) - }.to change(Spree::OptionType, :count).by(1) - - ot = Spree::OptionType.last - ot.name.should == 'unit_weight' - ot.presentation.should == 'Weight' - - p.option_types.should == [ot] - end - - it "does the same with volume" do - expect { - p.update_attributes!(variant_unit: 'volume', variant_unit_scale: 1000) - }.to change(Spree::OptionType, :count).by(1) - - ot = Spree::OptionType.last - ot.name.should == 'unit_volume' - ot.presentation.should == 'Volume' - - p.option_types.should == [ot] - end - - it "does the same with items" do - expect { - p.update_attributes!(variant_unit: 'items', variant_unit_name: 'packet') - }.to change(Spree::OptionType, :count).by(1) - - ot = Spree::OptionType.last - ot.name.should == 'unit_items' - ot.presentation.should == 'Items' - - p.option_types.should == [ot] - end - end - - context "when the required option type already exists" do - let!(:ot) { create(:option_type, name: 'unit_weight', presentation: 'Weight') } - - it "looks up the option type and assigns it to the product" do - expect { - p.update_attributes!(variant_unit: 'weight', variant_unit_scale: 1000) - }.to change(Spree::OptionType, :count).by(0) - - p.option_types.should == [ot] - end - end - end - context "when the product already has a variant unit set (and all required option types exist)" do let!(:p) { create(:simple_product, variant_unit: 'weight', @@ -482,11 +426,6 @@ module Spree p.option_types.should == [ot_volume] end - it "leaves option type unassigned if none is provided" do - p.update_attributes!(variant_unit: nil, variant_unit_scale: nil) - p.option_types.should == [] - end - it "does not remove and re-add the option type if it is not changed" do p.option_types.should_receive(:delete).never p.update_attributes!(name: 'foo') @@ -523,13 +462,6 @@ module Spree end end - describe "returning the variant unit option type" do - it "returns nil when variant_unit is not set" do - p = create(:simple_product, variant_unit: nil) - p.variant_unit_option_type.should be_nil - end - end - it "finds all variant unit option types" do ot1 = create(:option_type, name: 'unit_weight', presentation: 'Weight') ot2 = create(:option_type, name: 'unit_volume', presentation: 'Volume') diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index 5a82f04ba7..aa3296e5dc 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -240,12 +240,14 @@ module Spree end context "when the product does not have variants" do - let(:product) { create(:simple_product, variant_unit: nil) } + let(:product) { create(:simple_product) } let(:variant) { product.master } - it "does not require unit value or unit description when the product's unit is empty" do + it "requires unit value and/or unit description" do variant.unit_value = nil variant.unit_description = nil + variant.should be_invalid + variant.unit_value = 1 variant.should be_valid end end @@ -283,7 +285,7 @@ module Spree describe "setting the variant's weight from the unit value" do it "sets the variant's weight when unit is weight" do - p = create(:simple_product, variant_unit: nil, variant_unit_scale: nil) + p = create(:simple_product, variant_unit: 'volume') v = create(:variant, product: p, weight: nil) p.update_attributes! variant_unit: 'weight', variant_unit_scale: 1 @@ -293,7 +295,7 @@ module Spree end it "does nothing when unit is not weight" do - p = create(:simple_product, variant_unit: nil, variant_unit_scale: nil) + p = create(:simple_product, variant_unit: 'volume') v = create(:variant, product: p, weight: 123) p.update_attributes! variant_unit: 'volume', variant_unit_scale: 1 @@ -303,7 +305,7 @@ module Spree end it "does nothing when unit_value is not set" do - p = create(:simple_product, variant_unit: nil, variant_unit_scale: nil) + p = create(:simple_product, variant_unit: 'volume') v = create(:variant, product: p, weight: 123) p.update_attributes! variant_unit: 'weight', variant_unit_scale: 1 @@ -316,56 +318,6 @@ module Spree end end - context "when the variant initially has no value" do - context "when the required option value does not exist" do - let!(:p) { create(:simple_product, variant_unit: nil, variant_unit_scale: nil) } - let!(:v) { create(:variant, product: p, unit_value: nil, unit_description: nil) } - - before do - p.update_attributes!(variant_unit: 'weight', variant_unit_scale: 1) - @ot = Spree::OptionType.find_by_name 'unit_weight' - end - - it "creates the option value and assigns it to the variant" do - expect { - v.update_attributes!(unit_value: 10, unit_description: 'foo') - }.to change(Spree::OptionValue, :count).by(1) - - ov = Spree::OptionValue.last - ov.option_type.should == @ot - ov.name.should == '10g foo' - ov.presentation.should == '10g foo' - - v.option_values.should include ov - end - end - - context "when the required option value already exists" do - let!(:p_orig) { create(:simple_product, variant_unit: 'weight', variant_unit_scale: 1) } - let!(:v_orig) { create(:variant, product: p_orig, unit_value: 10, unit_description: 'foo') } - - let!(:p) { create(:simple_product, variant_unit: nil, variant_unit_scale: nil) } - let!(:v) { create(:variant, product: p, unit_value: nil, unit_description: nil) } - - before do - p.update_attributes!(variant_unit: 'weight', variant_unit_scale: 1) - @ot = Spree::OptionType.find_by_name 'unit_weight' - end - - it "looks up the option value and assigns it to the variant" do - expect { - v.update_attributes!(unit_value: 10, unit_description: 'foo') - }.to change(Spree::OptionValue, :count).by(0) - - ov = v.option_values.last - ov.option_type.should == @ot - ov.name.should == '10g foo' - ov.presentation.should == '10g foo' - - v_orig.option_values.should include ov - end - end - end context "when the variant already has a value set (and all required option values exist)" do let!(:p0) { create(:simple_product, variant_unit: 'weight', variant_unit_scale: 1) } let!(:v0) { create(:variant, product: p0, unit_value: 10, unit_description: 'foo') } From 13a910c3727755bbf174cd3ef869ccb40979459e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 17:08:18 +1000 Subject: [PATCH 008/206] Replace validates_associated on master with current spree method for error reporting master saves --- app/models/spree/product_decorator.rb | 20 +++++++++++++++++++- spec/models/spree/product_spec.rb | 5 +++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index b6cd0bb221..258d1e0df2 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -21,7 +21,6 @@ Spree::Product.class_eval do attr_accessible :supplier_id, :primary_taxon_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value, :unit_description, :notes, :images_attributes, :display_as - validates_associated :master, message: "^Price and On Hand must be valid" validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } validates :tax_category_id, presence: { message: "^Tax Category can't be blank" }, if: "Spree::Config.products_require_tax_category" @@ -212,4 +211,23 @@ Spree::Product.class_eval do variant.is_master = false self.variants << variant end + + # Override Spree's old save_master method and replace it with the most recent method from spree repository + # This fixes any problems arising from failing master saves, without the need for a validates_associated on + # master, while giving us more specific errors as to why saving failed + def save_master + begin + if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?))) + master.save! + end + + # If the master cannot be saved, the Product object will get its errors + # and will be destroyed + rescue ActiveRecord::RecordInvalid + master.errors.each do |att, error| + self.errors.add(att, error) + end + raise + end + end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index aca9404fb7..70bfb38227 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -25,9 +25,10 @@ module Spree it "does not save when master is invalid" do s = create(:supplier_enterprise) t = create(:taxon) - product = Product.new supplier_id: s.id, name: "Apples", price: 1, primary_taxon_id: t.id + product = Product.new supplier_id: s.id, name: "Apples", price: 1, primary_taxon_id: t.id, variant_unit: "weight", variant_unit_scale: 1000, unit_value: 1 product.on_hand = "10,000" - product.save.should be_false + expect(product.save).to be_false + expect(product.errors[:count_on_hand]).to include "is not a number" end it "defaults available_on to now" do From 869551a17c2b033061c197e9cbd48ed2e302658a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 17:22:30 +1000 Subject: [PATCH 009/206] Adding a standard variant again --- app/models/spree/product_decorator.rb | 5 ++-- spec/models/spree/product_spec.rb | 39 ++++++++++++++------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 258d1e0df2..c83ffd0c0a 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -21,6 +21,8 @@ Spree::Product.class_eval do attr_accessible :supplier_id, :primary_taxon_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value, :unit_description, :notes, :images_attributes, :display_as + before_validation :ensure_standard_variant + validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } validates :tax_category_id, presence: { message: "^Tax Category can't be blank" }, if: "Spree::Config.products_require_tax_category" @@ -31,7 +33,6 @@ Spree::Product.class_eval do validates_presence_of :variant_unit_name, if: -> p { p.variant_unit == 'items' } - #after_create :ensure_one_standard_variant after_initialize :set_available_on_to_now, :if => :new_record? after_save :update_units after_touch :touch_distributors @@ -205,7 +206,7 @@ Spree::Product.class_eval do Spree::OptionType.where('name LIKE ?', 'unit_%%') end - def ensure_one_standard_variant + def ensure_standard_variant variant = self.master.dup variant.product = self variant.is_master = false diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 70bfb38227..2160b42b5b 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -86,24 +86,27 @@ module Spree end end - # context "saving a new product" do - # let!(:product){ Spree::Product.new } - # - # before do - # product.primary_taxon = create(:taxon) - # product.supplier = create(:supplier_enterprise) - # product.name = "Product1" - # product.variant_unit = "weight" - # product.on_hand = 3 - # product.price = 4.27 - # product.save! - # end - # - # it "copies the properties on master variant to the first standard variant" do - # standard_variant = product.variants(:reload).first - # expect(standard_variant.price).to eq product.master.price - # end - # end + context "saving a new product" do + let!(:product){ Spree::Product.new } + + before do + product.primary_taxon = create(:taxon) + product.supplier = create(:supplier_enterprise) + product.name = "Product1" + product.variant_unit = "weight" + product.variant_unit_scale = 1000 + product.unit_value = 1 + product.on_hand = 3 + product.price = 4.27 + product.save! + end + + it "copies the properties on master variant to the first standard variant" do + expect(product.variants(:reload).length).to eq 1 + standard_variant = product.variants(:reload).first + expect(standard_variant.price).to eq product.master.price + end + end context "when the unit is items" do it "is valid when unit name is set and unit scale is not" do From 28486f9e76130e6381d45bd9df91b5031e446849 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 17:24:10 +1000 Subject: [PATCH 010/206] Only adds standard variant on create --- app/models/spree/product_decorator.rb | 2 +- spec/models/spree/product_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index c83ffd0c0a..bb246ace59 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -21,7 +21,7 @@ Spree::Product.class_eval do attr_accessible :supplier_id, :primary_taxon_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value, :unit_description, :notes, :images_attributes, :display_as - before_validation :ensure_standard_variant + before_validation :ensure_standard_variant, if: :new_record? validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 2160b42b5b..7eb5b777ac 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -106,6 +106,11 @@ module Spree standard_variant = product.variants(:reload).first expect(standard_variant.price).to eq product.master.price end + + it "only duplicates master variant on initial save" do + product.save! + expect(product.variants(:reload).length).to eq 1 + end end context "when the unit is items" do From 971723964e05d8d576d20be9aeb2dd5d1e02a5db Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 17:49:57 +1000 Subject: [PATCH 011/206] Update outdated spec --- spec/models/spree/product_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 7eb5b777ac..57327b75ad 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -28,7 +28,12 @@ module Spree product = Product.new supplier_id: s.id, name: "Apples", price: 1, primary_taxon_id: t.id, variant_unit: "weight", variant_unit_scale: 1000, unit_value: 1 product.on_hand = "10,000" expect(product.save).to be_false - expect(product.errors[:count_on_hand]).to include "is not a number" + + # It is expected that master variant :count_on_hand should cause the failure to save, and it does. However, this broke with the addition of + # the :ensure_standard_variant callback. Evidently, normal variants are checked and their errors added to the product model before the + # save_master callback on product is run. Therefore, the error that is raised here is one on a normal variant, rather than one on master. + # expect(product.errors[:count_on_hand]).to include "is not a number" + expect(product.errors[:"variants.count_on_hand"]).to include "is not a number" end it "defaults available_on to now" do From 89afbc80a6953a984854eb17f4e4975087d1f043 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 18:01:46 +1000 Subject: [PATCH 012/206] Set initial on_hand to 0 --- spec/models/spree/variant_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index aa3296e5dc..eec2868453 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -14,7 +14,7 @@ module Spree describe "finding variants in stock" do before do - p = create(:product) + p = create(:product, on_hand: 0) @v_in_stock = create(:variant, product: p) @v_on_demand = create(:variant, product: p, on_demand: true) @v_no_stock = create(:variant, product: p) From 5e2fe56c228ed1f1a7d7b40fc22f4de74529fba3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 18:05:24 +1000 Subject: [PATCH 013/206] Cleanup --- spec/models/spree/product_spec.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 57327b75ad..07b67ce872 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -135,10 +135,10 @@ module Spree end end - context "when product does not have variants" do + context "a basic product" do let(:product) { create(:simple_product) } - it "does not require any variant unit fields" do + it "requires variant unit fields" do product.variant_unit = nil product.variant_unit_name = nil product.variant_unit_scale = nil @@ -523,16 +523,6 @@ module Spree end describe "finding products in stock for a particular distribution" do - it "returns in-stock products without variants" do - p = create(:simple_product) - p.master.update_attribute(:count_on_hand, 1) - d = create(:distributor_enterprise) - oc = create(:simple_order_cycle, distributors: [d]) - oc.exchanges.outgoing.first.variants << p.master - - p.should have_stock_for_distribution(oc, d) - end - it "returns on-demand products" do p = create(:simple_product, on_demand: true) p.master.update_attribute(:count_on_hand, 0) From a223a2d662a44896b7fa1615dccda1751895bf50 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 18:32:40 +1000 Subject: [PATCH 014/206] Cannot remove all variants from a product --- app/models/spree/product_decorator.rb | 1 + spec/models/spree/product_spec.rb | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index bb246ace59..3d35700b7c 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -23,6 +23,7 @@ Spree::Product.class_eval do before_validation :ensure_standard_variant, if: :new_record? + validates_presence_of :variants, message: "Product must have at least one variant" validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } validates :tax_category_id, presence: { message: "^Tax Category can't be blank" }, if: "Spree::Config.products_require_tax_category" diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 07b67ce872..35dd847d1f 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -8,7 +8,7 @@ module Spree it { should belong_to(:primary_taxon) } it { should have_many(:product_distributions) } end - + describe "validations and defaults" do it "is valid when built from factory" do build(:product).should be_valid @@ -61,6 +61,14 @@ module Spree end + it "does not allow only standard variant to be deleted" do + product = create(:simple_product) + expect(product.variants(:reload).length).to eq 1 + product.variants = [] + expect(product.save).to be_false + expect(product.errors[:variants]).to include "Product must have at least one variant" + end + context "when the product has variants" do let(:product) do product = create(:simple_product) From fcb3bc894b18fb6e43a1b00f5b4413b19a3173b6 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 2 Oct 2014 18:35:37 +1000 Subject: [PATCH 015/206] Cleanup --- spec/models/spree/variant_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index eec2868453..c038bf4598 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -239,19 +239,6 @@ module Spree end end - context "when the product does not have variants" do - let(:product) { create(:simple_product) } - let(:variant) { product.master } - - it "requires unit value and/or unit description" do - variant.unit_value = nil - variant.unit_description = nil - variant.should be_invalid - variant.unit_value = 1 - variant.should be_valid - end - end - describe "unit value/description" do describe "getting name for display" do it "returns display_name if present" do From 2b47c9145a9b18f346dd186ef4254dd062eb0648 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 3 Oct 2014 13:59:34 +1000 Subject: [PATCH 016/206] Cannot delete last variant of product --- app/models/spree/variant_decorator.rb | 8 ++++++++ spec/models/spree/variant_spec.rb | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 703111c884..ee05b3b2b8 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -18,6 +18,7 @@ Spree::Variant.class_eval do if: -> v { v.product.andand.variant_unit.present? && v.unit_value.nil? } before_validation :update_weight_from_unit_value, if: -> v { v.product.present? } + before_destroy :check_product_variants after_save :update_units scope :with_order_cycles_inner, joins(exchanges: :order_cycle) @@ -123,4 +124,11 @@ Spree::Variant.class_eval do option_value_namer.name end end + + def check_product_variants + if product.variants == [self] # Only variant left on product + errors.add :product, "must have at least one variant" + return false + end + end end diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index c038bf4598..2c37065916 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -399,5 +399,20 @@ module Spree v.destroy e.reload.variant_ids.should be_empty end + + context "as the last variant of a product" do + let!(:product) { create(:simple_product) } + let!(:first_variant) { product.variants(:reload).first } + let!(:extra_variant) { create(:variant, product: product) } + + it "cannot be deleted" do + expect(product.variants(:reload).length).to eq 2 + expect(extra_variant.destroy).to eq extra_variant + expect(product.variants(:reload).length).to eq 1 + expect(first_variant.destroy).to be_false + expect(product.variants(:reload).length).to eq 1 + expect(first_variant.errors[:product]).to include "must have at least one variant" + end + end end end From 4b182f92484a138c31620977c3e5b40b6efccb9c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 3 Oct 2014 14:08:03 +1000 Subject: [PATCH 017/206] Can't delete final variant on a product from BPE --- .../admin/bulk_product_update.js.coffee | 19 +++++++++------- .../unit/bulk_product_update_spec.js.coffee | 22 +++++++++++++++---- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index e8e14413ae..5ec6eb52f8 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -135,15 +135,18 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.deleteVariant = (product, variant) -> - if !$scope.variantSaved(variant) - $scope.removeVariant(product, variant) + if product.variants.length > 1 + if !$scope.variantSaved(variant) + $scope.removeVariant(product, variant) + else + if confirm("Are you sure?") + $http( + method: "DELETE" + url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete" + ).success (data) -> + $scope.removeVariant(product, variant) else - if confirm("Are you sure?") - $http( - method: "DELETE" - url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete" - ).success (data) -> - $scope.removeVariant(product, variant) + alert("The last variant cannot be deleted!") $scope.removeVariant = (product, variant) -> product.variants.splice product.variants.indexOf(variant), 1 diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index a6fb0fa432..eb3830204c 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -777,22 +777,31 @@ describe "AdminProductEditCtrl", -> describe "deleting variants", -> + describe "when the variant is the only one left on the product", -> + it "alerts the user", -> + spyOn(window, "alert") + $scope.products = [ + {id: 1, variants: [{id: 1}]} + ] + $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] + expect(window.alert).toHaveBeenCalledWith "The last variant cannot be deleted!" + describe "when the variant has not been saved", -> it "removes the variant from products and dirtyProducts", -> spyOn(window, "confirm").andReturn true $scope.products = [ - {id: 1, variants: [{id: -1}]} + {id: 1, variants: [{id: -1},{id: -2}]} ] DirtyProducts.addVariantProperty 1, -1, "something", "something" DirtyProducts.addProductProperty 1, "something", "something" $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] expect($scope.products).toEqual([ - {id: 1, variants: []} + {id: 1, variants: [{id: -2}]} ]) expect(DirtyProducts.all()).toEqual 1: { id: 1, something: 'something'} - + describe "when the variant has been saved", -> it "deletes variants with a http delete request to /api/products/product_permalink/variants/(variant_id)/soft_delete", -> spyOn(window, "confirm").andReturn true @@ -800,9 +809,14 @@ describe "AdminProductEditCtrl", -> { id: 9 permalink_live: "apples" - variants: [ + variants: [{ id: 3 price: 12 + }, + { + id: 4 + price: 15 + } ] } { From 8248e382f34139b3cd51805c99c84058e66db784 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 3 Oct 2014 14:30:23 +1000 Subject: [PATCH 018/206] Greying out disabled action button --- .../stylesheets/admin/openfoodnetwork.css.scss | 15 +++++++++++++++ .../bulk_edit/_products_variant.html.haml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 641e6c5756..8518b47e50 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -249,3 +249,18 @@ span.required { color: red; font-size: 110%; } + +table td.actions { + .icon-trash, .icon-edit, icon-copy { + &.disabled { + border-color: #d0d0d0; + color: #c0c0c0; + background-color: #fafafa; + &:hover { + border-color: #a5a5a5; + color: #a5a5a5; + background-color: #fafafa; + } + } + } +} diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index 28f6ed6321..8825cec46c 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -20,4 +20,4 @@ %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" } %td.actions %td.actions - %a{ 'ng-click' => 'deleteVariant(product,variant)', :class => "delete-variant icon-trash no-text" } + %a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text" } From 447a5481a38aadbc21f7d5eebe15e17aaf772694 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 3 Oct 2014 17:56:05 +1000 Subject: [PATCH 019/206] WIP: Building migration to duplicate master variants --- ...060622_add_standard_variant_to_products.rb | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 db/migrate/20141003060622_add_standard_variant_to_products.rb diff --git a/db/migrate/20141003060622_add_standard_variant_to_products.rb b/db/migrate/20141003060622_add_standard_variant_to_products.rb new file mode 100644 index 0000000000..1463784fba --- /dev/null +++ b/db/migrate/20141003060622_add_standard_variant_to_products.rb @@ -0,0 +1,49 @@ +class AddStandardVariantToProducts < ActiveRecord::Migration + def change + # Find products without any standard variants + products_with_only_master = Spree::Product.where( variants: [] ) + + + products_with_only_master.each do |product| + # Run the callback to add a copy of the master variant as a standard variant + product.send(:ensure_standard_variant) + + existing_master = product.master + new_variant = product.variants.first + + # Replace any existing references to the master variant with the new standard variant + + # Option Values + # Strategy: add all option values on existing_master to new_variant, and keep on existing_master + option_values = existing_master.option_values + option_values.each do |option_value| + variant_ids = option_value.variant_ids + variant_ids << new_variant.id + option_value.update_attributes(variant_ids: variant_ids) + end + + # Inventory Units + # Strategy: completely replace all references to existing_master with new_variant + inventory_units = existing_master.inventory_units + inventory_units.each do |inventory_unit| + inventory_unit.update_attributes(variant_id: new_variant.id ) + end + + # Line Items + # Strategy: completely replace all references to existing_master with new_variant + line_items = existing_master.line_items + line_items.each do |line_item| + line_item.update_attributes(variant_id: new_variant.id ) + end + + # Prices + # Strategy: duplicate all prices on existing_master and assign them to new_variant + prices = existing_master.prices + new_prices = [] + prices.each do |price| + new_prices << price.dup + end + new_variant.update_attributes(prices: new_prices) + end + end +end From 5d9e861ee428796b1b6881c28e697f041fd85b14 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 28 Jan 2015 17:13:56 +1100 Subject: [PATCH 020/206] Working migration to complete deprecation of master variants --- ...060622_add_standard_variant_to_products.rb | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/db/migrate/20141003060622_add_standard_variant_to_products.rb b/db/migrate/20141003060622_add_standard_variant_to_products.rb index 1463784fba..035cbff4d7 100644 --- a/db/migrate/20141003060622_add_standard_variant_to_products.rb +++ b/db/migrate/20141003060622_add_standard_variant_to_products.rb @@ -1,8 +1,7 @@ class AddStandardVariantToProducts < ActiveRecord::Migration - def change + def up # Find products without any standard variants - products_with_only_master = Spree::Product.where( variants: [] ) - + products_with_only_master = Spree::Product.find(:all, :include => "variants", :conditions => ["spree_variants.id IS NULL"]) products_with_only_master.each do |product| # Run the callback to add a copy of the master variant as a standard variant @@ -11,7 +10,15 @@ class AddStandardVariantToProducts < ActiveRecord::Migration existing_master = product.master new_variant = product.variants.first - # Replace any existing references to the master variant with the new standard variant + # Replace any relevant references to the master variant with the new standard variant + + # Inventory Units + # Strategy: do nothing to inventory units pertaining to existing_master, + # new inventory units will be created with reference to new_variant + + # Line Items + # Strategy: do nothing to line items pertaining to existing_master, + # new line items will be created with reference to new_variant # Option Values # Strategy: add all option values on existing_master to new_variant, and keep on existing_master @@ -22,28 +29,19 @@ class AddStandardVariantToProducts < ActiveRecord::Migration option_value.update_attributes(variant_ids: variant_ids) end - # Inventory Units - # Strategy: completely replace all references to existing_master with new_variant - inventory_units = existing_master.inventory_units - inventory_units.each do |inventory_unit| - inventory_unit.update_attributes(variant_id: new_variant.id ) - end - - # Line Items - # Strategy: completely replace all references to existing_master with new_variant - line_items = existing_master.line_items - line_items.each do |line_item| - line_item.update_attributes(variant_id: new_variant.id ) - end - # Prices # Strategy: duplicate all prices on existing_master and assign them to new_variant - prices = existing_master.prices - new_prices = [] - prices.each do |price| - new_prices << price.dup + existing_prices = existing_master.prices + existing_prices.each do |price| + new_variant.prices << price.dup + end + + # Exchange Variants + # Strategy: Replace all references to existing master in exchanges with new_variant + exchange_variants = ExchangeVariant.where(variant_id: existing_master.id) + exchange_variants.each do |exchange_variant| + exchange_variant.update_attributes(variant_id: new_variant.id ) end - new_variant.update_attributes(prices: new_prices) end end end From 7596270154f45a03bd90e418c9adabfcb2457d27 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 29 Jan 2015 12:37:31 +1100 Subject: [PATCH 021/206] A few more changes to tidy up standard variant migration --- .../20141003060622_add_standard_variant_to_products.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/db/migrate/20141003060622_add_standard_variant_to_products.rb b/db/migrate/20141003060622_add_standard_variant_to_products.rb index 035cbff4d7..7e7f752a5d 100644 --- a/db/migrate/20141003060622_add_standard_variant_to_products.rb +++ b/db/migrate/20141003060622_add_standard_variant_to_products.rb @@ -1,7 +1,7 @@ class AddStandardVariantToProducts < ActiveRecord::Migration def up # Find products without any standard variants - products_with_only_master = Spree::Product.find(:all, :include => "variants", :conditions => ["spree_variants.id IS NULL"]) + products_with_only_master = Spree::Product.includes(:variants).where('spree_variants.id IS NULL') products_with_only_master.each do |product| # Run the callback to add a copy of the master variant as a standard variant @@ -39,9 +39,7 @@ class AddStandardVariantToProducts < ActiveRecord::Migration # Exchange Variants # Strategy: Replace all references to existing master in exchanges with new_variant exchange_variants = ExchangeVariant.where(variant_id: existing_master.id) - exchange_variants.each do |exchange_variant| - exchange_variant.update_attributes(variant_id: new_variant.id ) - end + exchange_variants.update_all(variant_id: new_variant.id) end end end From 2d7fb3fd6749e1e27cda29640dc4fd7c6f3f45dd Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 29 Jan 2015 12:43:40 +1100 Subject: [PATCH 022/206] Updating name of spec --- spec/models/spree/product_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 35dd847d1f..ddc8f99757 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -61,7 +61,7 @@ module Spree end - it "does not allow only standard variant to be deleted" do + it "does not allow the last variant to be deleted" do product = create(:simple_product) expect(product.variants(:reload).length).to eq 1 product.variants = [] From 9ee25c4e424ac74cd5c852e8dcdcff7f09a66aab Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 29 Jan 2015 13:06:28 +1100 Subject: [PATCH 023/206] Making spec better --- spec/models/spree/product_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index ddc8f99757..42c85e98e3 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -120,9 +120,10 @@ module Spree expect(standard_variant.price).to eq product.master.price end - it "only duplicates master variant on initial save" do + it "only duplicates master variant on create" do + expect(product).to_not receive :ensure_standard_variant + product.name = "Something else" product.save! - expect(product.variants(:reload).length).to eq 1 end end From b9f19d5777ed5034297dd3725c17bccffd39cf5d Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 17 Apr 2015 13:00:20 +1000 Subject: [PATCH 024/206] Fixing broken specs --- .../admin/variants_controller_decorator.rb | 2 +- app/models/spree/product_decorator.rb | 5 +- app/models/spree/product_set.rb | 2 +- app/models/spree/variant_decorator.rb | 18 +- app/views/shopping_shared/_contact.html.haml | 4 +- spec/controllers/cart_controller_spec.rb | 3 +- spec/controllers/shop_controller_spec.rb | 15 +- .../spree/admin/variants_controller_spec.rb | 10 +- spec/factories.rb | 2 +- .../admin/bulk_product_update_spec.rb | 292 ++++-------------- spec/features/admin/order_cycles_spec.rb | 42 +-- spec/features/admin/orders_spec.rb | 2 +- spec/features/admin/products_spec.rb | 5 +- spec/features/admin/reports_spec.rb | 26 +- spec/features/admin/variants_spec.rb | 27 +- .../consumer/shopping/shopping_spec.rb | 65 ++-- .../products_and_inventory_report_spec.rb | 45 ++- spec/models/enterprise_spec.rb | 33 +- spec/models/order_cycle_spec.rb | 18 +- spec/requests/shop_spec.rb | 73 ++--- 20 files changed, 237 insertions(+), 452 deletions(-) diff --git a/app/controllers/spree/admin/variants_controller_decorator.rb b/app/controllers/spree/admin/variants_controller_decorator.rb index acb55327ae..f83bf69eca 100644 --- a/app/controllers/spree/admin/variants_controller_decorator.rb +++ b/app/controllers/spree/admin/variants_controller_decorator.rb @@ -4,7 +4,7 @@ Spree::Admin::VariantsController.class_eval do def search search_params = { :product_name_cont => params[:q], :sku_cont => params[:q] } - @variants = Spree::Variant.ransack(search_params.merge(:m => 'or')).result + @variants = Spree::Variant.where(is_master: false).ransack(search_params.merge(:m => 'or')).result if params[:distributor_id].present? distributor = Enterprise.find params[:distributor_id] diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 3d35700b7c..f7834dc3ee 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -21,9 +21,7 @@ Spree::Product.class_eval do attr_accessible :supplier_id, :primary_taxon_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value, :unit_description, :notes, :images_attributes, :display_as - before_validation :ensure_standard_variant, if: :new_record? - - validates_presence_of :variants, message: "Product must have at least one variant" + # validates_presence_of :variants, unless: :new_record?, message: "Product must have at least one variant" validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } validates :tax_category_id, presence: { message: "^Tax Category can't be blank" }, if: "Spree::Config.products_require_tax_category" @@ -34,6 +32,7 @@ Spree::Product.class_eval do validates_presence_of :variant_unit_name, if: -> p { p.variant_unit == 'items' } + after_save :ensure_standard_variant, if: lambda { master.valid? && variants.empty? } after_initialize :set_available_on_to_now, :if => :new_record? after_save :update_units after_touch :touch_distributors diff --git a/app/models/spree/product_set.rb b/app/models/spree/product_set.rb index 9b4b771f37..1a73ab00fe 100644 --- a/app/models/spree/product_set.rb +++ b/app/models/spree/product_set.rb @@ -14,7 +14,7 @@ class Spree::ProductSet < ModelSet if e.nil? @klass.new(attributes).save unless @reject_if.andand.call(attributes) else - e.update_attributes(attributes.except(:id, :variants_attributes, :master_attributes)) and + ( attributes.except(:id, :variants_attributes, :master_attributes).present? ? e.update_attributes(attributes.except(:id, :variants_attributes, :master_attributes)) : true) and (attributes[:variants_attributes] ? update_variants_attributes(e, attributes[:variants_attributes]) : true ) and (attributes[:master_attributes] ? update_variant(e, attributes[:master_attributes]) : true ) end diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index ee05b3b2b8..20c4f20cf7 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -18,7 +18,6 @@ Spree::Variant.class_eval do if: -> v { v.product.andand.variant_unit.present? && v.unit_value.nil? } before_validation :update_weight_from_unit_value, if: -> v { v.product.present? } - before_destroy :check_product_variants after_save :update_units scope :with_order_cycles_inner, joins(exchanges: :order_cycle) @@ -103,9 +102,13 @@ Spree::Variant.class_eval do end def delete - transaction do - self.update_column(:deleted_at, Time.now) - ExchangeVariant.where(variant_id: self).destroy_all + if product.variants == [self] # Only variant left on product + errors.add :product, "must have at least one variant" + else + transaction do + self.update_column(:deleted_at, Time.now) + ExchangeVariant.where(variant_id: self).destroy_all + end end end @@ -124,11 +127,4 @@ Spree::Variant.class_eval do option_value_namer.name end end - - def check_product_variants - if product.variants == [self] # Only variant left on product - errors.add :product, "must have at least one variant" - return false - end - end end diff --git a/app/views/shopping_shared/_contact.html.haml b/app/views/shopping_shared/_contact.html.haml index a5e7cddad9..6069d453a2 100644 --- a/app/views/shopping_shared/_contact.html.haml +++ b/app/views/shopping_shared/_contact.html.haml @@ -2,7 +2,7 @@ .panel .row .small-12.large-4.columns - - if current_distributor.address.address1 || current_distributor.address.address2 || current_distributor.address.city || current_distributor.address.state || current_distributor.address.zipcode + - if current_distributor.address.address1 || current_distributor.address.address2 || current_distributor.address.city || current_distributor.address.state || current_distributor.address.zipcode %div.center .header Address %strong=current_distributor.name @@ -49,7 +49,7 @@ - unless current_distributor.linkedin.blank? %span %a{href: "http://#{current_distributor.linkedin}", target: "_blank" } - %i.ofn-i_042-linkedin + %i.ofn-i_042-linkedin / = current_distributor.linkedin - unless current_distributor.instagram.blank? diff --git a/spec/controllers/cart_controller_spec.rb b/spec/controllers/cart_controller_spec.rb index db311a8104..4fda0d7824 100644 --- a/spec/controllers/cart_controller_spec.rb +++ b/spec/controllers/cart_controller_spec.rb @@ -75,8 +75,9 @@ module OpenFoodNetwork it 'should add variant to new order and return the order' do product1.distributors << distributor product1.save + variant = product1.variants.first - put :add_variant, { cart_id: cart, variant_id: product1.master.id, quantity: (product1.master.on_hand-1), distributor_id: distributor, order_cycle_id: nil, max_quantity: nil } + put :add_variant, { cart_id: cart, variant_id: variant.id, quantity: (variant.on_hand-1), distributor_id: distributor, order_cycle_id: nil, max_quantity: nil } cart.orders.size.should == 1 cart.orders.first.line_items.size.should == 1 diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index 4b7c53da19..3fdc1a7ea9 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -112,10 +112,10 @@ describe ShopController do let!(:p4) { create(:product, name: "jkl", primary_taxon_id: t1.id) } before do - exchange.variants << p1.master - exchange.variants << p2.master - exchange.variants << p3.master - exchange.variants << p4.master + exchange.variants << p1.variants.first + exchange.variants << p2.variants.first + exchange.variants << p3.variants.first + exchange.variants << p4.variants.first end it "sorts products by the distributor's preferred taxon list" do @@ -136,19 +136,20 @@ describe ShopController do context "RABL tests" do render_views let(:product) { create(:product) } + let(:variant) { product.variants.first } before do - exchange.variants << product.master + exchange.variants << variant controller.stub(:current_order_cycle).and_return order_cycle end + it "only returns products for the current order cycle" do xhr :get, :products response.body.should have_content product.name end it "doesn't return products not in stock" do - product.update_attribute(:on_demand, false) - product.master.update_attribute(:count_on_hand, 0) + variant.update_attribute(:count_on_hand, 0) xhr :get, :products response.body.should_not have_content product.name end diff --git a/spec/controllers/spree/admin/variants_controller_spec.rb b/spec/controllers/spree/admin/variants_controller_spec.rb index 9c6d77194d..a0e7918d7c 100644 --- a/spec/controllers/spree/admin/variants_controller_spec.rb +++ b/spec/controllers/spree/admin/variants_controller_spec.rb @@ -8,22 +8,24 @@ module Spree describe "search action" do let!(:p1) { create(:simple_product, name: 'Product 1') } let!(:p2) { create(:simple_product, name: 'Product 2') } + let!(:v1) { p1.variants.first } + let!(:v2) { p2.variants.first } let!(:d) { create(:distributor_enterprise) } - let!(:oc) { create(:simple_order_cycle, distributors: [d], variants: [p1.master]) } + let!(:oc) { create(:simple_order_cycle, distributors: [d], variants: [v1]) } it "filters by distributor" do spree_get :search, q: 'Prod', distributor_id: d.id.to_s - assigns(:variants).should == [p1.master] + assigns(:variants).should == [v1] end it "filters by order cycle" do spree_get :search, q: 'Prod', order_cycle_id: oc.id.to_s - assigns(:variants).should == [p1.master] + assigns(:variants).should == [v1] end it "does not filter when no distributor or order cycle is specified" do spree_get :search, q: 'Prod' - assigns(:variants).sort.should == [p1.master, p2.master].sort + assigns(:variants).sort.should == [v1,v2].sort end end end diff --git a/spec/factories.rb b/spec/factories.rb index 7effe49a9d..82db4c1520 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -45,7 +45,7 @@ FactoryGirl.define do image = File.open(File.expand_path('../../app/assets/images/logo.jpg', __FILE__)) Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 1", :attachment => image, :position => 1}) - exchange.variants << product.master + exchange.variants << product.variants.first end variants = [ex1, ex2].map(&:variants).flatten diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index debb234284..f5cc856da2 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -53,62 +53,18 @@ feature %q{ expect(page).to have_field "available_on", with: p2.available_on.strftime("%F %T") end - it "displays a price input for each product without variants (ie. for master variant)" do - p1 = FactoryGirl.create(:product) - p2 = FactoryGirl.create(:product) - p3 = FactoryGirl.create(:product) - v = FactoryGirl.create(:variant, product: p3) - - p1.update_attribute :price, 22.0 - p2.update_attribute :price, 44.0 - p3.update_attribute :price, 66.0 + it "displays an on hand count in a span for each product" do + p1 = FactoryGirl.create(:product, on_hand: 15) + v1 = p1.variants.first + v1.on_hand = 4 + v1.save! visit '/admin/products/bulk_edit' - expect(page).to have_field "price", with: "22.0" - expect(page).to have_field "price", with: "44.0" - expect(page).to have_no_field "price", with: "66.0", visible: true - end - - it "displays an on hand count input for each product (ie. for master variant) if no regular variants exist" do - p1 = FactoryGirl.create(:product) - p2 = FactoryGirl.create(:product) - p1.on_hand = 15 - p2.on_hand = 12 - p1.save! - p2.save! - - visit '/admin/products/bulk_edit' - - expect(page).to have_no_selector "span[name='on_hand']", text: "0" - expect(page).to have_field "on_hand", with: "15" - expect(page).to have_field "on_hand", with: "12" - end - - it "displays an on hand count in a span for each product (ie. for master variant) if other variants exist" do - p1 = FactoryGirl.create(:product) - p2 = FactoryGirl.create(:product) - v1 = FactoryGirl.create(:variant, product: p1, is_master: false, on_hand: 4) - p1.on_hand = 15 - p2.on_hand = 12 - p1.save! - p2.save! - - visit '/admin/products/bulk_edit' - - expect(page).to have_no_field "on_hand", with: "15" - expect(page).to have_selector "span[name='on_hand']", text: "4" - expect(page).to have_field "on_hand", with: "12" - end - - it "displays 'on demand' for the on hand count when the product is available on demand" do - p1 = FactoryGirl.create(:product, on_demand: true) - p1.master.on_demand = true; p1.master.save! - - visit '/admin/products/bulk_edit' - - expect(page).to have_selector "span[name='on_hand']", text: "On demand" - expect(page).to have_no_field "on_hand", visible: true + within "#p_#{p1.id}" do + expect(page).to have_no_field "on_hand", with: "15" + expect(page).to have_selector "span[name='on_hand']", text: "4" + end end it "displays 'on demand' for any variant that is available on demand" do @@ -170,7 +126,7 @@ feature %q{ visit '/admin/products/bulk_edit' all("a.view-variants").each { |e| e.trigger('click') } - expect(page).to have_selector "span[name='on_hand']", text: "21" + expect(page).to have_selector "span[name='on_hand']", text: p1.variants.sum{ |v| v.on_hand }.to_s expect(page).to have_field "variant_on_hand", with: "15" expect(page).to have_field "variant_on_hand", with: "6" end @@ -218,7 +174,9 @@ feature %q{ expect(page).to have_content 'NEW PRODUCT' fill_in 'product_name', :with => 'Big Bag Of Apples' - select(s.name, :from => 'product_supplier_id') + select s.name, :from => 'product_supplier_id' + select 'Weight (g)', from: 'product_variant_unit_with_scale' + fill_in 'product_unit_value_with_description', with: '100' fill_in 'product_price', :with => '10.00' select taxon.name, from: 'product_primary_taxon_id' click_button 'Create' @@ -231,34 +189,25 @@ feature %q{ scenario "creating new variants" do # Given a product without variants or a unit - p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) + p = FactoryGirl.create(:product, variant_unit: 'weight', variant_unit_scale: 1000) login_to_admin_section visit '/admin/products/bulk_edit' - # I should not see an add variant button - expect(page).to have_no_selector 'a.add-variant', visible: true - - # When I set the unit - select "Weight (kg)", from: "variant_unit_with_scale" - # I should see an add variant button - expect(page).to have_selector 'a.add-variant', visible: true + page.find('a.view-variants').trigger('click') # When I add three variants page.find('a.add-variant', visible: true).trigger('click') page.find('a.add-variant', visible: true).trigger('click') - page.find('a.add-variant', visible: true).trigger('click') - # They should be added, and should see no edit buttons - variant_count = page.all("tr.variant").count - expect(variant_count).to eq 3 - expect(page).to have_no_selector "a.edit-variant", visible: true + # They should be added, and should not see edit buttons for new variants + expect(page).to have_selector "tr.variant", count: 3 + expect(page).to have_selector "a.edit-variant", count: 1 # When I remove two, they should be removed page.all('a.delete-variant').first.click page.all('a.delete-variant').first.click - variant_count = page.all("tr.variant").count - expect(variant_count).to eq 1 + expect(page).to have_selector "tr.variant", count: 1 # When I fill out variant details and hit update fill_in "variant_display_name", with: "Case of 12 Bottles" @@ -266,6 +215,7 @@ feature %q{ fill_in "variant_display_as", with: "Case" fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" + click_button 'Save Changes' expect(page.find("#status-message")).to have_content "Changes saved." @@ -281,59 +231,6 @@ feature %q{ expect(page).to have_selector "a.edit-variant", visible: true end - - scenario "updating a product with no variants (except master)" do - s1 = FactoryGirl.create(:supplier_enterprise) - s2 = FactoryGirl.create(:supplier_enterprise) - t1 = FactoryGirl.create(:taxon) - t2 = FactoryGirl.create(:taxon) - p = FactoryGirl.create(:product, supplier: s1, available_on: Date.today, variant_unit: 'volume', variant_unit_scale: 1, primary_taxon: t2) - p.price = 10.0 - p.on_hand = 6; - p.save! - - login_to_admin_section - - visit '/admin/products/bulk_edit' - - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click - first("div#columns_dropdown div.menu div.menu_item", text: "Category").click - - within "tr#p_#{p.id}" do - expect(page).to have_field "product_name", with: p.name - expect(page).to have_select "producer_id", selected: s1.name - expect(page).to have_field "available_on", with: p.available_on.strftime("%F %T") - expect(page).to have_field "price", with: "10.0" - expect(page).to have_selector "div#s2id_p#{p.id}_category_id a.select2-choice" - expect(page).to have_select "variant_unit_with_scale", selected: "Volume (L)" - expect(page).to have_field "on_hand", with: "6" - - fill_in "product_name", with: "Big Bag Of Potatoes" - select s2.name, :from => 'producer_id' - fill_in "available_on", with: (3.days.ago.beginning_of_day).strftime("%F %T") - fill_in "price", with: "20" - select "Weight (kg)", from: "variant_unit_with_scale" - select2_select t1.name, from: "p#{p.id}_category_id" - fill_in "on_hand", with: "18" - fill_in "display_as", with: "Big Bag" - end - - click_button 'Save Changes' - expect(page.find("#status-message")).to have_content "Changes saved." - - p.reload - expect(p.name).to eq "Big Bag Of Potatoes" - expect(p.supplier).to eq s2 - expect(p.variant_unit).to eq "weight" - expect(p.variant_unit_scale).to eq 1000 # Kg - expect(p.available_on).to eq 3.days.ago.beginning_of_day - expect(p.master.display_as).to eq "Big Bag" - expect(p.price).to eq 20.0 - expect(p.on_hand).to eq 18 - expect(p.primary_taxon).to eq t1 - end - scenario "updating a product with a variant unit of 'items'" do p = FactoryGirl.create(:product, variant_unit: 'weight', variant_unit_scale: 1000) @@ -355,74 +252,12 @@ feature %q{ expect(p.variant_unit_name).to eq "loaf" end - scenario "setting a variant unit on a product that has none" do - p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) - v = FactoryGirl.create(:variant, product: p, unit_value: nil, unit_description: nil) - - login_to_admin_section - - visit '/admin/products/bulk_edit' - - expect(page).to have_select "variant_unit_with_scale", selected: '' - - select "Weight (kg)", from: "variant_unit_with_scale" - first("a.view-variants").trigger('click') - fill_in "variant_unit_value_with_description", with: '123 abc' - - click_button 'Save Changes' - expect(page.find("#status-message")).to have_content "Changes saved." - - p.reload - expect(p.variant_unit).to eq "weight" - expect(p.variant_unit_scale).to eq 1000 # Kg - v.reload - expect(v.unit_value).to eq 123000 # 123 kg in g - expect(v.unit_description).to eq "abc" - end - - describe "setting the master unit value for a product without variants" do - it "sets the master unit value" do - p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) - - login_to_admin_section - - visit '/admin/products/bulk_edit' - - expect(page).to have_select "variant_unit_with_scale", selected: '' - expect(page).to have_no_field "master_unit_value_with_description", visible: true - - select "Weight (kg)", from: "variant_unit_with_scale" - fill_in "master_unit_value_with_description", with: '123 abc' - - click_button 'Save Changes' - expect(page.find("#status-message")).to have_content "Changes saved." - - p.reload - expect(p.variant_unit).to eq 'weight' - expect(p.variant_unit_scale).to eq 1000 - expect(p.master.unit_value).to eq 123000 - expect(p.master.unit_description).to eq 'abc' - end - - it "does not show the field when the product has variants" do - p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) - v = FactoryGirl.create(:variant, product: p, unit_value: nil, unit_description: nil) - - login_to_admin_section - - visit '/admin/products/bulk_edit' - - select "Weight (kg)", from: "variant_unit_with_scale" - expect(page).to have_no_field "master_unit_value_with_description", visible: true - end - end - - scenario "updating a product with variants" do s1 = FactoryGirl.create(:supplier_enterprise) s2 = FactoryGirl.create(:supplier_enterprise) - p = FactoryGirl.create(:product, supplier: s1, available_on: Date.today, variant_unit: 'volume', variant_unit_scale: 0.001) - v = FactoryGirl.create(:variant, product: p, price: 3.0, on_hand: 9, unit_value: 0.25, unit_description: '(bottle)') + p = FactoryGirl.create(:product, supplier: s1, available_on: Date.today, variant_unit: 'volume', variant_unit_scale: 0.001, + price: 3.0, on_hand: 9, unit_value: 0.25, unit_description: '(bottle)' ) + v = p.variants.first login_to_admin_section @@ -433,7 +268,7 @@ feature %q{ expect(page).to have_field "variant_price", with: "3.0" expect(page).to have_field "variant_unit_value_with_description", with: "250 (bottle)" expect(page).to have_field "variant_on_hand", with: "9" - expect(page).to have_selector "span[name='on_hand']", text: "9" + expect(page).to have_selector "span[name='on_hand']", "9" select "Volume (L)", from: "variant_unit_with_scale" fill_in "variant_price", with: "4.0" @@ -464,7 +299,9 @@ feature %q{ expect(page).to have_field "variant_price", with: "3.0" - fill_in "variant_price", with: "10.0" + within "#v_#{v.id}" do + fill_in "variant_price", with: "10.0" + end click_button 'Save Changes' expect(page.find("#status-message")).to have_content "Changes saved." @@ -553,40 +390,39 @@ feature %q{ describe "using action buttons" do describe "using delete buttons" do - it "shows a delete button for products, which deletes the appropriate product when clicked" do - p1 = FactoryGirl.create(:product) - p2 = FactoryGirl.create(:product) - p3 = FactoryGirl.create(:product) - login_to_admin_section + let!(:p1) { FactoryGirl.create(:product) } + let!(:p2) { FactoryGirl.create(:product) } + let!(:v1) { p1.variants.first } + let!(:v2) { p2.variants.first } + let!(:v3) { FactoryGirl.create(:variant, product: p2 ) } + + before do + quick_login_as_admin visit '/admin/products/bulk_edit' + end - expect(page).to have_selector "a.delete-product", :count => 3 + it "shows a delete button for products, which deletes the appropriate product when clicked" do + expect(page).to have_selector "a.delete-product", :count => 2 within "tr#p_#{p1.id}" do first("a.delete-product").click end - expect(page).to have_selector "a.delete-product", :count => 2 + expect(page).to have_selector "a.delete-product", :count => 1 visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.delete-product", :count => 2 + expect(page).to have_selector "a.delete-product", :count => 1 end it "shows a delete button for variants, which deletes the appropriate variant when clicked" do - v1 = FactoryGirl.create(:variant) - v2 = FactoryGirl.create(:variant) - v3 = FactoryGirl.create(:variant) - login_to_admin_section - - visit '/admin/products/bulk_edit' expect(page).to have_selector "a.view-variants" all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_selector "a.delete-variant", :count => 3 - within "tr#v_#{v1.id}" do + within "tr#v_#{v3.id}" do first("a.delete-variant").click end @@ -601,15 +437,18 @@ feature %q{ end describe "using edit buttons" do - it "shows an edit button for products, which takes the user to the standard edit page for that product" do - p1 = FactoryGirl.create(:product) - p2 = FactoryGirl.create(:product) - p3 = FactoryGirl.create(:product) - login_to_admin_section + let!(:p1) { FactoryGirl.create(:product) } + let!(:p2) { FactoryGirl.create(:product) } + let!(:v1) { p1.variants.first } + let!(:v2) { p2.variants.first } + before do + quick_login_as_admin visit '/admin/products/bulk_edit' + end - expect(page).to have_selector "a.edit-product", :count => 3 + it "shows an edit button for products, which takes the user to the standard edit page for that product" do + expect(page).to have_selector "a.edit-product", :count => 2 within "tr#p_#{p1.id}" do first("a.edit-product").click @@ -619,16 +458,10 @@ feature %q{ end it "shows an edit button for variants, which takes the user to the standard edit page for that variant" do - v1 = FactoryGirl.create(:variant) - v2 = FactoryGirl.create(:variant) - v3 = FactoryGirl.create(:variant) - login_to_admin_section - - visit '/admin/products/bulk_edit' expect(page).to have_selector "a.view-variants" all("a.view-variants").each { |e| e.trigger('click') } - expect(page).to have_selector "a.edit-variant", :count => 3 + expect(page).to have_selector "a.edit-variant", :count => 2 within "tr#v_#{v1.id}" do first("a.edit-variant").click @@ -788,6 +621,8 @@ feature %q{ within 'fieldset#new_product' do fill_in 'product_name', with: 'Big Bag Of Apples' select supplier_permitted.name, from: 'product_supplier_id' + select 'Weight (g)', from: 'product_variant_unit_with_scale' + fill_in 'product_unit_value_with_description', with: '100' fill_in 'product_price', with: '10.00' select taxon.name, from: 'product_primary_taxon_id' end @@ -800,6 +635,7 @@ feature %q{ it "allows me to update a product" do p = product_supplied_permitted + v = p.variants.first visit '/admin/products/bulk_edit' first("div#columns_dropdown", :text => "COLUMNS").click @@ -809,30 +645,34 @@ feature %q{ expect(page).to have_field "product_name", with: p.name expect(page).to have_select "producer_id", selected: supplier_permitted.name expect(page).to have_field "available_on", with: p.available_on.strftime("%F %T") - expect(page).to have_field "price", with: "10.0" - expect(page).to have_field "on_hand", with: "6" fill_in "product_name", with: "Big Bag Of Potatoes" select supplier_managed2.name, :from => 'producer_id' fill_in "available_on", with: (3.days.ago.beginning_of_day).strftime("%F %T") - fill_in "price", with: "20" select "Weight (kg)", from: "variant_unit_with_scale" - fill_in "on_hand", with: "18" - fill_in "display_as", with: "Big Bag" + + find("a.view-variants").trigger('click') + end + + within "#v_#{v.id}" do + fill_in "variant_price", with: "20" + fill_in "variant_on_hand", with: "18" + fill_in "variant_display_as", with: "Big Bag" end click_button 'Save Changes' expect(page.find("#status-message")).to have_content "Changes saved." p.reload + v.reload expect(p.name).to eq "Big Bag Of Potatoes" expect(p.supplier).to eq supplier_managed2 expect(p.variant_unit).to eq "weight" expect(p.variant_unit_scale).to eq 1000 # Kg expect(p.available_on).to eq 3.days.ago.beginning_of_day - expect(p.master.display_as).to eq "Big Bag" - expect(p.price).to eq 20.0 - expect(p.on_hand).to eq 18 + expect(v.display_as).to eq "Big Bag" + expect(v.price).to eq 20.0 + expect(v.on_hand).to eq 18 end end end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index cb96799fbb..bbf9fb7b36 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -487,7 +487,9 @@ feature %q{ let!(:shipping_method) { create(:shipping_method, distributors: [distributor_managed, distributor_unmanaged, distributor_permitted]) } let!(:payment_method) { create(:payment_method, distributors: [distributor_managed, distributor_unmanaged, distributor_permitted]) } let!(:product_managed) { create(:product, supplier: supplier_managed) } + let!(:variant_managed) { product_managed.variants.first } let!(:product_permitted) { create(:product, supplier: supplier_permitted) } + let!(:variant_permitted) { product_permitted.variants.first } before do # Relationships required for interface to work @@ -533,8 +535,6 @@ feature %q{ click_link "Order Cycles" click_link 'New Order Cycle' - # We go straight through to the new form, because only one coordinator is available - fill_in 'order_cycle_name', with: 'My order cycle' fill_in 'order_cycle_orders_open_at', with: '2040-11-06 06:00:00' fill_in 'order_cycle_orders_close_at', with: '2040-11-13 17:00:00' @@ -544,8 +544,8 @@ feature %q{ select 'Permitted supplier', from: 'new_supplier_id' click_button 'Add supplier' - select_incoming_variant supplier_managed, 0, product_managed.master - select_incoming_variant supplier_permitted, 1, product_permitted.master + select_incoming_variant supplier_managed, 0, variant_managed + select_incoming_variant supplier_permitted, 1, variant_permitted click_button 'Add coordinator fee' select 'Managed distributor fee', from: 'order_cycle_coordinator_fee_0_id' @@ -750,7 +750,9 @@ feature %q{ let!(:p1) { create(:simple_product, supplier: enterprise) } let!(:p2) { create(:simple_product, supplier: enterprise) } let!(:p3) { create(:simple_product, supplier: enterprise) } - let!(:v) { create(:variant, product: p3) } + let!(:v1) { p1.variants.first } + let!(:v2) { p2.variants.first } + let!(:v3) { p3.variants.first } let!(:fee) { create(:enterprise_fee, enterprise: enterprise, name: 'Coord fee') } before do @@ -779,12 +781,12 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' # Then my products / variants should already be selected - page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{p1.master.id}" - page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{p2.master.id}" - page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{v.id}" + page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{v1.id}" + page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{v2.id}" + page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{v3.id}" # When I unselect a product - uncheck "order_cycle_incoming_exchange_0_variants_#{p2.master.id}" + uncheck "order_cycle_incoming_exchange_0_variants_#{v2.id}" # And I add a fee and save click_button 'Add coordinator fee' @@ -819,7 +821,7 @@ feature %q{ scenario "editing an order cycle" do # Given an order cycle with pickup time and instructions fee = create(:enterprise_fee, name: 'my fee', enterprise: enterprise) - oc = create(:simple_order_cycle, suppliers: [enterprise], coordinator: enterprise, distributors: [enterprise], variants: [p1.master], coordinator_fees: [fee]) + oc = create(:simple_order_cycle, suppliers: [enterprise], coordinator: enterprise, distributors: [enterprise], variants: [v1], coordinator_fees: [fee]) ex = oc.exchanges.outgoing.first ex.update_attributes! pickup_time: 'pickup time', pickup_instructions: 'pickup instructions' @@ -837,9 +839,9 @@ feature %q{ page.should have_field 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' # And I should see the products - page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{p1.master.id}" - page.should have_unchecked_field "order_cycle_incoming_exchange_0_variants_#{p2.master.id}" - page.should have_unchecked_field "order_cycle_incoming_exchange_0_variants_#{v.id}" + page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{v1.id}" + page.should have_unchecked_field "order_cycle_incoming_exchange_0_variants_#{v2.id}" + page.should have_unchecked_field "order_cycle_incoming_exchange_0_variants_#{v3.id}" # And I should see the coordinator fees page.should have_select 'order_cycle_coordinator_fee_0_id', selected: 'my fee' @@ -849,7 +851,7 @@ feature %q{ # Given an order cycle with pickup time and instructions fee1 = create(:enterprise_fee, name: 'my fee', enterprise: enterprise) fee2 = create(:enterprise_fee, name: 'that fee', enterprise: enterprise) - oc = create(:simple_order_cycle, suppliers: [enterprise], coordinator: enterprise, distributors: [enterprise], variants: [p1.master], coordinator_fees: [fee1]) + oc = create(:simple_order_cycle, suppliers: [enterprise], coordinator: enterprise, distributors: [enterprise], variants: [v1], coordinator_fees: [fee1]) ex = oc.exchanges.outgoing.first ex.update_attributes! pickup_time: 'pickup time', pickup_instructions: 'pickup instructions' @@ -866,10 +868,10 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy' # And I make some product selections - uncheck "order_cycle_incoming_exchange_0_variants_#{p1.master.id}" - check "order_cycle_incoming_exchange_0_variants_#{p2.master.id}" - check "order_cycle_incoming_exchange_0_variants_#{v.id}" - uncheck "order_cycle_incoming_exchange_0_variants_#{v.id}" + uncheck "order_cycle_incoming_exchange_0_variants_#{v1.id}" + check "order_cycle_incoming_exchange_0_variants_#{v2.id}" + check "order_cycle_incoming_exchange_0_variants_#{v3.id}" + uncheck "order_cycle_incoming_exchange_0_variants_#{v3.id}" # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' @@ -887,8 +889,8 @@ feature %q{ # And it should have a variant selected oc = OrderCycle.last - oc.exchanges.incoming.first.variants.should == [p2.master] - oc.exchanges.outgoing.first.variants.should == [p2.master] + oc.exchanges.incoming.first.variants.should == [v2] + oc.exchanges.outgoing.first.variants.should == [v2] # And it should have the fee oc.coordinator_fees.should == [fee2] diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index bb38e5d8ba..c2a302fcbb 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -11,7 +11,7 @@ feature %q{ @user = create(:user) @product = create(:simple_product) @distributor = create(:distributor_enterprise) - @order_cycle = create(:simple_order_cycle, distributors: [@distributor], variants: [@product.master]) + @order_cycle = create(:simple_order_cycle, distributors: [@distributor], variants: [@product.variants.first]) @order = create(:order_with_totals_and_distribution, user: @user, distributor: @distributor, order_cycle: @order_cycle, state: 'complete', payment_state: 'balance_due') diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 740de12045..269c77d801 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -117,9 +117,8 @@ feature %q{ end end - scenario "creating a new product" do + scenario "creating a new product", js: true do Spree::Config.products_require_tax_category = false - click_link 'Products' click_link 'New Product' @@ -128,6 +127,8 @@ feature %q{ page.should have_selector('#product_supplier_id') select 'Another Supplier', :from => 'product_supplier_id' + select 'Weight (g)', from: 'product_variant_unit_with_scale' + fill_in 'product_unit_value_with_description', with: '500' select taxon.name, from: "product_primary_taxon_id" select 'None', from: "product_tax_category_id" diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index f16b736c00..277f33593b 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -223,14 +223,16 @@ feature %q{ describe "products and inventory report" do it "shows products and inventory report" do - product_1 = create(:simple_product, name: "Product Name", variant_unit: nil) - variant_1 = create(:variant, product: product_1, price: 100.0) - variant_2 = create(:variant, product: product_1, price: 80.0) - product_2 = create(:simple_product, name: "Product 2", price: 99.0, variant_unit: nil) - variant_1.update_column(:count_on_hand, 10) - variant_2.update_column(:count_on_hand, 20) - product_2.master.update_column(:count_on_hand, 9) - variant_1.option_values = [create(:option_value, :presentation => "Test")] + product1 = create(:simple_product, name: "Product Name", price: 100) + variant1 = product1.variants.first + variant2 = create(:variant, product: product1, price: 80.0) + product2 = create(:simple_product, name: "Product 2", price: 99.0, variant_unit: 'weight', variant_unit_scale: 1, unit_value: '100') + variant3 = product2.variants.first + variant1.update_column(:count_on_hand, 10) + variant2.update_column(:count_on_hand, 20) + variant3.update_column(:count_on_hand, 9) + variant1.option_values = [create(:option_value, :presentation => "Test")] + variant2.option_values = [create(:option_value, :presentation => "Something")] login_to_admin_section click_link 'Reports' @@ -244,10 +246,10 @@ feature %q{ table = rows.map { |r| r.all("th,td").map { |c| c.text.strip } } table.sort.should == [ - ["Supplier", "Producer Suburb", "Product", "Product Properties", "Taxons", "Variant Value", "Price", "Group Buy Unit Quantity", "Amount"], - [product_1.supplier.name, product_1.supplier.address.city, "Product Name", product_1.properties.join(", "), product_1.primary_taxon.name, "Test", "100.0", product_1.group_buy_unit_size.to_s, ""], - [product_1.supplier.name, product_1.supplier.address.city, "Product Name", product_1.properties.join(", "), product_1.primary_taxon.name, "S", "80.0", product_1.group_buy_unit_size.to_s, ""], - [product_2.supplier.name, product_1.supplier.address.city, "Product 2", product_1.properties.join(", "), product_2.primary_taxon.name, "", "99.0", product_1.group_buy_unit_size.to_s, ""] + ["Supplier", "Producer Suburb", "Product", "Product Properties", "Taxons", "Variant Value", "Price", "Group Buy Unit Quantity", "Amount"], + [product1.supplier.name, product1.supplier.address.city, "Product Name", product1.properties.join(", "), product1.primary_taxon.name, "Test", "100.0", product1.group_buy_unit_size.to_s, ""], + [product1.supplier.name, product1.supplier.address.city, "Product Name", product1.properties.join(", "), product1.primary_taxon.name, "Something", "80.0", product1.group_buy_unit_size.to_s, ""], + [product2.supplier.name, product1.supplier.address.city, "Product 2", product1.properties.join(", "), product2.primary_taxon.name, "100g", "99.0", product1.group_buy_unit_size.to_s, ""] ].sort end end diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb index 19767d8ea2..c947872949 100644 --- a/spec/features/admin/variants_spec.rb +++ b/spec/features/admin/variants_spec.rb @@ -25,10 +25,11 @@ feature %q{ end - scenario "editing unit value and description for a variant" do + scenario "editing unit value and description for a variant", js:true do # Given a product with unit-related option types, with a variant p = create(:simple_product, variant_unit: "weight", variant_unit_scale: "1") - v = create(:variant, product: p, unit_value: 1, unit_description: 'foo') + v = p.variants.first + v.update_attributes( unit_value: 1, unit_description: 'foo' ) # And the product has option types for the unit-related and non-unit-related option values p.option_types << v.option_values.first.option_type @@ -39,7 +40,7 @@ feature %q{ page.find('table.index .icon-edit').click # Then I should not see a traditional option value field for the unit-related option value - page.all("div[data-hook='presentation'] input").count.should == 1 + expect(page).to_not have_selector "div[data-hook='presentation'] input" # And I should see unit value and description fields for the unit-related option value page.should have_field "variant_unit_value", with: "1" @@ -57,27 +58,9 @@ feature %q{ v.unit_description.should == 'bar' end - it "does not show unit value or description fields when the product does not have a unit-related option type" do - # Given a product without unit-related option types, with a variant - p = create(:simple_product, variant_unit: nil, variant_unit_scale: nil) - v = create(:variant, product: p, unit_value: nil, unit_description: nil) - - # And the product has option types for the variant's option values - p.option_types << v.option_values.first.option_type - - # When I view the variant - login_to_admin_section - visit spree.admin_product_variants_path p - page.find('table.index .icon-edit').click - - # Then I should not see unit value and description fields - page.should_not have_field "variant_unit_value" - page.should_not have_field "variant_unit_description" - end - it "soft-deletes variants", js: true do p = create(:simple_product) - v = create(:variant, product: p) + v = p.variants.first login_to_admin_section visit spree.admin_product_variants_path p diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index a47b73ba6c..e11664c457 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -13,9 +13,10 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:oc1) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) } let(:oc2) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), orders_close_at: 3.days.from_now) } let(:product) { create(:simple_product, supplier: supplier) } + let(:variant) { product.variants.first } let(:order) { create(:order, distributor: distributor) } - before do + before do set_order order end @@ -28,23 +29,23 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path page.should have_text distributor.name find("#tab_about a").click - first("distributor img")['src'].should == distributor.logo.url(:thumb) + first("distributor img")['src'].should == distributor.logo.url(:thumb) end it "shows the producers for a distributor" do - exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) - exchange.variants << product.master + exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.variants << variant visit shop_path find("#tab_producers a").click - page.should have_content supplier.name + page.should have_content supplier.name end describe "selecting an order cycle" do let(:exchange1) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } it "selects an order cycle if only one is open" do - exchange1.update_attribute :pickup_time, "turtles" + exchange1.update_attribute :pickup_time, "turtles" visit shop_path page.should have_selector "option[selected]", text: 'turtles' end @@ -52,8 +53,8 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "with multiple order cycles" do let(:exchange2) { Exchange.find(oc2.exchanges.to_enterprises(distributor).outgoing.first.id) } before do - exchange1.update_attribute :pickup_time, "frogs" - exchange2.update_attribute :pickup_time, "turtles" + exchange1.update_attribute :pickup_time, "frogs" + exchange2.update_attribute :pickup_time, "turtles" end it "shows a select with all order cycles, but doesn't show the products by default" do @@ -62,22 +63,22 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should have_selector "option", text: 'turtles' page.should_not have_selector("input.button.right", visible: true) end - + it "shows products after selecting an order cycle" do - product.master.update_attribute(:display_name, "kitten") - product.master.update_attribute(:display_as, "rabbit") - exchange1.variants << product.master ## add product to exchange + variant.update_attribute(:display_name, "kitten") + variant.update_attribute(:display_as, "rabbit") + exchange1.variants << variant ## add product to exchange visit shop_path - page.should_not have_content product.name + page.should_not have_content product.name Spree::Order.last.order_cycle.should == nil select "frogs", :from => "order_cycle_id" page.should have_selector "products" - page.should have_content "Next order closing in 2 days" + page.should have_content "Next order closing in 2 days" Spree::Order.last.order_cycle.should == oc1 - page.should have_content product.name - page.should have_content product.master.display_name - page.should have_content product.master.display_as + page.should have_content product.name + page.should have_content variant.display_name + page.should have_content variant.display_as open_product_modal product modal_should_be_open_for product @@ -88,21 +89,16 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "after selecting an order cycle with products visible" do let(:variant1) { create(:variant, product: product, price: 20) } let(:variant2) { create(:variant, product: product, price: 30) } - let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } + let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } before do - exchange.update_attribute :pickup_time, "frogs" - exchange.variants << product.master + exchange.update_attribute :pickup_time, "frogs" + exchange.variants << variant exchange.variants << variant1 exchange.variants << variant2 order.order_cycle = oc1 end - it "should not show quantity field for product with variants" do - visit shop_path - page.should_not have_selector("#variants_#{product.master.id}", visible: true) - end - it "uses the adjusted price" do enterprise_fee1 = create(:enterprise_fee, amount: 20) enterprise_fee2 = create(:enterprise_fee, amount: 3) @@ -126,6 +122,7 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "group buy products" do let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } let(:product) { create(:simple_product, group_buy: true, on_hand: 15) } + let(:variant) { product.variants.first } let(:product2) { create(:simple_product, group_buy: false) } describe "without variants" do @@ -134,22 +131,11 @@ feature "As a consumer I want to shop with a distributor", js: true do set_order_cycle(order, oc1) visit shop_path end - - it "should save group buy data to ze cart" do - fill_in "variants[#{product.master.id}]", with: 5 - fill_in "variant_attributes[#{product.master.id}][max_quantity]", with: 9 - wait_until { !cart_dirty } - - li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last - li.max_quantity.should == 9 - li.quantity.should == 5 - end - # TODO move to controller test pending "adding a product with a max quantity less than quantity results in max_quantity==quantity" do - fill_in "variants[#{product.master.id}]", with: 5 - fill_in "variant_attributes[#{product.master.id}][max_quantity]", with: 1 + fill_in "variants[#{variant.id}]", with: 5 + fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 1 add_to_cart page.should have_content product.name li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last @@ -165,7 +151,7 @@ feature "As a consumer I want to shop with a distributor", js: true do set_order_cycle(order, oc1) visit shop_path end - + it "should save group buy data to the cart" do fill_in "variants[#{variant.id}]", with: 6 fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7 @@ -213,4 +199,3 @@ feature "As a consumer I want to shop with a distributor", js: true do end end end - diff --git a/spec/lib/open_food_network/products_and_inventory_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_report_spec.rb index 2e23919fe6..3cde6e5f8e 100644 --- a/spec/lib/open_food_network/products_and_inventory_report_spec.rb +++ b/spec/lib/open_food_network/products_and_inventory_report_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' module OpenFoodNetwork describe ProductsAndInventoryReport do context "As a site admin" do - let(:user) do + let(:user) do user = create(:user) user.spree_roles << Spree::Role.find_or_create_by_name!("admin") user @@ -14,7 +14,7 @@ module OpenFoodNetwork it "Should return headers" do subject.header.should == [ - "Supplier", + "Supplier", "Producer Suburb", "Product", "Product Properties", @@ -63,7 +63,7 @@ module OpenFoodNetwork context "As an enterprise user" do let(:supplier) { create(:supplier_enterprise) } - let(:enterprise_user) do + let(:enterprise_user) do user = create(:user) user.enterprise_roles.create(enterprise: supplier) user.spree_roles = [] @@ -76,7 +76,7 @@ module OpenFoodNetwork describe "fetching child variants" do it "returns some variants" do product1 = create(:simple_product, supplier: supplier) - variant_1 = create(:variant, product: product1) + variant_1 = product1.variants.first variant_2 = create(:variant, product: product1) subject.child_variants.sort.should == [variant_1, variant_2].sort @@ -85,41 +85,34 @@ module OpenFoodNetwork it "should only return variants managed by the user" do product1 = create(:simple_product, supplier: create(:supplier_enterprise)) product2 = create(:simple_product, supplier: supplier) - variant_1 = create(:variant, product: product1) - variant_2 = create(:variant, product: product2) - + variant_1 = product1.variants.first + variant_2 = product2.variants.first + subject.child_variants.should == [variant_2] end end describe "fetching master variants" do - it "should only return variants managed by the user" do - product1 = create(:simple_product, supplier: create(:supplier_enterprise)) - product2 = create(:simple_product, supplier: supplier) - - subject.master_variants.should == [product2.master] - end - it "doesn't return master variants with siblings" do product = create(:simple_product, supplier: supplier) - create(:variant, product: product) - - subject.master_variants.should be_empty + + subject.master_variants.should be_empty end end describe "Filtering variants" do - let(:variants) { Spree::Variant.scoped.joins(:product) } + let(:variants) { Spree::Variant.scoped.joins(:product).where(is_master: false) } it "should return unfiltered variants sans-params" do product1 = create(:simple_product, supplier: supplier) product2 = create(:simple_product, supplier: supplier) - subject.filter(Spree::Variant.scoped).sort.should == [product1.master, product2.master].sort + + subject.filter(Spree::Variant.scoped).sort.should == [product1.master, product1.variants.first, product2.master, product2.variants.first].sort end it "should filter deleted products" do product1 = create(:simple_product, supplier: supplier) product2 = create(:simple_product, supplier: supplier) product2.delete - subject.filter(Spree::Variant.scoped).sort.should == [product1.master].sort + subject.filter(Spree::Variant.scoped).sort.should == [product1.master, product1.variants.first].sort end describe "based on report type" do it "returns only variants on hand" do @@ -127,7 +120,7 @@ module OpenFoodNetwork product2 = create(:simple_product, supplier: supplier, on_hand: 0) subject.stub(:params).and_return(report_type: 'inventory') - subject.filter(variants).should == [product1.master] + subject.filter(variants).should == [product1.variants.first] end end it "filters to a specific supplier" do @@ -136,7 +129,7 @@ module OpenFoodNetwork product2 = create(:simple_product, supplier: supplier2) subject.stub(:params).and_return(supplier_id: supplier.id) - subject.filter(variants).should == [product1.master] + subject.filter(variants).should == [product1.variants.first] end it "filters to a specific distributor" do distributor = create(:distributor_enterprise) @@ -144,23 +137,23 @@ module OpenFoodNetwork product2 = create(:simple_product, supplier: supplier, distributors: [distributor]) subject.stub(:params).and_return(distributor_id: distributor.id) - subject.filter(variants).should == [product2.master] + subject.filter(variants).should == [product2.variants.first] end it "filters to a specific order cycle" do distributor = create(:distributor_enterprise) product1 = create(:simple_product, supplier: supplier, distributors: [distributor]) product2 = create(:simple_product, supplier: supplier, distributors: [distributor]) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [product1.master]) + order_cycle = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [product1.variants.first]) subject.stub(:params).and_return(order_cycle_id: order_cycle.id) - subject.filter(variants).should == [product1.master] + subject.filter(variants).should == [product1.variants.first] end it "should do all the filters at once" do distributor = create(:distributor_enterprise) product1 = create(:simple_product, supplier: supplier, distributors: [distributor]) product2 = create(:simple_product, supplier: supplier, distributors: [distributor]) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [product1.master]) + order_cycle = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [product1.variants.first]) subject.stub(:params).and_return( order_cycle_id: order_cycle.id, diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index 97a4b988d8..47a64c06a9 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -693,45 +693,38 @@ describe Enterprise do end describe "finding variants distributed by the enterprise" do - it "finds the master variant" do + it "finds master and other variants" do d = create(:distributor_enterprise) p = create(:product, distributors: [d]) - d.distributed_variants.should == [p.master] - end - - it "finds other variants" do - d = create(:distributor_enterprise) - p = create(:product, distributors: [d]) - v = create(:variant, product: p) + v = p.variants.first d.distributed_variants.sort.should == [p.master, v].sort end - it "finds variants distributed by order cycle" do + pending "finds variants distributed by order cycle" do + # there isn't actually a method for this on Enterprise? d = create(:distributor_enterprise) p = create(:product) - oc = create(:simple_order_cycle, distributors: [d], variants: [p.master]) - d.distributed_variants.should == [p.master] + v = p.variants.first + oc = create(:simple_order_cycle, distributors: [d], variants: [v]) + + # This method doesn't do what this test says it does... + d.distributed_variants.sort.should == [v] end end describe "finding variants distributed by the enterprise in a product distribution only" do - it "finds the master variant" do + it "finds master and other variants" do d = create(:distributor_enterprise) p = create(:product, distributors: [d]) - d.product_distribution_variants.should == [p.master] - end - - it "finds other variants" do - d = create(:distributor_enterprise) - p = create(:product, distributors: [d]) - v = create(:variant, product: p) + v = p.variants.first d.product_distribution_variants.sort.should == [p.master, v].sort end it "does not find variants distributed by order cycle" do d = create(:distributor_enterprise) p = create(:product) - oc = create(:simple_order_cycle, distributors: [d], variants: [p.master]) + v = p.variants.first + oc = create(:simple_order_cycle, distributors: [d], variants: [v]) d.product_distribution_variants.should == [] end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 817d2e8e93..5f95a09a8f 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -239,10 +239,12 @@ describe OrderCycle do it "returns valid products but not invalid products" do p_valid = create(:product) p_invalid = create(:product) - v = create(:variant, product: p_invalid) + v_valid = p_valid.variants.first + v_invalid = p_invalid.variants.first d = create(:distributor_enterprise) - oc = create(:simple_order_cycle, distributors: [d], variants: [p_valid.master, p_invalid.master]) + oc = create(:simple_order_cycle, distributors: [d], variants: [v_valid, p_invalid.master]) + oc.valid_products_distributed_by(d).should == [p_valid] end @@ -313,7 +315,7 @@ describe OrderCycle do @oc.pickup_time_for(@d2).should == '2-8pm Friday' end end - + describe "finding pickup instructions for a distributor" do it "returns the pickup instructions" do @oc.pickup_instructions_for(@d1).should == "Come get it!" @@ -375,7 +377,7 @@ describe OrderCycle do occ.coordinator_fee_ids.should_not be_empty occ.coordinator_fee_ids.should == oc.coordinator_fee_ids - + # to_h gives us a unique hash for each exchange # check that the clone has no additional exchanges occ.exchanges.map(&:to_h).all? do |ex| @@ -402,7 +404,7 @@ describe OrderCycle do describe "finding order cycles opening in the future" do it "should give the soonest opening order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) OrderCycle.first_opening_for(distributor).should == oc end @@ -411,12 +413,12 @@ describe OrderCycle do OrderCycle.first_opening_for(distributor).should == nil end end - + describe "finding open order cycles" do it "should give the soonest closing order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) - oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) + oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) OrderCycle.first_closing_for(distributor).should == oc end end diff --git a/spec/requests/shop_spec.rb b/spec/requests/shop_spec.rb index c13cfc675f..cc8838220c 100644 --- a/spec/requests/shop_spec.rb +++ b/spec/requests/shop_spec.rb @@ -7,72 +7,57 @@ describe "Shop API" do let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) } - let(:p1) { create(:simple_product, on_demand: false) } - let(:p2) { create(:simple_product, on_demand: true) } - let(:p3) { create(:simple_product, on_demand: false) } let(:p4) { create(:simple_product, on_demand: false) } let(:p5) { create(:simple_product, on_demand: false) } let(:p6) { create(:simple_product, on_demand: false) } let(:p7) { create(:simple_product, on_demand: false) } - let(:v1) { create(:variant, product: p4, unit_value: 2) } - let(:v2) { create(:variant, product: p4, unit_value: 3, on_demand: false) } - let(:v3) { create(:variant, product: p4, unit_value: 4, on_demand: true) } - let(:v4) { create(:variant, product: p5) } - let(:v5) { create(:variant, product: p5) } - let(:v6) { create(:variant, product: p7) } + let(:v41) { p4.variants.first } + let(:v42) { create(:variant, product: p4, unit_value: 3, on_demand: false) } + let(:v43) { create(:variant, product: p4, unit_value: 4, on_demand: true) } + let(:v51) { p5.variants.first } + let(:v52) { create(:variant, product: p5) } + let(:v61) { p6.variants.first } + let(:v71) { p7.variants.first } let(:order) { create(:order, distributor: distributor, order_cycle: oc1) } before do set_order order - p1.master.update_attribute(:count_on_hand, 1) - p2.master.update_attribute(:count_on_hand, 0) - p3.master.update_attribute(:count_on_hand, 0) - p6.master.update_attribute(:count_on_hand, 1) + v61.update_attribute(:count_on_hand, 1) p6.delete - p7.master.update_attribute(:count_on_hand, 1) - v1.update_attribute(:count_on_hand, 1) - v2.update_attribute(:count_on_hand, 0) - v3.update_attribute(:count_on_hand, 0) - v4.update_attribute(:count_on_hand, 1) - v5.update_attribute(:count_on_hand, 0) - v6.update_attribute(:count_on_hand, 1) - v6.update_attribute(:deleted_at, Time.now) - exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) - exchange.update_attribute :pickup_time, "frogs" - exchange.variants << p1.master - exchange.variants << p2.master - exchange.variants << p3.master - exchange.variants << p6.master - exchange.variants << v1 - exchange.variants << v2 - exchange.variants << v3 - # v4 is in stock but not in distribution - # v5 is out of stock and in the distribution + v71.update_attribute(:count_on_hand, 1) + v41.update_attribute(:count_on_hand, 1) + v42.update_attribute(:count_on_hand, 0) + v43.update_attribute(:count_on_hand, 0) + v51.update_attribute(:count_on_hand, 1) + v52.update_attribute(:count_on_hand, 0) + v71.update_attribute(:count_on_hand, 1) + v71.update_attribute(:deleted_at, Time.now) + exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.update_attribute :pickup_time, "frogs" + exchange.variants << v61 + exchange.variants << v41 + exchange.variants << v42 + exchange.variants << v43 + # v51 is in stock but not in distribution + # v52 is out of stock and in the distribution # Neither should display, nor should their product, p5 - exchange.variants << v5 - exchange.variants << v6 + exchange.variants << v52 + exchange.variants << v71 get products_shop_path end it "filters products based on availability" do - # It shows on hand products - response.body.should include p1.name - response.body.should include p4.name - # It shows on demand products - response.body.should include p2.name - # It does not show products that are neither on hand or on demand - response.body.should_not include p3.name # It shows on demand variants - response.body.should include v3.options_text + response.body.should include v43.options_text # It does not show variants that are neither on hand or on demand - response.body.should_not include v2.options_text + response.body.should_not include v42.options_text # It does not show products that have no available variants in this distribution response.body.should_not include p5.name # It does not show deleted products response.body.should_not include p6.name # It does not show deleted variants - response.body.should_not include v6.name + response.body.should_not include v71.name response.body.should_not include p7.name end end From 63353ebace437410161d1456c3668537372bc525 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 17 Apr 2015 16:49:14 +1000 Subject: [PATCH 025/206] Don't try and delete the only variant, that will never work! --- spec/features/admin/variants_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb index c947872949..910f299386 100644 --- a/spec/features/admin/variants_spec.rb +++ b/spec/features/admin/variants_spec.rb @@ -60,12 +60,14 @@ feature %q{ it "soft-deletes variants", js: true do p = create(:simple_product) - v = p.variants.first + v = create(:variant, product: p) login_to_admin_section visit spree.admin_product_variants_path p - page.find('a.delete-resource').click + within "tr#spree_variant_#{v.id}" do + page.find('a.delete-resource').click + end page.should_not have_content v.options_text v.reload From 72d553ef0c12bf7cf02c4123b613e11b3dc52115 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 17 Apr 2015 16:51:02 +1000 Subject: [PATCH 026/206] Test actual deletion of variants --- spec/models/spree/product_spec.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 42c85e98e3..d6e814fed2 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -29,11 +29,7 @@ module Spree product.on_hand = "10,000" expect(product.save).to be_false - # It is expected that master variant :count_on_hand should cause the failure to save, and it does. However, this broke with the addition of - # the :ensure_standard_variant callback. Evidently, normal variants are checked and their errors added to the product model before the - # save_master callback on product is run. Therefore, the error that is raised here is one on a normal variant, rather than one on master. - # expect(product.errors[:count_on_hand]).to include "is not a number" - expect(product.errors[:"variants.count_on_hand"]).to include "is not a number" + expect(product.errors[:count_on_hand]).to include "is not a number" end it "defaults available_on to now" do @@ -64,9 +60,9 @@ module Spree it "does not allow the last variant to be deleted" do product = create(:simple_product) expect(product.variants(:reload).length).to eq 1 - product.variants = [] - expect(product.save).to be_false - expect(product.errors[:variants]).to include "Product must have at least one variant" + v = product.variants.last + v.delete + expect(v.errors[:product]).to include "must have at least one variant" end context "when the product has variants" do From 03fd148f418b285be35c145e9f3a25937535ce38 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 17 Apr 2015 16:58:49 +1000 Subject: [PATCH 027/206] showing profile modals on groups/hubs --- .../controllers/group_enterprises_controller.js.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee index db1a9a1d8b..ea652b8105 100644 --- a/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/group_enterprises_controller.js.coffee @@ -1,9 +1,10 @@ -Darkswarm.controller "GroupEnterprisesCtrl", ($scope, Search, FilterSelectorsService) -> +Darkswarm.controller "GroupEnterprisesCtrl", ($scope, Search, FilterSelectorsService, EnterpriseModal) -> $scope.totalActive = FilterSelectorsService.totalActive $scope.clearAll = FilterSelectorsService.clearAll $scope.filterText = FilterSelectorsService.filterText $scope.FilterSelectorsService = FilterSelectorsService $scope.query = Search.search() + $scope.openModal = EnterpriseModal.open $scope.activeTaxons = [] $scope.show_profiles = false $scope.filtersActive = false From 893b743973fceb8bf3f610597b30ea5df1d302ee Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 17 Apr 2015 17:00:18 +1000 Subject: [PATCH 028/206] tidy (rm comment) --- .../darkswarm/controllers/groups_controller.js.coffee | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee index 4bb856ace4..aafb7597d6 100644 --- a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee @@ -1,8 +1,3 @@ Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) -> $scope.Groups = Groups $scope.order = 'position' - - #$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) -> - #$anchorScroll() - # - # From ff2e6d9ca46806af706181c9087e77c603823826 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 17 Apr 2015 17:25:13 +1000 Subject: [PATCH 029/206] Test deletion rather than destruction on variant model spec --- app/models/spree/variant_decorator.rb | 2 ++ spec/models/spree/variant_spec.rb | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 20c4f20cf7..88c9b14e26 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -104,10 +104,12 @@ Spree::Variant.class_eval do def delete if product.variants == [self] # Only variant left on product errors.add :product, "must have at least one variant" + false else transaction do self.update_column(:deleted_at, Time.now) ExchangeVariant.where(variant_id: self).destroy_all + self end end end diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index 2c37065916..7910087dfd 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -401,15 +401,17 @@ module Spree end context "as the last variant of a product" do - let!(:product) { create(:simple_product) } - let!(:first_variant) { product.variants(:reload).first } - let!(:extra_variant) { create(:variant, product: product) } + let!(:extra_variant) { create(:variant) } + let!(:product) { extra_variant.product } + let!(:first_variant) { product.variants.first } + + before { product.reload } it "cannot be deleted" do - expect(product.variants(:reload).length).to eq 2 - expect(extra_variant.destroy).to eq extra_variant + expect(product.variants.length).to eq 2 + expect(extra_variant.delete).to eq extra_variant expect(product.variants(:reload).length).to eq 1 - expect(first_variant.destroy).to be_false + expect(first_variant.delete).to be_false expect(product.variants(:reload).length).to eq 1 expect(first_variant.errors[:product]).to include "must have at least one variant" end From 05c350b5ff482d8d3baf533179b8ee7dfc965694 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 24 Apr 2015 11:27:47 +1000 Subject: [PATCH 030/206] Refactoring unitsCtrl --- .../admin/products/units_controller.js.coffee | 12 ++-- .../products/units_controller_spec.js.coffee | 55 +++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 spec/javascripts/unit/admin/products/units_controller_spec.js.coffee diff --git a/app/assets/javascripts/admin/products/units_controller.js.coffee b/app/assets/javascripts/admin/products/units_controller.js.coffee index db005e490a..c9c5807af3 100644 --- a/app/assets/javascripts/admin/products/units_controller.js.coffee +++ b/app/assets/javascripts/admin/products/units_controller.js.coffee @@ -5,6 +5,13 @@ angular.module("admin.products") $scope.placeholder_text = "" $scope.$watchCollection '[product.variant_unit_with_scale, product.master.unit_value_with_description]', -> + $scope.processVariantUnitWithScale() + $scope.processUnitValueWithDescription() + $scope.placeholder_text = new OptionValueNamer($scope.product.master).name() + + $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() + + $scope.processVariantUnitWithScale = -> if $scope.product.variant_unit_with_scale match = $scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/) if match @@ -16,6 +23,7 @@ angular.module("admin.products") else $scope.product.variant_unit = $scope.product.variant_unit_scale = null + $scope.processUnitValueWithDescription = -> if $scope.product.master.hasOwnProperty("unit_value_with_description") match = $scope.product.master.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/) if match @@ -24,10 +32,6 @@ angular.module("admin.products") $scope.product.master.unit_value *= $scope.product.variant_unit_scale if $scope.product.master.unit_value && $scope.product.variant_unit_scale $scope.product.master.unit_description = match[3] - $scope.placeholder_text = new OptionValueNamer($scope.product.master).name() - - $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() - $scope.hasVariants = (product) -> Object.keys(product.variants).length > 0 diff --git a/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee b/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee new file mode 100644 index 0000000000..c5f104f07a --- /dev/null +++ b/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee @@ -0,0 +1,55 @@ +describe "unitsCtrl", -> + ctrl = null + scope = null + product = null + + beforeEach -> + module('admin.products') + inject ($rootScope, $controller, VariantUnitManager) -> + scope = $rootScope + ctrl = $controller 'unitsCtrl', {$scope: scope, VariantUnitManager: VariantUnitManager} + + describe "interpretting variant_unit_with_scale", -> + it "splits string with one underscore and stores the two parts", -> + scope.product.variant_unit_with_scale = "weight_1000" + scope.processVariantUnitWithScale() + expect(scope.product.variant_unit).toEqual "weight" + expect(scope.product.variant_unit_scale).toEqual 1000 + + it "interprets strings with no underscore as variant_unit", -> + scope.product.variant_unit_with_scale = "items" + scope.processVariantUnitWithScale() + expect(scope.product.variant_unit).toEqual "items" + expect(scope.product.variant_unit_scale).toEqual null + + it "sets variant_unit and variant_unit_scale to null", -> + scope.product.variant_unit_with_scale = null + scope.processVariantUnitWithScale() + expect(scope.product.variant_unit).toEqual null + expect(scope.product.variant_unit_scale).toEqual null + + describe "interpretting unit_value_with_description", -> + beforeEach -> + scope.product.master = {} + + describe "when a variant_unit_scale is present", -> + beforeEach -> + scope.product.variant_unit_scale = 1 + + it "splits by whitespace in to unit_value and unit_description", -> + scope.product.master.unit_value_with_description = "12 boxes" + scope.processUnitValueWithDescription() + expect(scope.product.master.unit_value).toEqual 12 + expect(scope.product.master.unit_description).toEqual "boxes" + + it "uses whole string as unit_value when only numerical characters are present", -> + scope.product.master.unit_value_with_description = "12345" + scope.processUnitValueWithDescription() + expect(scope.product.master.unit_value).toEqual 12345 + expect(scope.product.master.unit_description).toEqual '' + + it "uses whole string as description when string does not start with a number", -> + scope.product.master.unit_value_with_description = "boxes 12" + scope.processUnitValueWithDescription() + expect(scope.product.master.unit_value).toEqual null + expect(scope.product.master.unit_description).toEqual "boxes 12" From ed7b763ecf3152dac7ada2eb5d0d51a25425dc06 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 24 Apr 2015 12:15:35 +1000 Subject: [PATCH 031/206] UnitsCtrl can interpret unit_value_with_description without a separating space --- .../admin/products/units_controller.js.coffee | 2 +- .../admin/products/units_controller_spec.js.coffee | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/products/units_controller.js.coffee b/app/assets/javascripts/admin/products/units_controller.js.coffee index c9c5807af3..a84f048580 100644 --- a/app/assets/javascripts/admin/products/units_controller.js.coffee +++ b/app/assets/javascripts/admin/products/units_controller.js.coffee @@ -25,7 +25,7 @@ angular.module("admin.products") $scope.processUnitValueWithDescription = -> if $scope.product.master.hasOwnProperty("unit_value_with_description") - match = $scope.product.master.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/) + match = $scope.product.master.unit_value_with_description.match(/^([\d\.]+(?= *|$)|)( *)(.*)$/) if match $scope.product.master.unit_value = parseFloat(match[1]) $scope.product.master.unit_value = null if isNaN($scope.product.master.unit_value) diff --git a/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee b/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee index c5f104f07a..b846126711 100644 --- a/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/products/units_controller_spec.js.coffee @@ -53,3 +53,15 @@ describe "unitsCtrl", -> scope.processUnitValueWithDescription() expect(scope.product.master.unit_value).toEqual null expect(scope.product.master.unit_description).toEqual "boxes 12" + + it "does not require whitespace to split unit value and description", -> + scope.product.master.unit_value_with_description = "12boxes" + scope.processUnitValueWithDescription() + expect(scope.product.master.unit_value).toEqual 12 + expect(scope.product.master.unit_description).toEqual "boxes" + + it "once a whitespace occurs, all subsequent numerical characters are counted as description", -> + scope.product.master.unit_value_with_description = "123 54 boxes" + scope.processUnitValueWithDescription() + expect(scope.product.master.unit_value).toEqual 123 + expect(scope.product.master.unit_description).toEqual "54 boxes" From 162a5651401d2a8288123e4a807e3d576218017a Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 24 Apr 2015 16:14:24 +0100 Subject: [PATCH 032/206] Removing notes to myself from this file --- .../spree/api/line_items_controller_decorator.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/controllers/spree/api/line_items_controller_decorator.rb b/app/controllers/spree/api/line_items_controller_decorator.rb index f9a025873d..35fca864f4 100644 --- a/app/controllers/spree/api/line_items_controller_decorator.rb +++ b/app/controllers/spree/api/line_items_controller_decorator.rb @@ -6,12 +6,3 @@ Spree::Api::LineItemsController.class_eval do order.update_distribution_charge! end end - - -#when we update a line item the .update_distribution_charge! is called -# order.should_receive .update_distribution_charge! -# check fails when absent - -# in order model check that .update_distribution_charge! is properly tested. -# think through use cases - existing completed order -# currently likely just used to complete orders so add test case that works on a completed order From 60452835497ee52b36c1264e4b214837f8390ffb Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 24 Apr 2015 16:17:00 +0100 Subject: [PATCH 033/206] Populate the line item unit value, when line_item created and update old data in migration --- app/models/spree/order_decorator.rb | 1 + .../20150424151117_populate_line_item_unit_value.rb | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 db/migrate/20150424151117_populate_line_item_unit_value.rb diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 9d213bd633..a7158096dd 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -127,6 +127,7 @@ Spree::Order.class_eval do else current_item = Spree::LineItem.new(:quantity => quantity, max_quantity: max_quantity) current_item.variant = variant + current_item.unit_value = variant.unit_value if currency current_item.currency = currency unless currency.nil? current_item.price = variant.price_in(currency).amount diff --git a/db/migrate/20150424151117_populate_line_item_unit_value.rb b/db/migrate/20150424151117_populate_line_item_unit_value.rb new file mode 100644 index 0000000000..2122b84472 --- /dev/null +++ b/db/migrate/20150424151117_populate_line_item_unit_value.rb @@ -0,0 +1,9 @@ +class PopulateLineItemUnitValue < ActiveRecord::Migration + def up + execute "UPDATE spree_line_items SET unit_value = spree_variants.unit_value FROM spree_variants WHERE spree_line_items.variant_id = spree_variants.id" + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end From d344c3dec8da637abe28753a61509077e6884ed7 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 24 Apr 2015 16:22:17 +0100 Subject: [PATCH 034/206] Updating the spec based on @Robs suggestions, hoping for his insights. Still doesn't work. --- spec/controllers/spree/api/line_items_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/spree/api/line_items_controller_spec.rb b/spec/controllers/spree/api/line_items_controller_spec.rb index e3c9a262af..7d0b215ae9 100644 --- a/spec/controllers/spree/api/line_items_controller_spec.rb +++ b/spec/controllers/spree/api/line_items_controller_spec.rb @@ -20,9 +20,9 @@ module Spree make_simple_data! context "as a line item is updated" do - it "apply enterprise fees on the item" do + it "update distribution charge on the order" do line_item_params = { order_id: order.number, id: line_item.id, line_item: { id: line_item.id, unit_value: 520 }, format: :json} - controller.should_receive(:apply_enterprise_fees).and_return(true) + order.should_receive(:update_distribution_charge!).and_call_original spree_post :update, line_item_params end end From 6bbd3f7c13182f3eb1918539113c3b5c1f5699ce Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sun, 26 Apr 2015 11:02:06 +0100 Subject: [PATCH 035/206] Added auth for order_cycle_management_report. This report was breaking supplier enterprises reports due to incorrect authorization. --- 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 9bff908346..dbb0d4fe10 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -112,7 +112,7 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit], Spree::Classification # Reports page - can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory], :report + can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], :report end def add_order_management_abilities(user) From 9e61a7d083da2134a44cbf56e3a01ee00f677fd7 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sun, 26 Apr 2015 11:03:32 +0100 Subject: [PATCH 036/206] Adding report type drop down to order_cycle_management_report --- .../spree/admin/reports/order_cycle_management.html.haml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/spree/admin/reports/order_cycle_management.html.haml b/app/views/spree/admin/reports/order_cycle_management.html.haml index fbe365c742..418cba8b75 100644 --- a/app/views/spree/admin/reports/order_cycle_management.html.haml +++ b/app/views/spree/admin/reports/order_cycle_management.html.haml @@ -20,6 +20,10 @@ include_blank: true) %br %br + = label_tag nil, "Report Type: " + = select_tag(:report_type, options_for_select(@report_types, @report_type)) + %br + %br = check_box_tag :csv = label_tag :csv, "Download as csv" %br @@ -41,4 +45,3 @@ - if @report.table.empty? %tr %td{:colspan => "2"}= t(:none) - From a253b8852517d327c9b6fa00c64edb041b619c47 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 29 Apr 2015 10:42:38 +1000 Subject: [PATCH 037/206] Fixing line item controller spec --- spec/controllers/spree/api/line_items_controller_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/controllers/spree/api/line_items_controller_spec.rb b/spec/controllers/spree/api/line_items_controller_spec.rb index 7d0b215ae9..37ec50eb7e 100644 --- a/spec/controllers/spree/api/line_items_controller_spec.rb +++ b/spec/controllers/spree/api/line_items_controller_spec.rb @@ -22,7 +22,8 @@ module Spree context "as a line item is updated" do it "update distribution charge on the order" do line_item_params = { order_id: order.number, id: line_item.id, line_item: { id: line_item.id, unit_value: 520 }, format: :json} - order.should_receive(:update_distribution_charge!).and_call_original + allow(controller).to receive(:order) { order } + expect(order).to receive(:update_distribution_charge!) spree_post :update, line_item_params end end From 295da25dd23aef786b845eee8ecf26025bee2e2c Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 30 Apr 2015 17:17:28 +1000 Subject: [PATCH 038/206] insert clone after cloned product --- .../javascripts/admin/services/bulk_products.js.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/services/bulk_products.js.coffee b/app/assets/javascripts/admin/services/bulk_products.js.coffee index d898356846..6085d48fcc 100644 --- a/app/assets/javascripts/admin/services/bulk_products.js.coffee +++ b/app/assets/javascripts/admin/services/bulk_products.js.coffee @@ -19,7 +19,7 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher) # when a respond_overrride for the clone action is used. id = data.product.id dataFetcher("/api/products/" + id + "?template=bulk_show").then (newProduct) => - @addProducts [newProduct] + @insertProductAfter(product, newProduct) updateVariantLists: (serverProducts, productsWithUnsavedVariants) -> for product in productsWithUnsavedVariants @@ -39,6 +39,10 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher) @unpackProduct product @products.push product + insertProductAfter: (product, newProduct) -> + index = @products.indexOf(product) + @products.splice(index + 1, 0, newProduct) + unpackProduct: (product) -> #$scope.matchProducer product @loadVariantUnit product From 66f847f673cd47c82238b094f7620a7c4ab1e983 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 30 Apr 2015 17:22:54 +1000 Subject: [PATCH 039/206] showing save button at the bottom as well --- app/views/spree/admin/products/bulk_edit.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index f230d1c331..383e080177 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -8,3 +8,4 @@ = render 'spree/admin/products/bulk_edit/actions' = render 'spree/admin/products/bulk_edit/indicators' = render 'spree/admin/products/bulk_edit/products' + = render 'spree/admin/products/bulk_edit/actions' From baabb5c07fa11d94a9ddbff464bdcf929152e823 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 1 May 2015 11:30:26 +1000 Subject: [PATCH 040/206] fixing BPE feature spec --- .../spree/admin/products/bulk_edit.html.haml | 2 +- .../bulk_edit/_save_button_row.html.haml | 3 ++ .../admin/bulk_product_update_spec.rb | 28 +++++++++---------- 3 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 383e080177..22b0a195da 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -8,4 +8,4 @@ = render 'spree/admin/products/bulk_edit/actions' = render 'spree/admin/products/bulk_edit/indicators' = render 'spree/admin/products/bulk_edit/products' - = render 'spree/admin/products/bulk_edit/actions' + = render 'spree/admin/products/bulk_edit/save_button_row' diff --git a/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml b/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml new file mode 100644 index 0000000000..a5f18986c0 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml @@ -0,0 +1,3 @@ +%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" } + %div.four.columns.alpha + %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index ac101da5d9..089f3188f7 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -266,7 +266,7 @@ feature %q{ fill_in "variant_display_as", with: "Case" fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." updated_variant = Spree::Variant.where(deleted_at: nil).last @@ -325,7 +325,7 @@ feature %q{ fill_in "product_sku", with: "NEW SKU" end - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -354,7 +354,7 @@ feature %q{ select "Items", from: "variant_unit_with_scale" fill_in "variant_unit_name", with: "loaf" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -377,7 +377,7 @@ feature %q{ first("a.view-variants").trigger('click') fill_in "variant_unit_value_with_description", with: '123 abc' - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -402,7 +402,7 @@ feature %q{ select "Weight (kg)", from: "variant_unit_with_scale" fill_in "master_unit_value_with_description", with: '123 abc' - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -450,7 +450,7 @@ feature %q{ expect(page).to have_selector "span[name='on_hand']", text: "10" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -474,7 +474,7 @@ feature %q{ fill_in "variant_price", with: "10.0" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -491,21 +491,21 @@ feature %q{ fill_in "product_name", with: "new name 1" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 1" fill_in "product_name", with: "new name 2" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 2" fill_in "product_name", with: "original name" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "original name" @@ -521,7 +521,7 @@ feature %q{ fill_in "product_name", :with => "new product name" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new product name" @@ -534,7 +534,7 @@ feature %q{ visit '/admin/products/bulk_edit' - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "No changes to save." end end @@ -553,7 +553,7 @@ feature %q{ expect(page).to have_no_field "product_name", with: p2.name fill_in "product_name", :with => "new product1" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p1.reload expect(p1.name).to eq "new product1" @@ -829,7 +829,7 @@ feature %q{ fill_in "display_as", with: "Big Bag" end - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload From 993183f2f57b79c8d3d981e60a4d47fde759df60 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 1 May 2015 15:04:12 +1000 Subject: [PATCH 041/206] updating js spec: cloning product calls insertProductAfter now --- .../unit/admin/services/bulk_products_spec.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee index 70c22c80e0..0b3c0841a6 100644 --- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee @@ -61,7 +61,7 @@ describe "BulkProducts service", -> clonedProduct = id: 17 - spyOn(BulkProducts, "addProducts") + spyOn(BulkProducts, "insertProductAfter") BulkProducts.products = [originalProduct] $httpBackend.expectGET("/admin/products/oranges/clone.json").respond 200, product: clonedProduct From 89b153dc2c8fcd899fe1606b0c8f09cb06a4c4ce Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 29 Apr 2015 13:43:11 +1000 Subject: [PATCH 042/206] Update AJAX request to use sells instead of deprecated is_distributor attribute --- .../javascripts/admin/bulk_order_management.js.coffee | 2 +- spec/javascripts/unit/bulk_order_management_spec.js.coffee | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 4c1a319c1a..03d0890408 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -44,7 +44,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) -> $scope.suppliers = data $scope.suppliers.unshift blankOption() - dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_distributor_eq]=true").then (data) -> + dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) -> $scope.distributors = data $scope.distributors.unshift blankOption() ocFetcher = dataFetcher("/api/order_cycles/accessible").then (data) -> diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 35b9e13d61..61164870ed 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -22,7 +22,7 @@ describe "AdminOrderMgmtCtrl", -> returnedOrderCycles = [ "oc1", "oc2", "oc3" ] httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").respond returnedSuppliers - httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_distributor_eq]=true").respond returnedDistributors + httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").respond returnedDistributors httpBackend.expectGET("/api/order_cycles/accessible").respond returnedOrderCycles spyOn(scope, "initialiseVariables").andCallThrough() spyOn(scope, "fetchOrders").andReturn "nothing" @@ -33,8 +33,8 @@ describe "AdminOrderMgmtCtrl", -> httpBackend.flush() expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] - expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] - expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] + expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] + expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] expect(scope.initialiseVariables.calls.length).toBe 1 expect(scope.fetchOrders.calls.length).toBe 1 From d8c23d37ac02c3cf6f4ecf0dbbeef3fffcc54e24 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 29 Apr 2015 13:44:00 +1000 Subject: [PATCH 043/206] Update accessible_by scope on enterprise, to read from permissions --- app/models/enterprise.rb | 6 ++---- spec/models/enterprise_spec.rb | 11 +++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 1aa76b88a8..3cf00730a8 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -162,14 +162,12 @@ class Enterprise < ActiveRecord::Base end } - # Return enterprises that participate in order cycles that user coordinates, sends to or receives from + # Return enterprises that the user manages and those that have granted P-OC to managed enterprises scope :accessible_by, lambda { |user| if user.has_spree_role?('admin') scoped else - with_order_cycles_outer. - where('order_cycles.id IN (?)', OrderCycle.accessible_by(user)). - select('DISTINCT enterprises.*') + where(id: OpenFoodNetwork::Permissions.new(user).order_cycle_enterprises) end } diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index 69ba68569f..df5dbbfbc7 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -560,20 +560,19 @@ describe Enterprise do end describe "accessible_by" do - it "shows only enterprises that are invloved in order cycles which are common to those managed by the given user" do + it "shows only managed enterprises and enterprises granting them P-OC" do user = create(:user) user.spree_roles = [] e1 = create(:enterprise) e2 = create(:enterprise) e3 = create(:enterprise) - e4 = create(:enterprise) e1.enterprise_roles.build(user: user).save - oc = create(:simple_order_cycle, coordinator: e2, suppliers: [e1], distributors: [e3]) + create(:enterprise_relationship, parent: e2, child: e1, permissions_list: [:add_to_order_cycle]) enterprises = Enterprise.accessible_by user - enterprises.length.should == 3 - enterprises.should include e1, e2, e3 - enterprises.should_not include e4 + enterprises.length.should == 2 + enterprises.should include e1, e2 + enterprises.should_not include e3 end it "shows all enterprises for admin user" do From 9ab16d8cec54129265bc46237cba8781cc57cc9d Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 30 Apr 2015 10:09:51 +1000 Subject: [PATCH 044/206] Allowing calls to Api::OrderCyclesController#accessible to specify :as => 'distributor' or 'producer' --- .../admin/bulk_order_management.js.coffee | 2 +- .../api/order_cycles_controller.rb | 11 +- app/models/order_cycle.rb | 19 +- .../api/order_cycles_controller_spec.rb | 257 ++++++++++++------ .../unit/bulk_order_management_spec.js.coffee | 2 +- 5 files changed, 209 insertions(+), 82 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 03d0890408..5d36718e9a 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -47,7 +47,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) -> $scope.distributors = data $scope.distributors.unshift blankOption() - ocFetcher = dataFetcher("/api/order_cycles/accessible").then (data) -> + ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) -> $scope.orderCycles = data $scope.orderCycles.unshift blankOption() $scope.fetchOrders() diff --git a/app/controllers/api/order_cycles_controller.rb b/app/controllers/api/order_cycles_controller.rb index b4b3486778..23b333625e 100644 --- a/app/controllers/api/order_cycles_controller.rb +++ b/app/controllers/api/order_cycles_controller.rb @@ -9,7 +9,16 @@ module Api end def accessible - @order_cycles = OrderCycle.ransack(params[:q]).result.accessible_by(current_api_user) + @order_cycles = if params[:as] == "distributor" + OrderCycle.ransack(params[:q]).result. + involving_managed_distributors_of(current_api_user).order('updated_at DESC') + elsif params[:as] == "producer" + OrderCycle.ransack(params[:q]).result. + involving_managed_producers_of(current_api_user).order('updated_at DESC') + else + OrderCycle.ransack(params[:q]).result.accessible_by(current_api_user) + end + render params[:template] || :bulk_index end end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index adcd596a8c..d16f1d7b8b 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -26,7 +26,7 @@ class OrderCycle < ActiveRecord::Base closed. where("order_cycles.orders_close_at >= ?", 31.days.ago). order("order_cycles.orders_close_at DESC") } - + scope :soonest_opening, lambda { upcoming.order('order_cycles.orders_open_at ASC') } scope :distributing_product, lambda { |product| @@ -64,6 +64,23 @@ class OrderCycle < ActiveRecord::Base joins('LEFT OUTER JOIN enterprises ON (enterprises.id = exchanges.sender_id OR enterprises.id = exchanges.receiver_id)') } + scope :involving_managed_distributors_of, lambda { |user| + enterprises = Enterprise.managed_by(user) + + # Order cycles where I managed an enterprise at either end of an outgoing exchange + # ie. coordinator or distibutor + joins(:exchanges).merge(Exchange.outgoing). + where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises) + } + + scope :involving_managed_producers_of, lambda { |user| + enterprises = Enterprise.managed_by(user) + + # Order cycles where I managed an enterprise at either end of an incoming exchange + # ie. coordinator or producer + joins(:exchanges).merge(Exchange.incoming). + where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises) + } def self.first_opening_for(distributor) with_distributor(distributor).soonest_opening.first diff --git a/spec/controllers/api/order_cycles_controller_spec.rb b/spec/controllers/api/order_cycles_controller_spec.rb index a9cceac796..3bb9a76602 100644 --- a/spec/controllers/api/order_cycles_controller_spec.rb +++ b/spec/controllers/api/order_cycles_controller_spec.rb @@ -4,109 +4,210 @@ require 'spree/api/testing_support/helpers' module Api describe OrderCyclesController do include Spree::Api::TestingSupport::Helpers + include AuthenticationWorkflow render_views - let!(:oc1) { FactoryGirl.create(:simple_order_cycle) } - let!(:oc2) { FactoryGirl.create(:simple_order_cycle) } - let(:coordinator) { oc1.coordinator } - let(:attributes) { [:id, :name, :suppliers, :distributors] } + describe "managed" do + let!(:oc1) { FactoryGirl.create(:simple_order_cycle) } + let!(:oc2) { FactoryGirl.create(:simple_order_cycle) } + let(:coordinator) { oc1.coordinator } + let(:attributes) { [:id, :name, :suppliers, :distributors] } - before do - stub_authentication! - Spree.user_class.stub :find_by_spree_api_key => current_api_user - end + before do + stub_authentication! + Spree.user_class.stub :find_by_spree_api_key => current_api_user + end - context "as a normal user" do - sign_in_as_user! + context "as a normal user" do + sign_in_as_user! - it "should deny me access to managed order cycles" do - spree_get :managed, { :format => :json } - assert_unauthorized! + it "should deny me access to managed order cycles" do + spree_get :managed, { :format => :json } + assert_unauthorized! + end + end + + context "as an enterprise user" do + sign_in_as_enterprise_user! [:coordinator] + + it "retrieves a list of variants with appropriate attributes" do + get :managed, { :format => :json } + keys = json_response.first.keys.map{ |key| key.to_sym } + attributes.all?{ |attr| keys.include? attr }.should == true + end + end + + context "as an administrator" do + sign_in_as_admin! + + it "retrieves a list of variants with appropriate attributes" do + get :managed, { :format => :json } + keys = json_response.first.keys.map{ |key| key.to_sym } + attributes.all?{ |attr| keys.include? attr }.should == true + end end end - context "as an enterprise user" do - sign_in_as_enterprise_user! [:coordinator] + describe "accessible" do + context "without :as parameter" do + let(:oc_supplier) { create(:supplier_enterprise) } + let(:oc_distributor) { create(:distributor_enterprise) } + let(:other_supplier) { create(:supplier_enterprise) } + let(:oc_supplier_user) do + user = create(:user) + user.spree_roles = [] + user.enterprise_roles.create(enterprise: oc_supplier) + user.save! + user + end + let(:oc_distributor_user) do + user = create(:user) + user.spree_roles = [] + user.enterprise_roles.create(enterprise: oc_distributor) + user.save! + user + end + let(:other_supplier_user) do + user = create(:user) + user.spree_roles = [] + user.enterprise_roles.create(enterprise: other_supplier) + user.save! + user + end + let!(:order_cycle) { create(:simple_order_cycle, suppliers: [oc_supplier], distributors: [oc_distributor]) } - it "retrieves a list of variants with appropriate attributes" do - get :managed, { :format => :json } - keys = json_response.first.keys.map{ |key| key.to_sym } - attributes.all?{ |attr| keys.include? attr }.should == true - end - end + context "as the user of a supplier to an order cycle" do + before :each do + stub_authentication! + Spree.user_class.stub :find_by_spree_api_key => oc_supplier_user + spree_get :accessible, { :template => 'bulk_index', :format => :json } + end - context "as an administrator" do - sign_in_as_admin! + it "gives me access" do + json_response.length.should == 1 + json_response[0]['id'].should == order_cycle.id + end + end - it "retrieves a list of variants with appropriate attributes" do - get :managed, { :format => :json } - keys = json_response.first.keys.map{ |key| key.to_sym } - attributes.all?{ |attr| keys.include? attr }.should == true - end - end + context "as the user of some other supplier" do + before :each do + stub_authentication! + Spree.user_class.stub :find_by_spree_api_key => other_supplier_user + spree_get :accessible, { :template => 'bulk_index', :format => :json } + end - context "using the accessible action to list order cycles" do - let(:oc_supplier) { create(:supplier_enterprise) } - let(:oc_distributor) { create(:distributor_enterprise) } - let(:other_supplier) { create(:supplier_enterprise) } - let(:oc_supplier_user) do - user = create(:user) - user.spree_roles = [] - user.enterprise_roles.create(enterprise: oc_supplier) - user.save! - user - end - let(:oc_distributor_user) do - user = create(:user) - user.spree_roles = [] - user.enterprise_roles.create(enterprise: oc_distributor) - user.save! - user - end - let(:other_supplier_user) do - user = create(:user) - user.spree_roles = [] - user.enterprise_roles.create(enterprise: other_supplier) - user.save! - user - end - let!(:order_cycle) { create(:simple_order_cycle, suppliers: [oc_supplier], distributors: [oc_distributor]) } + it "does not give me access" do + json_response.length.should == 0 + end + end - context "as the user of a supplier to an order cycle" do - before :each do + context "as the user of a hub for the order cycle" do + before :each do + stub_authentication! + Spree.user_class.stub :find_by_spree_api_key => oc_distributor_user + spree_get :accessible, { :template => 'bulk_index', :format => :json } + end + + it "gives me access" do + json_response.length.should == 1 + json_response[0]['id'].should == order_cycle.id + end + end + end + + context "when the :as parameter is set to 'distributor'" do + let(:user) { create_enterprise_user } + let(:distributor) { create(:distributor_enterprise) } + let(:producer) { create(:supplier_enterprise) } + let(:coordinator) { create(:distributor_enterprise) } + let!(:oc) { create(:simple_order_cycle, coordinator: coordinator, distributors: [distributor], suppliers: [producer]) } + + let(:params) { { format: :json, as: 'distributor' } } + + before do stub_authentication! - Spree.user_class.stub :find_by_spree_api_key => oc_supplier_user - spree_get :accessible, { :template => 'bulk_index', :format => :json } + Spree.user_class.stub :find_by_spree_api_key => user end - it "gives me access" do - json_response.length.should == 1 - json_response[0]['id'].should == order_cycle.id + context "as the manager of a supplier in an order cycle" do + before do + user.enterprise_roles.create(enterprise: producer) + spree_get :accessible, params + end + + it "does not return the order cycle" do + expect(assigns(:order_cycles)).to_not include oc + end + end + + context "as the manager of a distributor in an order cycle" do + before do + user.enterprise_roles.create(enterprise: distributor) + spree_get :accessible, params + end + + it "returns the order cycle" do + expect(assigns(:order_cycles)).to include oc + end + end + + context "as the manager of the coordinator of an order cycle" do + before do + user.enterprise_roles.create(enterprise: coordinator) + spree_get :accessible, params + end + + it "returns the order cycle" do + expect(assigns(:order_cycles)).to include oc + end end end - context "as the user of some other supplier" do - before :each do + context "when the :as parameter is set to 'producer'" do + let(:user) { create_enterprise_user } + let(:distributor) { create(:distributor_enterprise) } + let(:producer) { create(:supplier_enterprise) } + let(:coordinator) { create(:distributor_enterprise) } + let!(:oc) { create(:simple_order_cycle, coordinator: coordinator, distributors: [distributor], suppliers: [producer]) } + + let(:params) { { format: :json, as: 'producer' } } + + before do stub_authentication! - Spree.user_class.stub :find_by_spree_api_key => other_supplier_user - spree_get :accessible, { :template => 'bulk_index', :format => :json } + Spree.user_class.stub :find_by_spree_api_key => user end - it "does not give me access" do - json_response.length.should == 0 - end - end + context "as the manager of a producer in an order cycle" do + before do + user.enterprise_roles.create(enterprise: producer) + spree_get :accessible, params + end - context "as the user of a hub for the order cycle" do - before :each do - stub_authentication! - Spree.user_class.stub :find_by_spree_api_key => oc_distributor_user - spree_get :accessible, { :template => 'bulk_index', :format => :json } + it "returns the order cycle" do + expect(assigns(:order_cycles)).to include oc + end end - it "gives me access" do - json_response.length.should == 1 - json_response[0]['id'].should == order_cycle.id + context "as the manager of a distributor in an order cycle" do + before do + user.enterprise_roles.create(enterprise: distributor) + spree_get :accessible, params + end + + it "does not return the order cycle" do + expect(assigns(:order_cycles)).to_not include oc + end + end + + context "as the manager of the coordinator of an order cycle" do + before do + user.enterprise_roles.create(enterprise: coordinator) + spree_get :accessible, params + end + + it "returns the order cycle" do + expect(assigns(:order_cycles)).to include oc + end end end end diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 61164870ed..e92f65ed5b 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -23,7 +23,7 @@ describe "AdminOrderMgmtCtrl", -> httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").respond returnedSuppliers httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").respond returnedDistributors - httpBackend.expectGET("/api/order_cycles/accessible").respond returnedOrderCycles + httpBackend.expectGET("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=SomeDate").respond returnedOrderCycles spyOn(scope, "initialiseVariables").andCallThrough() spyOn(scope, "fetchOrders").andReturn "nothing" #spyOn(returnedSuppliers, "unshift") From e640376d630d4d85c240b77f4d725ca5e26d6d15 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 30 Apr 2015 10:52:34 +1000 Subject: [PATCH 045/206] Don't load cancelled orders into bulk order management --- app/assets/javascripts/admin/bulk_order_management.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 5d36718e9a..61f275c9ed 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -60,7 +60,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.fetchOrders = -> $scope.loading = true - dataFetcher("/api/orders/managed?template=bulk_index;page=1;per_page=500;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) -> + dataFetcher("/api/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) -> $scope.resetOrders data $scope.loading = false From b5c7607d67bc5ccb34d5072ddd8870d989b9a254 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 30 Apr 2015 15:15:05 +1000 Subject: [PATCH 046/206] Order cycle filter resets date filters on BOM --- .../javascripts/admin/bulk_order_management.js.coffee | 7 +++++++ app/views/api/order_cycles/bulk_show.v1.rabl | 4 +++- spec/javascripts/unit/bulk_order_management_spec.js.coffee | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 61f275c9ed..420f7c3275 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -49,6 +49,8 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.distributors.unshift blankOption() ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) -> $scope.orderCycles = data + $scope.orderCyclesByID = [] + $scope.orderCyclesByID[oc.id] = oc for oc in $scope.orderCycles $scope.orderCycles.unshift blankOption() $scope.fetchOrders() ocFetcher.then -> @@ -162,6 +164,11 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.supplierFilter = $scope.suppliers[0].id $scope.orderCycleFilter = $scope.orderCycles[0].id $scope.quickSearch = "" + + $scope.$watch "orderCycleFilter", (newVal, oldVal) -> + unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal) + $scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order + $scope.endDate = $scope.orderCyclesByID[$scope.orderCycleFilter].last_order ] daysFromToday = (days) -> diff --git a/app/views/api/order_cycles/bulk_show.v1.rabl b/app/views/api/order_cycles/bulk_show.v1.rabl index a0a5b16927..b012b879e2 100644 --- a/app/views/api/order_cycles/bulk_show.v1.rabl +++ b/app/views/api/order_cycles/bulk_show.v1.rabl @@ -1,9 +1,11 @@ object @order_cycle attributes :id, :name +node( :first_order ) { |order| order.orders_open_at.strftime("%F") } +node( :last_order ) { |order| (order.orders_close_at + 1.day).strftime("%F") } node( :suppliers ) do |oc| partial 'api/enterprises/bulk_index', :object => oc.suppliers end node( :distributors ) do |oc| partial 'api/enterprises/bulk_index', :object => oc.distributors -end \ No newline at end of file +end diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index e92f65ed5b..3377c799db 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -43,7 +43,7 @@ describe "AdminOrderMgmtCtrl", -> describe "fetching orders", -> beforeEach -> scope.initialiseVariables() - httpBackend.expectGET("/api/orders/managed?template=bulk_index;page=1;per_page=500;q[completed_at_not_null]=true;q[completed_at_gt]=SomeDate;q[completed_at_lt]=SomeDate").respond "list of orders" + httpBackend.expectGET("/api/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=SomeDate;q[completed_at_lt]=SomeDate").respond "list of orders" it "makes a call to dataFetcher, with current start and end date parameters", -> scope.fetchOrders() From ed9bbe2c45aeb03f022bbf9c4bbaafb504f6bca7 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 30 Apr 2015 15:26:54 +1000 Subject: [PATCH 047/206] Sorting Hub and Producer filter selectors by name --- .../javascripts/admin/bulk_order_management.js.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 420f7c3275..20ddc177ba 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -1,6 +1,6 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ - "$scope", "$http", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey" - ($scope, $http, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey) -> + "$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey" + ($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey) -> $scope.loading = true $scope.initialiseVariables = -> @@ -42,10 +42,10 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ if $scope.spree_api_key_ok $http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) -> - $scope.suppliers = data + $scope.suppliers = $filter('orderBy')(data, 'name') $scope.suppliers.unshift blankOption() dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) -> - $scope.distributors = data + $scope.distributors = $filter('orderBy')(data, 'name') $scope.distributors.unshift blankOption() ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) -> $scope.orderCycles = data From 28bf7037db72bf0fb790fe1965e443ce1263bf4b Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 30 Apr 2015 16:20:15 +1000 Subject: [PATCH 048/206] Updating methods for retrieving allowed producers, distributors and order cycles for order and fulfillment reports --- .../spree/admin/reports_controller_decorator.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 92653a5aa9..ce714b088e 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -420,13 +420,17 @@ Spree::Admin::ReportsController.class_eval do my_distributors = Enterprise.is_distributor.managed_by(spree_current_user) my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user) + permissions = OpenFoodNetwork::Permissions.new(spree_current_user) + # My distributors and any distributors distributing products I supply - @distributors = my_distributors | Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers)) + @distributors = permissions.order_cycle_enterprises.is_distributor # My suppliers and any suppliers supplying products I distribute - @suppliers = my_suppliers | my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq + @suppliers = permissions.order_cycle_enterprises.is_primary_producer + + @order_cycles = OrderCycle.active_or_complete. + involving_managed_distributors_of(spree_current_user).order('orders_close_at DESC') - @order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC') @report_types = REPORT_TYPES[:orders_and_fulfillment] @report_type = params[:report_type] From 0a03483e3675eea49a1fbb1f8beff15f96079f40 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 1 May 2015 12:46:20 +1000 Subject: [PATCH 049/206] Adding permissions methods for visible and editable orders and line_items --- lib/open_food_network/permissions.rb | 50 +++++++ .../lib/open_food_network/permissions_spec.rb | 131 ++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 4edb3d5f39..59fff81a87 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -56,6 +56,51 @@ module OpenFoodNetwork permissions end + # Find enterprises that an admin is allowed to add to an order cycle + def visible_orders + # Any orders that I can edit + editable = editable_orders.pluck(:id) + + # Any orders placed through hubs that my producers have granted P-OC, and which contain my their products + # This is pretty complicated but it's looking for order where at least one of my producers has granted + # P-OC to the distributor AND the order contains products of at least one of THE SAME producers + granted_distributors = granted(:add_to_order_cycle, by: managed_enterprises.is_primary_producer) + produced = Spree::Order.with_line_items_variants_and_products_outer. + where( + "spree_orders.distributor_id IN (?) AND spree_products.supplier_id IN (?)", + granted_distributors, + granting(:add_to_order_cycle, to: granted_distributors).merge(managed_enterprises.is_primary_producer) + ).pluck(:id) + + Spree::Order.where(id: editable | produced) + end + + # Find enterprises that an admin is allowed to add to an order cycle + def editable_orders + # Any orders placed through any hub that I manage + managed = Spree::Order.where(distributor_id: managed_enterprises.pluck(:id)).pluck(:id) + + # Any order that is placed through an order cycle one of my managed enterprises coordinates + coordinated = Spree::Order.where(order_cycle_id: coordinated_order_cycles.pluck(:id)).pluck(:id) + + Spree::Order.where(id: managed | coordinated ) + end + + def visible_line_items + # Any line items that I can edit + editable = editable_line_items.pluck(:id) + + # Any from visible orders, where the product is produced by one of my managed producers + produced = Spree::LineItem.where(order_id: visible_orders.pluck(:id)).joins(:product). + where('spree_products.supplier_id IN (?)', managed_enterprises.is_primary_producer.pluck(:id)) + + Spree::LineItem.where(id: editable | produced) + end + + def editable_line_items + Spree::LineItem.where(order_id: editable_orders) + end + def managed_products managed_enterprise_products_ids = managed_enterprise_products.pluck :id permitted_enterprise_products_ids = related_enterprise_products.pluck :id @@ -85,6 +130,11 @@ module OpenFoodNetwork @managed_enterprises = Enterprise.managed_by(@user) end + def coordinated_order_cycles + return @coordinated_order_cycles unless @coordinated_order_cycles.nil? + @coordinated_order_cycles = OrderCycle.managed_by(@user) + end + def related_enterprises_with(permission) parent_ids = EnterpriseRelationship. permitting(managed_enterprises). diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 88611b857c..d9f4608a60 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -185,5 +185,136 @@ module OpenFoodNetwork permissions.send(:related_enterprise_products).should == [p] end end + + describe "finding orders that are visible in reports" do + let(:distributor) { create(:distributor_enterprise) } + let(:coordinator) { create(:distributor_enterprise) } + let(:random_enterprise) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator, distributors: [distributor]) } + let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor ) } + let!(:line_item) { create(:line_item, order: order) } + let!(:producer) { create(:supplier_enterprise) } + + before do + permissions.stub(:coordinated_order_cycles) { Enterprise.where("1=0") } + end + + context "as the hub through which the order was placed" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: distributor) } + end + + it "should let me see the order" do + expect(permissions.visible_orders).to include order + end + end + + context "as the coordinator of the order cycle through which the order was placed" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: coordinator) } + permissions.stub(:coordinated_order_cycles) { OrderCycle.where(id: order_cycle) } + end + + it "should let me see the order" do + expect(permissions.visible_orders).to include order + end + end + + context "as a producer which has granted P-OC to the distributor of an order" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: producer) } + create(:enterprise_relationship, parent: producer, child: distributor, permissions_list: [:add_to_order_cycle]) + end + + context "which contains my products" do + before do + line_item.product.supplier = producer + line_item.product.save + end + + it "should let me see the order" do + expect(permissions.visible_orders).to include order + end + end + + context "which does not contain my products" do + it "should not let me see the order" do + expect(permissions.visible_orders).to_not include order + end + end + end + + context "as an enterprise that is a distributor in the order cycle, but not the distributor of the order" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: random_enterprise) } + end + + it "should not let me see the order" do + expect(permissions.visible_orders).to_not include order + end + end + end + + describe "finding line items that are visible in reports" do + let(:distributor) { create(:distributor_enterprise) } + let(:coordinator) { create(:distributor_enterprise) } + let(:random_enterprise) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator, distributors: [distributor]) } + let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor ) } + let!(:line_item1) { create(:line_item, order: order) } + let!(:line_item2) { create(:line_item, order: order) } + let!(:producer) { create(:supplier_enterprise) } + + before do + permissions.stub(:coordinated_order_cycles) { Enterprise.where("1=0") } + end + + context "as the hub through which the parent order was placed" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: distributor) } + end + + it "should let me see the line_items" do + expect(permissions.visible_line_items).to include line_item1, line_item2 + end + end + + context "as the coordinator of the order cycle through which the parent order was placed" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: coordinator) } + permissions.stub(:coordinated_order_cycles) { OrderCycle.where(id: order_cycle) } + end + + it "should let me see the line_items" do + expect(permissions.visible_line_items).to include line_item1, line_item2 + end + end + + context "as the manager producer which has granted P-OC to the distributor of the parent order" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: producer) } + create(:enterprise_relationship, parent: producer, child: distributor, permissions_list: [:add_to_order_cycle]) + + line_item1.product.supplier = producer + line_item1.product.save + end + + it "should let me see the line_items pertaining to variants I produce" do + ps = permissions.visible_line_items + expect(ps).to include line_item1 + expect(ps).to_not include line_item2 + end + end + + context "as an enterprise that is a distributor in the order cycle, but not the distributor of the parent order" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where(id: random_enterprise) } + end + + it "should not let me see the line_items" do + expect(permissions.visible_line_items).to_not include line_item1, line_item2 + end + end + end end end From 4259b466f5a737b58999bb1ddaf0f861c5495177 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sat, 2 May 2015 17:41:18 +1000 Subject: [PATCH 050/206] Using new order and line item permissions to fetch items to display in Orders and Fullfillment reports --- .../admin/reports_controller_decorator.rb | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index ce714b088e..beaee2c9be 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -406,22 +406,16 @@ Spree::Admin::ReportsController.class_eval do end params[:q][:meta_sort] ||= "completed_at.desc" - # -- Search - @search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q]) - orders = @search.result - @line_items = orders.map do |o| - lis = o.line_items.managed_by(spree_current_user) - lis = lis.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present? - lis - end.flatten - #payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments - - # -- Prepare form options - my_distributors = Enterprise.is_distributor.managed_by(spree_current_user) - my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user) - permissions = OpenFoodNetwork::Permissions.new(spree_current_user) + # -- Search + + @search = Spree::Order.complete.not_state(:canceled).search(params[:q]) + orders = permissions.visible_orders.merge(@search.result) + + @line_items = permissions.visible_line_items.merge(Spree::LineItem.where(order_id: orders)) + @line_items = @line_items.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present? + # My distributors and any distributors distributing products I supply @distributors = permissions.order_cycle_enterprises.is_distributor From f79fba52beab4aa818bc01a5ae7959392b1e1df1 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sat, 2 May 2015 17:43:21 +1000 Subject: [PATCH 051/206] Hiding personal details of customers, where the user does not manage the distributor of the order or the coordinator of the order cycle --- .../spree/admin/reports_controller_decorator.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index beaee2c9be..ccc142f195 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -416,6 +416,15 @@ Spree::Admin::ReportsController.class_eval do @line_items = permissions.visible_line_items.merge(Spree::LineItem.where(order_id: orders)) @line_items = @line_items.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present? + line_items_with_hidden_details = @line_items.where("id NOT IN (?)", permissions.editable_line_items) + @line_items.select{ |li| line_items_with_hidden_details.include? li }.each do |line_item| + # TODO We should really be hiding customer code here too, but until we + # have an actual association between order and customer, it's a bit tricky + line_item.order.bill_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil) + line_item.order.ship_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil) + line_item.order.assign_attributes(email: "HIDDEN") + end + # My distributors and any distributors distributing products I supply @distributors = permissions.order_cycle_enterprises.is_distributor From 7ffe0f042ee7e925d3a81214c54e54b7138395e0 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 11:48:39 +1000 Subject: [PATCH 052/206] Moving accessible_by scope on Enterprise to permissions --- app/controllers/api/enterprises_controller.rb | 3 +- app/models/enterprise.rb | 9 ------ lib/open_food_network/permissions.rb | 13 +++++++++ .../lib/open_food_network/permissions_spec.rb | 12 ++++++-- spec/models/enterprise_spec.rb | 28 ------------------- 5 files changed, 24 insertions(+), 41 deletions(-) diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index 468f3a22ad..973492b5e7 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -13,7 +13,8 @@ module Api end def accessible - @enterprises = Enterprise.ransack(params[:q]).result.accessible_by(current_api_user) + permitted = Permissions.new(current_api_user).enterprises_managed_or_granting_add_to_order_cycle + @enterprises = permitted.ransack(params[:q]).result render params[:template] || :bulk_index end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 3cf00730a8..e921167cf0 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -162,15 +162,6 @@ class Enterprise < ActiveRecord::Base end } - # Return enterprises that the user manages and those that have granted P-OC to managed enterprises - scope :accessible_by, lambda { |user| - if user.has_spree_role?('admin') - scoped - else - where(id: OpenFoodNetwork::Permissions.new(user).order_cycle_enterprises) - end - } - def self.find_near(suburb) enterprises = [] diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 59fff81a87..6b6ef85405 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -15,6 +15,15 @@ module OpenFoodNetwork managed_and_related_enterprises_with :add_to_order_cycle end + def enterprises_managed_or_granting_add_to_order_cycle + # Return enterprises that the user manages and those that have granted P-OC to managed enterprises + if admin? + Enterprise.scoped + else + managed_and_related_enterprises_with :add_to_order_cycle + end + end + # Find enterprises for which an admin is allowed to edit their profile def editable_enterprises managed_and_related_enterprises_with :edit_profile @@ -118,6 +127,10 @@ module OpenFoodNetwork private + def admin? + @user.admin? + end + def managed_and_related_enterprises_with(permission) managed_enterprise_ids = managed_enterprises.pluck :id permitting_enterprise_ids = related_enterprises_with(permission).pluck :id diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index d9f4608a60..e3262949ee 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -12,12 +12,18 @@ module OpenFoodNetwork let(:e) { double(:enterprise) } it "returns managed and related enterprises with add_to_order_cycle permission" do - permissions. - should_receive(:managed_and_related_enterprises_with). + allow(user).to receive(:admin?) { false } + expect(permissions).to receive(:managed_and_related_enterprises_with). with(:add_to_order_cycle). and_return([e]) - permissions.order_cycle_enterprises.should == [e] + expect(permissions.enterprises_managed_or_granting_add_to_order_cycle).to eq [e] + end + + it "shows all enterprises for admin user" do + allow(user).to receive(:admin?) { true } + + expect(permissions.enterprises_managed_or_granting_add_to_order_cycle).to eq [e1, e2] end end diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index df5dbbfbc7..df1b58aca6 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -558,34 +558,6 @@ describe Enterprise do enterprises.should include e2 end end - - describe "accessible_by" do - it "shows only managed enterprises and enterprises granting them P-OC" do - user = create(:user) - user.spree_roles = [] - e1 = create(:enterprise) - e2 = create(:enterprise) - e3 = create(:enterprise) - e1.enterprise_roles.build(user: user).save - create(:enterprise_relationship, parent: e2, child: e1, permissions_list: [:add_to_order_cycle]) - - enterprises = Enterprise.accessible_by user - enterprises.length.should == 2 - enterprises.should include e1, e2 - enterprises.should_not include e3 - end - - it "shows all enterprises for admin user" do - user = create(:admin_user) - e1 = create(:enterprise) - e2 = create(:enterprise) - - enterprises = Enterprise.managed_by user - enterprises.length.should == 2 - enterprises.should include e1 - enterprises.should include e2 - end - end end describe "callbacks" do From f0f7e0ee2f164a725d88928e4e9ec9d656d11842 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 12:03:52 +1000 Subject: [PATCH 053/206] Making permissions method managed_and_related_enterprise_with method more specific --- lib/open_food_network/permissions.rb | 16 ++++++++-------- spec/lib/open_food_network/permissions_spec.rb | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 6b6ef85405..953314c8f8 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -5,14 +5,14 @@ module OpenFoodNetwork end def can_manage_complex_order_cycles? - managed_and_related_enterprises_with(:add_to_order_cycle).any? do |e| + managed_and_related_enterprises_granting(:add_to_order_cycle).any? do |e| e.sells == 'any' end end # Find enterprises that an admin is allowed to add to an order cycle def order_cycle_enterprises - managed_and_related_enterprises_with :add_to_order_cycle + managed_and_related_enterprises_granting :add_to_order_cycle end def enterprises_managed_or_granting_add_to_order_cycle @@ -20,17 +20,17 @@ module OpenFoodNetwork if admin? Enterprise.scoped else - managed_and_related_enterprises_with :add_to_order_cycle + managed_and_related_enterprises_granting :add_to_order_cycle end end # Find enterprises for which an admin is allowed to edit their profile def editable_enterprises - managed_and_related_enterprises_with :edit_profile + managed_and_related_enterprises_granting :edit_profile end def variant_override_hubs - managed_and_related_enterprises_with(:add_to_order_cycle).is_hub + managed_and_related_enterprises_granting(:add_to_order_cycle).is_hub end def variant_override_producers @@ -42,7 +42,7 @@ module OpenFoodNetwork # override variants # {hub1_id => [producer1_id, producer2_id, ...], ...} def variant_override_enterprises_per_hub - hubs = managed_and_related_enterprises_with(:add_to_order_cycle).is_distributor + hubs = managed_and_related_enterprises_granting(:add_to_order_cycle).is_distributor # Permissions granted by create_variant_overrides relationship from producer to hub permissions = Hash[ @@ -117,7 +117,7 @@ module OpenFoodNetwork end def managed_product_enterprises - managed_and_related_enterprises_with :manage_products + managed_and_related_enterprises_granting :manage_products end def manages_one_enterprise? @@ -131,7 +131,7 @@ module OpenFoodNetwork @user.admin? end - def managed_and_related_enterprises_with(permission) + def managed_and_related_enterprises_granting(permission) managed_enterprise_ids = managed_enterprises.pluck :id permitting_enterprise_ids = related_enterprises_with(permission).pluck :id diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index e3262949ee..6c89dc3db1 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -13,7 +13,7 @@ module OpenFoodNetwork it "returns managed and related enterprises with add_to_order_cycle permission" do allow(user).to receive(:admin?) { false } - expect(permissions).to receive(:managed_and_related_enterprises_with). + expect(permissions).to receive(:managed_and_related_enterprises_granting). with(:add_to_order_cycle). and_return([e]) @@ -32,7 +32,7 @@ module OpenFoodNetwork it "returns managed and related enterprises with edit_profile permission" do permissions. - should_receive(:managed_and_related_enterprises_with). + should_receive(:managed_and_related_enterprises_granting). with(:edit_profile). and_return([e]) @@ -139,7 +139,7 @@ module OpenFoodNetwork it "returns managed and related enterprises with manage_products permission" do permissions. - should_receive(:managed_and_related_enterprises_with). + should_receive(:managed_and_related_enterprises_granting). with(:manage_products). and_return([e]) @@ -171,13 +171,13 @@ module OpenFoodNetwork it "returns managed enterprises" do permissions.should_receive(:managed_enterprises) { Enterprise.where(id: e1) } - permissions.send(:managed_and_related_enterprises_with, permission).should == [e1] + permissions.send(:managed_and_related_enterprises_granting, permission).should == [e1] end it "returns permitted enterprises" do permissions.should_receive(:related_enterprises_with).with(permission). and_return(Enterprise.where(id: e2)) - permissions.send(:managed_and_related_enterprises_with, permission).should == [e2] + permissions.send(:managed_and_related_enterprises_granting, permission).should == [e2] end end From 5cd528a87d8ce669ca5ed33052bddcc732b89ed7 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 12:09:54 +1000 Subject: [PATCH 054/206] Removing obsolete related_enterprises_with permission method --- lib/open_food_network/permissions.rb | 13 ++----------- spec/lib/open_food_network/permissions_spec.rb | 10 +++++----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 953314c8f8..3c6526ff30 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -133,7 +133,7 @@ module OpenFoodNetwork def managed_and_related_enterprises_granting(permission) managed_enterprise_ids = managed_enterprises.pluck :id - permitting_enterprise_ids = related_enterprises_with(permission).pluck :id + permitting_enterprise_ids = granting(permission).pluck :id Enterprise.where('id IN (?)', managed_enterprise_ids + permitting_enterprise_ids) end @@ -148,15 +148,6 @@ module OpenFoodNetwork @coordinated_order_cycles = OrderCycle.managed_by(@user) end - def related_enterprises_with(permission) - parent_ids = EnterpriseRelationship. - permitting(managed_enterprises). - with_permission(permission). - pluck(:parent_id) - - Enterprise.where('id IN (?)', parent_ids) - end - def granting(permission, options={}) parent_ids = EnterpriseRelationship. permitting(options[:to] || managed_enterprises). @@ -180,7 +171,7 @@ module OpenFoodNetwork end def related_enterprise_products - Spree::Product.where('supplier_id IN (?)', related_enterprises_with(:manage_products)) + Spree::Product.where('supplier_id IN (?)', granting(:manage_products)) end end end diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 6c89dc3db1..bc26b2da31 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -154,19 +154,19 @@ module OpenFoodNetwork it "returns the enterprises" do permissions.stub(:managed_enterprises) { e2 } - permissions.send(:related_enterprises_with, permission).should == [e1] + permissions.send(:granting, permission).should == [e1] end it "returns an empty array when there are none" do permissions.stub(:managed_enterprises) { e1 } - permissions.send(:related_enterprises_with, permission).should == [] + permissions.send(:granting, permission).should == [] end end describe "finding enterprises that are managed or with a particular permission" do before do permissions.stub(:managed_enterprises) { Enterprise.where('1=0') } - permissions.stub(:related_enterprises_with) { Enterprise.where('1=0') } + permissions.stub(:granting) { Enterprise.where('1=0') } end it "returns managed enterprises" do @@ -175,7 +175,7 @@ module OpenFoodNetwork end it "returns permitted enterprises" do - permissions.should_receive(:related_enterprises_with).with(permission). + permissions.should_receive(:granting).with(permission). and_return(Enterprise.where(id: e2)) permissions.send(:managed_and_related_enterprises_granting, permission).should == [e2] end @@ -186,7 +186,7 @@ module OpenFoodNetwork let!(:p) { create(:simple_product, supplier: e) } it "returns supplied products" do - permissions.should_receive(:related_enterprises_with).with(:manage_products) { [e] } + permissions.should_receive(:granting).with(:manage_products) { [e] } permissions.send(:related_enterprise_products).should == [p] end From 5806f50a849e8c6958d855a62095d5cae93aa238 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 12:14:23 +1000 Subject: [PATCH 055/206] Renaming granting > related_enterprises_granting --- .../order_cycle_permissions.rb | 18 +++++++++--------- lib/open_food_network/permissions.rb | 8 ++++---- spec/lib/open_food_network/permissions_spec.rb | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/open_food_network/order_cycle_permissions.rb b/lib/open_food_network/order_cycle_permissions.rb index 2af60c5696..14ac9a68be 100644 --- a/lib/open_food_network/order_cycle_permissions.rb +++ b/lib/open_food_network/order_cycle_permissions.rb @@ -19,7 +19,7 @@ module OpenFoodNetwork if @coordinator.sells == "any" # If the coordinator sells any, relationships come into play - granting(:add_to_order_cycle, to: [@coordinator]).pluck(:id).each do |enterprise_id| + related_enterprises_granting(:add_to_order_cycle, to: [@coordinator]).pluck(:id).each do |enterprise_id| coordinator_permitted << enterprise_id end @@ -30,19 +30,19 @@ module OpenFoodNetwork Enterprise.where(id: coordinator_permitted | all_active) else # Any enterprises that I manage directly, which have granted P-OC to the coordinator - managed_permitted = granting(:add_to_order_cycle, to: [@coordinator], scope: managed_participating_enterprises ).pluck(:id) + managed_permitted = related_enterprises_granting(:add_to_order_cycle, to: [@coordinator], scope: managed_participating_enterprises ).pluck(:id) # Any hubs in this OC that have been granted P-OC by producers I manage in this OC hubs_permitted = granted(:add_to_order_cycle, by: managed_participating_producers, scope: @order_cycle.distributors).pluck(:id) # Any hubs in this OC that have granted P-OC to producers I manage in this OC - hubs_permitting = granting(:add_to_order_cycle, to: managed_participating_producers, scope: @order_cycle.distributors).pluck(:id) + hubs_permitting = related_enterprises_granting(:add_to_order_cycle, to: managed_participating_producers, scope: @order_cycle.distributors).pluck(:id) # Any producers in this OC that have been granted P-OC by hubs I manage in this OC producers_permitted = granted(:add_to_order_cycle, by: managed_participating_hubs, scope: @order_cycle.suppliers).pluck(:id) # Any producers in this OC that have granted P-OC to hubs I manage in this OC - producers_permitting = granting(:add_to_order_cycle, to: managed_participating_hubs, scope: @order_cycle.suppliers).pluck(:id) + producers_permitting = related_enterprises_granting(:add_to_order_cycle, to: managed_participating_hubs, scope: @order_cycle.suppliers).pluck(:id) managed_active = [] hubs_active = [] @@ -125,7 +125,7 @@ module OpenFoodNetwork end # Any variants of any producers that have granted the hub P-OC - producers = granting(:add_to_order_cycle, to: [hub], scope: Enterprise.is_primary_producer) + producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: Enterprise.is_primary_producer) permitted_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id IN (?)', producers) # PLUS any variants that are already in an outgoing exchange of this hub, so things don't break @@ -138,7 +138,7 @@ module OpenFoodNetwork Spree::Variant.where(id: coordinator_variants | permitted_variants | active_variants) else # Any variants produced by MY PRODUCERS that are in this order cycle, where my producer has granted P-OC to the hub - producers = granting(:add_to_order_cycle, to: [hub], scope: managed_participating_producers) + producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: managed_participating_producers) permitted_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id IN (?)', producers) # PLUS any of my incoming producers' variants that are already in an outgoing exchange of this hub, so things don't break @@ -162,7 +162,7 @@ module OpenFoodNetwork end # Any variants of any producers that have granted the hub P-OC - producers = granting(:add_to_order_cycle, to: [hub], scope: Enterprise.is_primary_producer) + producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: Enterprise.is_primary_producer) permitted_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id IN (?)', producers) # PLUS any variants that are already in an outgoing exchange of this hub, so things don't break @@ -178,7 +178,7 @@ module OpenFoodNetwork granted_producers = granted(:add_to_order_cycle, by: [hub], scope: managed_participating_producers) # Any variants produced by MY PRODUCERS that are in this order cycle, where my producer has granted P-OC to the hub - granting_producers = granting(:add_to_order_cycle, to: [hub], scope: granted_producers) + granting_producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: granted_producers) permitted_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id IN (?)', granting_producers) Spree::Variant.where(id: permitted_variants) @@ -216,7 +216,7 @@ module OpenFoodNetwork # Find my managed hubs in this order cycle hubs = managed_participating_hubs # Any incoming exchange where the producer has granted P-OC to one or more of those hubs - producers = granting(:add_to_order_cycle, to: hubs, scope: Enterprise.is_primary_producer).pluck :id + producers = related_enterprises_granting(:add_to_order_cycle, to: hubs, scope: Enterprise.is_primary_producer).pluck :id permitted_exchanges = @order_cycle.exchanges.incoming.where(sender_id: producers).pluck :id # TODO: remove active_exchanges when we think it is safe to do so diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 3c6526ff30..296ab9843c 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -78,7 +78,7 @@ module OpenFoodNetwork where( "spree_orders.distributor_id IN (?) AND spree_products.supplier_id IN (?)", granted_distributors, - granting(:add_to_order_cycle, to: granted_distributors).merge(managed_enterprises.is_primary_producer) + related_enterprises_granting(:add_to_order_cycle, to: granted_distributors).merge(managed_enterprises.is_primary_producer) ).pluck(:id) Spree::Order.where(id: editable | produced) @@ -133,7 +133,7 @@ module OpenFoodNetwork def managed_and_related_enterprises_granting(permission) managed_enterprise_ids = managed_enterprises.pluck :id - permitting_enterprise_ids = granting(permission).pluck :id + permitting_enterprise_ids = related_enterprises_granting(permission).pluck :id Enterprise.where('id IN (?)', managed_enterprise_ids + permitting_enterprise_ids) end @@ -148,7 +148,7 @@ module OpenFoodNetwork @coordinated_order_cycles = OrderCycle.managed_by(@user) end - def granting(permission, options={}) + def related_enterprises_granting(permission, options={}) parent_ids = EnterpriseRelationship. permitting(options[:to] || managed_enterprises). with_permission(permission). @@ -171,7 +171,7 @@ module OpenFoodNetwork end def related_enterprise_products - Spree::Product.where('supplier_id IN (?)', granting(:manage_products)) + Spree::Product.where('supplier_id IN (?)', related_enterprises_granting(:manage_products)) end end end diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index bc26b2da31..3d796b9f46 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -154,19 +154,19 @@ module OpenFoodNetwork it "returns the enterprises" do permissions.stub(:managed_enterprises) { e2 } - permissions.send(:granting, permission).should == [e1] + permissions.send(:related_enterprises_granting, permission).should == [e1] end it "returns an empty array when there are none" do permissions.stub(:managed_enterprises) { e1 } - permissions.send(:granting, permission).should == [] + permissions.send(:related_enterprises_granting, permission).should == [] end end describe "finding enterprises that are managed or with a particular permission" do before do permissions.stub(:managed_enterprises) { Enterprise.where('1=0') } - permissions.stub(:granting) { Enterprise.where('1=0') } + permissions.stub(:related_enterprises_granting) { Enterprise.where('1=0') } end it "returns managed enterprises" do @@ -175,7 +175,7 @@ module OpenFoodNetwork end it "returns permitted enterprises" do - permissions.should_receive(:granting).with(permission). + permissions.should_receive(:related_enterprises_granting).with(permission). and_return(Enterprise.where(id: e2)) permissions.send(:managed_and_related_enterprises_granting, permission).should == [e2] end @@ -186,7 +186,7 @@ module OpenFoodNetwork let!(:p) { create(:simple_product, supplier: e) } it "returns supplied products" do - permissions.should_receive(:granting).with(:manage_products) { [e] } + permissions.should_receive(:related_enterprises_granting).with(:manage_products) { [e] } permissions.send(:related_enterprise_products).should == [p] end From d8f5669fbbf7e7bfb38c1fa1ac7696b0c0488b98 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 12:18:15 +1000 Subject: [PATCH 056/206] Renaming granted > related_enterprises_granted --- lib/open_food_network/order_cycle_permissions.rb | 8 ++++---- lib/open_food_network/permissions.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/open_food_network/order_cycle_permissions.rb b/lib/open_food_network/order_cycle_permissions.rb index 14ac9a68be..08fe7b2ee4 100644 --- a/lib/open_food_network/order_cycle_permissions.rb +++ b/lib/open_food_network/order_cycle_permissions.rb @@ -33,13 +33,13 @@ module OpenFoodNetwork managed_permitted = related_enterprises_granting(:add_to_order_cycle, to: [@coordinator], scope: managed_participating_enterprises ).pluck(:id) # Any hubs in this OC that have been granted P-OC by producers I manage in this OC - hubs_permitted = granted(:add_to_order_cycle, by: managed_participating_producers, scope: @order_cycle.distributors).pluck(:id) + hubs_permitted = related_enterprises_granted(:add_to_order_cycle, by: managed_participating_producers, scope: @order_cycle.distributors).pluck(:id) # Any hubs in this OC that have granted P-OC to producers I manage in this OC hubs_permitting = related_enterprises_granting(:add_to_order_cycle, to: managed_participating_producers, scope: @order_cycle.distributors).pluck(:id) # Any producers in this OC that have been granted P-OC by hubs I manage in this OC - producers_permitted = granted(:add_to_order_cycle, by: managed_participating_hubs, scope: @order_cycle.suppliers).pluck(:id) + producers_permitted = related_enterprises_granted(:add_to_order_cycle, by: managed_participating_hubs, scope: @order_cycle.suppliers).pluck(:id) # Any producers in this OC that have granted P-OC to hubs I manage in this OC producers_permitting = related_enterprises_granting(:add_to_order_cycle, to: managed_participating_hubs, scope: @order_cycle.suppliers).pluck(:id) @@ -175,7 +175,7 @@ module OpenFoodNetwork Spree::Variant.where(id: coordinator_variants | permitted_variants | active_variants) else # Any of my managed producers in this order cycle granted P-OC by the hub - granted_producers = granted(:add_to_order_cycle, by: [hub], scope: managed_participating_producers) + granted_producers = related_enterprises_granted(:add_to_order_cycle, by: [hub], scope: managed_participating_producers) # Any variants produced by MY PRODUCERS that are in this order cycle, where my producer has granted P-OC to the hub granting_producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: granted_producers) @@ -235,7 +235,7 @@ module OpenFoodNetwork # Find my producers in this order cycle producers = managed_participating_producers.pluck :id # Any outgoing exchange where the distributor has been granted P-OC by one or more of those producers - hubs = granted(:add_to_order_cycle, by: producers, scope: Enterprise.is_hub) + hubs = related_enterprises_granted(:add_to_order_cycle, by: producers, scope: Enterprise.is_hub) permitted_exchanges = @order_cycle.exchanges.outgoing.where(receiver_id: hubs).pluck :id # TODO: remove active_exchanges when we think it is safe to do so diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 296ab9843c..ea4d58208e 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -73,7 +73,7 @@ module OpenFoodNetwork # Any orders placed through hubs that my producers have granted P-OC, and which contain my their products # This is pretty complicated but it's looking for order where at least one of my producers has granted # P-OC to the distributor AND the order contains products of at least one of THE SAME producers - granted_distributors = granted(:add_to_order_cycle, by: managed_enterprises.is_primary_producer) + granted_distributors = related_enterprises_granted(:add_to_order_cycle, by: managed_enterprises.is_primary_producer) produced = Spree::Order.with_line_items_variants_and_products_outer. where( "spree_orders.distributor_id IN (?) AND spree_products.supplier_id IN (?)", @@ -157,7 +157,7 @@ module OpenFoodNetwork (options[:scope] || Enterprise).where('enterprises.id IN (?)', parent_ids) end - def granted(permission, options={}) + def related_enterprises_granted(permission, options={}) child_ids = EnterpriseRelationship. permitted_by(options[:by] || managed_enterprises). with_permission(permission). From bd66091d75a18104f121ddd6288f9b169c234614 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 12:49:07 +1000 Subject: [PATCH 057/206] Push logic for checking of user super admin status down into private method --- .../admin/reports_controller_decorator.rb | 4 +-- lib/open_food_network/permissions.rb | 16 +++++----- .../lib/open_food_network/permissions_spec.rb | 30 ++++++++++++++----- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index ccc142f195..69165da484 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -426,10 +426,10 @@ Spree::Admin::ReportsController.class_eval do end # My distributors and any distributors distributing products I supply - @distributors = permissions.order_cycle_enterprises.is_distributor + @distributors = permissions.order_report_enterprises(:add_to_order_cycle).is_distributor # My suppliers and any suppliers supplying products I distribute - @suppliers = permissions.order_cycle_enterprises.is_primary_producer + @suppliers = permissions.order_report_enterprises(:add_to_order_cycle).is_primary_producer @order_cycles = OrderCycle.active_or_complete. involving_managed_distributors_of(spree_current_user).order('orders_close_at DESC') diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index ea4d58208e..d7126263d5 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -17,11 +17,7 @@ module OpenFoodNetwork def enterprises_managed_or_granting_add_to_order_cycle # Return enterprises that the user manages and those that have granted P-OC to managed enterprises - if admin? - Enterprise.scoped - else - managed_and_related_enterprises_granting :add_to_order_cycle - end + managed_and_related_enterprises_granting :add_to_order_cycle end # Find enterprises for which an admin is allowed to edit their profile @@ -132,10 +128,14 @@ module OpenFoodNetwork end def managed_and_related_enterprises_granting(permission) - managed_enterprise_ids = managed_enterprises.pluck :id - permitting_enterprise_ids = related_enterprises_granting(permission).pluck :id + if admin? + Enterprise.scoped + else + managed_enterprise_ids = managed_enterprises.pluck :id + permitting_enterprise_ids = related_enterprises_granting(permission).pluck :id - Enterprise.where('id IN (?)', managed_enterprise_ids + permitting_enterprise_ids) + Enterprise.where('id IN (?)', managed_enterprise_ids + permitting_enterprise_ids) + end end def managed_enterprises diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 3d796b9f46..de22db9a59 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -8,23 +8,37 @@ module OpenFoodNetwork let(:e1) { create(:enterprise) } let(:e2) { create(:enterprise) } + describe "finding managed and related enterprises granting a particular permission" do + describe "as super admin" do + before { allow(user).to receive(:admin?) { true } } + + it "returns all enterprises" do + expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to eq [e1, e2] + end + end + + describe "as an enterprise user" do + let(:e3) { create(:enterprise) } + before { allow(user).to receive(:admin?) { false } } + + it "returns only my managed enterprises any that have granting them P-OC" do + expect(permissions).to receive(:managed_enterprises) { Enterprise.where(id: e1) } + expect(permissions).to receive(:related_enterprises_granting).with(:some_permission) { Enterprise.where(id: e3) } + expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to eq [e1, e3] + end + end + end + describe "finding enterprises that can be added to an order cycle" do let(:e) { double(:enterprise) } it "returns managed and related enterprises with add_to_order_cycle permission" do - allow(user).to receive(:admin?) { false } expect(permissions).to receive(:managed_and_related_enterprises_granting). with(:add_to_order_cycle). and_return([e]) expect(permissions.enterprises_managed_or_granting_add_to_order_cycle).to eq [e] end - - it "shows all enterprises for admin user" do - allow(user).to receive(:admin?) { true } - - expect(permissions.enterprises_managed_or_granting_add_to_order_cycle).to eq [e1, e2] - end end describe "finding enterprises whose profiles can be edited" do @@ -61,6 +75,7 @@ module OpenFoodNetwork before do permissions.stub(:managed_enterprises) { Enterprise.where(id: hub.id) } + permissions.stub(:admin?) { false } end it "returns enterprises as hub_id => [producer, ...]" do @@ -167,6 +182,7 @@ module OpenFoodNetwork before do permissions.stub(:managed_enterprises) { Enterprise.where('1=0') } permissions.stub(:related_enterprises_granting) { Enterprise.where('1=0') } + permissions.stub(:admin?) { false } end it "returns managed enterprises" do From a7019e7e7829b212dc86bfb95a28274de5be2b6e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 13:00:36 +1000 Subject: [PATCH 058/206] Adding permissions method for order report enterprises --- app/controllers/api/enterprises_controller.rb | 2 +- .../admin/reports_controller_decorator.rb | 4 +- lib/open_food_network/permissions.rb | 17 +++++++-- .../lib/open_food_network/permissions_spec.rb | 37 ++++++++++++++++++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index 973492b5e7..f39c34d22e 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -13,7 +13,7 @@ module Api end def accessible - permitted = Permissions.new(current_api_user).enterprises_managed_or_granting_add_to_order_cycle + permitted = Permissions.new(current_api_user).order_cycle_enterprises @enterprises = permitted.ransack(params[:q]).result render params[:template] || :bulk_index end diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 69165da484..3aa4f560a1 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -426,10 +426,10 @@ Spree::Admin::ReportsController.class_eval do end # My distributors and any distributors distributing products I supply - @distributors = permissions.order_report_enterprises(:add_to_order_cycle).is_distributor + @distributors = permissions.visible_enterprises_for_order_reports.is_distributor # My suppliers and any suppliers supplying products I distribute - @suppliers = permissions.order_report_enterprises(:add_to_order_cycle).is_primary_producer + @suppliers = permissions.visible_enterprises_for_order_reports.is_primary_producer @order_cycles = OrderCycle.active_or_complete. involving_managed_distributors_of(spree_current_user).order('orders_close_at DESC') diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index d7126263d5..4da645582d 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -11,11 +11,11 @@ module OpenFoodNetwork end # Find enterprises that an admin is allowed to add to an order cycle - def order_cycle_enterprises - managed_and_related_enterprises_granting :add_to_order_cycle + def visible_enterprises_for_order_reports + managed_and_related_enterprises_with :add_to_order_cycle end - def enterprises_managed_or_granting_add_to_order_cycle + def order_cycle_enterprises # Return enterprises that the user manages and those that have granted P-OC to managed enterprises managed_and_related_enterprises_granting :add_to_order_cycle end @@ -138,6 +138,17 @@ module OpenFoodNetwork end end + def managed_and_related_enterprises_with(permission) + if admin? + Enterprise.scoped + else + managed = managed_enterprises.pluck(:id) + granting = related_enterprises_granting(permission).pluck(:id) + granted = related_enterprises_granted(permission).pluck(:id) + Enterprise.where(id: managed | granting | granted) + end + end + def managed_enterprises return @managed_enterprises unless @managed_enterprises.nil? @managed_enterprises = Enterprise.managed_by(@user) diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index de22db9a59..10552572f0 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -29,6 +29,41 @@ module OpenFoodNetwork end end + describe "finding managed and related enterprises granting or granted a particular permission" do + describe "as super admin" do + before { allow(user).to receive(:admin?) { true } } + + it "returns all enterprises" do + expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to eq [e1, e2] + end + end + + describe "as an enterprise user" do + let(:e3) { create(:enterprise) } + let(:e4) { create(:enterprise) } + before { allow(user).to receive(:admin?) { false } } + + it "returns only my managed enterprises any that have granting them P-OC" do + expect(permissions).to receive(:managed_enterprises) { Enterprise.where(id: e1) } + expect(permissions).to receive(:related_enterprises_granting).with(:some_permission) { Enterprise.where(id: e3) } + expect(permissions).to receive(:related_enterprises_granted).with(:some_permission) { Enterprise.where(id: e4) } + expect(permissions.send(:managed_and_related_enterprises_with, :some_permission)).to eq [e1, e3, e4] + end + end + end + + describe "finding enterprises that can be selected in order report filters" do + let(:e) { double(:enterprise) } + + it "returns managed and related enterprises with add_to_order_cycle permission" do + expect(permissions).to receive(:managed_and_related_enterprises_with). + with(:add_to_order_cycle). + and_return([e]) + + expect(permissions.visible_enterprises_for_order_reports).to eq [e] + end + end + describe "finding enterprises that can be added to an order cycle" do let(:e) { double(:enterprise) } @@ -37,7 +72,7 @@ module OpenFoodNetwork with(:add_to_order_cycle). and_return([e]) - expect(permissions.enterprises_managed_or_granting_add_to_order_cycle).to eq [e] + expect(permissions.order_cycle_enterprises).to eq [e] end end From f88fdac710e637d18e4c3251c05a3df9767e65ba Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 13:53:59 +1000 Subject: [PATCH 059/206] Adding module - doh! --- app/controllers/api/enterprises_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index f39c34d22e..41b24f30a1 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -13,7 +13,7 @@ module Api end def accessible - permitted = Permissions.new(current_api_user).order_cycle_enterprises + permitted = OpenFoodNetwork::Permissions.new(current_api_user).order_cycle_enterprises @enterprises = permitted.ransack(params[:q]).result render params[:template] || :bulk_index end From 68b4cb59be1cce148b663f806e921eb4a9fffce1 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 14:46:23 +1000 Subject: [PATCH 060/206] Fixing bulk management specs broken by making order_cycles filter update dates --- .../admin/bulk_order_management_spec.rb | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index c8ad230a00..1058bebfc0 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -238,8 +238,9 @@ feature %q{ end context "order_cycle filter" do - let!(:oc1) { FactoryGirl.create(:simple_order_cycle ) } - let!(:oc2) { FactoryGirl.create(:simple_order_cycle ) } + let!(:distributor) { create(:distributor_enterprise) } + let!(:oc1) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } + let!(:oc2) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, order_cycle: oc1 ) } let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, order_cycle: oc2 ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } @@ -255,20 +256,22 @@ feature %q{ find("div.select2-container#s2id_order_cycle_filter").click order_cycle_names.each { |ocn| page.should have_selector "div.select2-drop-active ul.select2-results li", text: ocn } find("div.select2-container#s2id_order_cycle_filter").click - page.should have_selector "tr#li_#{li1.id}", visible: true - page.should have_selector "tr#li_#{li2.id}", visible: true + page.should have_selector "tr#li_#{li1.id}" + page.should have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" - page.should have_selector "tr#li_#{li1.id}", visible: true - page.should_not have_selector "tr#li_#{li2.id}", visible: true + page.should have_selector "#loading img.spinner" + page.should_not have_selector "#loading img.spinner" + page.should have_selector "tr#li_#{li1.id}" + page.should_not have_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from order_cycle filter" do select2_select oc1.name, from: "order_cycle_filter" - page.should have_selector "tr#li_#{li1.id}", visible: true - page.should_not have_selector "tr#li_#{li2.id}", visible: true + page.should have_selector "tr#li_#{li1.id}" + page.should_not have_selector "tr#li_#{li2.id}" select2_select "All", from: "order_cycle_filter" - page.should have_selector "tr#li_#{li1.id}", visible: true - page.should have_selector "tr#li_#{li2.id}", visible: true + page.should have_selector "tr#li_#{li1.id}" + page.should have_selector "tr#li_#{li2.id}" end end From 0d5ce5ff57a804a76e380a41f60c2078b524ad49 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 15:19:48 +1000 Subject: [PATCH 061/206] Fixing issues with reports controller spec --- .../spree/admin/reports_controller_spec.rb | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 432bc4e67f..58d406519b 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -1,23 +1,25 @@ require 'spec_helper' describe Spree::Admin::ReportsController do - + # Given two distributors and two suppliers let(:ba) { create(:address) } let(:si) { "pick up on thursday please" } - let(:s1) { create(:supplier_enterprise, address: create(:address)) } - let(:s2) { create(:supplier_enterprise, address: create(:address)) } - let(:s3) { create(:supplier_enterprise, address: create(:address)) } - let(:d1) { create(:distributor_enterprise, address: create(:address)) } - let(:d2) { create(:distributor_enterprise, address: create(:address)) } - let(:d3) { create(:distributor_enterprise, address: create(:address)) } + let(:c1) { create(:distributor_enterprise) } + let(:c2) { create(:distributor_enterprise) } + let(:s1) { create(:supplier_enterprise) } + let(:s2) { create(:supplier_enterprise) } + let(:s3) { create(:supplier_enterprise) } + let(:d1) { create(:distributor_enterprise) } + let(:d2) { create(:distributor_enterprise) } + let(:d3) { create(:distributor_enterprise) } let(:p1) { create(:product, price: 12.34, distributors: [d1], supplier: s1) } let(:p2) { create(:product, price: 23.45, distributors: [d2], supplier: s2) } let(:p3) { create(:product, price: 34.56, distributors: [d3], supplier: s3) } # Given two order cycles with both distributors - let(:ocA) { create(:simple_order_cycle, distributors: [d1, d2], suppliers: [s1, s2, s3], variants: [p1.master, p3.master]) } - let(:ocB) { create(:simple_order_cycle, distributors: [d1, d2], suppliers: [s1, s2, s3], variants: [p2.master]) } + let(:ocA) { create(:simple_order_cycle, coordinator: c1, distributors: [d1, d2], suppliers: [s1, s2, s3], variants: [p1.master, p3.master]) } + let(:ocB) { create(:simple_order_cycle, coordinator: c2, distributors: [d1, d2], suppliers: [s1, s2, s3], variants: [p2.master]) } # orderA1 can only be accessed by s1, s3 and d1 let!(:orderA1) do @@ -53,15 +55,29 @@ describe Spree::Admin::ReportsController do order.save order end - - # As a Distributor Enterprise user for d1 + + # As manager of a coordinator (c1) + context "Coordinator Enterprise User" do + before { login_as_enterprise_user [c1] } + + describe 'Orders & Fulfillment' do + it "shows all orders in order cycles I coordinate" do + spree_get :orders_and_fulfillment + + assigns(:line_items).map(&:order).should include orderA1, orderA2 + assigns(:line_items).map(&:order).should_not include orderB1, orderB2 + end + end + end + + # As a Distributor Enterprise user for d1 context "Distributor Enterprise User" do before { login_as_enterprise_user [d1] } describe 'Orders and Distributors' do it "only shows orders that I have access to" do spree_get :orders_and_distributors - + assigns(:search).result.should include(orderA1, orderB1) assigns(:search).result.should_not include(orderA2) assigns(:search).result.should_not include(orderB2) @@ -71,7 +87,7 @@ describe Spree::Admin::ReportsController do describe 'Bulk Coop' do it "only shows orders that I have access to" do spree_get :bulk_coop - + assigns(:search).result.should include(orderA1, orderB1) assigns(:search).result.should_not include(orderA2) assigns(:search).result.should_not include(orderB2) @@ -81,7 +97,7 @@ describe Spree::Admin::ReportsController do describe 'Payments' do it "only shows orders that I have access to" do spree_get :payments - + assigns(:search).result.should include(orderA1, orderB1) assigns(:search).result.should_not include(orderA2) assigns(:search).result.should_not include(orderB2) @@ -89,12 +105,11 @@ describe Spree::Admin::ReportsController do end describe 'Orders & Fulfillment' do - it "only shows orders that I have access to" do + it "only shows orders that I distribute" do spree_get :orders_and_fulfillment - assigns(:search).result.should include(orderA1, orderB1) - assigns(:search).result.should_not include(orderA2) - assigns(:search).result.should_not include(orderB2) + assigns(:line_items).map(&:order).should include orderA1, orderB1 + assigns(:line_items).map(&:order).should_not include orderA2, orderB2 end it "only shows the selected order cycle" do @@ -114,19 +129,31 @@ describe Spree::Admin::ReportsController do it "only shows product line items that I am supplying" do spree_get :bulk_coop - assigns(:line_items).map(&:product).should include(p1) - assigns(:line_items).map(&:product).should_not include(p2) - assigns(:line_items).map(&:product).should_not include(p3) + assigns(:line_items).map(&:product).should include p1 + assigns(:line_items).map(&:product).should_not include p2, p3 end end describe 'Orders & Fulfillment' do - it "only shows product line items that I am supplying" do - spree_get :orders_and_fulfillment + context "where I have granted P-OC to the distributor" do + before do + create(:enterprise_relationship, parent: s1, child: d1, permissions_list: [:add_to_order_cycle]) + end - assigns(:line_items).map(&:product).should include(p1) - assigns(:line_items).map(&:product).should_not include(p2) - assigns(:line_items).map(&:product).should_not include(p3) + it "only shows product line items that I am supplying" do + spree_get :orders_and_fulfillment + + assigns(:line_items).map(&:product).should include p1 + assigns(:line_items).map(&:product).should_not include p2, p3 + end + end + + context "where I have not granted P-OC to the distributor" do + it "does not show me line_items I supply" do + spree_get :orders_and_fulfillment + + assigns(:line_items).map(&:product).should_not include p1, p2, p3 + end end it "only shows the selected order cycle" do @@ -143,7 +170,7 @@ describe Spree::Admin::ReportsController do it "should build distributors for the current user" do spree_get :products_and_inventory - assigns(:distributors).sort.should == [d1, d2, d3].sort + assigns(:distributors).sort.should == [c1, c2, d1, d2, d3].sort end it "builds suppliers for the current user" do @@ -184,7 +211,7 @@ describe Spree::Admin::ReportsController do it "should build distributors for the current user" do spree_get :customers - assigns(:distributors).sort.should == [d1, d2, d3].sort + assigns(:distributors).sort.should == [c1, c2, d1, d2, d3].sort end it "builds suppliers for the current user" do From f3f07662795f3b4aa281ad24bd0eaf9d3fc666aa Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 12:48:31 +1000 Subject: [PATCH 062/206] Adding a distributor to order cycle to fix broken feature spec --- spec/features/admin/reports_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index b1685be6db..309e0a3e81 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -211,8 +211,9 @@ feature %q{ end it "handles order cycles with nil opening or closing times" do - oc = create(:simple_order_cycle, name: "My Order Cycle", orders_open_at: Time.now, orders_close_at: nil) - o = create(:order, order_cycle: oc) + distributor = create(:distributor_enterprise) + oc = create(:simple_order_cycle, name: "My Order Cycle", distributors: [distributor], orders_open_at: Time.now, orders_close_at: nil) + o = create(:order, order_cycle: oc, distributor: distributor) login_to_admin_section visit spree.orders_and_fulfillment_admin_reports_path From 6fb3fa55a15e1dcab42cecbf0bdcb02f771ccf2f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 7 May 2015 14:23:58 +1000 Subject: [PATCH 063/206] Allow extended time for all parts of this spec to fix intermittent fails --- spec/features/admin/authentication_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/features/admin/authentication_spec.rb b/spec/features/admin/authentication_spec.rb index 059963fd70..8473551c99 100644 --- a/spec/features/admin/authentication_spec.rb +++ b/spec/features/admin/authentication_spec.rb @@ -10,15 +10,15 @@ feature "Authentication", js: true do scenario "logging into admin redirects home, then back to admin" do # This is the first admin spec, so give a little extra load time for slow systems - Capybara.using_wait_time(60) do + Capybara.using_wait_time(120) do visit spree.admin_path - end - fill_in "Email", with: user.email - fill_in "Password", with: user.password - click_login_button - page.should have_content "DASHBOARD" - current_path.should == spree.admin_path + fill_in "Email", with: user.email + fill_in "Password", with: user.password + click_login_button + page.should have_content "DASHBOARD" + current_path.should == spree.admin_path + end end scenario "viewing my account" do From f7ade48e8655c282d765f06ed47dc865b142bbe0 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 4 Apr 2015 17:06:26 +0100 Subject: [PATCH 064/206] Update DB schema to store updatable weight on items sold --- .../api/line_items_controller_decorator.rb | 17 +++++++++++++++++ .../20150305004846_add_weight_to_line_items.rb | 5 +++++ db/schema.rb | 1 + 3 files changed, 23 insertions(+) create mode 100644 app/controllers/spree/api/line_items_controller_decorator.rb create mode 100644 db/migrate/20150305004846_add_weight_to_line_items.rb diff --git a/app/controllers/spree/api/line_items_controller_decorator.rb b/app/controllers/spree/api/line_items_controller_decorator.rb new file mode 100644 index 0000000000..f9a025873d --- /dev/null +++ b/app/controllers/spree/api/line_items_controller_decorator.rb @@ -0,0 +1,17 @@ +Spree::Api::LineItemsController.class_eval do + after_filter :apply_enterprise_fees, :only => :update + + def apply_enterprise_fees + authorize! :read, order + order.update_distribution_charge! + end +end + + +#when we update a line item the .update_distribution_charge! is called +# order.should_receive .update_distribution_charge! +# check fails when absent + +# in order model check that .update_distribution_charge! is properly tested. +# think through use cases - existing completed order +# currently likely just used to complete orders so add test case that works on a completed order diff --git a/db/migrate/20150305004846_add_weight_to_line_items.rb b/db/migrate/20150305004846_add_weight_to_line_items.rb new file mode 100644 index 0000000000..a4df1a12f0 --- /dev/null +++ b/db/migrate/20150305004846_add_weight_to_line_items.rb @@ -0,0 +1,5 @@ +class AddWeightToLineItems < ActiveRecord::Migration + def change + add_column :spree_line_items, :unit_value, :decimal, :precision => 8, :scale => 2 + end +end diff --git a/db/schema.rb b/db/schema.rb index cb8b503583..96f5896c79 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -549,6 +549,7 @@ ActiveRecord::Schema.define(:version => 20150424025907) do t.string "currency" t.decimal "distribution_fee", :precision => 10, :scale => 2 t.string "shipping_method_name" + t.decimal "unit_value", :precision => 8, :scale => 2 end add_index "spree_line_items", ["order_id"], :name => "index_line_items_on_order_id" From 2f463474fba9f1ce9c2b01e1bb7940369482a8d9 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 4 Apr 2015 17:44:02 +0100 Subject: [PATCH 065/206] Adding specs for variable weight adjustment via builk order management --- .../spree/api/line_items_controller_spec.rb | 31 +++++++++++++++++++ .../admin/bulk_order_management_spec.rb | 18 +++++++++-- .../unit/bulk_order_management_spec.js.coffee | 31 +++++++++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 spec/controllers/spree/api/line_items_controller_spec.rb diff --git a/spec/controllers/spree/api/line_items_controller_spec.rb b/spec/controllers/spree/api/line_items_controller_spec.rb new file mode 100644 index 0000000000..e3c9a262af --- /dev/null +++ b/spec/controllers/spree/api/line_items_controller_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +module Spree + describe Spree::Api::LineItemsController do + render_views + + before do + stub_authentication! + Spree.user_class.stub :find_by_spree_api_key => current_api_user + end + + def self.make_simple_data! + let!(:order) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now) } + let!(:line_item) { FactoryGirl.create(:line_item, order: order, unit_value: 500) } + end + + #test that when a line item is updated, an order's fees are updated too + context "as an admin user" do + sign_in_as_admin! + make_simple_data! + + context "as a line item is updated" do + it "apply enterprise fees on the item" do + line_item_params = { order_id: order.number, id: line_item.id, line_item: { id: line_item.id, unit_value: 520 }, format: :json} + controller.should_receive(:apply_enterprise_fees).and_return(true) + spree_post :update, line_item_params + end + end + end + end +end diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index c8ad230a00..7aa6449c1e 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -57,7 +57,7 @@ feature %q{ end it "displays a column for order date" do - page.should have_selector "th,date", text: "ORDER DATE", :visible => true + page.should have_selector "th.date", text: "ORDER DATE", :visible => true page.should have_selector "td.date", text: o1.completed_at.strftime("%F %T"), :visible => true page.should have_selector "td.date", text: o2.completed_at.strftime("%F %T"), :visible => true end @@ -141,8 +141,22 @@ feature %q{ admin_user = quick_login_as_admin end + let!(:p1) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } + let!(:v1) { p1.variants.first } let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } + let!(:li1) { FactoryGirl.create(:line_item, order: o1, variant: v1, :quantity => 5, :unit_value => 1000 ) } + + context "modifying the weight/volume of a line item" do + it "update-pending is added to variable 'price'" do + visit '/admin/orders/bulk_management' + first("div#columns_dropdown", :text => "COLUMNS").click + first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click + page.should_not have_css "input[name='price'].update-pending" + li1_unit_value_column = find("tr#li_#{li1.id} td.unit_value") + li1_unit_value_column.fill_in "unit_value", :with => 1200 + page.should have_css "input[name='price'].update-pending", :visible => false + end + end context "using column display toggle" do it "shows a column display toggle button, which shows a list of columns when clicked" do diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 35b9e13d61..76ae925785 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -33,8 +33,8 @@ describe "AdminOrderMgmtCtrl", -> httpBackend.flush() expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] - expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] - expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] + expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] + expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] expect(scope.initialiseVariables.calls.length).toBe 1 expect(scope.fetchOrders.calls.length).toBe 1 @@ -350,6 +350,33 @@ describe "AdminOrderMgmtCtrl", -> spyOn(VariantUnitManager, "getUnitName").andReturn "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" + describe "updating the price upon updating the weight of a line item", -> + + it "resets the weight if the weight is set to zero", -> + scope.filteredLineItems = [ + { units_variant: { unit_value: 100 }, price: 2, unit_value: 0 } + ] + expect(scope.weightAdjustedPrice(scope.filteredLineItems[0], 100)).toEqual scope.filteredLineItems[0].price + + it "updates the price if the weight is changed", -> + scope.filteredLineItems = [ + { units_variant: { unit_value: 100 }, price: 2, unit_value: 200 } + ] + old_value = scope.filteredLineItems[0].units_variant.unit_value + new_value = scope.filteredLineItems[0].unit_value + sp = scope.filteredLineItems[0].price * new_value / old_value + expect(scope.weightAdjustedPrice(scope.filteredLineItems[0], old_value)).toEqual sp + + it "doesn't update the price if the weight is not changed", -> + scope.filteredLineItems = [ + { units_variant: { unit_value: 100 }, price: 2, unit_value: 100 } + ] + old_value = scope.filteredLineItems[0].unit_value + new_value = scope.filteredLineItems[0].unit_value + sp = scope.filteredLineItems[0].price + expect(scope.weightAdjustedPrice(scope.filteredLineItems[0], old_value)).toEqual sp + + describe "managing pending changes", -> dataSubmitter = pendingChangesService = null From 2a991ad1300fda8dcf17fca2b9d6277f5d44c1ea Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 4 Apr 2015 17:52:31 +0100 Subject: [PATCH 066/206] Variable Weights: Adding ability to update the weight/volume of a line_item after checkout. The price of the line_item is automatically updated to reflect the value of the new weight. --- .../admin/bulk_order_management.js.coffee | 9 +- .../directives/line_item_upd_attr.js.coffee | 6 +- app/models/spree/line_item_decorator.rb | 3 +- .../admin/orders/bulk_management.html.haml | 107 ++++++++++-------- .../spree/api/line_items/bulk_show.v1.rabl | 6 +- 5 files changed, 75 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 4c1a319c1a..a20cdd27e7 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -32,7 +32,8 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ variant: { name: "Variant", visible: true } quantity: { name: "Quantity", visible: true } max: { name: "Max", visible: true } - + unit_value: { name: "Weight/Volume", visible: false } + price: { name: "Price", visible: false } $scope.initialise = -> $scope.initialiseVariables() authorise_api_reponse = "" @@ -162,6 +163,12 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.supplierFilter = $scope.suppliers[0].id $scope.orderCycleFilter = $scope.orderCycles[0].id $scope.quickSearch = "" + + $scope.weightAdjustedPrice = (lineItem, oldValue) -> + if lineItem.unit_value <= 0 + lineItem.unit_value = lineItem.units_variant.unit_value + lineItem.price = lineItem.price * lineItem.unit_value / oldValue + #$scope.bulk_order_form.line_item.price.$setViewValue($scope.bulk_order_form.line_item.price.$viewValue) ] daysFromToday = (days) -> diff --git a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee index c83d7fdc0f..c5afce07a5 100644 --- a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee +++ b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee @@ -8,7 +8,9 @@ angular.module("ofn.admin").directive "ofnLineItemUpdAttr", [ scope.$watch -> scope.$eval(attrs.ngModel) , (value) -> - if ngModel.$dirty + #if ngModel.$dirty + # i think i can take this out, this directive is still only called + # on a change and only an updated value will create a db call. if value == element.dbValue pendingChanges.remove(scope.line_item.id, attrName) switchClass( element, "", ["update-pending", "update-error", "update-success"], false ) @@ -20,4 +22,4 @@ angular.module("ofn.admin").directive "ofnLineItemUpdAttr", [ url: "/api/orders/#{scope.line_item.order.number}/line_items/#{scope.line_item.id}?line_item[#{attrName}]=#{value}" pendingChanges.add(scope.line_item.id, attrName, changeObj) switchClass( element, "update-pending", ["update-error", "update-success"], false ) -] \ No newline at end of file +] diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index b2c0a583fc..4eec0bcd2b 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -1,5 +1,6 @@ Spree::LineItem.class_eval do - attr_accessible :max_quantity + attr_accessible :max_quantity, :unit_value + attr_accessible :unit_value, :price, :as => :api # -- Scopes scope :managed_by, lambda { |user| diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 61bf9fbb7e..a1f53826e1 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -100,53 +100,60 @@ %div{ :class => "sixteen columns alpha", 'ng-show' => '!loading && filteredLineItems.length == 0'} %h1#no_results No orders found. %div{ 'ng-hide' => 'loading || filteredLineItems.length == 0' } - %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } - %thead - %tr - %th.bulk - %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } - %th.order_no{ 'ng-show' => 'columns.order_no.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } Order No. - %th.full_name{ 'ng-show' => 'columns.full_name.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } Name - %th.email{ 'ng-show' => 'columns.email.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } Email - %th.phone{ 'ng-show' => 'columns.phone.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } Phone - %th.date{ 'ng-show' => 'columns.order_date.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } Order Date - %th.producer{ 'ng-show' => 'columns.producer.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } Producer - %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } Order Cycle - %th.hub{ 'ng-show' => 'columns.hub.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub - %th.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit - %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity - %th.max{ 'ng-show' => 'columns.max.visible' } Max - %th.actions - %th.actions - Ask?  - %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } - %td.bulk - %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } - %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} - %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} - %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} - %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} - %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} - %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} - %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} - %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} - %td.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} - %td.quantity{ 'ng-show' => 'columns.quantity.visible' } - %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } - %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} - %td.actions - %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } - %td.actions - %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } - %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } \ No newline at end of file + %form{ 'ng-model' => "bulk_order_form" } + %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } + %thead + %tr + %th.bulk + %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } + %th.order_no{ 'ng-show' => 'columns.order_no.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } Order No. + %th.full_name{ 'ng-show' => 'columns.full_name.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } Name + %th.email{ 'ng-show' => 'columns.email.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } Email + %th.phone{ 'ng-show' => 'columns.phone.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } Phone + %th.date{ 'ng-show' => 'columns.order_date.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } Order Date + %th.producer{ 'ng-show' => 'columns.producer.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } Producer + %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } Order Cycle + %th.hub{ 'ng-show' => 'columns.hub.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub + %th.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit + %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity + %th.max{ 'ng-show' => 'columns.max.visible' } Max + %th.unit_value{ 'ng-show' => 'columns.unit_value.visible' } Weight/Volume + %th.price{ 'ng-show' => 'columns.price.visible' } Price + %th.actions + %th.actions + Ask?  + %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } + %td.bulk + %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } + %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} + %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} + %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} + %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} + %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} + %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} + %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} + %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} + %td.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} + %td.quantity{ 'ng-show' => 'columns.quantity.visible' } + %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } + %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} + %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } + %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "unit_value" } + %td.price{ 'ng-show' => 'columns.price.visible' } + %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-model' => "line_item.price", 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "price" } + %td.actions + %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } + %td.actions + %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } + %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } diff --git a/app/views/spree/api/line_items/bulk_show.v1.rabl b/app/views/spree/api/line_items/bulk_show.v1.rabl index 8b53c42086..6b24ef4bf2 100644 --- a/app/views/spree/api/line_items/bulk_show.v1.rabl +++ b/app/views/spree/api/line_items/bulk_show.v1.rabl @@ -1,5 +1,7 @@ object @line_item -attributes :id, :quantity, :max_quantity +attributes :id, :quantity, :max_quantity, :price node( :supplier ) { |li| partial 'api/enterprises/bulk_show', :object => li.product.supplier } node( :units_product ) { |li| partial 'spree/api/products/units_show', :object => li.product } -node( :units_variant ) { |li| partial 'spree/api/variants/units_show', :object => li.variant } \ No newline at end of file +node( :units_variant ) { |li| partial 'spree/api/variants/units_show', :object => li.variant } +node( :unit_value ) { |li| li.unit_value.to_f } +node( :price ) { |li| li.price } From 54da7ae241262a7e79666037828c02108fd0170e Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Tue, 14 Apr 2015 17:29:56 +0100 Subject: [PATCH 067/206] Adding additional logic for if the line_item unit_value is nil --- app/assets/javascripts/admin/bulk_order_management.js.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index a20cdd27e7..a52b770831 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -165,6 +165,8 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.quickSearch = "" $scope.weightAdjustedPrice = (lineItem, oldValue) -> + if oldValue <= 0 + oldValue = lineItem.units_variant.unit_value if lineItem.unit_value <= 0 lineItem.unit_value = lineItem.units_variant.unit_value lineItem.price = lineItem.price * lineItem.unit_value / oldValue From 662c7fe3681a9b454823607c8654213a147af251 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 24 Apr 2015 16:14:24 +0100 Subject: [PATCH 068/206] Removing notes to myself from this file --- .../spree/api/line_items_controller_decorator.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/controllers/spree/api/line_items_controller_decorator.rb b/app/controllers/spree/api/line_items_controller_decorator.rb index f9a025873d..35fca864f4 100644 --- a/app/controllers/spree/api/line_items_controller_decorator.rb +++ b/app/controllers/spree/api/line_items_controller_decorator.rb @@ -6,12 +6,3 @@ Spree::Api::LineItemsController.class_eval do order.update_distribution_charge! end end - - -#when we update a line item the .update_distribution_charge! is called -# order.should_receive .update_distribution_charge! -# check fails when absent - -# in order model check that .update_distribution_charge! is properly tested. -# think through use cases - existing completed order -# currently likely just used to complete orders so add test case that works on a completed order From 521834bd76d8a22b6279c49262821fa97528abec Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 24 Apr 2015 16:17:00 +0100 Subject: [PATCH 069/206] Populate the line item unit value, when line_item created and update old data in migration --- app/models/spree/order_decorator.rb | 1 + .../20150424151117_populate_line_item_unit_value.rb | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 db/migrate/20150424151117_populate_line_item_unit_value.rb diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 0dc6977965..cbc5c0086d 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -127,6 +127,7 @@ Spree::Order.class_eval do else current_item = Spree::LineItem.new(:quantity => quantity, max_quantity: max_quantity) current_item.variant = variant + current_item.unit_value = variant.unit_value if currency current_item.currency = currency unless currency.nil? current_item.price = variant.price_in(currency).amount diff --git a/db/migrate/20150424151117_populate_line_item_unit_value.rb b/db/migrate/20150424151117_populate_line_item_unit_value.rb new file mode 100644 index 0000000000..2122b84472 --- /dev/null +++ b/db/migrate/20150424151117_populate_line_item_unit_value.rb @@ -0,0 +1,9 @@ +class PopulateLineItemUnitValue < ActiveRecord::Migration + def up + execute "UPDATE spree_line_items SET unit_value = spree_variants.unit_value FROM spree_variants WHERE spree_line_items.variant_id = spree_variants.id" + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end From 4d025ee7a923060a8e09d5977f8136094132b726 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 24 Apr 2015 16:22:17 +0100 Subject: [PATCH 070/206] Updating the spec based on @Robs suggestions, hoping for his insights. Still doesn't work. --- spec/controllers/spree/api/line_items_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/spree/api/line_items_controller_spec.rb b/spec/controllers/spree/api/line_items_controller_spec.rb index e3c9a262af..7d0b215ae9 100644 --- a/spec/controllers/spree/api/line_items_controller_spec.rb +++ b/spec/controllers/spree/api/line_items_controller_spec.rb @@ -20,9 +20,9 @@ module Spree make_simple_data! context "as a line item is updated" do - it "apply enterprise fees on the item" do + it "update distribution charge on the order" do line_item_params = { order_id: order.number, id: line_item.id, line_item: { id: line_item.id, unit_value: 520 }, format: :json} - controller.should_receive(:apply_enterprise_fees).and_return(true) + order.should_receive(:update_distribution_charge!).and_call_original spree_post :update, line_item_params end end From 3179887842309f497ffb8685cca3ac726d64dcf9 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Sat, 9 May 2015 19:03:06 +0100 Subject: [PATCH 071/206] Do not allow line_item.unit_value to be updated if the variant.unit_value is zero --- .../javascripts/admin/bulk_order_management.js.coffee | 6 ++++++ app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index a52b770831..bf80b9f353 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -171,6 +171,12 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ lineItem.unit_value = lineItem.units_variant.unit_value lineItem.price = lineItem.price * lineItem.unit_value / oldValue #$scope.bulk_order_form.line_item.price.$setViewValue($scope.bulk_order_form.line_item.price.$viewValue) + + $scope.unitValueLessThanZero = (lineItem) -> + if lineItem.units_variant.unit_value <= 0 + true + else + false ] daysFromToday = (days) -> diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index a1f53826e1..f6680aefae 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -149,7 +149,7 @@ %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } - %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "unit_value" } + %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-readonly' => "unitValueLessThanZero(line_item)", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "unit_value" } %td.price{ 'ng-show' => 'columns.price.visible' } %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-model' => "line_item.price", 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "price" } %td.actions From 62ae38372ef4f47ae1b03881a7375e0dfffc1b87 Mon Sep 17 00:00:00 2001 From: Rick Giner Date: Mon, 11 May 2015 20:31:24 +1000 Subject: [PATCH 072/206] #541 add "show more" link to producers lists in hub on home page --- .../controllers/hub_node_controller.js.coffee | 4 ++-- .../stylesheets/darkswarm/hub_node.css.sass | 17 +++++++++++++++++ app/views/home/_fat.html.haml | 18 +++++++++++++++--- app/views/home/_skinny.html.haml | 2 +- app/views/producers/_fat.html.haml | 2 +- app/views/producers/_skinny.html.haml | 2 +- 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/hub_node_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/hub_node_controller.js.coffee index e73ca5fa27..922dc8abb4 100644 --- a/app/assets/javascripts/darkswarm/controllers/hub_node_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/hub_node_controller.js.coffee @@ -1,6 +1,6 @@ Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, Navigation, $location, $templateCache, CurrentHub) -> - $scope.toggle = -> - HashNavigation.toggle $scope.hub.hash + $scope.toggle = (e) -> + HashNavigation.toggle $scope.hub.hash if !angular.element(e.target).inheritedData('is-link') $scope.open = -> HashNavigation.active $scope.hub.hash diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index d02f9b5299..dfe4d42074 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -83,6 +83,23 @@ .active_table_row:first-child .skinny-head background-color: rgba(255,255,255,0.85) + .producers-list + li.more-producers-link + .less + display: none + a:hover + text-decoration: underline + li.additional-producer + display: none + &.show-more-producers + li.additional-producer + display: block + li.more-producers-link + .more + display: none + .less + display: block + //INACTIVE - closed hub &.inactive &.closed, &.open diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index 6796601637..092d9e898d 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -1,4 +1,4 @@ -.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle()", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} +.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} .columns.small-12.medium-6.large-5.fat %div{"bo-if" => "hub.taxons"} %label Shop for @@ -21,8 +21,20 @@ .columns.small-12.medium-3.large-5.fat %div{"bo-if" => "hub.producers"} %label Our producers - %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2 - %li{"ng-repeat" => "enterprise in hub.producers"} + %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"} + %li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"} + %enterprise-modal + %i.ofn-i_036-producers + %span{"bo-text" => "enterprise.name"} + %li{"data-is-link" => "true", "class" => "more-producers-link"} + %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers"} + .more + + + %span{"bo-text" => "hub.producers.length-7"} + More + .less + Show less + %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"} %enterprise-modal %i.ofn-i_036-producers %span{"bo-text" => "enterprise.name"} diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index cf001371f7..9cf7a01c77 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,4 +1,4 @@ -.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true} +.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true} .columns.small-12.medium-6.large-5.skinny-head %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index a6d6661501..99099a31f8 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -1,4 +1,4 @@ -.row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle()", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} +.row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} .columns.small-12.medium-7.large-7.fat / Will add in long description available once clean up HTML formatting producer.long_description diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index 49ab0a35be..11860d35d6 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -1,4 +1,4 @@ -.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} +.row.active_table_row{"ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} .columns.small-12.medium-4.large-4.skinny-head %span{"bo-if" => "producer.is_distributor" } %a.is_distributor{"bo-href" => "producer.path" } From 3520127c41a7928e25ba1ea3d1e7dfd86339ff4e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 13 May 2015 11:06:42 +1000 Subject: [PATCH 073/206] Fix infinite job loop --- app/models/spree/user_decorator.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 8298627547..d8ea312e23 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -1,5 +1,9 @@ Spree.user_class.class_eval do - handle_asynchronously :send_reset_password_instructions + if method_defined? :send_reset_password_instructions_with_delay + Bugsnag.notify RuntimeError.new "send_reset_password_instructions already handled asyncronously - double-calling results in infinite job loop" + else + handle_asynchronously :send_reset_password_instructions + end has_many :enterprise_roles, :dependent => :destroy has_many :enterprises, through: :enterprise_roles From 30bb948d35f392cb59cd7983626cdbe03cd5997c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 15 Apr 2015 10:25:20 +1000 Subject: [PATCH 074/206] Making ofn-line-item-upd-attr work without ng-model Conflicts: app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee app/views/spree/admin/orders/bulk_management.html.haml --- .../directives/line_item_upd_attr.js.coffee | 41 +++---- .../admin/orders/bulk_management.html.haml | 107 ++++++++++-------- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee index c83d7fdc0f..a33fa6a1b9 100644 --- a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee +++ b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee @@ -1,23 +1,18 @@ -angular.module("ofn.admin").directive "ofnLineItemUpdAttr", [ - "switchClass", "pendingChanges" - (switchClass, pendingChanges) -> - require: "ngModel" - link: (scope, element, attrs, ngModel) -> - attrName = attrs.ofnLineItemUpdAttr - element.dbValue = scope.$eval(attrs.ngModel) - scope.$watch -> - scope.$eval(attrs.ngModel) - , (value) -> - if ngModel.$dirty - if value == element.dbValue - pendingChanges.remove(scope.line_item.id, attrName) - switchClass( element, "", ["update-pending", "update-error", "update-success"], false ) - else - changeObj = - lineItem: scope.line_item - element: element - attrName: attrName - url: "/api/orders/#{scope.line_item.order.number}/line_items/#{scope.line_item.id}?line_item[#{attrName}]=#{value}" - pendingChanges.add(scope.line_item.id, attrName, changeObj) - switchClass( element, "update-pending", ["update-error", "update-success"], false ) -] \ No newline at end of file +angular.module("ofn.admin").directive "ofnLineItemUpdAttr", (switchClass, pendingChanges) -> + scope: + lineItem: "&ofnLineItemUpdAttr" + attrName: "@" + link: (scope, element, attrs) -> + element.dbValue = scope.lineItem()[scope.attrName] + scope.$watch "lineItem().#{scope.attrName}", (value) -> + if value == element.dbValue + pendingChanges.remove(scope.lineItem().id, scope.attrName) + switchClass( element, "", ["update-pending", "update-error", "update-success"], false ) + else + changeObj = + lineItem: scope.lineItem() + element: element + attrName: scope.attrName + url: "/api/orders/#{scope.lineItem().order.number}/line_items/#{scope.lineItem().id}?line_item[#{scope.attrName}]=#{value}" + pendingChanges.add(scope.lineItem().id, scope.attrName, changeObj) + switchClass( element, "update-pending", ["update-error", "update-success"], false ) diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 61bf9fbb7e..406dc18046 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -100,53 +100,60 @@ %div{ :class => "sixteen columns alpha", 'ng-show' => '!loading && filteredLineItems.length == 0'} %h1#no_results No orders found. %div{ 'ng-hide' => 'loading || filteredLineItems.length == 0' } - %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } - %thead - %tr - %th.bulk - %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } - %th.order_no{ 'ng-show' => 'columns.order_no.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } Order No. - %th.full_name{ 'ng-show' => 'columns.full_name.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } Name - %th.email{ 'ng-show' => 'columns.email.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } Email - %th.phone{ 'ng-show' => 'columns.phone.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } Phone - %th.date{ 'ng-show' => 'columns.order_date.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } Order Date - %th.producer{ 'ng-show' => 'columns.producer.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } Producer - %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } Order Cycle - %th.hub{ 'ng-show' => 'columns.hub.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub - %th.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit - %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity - %th.max{ 'ng-show' => 'columns.max.visible' } Max - %th.actions - %th.actions - Ask?  - %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } - %td.bulk - %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } - %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} - %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} - %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} - %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} - %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} - %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} - %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} - %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} - %td.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} - %td.quantity{ 'ng-show' => 'columns.quantity.visible' } - %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } - %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} - %td.actions - %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } - %td.actions - %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } - %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } \ No newline at end of file + %form{ 'ng-model' => "bulk_order_form" } + %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } + %thead + %tr + %th.bulk + %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } + %th.order_no{ 'ng-show' => 'columns.order_no.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } Order No. + %th.full_name{ 'ng-show' => 'columns.full_name.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } Name + %th.email{ 'ng-show' => 'columns.email.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } Email + %th.phone{ 'ng-show' => 'columns.phone.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } Phone + %th.date{ 'ng-show' => 'columns.order_date.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } Order Date + %th.producer{ 'ng-show' => 'columns.producer.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } Producer + %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } Order Cycle + %th.hub{ 'ng-show' => 'columns.hub.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub + %th.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit + %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity + %th.max{ 'ng-show' => 'columns.max.visible' } Max + %th.unit_value{ 'ng-show' => 'columns.unit_value.visible' } Weight/Volume + %th.price{ 'ng-show' => 'columns.price.visible' } Price + %th.actions + %th.actions + Ask?  + %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } + %td.bulk + %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } + %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} + %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} + %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} + %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} + %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} + %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} + %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} + %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} + %td.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} + %td.quantity{ 'ng-show' => 'columns.quantity.visible' } + %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "quantity" } + %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} + %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } + %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "unit_value" } + %td.price{ 'ng-show' => 'columns.price.visible' } + %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "price" } + %td.actions + %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } + %td.actions + %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } + %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } From b16fa9cdc14e76494a150e10936800dff8b0c579 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 15 Apr 2015 11:29:51 +1000 Subject: [PATCH 075/206] Generalising datasubmitter pendingChanges and watcher directive --- .../directives/line_item_upd_attr.js.coffee | 46 +++++++++++------ .../admin/services/data_submitter.js.coffee | 24 +++++---- .../admin/services/pending_changes.js.coffee | 49 +++++++++---------- 3 files changed, 66 insertions(+), 53 deletions(-) diff --git a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee index a33fa6a1b9..a1652950e7 100644 --- a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee +++ b/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee @@ -1,18 +1,36 @@ angular.module("ofn.admin").directive "ofnLineItemUpdAttr", (switchClass, pendingChanges) -> scope: - lineItem: "&ofnLineItemUpdAttr" - attrName: "@" + object: "&ofnLineItemUpdAttr" + type: "@ofnLineItemUpdAttr" + attr: "@attrName" link: (scope, element, attrs) -> - element.dbValue = scope.lineItem()[scope.attrName] - scope.$watch "lineItem().#{scope.attrName}", (value) -> - if value == element.dbValue - pendingChanges.remove(scope.lineItem().id, scope.attrName) - switchClass( element, "", ["update-pending", "update-error", "update-success"], false ) + scope.savedValue = scope.object()[scope.attr] + + scope.$watch "object().#{scope.attr}", (value) -> + if value == scope.savedValue + pendingChanges.remove(scope.object().id, scope.attr) + scope.clear() else - changeObj = - lineItem: scope.lineItem() - element: element - attrName: scope.attrName - url: "/api/orders/#{scope.lineItem().order.number}/line_items/#{scope.lineItem().id}?line_item[#{scope.attrName}]=#{value}" - pendingChanges.add(scope.lineItem().id, scope.attrName, changeObj) - switchClass( element, "update-pending", ["update-error", "update-success"], false ) + change = + object: scope.object() + type: scope.type + attr: scope.attr + value: value + scope: scope + scope.pending() + pendingChanges.add(scope.object().id, scope.attr, change) + + scope.reset = (value) -> + scope.savedValue = value + + scope.success = -> + switchClass( element, "update-success", ["update-pending", "update-error"], 3000 ) + + scope.pending = -> + switchClass( element, "update-pending", ["update-error", "update-success"], false ) + + scope.error = -> + switchClass( element, "update-error", ["update-pending", "update-success"], false ) + + scope.clear = -> + switchClass( element, "", ["update-pending", "update-error", "update-success"], false ) diff --git a/app/assets/javascripts/admin/services/data_submitter.js.coffee b/app/assets/javascripts/admin/services/data_submitter.js.coffee index 7d121ec645..e6496a1303 100644 --- a/app/assets/javascripts/admin/services/data_submitter.js.coffee +++ b/app/assets/javascripts/admin/services/data_submitter.js.coffee @@ -1,13 +1,11 @@ -angular.module("ofn.admin").factory "dataSubmitter", [ - "$http", "$q", "switchClass" - ($http, $q, switchClass) -> - return (changeObj) -> - deferred = $q.defer() - $http.put(changeObj.url).success((data) -> - switchClass changeObj.element, "update-success", ["update-pending", "update-error"], 3000 - deferred.resolve data - ).error -> - switchClass changeObj.element, "update-error", ["update-pending", "update-success"], false - deferred.reject() - deferred.promise -] \ No newline at end of file +angular.module("ofn.admin").factory "dataSubmitter", ($http, $q) -> + return (change) -> + deferred = $q.defer() + url = "/api/orders/#{change.object.order.number}/line_items/#{change.object.id}?line_item[#{change.attr}]=#{change.value}" + $http.put(url).success((data) -> + change.scope.success() + deferred.resolve data + ).error -> + change.scope.error() + deferred.reject() + deferred.promise diff --git a/app/assets/javascripts/admin/services/pending_changes.js.coffee b/app/assets/javascripts/admin/services/pending_changes.js.coffee index d72a4ac7bc..64a463eb62 100644 --- a/app/assets/javascripts/admin/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/services/pending_changes.js.coffee @@ -1,32 +1,29 @@ -angular.module("ofn.admin").factory "pendingChanges",[ - "dataSubmitter" - (dataSubmitter) -> - pendingChanges: {} +angular.module("ofn.admin").factory "pendingChanges", (dataSubmitter) -> + pendingChanges: {} - add: (id, attrName, changeObj) -> - @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}") - @pendingChanges["#{id}"]["#{attrName}"] = changeObj + add: (id, attr, change) -> + @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}") + @pendingChanges["#{id}"]["#{attr}"] = change - removeAll: -> - @pendingChanges = {} + removeAll: -> + @pendingChanges = {} - remove: (id, attrName) -> - if @pendingChanges.hasOwnProperty("#{id}") - delete @pendingChanges["#{id}"]["#{attrName}"] - delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 + remove: (id, attr) -> + if @pendingChanges.hasOwnProperty("#{id}") + delete @pendingChanges["#{id}"]["#{attr}"] + delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 - submitAll: -> - all = [] - for id,lineItem of @pendingChanges - for attrName,changeObj of lineItem - all.push @submit(id, attrName, changeObj) - all + submitAll: -> + all = [] + for id, objectChanges of @pendingChanges + for attrName, change of objectChanges + all.push @submit(change) + all - submit: (id, attrName, change) -> - dataSubmitter(change).then (data) => - @remove id, attrName - change.element.dbValue = data["#{attrName}"] + submit: (change) -> + dataSubmitter(change).then (data) => + @remove change.object.id, change.attr + change.scope.reset( data["#{change.attr}"] ) - changeCount: (lineItem) -> - Object.keys(lineItem).length -] \ No newline at end of file + changeCount: (objectChanges) -> + Object.keys(objectChanges).length From 57dbc33a7be52b5c4fbe45070e78cd0b399882a4 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 16 Apr 2015 08:44:24 +1000 Subject: [PATCH 076/206] Adding resource service to determine submission request based on object type --- .../admin/services/data_submitter.js.coffee | 7 +++--- .../admin/services/resources.js.coffee | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/admin/services/resources.js.coffee diff --git a/app/assets/javascripts/admin/services/data_submitter.js.coffee b/app/assets/javascripts/admin/services/data_submitter.js.coffee index e6496a1303..a9b917aae2 100644 --- a/app/assets/javascripts/admin/services/data_submitter.js.coffee +++ b/app/assets/javascripts/admin/services/data_submitter.js.coffee @@ -1,11 +1,10 @@ -angular.module("ofn.admin").factory "dataSubmitter", ($http, $q) -> +angular.module("ofn.admin").factory "dataSubmitter", ($http, $q, resources) -> return (change) -> deferred = $q.defer() - url = "/api/orders/#{change.object.order.number}/line_items/#{change.object.id}?line_item[#{change.attr}]=#{change.value}" - $http.put(url).success((data) -> + resources.update(change).$promise.then (data) -> change.scope.success() deferred.resolve data - ).error -> + , -> change.scope.error() deferred.reject() deferred.promise diff --git a/app/assets/javascripts/admin/services/resources.js.coffee b/app/assets/javascripts/admin/services/resources.js.coffee new file mode 100644 index 0000000000..337f1c2601 --- /dev/null +++ b/app/assets/javascripts/admin/services/resources.js.coffee @@ -0,0 +1,22 @@ +angular.module("ofn.admin").factory "resources", ($resource) -> + LineItem = $resource '/api/orders/:order_number/line_items/:line_item_id.json', + { order_number: '@order_cycle_id', line_item_id: '@line_item_id'}, + 'update': { method: 'PUT' } + + return { + update: (change) -> + params = {} + data = {} + resource = null + + switch change.type + when "line_item" + resource = LineItem; + params.order_number = change.object.order.number + params.line_item_id = change.object.id + data.line_item = {} + data.line_item[change.attr] = change.value + else "" + + resource.update(params, data) + } From 37ff61d663fd69de8e0ac96c8f8122b52ae66585 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sat, 2 May 2015 19:51:54 +1000 Subject: [PATCH 077/206] Adding basic routing to display customer index page --- app/controllers/admin/customers_controller.rb | 4 ++++ app/models/spree/ability_decorator.rb | 2 ++ app/views/admin/customers/index.html.haml | 1 + config/routes.rb | 2 ++ spec/models/spree/ability_spec.rb | 8 ++++++++ 5 files changed, 17 insertions(+) create mode 100644 app/controllers/admin/customers_controller.rb create mode 100644 app/views/admin/customers/index.html.haml diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb new file mode 100644 index 0000000000..d663b4f0ea --- /dev/null +++ b/app/controllers/admin/customers_controller.rb @@ -0,0 +1,4 @@ +module Admin + class CustomersController < ResourceController + end +end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index b8539979c2..b5ad4ec07b 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -185,6 +185,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], :report + + can [:admin, :index], Customer end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml new file mode 100644 index 0000000000..95b841ac50 --- /dev/null +++ b/app/views/admin/customers/index.html.haml @@ -0,0 +1 @@ +Customers diff --git a/config/routes.rb b/config/routes.rb index 4621ee4a35..aa49f225dc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,6 +79,8 @@ Openfoodnetwork::Application.routes.draw do resources :variant_overrides do post :bulk_update, on: :collection end + + resources :customers, only: [:index] end namespace :api do diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 0dc9364967..8ff1054f19 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -220,6 +220,10 @@ module Spree should_not have_ability([:sales_total, :group_buys, :payments, :orders_and_distributors, :users_and_enterprises], for: :report) end + it "should not be able to list customers" do + should_not have_ability([:admin, :index], for: Customer) + end + describe "order_cycles abilities" do context "where the enterprise is not in an order_cycle" do let!(:order_cycle) { create(:simple_order_cycle) } @@ -407,6 +411,10 @@ module Spree should_not have_ability([:sales_total, :users_and_enterprises], for: :report) end + it "should be able to list customers" do + should have_ability([:admin, :index], for: Customer) + end + context "for a given order_cycle" do let!(:order_cycle) { create(:simple_order_cycle) } let!(:exchange){ create(:exchange, incoming: false, order_cycle: order_cycle, receiver: d1, sender: order_cycle.coordinator) } From 220f42fcf2572382f22fc170be49e08d84916696 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 19:15:38 +1000 Subject: [PATCH 078/206] Customers controller serializes data for json requests, just renders view without data for html --- app/controllers/admin/customers_controller.rb | 24 +++++++ .../spree/admin/base_controller_decorator.rb | 4 ++ .../api/admin/customer_serializer.rb | 3 + .../admin/customers_controller_spec.rb | 62 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 app/serializers/api/admin/customer_serializer.rb create mode 100644 spec/controllers/admin/customers_controller_spec.rb diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index d663b4f0ea..b14ca38296 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -1,4 +1,28 @@ module Admin class CustomersController < ResourceController + before_filter :load_managed_shops, only: :index, if: :html_request? + + def index + respond_to do |format| + format.html + format.json do + render json: ActiveModel::ArraySerializer.new( @collection, + each_serializer: Api::Admin::CustomerSerializer, spree_current_user: spree_current_user + ).to_json + end + end + end + + private + + def collection + return Customer.where("1=0") if html_request? || params[:enterprise_id].nil? + enterprise = Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id]) + Customer.of(enterprise) + end + + def load_managed_shops + @shops = Enterprise.managed_by(spree_current_user).is_distributor + end end end diff --git a/app/controllers/spree/admin/base_controller_decorator.rb b/app/controllers/spree/admin/base_controller_decorator.rb index 85904590c3..3fa6a5c5e1 100644 --- a/app/controllers/spree/admin/base_controller_decorator.rb +++ b/app/controllers/spree/admin/base_controller_decorator.rb @@ -58,4 +58,8 @@ Spree::Admin::BaseController.class_eval do "Until you set these up, customers will not be able to shop at this hub." end end + + def html_request? + request.format.html? + end end diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb new file mode 100644 index 0000000000..84f32b6e8b --- /dev/null +++ b/app/serializers/api/admin/customer_serializer.rb @@ -0,0 +1,3 @@ +class Api::Admin::CustomerSerializer < ActiveModel::Serializer + attributes :id, :email, :enterprise_id, :user_id, :code +end diff --git a/spec/controllers/admin/customers_controller_spec.rb b/spec/controllers/admin/customers_controller_spec.rb new file mode 100644 index 0000000000..ee23b90552 --- /dev/null +++ b/spec/controllers/admin/customers_controller_spec.rb @@ -0,0 +1,62 @@ +describe Admin::CustomersController, type: :controller do + include AuthenticationWorkflow + + describe "index" do + let(:enterprise) { create(:distributor_enterprise) } + let(:another_enterprise) { create(:distributor_enterprise) } + + context "html" do + before do + controller.stub spree_current_user: enterprise.owner + end + + it "returns an empty @collection" do + spree_get :index, format: :html + expect(assigns(:collection)).to eq [] + end + end + + context "json" do + let!(:customer) { create(:customer, enterprise: enterprise) } + + context "where I manage the enterprise" do + before do + controller.stub spree_current_user: enterprise.owner + end + + context "and enterprise_id is given in params" do + let(:params) { { format: :json, enterprise_id: enterprise.id } } + + it "scopes @collection to customers of that enterprise" do + spree_get :index, params + expect(assigns(:collection)).to eq [customer] + end + + it "serializes the data" do + expect(ActiveModel::ArraySerializer).to receive(:new) + spree_get :index, params + end + end + + context "and enterprise_id is not given in params" do + it "returns an empty collection" do + spree_get :index, format: :json + expect(assigns(:collection)).to eq [] + end + end + end + + context "and I do not manage the enterprise" do + before do + controller.stub spree_current_user: another_enterprise.owner + end + + it "returns an empty collection" do + spree_get :index, format: :json + expect(assigns(:collection)).to eq [] + end + end + end + + end +end From b6d63d40c6d877ce39f2c2b48f9ef289db1cb3ae Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 22:35:43 +1000 Subject: [PATCH 079/206] Pulling columns logic out into its own services/controller in a new indexUtils module --- .../controllers/columns_controller.js.coffee | 4 ++++ .../admin/index_utils/index_utils.js.coffee | 1 + .../index_utils/services/columns.js.coffee | 8 ++++++++ .../columns_controller_spec.js.coffee | 17 +++++++++++++++++ .../index_utils/services/columns.js.coffee | 15 +++++++++++++++ 5 files changed, 45 insertions(+) create mode 100644 app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee create mode 100644 app/assets/javascripts/admin/index_utils/index_utils.js.coffee create mode 100644 app/assets/javascripts/admin/index_utils/services/columns.js.coffee create mode 100644 spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee create mode 100644 spec/javascripts/unit/admin/index_utils/services/columns.js.coffee diff --git a/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee b/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee new file mode 100644 index 0000000000..39556983b3 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee @@ -0,0 +1,4 @@ +angular.module("admin.indexUtils").controller "ColumnsCtrl", ($scope, Columns) -> + $scope.columns = Columns.columns + $scope.predicate = "" + $scope.reverse = false diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee new file mode 100644 index 0000000000..eef7b0aa5f --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee @@ -0,0 +1 @@ +angular.module("admin.indexUtils", []) \ No newline at end of file diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee new file mode 100644 index 0000000000..46e7ccd3b8 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.indexUtils").factory 'Columns', -> + new class Columns + columns: {} + + setColumns: (columns) -> + @columns = {} + @columns[name] = column for name, column of columns + @columns diff --git a/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee new file mode 100644 index 0000000000..5fd79e71bf --- /dev/null +++ b/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee @@ -0,0 +1,17 @@ +describe "ColumnsCtrl", -> + ctrl = null + scope = null + Columns = null + + beforeEach -> + Columns = { columns: { name: { visible: true} } } + + module('admin.indexUtils') + inject ($controller, $rootScope) -> + scope = $rootScope + ctrl = $controller 'ColumnsCtrl', {$scope: scope, Columns: Columns} + + it "initialises data", -> + expect(scope.columns).toEqual Columns.columns + expect(scope.predicate).toEqual "" + expect(scope.reverse).toEqual false diff --git a/spec/javascripts/unit/admin/index_utils/services/columns.js.coffee b/spec/javascripts/unit/admin/index_utils/services/columns.js.coffee new file mode 100644 index 0000000000..0b6093f31d --- /dev/null +++ b/spec/javascripts/unit/admin/index_utils/services/columns.js.coffee @@ -0,0 +1,15 @@ +describe "Columns service", -> + Columns = null + + beforeEach -> + module 'admin.indexUtils' + + inject (_Columns_) -> + Columns = _Columns_ + + Columns.columns = ["something"] + + describe "setting columns", -> + it "sets resets @columns and copies each column of the provided object across", -> + Columns.setColumns({ name: { visible: true } }) + expect(Columns.columns).toEqual { name: { visible: true } } From b3d314cfdb19e136dc7bd70b6ef6d7c043497478 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 22:37:00 +1000 Subject: [PATCH 080/206] Adding require line for index_utils module to all.js --- app/assets/javascripts/admin/all.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 9a07309352..4dfc52395c 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -20,6 +20,7 @@ //= require ./admin //= require ./enterprises/enterprises //= require ./enterprise_groups/enterprise_groups +//= require ./index_utils/index_utils //= require ./payment_methods/payment_methods //= require ./products/products //= require ./shipping_methods/shipping_methods From c00c93816c8e2fa7a5faa687d258c1ee2c9f3530 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 6 May 2015 22:58:58 +1000 Subject: [PATCH 081/206] Renaming spec file --- .../services/{columns.js.coffee => columns_spec.js.coffee} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/javascripts/unit/admin/index_utils/services/{columns.js.coffee => columns_spec.js.coffee} (100%) diff --git a/spec/javascripts/unit/admin/index_utils/services/columns.js.coffee b/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee similarity index 100% rename from spec/javascripts/unit/admin/index_utils/services/columns.js.coffee rename to spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee From 307302038aebf87925122576eabba2159fc94da6 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 15:13:21 +1000 Subject: [PATCH 082/206] Adding customers controller, service and resource --- app/assets/javascripts/admin/all.js | 1 + .../customers_controller.js.coffee | 13 ++++++++ .../admin/customers/customers.js.coffee | 1 + .../services/customer_resource.js.coffee | 8 +++++ .../customers/services/customers.js.coffee | 14 +++++++++ .../customers_controller_spec.js.coffee | 25 +++++++++++++++ .../services/customers_spec.js.coffee | 31 +++++++++++++++++++ 7 files changed, 93 insertions(+) create mode 100644 app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee create mode 100644 app/assets/javascripts/admin/customers/customers.js.coffee create mode 100644 app/assets/javascripts/admin/customers/services/customer_resource.js.coffee create mode 100644 app/assets/javascripts/admin/customers/services/customers.js.coffee create mode 100644 spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee create mode 100644 spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 4dfc52395c..03999b3cf0 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -18,6 +18,7 @@ //= require admin/spree_paypal_express //= require ../shared/ng-infinite-scroll.min.js //= require ./admin +//= require ./customers/customers //= require ./enterprises/enterprises //= require ./enterprise_groups/enterprise_groups //= require ./index_utils/index_utils diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee new file mode 100644 index 0000000000..6bee8f993e --- /dev/null +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -0,0 +1,13 @@ +angular.module("admin.customers").controller "customersCtrl", ($scope, Customers, Columns, shops) -> + $scope.shop = null + $scope.shops = shops + + $scope.columns = Columns.setColumns + email: { name: "Email", visible: true } + code: { name: "Code", visible: true } + + $scope.initialise = -> + $scope.customers = Customers.index(enterprise_id: $scope.shop.id) + + $scope.loaded = -> + Customers.loaded diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee new file mode 100644 index 0000000000..66be0e78a8 --- /dev/null +++ b/app/assets/javascripts/admin/customers/customers.js.coffee @@ -0,0 +1 @@ +angular.module("admin.customers", ['ngResource', 'admin.indexUtils']) \ No newline at end of file diff --git a/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee b/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee new file mode 100644 index 0000000000..523e0c1495 --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.customers").factory 'CustomerResource', ($resource) -> + $resource('/admin/customers.json', {}, { + 'index': + method: 'GET' + isArray: true + params: + enterprise_id: '@enterprise_id' + }) diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee new file mode 100644 index 0000000000..2be0cfe8e8 --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/customers.js.coffee @@ -0,0 +1,14 @@ +angular.module("admin.customers").factory 'Customers', (CustomerResource) -> + new class Customers + customers: {} + loaded: false + + index: (params={}, callback=null) -> + CustomerResource.index params, (data) => + for customer in data + @customers[customer.id] = customer + + @loaded = true + (callback || angular.noop)(@customers) + + @customers diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee new file mode 100644 index 0000000000..ced19c8b33 --- /dev/null +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -0,0 +1,25 @@ +describe "CustomersCtrl", -> + ctrl = null + scope = null + Customers = null + + beforeEach -> + shops = "list of shops" + + module('admin.customers') + inject ($controller, $rootScope, _Customers_) -> + scope = $rootScope + Customers = _Customers_ + ctrl = $controller 'customersCtrl', {$scope: scope, Customers: Customers, shops: shops} + + describe "initialise()", -> + beforeEach -> + spyOn(Customers, "index").andReturn "list of customers" + scope.shop = {id: 1} + scope.initialise() + + it "calls Customers#index with the correct params", -> + expect(Customers.index).toHaveBeenCalledWith({enterprise_id: 1}) + + it "resets $scope.customers with the result of Customers#index", -> + expect(scope.customers).toEqual "list of customers" diff --git a/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee new file mode 100644 index 0000000000..04779e3247 --- /dev/null +++ b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee @@ -0,0 +1,31 @@ +describe "Customers service", -> + Customers = CustomerResource = customers = $httpBackend = null + + beforeEach -> + module 'admin.customers' + + inject ($q, _$httpBackend_, _Customers_, _CustomerResource_) -> + Customers = _Customers_ + CustomerResource = _CustomerResource_ + $httpBackend = _$httpBackend_ + $httpBackend.expectGET('/admin/customers.json?enterprise_id=2').respond 200, [{ id: 5, email: 'someone@email.com'}] + + describe "#index", -> + result = null + + beforeEach -> + expect(Customers.loaded).toBe false + result = Customers.index(enterprise_id: 2) + $httpBackend.flush() + + it "stores returned data in @customers, with ids as keys", -> + # This is super weird and freaking annoying. I think resource results have extra + # properties ($then, $promise) that cause them to not be equal to the reponse object + # provided to the expectGET clause above. + expect(Customers.customers).toEqual { 5: new CustomerResource({ id: 5, email: 'someone@email.com'}) } + + it "returns @customers", -> + expect(result).toEqual Customers.customers + + it "sets @loaded to true", -> + expect(Customers.loaded).toBe true From 7a4f0e214cc10094775bd6a98742d01adcbe70e6 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 16:24:58 +1000 Subject: [PATCH 083/206] Adding customers index view --- app/helpers/admin/injection_helper.rb | 4 +++ app/views/admin/customers/index.html.haml | 34 +++++++++++++++++++++- spec/factories.rb | 2 +- spec/features/admin/customers_spec.rb | 35 +++++++++++++++++++++++ spec/support/matchers/select2_matchers.rb | 9 ++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 spec/features/admin/customers_spec.rb diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 36ddccdb9a..70587863fd 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -25,6 +25,10 @@ module Admin admin_inject_json_ams_array "admin.shipping_methods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer end + def admin_inject_shops + admin_inject_json_ams_array "admin.customers", "shops", @shops, Api::Admin::IdNameSerializer + end + def admin_inject_hubs admin_inject_json_ams_array "ofn.admin", "hubs", @hubs, Api::Admin::IdNameSerializer end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 95b841ac50..7bd45ee4c3 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -1 +1,33 @@ -Customers += admin_inject_shops + +%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } } + .row{ ng: { hide: "loaded()" } } + .two.columns.alpha + Hub + .four.columns + %select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' } + .ten.columns.omega + %input{ type: 'button', value: 'Go', ng: { click: 'initialise()' } } + + .row{ ng: { show: "loaded()" } } + %form{ name: "customers" } + %table.index#customers{ :class => "sixteen columns alpha" } + %thead + %tr{ ng: { controller: "ColumnsCtrl" } } + %th.bulk + %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } + %th.email{ 'ng-show' => 'columns.email.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" } Email + %th.code{ 'ng-show' => 'columns.code.visible' } + %a{ :href => '', 'ng-click' => "predicate = 'customer.code'; reverse = !reverse" } Code + %th.actions + Ask?  + %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } + %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{id}}" } + %td.bulk + %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } + %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} + %td.code{ 'ng-show' => 'columns.code.visible' } {{ customer.code }} + %td.actions + %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } + -# %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } diff --git a/spec/factories.rb b/spec/factories.rb index c798999134..7d09115a12 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -208,7 +208,7 @@ FactoryGirl.define do factory :customer, :class => Customer do email { Faker::Internet.email } enterprise - code 'abc123' + code { Faker::Lorem.word } user end end diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb new file mode 100644 index 0000000000..3cc46e165d --- /dev/null +++ b/spec/features/admin/customers_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +feature 'Customers' do + include AuthenticationWorkflow + include WebHelper + + context "as an enterprise user" do + let(:user) { create_enterprise_user } + let(:managed_distributor) { create(:distributor_enterprise, owner: user) } + let(:unmanaged_distributor) { create(:distributor_enterprise) } + + describe "using the customers index" do + let!(:customer1) { create(:customer, enterprise: managed_distributor) } + let!(:customer2) { create(:customer, enterprise: unmanaged_distributor) } + + before do + quick_login_as user + visit admin_customers_path + end + + it "lists my customers", js: true do + # Prompts for a hub + expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] + + select2_select managed_distributor.name, from: "shop_id" + click_button "Go" + + expect(page).to have_content customer1.email + expect(page).to have_content customer1.code + expect(page).to_not have_content customer2.email + expect(page).to_not have_content customer2.code + end + end + end +end diff --git a/spec/support/matchers/select2_matchers.rb b/spec/support/matchers/select2_matchers.rb index 67cfcd81d0..cf67c7c7cb 100644 --- a/spec/support/matchers/select2_matchers.rb +++ b/spec/support/matchers/select2_matchers.rb @@ -21,6 +21,7 @@ RSpec::Matchers.define :have_select2 do |id, options={}| if results.all? results << all_options_present(from, options[:with_options]) if options.key? :with_options results << exact_options_present(from, options[:options]) if options.key? :options + results << no_options_present(from, options[:without_options]) if options.key? :without_options end results.all? @@ -51,6 +52,14 @@ RSpec::Matchers.define :have_select2 do |id, options={}| end end + def no_options_present(from, options) + with_select2_open(from) do + options.none? do |option| + @node.has_selector? "div.select2-drop-active ul.select2-results li", text: option + end + end + end + def selected_option_is(from, text) within find(from) do find("a.select2-choice").text == text From 1c03e27686bb334fdf3f2981a661cc617e52cc3e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 16:43:05 +1000 Subject: [PATCH 084/206] Moving dropdown to its own folder --- app/assets/javascripts/admin/{ => dropdown}/dropdown.js.coffee | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/assets/javascripts/admin/{ => dropdown}/dropdown.js.coffee (100%) diff --git a/app/assets/javascripts/admin/dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/dropdown.js.coffee similarity index 100% rename from app/assets/javascripts/admin/dropdown.js.coffee rename to app/assets/javascripts/admin/dropdown/dropdown.js.coffee From f5c0ae0f4192f3550502e558653a7eef37a5d54c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 16:48:37 +1000 Subject: [PATCH 085/206] Moving dropdown to its own module folder and renaming --- app/assets/javascripts/admin/admin.js.coffee | 2 +- app/assets/javascripts/admin/all.js | 1 + .../controllers/dropdown_controller.js.coffee | 2 ++ .../directives/close_on_click.js.coffee | 5 +++ .../dropdown/directives/dropdown.js.coffee | 20 ++++++++++++ .../admin/dropdown/dropdown.js.coffee | 32 +------------------ 6 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 app/assets/javascripts/admin/dropdown/controllers/dropdown_controller.js.coffee create mode 100644 app/assets/javascripts/admin/dropdown/directives/close_on_click.js.coffee create mode 100644 app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee diff --git a/app/assets/javascripts/admin/admin.js.coffee b/app/assets/javascripts/admin/admin.js.coffee index 1c9f65f91a..8a3de223fd 100644 --- a/app/assets/javascripts/admin/admin.js.coffee +++ b/app/assets/javascripts/admin/admin.js.coffee @@ -1,3 +1,3 @@ -angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products", "admin.taxons", "infinite-scroll"]).config ($httpProvider) -> +angular.module("ofn.admin", ["ngResource", "ngAnimate", "admin.dropdown", "admin.products", "admin.taxons", "infinite-scroll"]).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content") $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*" diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 03999b3cf0..f6d016dc9a 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -19,6 +19,7 @@ //= require ../shared/ng-infinite-scroll.min.js //= require ./admin //= require ./customers/customers +//= require ./dropdown/dropdown //= require ./enterprises/enterprises //= require ./enterprise_groups/enterprise_groups //= require ./index_utils/index_utils diff --git a/app/assets/javascripts/admin/dropdown/controllers/dropdown_controller.js.coffee b/app/assets/javascripts/admin/dropdown/controllers/dropdown_controller.js.coffee new file mode 100644 index 0000000000..02e47ff9f7 --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/controllers/dropdown_controller.js.coffee @@ -0,0 +1,2 @@ +angular.module("admin.dropdown").controller "DropDownCtrl", ($scope) -> + $scope.expanded = false diff --git a/app/assets/javascripts/admin/dropdown/directives/close_on_click.js.coffee b/app/assets/javascripts/admin/dropdown/directives/close_on_click.js.coffee new file mode 100644 index 0000000000..9b506cb8fb --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/directives/close_on_click.js.coffee @@ -0,0 +1,5 @@ + angular.module("admin.dropdown").directive "ofnCloseOnClick", ($document) -> + link: (scope, element, attrs) -> + element.click (event) -> + event.stopPropagation() + scope.$emit "offClick" diff --git a/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee new file mode 100644 index 0000000000..560598d23e --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee @@ -0,0 +1,20 @@ + angular.module("admin.dropdown").directive "ofnDropDown", ($document) -> + link: (scope, element, attrs) -> + outsideClickListener = (event) -> + unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") || + $(event.target).parents("div.ofn_drop_down##{attrs.id} div.menu").length > 0 + scope.$emit "offClick" + + element.click (event) -> + if !scope.expanded + event.stopPropagation() + scope.deregistrationCallback = scope.$on "offClick", -> + $document.off "click", outsideClickListener + scope.$apply -> + scope.expanded = false + element.removeClass "expanded" + scope.deregistrationCallback() + $document.on "click", outsideClickListener + scope.$apply -> + scope.expanded = true + element.addClass "expanded" diff --git a/app/assets/javascripts/admin/dropdown/dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/dropdown.js.coffee index e18407abcc..ae6118390f 100644 --- a/app/assets/javascripts/admin/dropdown/dropdown.js.coffee +++ b/app/assets/javascripts/admin/dropdown/dropdown.js.coffee @@ -1,31 +1 @@ -dropDownModule = angular.module("ofn.dropdown", []) - -dropDownModule.directive "ofnDropDown", ($document) -> - link: (scope, element, attrs) -> - outsideClickListener = (event) -> - unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") || - $(event.target).parents("div.ofn_drop_down##{attrs.id} div.menu").length > 0 - scope.$emit "offClick" - - element.click (event) -> - if !scope.expanded - event.stopPropagation() - scope.deregistrationCallback = scope.$on "offClick", -> - $document.off "click", outsideClickListener - scope.$apply -> - scope.expanded = false - element.removeClass "expanded" - scope.deregistrationCallback() - $document.on "click", outsideClickListener - scope.$apply -> - scope.expanded = true - element.addClass "expanded" - -dropDownModule.directive "ofnCloseOnClick", ($document) -> - link: (scope, element, attrs) -> - element.click (event) -> - event.stopPropagation() - scope.$emit "offClick" - -dropDownModule.controller "DropDownCtrl", ($scope) -> - $scope.expanded = false \ No newline at end of file +angular.module("admin.dropdown", []) From 5bd842fe1a26d3831ce89593341a569ac9c1d9ca Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 16:54:59 +1000 Subject: [PATCH 086/206] Moving toggle_column directive to dropdown module --- .../admin/{ => dropdown}/directives/toggle_column.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename app/assets/javascripts/admin/{ => dropdown}/directives/toggle_column.js.coffee (74%) diff --git a/app/assets/javascripts/admin/directives/toggle_column.js.coffee b/app/assets/javascripts/admin/dropdown/directives/toggle_column.js.coffee similarity index 74% rename from app/assets/javascripts/admin/directives/toggle_column.js.coffee rename to app/assets/javascripts/admin/dropdown/directives/toggle_column.js.coffee index 1b8487eeb1..2129562e78 100644 --- a/app/assets/javascripts/admin/directives/toggle_column.js.coffee +++ b/app/assets/javascripts/admin/dropdown/directives/toggle_column.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").directive "ofnToggleColumn", -> +angular.module("admin.dropdown").directive "ofnToggleColumn", -> link: (scope, element, attrs) -> element.addClass "selected" if scope.column.visible element.click "click", -> @@ -8,4 +8,4 @@ angular.module("ofn.admin").directive "ofnToggleColumn", -> element.removeClass "selected" else scope.column.visible = true - element.addClass "selected" \ No newline at end of file + element.addClass "selected" From 69ba8540c1559a1b56812d25fbd3e25a572d31ad Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 16:57:02 +1000 Subject: [PATCH 087/206] Moving toggle_column directive to index_utils --- .../directives/toggle_column.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename app/assets/javascripts/admin/{dropdown => index_utils}/directives/toggle_column.js.coffee (83%) diff --git a/app/assets/javascripts/admin/dropdown/directives/toggle_column.js.coffee b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee similarity index 83% rename from app/assets/javascripts/admin/dropdown/directives/toggle_column.js.coffee rename to app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee index 2129562e78..d7871421fa 100644 --- a/app/assets/javascripts/admin/dropdown/directives/toggle_column.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.dropdown").directive "ofnToggleColumn", -> +angular.module("admin.index_utils").directive "ofnToggleColumn", -> link: (scope, element, attrs) -> element.addClass "selected" if scope.column.visible element.click "click", -> From 3b7ab086c9362c6234461800b807247c34eebbdc Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 17:04:42 +1000 Subject: [PATCH 088/206] Fix module name --- .../admin/index_utils/directives/toggle_column.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee index d7871421fa..d6239ff47f 100644 --- a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.index_utils").directive "ofnToggleColumn", -> +angular.module("admin.indexUtils").directive "ofnToggleColumn", -> link: (scope, element, attrs) -> element.addClass "selected" if scope.column.visible element.click "click", -> From 3e7d331892a81dc759e0ec15bf1b8613b64e1f9f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 17:05:26 +1000 Subject: [PATCH 089/206] BOM uses columns controller from index utils module --- app/assets/javascripts/admin/admin.js.coffee | 2 +- .../admin/bulk_order_management.js.coffee | 8 ++- .../admin/orders/bulk_management.html.haml | 50 +++++++++---------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/admin/admin.js.coffee b/app/assets/javascripts/admin/admin.js.coffee index 8a3de223fd..ffe2c9eaaa 100644 --- a/app/assets/javascripts/admin/admin.js.coffee +++ b/app/assets/javascripts/admin/admin.js.coffee @@ -1,3 +1,3 @@ -angular.module("ofn.admin", ["ngResource", "ngAnimate", "admin.dropdown", "admin.products", "admin.taxons", "infinite-scroll"]).config ($httpProvider) -> +angular.module("ofn.admin", ["ngResource", "ngAnimate", "admin.indexUtils", "admin.dropdown", "admin.products", "admin.taxons", "infinite-scroll"]).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content") $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*" diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 4c1a319c1a..3cb33e8432 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -1,6 +1,6 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ - "$scope", "$http", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey" - ($scope, $http, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey) -> + "$scope", "$http", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns" + ($scope, $http, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) -> $scope.loading = true $scope.initialiseVariables = -> @@ -18,9 +18,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.selectedUnitsProduct = {}; $scope.selectedUnitsVariant = {}; $scope.sharedResource = false - $scope.predicate = "" - $scope.reverse = false - $scope.columns = + $scope.columns = Columns.setColumns order_no: { name: "Order No.", visible: false } full_name: { name: "Name", visible: true } email: { name: "Email", visible: false } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 406dc18046..c8ca7dd740 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -103,7 +103,7 @@ %form{ 'ng-model' => "bulk_order_form" } %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } %thead - %tr + %tr{ ng: { controller: "ColumnsCtrl" } } %th.bulk %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } %th.order_no{ 'ng-show' => 'columns.order_no.visible' } @@ -132,28 +132,28 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } - %td.bulk - %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } - %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} - %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} - %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} - %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} - %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} - %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} - %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} - %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} - %td.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} - %td.quantity{ 'ng-show' => 'columns.quantity.visible' } - %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "quantity" } - %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} - %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } - %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "unit_value" } - %td.price{ 'ng-show' => 'columns.price.visible' } - %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "price" } - %td.actions - %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } - %td.actions - %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } + %td.bulk + %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } + %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} + %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} + %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} + %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} + %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} + %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} + %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} + %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} + %td.variant{ 'ng-show' => 'columns.variant.visible' } + %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} + %td.quantity{ 'ng-show' => 'columns.quantity.visible' } + %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "quantity" } + %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} + %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } + %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "unit_value" } + %td.price{ 'ng-show' => 'columns.price.visible' } + %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "price" } + %td.actions + %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } + %td.actions + %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } From 79d50a64aec5b8a2ead190040f783fa715121dfc Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 17:08:44 +1000 Subject: [PATCH 090/206] BPE uses columns controller from intex utils --- app/assets/javascripts/admin/bulk_product_update.js.coffee | 4 ++-- .../spree/admin/products/bulk_edit/_products_head.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 8cac0abeda..422199ad28 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,9 +1,9 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth) -> +angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns) -> $scope.loading = true $scope.StatusMessage = StatusMessage - $scope.columns = + $scope.columns = Columns.setColumns producer: {name: "Producer", visible: true} sku: {name: "SKU", visible: false} name: {name: "Name", visible: true} diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 2e37da2bc8..88bccea61b 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -15,7 +15,7 @@ %col.actions %thead - %tr + %tr{ ng: { controller: "ColumnsCtrl" } } %th.left-actions %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer %th.sku{ 'ng-show' => 'columns.sku.visible' } SKU From 8f35ccf0072df6e2544504a5ae09fb095cc02438 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 7 May 2015 18:00:12 +1000 Subject: [PATCH 091/206] Adding quick search and column dropdown to customers index --- .../admin/customers/customers.js.coffee | 2 +- .../customers/services/customers.js.coffee | 6 ++-- app/views/admin/customers/index.html.haml | 30 ++++++++++++++++++- spec/features/admin/customers_spec.rb | 23 +++++++++++--- .../services/customers_spec.js.coffee | 2 +- 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee index 66be0e78a8..462b7d5818 100644 --- a/app/assets/javascripts/admin/customers/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/customers.js.coffee @@ -1 +1 @@ -angular.module("admin.customers", ['ngResource', 'admin.indexUtils']) \ No newline at end of file +angular.module("admin.customers", ['ngResource', 'admin.indexUtils', 'admin.dropdown']) \ No newline at end of file diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee index 2be0cfe8e8..9acfa317d2 100644 --- a/app/assets/javascripts/admin/customers/services/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/services/customers.js.coffee @@ -1,12 +1,14 @@ angular.module("admin.customers").factory 'Customers', (CustomerResource) -> new class Customers - customers: {} + customers: [] + customers_by_id: {} loaded: false index: (params={}, callback=null) -> CustomerResource.index params, (data) => for customer in data - @customers[customer.id] = customer + @customers.push customer + @customers_by_id[customer.id] = customer @loaded = true (callback || angular.noop)(@customers) diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 7bd45ee4c3..aef398c991 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -9,6 +9,34 @@ .ten.columns.omega %input{ type: 'button', value: 'Go', ng: { click: 'initialise()' } } + .row{ 'ng-hide' => '!loaded() || lineItems.length == 0' } + .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } + .three.columns.alpha + %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } + .three.columns   + -# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } + -# %span{ :class => 'icon-check' }   Actions + -# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + -# %div.menu{ 'ng-show' => "expanded" } + -# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true } + -# %span{ :class => 'three columns omega' } {{action.name }} + .seven.columns   + .three.columns.omega + %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } + %span{ :class => 'icon-reorder' }   Columns + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } + %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} + %span{ :class => 'two columns omega' } {{column.name }} + .row + .sixteen.columns.alpha#loading{ 'ng-if' => 'shop_id && !loaded()' } + %img.spinner{ src: "/assets/spinning-circles.svg" } + %h1 LOADING CUSTOMERS + .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded() && filteredCustomers.length == 0'} + %h1#no_results No customers found. + + .row{ ng: { show: "loaded()" } } %form{ name: "customers" } %table.index#customers{ :class => "sixteen columns alpha" } @@ -23,7 +51,7 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{id}}" } + %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 3cc46e165d..38af3145f3 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -11,7 +11,8 @@ feature 'Customers' do describe "using the customers index" do let!(:customer1) { create(:customer, enterprise: managed_distributor) } - let!(:customer2) { create(:customer, enterprise: unmanaged_distributor) } + let!(:customer2) { create(:customer, enterprise: managed_distributor) } + let!(:customer3) { create(:customer, enterprise: unmanaged_distributor) } before do quick_login_as user @@ -25,10 +26,24 @@ feature 'Customers' do select2_select managed_distributor.name, from: "shop_id" click_button "Go" + # Loads the right customers + expect(page).to have_selector "tr#c_#{customer1.id}" + expect(page).to have_selector "tr#c_#{customer2.id}" + expect(page).to_not have_selector "tr#c_#{customer3.id}" + + # Searching + fill_in "quick_search", with: customer2.email + expect(page).to_not have_selector "tr#c_#{customer1.id}" + expect(page).to have_selector "tr#c_#{customer2.id}" + fill_in "quick_search", with: "" + + # Toggling columns + expect(page).to have_selector "th.email" expect(page).to have_content customer1.email - expect(page).to have_content customer1.code - expect(page).to_not have_content customer2.email - expect(page).to_not have_content customer2.code + first("div#columns_dropdown", :text => "COLUMNS").click + first("div#columns_dropdown div.menu div.menu_item", text: "Email").click + expect(page).to_not have_selector "th.email" + expect(page).to_not have_content customer1.email end end end diff --git a/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee index 04779e3247..7123055d63 100644 --- a/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee @@ -22,7 +22,7 @@ describe "Customers service", -> # This is super weird and freaking annoying. I think resource results have extra # properties ($then, $promise) that cause them to not be equal to the reponse object # provided to the expectGET clause above. - expect(Customers.customers).toEqual { 5: new CustomerResource({ id: 5, email: 'someone@email.com'}) } + expect(Customers.customers).toEqual [ new CustomerResource({ id: 5, email: 'someone@email.com'}) ] it "returns @customers", -> expect(result).toEqual Customers.customers From 2afd501af3efc933f300bdcd3172b79bb5c9bcb5 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 10:38:02 +1000 Subject: [PATCH 092/206] Wiring up update action for customers controller --- app/models/spree/ability_decorator.rb | 2 +- config/routes.rb | 2 +- .../admin/customers_controller_spec.rb | 33 +++++++++++++++++++ spec/models/spree/ability_spec.rb | 8 ++--- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index b5ad4ec07b..e7e8d9feb8 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, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], :report - can [:admin, :index], Customer + can [:admin, :index, :update], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) end diff --git a/config/routes.rb b/config/routes.rb index aa49f225dc..e262813cb7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -80,7 +80,7 @@ Openfoodnetwork::Application.routes.draw do post :bulk_update, on: :collection end - resources :customers, only: [:index] + resources :customers, only: [:index, :update] end namespace :api do diff --git a/spec/controllers/admin/customers_controller_spec.rb b/spec/controllers/admin/customers_controller_spec.rb index ee23b90552..3fc5451b8b 100644 --- a/spec/controllers/admin/customers_controller_spec.rb +++ b/spec/controllers/admin/customers_controller_spec.rb @@ -57,6 +57,39 @@ describe Admin::CustomersController, type: :controller do end end end + end + describe "update" do + let(:enterprise) { create(:distributor_enterprise) } + let(:another_enterprise) { create(:distributor_enterprise) } + + context "json" do + let!(:customer) { create(:customer, enterprise: enterprise) } + + context "where I manage the customer's enterprise" do + before do + controller.stub spree_current_user: enterprise.owner + end + + it "allows me to update the customer" do + spree_put :update, format: :json, id: customer.id, customer: { email: 'new.email@gmail.com' } + expect(assigns(:customer)).to eq customer + expect(customer.reload.email).to eq 'new.email@gmail.com' + end + end + + context "where I don't manage the customer's enterprise" do + before do + controller.stub spree_current_user: another_enterprise.owner + end + + it "prevents me from updating the customer" do + spree_put :update, format: :json, id: customer.id, customer: { email: 'new.email@gmail.com' } + expect(response).to redirect_to spree.unauthorized_path + expect(assigns(:customer)).to eq nil + expect(customer.email).to_not eq 'new.email@gmail.com' + end + end + end end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 8ff1054f19..e35291ec8e 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -220,8 +220,8 @@ module Spree should_not have_ability([:sales_total, :group_buys, :payments, :orders_and_distributors, :users_and_enterprises], for: :report) end - it "should not be able to list customers" do - should_not have_ability([:admin, :index], for: Customer) + it "should not be able to access customer actions" do + should_not have_ability([:admin, :index, :update], for: Customer) end describe "order_cycles abilities" do @@ -411,8 +411,8 @@ module Spree should_not have_ability([:sales_total, :users_and_enterprises], for: :report) end - it "should be able to list customers" do - should have_ability([:admin, :index], for: Customer) + it "should be able to access customer actions" do + should have_ability([:admin, :index, :update], for: Customer) end context "for a given order_cycle" do From a87327888157388a61882b83e7a6247e7d4a2425 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 10:53:59 +1000 Subject: [PATCH 093/206] Moving line-item-upd-attr to indexUtils module, renaming to obj-for-update --- .../directives/obj_for_update.js.coffee} | 8 ++++---- app/views/spree/admin/orders/bulk_management.html.haml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) rename app/assets/javascripts/admin/{directives/line_item_upd_attr.js.coffee => index_utils/directives/obj_for_update.js.coffee} (85%) diff --git a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee b/app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee similarity index 85% rename from app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee rename to app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee index a1652950e7..254fa5c438 100644 --- a/app/assets/javascripts/admin/directives/line_item_upd_attr.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee @@ -1,8 +1,8 @@ -angular.module("ofn.admin").directive "ofnLineItemUpdAttr", (switchClass, pendingChanges) -> +angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendingChanges) -> scope: - object: "&ofnLineItemUpdAttr" - type: "@ofnLineItemUpdAttr" - attr: "@attrName" + object: "&objForUpdate" + type: "@objForUpdate" + attr: "@attrForUpdate" link: (scope, element, attrs) -> scope.savedValue = scope.object()[scope.attr] diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index c8ca7dd740..a2fc15e271 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -146,12 +146,12 @@ %td.variant{ 'ng-show' => 'columns.variant.visible' } %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} %td.quantity{ 'ng-show' => 'columns.quantity.visible' } - %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "quantity" } + %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'obj-for-update' => "line_item", "attr-for-update" => "quantity" } %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } - %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "unit_value" } + %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'obj-for-update' => "line_item", "attr-for-update" => "unit_value" } %td.price{ 'ng-show' => 'columns.price.visible' } - %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "line_item", "attr-name" => "price" } + %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-readonly' => "true", 'obj-for-update' => "line_item", "attr-for-update" => "price" } %td.actions %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } %td.actions From 8f94390363b92a80cd4bc1005cb3e02228ab07a8 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 11:12:20 +1000 Subject: [PATCH 094/206] Moving a bunch of index related services from general admin module into indexUtils --- .../admin/index_utils/index_utils.js.coffee | 2 +- .../services/data_submitter.js.coffee | 2 +- .../services/pending_changes.js.coffee | 2 +- .../{ => index_utils}/services/resources.js.coffee | 2 +- .../index_utils/services/switch_class.js.coffee | 10 ++++++++++ .../admin/services/switch_class.js.coffee | 13 ------------- 6 files changed, 14 insertions(+), 17 deletions(-) rename app/assets/javascripts/admin/{ => index_utils}/services/data_submitter.js.coffee (73%) rename app/assets/javascripts/admin/{ => index_utils}/services/pending_changes.js.coffee (91%) rename app/assets/javascripts/admin/{ => index_utils}/services/resources.js.coffee (89%) create mode 100644 app/assets/javascripts/admin/index_utils/services/switch_class.js.coffee delete mode 100644 app/assets/javascripts/admin/services/switch_class.js.coffee diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee index eef7b0aa5f..46fc050742 100644 --- a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee +++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee @@ -1 +1 @@ -angular.module("admin.indexUtils", []) \ No newline at end of file +angular.module("admin.indexUtils", ['ngResource']) \ No newline at end of file diff --git a/app/assets/javascripts/admin/services/data_submitter.js.coffee b/app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee similarity index 73% rename from app/assets/javascripts/admin/services/data_submitter.js.coffee rename to app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee index a9b917aae2..934ae4a07e 100644 --- a/app/assets/javascripts/admin/services/data_submitter.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "dataSubmitter", ($http, $q, resources) -> +angular.module("admin.indexUtils").factory "dataSubmitter", ($http, $q, resources) -> return (change) -> deferred = $q.defer() resources.update(change).$promise.then (data) -> diff --git a/app/assets/javascripts/admin/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee similarity index 91% rename from app/assets/javascripts/admin/services/pending_changes.js.coffee rename to app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 64a463eb62..f39a1b3ca6 100644 --- a/app/assets/javascripts/admin/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "pendingChanges", (dataSubmitter) -> +angular.module("admin.indexUtils").factory "pendingChanges", (dataSubmitter) -> pendingChanges: {} add: (id, attr, change) -> diff --git a/app/assets/javascripts/admin/services/resources.js.coffee b/app/assets/javascripts/admin/index_utils/services/resources.js.coffee similarity index 89% rename from app/assets/javascripts/admin/services/resources.js.coffee rename to app/assets/javascripts/admin/index_utils/services/resources.js.coffee index 337f1c2601..c14744701b 100644 --- a/app/assets/javascripts/admin/services/resources.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/resources.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "resources", ($resource) -> +angular.module("admin.indexUtils").factory "resources", ($resource) -> LineItem = $resource '/api/orders/:order_number/line_items/:line_item_id.json', { order_number: '@order_cycle_id', line_item_id: '@line_item_id'}, 'update': { method: 'PUT' } diff --git a/app/assets/javascripts/admin/index_utils/services/switch_class.js.coffee b/app/assets/javascripts/admin/index_utils/services/switch_class.js.coffee new file mode 100644 index 0000000000..c2a3419e2c --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/services/switch_class.js.coffee @@ -0,0 +1,10 @@ +angular.module("admin.indexUtils").factory "switchClass", ($timeout) -> + return (element,classToAdd,removeClasses,timeout) -> + $timeout.cancel element.timeout if element.timeout + element.removeClass className for className in removeClasses + element.addClass classToAdd + intRegex = /^\d+$/ + if timeout && intRegex.test(timeout) + element.timeout = $timeout(-> + element.removeClass classToAdd + , timeout, true) diff --git a/app/assets/javascripts/admin/services/switch_class.js.coffee b/app/assets/javascripts/admin/services/switch_class.js.coffee deleted file mode 100644 index e39c52d1f6..0000000000 --- a/app/assets/javascripts/admin/services/switch_class.js.coffee +++ /dev/null @@ -1,13 +0,0 @@ -angular.module("ofn.admin").factory "switchClass", [ - "$timeout" - ($timeout) -> - return (element,classToAdd,removeClasses,timeout) -> - $timeout.cancel element.timeout if element.timeout - element.removeClass className for className in removeClasses - element.addClass classToAdd - intRegex = /^\d+$/ - if timeout && intRegex.test(timeout) - element.timeout = $timeout(-> - element.removeClass classToAdd - , timeout, true) -] \ No newline at end of file From 3890ba9a11f88527c28638c418b94bcf4d01f744 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 12:34:41 +1000 Subject: [PATCH 095/206] Wrapping pending changes service in a class --- .../services/pending_changes.js.coffee | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index f39a1b3ca6..86a6b2821f 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -1,29 +1,30 @@ angular.module("admin.indexUtils").factory "pendingChanges", (dataSubmitter) -> - pendingChanges: {} + new class pendingChanges + pendingChanges: {} - add: (id, attr, change) -> - @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}") - @pendingChanges["#{id}"]["#{attr}"] = change + add: (id, attr, change) => + @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}") + @pendingChanges["#{id}"]["#{attr}"] = change - removeAll: -> - @pendingChanges = {} + removeAll: => + @pendingChanges = {} - remove: (id, attr) -> - if @pendingChanges.hasOwnProperty("#{id}") - delete @pendingChanges["#{id}"]["#{attr}"] - delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 + remove: (id, attr) => + if @pendingChanges.hasOwnProperty("#{id}") + delete @pendingChanges["#{id}"]["#{attr}"] + delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 - submitAll: -> - all = [] - for id, objectChanges of @pendingChanges - for attrName, change of objectChanges - all.push @submit(change) - all + submitAll: => + all = [] + for id, objectChanges of @pendingChanges + for attrName, change of objectChanges + all.push @submit(change) + all - submit: (change) -> - dataSubmitter(change).then (data) => - @remove change.object.id, change.attr - change.scope.reset( data["#{change.attr}"] ) + submit: (change) -> + dataSubmitter(change).then (data) => + @remove change.object.id, change.attr + change.scope.reset( data["#{change.attr}"] ) - changeCount: (objectChanges) -> - Object.keys(objectChanges).length + changeCount: (objectChanges) -> + Object.keys(objectChanges).length From 4545e0ff95caaede5c04761c305ed7810db2caa0 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 12:35:20 +1000 Subject: [PATCH 096/206] Adding request defaults to index utils module --- app/assets/javascripts/admin/index_utils/index_utils.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee index 46fc050742..0925bf45ed 100644 --- a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee +++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee @@ -1 +1 @@ -angular.module("admin.indexUtils", ['ngResource']) \ No newline at end of file +angular.module("admin.indexUtils", ['ngResource']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file From dcde2d88ad4ef73ec2cecd0804db0e12a06e04b8 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 12:35:57 +1000 Subject: [PATCH 097/206] Adding Customer resource to resources service --- .../admin/index_utils/services/resources.js.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/index_utils/services/resources.js.coffee b/app/assets/javascripts/admin/index_utils/services/resources.js.coffee index c14744701b..65dad204d4 100644 --- a/app/assets/javascripts/admin/index_utils/services/resources.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/resources.js.coffee @@ -1,6 +1,9 @@ angular.module("admin.indexUtils").factory "resources", ($resource) -> LineItem = $resource '/api/orders/:order_number/line_items/:line_item_id.json', - { order_number: '@order_cycle_id', line_item_id: '@line_item_id'}, + { order_number: '@order_number', line_item_id: '@line_item_id'}, + 'update': { method: 'PUT' } + Customer = $resource '/admin/customers/:customer_id.json', + { customer_id: '@customer_id'}, 'update': { method: 'PUT' } return { @@ -11,11 +14,16 @@ angular.module("admin.indexUtils").factory "resources", ($resource) -> switch change.type when "line_item" - resource = LineItem; + resource = LineItem params.order_number = change.object.order.number params.line_item_id = change.object.id data.line_item = {} data.line_item[change.attr] = change.value + when "customer" + resource = Customer + params.customer_id = change.object.id + data.customer = {} + data.customer[change.attr] = change.value else "" resource.update(params, data) From 734ad21e82445630b706496e0a6a415f6fae0c18 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 12:36:56 +1000 Subject: [PATCH 098/206] Customers controller responds with json --- app/controllers/admin/customers_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index b14ca38296..27b5b1380d 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -1,6 +1,7 @@ module Admin class CustomersController < ResourceController before_filter :load_managed_shops, only: :index, if: :html_request? + respond_to :json def index respond_to do |format| From 9add073b171c6c98a7182a2dce3f9fefcd1a7ba5 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 12:46:37 +1000 Subject: [PATCH 099/206] User can update customer code from customer index page --- .../controllers/customers_controller.js.coffee | 3 ++- app/views/admin/customers/index.html.haml | 17 +++++++++-------- spec/features/admin/customers_spec.rb | 11 ++++++++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index 6bee8f993e..5f078525d9 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,6 +1,7 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, Customers, Columns, shops) -> +angular.module("admin.customers").controller "customersCtrl", ($scope, Customers, Columns, pendingChanges, shops) -> $scope.shop = null $scope.shops = shops + $scope.submitAll = pendingChanges.submitAll $scope.columns = Columns.setColumns email: { name: "Email", visible: true } diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index aef398c991..a8e6141796 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -29,12 +29,12 @@ %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} %span{ :class => 'two columns omega' } {{column.name }} - .row - .sixteen.columns.alpha#loading{ 'ng-if' => 'shop_id && !loaded()' } - %img.spinner{ src: "/assets/spinning-circles.svg" } - %h1 LOADING CUSTOMERS - .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded() && filteredCustomers.length == 0'} - %h1#no_results No customers found. + .row{ 'ng-if' => 'shop_id && !loaded()' } + .sixteen.columns.alpha#loading + %img.spinner{ src: "/assets/spinning-circles.svg" } + %h1 LOADING CUSTOMERS + .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded() && filteredCustomers.length == 0'} + %h1#no_results No customers found. .row{ ng: { show: "loaded()" } } @@ -55,7 +55,8 @@ %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} - %td.code{ 'ng-show' => 'columns.code.visible' } {{ customer.code }} + %td.code{ 'ng-show' => 'columns.code.visible' } + %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } %td.actions %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } - -# %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } + %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' } diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 38af3145f3..75088bf395 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -19,7 +19,7 @@ feature 'Customers' do visit admin_customers_path end - it "lists my customers", js: true do + it "passes the smoke test", js: true do # Prompts for a hub expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] @@ -44,6 +44,15 @@ feature 'Customers' do first("div#columns_dropdown div.menu div.menu_item", text: "Email").click expect(page).to_not have_selector "th.email" expect(page).to_not have_content customer1.email + + # Updating attributes + within "tr#c_#{customer1.id}" do + fill_in "code", with: "new-customer-code" + expect(page).to have_css "input#code.update-pending" + end + click_button "Update" + expect(page).to have_css "input#code.update-success" + expect(customer1.reload.code).to eq "new-customer-code" end end end From 5b49e64bf204c7065db3adb91b3cddbc5a79fc1f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 12:52:36 +1000 Subject: [PATCH 100/206] Hiding bulk checkboxes on customer interface (for now) --- app/views/admin/customers/index.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index a8e6141796..c234705e4f 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -42,8 +42,8 @@ %table.index#customers{ :class => "sixteen columns alpha" } %thead %tr{ ng: { controller: "ColumnsCtrl" } } - %th.bulk - %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } + -# %th.bulk + -# %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } %th.email{ 'ng-show' => 'columns.email.visible' } %a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" } Email %th.code{ 'ng-show' => 'columns.code.visible' } @@ -52,8 +52,8 @@ Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } - %td.bulk - %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } + -# %td.bulk + -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} %td.code{ 'ng-show' => 'columns.code.visible' } %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } From 4c5e75c3f96437d905e93562687538cccb2f9287 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 14:55:00 +1000 Subject: [PATCH 101/206] Adding acts-as-taggable-on gem --- Gemfile | 1 + Gemfile.lock | 3 ++ ...on_migration.acts_as_taggable_on_engine.rb | 31 +++++++++++++++++++ ...ique_indices.acts_as_taggable_on_engine.rb | 20 ++++++++++++ ...ache_to_tags.acts_as_taggable_on_engine.rb | 15 +++++++++ ...ggable_index.acts_as_taggable_on_engine.rb | 10 ++++++ ...or_tag_names.acts_as_taggable_on_engine.rb | 10 ++++++ db/schema.rb | 25 +++++++++++++-- 8 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20150508030520_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20150508030521_add_missing_unique_indices.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20150508030522_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20150508030523_add_missing_taggable_index.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20150508030524_change_collation_for_tag_names.acts_as_taggable_on_engine.rb diff --git a/Gemfile b/Gemfile index b63961a3ef..0393773df0 100644 --- a/Gemfile +++ b/Gemfile @@ -49,6 +49,7 @@ gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg' gem 'angularjs-file-upload-rails', '~> 1.1.0' gem 'roadie-rails', '~> 1.0.3' gem 'figaro' +gem 'acts-as-taggable-on', '~> 3.4' gem 'foreigner' gem 'immigrant' diff --git a/Gemfile.lock b/Gemfile.lock index 1b62201120..3b1412d1c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,6 +142,8 @@ GEM activesupport (3.2.21) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) + acts-as-taggable-on (3.5.0) + activerecord (>= 3.2, < 5) acts_as_list (0.1.4) addressable (2.3.3) andand (1.3.3) @@ -532,6 +534,7 @@ PLATFORMS DEPENDENCIES active_model_serializers + acts-as-taggable-on (~> 3.4) andand angular-rails-templates angularjs-file-upload-rails (~> 1.1.0) diff --git a/db/migrate/20150508030520_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20150508030520_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000000..6bbd5594ea --- /dev/null +++ b/db/migrate/20150508030520_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb @@ -0,0 +1,31 @@ +# This migration comes from acts_as_taggable_on_engine (originally 1) +class ActsAsTaggableOnMigration < ActiveRecord::Migration + def self.up + create_table :tags do |t| + t.string :name + end + + create_table :taggings do |t| + t.references :tag + + # You should make sure that the column created is + # long enough to store the required class names. + t.references :taggable, polymorphic: true + t.references :tagger, polymorphic: true + + # Limit is created to prevent MySQL error on index + # length for MyISAM table type: http://bit.ly/vgW2Ql + t.string :context, limit: 128 + + t.datetime :created_at + end + + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + drop_table :taggings + drop_table :tags + end +end diff --git a/db/migrate/20150508030521_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20150508030521_add_missing_unique_indices.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000000..4ca676f6c7 --- /dev/null +++ b/db/migrate/20150508030521_add_missing_unique_indices.acts_as_taggable_on_engine.rb @@ -0,0 +1,20 @@ +# This migration comes from acts_as_taggable_on_engine (originally 2) +class AddMissingUniqueIndices < ActiveRecord::Migration + def self.up + add_index :tags, :name, unique: true + + remove_index :taggings, :tag_id + remove_index :taggings, [:taggable_id, :taggable_type, :context] + add_index :taggings, + [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], + unique: true, name: 'taggings_idx' + end + + def self.down + remove_index :tags, :name + + remove_index :taggings, name: 'taggings_idx' + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20150508030522_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20150508030522_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000000..8edb508078 --- /dev/null +++ b/db/migrate/20150508030522_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb @@ -0,0 +1,15 @@ +# This migration comes from acts_as_taggable_on_engine (originally 3) +class AddTaggingsCounterCacheToTags < ActiveRecord::Migration + def self.up + add_column :tags, :taggings_count, :integer, default: 0 + + ActsAsTaggableOn::Tag.reset_column_information + ActsAsTaggableOn::Tag.find_each do |tag| + ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings) + end + end + + def self.down + remove_column :tags, :taggings_count + end +end diff --git a/db/migrate/20150508030523_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20150508030523_add_missing_taggable_index.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000000..71f2d7f433 --- /dev/null +++ b/db/migrate/20150508030523_add_missing_taggable_index.acts_as_taggable_on_engine.rb @@ -0,0 +1,10 @@ +# This migration comes from acts_as_taggable_on_engine (originally 4) +class AddMissingTaggableIndex < ActiveRecord::Migration + def self.up + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + remove_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20150508030524_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20150508030524_change_collation_for_tag_names.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000000..bfb06bc7cd --- /dev/null +++ b/db/migrate/20150508030524_change_collation_for_tag_names.acts_as_taggable_on_engine.rb @@ -0,0 +1,10 @@ +# This migration comes from acts_as_taggable_on_engine (originally 5) +# This migration is added to circumvent issue #623 and have special characters +# work properly +class ChangeCollationForTagNames < ActiveRecord::Migration + def up + if ActsAsTaggableOn::Utils.using_mysql? + execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") + end + end +end diff --git a/db/schema.rb b/db/schema.rb index cb8b503583..9f3cee5f5e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150424025907) do +ActiveRecord::Schema.define(:version => 20150508030524) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -549,6 +549,7 @@ ActiveRecord::Schema.define(:version => 20150424025907) do t.string "currency" t.decimal "distribution_fee", :precision => 10, :scale => 2 t.string "shipping_method_name" + t.decimal "unit_value", :precision => 8, :scale => 2 end add_index "spree_line_items", ["order_id"], :name => "index_line_items_on_order_id" @@ -618,9 +619,9 @@ ActiveRecord::Schema.define(:version => 20150424025907) do t.string "email" t.text "special_instructions" t.integer "distributor_id" + t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" - t.integer "order_cycle_id" t.integer "cart_id" end @@ -1080,6 +1081,26 @@ ActiveRecord::Schema.define(:version => 20150424025907) do t.integer "state_id" end + create_table "taggings", :force => true do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", :limit => 128 + t.datetime "created_at" + end + + add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], :name => "taggings_idx", :unique => true + add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context" + + create_table "tags", :force => true do |t| + t.string "name" + t.integer "taggings_count", :default => 0 + end + + add_index "tags", ["name"], :name => "index_tags_on_name", :unique => true + create_table "variant_overrides", :force => true do |t| t.integer "variant_id", :null => false t.integer "hub_id", :null => false From a60fd3d388bcb34780e08873a203dbcdda2223b2 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 14:56:03 +1000 Subject: [PATCH 102/206] Installing ngTagInput --- app/assets/javascripts/shared/ng-tags-input.min.js | 1 + app/assets/stylesheets/shared/ng-tags-input.min.css | 1 + 2 files changed, 2 insertions(+) create mode 100755 app/assets/javascripts/shared/ng-tags-input.min.js create mode 100755 app/assets/stylesheets/shared/ng-tags-input.min.css diff --git a/app/assets/javascripts/shared/ng-tags-input.min.js b/app/assets/javascripts/shared/ng-tags-input.min.js new file mode 100755 index 0000000000..9a1acd6e0d --- /dev/null +++ b/app/assets/javascripts/shared/ng-tags-input.min.js @@ -0,0 +1 @@ +/*! ngTagsInput v2.3.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tags},getCurrentTagText:function(){return a.newTag.text},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",b.tags.length<=n.maxTags),i.$setValidity("minTags",b.tags.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text)},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:"",invalid:null,setText:function(a){this.text=a,m.trigger("input-change",a)}},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){b.tags=h.makeObjectArray(a,n.displayProperty),l.items=b.tags}),b.$watch("tags.length",function(){j()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{change:function(a){m.trigger("input-change",a)},keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-added",function(){b.newTag.setText("")}).on("tag-added tag-removed",function(){i.$setViewValue(b.tags)}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,h=c.keyCode,i=c.shiftKey||c.altKey||c.ctrlKey||c.metaKey,j={};if(!i&&-1!==k.indexOf(h)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[h],e=(h===a.backspace||h===a["delete"])&&l.selected,g=h===a.backspace&&0===b.newTag.text.length&&n.enableEditingLastTag,f=(h===a.backspace||h===a.left||h===a.right)&&0===b.newTag.text.length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text);else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.setText(m[n.displayProperty])}else e?l.removeSelected():f&&(h===a.left||h===a.backspace?l.selectPrior():h===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,g=[a.enter,a.tab,a.escape,a.up,a.down],h=b.suggestionList,j=e.registerAutocomplete(),k=b.options,l=b.events;k.tagsInput=j.getOptions(),f=function(a){return a&&a.length>=k.minLength||!a&&k.loadOnEmpty},b.addSuggestionByIndex=function(a){h.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return h.selected&&(j.addTag(angular.copy(h.selected)),h.reset(),j.focusInput(),a=!0),a},b.track=function(a){return a[k.tagsInput.keyProperty||k.tagsInput.displayProperty]},j.on("tag-added invalid-tag input-blur",function(){h.reset()}).on("input-change",function(a){f(a)?h.load(a,j.getTags()):h.reset()}).on("input-focus",function(){var a=j.getCurrentTagText();k.loadOnFocus&&f(a)&&h.load(a,j.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(-1!==g.indexOf(d))return h.visible?d===a.down?(h.selectNext(),e=!0):d===a.up?(h.selectPrior(),e=!0):d===a.escape?(h.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(h.load(j.getCurrentTagText(),j.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),l.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
'),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
'),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); \ No newline at end of file diff --git a/app/assets/stylesheets/shared/ng-tags-input.min.css b/app/assets/stylesheets/shared/ng-tags-input.min.css new file mode 100755 index 0000000000..ee4a4a98d5 --- /dev/null +++ b/app/assets/stylesheets/shared/ng-tags-input.min.css @@ -0,0 +1 @@ +tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff} \ No newline at end of file From a03c7a5f220a3cac71b0a4910e2845550e9fb7e7 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 14:56:57 +1000 Subject: [PATCH 103/206] Adding ngTagsInput to admin assets --- app/assets/javascripts/admin/all.js | 1 + app/assets/stylesheets/admin/all.css | 1 + 2 files changed, 2 insertions(+) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index f6d016dc9a..e44e6ab96d 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -17,6 +17,7 @@ //= require admin/spree_promo //= require admin/spree_paypal_express //= require ../shared/ng-infinite-scroll.min.js +//= require ../shared/ng-tags-input.min.js //= require ./admin //= require ./customers/customers //= require ./dropdown/dropdown diff --git a/app/assets/stylesheets/admin/all.css b/app/assets/stylesheets/admin/all.css index 9b3603fe7b..e0d668b95b 100644 --- a/app/assets/stylesheets/admin/all.css +++ b/app/assets/stylesheets/admin/all.css @@ -10,6 +10,7 @@ *= require shared/jquery-ui-timepicker-addon *= require shared/textAngular.min + *= require shared/ng-tags-input.min *= require_self *= require_tree . From 5b3c9842e4e0b39ccdd297975c4ed2b6d8afdd70 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 14:58:56 +1000 Subject: [PATCH 104/206] Adding acts_as_taggable to customer model --- app/models/customer.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/customer.rb b/app/models/customer.rb index d3fa9e093f..b17e1407fa 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -1,4 +1,6 @@ class Customer < ActiveRecord::Base + acts_as_taggable + belongs_to :enterprise belongs_to :user, :class_name => Spree.user_class From d2e8b23dd4467ac3ab3c7833c20b5dd8b6230668 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 14:59:16 +1000 Subject: [PATCH 105/206] Creating association between enterprise and customers --- app/models/enterprise.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 1aa76b88a8..c11bfb97b4 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -30,6 +30,7 @@ class Enterprise < ActiveRecord::Base has_and_belongs_to_many :payment_methods, join_table: 'distributors_payment_methods', class_name: 'Spree::PaymentMethod', foreign_key: 'distributor_id' has_many :distributor_shipping_methods, foreign_key: :distributor_id has_many :shipping_methods, through: :distributor_shipping_methods + has_many :customers delegate :latitude, :longitude, :city, :state_name, :to => :address From 3bc69242ce9e0f7fc889e505f3aa72fae7d6f20a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 15:00:06 +1000 Subject: [PATCH 106/206] Style tweaks --- app/assets/stylesheets/admin/orders.css.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index c0c1dac86b..249447757c 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -8,15 +8,15 @@ } } -input.update-pending { +.update-pending { border: solid 1px orange; } -input.update-error { +.update-error { border: solid 1px red; } -input.update-success { +.update-success { border: solid 1px #9fc820; } @@ -42,4 +42,4 @@ div#group_buy_calculation { .row span { text-align: center; } -} \ No newline at end of file +} From b364994cc8caeb92664360c1118416c91aff2b0e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 15:30:04 +1000 Subject: [PATCH 107/206] Adding tags input to customer index using ngTagsInput --- .../customers_controller.js.coffee | 1 + .../admin/customers/customers.js.coffee | 2 +- .../tags_with_translation.js.coffee | 8 ++++++ .../api/admin/customer_serializer.rb | 10 ++++++- app/views/admin/customers/index.html.haml | 26 ++++++++++++------- spec/features/admin/customers_spec.rb | 18 +++++++++++-- 6 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index 5f078525d9..0e82806ef9 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -6,6 +6,7 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, Customers $scope.columns = Columns.setColumns email: { name: "Email", visible: true } code: { name: "Code", visible: true } + tags: { name: "Tags", visible: true } $scope.initialise = -> $scope.customers = Customers.index(enterprise_id: $scope.shop.id) diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee index 462b7d5818..3733fe2eea 100644 --- a/app/assets/javascripts/admin/customers/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/customers.js.coffee @@ -1 +1 @@ -angular.module("admin.customers", ['ngResource', 'admin.indexUtils', 'admin.dropdown']) \ No newline at end of file +angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.dropdown']) \ No newline at end of file diff --git a/app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee b/app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee new file mode 100644 index 0000000000..e15ec10342 --- /dev/null +++ b/app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.customers").directive "tagsWithTranslation", -> + restrict: "E" + template: "" + scope: + object: "=" + link: (scope, element, attrs) -> + scope.$watchCollection "object.tags", -> + scope.object.tag_list = (tag.text for tag in scope.object.tags).join(",") diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb index 84f32b6e8b..3cb9518a9f 100644 --- a/app/serializers/api/admin/customer_serializer.rb +++ b/app/serializers/api/admin/customer_serializer.rb @@ -1,3 +1,11 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer - attributes :id, :email, :enterprise_id, :user_id, :code + attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list + + def tag_list + object.tag_list.join(",") + end + + def tags + object.tag_list.map{ |t| { text: t } } + end end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index c234705e4f..603f929477 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -39,7 +39,11 @@ .row{ ng: { show: "loaded()" } } %form{ name: "customers" } - %table.index#customers{ :class => "sixteen columns alpha" } + %table.index#customers + %col.email{ width: "20%"} + %col.code{ width: "20%"} + %col.tags{ width: "50%"} + %col.actions{ width: "10%"} %thead %tr{ ng: { controller: "ColumnsCtrl" } } -# %th.bulk @@ -48,15 +52,19 @@ %a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" } Email %th.code{ 'ng-show' => 'columns.code.visible' } %a{ :href => '', 'ng-click' => "predicate = 'customer.code'; reverse = !reverse" } Code + %th.tags{ 'ng-show' => 'columns.tags.visible' } Tags %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } - -# %td.bulk - -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } - %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} - %td.code{ 'ng-show' => 'columns.code.visible' } - %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } - %td.actions - %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } + %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } + -# %td.bulk + -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } + %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} + %td.code{ 'ng-show' => 'columns.code.visible' } + %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } + %td.tags{ 'ng-show' => 'columns.tags.visible' } + .tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"} + %tags_with_translation{ object: 'customer' } + %td.actions + %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' } diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 75088bf395..b6c84e10cd 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -20,7 +20,7 @@ feature 'Customers' do end it "passes the smoke test", js: true do - # Prompts for a hub + # Prompts for a hub for a list of my managed enterprises expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] select2_select managed_distributor.name, from: "shop_id" @@ -44,15 +44,29 @@ feature 'Customers' do first("div#columns_dropdown div.menu div.menu_item", text: "Email").click expect(page).to_not have_selector "th.email" expect(page).to_not have_content customer1.email + end + + it "allows updating of attributes", js: true do + select2_select managed_distributor.name, from: "shop_id" + click_button "Go" - # Updating attributes within "tr#c_#{customer1.id}" do fill_in "code", with: "new-customer-code" expect(page).to have_css "input#code.update-pending" end + within "tr#c_#{customer1.id}" do + find(:css, "tags-input .tags input").set "awesome\n" + expect(page).to have_css ".tag_watcher.update-pending" + end click_button "Update" + + # Every says it updated expect(page).to have_css "input#code.update-success" + expect(page).to have_css ".tag_watcher.update-success" + + # And it actually did expect(customer1.reload.code).to eq "new-customer-code" + expect(customer1.tag_list).to eq ["awesome"] end end end From dd9c192d480381dca6c9098002035f4ee0f04380 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 16:13:57 +1000 Subject: [PATCH 108/206] Shuffling layout a little on customer index --- app/views/admin/customers/index.html.haml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 603f929477..f3e82f9cdf 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -1,3 +1,6 @@ +- content_for :page_title do + %h1.page-title Customers + = admin_inject_shops %div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } } @@ -11,16 +14,16 @@ .row{ 'ng-hide' => '!loaded() || lineItems.length == 0' } .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } - .three.columns.alpha + .five.columns.alpha %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } - .three.columns   + .five.columns   -# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } -# %span{ :class => 'icon-check' }   Actions -# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } -# %div.menu{ 'ng-show' => "expanded" } -# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true } -# %span{ :class => 'three columns omega' } {{action.name }} - .seven.columns   + .three.columns   .three.columns.omega %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } %span{ :class => 'icon-reorder' }   Columns From ed941e211d771526c06cd1e50878311c25c83978 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 8 May 2015 17:35:58 +1000 Subject: [PATCH 109/206] Don't require a code on customer --- app/models/customer.rb | 4 ++-- ...508072454_remove_customer_code_not_null_constraint.rb | 9 +++++++++ db/schema.rb | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20150508072454_remove_customer_code_not_null_constraint.rb diff --git a/app/models/customer.rb b/app/models/customer.rb index b17e1407fa..6bbd4606e5 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -4,8 +4,8 @@ class Customer < ActiveRecord::Base belongs_to :enterprise belongs_to :user, :class_name => Spree.user_class - validates :code, presence: true, uniqueness: {scope: :enterprise_id} - validates :email, presence: true + validates :code, uniqueness: { scope: :enterprise_id, allow_blank: true, allow_nil: true } + validates :email, presence: true, uniqueness: { scope: :enterprise_id, message: "is associated with an existing customer" } validates :enterprise_id, presence: true scope :of, ->(enterprise) { where(enterprise_id: enterprise) } diff --git a/db/migrate/20150508072454_remove_customer_code_not_null_constraint.rb b/db/migrate/20150508072454_remove_customer_code_not_null_constraint.rb new file mode 100644 index 0000000000..deeacbc608 --- /dev/null +++ b/db/migrate/20150508072454_remove_customer_code_not_null_constraint.rb @@ -0,0 +1,9 @@ +class RemoveCustomerCodeNotNullConstraint < ActiveRecord::Migration + def up + change_column :customers, :code, :string, null: true + end + + def down + change_column :customers, :code, :string, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 9f3cee5f5e..e5692233d3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150508030524) do +ActiveRecord::Schema.define(:version => 20150508072454) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -158,7 +158,7 @@ ActiveRecord::Schema.define(:version => 20150508030524) do create_table "customers", :force => true do |t| t.string "email", :null => false t.integer "enterprise_id", :null => false - t.string "code", :null => false + t.string "code" t.integer "user_id" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false From 1559b4e30abef454c0e42ad6f10724cb2aabe93a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 11:39:17 +1000 Subject: [PATCH 110/206] Adding customer reference to orders --- .../20150508072938_add_customer_to_orders.rb | 16 ++++++++++++++++ db/schema.rb | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150508072938_add_customer_to_orders.rb diff --git a/db/migrate/20150508072938_add_customer_to_orders.rb b/db/migrate/20150508072938_add_customer_to_orders.rb new file mode 100644 index 0000000000..79d69baf49 --- /dev/null +++ b/db/migrate/20150508072938_add_customer_to_orders.rb @@ -0,0 +1,16 @@ +class AddCustomerToOrders < ActiveRecord::Migration + def change + add_column :spree_orders, :customer_id, :integer + add_index :spree_orders, :customer_id + add_foreign_key :spree_orders, :customers, column: :customer_id + + Spree::Order.where("spree_orders.email IS NOT NULL AND distributor_id IS NOT NULL AND customer_id IS NULL").each do |order| + customer = Customer.find_by_email_and_enterprise_id(order.email, order.distributor_id) + unless customer.present? + user = Spree::User.find_by_email(order.email) + customer = Customer.create!(email: order.email, enterprise_id: order.distributor_id, user_id: user.andand.id ) + end + order.update_attribute(:customer, customer) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e5692233d3..b32ec3c4d7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150508072454) do +ActiveRecord::Schema.define(:version => 20150508072938) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -623,8 +623,10 @@ ActiveRecord::Schema.define(:version => 20150508072454) do t.string "currency" t.string "last_ip_address" t.integer "cart_id" + t.integer "customer_id" end + add_index "spree_orders", ["customer_id"], :name => "index_spree_orders_on_customer_id" add_index "spree_orders", ["number"], :name => "index_orders_on_number" create_table "spree_payment_methods", :force => true do |t| @@ -1206,6 +1208,7 @@ ActiveRecord::Schema.define(:version => 20150508072454) do add_foreign_key "spree_option_values_variants", "spree_variants", name: "spree_option_values_variants_variant_id_fk", column: "variant_id" add_foreign_key "spree_orders", "carts", name: "spree_orders_cart_id_fk" + add_foreign_key "spree_orders", "customers", name: "spree_orders_customer_id_fk" add_foreign_key "spree_orders", "enterprises", name: "spree_orders_distributor_id_fk", column: "distributor_id" add_foreign_key "spree_orders", "order_cycles", name: "spree_orders_order_cycle_id_fk" add_foreign_key "spree_orders", "spree_addresses", name: "spree_orders_bill_address_id_fk", column: "bill_address_id" From 34f5cfb6b5094b2d6169e230b051b87dafe16432 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 12:32:55 +1000 Subject: [PATCH 111/206] Completed orders require a customer, add association logic --- app/models/customer.rb | 8 ++++++ app/models/spree/order_decorator.rb | 23 +++++++++++++++++ spec/models/customer_spec.rb | 20 +++++++++++++++ spec/models/spree/order_spec.rb | 39 +++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 spec/models/customer_spec.rb diff --git a/app/models/customer.rb b/app/models/customer.rb index 6bbd4606e5..856f7e94d7 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -9,4 +9,12 @@ class Customer < ActiveRecord::Base validates :enterprise_id, presence: true scope :of, ->(enterprise) { where(enterprise_id: enterprise) } + + before_create :associate_user + + private + + def associate_user + self.user = user || Spree::User.find_by_email(email) + end end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 0dc6977965..28a8007da6 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -10,11 +10,14 @@ Spree::Order.class_eval do belongs_to :order_cycle belongs_to :distributor, :class_name => 'Enterprise' belongs_to :cart + belongs_to :customer + validates :customer, presence: true, if: :require_customer? validate :products_available_from_new_distribution, :if => lambda { distributor_id_changed? || order_cycle_id_changed? } attr_accessible :order_cycle_id, :distributor_id before_validation :shipping_address_from_distributor + before_validation :associate_customer, unless: :customer_is_valid? checkout_flow do go_to_state :address @@ -260,4 +263,24 @@ Spree::Order.class_eval do def product_distribution_for(line_item) line_item.variant.product.product_distribution_for self.distributor end + + def require_customer? + return true unless new_record? or state == 'cart' + end + + def customer_is_valid? + return true unless require_customer? + customer.present? && customer.enterprise_id == distributor_id && customer.email == (user.andand.email || email) + end + + def associate_customer + email_for_customer = user.andand.email || email + existing_customer = Customer.of(distributor).find_by_email(email_for_customer) + if existing_customer + self.customer = existing_customer + else + new_customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user) + self.customer = new_customer + end + end end diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb new file mode 100644 index 0000000000..0e43ce9df5 --- /dev/null +++ b/spec/models/customer_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Customer, type: :model do + describe 'creation callbacks' do + let!(:user1) { create(:user) } + let!(:user2) { create(:user) } + let!(:enterprise) { create(:distributor_enterprise) } + + it "associates an existing user using email" do + c1 = Customer.create(enterprise: enterprise, email: 'some-email-not-associated-with-a-user@email.com') + expect(c1.user).to be_nil + + c2 = Customer.create(enterprise: enterprise, email: 'some-email-not-associated-with-a-user@email.com', user: user1) + expect(c2.user).to eq user1 + + c3 = Customer.create(enterprise: enterprise, email: user2.email) + expect(c3.user).to eq user2 + end + end +end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index ecc19f5493..dddaf554a2 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -511,4 +511,43 @@ describe Spree::Order do end.to enqueue_job ConfirmOrderJob end end + + describe "associating a customer" do + let(:user) { create(:user) } + let(:distributor) { create(:distributor_enterprise) } + + context "when a user has been set on the order" do + let!(:order) { create(:order, distributor: distributor, user: user) } + context "and a customer for order.distributor and order.user.email already exists" do + let!(:customer) { create(:customer, enterprise: distributor, email: user.email) } + it "associates the order with the existing customer" do + order.send(:associate_customer) + expect(order.customer).to eq customer + end + end + context "and a customer for order.distributor and order.user.email does not alread exist" do + let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') } + it "creates a new customer" do + expect{order.send(:associate_customer)}.to change{Customer.count}.by 1 + end + end + end + + context "when a user has not been set on the order" do + let!(:order) { create(:order, distributor: distributor, user: nil) } + context "and a customer for order.distributor and order.email already exists" do + let!(:customer) { create(:customer, enterprise: distributor, email: order.email) } + it "creates a new customer" do + order.send(:associate_customer) + expect(order.customer).to eq customer + end + end + context "and a customer for order.distributor and order.email does not alread exist" do + let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') } + it "creates a new customer" do + expect{order.send(:associate_customer)}.to change{Customer.count}.by 1 + end + end + end + end end From b86872095a4425fe5047a4a800b0ecf868e04382 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 13 May 2015 14:52:17 +1000 Subject: [PATCH 112/206] Add google analytics --- app/views/layouts/darkswarm.html.haml | 1 + app/views/shared/_analytics.html.haml | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 app/views/shared/_analytics.html.haml diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index e1976ab167..a627ba4896 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -41,3 +41,4 @@ #footer %loading + = render 'shared/analytics' diff --git a/app/views/shared/_analytics.html.haml b/app/views/shared/_analytics.html.haml new file mode 100644 index 0000000000..ee9ba69923 --- /dev/null +++ b/app/views/shared/_analytics.html.haml @@ -0,0 +1,8 @@ +:javascript + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-62912229-1', 'auto'); + ga('send', 'pageview'); From a473d0ed11cdce163d37fac574b31df35fa4b6d5 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 15:58:03 +1000 Subject: [PATCH 113/206] Checking in db version change --- db/schema.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 96f5896c79..f9f32af7d1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150424025907) do +ActiveRecord::Schema.define(:version => 20150424151117) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -619,9 +619,9 @@ ActiveRecord::Schema.define(:version => 20150424025907) do t.string "email" t.text "special_instructions" t.integer "distributor_id" + t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" - t.integer "order_cycle_id" t.integer "cart_id" end From ffac0e4cebc82670aae8305947ef3c299a09a9a9 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 16:06:38 +1000 Subject: [PATCH 114/206] Adding ngTagsInput to angular spec manifest --- spec/javascripts/application_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index 10db2226a4..07202027b4 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -7,6 +7,7 @@ //= require angularjs-file-upload //= require lodash.underscore.js //= require angular-flash.min.js +//= require shared/ng-tags-input.min.js //= require shared/mm-foundation-tpls-0.2.2.min.js //= require textAngular.min.js //= require textAngular-sanitize.min.js From 3849b39d3e1b84d302d2f3d41d444c8eeb3cfddc Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 16:39:43 +1000 Subject: [PATCH 115/206] Splitting out specs for pending changes service --- .../services/pending_changes_spec.js.coffee | 119 +++++++++++++++++ .../unit/bulk_order_management_spec.js.coffee | 123 +----------------- 2 files changed, 121 insertions(+), 121 deletions(-) create mode 100644 spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee diff --git a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee new file mode 100644 index 0000000000..2acf684754 --- /dev/null +++ b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee @@ -0,0 +1,119 @@ +describe "Pending Changes", -> + dataSubmitter = pendingChanges = null + + beforeEach -> + dataSubmitter = jasmine.createSpy('dataSubmitter').andReturn { + then: (thenFn) -> + thenFn({propertyName: "new_value"}) + } + module 'admin.indexUtils', ($provide) -> + $provide.value 'dataSubmitter', dataSubmitter + return + + inject (_pendingChanges_) -> + pendingChanges = _pendingChanges_ + + + describe "adding a new change", -> + it "adds a new object with key of id if it does not already exist", -> + expect(pendingChanges.pendingChanges).toEqual {} + expect(pendingChanges.pendingChanges["1"]).not.toBeDefined() + pendingChanges.add 1, "propertyName", { a: 1 } + expect(pendingChanges.pendingChanges["1"]).toBeDefined() + + it "adds a new object with key of the altered attribute name if it does not already exist", -> + pendingChanges.add 1, "propertyName", { a: 1 } + expect(pendingChanges.pendingChanges["1"]).toBeDefined() + expect(pendingChanges.pendingChanges["1"]["propertyName"]).toEqual { a: 1 } + + it "replaces the existing object when adding a change to an attribute which already exists", -> + pendingChanges.add 1, "propertyName", { a: 1 } + expect(pendingChanges.pendingChanges["1"]).toBeDefined() + expect(pendingChanges.pendingChanges["1"]["propertyName"]).toEqual { a: 1 } + pendingChanges.add 1, "propertyName", { b: 2 } + expect(pendingChanges.pendingChanges["1"]["propertyName"]).toEqual { b: 2 } + + it "adds an attribute to key to a line item object when one already exists", -> + pendingChanges.add 1, "propertyName1", { a: 1 } + pendingChanges.add 1, "propertyName2", { b: 2 } + expect(pendingChanges.pendingChanges["1"]).toEqual { propertyName1: { a: 1}, propertyName2: { b: 2 } } + + describe "removing all existing changes", -> + it "resets pendingChanges object", -> + pendingChanges.pendingChanges = { 1: { "propertyName1": { a: 1 }, "propertyName2": { b: 2 } } } + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).toBeDefined() + expect(pendingChanges.pendingChanges["1"]["propertyName2"]).toBeDefined() + pendingChanges.removeAll() + expect(pendingChanges.pendingChanges["1"]).not.toBeDefined() + expect(pendingChanges.pendingChanges).toEqual {} + + describe "removing an existing change", -> + it "deletes a change if it exists", -> + pendingChanges.pendingChanges = { 1: { "propertyName1": { a: 1 }, "propertyName2": { b: 2 } } } + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).toBeDefined() + pendingChanges.remove 1, "propertyName1" + expect(pendingChanges.pendingChanges["1"]).toBeDefined() + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).not.toBeDefined() + + it "deletes a line item object if it is empty", -> + pendingChanges.pendingChanges = { 1: { "propertyName1": { a: 1 } } } + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).toBeDefined() + pendingChanges.remove 1, "propertyName1" + expect(pendingChanges.pendingChanges["1"]).not.toBeDefined() + + it "does nothing if key with specified attribute does not exist", -> + pendingChanges.pendingChanges = { 1: { "propertyName1": { a: 1 } } } + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).toBeDefined() + pendingChanges.remove 1, "propertyName2" + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).toEqual { a: 1 } + + it "does nothing if key with specified id does not exist", -> + pendingChanges.pendingChanges = { 1: { "propertyName1": { a: 1 } } } + expect(pendingChanges.pendingChanges["1"]["propertyName1"]).toBeDefined() + pendingChanges.remove 2, "propertyName1" + expect(pendingChanges.pendingChanges["1"]).toEqual { "propertyName1": { a: 1 } } + + describe "submitting an individual change to the server", -> + change = null + beforeEach -> + object = {id: 1} + scope = { reset: jasmine.createSpy('reset') }; + attr = "propertyName" + change = { object: object, scope: scope, attr: attr } + + it "sends the correct object to dataSubmitter", -> + pendingChanges.submit change + expect(dataSubmitter.calls.length).toEqual 1 + expect(dataSubmitter).toHaveBeenCalledWith change + + it "calls remove with id and attribute name", -> + spyOn(pendingChanges, "remove").andCallFake(->) + pendingChanges.submit change + expect(pendingChanges.remove.calls.length).toEqual 1 + expect(pendingChanges.remove).toHaveBeenCalledWith 1, "propertyName" + + it "calls reset on the relevant scope", -> + pendingChanges.submit change + expect(change.scope.reset).toHaveBeenCalledWith "new_value" + + describe "cycling through all changes to submit to server", -> + it "sends the correct object to dataSubmitter", -> + spyOn(pendingChanges, "submit").andCallFake(->) + pendingChanges.pendingChanges = + 1: { "prop1": { attr: "prop1", value: 1 }, "prop2": { attr: "prop2", value: 2 } } + 2: { "prop1": { attr: "prop1", value: 2 }, "prop2": { attr: "prop2", value: 4 } } + 7: { "prop2": { attr: "prop2", value: 5 } } + pendingChanges.submitAll() + expect(pendingChanges.submit.calls.length).toEqual 5 + expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop1", value: 1 } + expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 2 } + expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop1", value: 2 } + expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 4 } + expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 5 } + + it "returns an array of promises representing all sumbit requests", -> + spyOn(pendingChanges, "submit").andCallFake (change) -> change.value + pendingChanges.pendingChanges = + 1: { "prop1": { attr: "prop1", value: 1 } } + 2: { "prop1": { attr: "prop1", value: 2 }, "prop2": { attr: "prop1", value: 4 } } + expect(pendingChanges.submitAll()).toEqual [ 1, 2, 4 ] diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 35b9e13d61..cbfc929e01 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -33,8 +33,8 @@ describe "AdminOrderMgmtCtrl", -> httpBackend.flush() expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] - expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] - expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] + expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] + expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] expect(scope.initialiseVariables.calls.length).toBe 1 expect(scope.fetchOrders.calls.length).toBe 1 @@ -350,125 +350,6 @@ describe "AdminOrderMgmtCtrl", -> spyOn(VariantUnitManager, "getUnitName").andReturn "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" -describe "managing pending changes", -> - dataSubmitter = pendingChangesService = null - - beforeEach -> - dataSubmitter = jasmine.createSpy('dataSubmitter').andReturn { - then: (thenFn) -> - thenFn({propertyName: "new_value"}) - } - - beforeEach -> - module "ofn.admin", ($provide) -> - $provide.value 'dataSubmitter', dataSubmitter - return - - beforeEach inject (pendingChanges) -> - pendingChangesService = pendingChanges - - describe "adding a new change", -> - it "adds a new object with key of id if it does not already exist", -> - expect(pendingChangesService.pendingChanges).toEqual {} - expect(pendingChangesService.pendingChanges["1"]).not.toBeDefined() - pendingChangesService.add 1, "propertyName", { a: 1 } - expect(pendingChangesService.pendingChanges["1"]).toBeDefined() - - it "adds a new object with key of the altered attribute name if it does not already exist", -> - pendingChangesService.add 1, "propertyName", { a: 1 } - expect(pendingChangesService.pendingChanges["1"]).toBeDefined() - expect(pendingChangesService.pendingChanges["1"]["propertyName"]).toEqual { a: 1 } - - it "replaces the existing object when adding a change to an attribute which already exists", -> - pendingChangesService.add 1, "propertyName", { a: 1 } - expect(pendingChangesService.pendingChanges["1"]).toBeDefined() - expect(pendingChangesService.pendingChanges["1"]["propertyName"]).toEqual { a: 1 } - pendingChangesService.add 1, "propertyName", { b: 2 } - expect(pendingChangesService.pendingChanges["1"]["propertyName"]).toEqual { b: 2 } - - it "adds an attribute to key to a line item object when one already exists", -> - pendingChangesService.add 1, "propertyName1", { a: 1 } - pendingChangesService.add 1, "propertyName2", { b: 2 } - expect(pendingChangesService.pendingChanges["1"]).toEqual { propertyName1: { a: 1}, propertyName2: { b: 2 } } - - describe "removing all existing changes", -> - it "resets pendingChanges object", -> - pendingChangesService.pendingChanges = { 1: { "propertyName1": { a: 1 }, "propertyName2": { b: 2 } } } - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).toBeDefined() - expect(pendingChangesService.pendingChanges["1"]["propertyName2"]).toBeDefined() - pendingChangesService.removeAll() - expect(pendingChangesService.pendingChanges["1"]).not.toBeDefined() - expect(pendingChangesService.pendingChanges).toEqual {} - - describe "removing an existing change", -> - it "deletes a change if it exists", -> - pendingChangesService.pendingChanges = { 1: { "propertyName1": { a: 1 }, "propertyName2": { b: 2 } } } - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).toBeDefined() - pendingChangesService.remove 1, "propertyName1" - expect(pendingChangesService.pendingChanges["1"]).toBeDefined() - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).not.toBeDefined() - - it "deletes a line item object if it is empty", -> - pendingChangesService.pendingChanges = { 1: { "propertyName1": { a: 1 } } } - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).toBeDefined() - pendingChangesService.remove 1, "propertyName1" - expect(pendingChangesService.pendingChanges["1"]).not.toBeDefined() - - it "does nothing if key with specified attribute does not exist", -> - pendingChangesService.pendingChanges = { 1: { "propertyName1": { a: 1 } } } - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).toBeDefined() - pendingChangesService.remove 1, "propertyName2" - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).toEqual { a: 1 } - - it "does nothing if key with specified id does not exist", -> - pendingChangesService.pendingChanges = { 1: { "propertyName1": { a: 1 } } } - expect(pendingChangesService.pendingChanges["1"]["propertyName1"]).toBeDefined() - pendingChangesService.remove 2, "propertyName1" - expect(pendingChangesService.pendingChanges["1"]).toEqual { "propertyName1": { a: 1 } } - - describe "submitting an individual change to the server", -> - it "sends the correct object to dataSubmitter", -> - changeObj = { element: {} } - pendingChangesService.submit 1, "propertyName", changeObj - expect(dataSubmitter.calls.length).toEqual 1 - expect(dataSubmitter).toHaveBeenCalledWith changeObj - - it "calls remove with id and attribute name", -> - changeObj = { element: {} } - spyOn(pendingChangesService, "remove").andCallFake(->) - pendingChangesService.submit 1, "propertyName", changeObj - expect(pendingChangesService.remove.calls.length).toEqual 1 - expect(pendingChangesService.remove).toHaveBeenCalledWith 1, "propertyName" - - it "resets the dbValue attribute of the element in question", -> - element = { dbValue: 2 } - changeObj = { element: element } - pendingChangesService.submit 1, "propertyName", changeObj - expect(element.dbValue).toEqual "new_value" - - describe "cycling through all changes to submit to server", -> - it "sends the correct object to dataSubmitter", -> - spyOn(pendingChangesService, "submit").andCallFake(->) - pendingChangesService.pendingChanges = - 1: { "prop1": 1, "prop2": 2 } - 2: { "prop1": 2, "prop2": 4 } - 7: { "prop2": 5 } - pendingChangesService.submitAll() - expect(pendingChangesService.submit.calls.length).toEqual 5 - expect(pendingChangesService.submit).toHaveBeenCalledWith '1', "prop1", 1 - expect(pendingChangesService.submit).toHaveBeenCalledWith '1', "prop2", 2 - expect(pendingChangesService.submit).toHaveBeenCalledWith '2', "prop1", 2 - expect(pendingChangesService.submit).toHaveBeenCalledWith '2', "prop2", 4 - expect(pendingChangesService.submit).toHaveBeenCalledWith '7', "prop2", 5 - - it "returns an array of promises representing all sumbit requests", -> - spyOn(pendingChangesService, "submit").andCallFake (id,attrName,changeObj) -> - id - pendingChangesService.pendingChanges = - 1: { "prop1": 1 } - 2: { "prop1": 2, "prop2": 4 } - expect(pendingChangesService.submitAll()).toEqual [ '1','2','2' ] - describe "dataSubmitter service", -> qMock = httpMock = {} switchClassSpy = resolveSpy = rejectSpy = dataSubmitterService = null From 640c02570d49a08a5ef233e2cb3f8dae76880dac Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 16:41:28 +1000 Subject: [PATCH 116/206] Splitting out specs for switchClass service --- .../services/switch_class_spec.js.coffee | 52 ++++++++++++++++++ .../unit/bulk_order_management_spec.js.coffee | 53 ------------------- 2 files changed, 52 insertions(+), 53 deletions(-) create mode 100644 spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee diff --git a/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee new file mode 100644 index 0000000000..e7dedb4e92 --- /dev/null +++ b/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee @@ -0,0 +1,52 @@ +describe "switchClass service", -> + elementMock = timeoutMock = {} + removeClass = addClass = switchClassService = null + + beforeEach -> + addClass = jasmine.createSpy('addClass') + removeClass = jasmine.createSpy('removeClass') + elementMock = + addClass: addClass + removeClass: removeClass + timeoutMock = jasmine.createSpy('timeout').andReturn "new timeout" + timeoutMock.cancel = jasmine.createSpy('timeout.cancel') + + beforeEach -> + module "ofn.admin" , ($provide) -> + $provide.value '$timeout', timeoutMock + return + + beforeEach inject (switchClass) -> + switchClassService = switchClass + + it "calls addClass on the element once", -> + switchClassService elementMock, "addClass", [], false + expect(addClass).toHaveBeenCalledWith "addClass" + expect(addClass.calls.length).toEqual 1 + + it "calls removeClass on the element for ", -> + switchClassService elementMock, "", ["remClass1", "remClass2", "remClass3"], false + expect(removeClass).toHaveBeenCalledWith "remClass1" + expect(removeClass).toHaveBeenCalledWith "remClass2" + expect(removeClass).toHaveBeenCalledWith "remClass3" + expect(removeClass.calls.length).toEqual 3 + + it "call cancel on element.timout only if it exists", -> + switchClassService elementMock, "", [], false + expect(timeoutMock.cancel).not.toHaveBeenCalled() + elementMock.timeout = true + switchClassService elementMock, "", [], false + expect(timeoutMock.cancel).toHaveBeenCalled() + + it "doesn't set up a new timeout if 'timeout' is false", -> + switchClassService elementMock, "class1", ["class2"], false + expect(timeoutMock).not.toHaveBeenCalled() + + it "doesn't set up a new timeout if 'timeout' is a string", -> + switchClassService elementMock, "class1", ["class2"], "string" + expect(timeoutMock).not.toHaveBeenCalled() + + it "sets up a new timeout if 'timeout' parameter is an integer", -> + switchClassService elementMock, "class1", ["class2"], 1000 + expect(timeoutMock).toHaveBeenCalled() + expect(elementMock.timeout).toEqual "new timeout" diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index cbfc929e01..87f9903e05 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -407,59 +407,6 @@ describe "dataSubmitter service", -> expect(rejectSpy.calls.length).toEqual 1 expect(switchClassSpy).toHaveBeenCalledWith element, "update-error", ["update-pending", "update-success"], false -describe "switchClass service", -> - elementMock = timeoutMock = {} - removeClass = addClass = switchClassService = null - - beforeEach -> - addClass = jasmine.createSpy('addClass') - removeClass = jasmine.createSpy('removeClass') - elementMock = - addClass: addClass - removeClass: removeClass - timeoutMock = jasmine.createSpy('timeout').andReturn "new timeout" - timeoutMock.cancel = jasmine.createSpy('timeout.cancel') - - beforeEach -> - module "ofn.admin" , ($provide) -> - $provide.value '$timeout', timeoutMock - return - - beforeEach inject (switchClass) -> - switchClassService = switchClass - - it "calls addClass on the element once", -> - switchClassService elementMock, "addClass", [], false - expect(addClass).toHaveBeenCalledWith "addClass" - expect(addClass.calls.length).toEqual 1 - - it "calls removeClass on the element for ", -> - switchClassService elementMock, "", ["remClass1", "remClass2", "remClass3"], false - expect(removeClass).toHaveBeenCalledWith "remClass1" - expect(removeClass).toHaveBeenCalledWith "remClass2" - expect(removeClass).toHaveBeenCalledWith "remClass3" - expect(removeClass.calls.length).toEqual 3 - - it "call cancel on element.timout only if it exists", -> - switchClassService elementMock, "", [], false - expect(timeoutMock.cancel).not.toHaveBeenCalled() - elementMock.timeout = true - switchClassService elementMock, "", [], false - expect(timeoutMock.cancel).toHaveBeenCalled() - - it "doesn't set up a new timeout if 'timeout' is false", -> - switchClassService elementMock, "class1", ["class2"], false - expect(timeoutMock).not.toHaveBeenCalled() - - it "doesn't set up a new timeout if 'timeout' is a string", -> - switchClassService elementMock, "class1", ["class2"], "string" - expect(timeoutMock).not.toHaveBeenCalled() - - it "sets up a new timeout if 'timeout' parameter is an integer", -> - switchClassService elementMock, "class1", ["class2"], 1000 - expect(timeoutMock).toHaveBeenCalled() - expect(elementMock.timeout).toEqual "new timeout" - describe "Auxiliary functions", -> describe "getting a zero filled two digit number", -> it "returns the number as a string if its value is greater than or equal to 10", -> From 50d0d04994962ab5f8900e699fa58847402f47f6 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 14 May 2015 11:38:55 +1000 Subject: [PATCH 117/206] Removing obsolete dataSubmitter service --- .../services/data_submitter.js.coffee | 10 --- .../services/pending_changes.js.coffee | 7 +- .../services/pending_changes_spec.js.coffee | 64 ++++++++++++++----- .../unit/bulk_order_management_spec.js.coffee | 57 ----------------- 4 files changed, 52 insertions(+), 86 deletions(-) delete mode 100644 app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee diff --git a/app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee b/app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee deleted file mode 100644 index 934ae4a07e..0000000000 --- a/app/assets/javascripts/admin/index_utils/services/data_submitter.js.coffee +++ /dev/null @@ -1,10 +0,0 @@ -angular.module("admin.indexUtils").factory "dataSubmitter", ($http, $q, resources) -> - return (change) -> - deferred = $q.defer() - resources.update(change).$promise.then (data) -> - change.scope.success() - deferred.resolve data - , -> - change.scope.error() - deferred.reject() - deferred.promise diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 86a6b2821f..2f40a7faef 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.indexUtils").factory "pendingChanges", (dataSubmitter) -> +angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> new class pendingChanges pendingChanges: {} @@ -22,9 +22,12 @@ angular.module("admin.indexUtils").factory "pendingChanges", (dataSubmitter) -> all submit: (change) -> - dataSubmitter(change).then (data) => + resources.update(change).$promise.then (data) => @remove change.object.id, change.attr change.scope.reset( data["#{change.attr}"] ) + change.scope.success() + , (error) -> + change.scope.error() changeCount: (objectChanges) -> Object.keys(objectChanges).length diff --git a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee index 2acf684754..31b85df217 100644 --- a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee @@ -1,13 +1,17 @@ describe "Pending Changes", -> - dataSubmitter = pendingChanges = null + resourcesMock = pendingChanges = null beforeEach -> - dataSubmitter = jasmine.createSpy('dataSubmitter').andReturn { - then: (thenFn) -> - thenFn({propertyName: "new_value"}) - } + + resourcesMock = + update: jasmine.createSpy('update').andCallFake (change) -> + $promise: + then: (successFn, errorFn) -> + return successFn({propertyName: "new_value"}) if change.success + errorFn("error") + module 'admin.indexUtils', ($provide) -> - $provide.value 'dataSubmitter', dataSubmitter + $provide.value 'resources', resourcesMock return inject (_pendingChanges_) -> @@ -77,24 +81,50 @@ describe "Pending Changes", -> change = null beforeEach -> object = {id: 1} - scope = { reset: jasmine.createSpy('reset') }; + scope = { reset: jasmine.createSpy('reset'), success: jasmine.createSpy('success'), error: jasmine.createSpy('error') }; attr = "propertyName" change = { object: object, scope: scope, attr: attr } + it "sends the correct object to dataSubmitter", -> pendingChanges.submit change - expect(dataSubmitter.calls.length).toEqual 1 - expect(dataSubmitter).toHaveBeenCalledWith change + expect(resourcesMock.update.calls.length).toEqual 1 + expect(resourcesMock.update).toHaveBeenCalledWith change - it "calls remove with id and attribute name", -> - spyOn(pendingChanges, "remove").andCallFake(->) - pendingChanges.submit change - expect(pendingChanges.remove.calls.length).toEqual 1 - expect(pendingChanges.remove).toHaveBeenCalledWith 1, "propertyName" + describe "successful request", -> + beforeEach -> + change.success = true - it "calls reset on the relevant scope", -> - pendingChanges.submit change - expect(change.scope.reset).toHaveBeenCalledWith "new_value" + it "calls remove with id and attribute name", -> + spyOn(pendingChanges, "remove").andCallFake(->) + pendingChanges.submit change + expect(pendingChanges.remove.calls.length).toEqual 1 + expect(pendingChanges.remove).toHaveBeenCalledWith 1, "propertyName" + + it "calls reset on the relevant scope", -> + pendingChanges.submit change + expect(change.scope.reset).toHaveBeenCalledWith "new_value" + + it "calls success on the relevant scope", -> + pendingChanges.submit change + expect(change.scope.success).toHaveBeenCalled() + + describe "unsuccessful request", -> + beforeEach -> + change.success = false + + it "does not call remove", -> + spyOn(pendingChanges, "remove").andCallFake(->) + pendingChanges.submit change + expect(pendingChanges.remove).not.toHaveBeenCalled() + + it "does not call reset on the relevant scope", -> + pendingChanges.submit change + expect(change.scope.reset).not.toHaveBeenCalled() + + it "calls error on the relevant scope", -> + pendingChanges.submit change + expect(change.scope.error).toHaveBeenCalled() describe "cycling through all changes to submit to server", -> it "sends the correct object to dataSubmitter", -> diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 87f9903e05..abf0b93bd7 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -350,63 +350,6 @@ describe "AdminOrderMgmtCtrl", -> spyOn(VariantUnitManager, "getUnitName").andReturn "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" -describe "dataSubmitter service", -> - qMock = httpMock = {} - switchClassSpy = resolveSpy = rejectSpy = dataSubmitterService = null - - beforeEach -> - resolveSpy = jasmine.createSpy('resolve') - rejectSpy = jasmine.createSpy('reject') - qMock.defer = -> - resolve: resolveSpy - reject: rejectSpy - promise: "promise1" - - # Can't use httpBackend because the qMock interferes with it - httpMock.put = (url) -> - success: (successFn) -> - successFn("somedata") if url == "successURL" - error: (errorFn) -> - errorFn() if url == "errorURL" - - spyOn(httpMock, "put").andCallThrough() - spyOn(qMock, "defer").andCallThrough() - - switchClassSpy = jasmine.createSpy('switchClass') - - beforeEach -> - module "ofn.admin" , ($provide) -> - $provide.value '$q', qMock - $provide.value '$http', httpMock - $provide.value 'switchClass', switchClassSpy - return - - beforeEach inject (dataSubmitter) -> - dataSubmitterService = dataSubmitter - - it "returns a promise", -> - expect(dataSubmitterService( { url: "successURL" } )).toEqual "promise1" - expect(qMock.defer).toHaveBeenCalled() - - it "sends a PUT request with the url property of changeObj", -> - dataSubmitterService { url: "successURL" } - expect(httpMock.put).toHaveBeenCalledWith "successURL" - - it "calls resolve on deferred object when request is successful", -> - element = { a: 1 } - dataSubmitterService { url: "successURL", element: element } - expect(resolveSpy.calls.length).toEqual 1 - expect(rejectSpy.calls.length).toEqual 0 - expect(resolveSpy).toHaveBeenCalledWith "somedata" - expect(switchClassSpy).toHaveBeenCalledWith element, "update-success", ["update-pending", "update-error"], 3000 - - it "calls reject on deferred object when request is erroneous", -> - element = { b: 2 } - dataSubmitterService { url: "errorURL", element: element } - expect(resolveSpy.calls.length).toEqual 0 - expect(rejectSpy.calls.length).toEqual 1 - expect(switchClassSpy).toHaveBeenCalledWith element, "update-error", ["update-pending", "update-success"], false - describe "Auxiliary functions", -> describe "getting a zero filled two digit number", -> it "returns the number as a string if its value is greater than or equal to 10", -> From 78fc3e376bb36482c88ee1cd91c07fced7cdcf20 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 14 May 2015 11:39:37 +1000 Subject: [PATCH 118/206] Fixing styling of inputs for with pending/success/error status --- app/assets/stylesheets/admin/orders.css.scss | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 249447757c..b1327630c6 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -8,16 +8,22 @@ } } -.update-pending { - border: solid 1px orange; +input, div { + &.update-pending { + border: solid 1px orange; + } } -.update-error { - border: solid 1px red; +input, div { + &.update-error { + border: solid 1px red; + } } -.update-success { - border: solid 1px #9fc820; +input, div { + &.update-success { + border: solid 1px #9fc820; + } } .no-close .ui-dialog-titlebar-close { From c8502747beebbdfb57284139e465a6932c155b01 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 14 May 2015 12:37:55 +1000 Subject: [PATCH 119/206] Fixing layout of customers index --- .../controllers/customers_controller.js.coffee | 3 ++- app/views/admin/customers/index.html.haml | 15 +++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index 0e82806ef9..be526a4349 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -8,7 +8,8 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, Customers code: { name: "Code", visible: true } tags: { name: "Tags", visible: true } - $scope.initialise = -> + $scope.$watch "shop", -> + Customers.loaded = false $scope.customers = Customers.index(enterprise_id: $scope.shop.id) $scope.loaded = -> diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index f3e82f9cdf..66790a34df 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -4,15 +4,14 @@ = admin_inject_shops %div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } } - .row{ ng: { hide: "loaded()" } } - .two.columns.alpha - Hub + .row{ ng: { hide: "loaded() && filteredCustomers.length > 0" } } + .five.columns.alpha + %h3 Please select a Hub: .four.columns %select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' } - .ten.columns.omega - %input{ type: 'button', value: 'Go', ng: { click: 'initialise()' } } + .seven.columns.omega   - .row{ 'ng-hide' => '!loaded() || lineItems.length == 0' } + .row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' } .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } .five.columns.alpha %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } @@ -32,7 +31,7 @@ %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} %span{ :class => 'two columns omega' } {{column.name }} - .row{ 'ng-if' => 'shop_id && !loaded()' } + .row{ 'ng-if' => 'shop && !loaded()' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 LOADING CUSTOMERS @@ -40,7 +39,7 @@ %h1#no_results No customers found. - .row{ ng: { show: "loaded()" } } + .row{ ng: { show: "loaded() && filteredCustomers.length > 0" } } %form{ name: "customers" } %table.index#customers %col.email{ width: "20%"} From ef064819f9943a81066f0785edb8d2ef8ef096f9 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 15 May 2015 10:41:29 +1000 Subject: [PATCH 120/206] Add spec for order_cycle_management report access --- spec/models/spree/ability_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 0dc9364967..9ed432e4af 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -213,7 +213,7 @@ module Spree end it "should be able to read some reports" do - should have_ability([:admin, :index, :customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory], for: :report) + should have_ability([:admin, :index, :customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], for: :report) end it "should not be able to read other reports" do @@ -400,7 +400,7 @@ module Spree end it "should be able to read some reports" do - should have_ability([:admin, :index, :customers, :sales_tax, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], for: :report) + 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], for: :report) end it "should not be able to read other reports" do From 312a6299a84d85dc1b5b87291ea3dc7e3edabdb3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 15 May 2015 21:19:16 +1000 Subject: [PATCH 121/206] Making where clause unambiguous --- app/controllers/spree/admin/reports_controller_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 3aa4f560a1..34f2d640c2 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -416,7 +416,7 @@ Spree::Admin::ReportsController.class_eval do @line_items = permissions.visible_line_items.merge(Spree::LineItem.where(order_id: orders)) @line_items = @line_items.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present? - line_items_with_hidden_details = @line_items.where("id NOT IN (?)", permissions.editable_line_items) + line_items_with_hidden_details = @line_items.where('"spree_line_items"."id" NOT IN (?)', permissions.editable_line_items) @line_items.select{ |li| line_items_with_hidden_details.include? li }.each do |line_item| # TODO We should really be hiding customer code here too, but until we # have an actual association between order and customer, it's a bit tricky From 6b956a8a38c904347fd95a482b80f12eed3a1377 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 May 2015 10:19:37 +1000 Subject: [PATCH 122/206] Updating product clone spec --- .../unit/admin/services/bulk_products_spec.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee index 0b3c0841a6..54b57aed48 100644 --- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee @@ -68,7 +68,7 @@ describe "BulkProducts service", -> $httpBackend.expectGET("/api/products/17?template=bulk_show").respond 200, clonedProduct BulkProducts.cloneProduct BulkProducts.products[0] $httpBackend.flush() - expect(BulkProducts.addProducts).toHaveBeenCalledWith [clonedProduct] + expect(BulkProducts.insertProductAfter).toHaveBeenCalledWith originalProduct, clonedProduct describe "preparing products", -> From 828456118b23429d9c48696fb88e4fdc08d5f697 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 May 2015 11:15:18 +1000 Subject: [PATCH 123/206] Remove forgotten binding.pry --- app/models/spree/ability_decorator.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 7bc6fb6535..e652825911 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -154,7 +154,6 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization can [:destroy], Spree::Adjustment do |adjustment| # Sharing code with destroying a line item. This should be unified and probably applied for other actions as well. - binding.pry if user.admin? true elsif adjustment.adjustable.instance_of? Spree::Order From 7f80c02c0ea0736cca16117823dc48c55b9e4240 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 11:45:05 +1000 Subject: [PATCH 124/206] Adding route for managed route for admin orders --- config/routes.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 4621ee4a35..f8b70ba4d6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -169,6 +169,10 @@ Spree::Core::Engine.routes.prepend do post :bulk_update, :on => :collection, :as => :bulk_update end + + resources :orders do + get :managed, on: :collection + end end resources :orders do From 823adf32721a1cd0d44ea18497f79b5d43b03f5b Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 11:45:48 +1000 Subject: [PATCH 125/206] Translating existing order-related rabl templates accross to AMS --- .../api/admin/basic_order_cycle_serializer.rb | 14 +++++++++ .../api/admin/line_item_serializer.rb | 15 +++++++++ app/serializers/api/admin/order_serializer.rb | 31 +++++++++++++++++++ .../api/admin/units_product_serializer.rb | 3 ++ .../api/admin/units_variant_serializer.rb | 8 +++++ 5 files changed, 71 insertions(+) create mode 100644 app/serializers/api/admin/basic_order_cycle_serializer.rb create mode 100644 app/serializers/api/admin/line_item_serializer.rb create mode 100644 app/serializers/api/admin/order_serializer.rb create mode 100644 app/serializers/api/admin/units_product_serializer.rb create mode 100644 app/serializers/api/admin/units_variant_serializer.rb diff --git a/app/serializers/api/admin/basic_order_cycle_serializer.rb b/app/serializers/api/admin/basic_order_cycle_serializer.rb new file mode 100644 index 0000000000..e94795821a --- /dev/null +++ b/app/serializers/api/admin/basic_order_cycle_serializer.rb @@ -0,0 +1,14 @@ +class Api::Admin::BasicOrderCycleSerializer < ActiveModel::Serializer + attributes :id, :name, :first_order, :last_order + + has_many :suppliers, serializer: Api::Admin::IdNameSerializer + has_many :distributors, serializer: Api::Admin::IdNameSerializer + + def first_order + object.orders_open_at.strftime("%F") + end + + def last_order + (object.orders_close_at + 1.day).strftime("%F") + end +end diff --git a/app/serializers/api/admin/line_item_serializer.rb b/app/serializers/api/admin/line_item_serializer.rb new file mode 100644 index 0000000000..784e1e8be3 --- /dev/null +++ b/app/serializers/api/admin/line_item_serializer.rb @@ -0,0 +1,15 @@ +class Api::Admin::LineItemSerializer < ActiveModel::Serializer + attributes :id, :quantity, :max_quantity, :supplier, :units_product, :units_variant + + def supplier + Api::Admin::IdNameSerializer.new(object.product.supplier).serializable_hash + end + + def units_product + Api::Admin::UnitsProductSerializer.new(object.product).serializable_hash + end + + def units_variant + Api::Admin::UnitsVariantSerializer.new(object.variant).serializable_hash + end +end diff --git a/app/serializers/api/admin/order_serializer.rb b/app/serializers/api/admin/order_serializer.rb new file mode 100644 index 0000000000..2277551989 --- /dev/null +++ b/app/serializers/api/admin/order_serializer.rb @@ -0,0 +1,31 @@ +class Api::Admin::OrderSerializer < ActiveModel::Serializer + attributes :id, :number, :full_name, :email, :phone, :completed_at, :line_items + + has_one :distributor, serializer: Api::Admin::IdNameSerializer + has_one :order_cycle, serializer: Api::Admin::BasicOrderCycleSerializer + + def full_name + object.billing_address.nil? ? "" : ( object.billing_address.full_name || "" ) + end + + def email + object.email || "" + end + + def phone + object.billing_address.nil? ? "a" : ( object.billing_address.phone || "" ) + end + + def completed_at + object.completed_at.blank? ? "" : object.completed_at.strftime("%F %T") + end + + def line_items + # we used to have a scope here, but we are at the point where a user which can edit an order + # should be able to edit all of the line_items as well, making the scope redundant + ActiveModel::ArraySerializer.new( + object.line_items.order('id ASC'), + {each_serializer: Api::Admin::LineItemSerializer} + ) + end +end diff --git a/app/serializers/api/admin/units_product_serializer.rb b/app/serializers/api/admin/units_product_serializer.rb new file mode 100644 index 0000000000..37521b2bb9 --- /dev/null +++ b/app/serializers/api/admin/units_product_serializer.rb @@ -0,0 +1,3 @@ +class Api::Admin::UnitsProductSerializer < ActiveModel::Serializer + attributes :id, :name, :group_buy_unit_size, :variant_unit +end diff --git a/app/serializers/api/admin/units_variant_serializer.rb b/app/serializers/api/admin/units_variant_serializer.rb new file mode 100644 index 0000000000..e96f9299f6 --- /dev/null +++ b/app/serializers/api/admin/units_variant_serializer.rb @@ -0,0 +1,8 @@ +class Api::Admin::UnitsVariantSerializer < ActiveModel::Serializer + attributes :id, :unit_text, :unit_value + + def unit_text + options_text = object.options_text + object.product.name + (options_text.empty? ? "" : ": #{options_text}") + end +end From fc55a000b87e73cee57129cbf689edccbd620b5b Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 11:48:13 +1000 Subject: [PATCH 126/206] Adding managed controller action which uses new order serializer to render json --- .../admin/orders_controller_decorator.rb | 10 +- app/models/spree/ability_decorator.rb | 3 +- .../spree/admin/orders_controller_spec.rb | 139 +++++++++++++++++- 3 files changed, 147 insertions(+), 5 deletions(-) diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index 42e7068afc..247e780deb 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -7,7 +7,7 @@ Spree::Admin::OrdersController.class_eval do # We need to add expections for collection actions other than :index here # because spree_auth_devise causes load_order to be called, which results # in an auth failure as the @order object is nil for collection actions - before_filter :check_authorization, :except => :bulk_management + before_filter :check_authorization, except: [:bulk_management, :managed] # After updating an order, the fees should be updated as well # Currently, adding or deleting line items does not trigger updating the @@ -17,7 +17,7 @@ Spree::Admin::OrdersController.class_eval do after_filter :update_distribution_charge, :only => :update respond_override :index => { :html => - { :success => lambda { + { :success => lambda { # Filter orders to only show those distributed by current user (or all for admin user) @orders = @search.result.includes([:user, :shipments, :payments]). distributed_by_user(spree_current_user). @@ -37,4 +37,10 @@ Spree::Admin::OrdersController.class_eval do def update_distribution_charge @order.update_distribution_charge! end + + def managed + permissions = OpenFoodNetwork::Permissions.new(spree_current_user) + @orders = permissions.editable_orders.ransack(params[:q]).result.page(params[:page]).per(params[:per_page]) + render json: @orders, each_serializer: Api::Admin::OrderSerializer + end end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 7bc6fb6535..13fe55a852 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -142,7 +142,7 @@ class AbilityDecorator # during the order creation process from the admin backend order.distributor.nil? || user.enterprises.include?(order.distributor) end - can [:admin, :bulk_management], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor) + can [:admin, :bulk_management, :managed], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor) can [:admin, :create], Spree::LineItem can [:destroy], Spree::LineItem do |item| user.admin? || user.enterprises.include?(order.distributor) || user == order.order_cycle.manager @@ -154,7 +154,6 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization can [:destroy], Spree::Adjustment do |adjustment| # Sharing code with destroying a line item. This should be unified and probably applied for other actions as well. - binding.pry if user.admin? true elsif adjustment.adjustable.instance_of? Spree::Order diff --git a/spec/controllers/spree/admin/orders_controller_spec.rb b/spec/controllers/spree/admin/orders_controller_spec.rb index 5724732c50..ac6a5532b2 100644 --- a/spec/controllers/spree/admin/orders_controller_spec.rb +++ b/spec/controllers/spree/admin/orders_controller_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' describe Spree::Admin::OrdersController do - let!(:order) { create(:order) } + include AuthenticationWorkflow context "updating an order with line items" do + let!(:order) { create(:order) } let(:line_item) { create(:line_item) } before { login_as_admin } @@ -27,4 +28,140 @@ describe Spree::Admin::OrdersController do } end end + + describe "managed" do + render_views + + let(:order_attributes) { [:id, :full_name, :email, :phone, :completed_at, :line_items, :distributor, :order_cycle, :number] } + + def self.make_simple_data! + let!(:dist1) { FactoryGirl.create(:distributor_enterprise) } + let!(:order1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) } + let!(:order2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) } + let!(:order3) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) } + let!(:line_item1) { FactoryGirl.create(:line_item, order: order1) } + let!(:line_item2) { FactoryGirl.create(:line_item, order: order2) } + let!(:line_item3) { FactoryGirl.create(:line_item, order: order2) } + let!(:line_item4) { FactoryGirl.create(:line_item, order: order3) } + let(:line_item_attributes) { [:id, :quantity, :max_quantity, :supplier, :units_product, :units_variant] } + end + + context "as a normal user" do + before { controller.stub spree_current_user: create_enterprise_user } + + make_simple_data! + + it "should deny me access to managed orders" do + spree_get :managed, { :template => 'bulk_index', :format => :json } + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "as an administrator" do + make_simple_data! + + before do + controller.stub spree_current_user: quick_login_as_admin + spree_get :managed, { :template => 'bulk_index', :format => :json } + end + + it "retrieves a list of orders with appropriate attributes, including line items with appropriate attributes" do + keys = json_response.first.keys.map{ |key| key.to_sym } + order_attributes.all?{ |attr| keys.include? attr }.should == true + end + + it "retrieves a list of line items with appropriate attributes" do + li_keys = json_response.first['line_items'].first.keys.map{ |key| key.to_sym } + line_item_attributes.all?{ |attr| li_keys.include? attr }.should == true + end + + it "sorts orders in ascending id order" do + ids = json_response.map{ |order| order['id'] } + ids[0].should < ids[1] + ids[1].should < ids[2] + end + + it "formats completed_at to 'yyyy-mm-dd hh:mm'" do + json_response.map{ |order| order['completed_at'] }.all?{ |a| a.match("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") }.should == true + end + + it "returns an array for line_items" do + json_response.map{ |order| order['line_items'] }.all?{ |a| a.is_a? Array }.should == true + end + + it "returns quantity and max quantity at integers" do + json_response.map{ |order| order['line_items'] }.flatten.map{ |li| li['quantity'] }.all?{ |q| q.is_a? Fixnum }.should == true + json_response.map{ |order| order['line_items'] }.flatten.map{ |li| li['max_quantity'] }.all?{ |mq| mq.nil? || mq.is_a?( Fixnum ) }.should == true + end + + it "returns supplier object with id and name keys" do + json_response.map{ |order| order['line_items'] }.flatten.map{ |li| li['supplier'] }.all?{ |s| s.has_key?('id') && s.has_key?('name') }.should == true + end + + it "returns distributor object with id and name keys" do + json_response.map{ |order| order['distributor'] }.all?{ |d| d.has_key?('id') && d.has_key?('name') }.should == true + end + + it "retrieves the order number" do + json_response.map{ |order| order['number'] }.all?{ |number| number.match("^R\\d{5,10}$") }.should == true + end + end + + context "as an enterprise user" do + let(:supplier) { create(:supplier_enterprise) } + let(:distributor1) { create(:distributor_enterprise) } + let(:distributor2) { create(:distributor_enterprise) } + let(:coordinator) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order1) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) } + let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) } + let!(:line_item2) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) } + let!(:order2) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.now, distributor: distributor2, billing_address: FactoryGirl.create(:address) ) } + let!(:line_item3) { FactoryGirl.create(:line_item, order: order2, product: FactoryGirl.create(:product, supplier: supplier)) } + + context "producer enterprise" do + + before do + controller.stub spree_current_user: supplier.owner + spree_get :managed, { :format => :json } + end + + it "does not display line items for which my enterprise is a supplier" do + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "coordinator enterprise" do + before do + controller.stub spree_current_user: coordinator.owner + spree_get :managed, { :format => :json } + end + + it "retrieves a list of orders" do + keys = json_response.first.keys.map{ |key| key.to_sym } + order_attributes.all?{ |attr| keys.include? attr }.should == true + end + + it "only displays line items from orders for which my enterprise is the order_cycle coorinator" do + json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.sort.should == [line_item1.id, line_item2.id, line_item3.id].sort + end + end + + context "hub enterprise" do + before do + controller.stub spree_current_user: distributor1.owner + spree_get :managed, { :format => :json } + end + + it "retrieves a list of orders" do + keys = json_response.first.keys.map{ |key| key.to_sym } + order_attributes.all?{ |attr| keys.include? attr }.should == true + end + + it "only displays line items from orders for which my enterprise is a distributor" do + json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.sort.should == [line_item1.id, line_item2.id].sort + end + end + end + end end From 0ad2978926f17e663b96bafffe4f8365e660af50 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 11:48:48 +1000 Subject: [PATCH 127/206] Removing old managed route from api orders controller and switching BOM over to use new controller action --- .../admin/bulk_order_management.js.coffee | 2 +- .../spree/api/orders_controller_decorator.rb | 8 -- .../spree/api/orders_controller_spec.rb | 121 ------------------ .../unit/bulk_order_management_spec.js.coffee | 2 +- 4 files changed, 2 insertions(+), 131 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index 20ddc177ba..076dd55e3a 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -62,7 +62,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.fetchOrders = -> $scope.loading = true - dataFetcher("/api/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) -> + dataFetcher("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) -> $scope.resetOrders data $scope.loading = false diff --git a/app/controllers/spree/api/orders_controller_decorator.rb b/app/controllers/spree/api/orders_controller_decorator.rb index ca1fc4a570..21f64f51d8 100644 --- a/app/controllers/spree/api/orders_controller_decorator.rb +++ b/app/controllers/spree/api/orders_controller_decorator.rb @@ -4,12 +4,4 @@ Spree::Api::OrdersController.class_eval do # because Spree's API controller causes authorize_read! to be called, which # results in an ActiveRecord::NotFound Exception as the order object is not # defined for collection actions - before_filter :authorize_read!, :except => [:managed] - - def managed - authorize! :admin, Spree::Order - authorize! :read, Spree::Order - @orders = Spree::Order.ransack(params[:q]).result.distributed_by_user(current_api_user).page(params[:page]).per(params[:per_page]) - respond_with(@orders, default_template: :index) - end end diff --git a/spec/controllers/spree/api/orders_controller_spec.rb b/spec/controllers/spree/api/orders_controller_spec.rb index ec87169f76..5b651016f3 100644 --- a/spec/controllers/spree/api/orders_controller_spec.rb +++ b/spec/controllers/spree/api/orders_controller_spec.rb @@ -3,127 +3,6 @@ require 'spree/api/testing_support/helpers' module Spree describe Spree::Api::OrdersController do - include Spree::Api::TestingSupport::Helpers - render_views - before do - stub_authentication! - Spree.user_class.stub :find_by_spree_api_key => current_api_user - end - - let(:order_attributes) { [:id, :full_name, :email, :phone, :completed_at, :line_items, :distributor, :order_cycle, :number] } - - def self.make_simple_data! - let!(:dist1) { FactoryGirl.create(:distributor_enterprise) } - let!(:order1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) } - let!(:order2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) } - let!(:order3) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) } - let!(:line_item1) { FactoryGirl.create(:line_item, order: order1) } - let!(:line_item2) { FactoryGirl.create(:line_item, order: order2) } - let!(:line_item3) { FactoryGirl.create(:line_item, order: order2) } - let!(:line_item4) { FactoryGirl.create(:line_item, order: order3) } - let(:line_item_attributes) { [:id, :quantity, :max_quantity, :supplier, :units_product, :units_variant] } - end - - - context "as a normal user" do - sign_in_as_user! - make_simple_data! - - it "should deny me access to managed orders" do - spree_get :managed, { :template => 'bulk_index', :format => :json } - assert_unauthorized! - end - end - - context "as an administrator" do - sign_in_as_admin! - make_simple_data! - - before :each do - spree_get :managed, { :template => 'bulk_index', :format => :json } - end - - it "retrieves a list of orders with appropriate attributes, including line items with appropriate attributes" do - keys = json_response.first.keys.map{ |key| key.to_sym } - order_attributes.all?{ |attr| keys.include? attr }.should == true - end - - it "retrieves a list of line items with appropriate attributes" do - li_keys = json_response.first['line_items'].first.keys.map{ |key| key.to_sym } - line_item_attributes.all?{ |attr| li_keys.include? attr }.should == true - end - - it "sorts orders in ascending id order" do - ids = json_response.map{ |order| order['id'] } - ids[0].should < ids[1] - ids[1].should < ids[2] - end - - it "formats completed_at to 'yyyy-mm-dd hh:mm'" do - json_response.map{ |order| order['completed_at'] }.all?{ |a| a.match("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") }.should == true - end - - it "returns an array for line_items" do - json_response.map{ |order| order['line_items'] }.all?{ |a| a.is_a? Array }.should == true - end - - it "returns quantity and max quantity at integers" do - json_response.map{ |order| order['line_items'] }.flatten.map{ |li| li['quantity'] }.all?{ |q| q.is_a? Fixnum }.should == true - json_response.map{ |order| order['line_items'] }.flatten.map{ |li| li['max_quantity'] }.all?{ |mq| mq.nil? || mq.is_a?( Fixnum ) }.should == true - end - - it "returns supplier object with id and name keys" do - json_response.map{ |order| order['line_items'] }.flatten.map{ |li| li['supplier'] }.all?{ |s| s.has_key?('id') && s.has_key?('name') }.should == true - end - - it "returns distributor object with id and name keys" do - json_response.map{ |order| order['distributor'] }.all?{ |d| d.has_key?('id') && d.has_key?('name') }.should == true - end - - it "retrieves the order number" do - json_response.map{ |order| order['number'] }.all?{ |number| number.match("^R\\d{5,10}$") }.should == true - end - end - - context "as an enterprise user" do - let(:supplier) { create(:supplier_enterprise) } - let(:distributor1) { create(:distributor_enterprise) } - let(:distributor2) { create(:distributor_enterprise) } - let!(:order1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) } - let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) } - let!(:line_item2) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) } - let!(:order2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: distributor2, billing_address: FactoryGirl.create(:address) ) } - let!(:line_item3) { FactoryGirl.create(:line_item, order: order2, product: FactoryGirl.create(:product, supplier: supplier)) } - - context "producer enterprise" do - sign_in_as_enterprise_user! [:supplier] - - before :each do - spree_get :managed, { :template => 'bulk_index', :format => :json } - end - - it "does not display line item for which my enteprise is a supplier" do - response.status.should == 401 - end - end - - context "hub enterprise" do - sign_in_as_enterprise_user! [:distributor1] - - before :each do - spree_get :managed, { :template => 'bulk_index', :format => :json } - end - - it "retrieves a list of orders" do - keys = json_response.first.keys.map{ |key| key.to_sym } - order_attributes.all?{ |attr| keys.include? attr }.should == true - end - - it "only displays line items from orders for which my enterprise is a distributor" do - json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.should == [line_item1.id, line_item2.id] - end - end - end end end diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 3377c799db..71ebb9f3b9 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -43,7 +43,7 @@ describe "AdminOrderMgmtCtrl", -> describe "fetching orders", -> beforeEach -> scope.initialiseVariables() - httpBackend.expectGET("/api/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=SomeDate;q[completed_at_lt]=SomeDate").respond "list of orders" + httpBackend.expectGET("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=SomeDate;q[completed_at_lt]=SomeDate").respond "list of orders" it "makes a call to dataFetcher, with current start and end date parameters", -> scope.fetchOrders() From c56efabfbecb39bb15bb12c8b9e02556815f5537 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 11:52:49 +1000 Subject: [PATCH 128/206] Removing obsolete rabl templates --- app/views/spree/api/line_items/bulk_show.v1.rabl | 5 ----- app/views/spree/api/orders/bulk_index.v1.rabl | 2 -- app/views/spree/api/orders/bulk_show.v1.rabl | 14 -------------- app/views/spree/api/products/units_show.v1.rabl | 2 -- app/views/spree/api/variants/units_show.v1.rabl | 9 --------- 5 files changed, 32 deletions(-) delete mode 100644 app/views/spree/api/line_items/bulk_show.v1.rabl delete mode 100644 app/views/spree/api/orders/bulk_index.v1.rabl delete mode 100644 app/views/spree/api/orders/bulk_show.v1.rabl delete mode 100644 app/views/spree/api/products/units_show.v1.rabl delete mode 100644 app/views/spree/api/variants/units_show.v1.rabl diff --git a/app/views/spree/api/line_items/bulk_show.v1.rabl b/app/views/spree/api/line_items/bulk_show.v1.rabl deleted file mode 100644 index 8b53c42086..0000000000 --- a/app/views/spree/api/line_items/bulk_show.v1.rabl +++ /dev/null @@ -1,5 +0,0 @@ -object @line_item -attributes :id, :quantity, :max_quantity -node( :supplier ) { |li| partial 'api/enterprises/bulk_show', :object => li.product.supplier } -node( :units_product ) { |li| partial 'spree/api/products/units_show', :object => li.product } -node( :units_variant ) { |li| partial 'spree/api/variants/units_show', :object => li.variant } \ No newline at end of file diff --git a/app/views/spree/api/orders/bulk_index.v1.rabl b/app/views/spree/api/orders/bulk_index.v1.rabl deleted file mode 100644 index 384f518fcf..0000000000 --- a/app/views/spree/api/orders/bulk_index.v1.rabl +++ /dev/null @@ -1,2 +0,0 @@ -collection @orders.order('id ASC') -extends "spree/api/orders/bulk_show" \ No newline at end of file diff --git a/app/views/spree/api/orders/bulk_show.v1.rabl b/app/views/spree/api/orders/bulk_show.v1.rabl deleted file mode 100644 index 9addbbec6f..0000000000 --- a/app/views/spree/api/orders/bulk_show.v1.rabl +++ /dev/null @@ -1,14 +0,0 @@ -object @order -attributes :id, :number - -node( :full_name ) { |order| order.billing_address.nil? ? "" : ( order.billing_address.full_name || "" ) } -node( :email ) { |order| order.email || "" } -node( :phone ) { |order| order.billing_address.nil? ? "a" : ( order.billing_address.phone || "" ) } -node( :completed_at ) { |order| order.completed_at.blank? ? "" : order.completed_at.strftime("%F %T") } -node( :distributor ) { |order| partial 'api/enterprises/bulk_show', :object => order.distributor } -node( :order_cycle ) { |order| partial 'api/order_cycles/bulk_show', :object => order.order_cycle } -node( :line_items ) do |order| - order.line_items.managed_by(@current_api_user).order('id ASC').map do |line_item| - partial 'spree/api/line_items/bulk_show', :object => line_item - end -end \ No newline at end of file diff --git a/app/views/spree/api/products/units_show.v1.rabl b/app/views/spree/api/products/units_show.v1.rabl deleted file mode 100644 index 2108609a33..0000000000 --- a/app/views/spree/api/products/units_show.v1.rabl +++ /dev/null @@ -1,2 +0,0 @@ -object @product -attributes :id, :name, :group_buy_unit_size, :variant_unit \ No newline at end of file diff --git a/app/views/spree/api/variants/units_show.v1.rabl b/app/views/spree/api/variants/units_show.v1.rabl deleted file mode 100644 index 634213e53f..0000000000 --- a/app/views/spree/api/variants/units_show.v1.rabl +++ /dev/null @@ -1,9 +0,0 @@ -object @variant -attributes :id - -node( :unit_text ) do |v| - options_text = v.options_text - v.product.name + (options_text.empty? ? "" : ": #{options_text}") -end - -node( :unit_value ) { |v| v.unit_value } From 8d73b2f53258a00e67c784cd74a2ab423945d39d Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 13:47:01 +1000 Subject: [PATCH 129/206] involving... order cycle scopes return distinct OCs --- app/models/order_cycle.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index d16f1d7b8b..88e4953edc 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -70,7 +70,8 @@ class OrderCycle < ActiveRecord::Base # Order cycles where I managed an enterprise at either end of an outgoing exchange # ie. coordinator or distibutor joins(:exchanges).merge(Exchange.outgoing). - where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises) + where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises). + select('DISTINCT order_cycles.*') } scope :involving_managed_producers_of, lambda { |user| @@ -79,7 +80,8 @@ class OrderCycle < ActiveRecord::Base # Order cycles where I managed an enterprise at either end of an incoming exchange # ie. coordinator or producer joins(:exchanges).merge(Exchange.incoming). - where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises) + where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises). + select('DISTINCT order_cycles.*') } def self.first_opening_for(distributor) From 0a0bb67277ed6ee1572b71fe6317320ad4663444 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 May 2015 14:05:14 +1000 Subject: [PATCH 130/206] No SKU for cloned products. Community topic 175 --- app/models/spree/product_decorator.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 7846a0c1e1..fcaf30c44c 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -108,6 +108,12 @@ Spree::Product.class_eval do # -- Methods + # Called by Spree::Product::duplicate before saving. + def duplicate_extra(parent) + # Spree sets the SKU to "COPY OF #{parent sku}". + self.master.sku = '' + end + def properties_including_inherited # Product properties override producer properties ps = product_properties.all From 05131de1ad65897098f4c5f6125de2daa1af0aef Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 14:11:31 +1000 Subject: [PATCH 131/206] Use full_name on BOM instead of options_text --- app/serializers/api/admin/units_variant_serializer.rb | 8 ++++---- app/views/spree/admin/orders/bulk_management.html.haml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/serializers/api/admin/units_variant_serializer.rb b/app/serializers/api/admin/units_variant_serializer.rb index e96f9299f6..dc27e3adc3 100644 --- a/app/serializers/api/admin/units_variant_serializer.rb +++ b/app/serializers/api/admin/units_variant_serializer.rb @@ -1,8 +1,8 @@ class Api::Admin::UnitsVariantSerializer < ActiveModel::Serializer - attributes :id, :unit_text, :unit_value + attributes :id, :full_name, :unit_value - def unit_text - options_text = object.options_text - object.product.name + (options_text.empty? ? "" : ": #{options_text}") + def full_name + full_name = object.full_name + object.product.name + (full_name.empty? ? "" : ": #{full_name}") end end diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 61bf9fbb7e..0f9bd05d04 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -43,7 +43,7 @@ Shared Resource? %div{ :class => "eight columns" } %h6{ :class => "eight columns alpha", 'ng-show' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsProduct.name + ": ALL" }} - %h6{ :class => "eight columns alpha", 'ng-hide' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsVariant.unit_text }} + %h6{ :class => "eight columns alpha", 'ng-hide' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsVariant.full_name }} %div{ :class => "four columns omega" } %h6{ :class => "four columns alpha", :style => 'text-align: right;' } %a{ :href => '#', 'ng-click' => 'selectedUnitsVariant = {};selectedUnitsProduct = {};sharedResource=false;' } Clear @@ -122,7 +122,7 @@ %th.hub{ 'ng-show' => 'columns.hub.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } Hub %th.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'units_variant.unit_text'; reverse = !reverse" } Product: Unit + %a{ :href => '', 'ng-click' => "predicate = 'units_variant.full_name'; reverse = !reverse" } Product: Unit %th.quantity{ 'ng-show' => 'columns.quantity.visible' } Quantity %th.max{ 'ng-show' => 'columns.max.visible' } Max %th.actions @@ -141,7 +141,7 @@ %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} %td.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.unit_text }} + %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.full_name }} %td.quantity{ 'ng-show' => 'columns.quantity.visible' } %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} @@ -149,4 +149,4 @@ %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } %td.actions %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } - %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } \ No newline at end of file + %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } From 01d4cf6ecf7dc607bd568ee7c51370a1e6115b3a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 15:02:36 +1000 Subject: [PATCH 132/206] Renaming managed_products permissions method to editable_products --- lib/open_food_network/permissions.rb | 2 +- spec/lib/open_food_network/permissions_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 4da645582d..e207eea797 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -106,7 +106,7 @@ module OpenFoodNetwork Spree::LineItem.where(order_id: editable_orders) end - def managed_products + def editable_products managed_enterprise_products_ids = managed_enterprise_products.pluck :id permitted_enterprise_products_ids = related_enterprise_products.pluck :id Spree::Product.where('id IN (?)', managed_enterprise_products_ids + permitted_enterprise_products_ids) diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 10552572f0..2f19a7a404 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -164,7 +164,7 @@ module OpenFoodNetwork end end - describe "finding managed products" do + describe "finding editable products" do let!(:p1) { create(:simple_product) } let!(:p2) { create(:simple_product) } @@ -175,12 +175,12 @@ module OpenFoodNetwork it "returns products produced by managed enterprises" do permissions.stub(:managed_enterprise_products) { Spree::Product.where(id: p1) } - permissions.managed_products.should == [p1] + permissions.editable_products.should == [p1] end it "returns products produced by permitted enterprises" do permissions.stub(:related_enterprise_products) { Spree::Product.where(id: p2) } - permissions.managed_products.should == [p2] + permissions.editable_products.should == [p2] end end From 6953f6193974f404ee6058a195e73f4100675b1c Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 May 2015 15:07:22 +1000 Subject: [PATCH 133/206] bulk product edit: new col "on demand" --- app/assets/javascripts/admin/bulk_product_update.js.coffee | 4 ++++ .../spree/admin/products/bulk_edit/_products_head.html.haml | 2 ++ .../admin/products/bulk_edit/_products_product.html.haml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 8cac0abeda..673197ae6d 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -10,6 +10,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout unit: {name: "Unit", visible: true} price: {name: "Price", visible: true} on_hand: {name: "On Hand", visible: true} + on_demand: {name: "On Demand", visible: false} category: {name: "Category", visible: false} inherits_properties: {name: "Inherits Properties?", visible: false} available_on: {name: "Available On", visible: false} @@ -309,6 +310,9 @@ filterSubmitProducts = (productsToFilter) -> if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present filteredProduct.on_hand = product.on_hand hasUpdatableProperty = true + if product.hasOwnProperty("on_demand") + filteredProduct.on_demand = product.on_demand + hasUpdatableProperty = true if product.hasOwnProperty("category_id") filteredProduct.primary_taxon_id = product.category_id hasUpdatableProperty = true diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 2e37da2bc8..18bd000473 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -7,6 +7,7 @@ %col.display_as{ ng: { show: 'columns.unit.visible' } } %col.price{ ng: { show: 'columns.price.visible'} } %col.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %col.on_demand{ ng: { show: 'columns.on_demand.visible' } } %col.category{ ng: { show: 'columns.category.visible' } } %col.inherits_properties{ ng: { show: 'columns.inherits_properties.visible' } } %col.available_on{ ng: { show: 'columns.available_on.visible' } } @@ -24,6 +25,7 @@ %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As %th.price{ 'ng-show' => 'columns.price.visible' } Price %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand + %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand %th.category{ 'ng-show' => 'columns.category.visible' } Category %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties? %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index 0eb9ff5f58..48564991dd 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -20,6 +20,8 @@ %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } + %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } + %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox' } %td.category{ 'ng-if' => 'columns.category.visible' } %input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' } %td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } From 1e26466d119134a48cf73c5bee87d4215bbb47ee Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 May 2015 15:29:35 +1000 Subject: [PATCH 134/206] bulk product edit: "on demand" for new product --- .../admin/products/new/replace_form.html.haml.deface | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface index 33c70b728e..3fd5a74cb1 100644 --- a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface +++ b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface @@ -38,20 +38,26 @@ .twelve.columns.alpha .six.columns.alpha = render 'spree/admin/products/primary_taxon_form', f: f - .three.columns + .two.columns = f.field_container :price do = f.label :price, t(:price) %span.required * %br/ = f.text_field :price, class: 'fullwidth' = f.error_message_on :price - .three.columns.omega + .two.columns = f.field_container :on_hand do = f.label :on_hand, t(:on_hand) %span.required * %br/ = f.text_field :on_hand, class: 'fullwidth' = f.error_message_on :on_hand + .two.columns.omega + = f.field_container :on_demand do + = f.label :on_demand, t(:on_demand) + %br/ + = f.check_box :on_demand + = f.error_message_on :on_demand .twelve.columns.alpha .six.columns.alpha   .three.columns From 8132f07d8819d41704ceb7286f6aa1d09d2e44fb Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 15:53:10 +1000 Subject: [PATCH 135/206] Adding visible products method to permissions --- lib/open_food_network/permissions.rb | 18 +++++-- .../lib/open_food_network/permissions_spec.rb | 49 +++++++++++++------ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index e207eea797..87baa533ad 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -108,8 +108,18 @@ module OpenFoodNetwork def editable_products managed_enterprise_products_ids = managed_enterprise_products.pluck :id - permitted_enterprise_products_ids = related_enterprise_products.pluck :id - Spree::Product.where('id IN (?)', managed_enterprise_products_ids + permitted_enterprise_products_ids) + permitted_enterprise_products_ids = products_supplied_by( + related_enterprises_granting(:manage_products).pluck(:id) + ).pluck :id + Spree::Product.where('spree_products.id IN (?)', managed_enterprise_products_ids | permitted_enterprise_products_ids) + end + + def visible_products + managed_enterprise_products_ids = managed_enterprise_products.pluck :id + permitted_enterprise_products_ids = products_supplied_by( + related_enterprises_granting(:manage_products).pluck(:id) | related_enterprises_granting(:add_to_order_cycle).pluck(:id) + ).pluck :id + Spree::Product.where('spree_products.id IN (?)', managed_enterprise_products_ids | permitted_enterprise_products_ids) end def managed_product_enterprises @@ -181,8 +191,8 @@ module OpenFoodNetwork Spree::Product.managed_by(@user) end - def related_enterprise_products - Spree::Product.where('supplier_id IN (?)', related_enterprises_granting(:manage_products)) + def products_supplied_by(suppliers) + Spree::Product.where('supplier_id IN (?)', suppliers) end end end diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 2f19a7a404..cf2aa1a217 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -165,12 +165,12 @@ module OpenFoodNetwork end describe "finding editable products" do - let!(:p1) { create(:simple_product) } - let!(:p2) { create(:simple_product) } + let!(:p1) { create(:simple_product, supplier: create(:supplier_enterprise) ) } + let!(:p2) { create(:simple_product, supplier: create(:supplier_enterprise) ) } before do permissions.stub(:managed_enterprise_products) { Spree::Product.where('1=0') } - permissions.stub(:related_enterprise_products) { Spree::Product.where('1=0') } + allow(permissions).to receive(:related_enterprises_granting).with(:manage_products) { Enterprise.where("1=0") } end it "returns products produced by managed enterprises" do @@ -179,11 +179,41 @@ module OpenFoodNetwork end it "returns products produced by permitted enterprises" do - permissions.stub(:related_enterprise_products) { Spree::Product.where(id: p2) } + allow(permissions).to receive(:related_enterprises_granting). + with(:manage_products) { Enterprise.where(id: p2.supplier) } permissions.editable_products.should == [p2] end end + describe "finding visible products" do + let!(:p1) { create(:simple_product, supplier: create(:supplier_enterprise) ) } + let!(:p2) { create(:simple_product, supplier: create(:supplier_enterprise) ) } + let!(:p3) { create(:simple_product, supplier: create(:supplier_enterprise) ) } + + before do + permissions.stub(:managed_enterprise_products) { Spree::Product.where("1=0") } + allow(permissions).to receive(:related_enterprises_granting).with(:manage_products) { Enterprise.where("1=0") } + allow(permissions).to receive(:related_enterprises_granting).with(:add_to_order_cycle) { Enterprise.where("1=0") } + end + + it "returns products produced by managed enterprises" do + permissions.stub(:managed_enterprise_products) { Spree::Product.where(id: p1) } + permissions.visible_products.should == [p1] + end + + it "returns products produced by enterprises that have granted manage products" do + allow(permissions).to receive(:related_enterprises_granting). + with(:manage_products) { Enterprise.where(id: p2.supplier) } + permissions.visible_products.should == [p2] + end + + it "returns products produced by enterprises that have granted P-OC" do + allow(permissions).to receive(:related_enterprises_granting). + with(:add_to_order_cycle) { Enterprise.where(id: p3.supplier) } + permissions.visible_products.should == [p3] + end + end + describe "finding enterprises that we manage products for" do let(:e) { double(:enterprise) } @@ -232,17 +262,6 @@ module OpenFoodNetwork end end - describe "finding the supplied products of related enterprises" do - let!(:e) { create(:enterprise) } - let!(:p) { create(:simple_product, supplier: e) } - - it "returns supplied products" do - permissions.should_receive(:related_enterprises_granting).with(:manage_products) { [e] } - - permissions.send(:related_enterprise_products).should == [p] - end - end - describe "finding orders that are visible in reports" do let(:distributor) { create(:distributor_enterprise) } let(:coordinator) { create(:distributor_enterprise) } From 65a6329132739e99634ed261f64bf2a81e4b5e9e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 16:19:08 +1000 Subject: [PATCH 136/206] Products and inventory reports scopes products to visible in permissions --- .../products_and_inventory_report.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/open_food_network/products_and_inventory_report.rb b/lib/open_food_network/products_and_inventory_report.rb index 1cb485efee..163f9a4f50 100644 --- a/lib/open_food_network/products_and_inventory_report.rb +++ b/lib/open_food_network/products_and_inventory_report.rb @@ -9,7 +9,7 @@ module OpenFoodNetwork def header [ - "Supplier", + "Supplier", "Producer Suburb", "Product", "Product Properties", @@ -36,6 +36,16 @@ module OpenFoodNetwork end end + def permissions + return @permissions unless @permissions.nil? + @permissions = OpenFoodNetwork::Permissions.new(@user) + end + + def visible_products + return @visible_products unless @visible_products.nil? + @visible_products = permissions.visible_products + end + def variants filter(child_variants) + filter(master_variants) end @@ -43,7 +53,7 @@ module OpenFoodNetwork def child_variants Spree::Variant.where(:is_master => false) .joins(:product) - .merge(Spree::Product.managed_by(@user)) + .merge(visible_products) .order("spree_products.name") end @@ -53,7 +63,7 @@ module OpenFoodNetwork .where("(select spree_variants.id from spree_variants as other_spree_variants WHERE other_spree_variants.product_id = spree_variants.product_id AND other_spree_variants.is_master = 'f' LIMIT 1) IS NULL") - .merge(Spree::Product.managed_by(@user)) + .merge(visible_products) .order("spree_products.name") end From 3431c687b85aa8f56f73a6908db3c5c7924c3b3a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 20:54:27 +1000 Subject: [PATCH 137/206] Making sure every created by factories has a distributor --- spec/factories.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/factories.rb b/spec/factories.rb index c798999134..6cfddd85a5 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -238,6 +238,10 @@ FactoryGirl.modify do unit_description '' end + factory :order do + distributor { create(:distributor_enterprise) } + end + factory :shipping_method do distributors { [Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise)] } display_on '' From 1aca4657d9a7e1b7ba1ce9db0c78eb7081cfc095 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 20:59:06 +1000 Subject: [PATCH 138/206] Oops, switch managed_products to editable_products for Api::ProductsController --- app/controllers/spree/api/products_controller_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/spree/api/products_controller_decorator.rb b/app/controllers/spree/api/products_controller_decorator.rb index 6e9799c86d..e809dbc23e 100644 --- a/app/controllers/spree/api/products_controller_decorator.rb +++ b/app/controllers/spree/api/products_controller_decorator.rb @@ -11,7 +11,7 @@ Spree::Api::ProductsController.class_eval do # TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it. def bulk_products - @products = OpenFoodNetwork::Permissions.new(current_api_user).managed_products. + @products = OpenFoodNetwork::Permissions.new(current_api_user).editable_products. merge(product_scope). order('created_at DESC'). ransack(params[:q]).result. From 0b2877136460871cf17a6240a360361e6439d074 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 20 May 2015 21:06:20 +1000 Subject: [PATCH 139/206] Removing unrequired specs for variant/product 'units_show' --- spec/controllers/spree/api/products_controller_spec.rb | 7 ------- spec/controllers/spree/api/variants_controller_spec.rb | 7 ------- 2 files changed, 14 deletions(-) diff --git a/spec/controllers/spree/api/products_controller_spec.rb b/spec/controllers/spree/api/products_controller_spec.rb index eb56859365..312c6a29b4 100644 --- a/spec/controllers/spree/api/products_controller_spec.rb +++ b/spec/controllers/spree/api/products_controller_spec.rb @@ -13,7 +13,6 @@ module Spree let!(:product3) { FactoryGirl.create(:product, supplier: supplier) } let(:product_other_supplier) { FactoryGirl.create(:product, supplier: supplier2) } let(:attributes) { [:id, :name, :supplier, :price, :on_hand, :available_on, :permalink_live] } - let(:unit_attributes) { [:id, :name, :group_buy_unit_size, :variant_unit] } before do stub_authentication! @@ -72,12 +71,6 @@ module Spree attributes.all?{ |attr| keys.include? attr }.should == true end - it "retrieves a list of products with attributes relating to units" do - spree_get :show, { :id => product1.id, :template => "units_show", :format => :json } - keys = json_response.keys.map{ |key| key.to_sym } - unit_attributes.all?{ |attr| keys.include? attr }.should == true - end - it "sorts products in ascending id order" do spree_get :index, { :template => 'bulk_index', :format => :json } ids = json_response.map{ |product| product['id'] } diff --git a/spec/controllers/spree/api/variants_controller_spec.rb b/spec/controllers/spree/api/variants_controller_spec.rb index 5df1a40dde..5fb9f2f2a0 100644 --- a/spec/controllers/spree/api/variants_controller_spec.rb +++ b/spec/controllers/spree/api/variants_controller_spec.rb @@ -9,7 +9,6 @@ module Spree let!(:variant2) { FactoryGirl.create(:variant) } let!(:variant3) { FactoryGirl.create(:variant) } let(:attributes) { [:id, :options_text, :price, :on_hand, :unit_value, :unit_description, :on_demand, :display_as, :display_name] } - let(:unit_attributes) { [:id, :unit_text, :unit_value] } before do stub_authentication! @@ -25,12 +24,6 @@ module Spree attributes.all?{ |attr| keys.include? attr }.should == true end - it "retrieves a list of variants with attributes relating to units" do - spree_get :show, { :id => variant1.id, :template => "units_show", :format => :json } - keys = json_response.keys.map{ |key| key.to_sym } - unit_attributes.all?{ |attr| keys.include? attr }.should == true - end - it "is denied access when trying to delete a variant" do product = create(:product) variant = product.master From 19367670845a9bc63a619bee11dd27af6be283bd Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 21 May 2015 11:06:08 +1000 Subject: [PATCH 140/206] BPE: new col for tax category --- app/assets/javascripts/admin/bulk_product_update.js.coffee | 7 ++++++- app/helpers/admin/injection_helper.rb | 4 ++++ app/serializers/api/admin/product_serializer.rb | 2 +- app/serializers/api/admin/tax_category_serializer.rb | 3 +++ app/views/spree/admin/products/bulk_edit/_data.html.haml | 1 + .../admin/products/bulk_edit/_products_head.html.haml | 2 ++ .../admin/products/bulk_edit/_products_product.html.haml | 3 +++ 7 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 app/serializers/api/admin/tax_category_serializer.rb diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 673197ae6d..5098152551 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth) -> +angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, tax_categories) -> $scope.loading = true $scope.StatusMessage = StatusMessage @@ -12,6 +12,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout on_hand: {name: "On Hand", visible: true} on_demand: {name: "On Demand", visible: false} category: {name: "Category", visible: false} + tax_category: {name: "Tax Category", visible: false} inherits_properties: {name: "Inherits Properties?", visible: false} available_on: {name: "Available On", visible: false} @@ -33,6 +34,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.producers = producers $scope.taxons = Taxons.taxons + $scope.tax_categories = tax_categories $scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers $scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons $scope.producerFilter = "0" @@ -316,6 +318,9 @@ filterSubmitProducts = (productsToFilter) -> if product.hasOwnProperty("category_id") filteredProduct.primary_taxon_id = product.category_id hasUpdatableProperty = true + if product.hasOwnProperty("tax_category_id") + filteredProduct.tax_category_id = product.tax_category_id + hasUpdatableProperty = true if product.hasOwnProperty("inherits_properties") filteredProduct.inherits_properties = product.inherits_properties hasUpdatableProperty = true diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 36ddccdb9a..961bc60afb 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -50,6 +50,10 @@ module Admin admin_inject_json_ams_array "ofn.admin", "products", @products, Api::Admin::ProductSerializer end + def admin_inject_tax_categories + admin_inject_json_ams_array "ofn.admin", "tax_categories", @tax_categories, Api::Admin::TaxCategorySerializer + end + def admin_inject_taxons admin_inject_json_ams_array "admin.taxons", "taxons", @taxons, Api::Admin::TaxonSerializer end diff --git a/app/serializers/api/admin/product_serializer.rb b/app/serializers/api/admin/product_serializer.rb index b1f233c044..65dd34ba6b 100644 --- a/app/serializers/api/admin/product_serializer.rb +++ b/app/serializers/api/admin/product_serializer.rb @@ -1,7 +1,7 @@ class Api::Admin::ProductSerializer < ActiveModel::Serializer attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand, :inherits_properties - attributes :on_hand, :price, :available_on, :permalink_live + attributes :on_hand, :price, :available_on, :permalink_live, :tax_category_id has_one :supplier, key: :producer_id, embed: :id has_one :primary_taxon, key: :category_id, embed: :id diff --git a/app/serializers/api/admin/tax_category_serializer.rb b/app/serializers/api/admin/tax_category_serializer.rb new file mode 100644 index 0000000000..37e99578b8 --- /dev/null +++ b/app/serializers/api/admin/tax_category_serializer.rb @@ -0,0 +1,3 @@ +class Api::Admin::TaxCategorySerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml index 8b727be3eb..3624421870 100644 --- a/app/views/spree/admin/products/bulk_edit/_data.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml @@ -1,3 +1,4 @@ = admin_inject_producers = admin_inject_taxons += admin_inject_tax_categories = admin_inject_spree_api_key diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 18bd000473..6e22aef8ff 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -9,6 +9,7 @@ %col.on_hand{ ng: { show: 'columns.on_hand.visible' } } %col.on_demand{ ng: { show: 'columns.on_demand.visible' } } %col.category{ ng: { show: 'columns.category.visible' } } + %col.tax_category{ ng: { show: 'columns.tax_category.visible' } } %col.inherits_properties{ ng: { show: 'columns.inherits_properties.visible' } } %col.available_on{ ng: { show: 'columns.available_on.visible' } } %col.actions @@ -27,6 +28,7 @@ %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand %th.category{ 'ng-show' => 'columns.category.visible' } Category + %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' } Tax Category %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties? %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On %th.actions diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index 48564991dd..eab9936558 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -24,6 +24,9 @@ %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox' } %td.category{ 'ng-if' => 'columns.category.visible' } %input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' } + %td.tax_category{ 'ng-if' => 'columns.tax_category.visible' } + %select.select2{ name: 'product_tax_category_id', 'ofn-track-product' => 'tax_category_id', ng: {model: 'product.tax_category_id', options: 'tax_category.id as tax_category.name for tax_category in tax_categories'} } + %option{value: ''} None %td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } %input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" } %td.available_on{ 'ng-show' => 'columns.available_on.visible' } From 2ed519ef50b93d1b6c6d0c743533cd6da8fa981b Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 21 May 2015 12:37:10 +1000 Subject: [PATCH 141/206] on_demand checkbox for variants --- app/assets/javascripts/admin/bulk_product_update.js.coffee | 5 ++++- .../admin/products/bulk_edit/_products_product.html.haml | 2 +- .../admin/products/bulk_edit/_products_variant.html.haml | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 5098152551..c902328af4 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -312,7 +312,7 @@ filterSubmitProducts = (productsToFilter) -> if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present filteredProduct.on_hand = product.on_hand hasUpdatableProperty = true - if product.hasOwnProperty("on_demand") + if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present filteredProduct.on_demand = product.on_demand hasUpdatableProperty = true if product.hasOwnProperty("category_id") @@ -346,6 +346,9 @@ filterSubmitVariant = (variant) -> if variant.hasOwnProperty("on_hand") filteredVariant.on_hand = variant.on_hand hasUpdatableProperty = true + if variant.hasOwnProperty("on_demand") + filteredVariant.on_demand = variant.on_demand + hasUpdatableProperty = true if variant.hasOwnProperty("price") filteredVariant.price = variant.price hasUpdatableProperty = true diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index eab9936558..376e88071a 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -21,7 +21,7 @@ %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } - %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox' } + %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' } %td.category{ 'ng-if' => 'columns.category.visible' } %input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' } %td.tax_category{ 'ng-if' => 'columns.tax_category.visible' } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index 7e60cbfd4c..757b64e781 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -15,7 +15,10 @@ %td{ 'ng-show' => 'columns.on_hand.visible' } %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } + %td{ 'ng-show' => 'columns.on_demand.visible' } + %input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' } %td{ 'ng-show' => 'columns.category.visible' } + %td{ 'ng-show' => 'columns.tax_category.visible' } %td{ 'ng-show' => 'columns.inherits_properties.visible' } %td{ 'ng-show' => 'columns.available_on.visible' } %td.actions From 73884d4f019bebb3ffc0a3df94e1341f1fc81645 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 21 May 2015 13:01:28 +1000 Subject: [PATCH 142/206] BPE: Display notice about variant overrides --- app/models/spree/variant_decorator.rb | 1 + app/serializers/api/admin/variant_serializer.rb | 1 + .../spree/admin/products/bulk_edit/_products_variant.html.haml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 03543793b2..f35927ccf7 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -7,6 +7,7 @@ Spree::Variant.class_eval do has_many :exchange_variants, dependent: :destroy has_many :exchanges, through: :exchange_variants + has_many :variant_overrides attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name accepts_nested_attributes_for :images diff --git a/app/serializers/api/admin/variant_serializer.rb b/app/serializers/api/admin/variant_serializer.rb index 26ad2d7f37..510f7af333 100644 --- a/app/serializers/api/admin/variant_serializer.rb +++ b/app/serializers/api/admin/variant_serializer.rb @@ -1,6 +1,7 @@ class Api::Admin::VariantSerializer < ActiveModel::Serializer attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display attributes :on_hand, :price + has_many :variant_overrides def on_hand object.on_hand.nil? ? 0 : ( object.on_hand.to_f.finite? ? object.on_hand : "On demand" ) diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index 757b64e781..853d8e1520 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -24,5 +24,6 @@ %td.actions %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" } %td.actions + %span.icon-warning-sign.with-tip{ 'ng-if' => 'variant.variant_overrides', title: "This variant has {{variant.variant_overrides.length}} override(s)" } %td.actions %a{ 'ng-click' => 'deleteVariant(product,variant)', :class => "delete-variant icon-trash no-text" } From 79a59e2e812fe6d126bff783fde38620f6cd269c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 21 May 2015 13:34:32 +1000 Subject: [PATCH 143/206] Add order_with_distributor factory --- spec/factories.rb | 8 +-- .../admin/bulk_order_management_spec.rb | 64 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/spec/factories.rb b/spec/factories.rb index 6cfddd85a5..cb4d06b35e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -183,6 +183,10 @@ FactoryGirl.define do end end + factory :order_with_distributor, :parent => :order do + distributor { create(:distributor_enterprise) } + end + factory :zone_with_member, :parent => :zone do default_tax true @@ -238,10 +242,6 @@ FactoryGirl.modify do unit_description '' end - factory :order do - distributor { create(:distributor_enterprise) } - end - factory :shipping_method do distributors { [Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise)] } display_on '' diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 1058bebfc0..032acaca4e 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -18,9 +18,9 @@ feature %q{ end context "displaying the list of line items" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o3) { FactoryGirl.create(:order, state: 'address', completed_at: nil ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'address', completed_at: nil ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } let!(:li3) { FactoryGirl.create(:line_item, order: o3 ) } @@ -41,8 +41,8 @@ feature %q{ end context "displaying individual columns" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, bill_address: FactoryGirl.create(:address) ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, bill_address: nil ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, bill_address: FactoryGirl.create(:address) ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, bill_address: nil ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2, product: FactoryGirl.create(:product_with_option_types) ) } @@ -94,7 +94,7 @@ feature %q{ end context "tracking changes" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } before :each do @@ -117,7 +117,7 @@ feature %q{ end context "submitting data to the server" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } before :each do @@ -141,7 +141,7 @@ feature %q{ admin_user = quick_login_as_admin end - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } context "using column display toggle" do @@ -171,7 +171,7 @@ feature %q{ context "supplier filter" do let!(:s1) { create(:supplier_enterprise) } let!(:s2) { create(:supplier_enterprise) } - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s1) ) } let!(:li2) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s2) ) } @@ -205,8 +205,8 @@ feature %q{ context "distributor filter" do let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: d1 ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: d2 ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, distributor: d1 ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, distributor: d2 ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } @@ -241,8 +241,8 @@ feature %q{ let!(:distributor) { create(:distributor_enterprise) } let!(:oc1) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } let!(:oc2) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, order_cycle: oc1 ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, order_cycle: oc2 ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, order_cycle: oc1 ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, order_cycle: oc2 ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } @@ -284,8 +284,8 @@ feature %q{ let!(:oc2) { FactoryGirl.create(:simple_order_cycle, suppliers: [s2], distributors: [d2] ) } let!(:p1) { FactoryGirl.create(:product, supplier: s1) } let!(:p2) { FactoryGirl.create(:product, supplier: s2) } - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: d1, order_cycle: oc1 ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: d2, order_cycle: oc2 ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, distributor: d1, order_cycle: oc1 ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, distributor: d2, order_cycle: oc2 ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1, product: p1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2, product: p2 ) } @@ -328,9 +328,9 @@ feature %q{ end context "using quick search" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o3) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } let!(:li3) { FactoryGirl.create(:line_item, order: o3 ) } @@ -355,9 +355,9 @@ feature %q{ end context "using date restriction controls" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: (Date.today - 8).strftime("%F %T") ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o3) { FactoryGirl.create(:order, state: 'complete', completed_at: (Date.today + 2).strftime("%F %T") ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: (Date.today - 8).strftime("%F %T") ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: (Date.today + 2).strftime("%F %T") ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2, :quantity => 2 ) } let!(:li3) { FactoryGirl.create(:line_item, order: o3, :quantity => 3 ) } @@ -429,8 +429,8 @@ feature %q{ end context "bulk action controls" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } @@ -496,8 +496,8 @@ feature %q{ context "using action buttons" do context "using edit buttons" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } @@ -515,8 +515,8 @@ feature %q{ end context "using delete buttons" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } @@ -539,13 +539,13 @@ feature %q{ end context "clicking the link on variant name" do - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } let!(:p3) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } let!(:v3) { p3.variants.first } - let!(:o3) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } + let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now ) } let!(:li3) { FactoryGirl.create(:line_item, order: o3, variant: v3, quantity: 3, max_quantity: 6 ) } let!(:li4) { FactoryGirl.create(:line_item, order: o2, variant: v3, quantity: 1, max_quantity: 3 ) } @@ -605,8 +605,8 @@ feature %q{ let(:s1) { create(:supplier_enterprise, name: 'First Supplier') } let(:d1) { create(:distributor_enterprise, name: 'First Distributor') } let(:d2) { create(:distributor_enterprise, name: 'Another Distributor') } - let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: d1 ) } - let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now, distributor: d2 ) } + let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, distributor: d1 ) } + let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.now, distributor: d2 ) } let!(:line_item_distributed) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s1) ) } let!(:line_item_not_distributed) { FactoryGirl.create(:line_item, order: o2, product: create(:product, supplier: s1) ) } From cd44d43b3ebcea97c2b17387810c094f275f184d Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 21 May 2015 17:48:35 +1000 Subject: [PATCH 144/206] Adding price to line_item serializer (oops, forgot to include when merging in master) --- app/serializers/api/admin/line_item_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/api/admin/line_item_serializer.rb b/app/serializers/api/admin/line_item_serializer.rb index 7e2917acdd..21fde91145 100644 --- a/app/serializers/api/admin/line_item_serializer.rb +++ b/app/serializers/api/admin/line_item_serializer.rb @@ -1,5 +1,5 @@ class Api::Admin::LineItemSerializer < ActiveModel::Serializer - attributes :id, :quantity, :max_quantity, :supplier, :unit_value, :units_product, :units_variant + attributes :id, :quantity, :max_quantity, :supplier, :price, :unit_value, :units_product, :units_variant def supplier Api::Admin::IdNameSerializer.new(object.product.supplier).serializable_hash From f017197221121b65866b7d7c560c0390b39a9aa6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 21 May 2015 18:00:14 +1000 Subject: [PATCH 145/206] orders list: filter by distributor and order cycle --- .../spree/admin/orders_controller_decorator.rb | 7 +++++++ ...r_and_order_cycle_filter_inputs.html.haml.deface | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index 42e7068afc..ac3d0fa9e0 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -23,6 +23,13 @@ Spree::Admin::OrdersController.class_eval do distributed_by_user(spree_current_user). page(params[:page]). per(params[:per_page] || Spree::Config[:orders_per_page]) + # Filter orders by distributor + if params[:distributor_ids] + @orders = @orders.where(distributor_id: params[:distributor_ids]) + end + if params[:order_cycle_ids] + @orders = @orders.where(order_cycle_id: params[:order_cycle_ids]) + end } } } # Overwrite to use confirm_email_for_customer instead of confirm_email. diff --git a/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface b/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface new file mode 100644 index 0000000000..f9e5e54e88 --- /dev/null +++ b/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface @@ -0,0 +1,13 @@ +/ insert_before "div.clearfix" + +.field-block.alpha.eight.columns + = label_tag nil, t(:distributors) + = select_tag(:distributor_ids, + options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]), + {class: "select2 fullwidth", multiple: true}) + +.field-block.alpha.eight.columns + = label_tag nil, t(:order_cycles) + = select_tag(:order_cycle_ids, + options_for_select(OrderCycle.managed_by(spree_current_user).map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]), + {class: "select2 fullwidth", multiple: true}) From 7e4751cb3ae549fd3d1ad7bac8fd550ad38e9cbb Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 21 May 2015 22:30:23 +1000 Subject: [PATCH 146/206] updating bulk product js spec --- spec/javascripts/unit/bulk_product_update_spec.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index a6fb0fa432..6048814505 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -215,6 +215,7 @@ describe "filtering products for submission to database", -> variant_unit_scale: 1 variant_unit_name: 'loaf' available_on: available_on + tax_category_id: null master_attributes: id: 2 unit_value: 250 From 9c137ccf0faafde9f0c26fd9ad8a1b373dc7220f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 22 May 2015 11:03:21 +1000 Subject: [PATCH 147/206] provide tax_categories in spec --- spec/javascripts/unit/bulk_product_update_spec.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index 6048814505..fb439f1c0c 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -239,6 +239,7 @@ describe "AdminProductEditCtrl", -> module ($provide)-> $provide.value "producers", [] $provide.value "taxons", [] + $provide.value "tax_categories", [] $provide.value 'SpreeApiKey', 'API_KEY' null From d43df862015f2fc7d1a959210572fbca8c6b6554 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 22 May 2015 12:20:31 +1000 Subject: [PATCH 148/206] Moving conditional logic into ensure_standard_variant --- app/models/spree/product_decorator.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index e9b99c9b7a..82330c61cf 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -34,7 +34,7 @@ Spree::Product.class_eval do validates_presence_of :variant_unit_name, if: -> p { p.variant_unit == 'items' } - after_save :ensure_standard_variant, if: lambda { master.valid? && variants.empty? } + after_save :ensure_standard_variant after_initialize :set_available_on_to_now, :if => :new_record? after_save :update_units after_touch :touch_distributors @@ -211,10 +211,12 @@ Spree::Product.class_eval do end def ensure_standard_variant - variant = self.master.dup - variant.product = self - variant.is_master = false - self.variants << variant + if master.valid? && variants.empty? + variant = self.master.dup + variant.product = self + variant.is_master = false + self.variants << variant + end end # Override Spree's old save_master method and replace it with the most recent method from spree repository From 63f3ede766216c27c460d02f2a440d5fbacb4371 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 22 May 2015 12:22:05 +1000 Subject: [PATCH 149/206] Prepare master variants for duplication as standard variant by ensuring they have a unit value and that the product has a variant unit --- ...060622_add_standard_variant_to_products.rb | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/db/migrate/20141003060622_add_standard_variant_to_products.rb b/db/migrate/20141003060622_add_standard_variant_to_products.rb index 7e7f752a5d..5242f86686 100644 --- a/db/migrate/20141003060622_add_standard_variant_to_products.rb +++ b/db/migrate/20141003060622_add_standard_variant_to_products.rb @@ -1,9 +1,27 @@ class AddStandardVariantToProducts < ActiveRecord::Migration def up + # Make sure that all products have a variant_unit + Spree::Product.where("variant_unit IS NULL OR variant_unit = ''").update_all(variant_unit: "items", variant_unit_name: "each") + # Find products without any standard variants - products_with_only_master = Spree::Product.includes(:variants).where('spree_variants.id IS NULL') + products_with_only_master = Spree::Product.includes(:variants).where('spree_variants.id IS NULL').select('DISTINCT spree_products.*') products_with_only_master.each do |product| + # Add a unit_value to the master variant if it doesn't have one + if product.unit_value.blank? + if product.variant_unit == "weight" && match = product.unit_description.andand.match(/^(\d+(\.\d*)?)(k?g) ?(.*)$/) + scale = (match[3] == "kg" ? 1000 : 1) + product.unit_value = (match[1].to_i*scale) + product.unit_description = match[4] + product.save! + else + unless product.variant_unit == "items" && product.unit_description.present? + product.unit_value = 1 + product.save! + end + end + end + # Run the callback to add a copy of the master variant as a standard variant product.send(:ensure_standard_variant) From 5b65f67737b93b2c5a1b7f1f2e46d45179cd04c0 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 22 May 2015 15:02:49 +1000 Subject: [PATCH 150/206] Amending spec expect after_create when we want after_save --- spec/models/spree/product_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index c30a5920ca..9423e2672c 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -116,10 +116,10 @@ module Spree expect(standard_variant.price).to eq product.master.price end - it "only duplicates master variant on create" do - expect(product).to_not receive :ensure_standard_variant + it "only duplicates master with after_save when no standard variants exist" do + expect(product).to receive :ensure_standard_variant product.name = "Something else" - product.save! + expect{product.save!}.to_not change{product.variants.count} end end From 5d47dc2fdb3652d486d32c9422f90126a13c49b1 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 22 May 2015 15:03:21 +1000 Subject: [PATCH 151/206] Sort array so that order doesn't cause spec fail --- spec/lib/open_food_network/permissions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index cf2aa1a217..9bf5ef2d9d 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -47,7 +47,7 @@ module OpenFoodNetwork expect(permissions).to receive(:managed_enterprises) { Enterprise.where(id: e1) } expect(permissions).to receive(:related_enterprises_granting).with(:some_permission) { Enterprise.where(id: e3) } expect(permissions).to receive(:related_enterprises_granted).with(:some_permission) { Enterprise.where(id: e4) } - expect(permissions.send(:managed_and_related_enterprises_with, :some_permission)).to eq [e1, e3, e4] + expect(permissions.send(:managed_and_related_enterprises_with, :some_permission).sort).to eq [e1, e3, e4].sort end end end From 28dae3c6c6340b49ce65c51bf2c949c1f560ce37 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 22 May 2015 15:23:19 +1000 Subject: [PATCH 152/206] Enterprises cannot add themselves to Groups --- .../admin/enterprises_controller.rb | 5 +++++ .../form/_primary_details.html.haml | 18 +++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index d4b3244525..a0b55ad3c8 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -3,6 +3,7 @@ module Admin before_filter :load_enterprise_set, :only => :index before_filter :load_countries, :except => [:index, :set_sells, :check_permalink] before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create] + before_filter :load_groups, :only => [:new, :edit, :update, :create] before_filter :load_taxons, :only => [:new, :edit, :update, :create] before_filter :check_can_change_sells, only: :update before_filter :check_can_change_bulk_sells, only: :bulk_update @@ -127,6 +128,10 @@ module Admin @enterprise_fees = EnterpriseFee.managed_by(spree_current_user).for_enterprise(@enterprise).order(:fee_type, :name).all end + def load_groups + @groups = EnterpriseGroup.managed_by(spree_current_user) | @enterprise.groups + end + def load_taxons @taxons = Spree::Taxon.order(:name) end diff --git a/app/views/admin/enterprises/form/_primary_details.html.haml b/app/views/admin/enterprises/form/_primary_details.html.haml index dce5512f40..96f4674912 100644 --- a/app/views/admin/enterprises/form/_primary_details.html.haml +++ b/app/views/admin/enterprises/form/_primary_details.html.haml @@ -5,15 +5,15 @@ %span.required * .eight.columns.omega = f.text_field :name, { placeholder: "eg. Professor Plum's Biodynamic Truffles" } -.row - .alpha.eleven.columns - .three.columns.alpha - = f.label :group_ids, 'Groups' - .with-tip{'data-powertip' => "Select any groups or regions that you are a member of. This will help customers find your enterprise."} - %a What's this? - - .eight.columns.omega - = f.collection_select :group_ids, EnterpriseGroup.all, :id, :name, {}, class: "select2 fullwidth", multiple: true, placeholder: "Start typing to search available groups..." +- if @groups.present? + .row + .alpha.eleven.columns + .three.columns.alpha + = f.label :group_ids, 'Groups' + .with-tip{'data-powertip' => "Select any groups or regions that you are a member of. This will help customers find your enterprise."} + %a What's this? + .eight.columns.omega + = f.collection_select :group_ids, @groups, :id, :name, {}, class: "select2 fullwidth", multiple: true, placeholder: "Start typing to search available groups..." .row .three.columns.alpha From a5482c269f6ca18ead3bf5fe0c510b7f2f465671 Mon Sep 17 00:00:00 2001 From: Rick Giner Date: Sun, 24 May 2015 09:52:34 +1000 Subject: [PATCH 153/206] #541 Fixed issue of showing negative 'more' numbers, and only working for current Hub --- .../stylesheets/darkswarm/hub_node.css.sass | 34 ++++++++++--------- app/views/home/_fat.html.haml | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index dfe4d42074..94950fa22c 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -65,6 +65,24 @@ .active_table_row:nth-child(2) padding-bottom: 0.75rem + + .producers-list + li.more-producers-link + .less + display: none + a:hover + text-decoration: underline + li.additional-producer + display: none + &.show-more-producers + li.additional-producer + display: block + li.more-producers-link + .more + display: none + .less + display: block + //CURRENT hub (shows selected hub) &.current //overwrites active_table @@ -83,22 +101,6 @@ .active_table_row:first-child .skinny-head background-color: rgba(255,255,255,0.85) - .producers-list - li.more-producers-link - .less - display: none - a:hover - text-decoration: underline - li.additional-producer - display: none - &.show-more-producers - li.additional-producer - display: block - li.more-producers-link - .more - display: none - .less - display: block //INACTIVE - closed hub &.inactive diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index 092d9e898d..a5543fa2a3 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -26,7 +26,7 @@ %enterprise-modal %i.ofn-i_036-producers %span{"bo-text" => "enterprise.name"} - %li{"data-is-link" => "true", "class" => "more-producers-link"} + %li{"data-is-link" => "true", "class" => "more-producers-link", "bo-show" => "hub.producers.length>7"} %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers"} .more + From 99cb09c6f75bfc0889ccac2e9ec09f6958e45b44 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 20 May 2015 14:05:41 +1000 Subject: [PATCH 154/206] When loading products for shopfront, load all variants in one go --- app/controllers/shop_controller.rb | 28 ++++++++++++++++++--- app/serializers/api/product_serializer.rb | 14 +++-------- spec/controllers/shop_controller_spec.rb | 14 +++++++++++ spec/serializers/product_serializer_spec.rb | 14 ----------- 4 files changed, 42 insertions(+), 28 deletions(-) delete mode 100644 spec/serializers/product_serializer_spec.rb diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 73861def5c..35aece5e1d 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -10,12 +10,15 @@ class ShopController < BaseController end def products - # Can we make this query less slow? - # if @products = products_for_shop + render status: 200, - json: ActiveModel::ArraySerializer.new(@products, each_serializer: Api::ProductSerializer, - current_order_cycle: current_order_cycle, current_distributor: current_distributor).to_json + json: ActiveModel::ArraySerializer.new(@products, + each_serializer: Api::ProductSerializer, + current_order_cycle: current_order_cycle, + current_distributor: current_distributor, + variants: variants_for_shop_by_id).to_json + else render json: "", status: 404 end @@ -46,6 +49,23 @@ class ShopController < BaseController end end + def variants_for_shop_by_id + # We use the in_stock? method here instead of the in_stock scope because we need to + # look up the stock as overridden by VariantOverrides, and the scope method is not affected + # by them. + variants = Spree::Variant. + where(is_master: false). + for_distribution(current_order_cycle, current_distributor). + each { |v| v.scope_to_hub current_distributor }. + select(&:in_stock?) + + variants.inject({}) do |vs, v| + vs[v.product_id] ||= [] + vs[v.product_id] << v + vs + end + end + def taxon_order if current_distributor.preferred_shopfront_taxon_order.present? current_distributor diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index 0de794796b..b707aa281c 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -30,8 +30,9 @@ class Api::CachedProductSerializer < ActiveModel::Serializer #cached #delegate :cache_key, to: :object - attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy, - :notes, :description, :properties_with_values + attributes :id, :name, :permalink, :count_on_hand + attributes :on_demand, :group_buy, :notes, :description + attributes :properties_with_values has_many :variants, serializer: Api::VariantSerializer has_many :taxons, serializer: Api::IdSerializer @@ -46,13 +47,6 @@ class Api::CachedProductSerializer < ActiveModel::Serializer end def variants - # We use the in_stock? method here instead of the in_stock scope because we need to - # look up the stock as overridden by VariantOverrides, and the scope method is not affected - # by them. - - object.variants. - for_distribution(options[:current_order_cycle], options[:current_distributor]). - each { |v| v.scope_to_hub options[:current_distributor] }. - select(&:in_stock?) + options[:variants][object.id] || [] end end diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index 4b7c53da19..10f0eed2cd 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -175,4 +175,18 @@ describe ShopController do end end end + + describe "loading variants" do + let(:hub) { create(:distributor_enterprise) } + let(:oc) { create(:simple_order_cycle, distributors: [hub], variants: [v1]) } + let(:p) { create(:simple_product) } + let!(:v1) { create(:variant, product: p, unit_value: 3) } + let!(:v2) { create(:variant, product: p, unit_value: 5) } + + it "scopes variants to distribution" do + controller.stub(:current_order_cycle) { oc } + controller.stub(:current_distributor) { hub } + controller.send(:variants_for_shop_by_id).should == {p.id => [v1]} + end + end end diff --git a/spec/serializers/product_serializer_spec.rb b/spec/serializers/product_serializer_spec.rb deleted file mode 100644 index 0091668dbb..0000000000 --- a/spec/serializers/product_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -describe Api::ProductSerializer do - let(:hub) { create(:distributor_enterprise) } - let(:oc) { create(:simple_order_cycle, distributors: [hub], variants: [v1]) } - let(:p) { create(:simple_product) } - let!(:v1) { create(:variant, product: p, unit_value: 3) } - let!(:v2) { create(:variant, product: p, unit_value: 5) } - - it "scopes variants to distribution" do - s = Api::ProductSerializer.new p, current_distributor: hub, current_order_cycle: oc - json = s.to_json - json.should include v1.options_text - json.should_not include v2.options_text - end -end From c5f00d87bd3b63a750f5282583084b84e6e31df6 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 20 May 2015 15:22:01 +1000 Subject: [PATCH 155/206] When loading products for shopfront, load all master variants in one go --- app/controllers/shop_controller.rb | 46 ++++++++++++++--------- app/serializers/api/product_serializer.rb | 5 +++ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 35aece5e1d..0655624a0b 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -17,7 +17,8 @@ class ShopController < BaseController each_serializer: Api::ProductSerializer, current_order_cycle: current_order_cycle, current_distributor: current_distributor, - variants: variants_for_shop_by_id).to_json + variants: variants_for_shop_by_id, + master_variants: master_variants_for_shop_by_id).to_json else render json: "", status: 404 @@ -49,23 +50,6 @@ class ShopController < BaseController end end - def variants_for_shop_by_id - # We use the in_stock? method here instead of the in_stock scope because we need to - # look up the stock as overridden by VariantOverrides, and the scope method is not affected - # by them. - variants = Spree::Variant. - where(is_master: false). - for_distribution(current_order_cycle, current_distributor). - each { |v| v.scope_to_hub current_distributor }. - select(&:in_stock?) - - variants.inject({}) do |vs, v| - vs[v.product_id] ||= [] - vs[v.product_id] << v - vs - end - end - def taxon_order if current_distributor.preferred_shopfront_taxon_order.present? current_distributor @@ -76,4 +60,30 @@ class ShopController < BaseController "name ASC" end end + + def all_variants_for_shop + # We use the in_stock? method here instead of the in_stock scope because we need to + # look up the stock as overridden by VariantOverrides, and the scope method is not affected + # by them. + Spree::Variant. + for_distribution(current_order_cycle, current_distributor). + each { |v| v.scope_to_hub current_distributor }. + select(&:in_stock?) + end + + def variants_for_shop_by_id + index_by_product_id all_variants_for_shop.reject(&:is_master) + end + + def master_variants_for_shop_by_id + index_by_product_id all_variants_for_shop.select(&:is_master) + end + + def index_by_product_id(variants) + variants.inject({}) do |vs, v| + vs[v.product_id] ||= [] + vs[v.product_id] << v + vs + end + end end diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index b707aa281c..4c4aec2310 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -49,4 +49,9 @@ class Api::CachedProductSerializer < ActiveModel::Serializer def variants options[:variants][object.id] || [] end + + def master + options[:master_variants][object.id].andand.first + end + end From 769edbe9d52aebbb6cef71c3a06e90069f789b29 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:04:59 +1000 Subject: [PATCH 156/206] Find the earliest closing times for each distributor in an active order cycle --- app/models/order_cycle.rb | 16 +++++++++++++++- spec/models/order_cycle_spec.rb | 31 +++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 88e4953edc..dfd45230b6 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -92,11 +92,25 @@ class OrderCycle < ActiveRecord::Base with_distributor(distributor).soonest_closing.first end - def self.most_recently_closed_for(distributor) with_distributor(distributor).most_recently_closed.first end + # Find the earliest closing times for each distributor in an active order cycle, and return + # them in the format {distributor_id => closing_time, ...} + def self.earliest_closing_times + Hash[ + Exchange. + outgoing. + joins(:order_cycle). + merge(OrderCycle.active). + group('exchanges.receiver_id'). + select('exchanges.receiver_id AS receiver_id, MIN(order_cycles.orders_close_at) AS earliest_close_at'). + map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] } + ] + end + + def clone! oc = self.dup oc.name = "COPY OF #{oc.name}" diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 817d2e8e93..507f9977b1 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -313,7 +313,7 @@ describe OrderCycle do @oc.pickup_time_for(@d2).should == '2-8pm Friday' end end - + describe "finding pickup instructions for a distributor" do it "returns the pickup instructions" do @oc.pickup_instructions_for(@d1).should == "Come get it!" @@ -375,7 +375,7 @@ describe OrderCycle do occ.coordinator_fee_ids.should_not be_empty occ.coordinator_fee_ids.should == oc.coordinator_fee_ids - + # to_h gives us a unique hash for each exchange # check that the clone has no additional exchanges occ.exchanges.map(&:to_h).all? do |ex| @@ -402,7 +402,7 @@ describe OrderCycle do describe "finding order cycles opening in the future" do it "should give the soonest opening order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) OrderCycle.first_opening_for(distributor).should == oc end @@ -411,13 +411,32 @@ describe OrderCycle do OrderCycle.first_opening_for(distributor).should == nil end end - + describe "finding open order cycles" do it "should give the soonest closing order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) - oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) + oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) OrderCycle.first_closing_for(distributor).should == oc end end + + describe "finding the earliest closing times for each distributor" do + let(:time1) { 1.week.from_now } + let(:time2) { 2.weeks.from_now } + let(:time3) { 3.weeks.from_now } + let(:e1) { create(:distributor_enterprise) } + let(:e2) { create(:distributor_enterprise) } + let!(:oc1) { create(:simple_order_cycle, orders_close_at: time1, distributors: [e1]) } + let!(:oc2) { create(:simple_order_cycle, orders_close_at: time2, distributors: [e2]) } + let!(:oc3) { create(:simple_order_cycle, orders_close_at: time3, distributors: [e2]) } + + it "returns the closing time, indexed by enterprise id" do + OrderCycle.earliest_closing_times[e1.id].should == time1 + end + + it "returns the earliest closing time" do + OrderCycle.earliest_closing_times[e2.id].should == time2 + end + end end From f940984ca311313e3457da6ec98eb5c044c609f0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:07:11 +1000 Subject: [PATCH 157/206] Pull earliest closing time computations out of the serialization loop --- app/helpers/injection_helper.rb | 2 +- app/serializers/api/enterprise_serializer.rb | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 37794cef9d..a17d6e81f9 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,6 @@ module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors + inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times end def inject_current_order diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 532887ae01..626fe77cad 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -18,14 +18,12 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active def orders_close_at - OrderCycle.first_closing_for(object).andand.orders_close_at + options[:earliest_closing_times][object.id] end def active - @options[:active_distributors].andand.include? object + options[:active_distributors].andand.include? object end - - end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer From f0e909c92bc7a029ca9fb607accfaa9b4799c871 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:38:33 +1000 Subject: [PATCH 158/206] Look up the shipping services (pickup, delivery) that different hubs provide --- app/models/spree/shipping_method_decorator.rb | 16 +++++++++++ spec/models/spree/shipping_method_spec.rb | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb index 4a2cf75b47..b8be603048 100644 --- a/app/models/spree/shipping_method_decorator.rb +++ b/app/models/spree/shipping_method_decorator.rb @@ -25,6 +25,22 @@ Spree::ShippingMethod.class_eval do scope :by_name, order('spree_shipping_methods.name ASC') + + # Return the services (pickup, delivery) that different distributors provide, in the format: + # {distributor_id => {pickup: true, delivery: false}, ...} + def self.services + Hash[ + Spree::ShippingMethod. + joins(:distributor_shipping_methods). + group('distributor_id'). + select("distributor_id"). + select("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup"). + select("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery"). + map { |sm| [sm.distributor_id.to_i, {pickup: sm.pickup == 't', delivery: sm.delivery == 't'}] } + ] + end + + def available_to_order_with_distributor_check?(order, display_on=nil) available_to_order_without_distributor_check?(order, display_on) && self.distributors.include?(order.distributor) diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index d6b821e890..8b7f397191 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -55,5 +55,32 @@ module Spree sm.should be_available_to_order o end end + + describe "finding services offered by all distributors" do + let!(:d1) { create(:distributor_enterprise) } + let!(:d2) { create(:distributor_enterprise) } + let!(:d3) { create(:distributor_enterprise) } + let!(:d4) { create(:distributor_enterprise) } + let!(:d1_pickup) { create(:shipping_method, require_ship_address: false, distributors: [d1]) } + let!(:d1_delivery) { create(:shipping_method, require_ship_address: true, distributors: [d1]) } + let!(:d2_pickup) { create(:shipping_method, require_ship_address: false, distributors: [d2]) } + let!(:d3_delivery) { create(:shipping_method, require_ship_address: true, distributors: [d3]) } + + it "reports when the services are available" do + ShippingMethod.services[d1.id].should == {pickup: true, delivery: true} + end + + it "reports when only pickup is available" do + ShippingMethod.services[d2.id].should == {pickup: true, delivery: false} + end + + it "reports when only delivery is available" do + ShippingMethod.services[d3.id].should == {pickup: false, delivery: true} + end + + it "returns no entry when no service is available" do + ShippingMethod.services[d4.id].should be_nil + end + end end end From ee8db23fd99e08dc4037b8feef03079253276e77 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 12:40:04 +1000 Subject: [PATCH 159/206] Pull shipping method service computations out of the serialization loop --- app/helpers/injection_helper.rb | 2 +- app/serializers/api/enterprise_serializer.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index a17d6e81f9..39831711ff 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,6 @@ module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times + inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times, shipping_method_services: Spree::ShippingMethod.services end def inject_current_order diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 626fe77cad..f7bd06f239 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -42,11 +42,13 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer has_one :address, serializer: Api::AddressSerializer def pickup - object.shipping_methods.where(:require_ship_address => false).present? + services = options[:shipping_method_services][object.id] + services ? services[:pickup] : false end def delivery - object.shipping_methods.where(:require_ship_address => true).present? + services = options[:shipping_method_services][object.id] + services ? services[:delivery] : false end def email From 704955a1854fbf2739779e5a5a6f6f2845535bba Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 15:47:52 +1000 Subject: [PATCH 160/206] Load active distributors where they're needed rather than in most controllers --- app/controllers/base_controller.rb | 3 --- app/controllers/checkout_controller.rb | 3 --- app/controllers/enterprises_controller.rb | 2 +- app/controllers/groups_controller.rb | 1 - app/controllers/home_controller.rb | 4 +--- app/controllers/map_controller.rb | 1 - app/controllers/producers_controller.rb | 3 +-- app/helpers/injection_helper.rb | 18 +++++++++++++++++- app/views/groups/show.html.haml | 6 +++--- spec/controllers/base_controller_spec.rb | 5 ----- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 1d74df5706..392e2fef64 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -12,9 +12,6 @@ class BaseController < ApplicationController before_filter :check_order_cycle_expiry - def load_active_distributors - @active_distributors ||= Enterprise.distributors_with_active_order_cycles - end private diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 0c42ceb987..11be28ef04 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -12,9 +12,6 @@ class CheckoutController < Spree::CheckoutController include EnterprisesHelper def edit - # Because this controller doesn't inherit from our BaseController - # We need to duplicate the code here - @active_distributors ||= Enterprise.distributors_with_active_order_cycles end def update diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 097139556c..75ad5c475b 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -4,7 +4,7 @@ class EnterprisesController < BaseController include OrderCyclesHelper # These prepended filters are in the reverse order of execution - prepend_before_filter :load_active_distributors, :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop + prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop before_filter :clean_permalink, only: :check_permalink respond_to :js, only: :permalink_checker diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 8653131b5a..6930632966 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,6 +1,5 @@ class GroupsController < BaseController layout 'darkswarm' - before_filter :load_active_distributors def index @groups = EnterpriseGroup.on_front_page.by_position diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 76e179ed22..3bb7a68538 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,11 +1,9 @@ class HomeController < BaseController layout 'darkswarm' - before_filter :load_active_distributors - + def index end def about_us end end - diff --git a/app/controllers/map_controller.rb b/app/controllers/map_controller.rb index a980ba8f40..46a6f5852a 100644 --- a/app/controllers/map_controller.rb +++ b/app/controllers/map_controller.rb @@ -1,6 +1,5 @@ class MapController < BaseController layout 'darkswarm' - before_filter :load_active_distributors def index end diff --git a/app/controllers/producers_controller.rb b/app/controllers/producers_controller.rb index b101a95b7f..42d1d401e5 100644 --- a/app/controllers/producers_controller.rb +++ b/app/controllers/producers_controller.rb @@ -1,7 +1,6 @@ class ProducersController < BaseController layout 'darkswarm' - before_filter :load_active_distributors - + def index end end diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 39831711ff..2229888f98 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,10 @@ module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors, earliest_closing_times: OrderCycle.earliest_closing_times, shipping_method_services: Spree::ShippingMethod.services + inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, enterprise_injection_data + end + + def inject_group_enterprises + inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data end def inject_current_order @@ -53,4 +57,16 @@ module InjectionHelper end render partial: "json/injection_ams", locals: {name: name, json: json} end + + + private + + def enterprise_injection_data + @active_distributors ||= Enterprise.distributors_with_active_order_cycles + @earliest_closing_times ||= OrderCycle.earliest_closing_times + @shipping_method_services ||= Spree::ShippingMethod.services + + {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services} + end + end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 1bc965dfb3..80101d9e8c 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -3,8 +3,8 @@ = inject_enterprises -# inject enterprises in this group --# further hubs and producers of these enterprises can't be resoleved within this small subset -= inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, active_distributors: @active_distributors +-# further hubs and producers of these enterprises can't be resolved within this small subset += inject_group_enterprises #group-page.row.pad-top{"ng-controller" => "GroupPageCtrl"} .small-12.columns.pad-top @@ -95,7 +95,7 @@ = render partial: 'home/fat' = render partial: 'shared/components/enterprise_no_results' - + .small-12.medium-12.large-3.columns = render partial: 'contact' diff --git a/spec/controllers/base_controller_spec.rb b/spec/controllers/base_controller_spec.rb index 1040b0594c..b5ef006c5b 100644 --- a/spec/controllers/base_controller_spec.rb +++ b/spec/controllers/base_controller_spec.rb @@ -24,9 +24,4 @@ describe BaseController do response.should redirect_to root_url flash[:info].should == "The order cycle you've selected has just closed. Please try again!" end - - it "loads active_distributors" do - Enterprise.stub(:distributors_with_active_order_cycles) { 'active distributors' } - controller.load_active_distributors.should == 'active distributors' - end end From 4a59c85b3e83bcf02837c3665f911f90f2072411 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 15:48:40 +1000 Subject: [PATCH 161/206] Inject current hub from AMS rather than RABL --- app/helpers/injection_helper.rb | 4 ++++ app/views/json/_current_hub.rabl | 6 ------ app/views/layouts/darkswarm.html.haml | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 app/views/json/_current_hub.rabl diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 2229888f98..a0aa2f87f1 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -7,6 +7,10 @@ module InjectionHelper inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data end + def inject_current_hub + inject_json_ams "currentHub", current_distributor, Api::EnterpriseSerializer, enterprise_injection_data + end + def inject_current_order inject_json_ams "currentOrder", current_order, Api::CurrentOrderSerializer, current_distributor: current_distributor, current_order_cycle: current_order_cycle end diff --git a/app/views/json/_current_hub.rabl b/app/views/json/_current_hub.rabl deleted file mode 100644 index 103baf9fb3..0000000000 --- a/app/views/json/_current_hub.rabl +++ /dev/null @@ -1,6 +0,0 @@ -object current_distributor -extends 'json/partials/enterprise' - -child suppliers: :producers do - attributes :id -end diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index a627ba4896..5169efa942 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -24,7 +24,7 @@ = render partial: "shared/ie_warning" = javascript_include_tag "iehack" - = inject_json "currentHub", "current_hub" + = inject_current_hub = inject_json "user", "current_user" = inject_json "railsFlash", "flash" = inject_taxons From cf79b90044c219757dc5e2044b9ea1e70944e3b1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 16:24:13 +1000 Subject: [PATCH 162/206] Load relatives of all enterprises in one go --- app/models/enterprise_relationship.rb | 22 +++++++++++++++++++++ spec/models/enterprise_relationship_spec.rb | 12 +++++++++++ 2 files changed, 34 insertions(+) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index fbdef9d52c..22d04a6cd1 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -25,6 +25,28 @@ class EnterpriseRelationship < ActiveRecord::Base scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name') + # Load an array of the relatives of each enterprise (ie. any enterprise related to it in + # either direction). This array is split into distributors and producers, and has the format: + # {enterprise_id => {distributors: [id, ...], producers: [id, ...]} } + def self.relatives + relationships = EnterpriseRelationship.includes(:child, :parent) + relatives = {} + + relationships.each do |r| + relatives[r.parent_id] ||= {distributors: [], producers: []} + relatives[r.child_id] ||= {distributors: [], producers: []} + + relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer + relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor + + relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer + relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor + end + + relatives + end + + def permissions_list=(perms) perms.andand.each { |name| permissions.build name: name } end diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index e87c204037..cb1743dec1 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -69,4 +69,16 @@ describe EnterpriseRelationship do EnterpriseRelationship.with_permission('two').sort.should == [er1, er2].sort end end + + describe "finding relatives" do + let(:e1) { create(:supplier_enterprise) } + let(:e2) { create(:supplier_enterprise, sells: 'any') } + let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) } + + it "categorises enterprises into distributors and producers" do + EnterpriseRelationship.relatives.should == + {e1.id => {distributors: [e2.id], producers: [e2.id]}, + e2.id => {distributors: [], producers: [e1.id]}} + end + end end From 3afd636577ad2348687a098b01c0dec4d457126e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 May 2015 16:25:05 +1000 Subject: [PATCH 163/206] Pull relatives computation out of the serialization loop --- app/helpers/injection_helper.rb | 3 ++- app/serializers/api/enterprise_serializer.rb | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index a0aa2f87f1..1d5f4d19e2 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -69,8 +69,9 @@ module InjectionHelper @active_distributors ||= Enterprise.distributors_with_active_order_cycles @earliest_closing_times ||= OrderCycle.earliest_closing_times @shipping_method_services ||= Spree::ShippingMethod.services + @relatives ||= EnterpriseRelationship.relatives - {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services} + {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives} end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index f7bd06f239..a97544b3ee 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -72,11 +72,13 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer end def producers - ActiveModel::ArraySerializer.new(object.suppliers.activated, {each_serializer: Api::IdSerializer}) + relatives = options[:relatives][object.id] + relatives ? relatives[:producers] : [] end def hubs - ActiveModel::ArraySerializer.new(object.distributors.activated, {each_serializer: Api::IdSerializer}) + relatives = options[:relatives][object.id] + relatives ? relatives[:distributors] : [] end # Map svg icons. From 2c92b5a7516b888d87583ad6e35a876bc583d504 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 10:57:14 +1000 Subject: [PATCH 164/206] Find all supplied and distributed taxons --- app/models/spree/taxon_decorator.rb | 36 +++++++++++++++++++++++++++++ spec/models/spree/taxon_spec.rb | 28 ++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 spec/models/spree/taxon_spec.rb diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb index 10ee0b4719..1a26ce73a8 100644 --- a/app/models/spree/taxon_decorator.rb +++ b/app/models/spree/taxon_decorator.rb @@ -9,4 +9,40 @@ Spree::Taxon.class_eval do #fs << Spree::ProductFilters.distributor_filter if Spree::ProductFilters.respond_to? :distributor_filter fs end + + # Find all the taxons of supplied products for each enterprise, indexed by enterprise. + # Format: {enterprise_id => [taxon_id, ...]} + def self.supplied_taxons + taxons = {} + + Spree::Taxon. + joins(:products => :supplier). + select('spree_taxons.*, enterprises.id AS enterprise_id'). + each do |t| + + taxons[t.enterprise_id.to_i] ||= Set.new + taxons[t.enterprise_id.to_i] << t.id + end + + taxons + end + + # Find all the taxons of distributed products for each enterprise, indexed by enterprise. + # Format: {enterprise_id => [taxon_id, ...]} + def self.distributed_taxons + taxons = {} + + Spree::Taxon. + joins(:products). + merge(Spree::Product.with_order_cycles_outer). + where('o_exchanges.incoming = ?', false). + select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id'). + each do |t| + + taxons[t.enterprise_id.to_i] ||= Set.new + taxons[t.enterprise_id.to_i] << t.id + end + + taxons + end end diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb new file mode 100644 index 0000000000..a0d729c054 --- /dev/null +++ b/spec/models/spree/taxon_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Spree + describe Taxon do + let(:e) { create(:supplier_enterprise) } + let(:t0) { p1.taxons.order('id ASC').first } + let(:t1) { create(:taxon) } + let(:t2) { create(:taxon) } + + describe "finding all supplied taxons" do + let!(:p1) { create(:simple_product, supplier: e, taxons: [t1, t2]) } + + it "finds taxons" do + Taxon.supplied_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])} + end + end + + describe "finding all distributed taxons" do + let!(:oc) { create(:simple_order_cycle, distributors: [e], variants: [p1.master]) } + let(:s) { create(:supplier_enterprise) } + let(:p1) { create(:simple_product, supplier: s, taxons: [t1, t2]) } + + it "finds taxons" do + Taxon.distributed_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])} + end + end + end +end From 1a887df4127d932f2f0f40d54a4ffc9f416aa65b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 11:03:53 +1000 Subject: [PATCH 165/206] Pull taxon computation out of the serialization loop --- app/helpers/injection_helper.rb | 4 +++- app/serializers/api/enterprise_serializer.rb | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 1d5f4d19e2..c2e4e6c91a 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -70,8 +70,10 @@ module InjectionHelper @earliest_closing_times ||= OrderCycle.earliest_closing_times @shipping_method_services ||= Spree::ShippingMethod.services @relatives ||= EnterpriseRelationship.relatives + @supplied_taxons ||= Spree::Taxon.supplied_taxons + @distributed_taxons ||= Spree::Taxon.distributed_taxons - {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives} + {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives, supplied_taxons: @supplied_taxons, distributed_taxons: @distributed_taxons} end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index a97544b3ee..d15ee7860a 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -36,11 +36,18 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer :email, :hash, :logo, :promo_image, :path, :pickup, :delivery, :icon, :icon_font, :producer_icon_font, :category, :producers, :hubs - has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer - has_many :supplied_taxons, serializer: Api::IdSerializer + attributes :taxons, :supplied_taxons has_one :address, serializer: Api::AddressSerializer + def taxons + options[:distributed_taxons][object.id] + end + + def supplied_taxons + options[:supplied_taxons][object.id] + end + def pickup services = options[:shipping_method_services][object.id] services ? services[:pickup] : false From dd761719eea337c807645ae32a3f7909a81f2e54 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 11:27:47 +1000 Subject: [PATCH 166/206] Fix undefined Api::IdSerializer error --- app/serializers/api/enterprise_serializer.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index d15ee7860a..6f46d62eda 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -1,4 +1,7 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer + # We reference this here because otherwise the serializer complains about its absence + Api::IdSerializer + def serializable_hash cached_serializer_hash.merge uncached_serializer_hash end @@ -40,6 +43,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer has_one :address, serializer: Api::AddressSerializer + def taxons options[:distributed_taxons][object.id] end From 31b726613d5116dc2a1f615aa0f16f55d6f58559 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 12:17:09 +1000 Subject: [PATCH 167/206] Avoid loading enterprise injection data when it's not be needed due to caching --- app/helpers/injection_helper.rb | 14 ++++------ app/serializers/api/enterprise_serializer.rb | 16 +++++------ .../enterprise_injection_data.rb | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 lib/open_food_network/enterprise_injection_data.rb diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index c2e4e6c91a..05057c136b 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -1,6 +1,8 @@ +require 'open_food_network/enterprise_injection_data' + module InjectionHelper def inject_enterprises - inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, enterprise_injection_data + inject_json_ams "enterprises", Enterprise.activated.includes(:address).all, Api::EnterpriseSerializer, enterprise_injection_data end def inject_group_enterprises @@ -66,14 +68,8 @@ module InjectionHelper private def enterprise_injection_data - @active_distributors ||= Enterprise.distributors_with_active_order_cycles - @earliest_closing_times ||= OrderCycle.earliest_closing_times - @shipping_method_services ||= Spree::ShippingMethod.services - @relatives ||= EnterpriseRelationship.relatives - @supplied_taxons ||= Spree::Taxon.supplied_taxons - @distributed_taxons ||= Spree::Taxon.distributed_taxons - - {active_distributors: @active_distributors, earliest_closing_times: @earliest_closing_times, shipping_method_services: @shipping_method_services, relatives: @relatives, supplied_taxons: @supplied_taxons, distributed_taxons: @distributed_taxons} + @enterprise_injection_data ||= OpenFoodNetwork::EnterpriseInjectionData.new + {data: @enterprise_injection_data} end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 6f46d62eda..4f38c12964 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -21,11 +21,11 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active def orders_close_at - options[:earliest_closing_times][object.id] + options[:data].earliest_closing_times[object.id] end def active - options[:active_distributors].andand.include? object + options[:data].active_distributors.andand.include? object end end @@ -45,20 +45,20 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def taxons - options[:distributed_taxons][object.id] + options[:data].distributed_taxons[object.id] end def supplied_taxons - options[:supplied_taxons][object.id] + options[:data].supplied_taxons[object.id] end def pickup - services = options[:shipping_method_services][object.id] + services = options[:data].shipping_method_services[object.id] services ? services[:pickup] : false end def delivery - services = options[:shipping_method_services][object.id] + services = options[:data].shipping_method_services[object.id] services ? services[:delivery] : false end @@ -83,12 +83,12 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer end def producers - relatives = options[:relatives][object.id] + relatives = options[:data].relatives[object.id] relatives ? relatives[:producers] : [] end def hubs - relatives = options[:relatives][object.id] + relatives = options[:data].relatives[object.id] relatives ? relatives[:distributors] : [] end diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb new file mode 100644 index 0000000000..9862418b98 --- /dev/null +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -0,0 +1,27 @@ +module OpenFoodNetwork + class EnterpriseInjectionData + def active_distributors + @active_distributors ||= Enterprise.distributors_with_active_order_cycles + end + + def earliest_closing_times + @earliest_closing_times ||= OrderCycle.earliest_closing_times + end + + def shipping_method_services + @shipping_method_services ||= Spree::ShippingMethod.services + end + + def relatives + @relatives ||= EnterpriseRelationship.relatives + end + + def supplied_taxons + @supplied_taxons ||= Spree::Taxon.supplied_taxons + end + + def distributed_taxons + @distributed_taxons ||= Spree::Taxon.distributed_taxons + end + end +end From e1b4c3b1e4bb75efe713c8b246fdb835004caa36 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 13:47:24 +1000 Subject: [PATCH 168/206] Add benchmarking test for inject_enterprises --- spec/performance/injection_helper_spec.rb | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 spec/performance/injection_helper_spec.rb diff --git a/spec/performance/injection_helper_spec.rb b/spec/performance/injection_helper_spec.rb new file mode 100644 index 0000000000..4ba22d738f --- /dev/null +++ b/spec/performance/injection_helper_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe InjectionHelper, type: :helper do + let(:oc) { create(:simple_order_cycle) } + let(:relative_supplier) { create(:supplier_enterprise) } + let(:relative_distributor) { create(:distributor_enterprise) } + + before do + 50.times do + e = create(:enterprise) + oc.distributors << e + create(:enterprise_relationship, parent: e, child: relative_supplier) + create(:enterprise_relationship, parent: e, child: relative_distributor) + end + end + + it "is performant in injecting enterprises" do + results = [] + 4.times do |i| + ActiveRecord::Base.connection.query_cache.clear + Rails.cache.clear + result = Benchmark.measure { helper.inject_enterprises } + results << result.total if i > 0 + puts result + end + + puts (results.sum / results.count * 1000).round 0 + end +end From 41bc67e2d8e601b111c8e3820206b79e8cf1e04f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 May 2015 14:46:33 +1000 Subject: [PATCH 169/206] Add benchmark for product serialisation --- spec/performance/shop_controller_spec.rb | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 spec/performance/shop_controller_spec.rb diff --git a/spec/performance/shop_controller_spec.rb b/spec/performance/shop_controller_spec.rb new file mode 100644 index 0000000000..14e258d18d --- /dev/null +++ b/spec/performance/shop_controller_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe ShopController, type: :controller do + let(:d) { create(:distributor_enterprise) } + let(:enterprise_fee) { create(:enterprise_fee) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [d], coordinator_fees: [enterprise_fee]) } + + before do + controller.stub(:current_distributor) { d } + controller.stub(:current_order_cycle) { order_cycle } + end + + describe "fetching products" do + let(:exchange) { order_cycle.exchanges.to_enterprises(d).outgoing.first } + let(:image) { File.open(File.expand_path('../../../app/assets/images/logo.jpg', __FILE__)) } + + before do + 11.times do + p = create(:simple_product) + p.set_property 'Organic Certified', 'NASAA 12345' + v1 = create(:variant, product: p) + v2 = create(:variant, product: p) + Spree::Image.create! viewable_id: p.master.id, viewable_type: 'Spree::Variant', attachment: image + + exchange.variants << [v1, v2] + end + end + + it "returns products via json" do + results = [] + 4.times do |i| + ActiveRecord::Base.connection.query_cache.clear + Rails.cache.clear + result = Benchmark.measure do + xhr :get, :products + response.should be_success + end + + results << result.total if i > 0 + puts result + end + + puts (results.sum / results.count * 1000).round 0 + end + end +end From e74390a013b9b614375c98bd63eb847b804923ed Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 May 2015 16:26:08 +1000 Subject: [PATCH 170/206] Remove controller specs for @active_distributors, now set via helper --- spec/controllers/home_controller_spec.rb | 12 ------------ spec/controllers/map_controller_spec.rb | 13 ------------- spec/controllers/producers_controller_spec.rb | 15 --------------- 3 files changed, 40 deletions(-) delete mode 100644 spec/controllers/map_controller_spec.rb delete mode 100644 spec/controllers/producers_controller_spec.rb diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb index bbbff9a7b1..924462741f 100644 --- a/spec/controllers/home_controller_spec.rb +++ b/spec/controllers/home_controller_spec.rb @@ -9,21 +9,9 @@ describe HomeController do Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] } end - it "sets active distributors" do - get :index - assigns[:active_distributors].should == [distributor] - end - # Exclusion from actual rendered view handled in features/consumer/home it "shows JSON for invisible hubs" do get :index response.body.should have_content invisible_distributor.name end - - # This is done inside the json/hubs Serializer - it "gets the next order cycle for each hub" do - OrderCycle.should_receive(:first_closing_for).twice - get :index - end end - diff --git a/spec/controllers/map_controller_spec.rb b/spec/controllers/map_controller_spec.rb deleted file mode 100644 index fab9b7ac22..0000000000 --- a/spec/controllers/map_controller_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe MapController do - it "loads active distributors" do - active_distributors = double(:distributors) - - Enterprise.stub(:distributors_with_active_order_cycles) { active_distributors } - - get :index - - assigns(:active_distributors).should == active_distributors - end -end diff --git a/spec/controllers/producers_controller_spec.rb b/spec/controllers/producers_controller_spec.rb deleted file mode 100644 index ec3c39036c..0000000000 --- a/spec/controllers/producers_controller_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe ProducersController do - let!(:distributor) { create(:distributor_enterprise) } - - before do - Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] } - Enterprise.stub(:all).and_return([distributor]) - end - - it "sets active distributors" do - get :index - assigns[:active_distributors].should == [distributor] - end -end From 75f1f673ad3d63a6c366947876ce3479b62f050a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 May 2015 16:26:31 +1000 Subject: [PATCH 171/206] Update spec for EnterpriseSerializer --- spec/serializers/enterprise_serializer_spec.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index 1063a042e7..7c467d9c3d 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -3,19 +3,18 @@ describe Api::EnterpriseSerializer do let(:enterprise) { create(:distributor_enterprise) } let(:taxon) { create(:taxon) } + let(:data_class) { Struct.new(:earliest_closing_times, :active_distributors, + :distributed_taxons, :supplied_taxons, + :shipping_method_services, :relatives) } + let(:data) { data_class.new({}, [], {}, {}, {}, {producers: [], distributors: []}) } + it "serializes an enterprise" do - serializer = Api::EnterpriseSerializer.new enterprise + serializer = Api::EnterpriseSerializer.new enterprise, data: data serializer.to_json.should match enterprise.name end - it "includes distributed taxons" do - enterprise.stub(:distributed_taxons).and_return [taxon] - serializer = Api::EnterpriseSerializer.new enterprise - serializer.to_json.should match taxon.id.to_s - end - it "will render urls" do - serializer = Api::EnterpriseSerializer.new enterprise + serializer = Api::EnterpriseSerializer.new enterprise, data: data serializer.to_json.should match "map_005-hub.svg" end end From 3ab7df88e68bf024039f1a59ca64283b3daaef95 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 May 2015 16:26:40 +1000 Subject: [PATCH 172/206] Allow serialization of nil enterprise --- app/serializers/api/enterprise_serializer.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 4f38c12964..fc843a5138 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -3,17 +3,18 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer Api::IdSerializer def serializable_hash + cached_serializer_hash.merge uncached_serializer_hash end private def cached_serializer_hash - Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash + Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash || {} end def uncached_serializer_hash - Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash + Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash || {} end end @@ -31,7 +32,12 @@ end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer cached - delegate :cache_key, to: :object + #delegate :cache_key, to: :object + + def cache_key + object.andand.cache_key + end + attributes :name, :id, :description, :latitude, :longitude, :long_description, :website, :instagram, :linkedin, :twitter, From ccf1e2951cce17cd4c4ff9f348a1e690976cc466 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 28 May 2015 10:45:47 +1000 Subject: [PATCH 173/206] Fix intermittent failure in permissions spec --- spec/lib/open_food_network/permissions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index cf2aa1a217..646a219a5b 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -47,7 +47,7 @@ module OpenFoodNetwork expect(permissions).to receive(:managed_enterprises) { Enterprise.where(id: e1) } expect(permissions).to receive(:related_enterprises_granting).with(:some_permission) { Enterprise.where(id: e3) } expect(permissions).to receive(:related_enterprises_granted).with(:some_permission) { Enterprise.where(id: e4) } - expect(permissions.send(:managed_and_related_enterprises_with, :some_permission)).to eq [e1, e3, e4] + expect(permissions.send(:managed_and_related_enterprises_with, :some_permission)).to match_array [e1, e3, e4] end end end From 97e49c2bdb4c3173ec357ce8a498bb54e7235123 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 28 May 2015 10:46:08 +1000 Subject: [PATCH 174/206] Replace 'array.sort.should == expected.sort' pattern with match_array --- .../spree/admin/orders_controller_spec.rb | 4 ++-- .../spree/admin/reports_controller_spec.rb | 12 +++++----- .../spree/admin/variants_controller_spec.rb | 2 +- spec/features/admin/enterprise_groups_spec.rb | 2 +- .../admin/enterprise_relationships_spec.rb | 2 +- spec/features/admin/order_cycles_spec.rb | 20 ++++++++-------- spec/features/admin/products_spec.rb | 4 ++-- spec/features/admin/shipping_methods_spec.rb | 2 +- .../customers_report_spec.rb | 18 +++++++------- .../order_cycle_form_applicator_spec.rb | 16 ++++++------- .../lib/open_food_network/permissions_spec.rb | 2 +- .../products_and_inventory_report_spec.rb | 22 ++++++++--------- spec/models/enterprise_fee_spec.rb | 4 ++-- spec/models/enterprise_relationship_spec.rb | 8 +++---- spec/models/enterprise_spec.rb | 22 ++++++++--------- spec/models/exchange_spec.rb | 6 ++--- spec/models/order_cycle_spec.rb | 24 +++++++++---------- spec/models/spree/line_item_spec.rb | 2 +- spec/models/spree/payment_spec.rb | 4 ++-- spec/models/spree/product_spec.rb | 2 +- spec/models/spree/shipping_method_spec.rb | 2 +- spec/models/spree/variant_spec.rb | 2 +- spec/models/variant_override_spec.rb | 2 +- 23 files changed, 92 insertions(+), 92 deletions(-) diff --git a/spec/controllers/spree/admin/orders_controller_spec.rb b/spec/controllers/spree/admin/orders_controller_spec.rb index ac6a5532b2..dae1edcf29 100644 --- a/spec/controllers/spree/admin/orders_controller_spec.rb +++ b/spec/controllers/spree/admin/orders_controller_spec.rb @@ -143,7 +143,7 @@ describe Spree::Admin::OrdersController do end it "only displays line items from orders for which my enterprise is the order_cycle coorinator" do - json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.sort.should == [line_item1.id, line_item2.id, line_item3.id].sort + json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.should match_array [line_item1.id, line_item2.id, line_item3.id] end end @@ -159,7 +159,7 @@ describe Spree::Admin::OrdersController do end it "only displays line items from orders for which my enterprise is a distributor" do - json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.sort.should == [line_item1.id, line_item2.id].sort + json_response.map{ |order| order['line_items'] }.flatten.map{ |line_item| line_item["id"] }.should match_array [line_item1.id, line_item2.id] end end end diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 58d406519b..e930c1e339 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -170,17 +170,17 @@ describe Spree::Admin::ReportsController do it "should build distributors for the current user" do spree_get :products_and_inventory - assigns(:distributors).sort.should == [c1, c2, d1, d2, d3].sort + assigns(:distributors).should match_array [c1, c2, d1, d2, d3] end it "builds suppliers for the current user" do spree_get :products_and_inventory - assigns(:suppliers).sort.should == [s1, s2, s3].sort + assigns(:suppliers).should match_array [s1, s2, s3] end it "builds order cycles for the current user" do spree_get :products_and_inventory - assigns(:order_cycles).sort.should == [ocB, ocA].sort + assigns(:order_cycles).should match_array [ocB, ocA] end it "assigns report types" do @@ -211,17 +211,17 @@ describe Spree::Admin::ReportsController do it "should build distributors for the current user" do spree_get :customers - assigns(:distributors).sort.should == [c1, c2, d1, d2, d3].sort + assigns(:distributors).should match_array [c1, c2, d1, d2, d3] end it "builds suppliers for the current user" do spree_get :customers - assigns(:suppliers).sort.should == [s1, s2, s3].sort + assigns(:suppliers).should match_array [s1, s2, s3] end it "builds order cycles for the current user" do spree_get :customers - assigns(:order_cycles).sort.should == [ocB, ocA].sort + assigns(:order_cycles).should match_array [ocB, ocA] end it "assigns report types" do diff --git a/spec/controllers/spree/admin/variants_controller_spec.rb b/spec/controllers/spree/admin/variants_controller_spec.rb index 22972a7301..965ff8d7ce 100644 --- a/spec/controllers/spree/admin/variants_controller_spec.rb +++ b/spec/controllers/spree/admin/variants_controller_spec.rb @@ -30,7 +30,7 @@ module Spree it "does not filter when no distributor or order cycle is specified" do spree_get :search, q: 'Prod' - assigns(:variants).sort.should == [p1.master, p2.master].sort + assigns(:variants).should match_array [p1.master, p2.master] end end end diff --git a/spec/features/admin/enterprise_groups_spec.rb b/spec/features/admin/enterprise_groups_spec.rb index 59712f3bfe..2adb96c0a5 100644 --- a/spec/features/admin/enterprise_groups_spec.rb +++ b/spec/features/admin/enterprise_groups_spec.rb @@ -50,7 +50,7 @@ feature %q{ eg.name.should == 'EGEGEG' eg.description.should == 'This is a description' eg.on_front_page.should be_true - eg.enterprises.sort.should == [e1, e2].sort + eg.enterprises.should match_array [e1, e2] end scenario "editing an enterprise group" do diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index 3a2d9692de..ef0f1e5537 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -50,7 +50,7 @@ feature %q{ page.should have_relationship e1, e2, ['to add to order cycle', 'to override variant details', 'to edit profile'] er = EnterpriseRelationship.where(parent_id: e1, child_id: e2).first er.should be_present - er.permissions.map(&:name).sort.should == ['add_to_order_cycle', 'edit_profile', 'create_variant_overrides'].sort + er.permissions.map(&:name).should match_array ['add_to_order_cycle', 'edit_profile', 'create_variant_overrides'] end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index cb96799fbb..eed6551bfa 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -354,7 +354,7 @@ feature %q{ page.should have_selector 'td.distributors', text: 'My distributor' # And my coordinator fees should have been configured - OrderCycle.last.coordinator_fee_ids.sort.should == [coordinator_fee1.id, coordinator_fee2.id].sort + OrderCycle.last.coordinator_fee_ids.should match_array [coordinator_fee1.id, coordinator_fee2.id] # And my supplier fees should have been configured OrderCycle.last.exchanges.incoming.last.enterprise_fee_ids.should == [supplier_fee2.id] @@ -364,7 +364,7 @@ feature %q{ # And it should have some variants selected selected_initial_variants = initial_variants.take initial_variants.size - 1 - OrderCycle.last.variants.map(&:id).sort.should == (selected_initial_variants.map(&:id) + [v1.id, v2.id]).sort + OrderCycle.last.variants.map(&:id).should match_array (selected_initial_variants.map(&:id) + [v1.id, v2.id]) # And the collection details should have been updated OrderCycle.last.exchanges.where(pickup_time: 'New time 0', pickup_instructions: 'New instructions 0').should be_present @@ -568,9 +568,9 @@ feature %q{ flash_message.should == "Your order cycle has been created." order_cycle = OrderCycle.find_by_name('My order cycle') - order_cycle.suppliers.sort.should == [supplier_managed, supplier_permitted].sort + order_cycle.suppliers.should match_array [supplier_managed, supplier_permitted] order_cycle.coordinator.should == distributor_managed - order_cycle.distributors.sort.should == [distributor_managed, distributor_permitted].sort + order_cycle.distributors.should match_array [distributor_managed, distributor_permitted] end scenario "editing an order cycle we can see (and for now, edit) all exchanges in the order cycle" do @@ -592,9 +592,9 @@ feature %q{ page.should have_content "Your order cycle has been updated." oc.reload - oc.suppliers.sort.should == [supplier_managed, supplier_permitted, supplier_unmanaged].sort + oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed - oc.distributors.sort.should == [distributor_managed, distributor_permitted, distributor_unmanaged].sort + oc.distributors.should match_array [distributor_managed, distributor_permitted, distributor_unmanaged] end scenario "editing an order cycle" do @@ -683,9 +683,9 @@ feature %q{ page.should have_content "Your order cycle has been updated." oc.reload - oc.suppliers.sort.should == [supplier_managed, supplier_permitted, supplier_unmanaged].sort + oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed - oc.distributors.sort.should == [distributor_managed, distributor_permitted, distributor_unmanaged].sort + oc.distributors.should match_array [distributor_managed, distributor_permitted, distributor_unmanaged] end end @@ -736,9 +736,9 @@ feature %q{ page.should have_content "Your order cycle has been updated." oc.reload - oc.suppliers.sort.should == [supplier_managed, supplier_permitted, supplier_unmanaged].sort + oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed - oc.distributors.sort.should == [my_distributor, distributor_managed, distributor_permitted, distributor_unmanaged].sort + oc.distributors.should match_array [my_distributor, distributor_managed, distributor_permitted, distributor_unmanaged] end end end diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 740de12045..95569799b4 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -68,10 +68,10 @@ feature %q{ click_button 'Update' product.reload - product.distributors.sort.should == [@distributors[0], @distributors[2]].sort + product.distributors.should match_array [@distributors[0], @distributors[2]] - product.product_distributions.map { |pd| pd.enterprise_fee }.sort.should == [@enterprise_fees[0], @enterprise_fees[2]].sort + product.product_distributions.map { |pd| pd.enterprise_fee }.should match_array [@enterprise_fees[0], @enterprise_fees[2]] end scenario "making a product into a group buy product" do diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb index 2aeeb1db1d..2857fa6502 100644 --- a/spec/features/admin/shipping_methods_spec.rb +++ b/spec/features/admin/shipping_methods_spec.rb @@ -36,7 +36,7 @@ feature 'shipping methods' do sm = Spree::ShippingMethod.last sm.name.should == 'Carrier Pidgeon' - sm.distributors.sort.should == [d1, d2].sort + sm.distributors.should match_array [d1, d2] end it "at checkout, user can only see shipping methods for their current distributor (checkout spec)" diff --git a/spec/lib/open_food_network/customers_report_spec.rb b/spec/lib/open_food_network/customers_report_spec.rb index c655fa626e..289a32929e 100644 --- a/spec/lib/open_food_network/customers_report_spec.rb +++ b/spec/lib/open_food_network/customers_report_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' module OpenFoodNetwork describe CustomersReport do context "as a site admin" do - let(:user) do + let(:user) do user = create(:user) user.spree_roles << Spree::Role.find_or_create_by_name!("admin") user @@ -44,14 +44,14 @@ module OpenFoodNetwork it "builds a table from a list of variants" do a = create(:address) d = create(:distributor_enterprise) - o = create(:order, distributor: d, bill_address: a) + o = create(:order, distributor: d, bill_address: a) o.shipping_method = create(:shipping_method) subject.stub(:orders).and_return [o] subject.table.should == [[ - a.firstname, a.lastname, - [a.address1, a.address2, a.city].join(" "), - o.email, a.phone, d.name, + a.firstname, a.lastname, + [a.address1, a.address2, a.city].join(" "), + o.email, a.phone, d.name, [d.address.address1, d.address.address2, d.address.city].join(" "), o.shipping_method.name ]] @@ -74,7 +74,7 @@ module OpenFoodNetwork end context "as an enterprise user" do - let(:user) do + let(:user) do user = create(:user) user.spree_roles = [] user.save! @@ -131,7 +131,7 @@ module OpenFoodNetwork order2.line_items << create(:line_item, product: product2) subject.stub(:params).and_return(supplier_id: supplier.id) - subject.filter(orders).sort.should == [order1] + subject.filter(orders).should == [order1] end it "filters to a specific distributor" do @@ -141,7 +141,7 @@ module OpenFoodNetwork order2 = create(:order, distributor: d2) subject.stub(:params).and_return(distributor_id: d1.id) - subject.filter(orders).sort.should == [order1] + subject.filter(orders).should == [order1] end it "filters to a specific cycle" do @@ -151,7 +151,7 @@ module OpenFoodNetwork order2 = create(:order, order_cycle: oc2) subject.stub(:params).and_return(order_cycle_id: oc1.id) - subject.filter(orders).sort.should == [order1] + subject.filter(orders).should == [order1] end end end diff --git a/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb b/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb index 6a03964039..5314c4376c 100644 --- a/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb +++ b/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb @@ -300,8 +300,8 @@ module OpenFoodNetwork expect(exchange.sender).to eq sender expect(exchange.receiver).to eq receiver expect(exchange.incoming).to eq incoming - expect(exchange.variants.sort).to eq [variant1, variant2].sort - expect(exchange.enterprise_fees.sort).to eq [enterprise_fee1, enterprise_fee2].sort + expect(exchange.variants).to match_array [variant1, variant2] + expect(exchange.enterprise_fees).to match_array [enterprise_fee1, enterprise_fee2] applicator.send(:touched_exchanges).should == [exchange] end @@ -345,8 +345,8 @@ module OpenFoodNetwork it "updates the variants, enterprise fees and pickup information of the exchange" do exchange.reload - expect(exchange.variants.sort).to eq [variant1, variant3].sort - expect(exchange.enterprise_fees.sort).to eq [enterprise_fee2, enterprise_fee3] + expect(exchange.variants).to match_array [variant1, variant3] + expect(exchange.enterprise_fees).to match_array [enterprise_fee2, enterprise_fee3] expect(exchange.pickup_time).to eq 'New Pickup Time' expect(exchange.pickup_instructions).to eq 'New Pickup Instructions' expect(applicator.send(:touched_exchanges)).to eq [exchange] @@ -364,8 +364,8 @@ module OpenFoodNetwork it "updates the variants, enterprise fees and pickup information of the exchange" do exchange.reload - expect(exchange.variants.sort).to eq [variant1, variant3].sort - expect(exchange.enterprise_fees.sort).to eq [enterprise_fee2, enterprise_fee3] + expect(exchange.variants).to match_array [variant1, variant3] + expect(exchange.enterprise_fees).to match_array [enterprise_fee2, enterprise_fee3] expect(exchange.pickup_time).to eq 'New Pickup Time' expect(exchange.pickup_instructions).to eq 'New Pickup Instructions' expect(applicator.send(:touched_exchanges)).to eq [exchange] @@ -383,8 +383,8 @@ module OpenFoodNetwork it "updates the variants in the exchange, but not the fees or pickup information" do exchange.reload - expect(exchange.variants.sort).to eq [variant1, variant3].sort - expect(exchange.enterprise_fees.sort).to eq [enterprise_fee1, enterprise_fee2] + expect(exchange.variants).to match_array [variant1, variant3] + expect(exchange.enterprise_fees).to match_array [enterprise_fee1, enterprise_fee2] expect(exchange.pickup_time).to_not eq 'New Pickup Time' expect(exchange.pickup_instructions).to_not eq 'New Pickup Instructions' expect(applicator.send(:touched_exchanges)).to eq [exchange] diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 646a219a5b..4224a00d89 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -98,7 +98,7 @@ module OpenFoodNetwork {1 => [e1.id], 2 => [e1.id, e2.id]} end - permissions.variant_override_producers.sort.should == [e1, e2].sort + permissions.variant_override_producers.should match_array [e1, e2] end end diff --git a/spec/lib/open_food_network/products_and_inventory_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_report_spec.rb index 2e23919fe6..4ae93e7e64 100644 --- a/spec/lib/open_food_network/products_and_inventory_report_spec.rb +++ b/spec/lib/open_food_network/products_and_inventory_report_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' module OpenFoodNetwork describe ProductsAndInventoryReport do context "As a site admin" do - let(:user) do + let(:user) do user = create(:user) user.spree_roles << Spree::Role.find_or_create_by_name!("admin") user @@ -14,7 +14,7 @@ module OpenFoodNetwork it "Should return headers" do subject.header.should == [ - "Supplier", + "Supplier", "Producer Suburb", "Product", "Product Properties", @@ -63,7 +63,7 @@ module OpenFoodNetwork context "As an enterprise user" do let(:supplier) { create(:supplier_enterprise) } - let(:enterprise_user) do + let(:enterprise_user) do user = create(:user) user.enterprise_roles.create(enterprise: supplier) user.spree_roles = [] @@ -79,7 +79,7 @@ module OpenFoodNetwork variant_1 = create(:variant, product: product1) variant_2 = create(:variant, product: product1) - subject.child_variants.sort.should == [variant_1, variant_2].sort + subject.child_variants.should match_array [variant_1, variant_2] end it "should only return variants managed by the user" do @@ -87,7 +87,7 @@ module OpenFoodNetwork product2 = create(:simple_product, supplier: supplier) variant_1 = create(:variant, product: product1) variant_2 = create(:variant, product: product2) - + subject.child_variants.should == [variant_2] end end @@ -96,15 +96,15 @@ module OpenFoodNetwork it "should only return variants managed by the user" do product1 = create(:simple_product, supplier: create(:supplier_enterprise)) product2 = create(:simple_product, supplier: supplier) - + subject.master_variants.should == [product2.master] end it "doesn't return master variants with siblings" do product = create(:simple_product, supplier: supplier) - create(:variant, product: product) - - subject.master_variants.should be_empty + create(:variant, product: product) + + subject.master_variants.should be_empty end end @@ -113,13 +113,13 @@ module OpenFoodNetwork it "should return unfiltered variants sans-params" do product1 = create(:simple_product, supplier: supplier) product2 = create(:simple_product, supplier: supplier) - subject.filter(Spree::Variant.scoped).sort.should == [product1.master, product2.master].sort + subject.filter(Spree::Variant.scoped).should match_array [product1.master, product2.master] end it "should filter deleted products" do product1 = create(:simple_product, supplier: supplier) product2 = create(:simple_product, supplier: supplier) product2.delete - subject.filter(Spree::Variant.scoped).sort.should == [product1.master].sort + subject.filter(Spree::Variant.scoped).should match_array [product1.master] end describe "based on report type" do it "returns only variants on hand" do diff --git a/spec/models/enterprise_fee_spec.rb b/spec/models/enterprise_fee_spec.rb index fc72f383d2..f0f9df1f0b 100644 --- a/spec/models/enterprise_fee_spec.rb +++ b/spec/models/enterprise_fee_spec.rb @@ -43,7 +43,7 @@ describe EnterpriseFee do ef3 = create(:enterprise_fee, calculator: Spree::Calculator::PerItem.new) ef4 = create(:enterprise_fee, calculator: Spree::Calculator::PriceSack.new) - EnterpriseFee.per_item.sort.should == [ef1, ef2, ef3, ef4].sort + EnterpriseFee.per_item.should match_array [ef1, ef2, ef3, ef4] end end @@ -52,7 +52,7 @@ describe EnterpriseFee do ef1 = create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new) ef2 = create(:enterprise_fee, calculator: Spree::Calculator::FlexiRate.new) - EnterpriseFee.per_order.sort.should == [ef1, ef2].sort + EnterpriseFee.per_order.should match_array [ef1, ef2] end it "does not return fees with any other calculator" do diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index e87c204037..cbbdd00504 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -34,7 +34,7 @@ describe EnterpriseRelationship do it "creates permissions with a list" do er = EnterpriseRelationship.create! parent: e1, child: e2, permissions_list: ['one', 'two'] er.reload - er.permissions.map(&:name).sort.should == ['one', 'two'].sort + er.permissions.map(&:name).should match_array ['one', 'two'] end it "does nothing when the list is nil" do @@ -50,11 +50,11 @@ describe EnterpriseRelationship do let!(:er3) { create(:enterprise_relationship, parent: e1, child: e3) } it "finds relationships that grant permissions to some enterprises" do - EnterpriseRelationship.permitting([e1, e2]).sort.should == [er1, er2].sort + EnterpriseRelationship.permitting([e1, e2]).should match_array [er1, er2] end it "finds relationships that are granted by particular enterprises" do - EnterpriseRelationship.permitted_by([e1, e2]).sort.should == [er1, er3].sort + EnterpriseRelationship.permitted_by([e1, e2]).should match_array [er1, er3] end end @@ -66,7 +66,7 @@ describe EnterpriseRelationship do er3 = create(:enterprise_relationship, parent: e3, child: e1, permissions_list: ['three', 'four']) - EnterpriseRelationship.with_permission('two').sort.should == [er1, er2].sort + EnterpriseRelationship.with_permission('two').should match_array [er1, er2] end end end diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index df1b58aca6..ed4669c0c2 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -118,7 +118,7 @@ describe Enterprise do let!(:er2) { create(:enterprise_relationship, parent_id: e.id, child_id: c.id) } it "finds relatives" do - e.relatives.sort.should == [p, c].sort + e.relatives.should match_array [p, c] end it "scopes relatives to visible distributors" do @@ -422,7 +422,7 @@ describe Enterprise do create(:product, :distributors => [d1], :on_hand => 5) create(:product, :distributors => [d3], :on_hand => 0) - Enterprise.with_distributed_active_products_on_hand.sort.should == [d1, d2] + Enterprise.with_distributed_active_products_on_hand.should match_array [d1, d2] end it "returns distributors with available products in stock" do @@ -438,7 +438,7 @@ describe Enterprise do create(:product, :distributors => [d4], :on_hand => 0) create(:product, :distributors => [d5]).delete - Enterprise.with_distributed_active_products_on_hand.sort.should == [d1, d2] + Enterprise.with_distributed_active_products_on_hand.should match_array [d1, d2] Enterprise.with_distributed_active_products_on_hand.distinct_count.should == 2 end end @@ -458,7 +458,7 @@ describe Enterprise do create(:product, :supplier => d4, :on_hand => 0) create(:product, :supplier => d5).delete - Enterprise.with_supplied_active_products_on_hand.sort.should == [d1, d2] + Enterprise.with_supplied_active_products_on_hand.should match_array [d1, d2] Enterprise.with_supplied_active_products_on_hand.distinct_count.should == 2 end end @@ -485,7 +485,7 @@ describe Enterprise do p1 = create(:simple_product, supplier: s1) p2 = create(:simple_product, supplier: s2) - Enterprise.supplying_variant_in([p1.master, p2.master]).sort.should == [s1, s2].sort + Enterprise.supplying_variant_in([p1.master, p2.master]).should match_array [s1, s2] end it "does not return duplicates" do @@ -625,9 +625,9 @@ describe Enterprise do er = EnterpriseRelationship.where(parent_id: opts[:from], child_id: opts[:to]).last er.should_not be_nil if opts[:with] == :all_permissions - er.permissions.map(&:name).sort.should == ['add_to_order_cycle', 'manage_products', 'edit_profile', 'create_variant_overrides'].sort + er.permissions.map(&:name).should match_array ['add_to_order_cycle', 'manage_products', 'edit_profile', 'create_variant_overrides'] elsif opts.key? :with - er.permissions.map(&:name).sort.should == opts[:with].map(&:to_s).sort + er.permissions.map(&:name).should match_array opts[:with].map(&:to_s) end end end @@ -674,7 +674,7 @@ describe Enterprise do d = create(:distributor_enterprise) p = create(:product, distributors: [d]) v = create(:variant, product: p) - d.distributed_variants.sort.should == [p.master, v].sort + d.distributed_variants.should match_array [p.master, v] end it "finds variants distributed by order cycle" do @@ -696,7 +696,7 @@ describe Enterprise do d = create(:distributor_enterprise) p = create(:product, distributors: [d]) v = create(:variant, product: p) - d.product_distribution_variants.sort.should == [p.master, v].sort + d.product_distribution_variants.should match_array [p.master, v] end it "does not find variants distributed by order cycle" do @@ -752,12 +752,12 @@ describe Enterprise do it "gets all taxons of all distributed products" do Spree::Product.stub(:in_distributor).and_return [product1, product2] - distributor.distributed_taxons.sort.should == [taxon1, taxon2].sort + distributor.distributed_taxons.should match_array [taxon1, taxon2] end it "gets all taxons of all supplied products" do Spree::Product.stub(:in_supplier).and_return [product1, product2] - supplier.supplied_taxons.sort.should == [taxon1, taxon2].sort + supplier.supplied_taxons.should match_array [taxon1, taxon2] end end diff --git a/spec/models/exchange_spec.rb b/spec/models/exchange_spec.rb index 82f360aa16..0df7aeafab 100644 --- a/spec/models/exchange_spec.rb +++ b/spec/models/exchange_spec.rb @@ -170,17 +170,17 @@ describe Exchange do it "finds exchanges coming from any of a number of enterprises" do Exchange.from_enterprises([coordinator]).should == [outgoing_exchange] - Exchange.from_enterprises([supplier, coordinator]).sort.should == [incoming_exchange, outgoing_exchange].sort + Exchange.from_enterprises([supplier, coordinator]).should match_array [incoming_exchange, outgoing_exchange] end it "finds exchanges going to any of a number of enterprises" do Exchange.to_enterprises([coordinator]).should == [incoming_exchange] - Exchange.to_enterprises([coordinator, distributor]).sort.should == [incoming_exchange, outgoing_exchange].sort + Exchange.to_enterprises([coordinator, distributor]).should match_array [incoming_exchange, outgoing_exchange] end it "finds exchanges involving any of a number of enterprises" do Exchange.involving([supplier]).should == [incoming_exchange] - Exchange.involving([coordinator]).sort.should == [incoming_exchange, outgoing_exchange].sort + Exchange.involving([coordinator]).should match_array [incoming_exchange, outgoing_exchange] Exchange.involving([distributor]).should == [outgoing_exchange] end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 817d2e8e93..46cefd0600 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -37,7 +37,7 @@ describe OrderCycle do oc_undated = create(:simple_order_cycle, orders_open_at: nil, orders_close_at: nil) OrderCycle.active.should == [oc_active] - OrderCycle.inactive.sort.should == [oc_not_yet_open, oc_already_closed].sort + OrderCycle.inactive.should match_array [oc_not_yet_open, oc_already_closed] OrderCycle.upcoming.should == [oc_not_yet_open] OrderCycle.closed.should == [oc_already_closed] OrderCycle.undated.should == [oc_undated] @@ -153,7 +153,7 @@ describe OrderCycle do e2 = create(:exchange, incoming: true, order_cycle: oc, receiver: oc.coordinator, sender: create(:enterprise)) - oc.suppliers.sort.should == [e1.sender, e2.sender].sort + oc.suppliers.should match_array [e1.sender, e2.sender] end it "reports its distributors" do @@ -164,7 +164,7 @@ describe OrderCycle do e2 = create(:exchange, incoming: false, order_cycle: oc, sender: oc.coordinator, receiver: create(:enterprise)) - oc.distributors.sort.should == [e1.receiver, e2.receiver].sort + oc.distributors.should match_array [e1.receiver, e2.receiver] end it "checks for existance of distributors" do @@ -215,11 +215,11 @@ describe OrderCycle do end it "reports on the variants exchanged" do - @oc.variants.sort.should == [@p0.master, @p1.master, @p2.master, @p2_v].sort + @oc.variants.should match_array [@p0.master, @p1.master, @p2.master, @p2_v] end it "reports on the variants distributed" do - @oc.distributed_variants.sort.should == [@p1.master, @p2.master, @p2_v].sort + @oc.distributed_variants.should match_array [@p1.master, @p2.master, @p2_v] end it "reports on the variants distributed by a particular distributor" do @@ -231,7 +231,7 @@ describe OrderCycle do end it "reports on the products exchanged" do - @oc.products.sort.should == [@p0, @p1, @p2] + @oc.products.should match_array [@p0, @p1, @p2] end end @@ -313,7 +313,7 @@ describe OrderCycle do @oc.pickup_time_for(@d2).should == '2-8pm Friday' end end - + describe "finding pickup instructions for a distributor" do it "returns the pickup instructions" do @oc.pickup_instructions_for(@d1).should == "Come get it!" @@ -375,7 +375,7 @@ describe OrderCycle do occ.coordinator_fee_ids.should_not be_empty occ.coordinator_fee_ids.should == oc.coordinator_fee_ids - + # to_h gives us a unique hash for each exchange # check that the clone has no additional exchanges occ.exchanges.map(&:to_h).all? do |ex| @@ -402,7 +402,7 @@ describe OrderCycle do describe "finding order cycles opening in the future" do it "should give the soonest opening order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) OrderCycle.first_opening_for(distributor).should == oc end @@ -411,12 +411,12 @@ describe OrderCycle do OrderCycle.first_opening_for(distributor).should == nil end end - + describe "finding open order cycles" do it "should give the soonest closing order cycle for a distributor" do distributor = create(:distributor_enterprise) - oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) - oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) + oc = create(:simple_order_cycle, name: 'oc 1', distributors: [distributor], orders_open_at: 1.days.ago, orders_close_at: 11.days.from_now) + oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 12.days.from_now) OrderCycle.first_closing_for(distributor).should == oc end end diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index 6166732b76..4058ba30e1 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -22,7 +22,7 @@ module Spree it "finds line items for products supplied by one of a number of enterprises" do LineItem.supplied_by_any([s1]).should == [li1] LineItem.supplied_by_any([s2]).should == [li2] - LineItem.supplied_by_any([s1, s2]).sort.should == [li1, li2].sort + LineItem.supplied_by_any([s1, s2]).should match_array [li1, li2] end end diff --git a/spec/models/spree/payment_spec.rb b/spec/models/spree/payment_spec.rb index 8a781e7700..0877959cbb 100644 --- a/spec/models/spree/payment_spec.rb +++ b/spec/models/spree/payment_spec.rb @@ -7,7 +7,7 @@ module Spree let(:payment) { create(:payment, source: create(:credit_card)) } it "can capture and void" do - payment.actions.sort.should == %w(capture void).sort + payment.actions.should match_array %w(capture void) end describe "when a payment has been taken" do @@ -17,7 +17,7 @@ module Spree end it "can void and credit" do - payment.actions.sort.should == %w(void credit).sort + payment.actions.should match_array %w(void credit) end end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 275d8b1970..287f60b400 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -545,7 +545,7 @@ module Spree ot3 = create(:option_type, name: 'unit_items', presentation: 'Items') ot4 = create(:option_type, name: 'foo_unit_bar', presentation: 'Foo') - Spree::Product.all_variant_unit_option_types.sort.should == [ot1, ot2, ot3].sort + Spree::Product.all_variant_unit_option_types.should match_array [ot1, ot2, ot3] end end diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index d6b821e890..baea2ac56a 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -15,7 +15,7 @@ module Spree sm.distributors << d1 sm.distributors << d2 - sm.reload.distributors.sort.should == [d1, d2].sort + sm.reload.distributors.should match_array [d1, d2] end it "finds shipping methods for a particular distributor" do diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index 5a82f04ba7..866f17ff1d 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -25,7 +25,7 @@ module Spree end it "returns variants in stock or on demand, but not those that are neither" do - Variant.where(is_master: false).in_stock.sort.should == [@v_in_stock, @v_on_demand].sort + Variant.where(is_master: false).in_stock.should match_array [@v_in_stock, @v_on_demand] end end diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index ee06bd20ec..3a6bd9b1df 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -12,7 +12,7 @@ describe VariantOverride do let!(:vo2) { create(:variant_override, hub: hub2, variant: v) } it "finds variant overrides for a set of hubs" do - VariantOverride.for_hubs([hub1, hub2]).sort.should == [vo1, vo2].sort + VariantOverride.for_hubs([hub1, hub2]).should match_array [vo1, vo2] end end From 7f43dbf9bb23b237de19e6cd9b564cca4b394f37 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 28 May 2015 10:58:12 +1000 Subject: [PATCH 175/206] Fix further intermittent failures in permissions spec --- spec/lib/open_food_network/permissions_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index 4224a00d89..1b6e23eeb0 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -13,7 +13,7 @@ module OpenFoodNetwork before { allow(user).to receive(:admin?) { true } } it "returns all enterprises" do - expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to eq [e1, e2] + expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to match_array [e1, e2] end end @@ -24,7 +24,7 @@ module OpenFoodNetwork it "returns only my managed enterprises any that have granting them P-OC" do expect(permissions).to receive(:managed_enterprises) { Enterprise.where(id: e1) } expect(permissions).to receive(:related_enterprises_granting).with(:some_permission) { Enterprise.where(id: e3) } - expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to eq [e1, e3] + expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to match_array [e1, e3] end end end @@ -34,7 +34,7 @@ module OpenFoodNetwork before { allow(user).to receive(:admin?) { true } } it "returns all enterprises" do - expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to eq [e1, e2] + expect(permissions.send(:managed_and_related_enterprises_granting, :some_permission)).to match_array [e1, e2] end end From 19448a182ecade379bc1efaba59181f7e766f851 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 27 May 2015 11:54:13 +1000 Subject: [PATCH 176/206] Add permalink field to enterprise groups --- app/models/enterprise_group.rb | 1 + .../20150527004427_add_permalink_to_groups.rb | 26 +++++++++++++++++++ db/schema.rb | 6 +++-- spec/factories.rb | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20150527004427_add_permalink_to_groups.rb diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index 9d510d8f2b..aae2af4b17 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -15,6 +15,7 @@ class EnterpriseGroup < ActiveRecord::Base validates :name, presence: true validates :description, presence: true + validates :permalink, uniqueness: true, presence: true attr_accessible :name, :description, :long_description, :on_front_page, :enterprise_ids attr_accessible :owner_id diff --git a/db/migrate/20150527004427_add_permalink_to_groups.rb b/db/migrate/20150527004427_add_permalink_to_groups.rb new file mode 100644 index 0000000000..ab6886cff4 --- /dev/null +++ b/db/migrate/20150527004427_add_permalink_to_groups.rb @@ -0,0 +1,26 @@ +class AddPermalinkToGroups < ActiveRecord::Migration + def up + add_column :enterprise_groups, :permalink, :string + + EnterpriseGroup.reset_column_information + + EnterpriseGroup.all.each do |group| + counter = 1 + permalink = group.name.parameterize + permalink = "my-group-name" if permalink == "" + while EnterpriseGroup.find_by_permalink(permalink) do + permalink = group.name.parameterize + counter.to_s + counter += 1 + end + + group.update_column :permalink, permalink + end + + change_column :enterprise_groups, :permalink, :string, null: false + add_index :enterprise_groups, :permalink, :unique => true + end + + def down + remove_column :enterprise_groups, :permalink + end +end diff --git a/db/schema.rb b/db/schema.rb index f9f32af7d1..6d9de020f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150424151117) do +ActiveRecord::Schema.define(:version => 20150527004427) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -236,10 +236,12 @@ ActiveRecord::Schema.define(:version => 20150424151117) do t.string "linkedin", :default => "", :null => false t.string "twitter", :default => "", :null => false t.integer "owner_id" + t.string "permalink", :null => false end add_index "enterprise_groups", ["address_id"], :name => "index_enterprise_groups_on_address_id" add_index "enterprise_groups", ["owner_id"], :name => "index_enterprise_groups_on_owner_id" + add_index "enterprise_groups", ["permalink"], :name => "index_enterprise_groups_on_permalink", :unique => true create_table "enterprise_groups_enterprises", :id => false, :force => true do |t| t.integer "enterprise_group_id" @@ -619,9 +621,9 @@ ActiveRecord::Schema.define(:version => 20150424151117) do t.string "email" t.text "special_instructions" t.integer "distributor_id" - t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" + t.integer "order_cycle_id" t.integer "cart_id" end diff --git a/spec/factories.rb b/spec/factories.rb index cb4d06b35e..f61172dc2a 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -135,6 +135,7 @@ FactoryGirl.define do factory :enterprise_group, :class => EnterpriseGroup do name 'Enterprise group' + sequence(:permalink) { |n| "group#{n}" } description 'this is a group' on_front_page false address { FactoryGirl.build(:address) } From aef128f2c9282793ad55d7b9fa09a8413d06cdfc Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 27 May 2015 12:20:54 +1000 Subject: [PATCH 177/206] permalink editable --- app/models/enterprise_group.rb | 1 + .../admin/enterprise_groups/_form_primary_details.html.haml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index aae2af4b17..1524a80b53 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -19,6 +19,7 @@ class EnterpriseGroup < ActiveRecord::Base attr_accessible :name, :description, :long_description, :on_front_page, :enterprise_ids attr_accessible :owner_id + attr_accessible :permalink attr_accessible :logo, :promo_image attr_accessible :address_attributes attr_accessible :email, :website, :facebook, :instagram, :linkedin, :twitter diff --git a/app/views/admin/enterprise_groups/_form_primary_details.html.haml b/app/views/admin/enterprise_groups/_form_primary_details.html.haml index 6d326e33fa..d57333626f 100644 --- a/app/views/admin/enterprise_groups/_form_primary_details.html.haml +++ b/app/views/admin/enterprise_groups/_form_primary_details.html.haml @@ -19,3 +19,8 @@ = f.label :enterprise_ids, 'Enterprises' %br/ = f.collection_select :enterprise_ids, @enterprises, :id, :name, {}, {class: "select2 fullwidth", multiple: true} + + = f.field_container :permalink do + = f.label :permalink, "Permalink (unique, no spaces)" + %br/ + = f.text_field :permalink From e4f93863fd5ce50cb089af4d43a8008a8bb24ca2 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 28 May 2015 10:17:09 +1000 Subject: [PATCH 178/206] Finding unique permalink before validation. --- app/models/enterprise.rb | 2 +- app/models/enterprise_group.rb | 25 +++++++++++++ spec/factories.rb | 1 - spec/models/enterprise_group_spec.rb | 55 ++++++++++++++++++++++++++++ spec/models/enterprise_spec.rb | 5 +++ 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index e921167cf0..067e800e3c 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -299,7 +299,7 @@ class Enterprise < ActiveRecord::Base test_permalink = test_permalink.parameterize test_permalink = "my-enterprise" if test_permalink.blank? existing = Enterprise.select(:permalink).order(:permalink).where("permalink LIKE ?", "#{test_permalink}%").map(&:permalink) - if existing.empty? + unless existing.include?(test_permalink) test_permalink else used_indices = existing.map do |p| diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index 1524a80b53..a59b641d7c 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -15,6 +15,8 @@ class EnterpriseGroup < ActiveRecord::Base validates :name, presence: true validates :description, presence: true + + before_validation :sanitize_permalink validates :permalink, uniqueness: true, presence: true attr_accessible :name, :description, :long_description, :on_front_page, :enterprise_ids @@ -73,4 +75,27 @@ class EnterpriseGroup < ActiveRecord::Base address.zipcode.sub!(/^undefined$/, '') end + private + + def self.find_available_value(existing, requested) + return requested unless existing.include?(requested) + used_indices = existing.map do |p| + p.slice!(/^#{requested}/) + p.match(/^\d+$/).to_s.to_i + end + options = (1..used_indices.length + 1).to_a - used_indices + requested + options.first.to_s + end + + def find_available_permalink(requested) + existing = self.class.where(id: !id).where("permalink LIKE ?", "#{requested}%").pluck(:permalink) + self.class.find_available_value(existing, requested) + end + + def sanitize_permalink + if permalink.blank? || permalink_changed? + requested = permalink.presence || permalink_was.presence || name.presence || 'group' + self.permalink = find_available_permalink(requested.parameterize) + end + end end diff --git a/spec/factories.rb b/spec/factories.rb index f61172dc2a..abaecffa47 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -97,7 +97,6 @@ FactoryGirl.define do factory :enterprise, :class => Enterprise do owner { FactoryGirl.create :user } sequence(:name) { |n| "Enterprise #{n}" } - sequence(:permalink) { |n| "enterprise#{n}" } sells 'any' description 'enterprise' long_description '

Hello, world!

This is a paragraph.

' diff --git a/spec/models/enterprise_group_spec.rb b/spec/models/enterprise_group_spec.rb index ceca13b13b..45e7b0692c 100644 --- a/spec/models/enterprise_group_spec.rb +++ b/spec/models/enterprise_group_spec.rb @@ -2,11 +2,32 @@ require 'spec_helper' describe EnterpriseGroup do describe "validations" do + it "pass with name, description and address" do + e = EnterpriseGroup.new + e.name = 'Test Group' + e.description = 'A valid test group.' + e.address = build(:address) + e.should be_valid + end + it "is valid when built from factory" do e = build(:enterprise_group) e.should be_valid end + it "replace empty permalink and pass" do + e = build(:enterprise_group, permalink: '') + e.should be_valid + e.permalink.should == e.name.parameterize + end + + it "restores permalink and pass" do + e = create(:enterprise_group, permalink: 'p') + e.permalink = '' + e.should be_valid + e.permalink.should == 'p' + end + it "requires a name" do e = build(:enterprise_group, name: '') e.should_not be_valid @@ -60,5 +81,39 @@ describe EnterpriseGroup do EnterpriseGroup.managed_by(user).should == [eg1] end + + describe "finding a permalink" do + it "finds available permalink" do + existing = [] + expect(EnterpriseGroup.find_available_value(existing, "permalink")).to eq "permalink" + end + + it "finds available permalink similar to existing" do + existing = ["permalink1"] + expect(EnterpriseGroup.find_available_value(existing, "permalink")).to eq "permalink" + end + + it "adds unique number to existing permalinks" do + existing = ["permalink"] + expect(EnterpriseGroup.find_available_value(existing, "permalink")).to eq "permalink1" + existing = ["permalink", "permalink1"] + expect(EnterpriseGroup.find_available_value(existing, "permalink")).to eq "permalink2" + end + + it "ignores permalinks with characters after the index value" do + existing = ["permalink", "permalink1", "permalink2xxx"] + expect(EnterpriseGroup.find_available_value(existing, "permalink")).to eq "permalink2" + end + + it "finds gaps in the indices of existing permalinks" do + existing = ["permalink", "permalink1", "permalink3"] + expect(EnterpriseGroup.find_available_value(existing, "permalink")).to eq "permalink2" + end + + it "finds available indexed permalink" do + existing = ["permalink", "permalink1"] + expect(EnterpriseGroup.find_available_value(existing, "permalink1")).to eq "permalink11" + end + end end end diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index ed4669c0c2..c81c3818df 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -845,6 +845,11 @@ describe Enterprise do expect(Enterprise.find_available_permalink("permalink")).to eq "permalink2" end + it "finds available permalink similar to existing" do + create(:enterprise, permalink: "permalink2xxx") + expect(Enterprise.find_available_permalink("permalink2")).to eq "permalink2" + end + it "finds gaps in the indices of existing permalinks" do create(:enterprise, permalink: "permalink3") expect(Enterprise.find_available_permalink("permalink")).to eq "permalink2" From ff2eed77601d0813605e703ef77f3a224faabc6d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 28 May 2015 11:27:06 +1000 Subject: [PATCH 179/206] Using permalink in URLs pointing to groups --- app/controllers/admin/enterprise_groups_controller.rb | 10 ++++++++-- app/controllers/groups_controller.rb | 2 +- app/models/enterprise_group.rb | 4 ++++ app/views/groups/index.html.haml | 2 +- app/views/json/_groups.rabl | 2 +- app/views/shopping_shared/_groups.html.haml | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/controllers/admin/enterprise_groups_controller.rb b/app/controllers/admin/enterprise_groups_controller.rb index 3f8888edfb..cb3ac80935 100644 --- a/app/controllers/admin/enterprise_groups_controller.rb +++ b/app/controllers/admin/enterprise_groups_controller.rb @@ -9,7 +9,7 @@ module Admin def move_up EnterpriseGroup.with_isolation_level_serializable do - @enterprise_group = EnterpriseGroup.find params[:enterprise_group_id] + @enterprise_group = EnterpriseGroup.find_by_permalink params[:enterprise_group_id] @enterprise_group.move_higher end redirect_to main_app.admin_enterprise_groups_path @@ -17,7 +17,7 @@ module Admin def move_down EnterpriseGroup.with_isolation_level_serializable do - @enterprise_group = EnterpriseGroup.find params[:enterprise_group_id] + @enterprise_group = EnterpriseGroup.find_by_permalink params[:enterprise_group_id] @enterprise_group.move_lower end redirect_to main_app.admin_enterprise_groups_path @@ -33,6 +33,12 @@ module Admin end alias_method_chain :build_resource, :address + # Overriding method on Spree's resource controller, + # so that resources are found using permalink + def find_resource + EnterpriseGroup.find_by_permalink(params[:id]) + end + private def load_data diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 8653131b5a..fe43f0a0fa 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -7,6 +7,6 @@ class GroupsController < BaseController end def show - @group = EnterpriseGroup.find params[:id] + @group = EnterpriseGroup.find_by_permalink(params[:id]) || EnterpriseGroup.find(params[:id]) end end diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index a59b641d7c..986dc07d22 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -75,6 +75,10 @@ class EnterpriseGroup < ActiveRecord::Base address.zipcode.sub!(/^undefined$/, '') end + def to_param + permalink + end + private def self.find_available_value(existing, requested) diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml index 901cb17ad9..710039c7fb 100644 --- a/app/views/groups/index.html.haml +++ b/app/views/groups/index.html.haml @@ -20,7 +20,7 @@ .row.pad-top{bindonce: true} .small-12.medium-6.columns .groups-header - %a{"bo-href-i" => "/groups/{{group.id}}"} + %a{"bo-href-i" => "/groups/{{group.permalink}}"} %i.ofn-i_035-groups %span.group-name{"bo-text" => "group.name"} .small-3.medium-2.columns diff --git a/app/views/json/_groups.rabl b/app/views/json/_groups.rabl index a079290e23..bc50586a62 100644 --- a/app/views/json/_groups.rabl +++ b/app/views/json/_groups.rabl @@ -1,5 +1,5 @@ collection @groups -attributes :id, :name, :position, :description, :long_description, :email, :website, :facebook, :instagram, :linkedin, :twitter +attributes :id, :permalink, :name, :position, :description, :long_description, :email, :website, :facebook, :instagram, :linkedin, :twitter child enterprises: :enterprises do attributes :id diff --git a/app/views/shopping_shared/_groups.html.haml b/app/views/shopping_shared/_groups.html.haml index 68c68882fb..d94d61359c 100644 --- a/app/views/shopping_shared/_groups.html.haml +++ b/app/views/shopping_shared/_groups.html.haml @@ -9,5 +9,5 @@ %ul.bullet-list - for group in current_distributor.groups %li - %a{href: main_app.groups_path + "/#/#group#{group.id}"} + %a{href: main_app.groups_path + "/#{group.permalink}"} = group.name From 835b56b2220d25dc0f767d52ad7a7c9f8fca722d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 09:44:57 +1000 Subject: [PATCH 180/206] Attempt to fix intermittent failures in spec/features/consumer/authentication_spec.rb --- spec/features/consumer/authentication_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index d0302bcd6f..bfa9f141fe 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -3,6 +3,11 @@ require 'spec_helper' feature "Authentication", js: true do include UIComponentHelper + # Attempt to address intermittent failures in these specs + around do |example| + Capybara.using_wait_time(120) { example.run } + end + describe "login" do let(:user) { create(:user, password: "password", password_confirmation: "password") } From cdbf02ca20f57b5d776bbe65422c620097e39476 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 12:07:43 +1000 Subject: [PATCH 181/206] EnterpriseRelationship.relatives can find activated enterprises only --- app/models/enterprise_relationship.rb | 14 +++++++++----- spec/models/enterprise_relationship_spec.rb | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index 22d04a6cd1..49c26cb0f8 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -28,7 +28,7 @@ class EnterpriseRelationship < ActiveRecord::Base # Load an array of the relatives of each enterprise (ie. any enterprise related to it in # either direction). This array is split into distributors and producers, and has the format: # {enterprise_id => {distributors: [id, ...], producers: [id, ...]} } - def self.relatives + def self.relatives(activated_only=false) relationships = EnterpriseRelationship.includes(:child, :parent) relatives = {} @@ -36,11 +36,15 @@ class EnterpriseRelationship < ActiveRecord::Base relatives[r.parent_id] ||= {distributors: [], producers: []} relatives[r.child_id] ||= {distributors: [], producers: []} - relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer - relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor + if !activated_only || r.child.activated? + relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer + relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor + end - relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer - relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor + if !activated_only || r.parent.activated? + relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer + relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor + end end relatives diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index cb1743dec1..59e36c37e5 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -80,5 +80,15 @@ describe EnterpriseRelationship do {e1.id => {distributors: [e2.id], producers: [e2.id]}, e2.id => {distributors: [], producers: [e1.id]}} end + + it "finds inactive enterprises by default" do + e1.update_attribute :confirmed_at, nil + EnterpriseRelationship.relatives[e2.id][:producers].should == [e1.id] + end + + it "does not find inactive enterprises when requested" do + e1.update_attribute :confirmed_at, nil + EnterpriseRelationship.relatives(true)[e2.id][:producers].should be_empty + end end end From 69c54e1d704ebd15f5b935e0c93be619b24dc456 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 12:08:21 +1000 Subject: [PATCH 182/206] Only load activated relatives for EnterpriseInjectionData --- app/models/enterprise.rb | 4 ++++ .../enterprise_injection_data.rb | 2 +- .../enterprise_injection_data_spec.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 spec/lib/open_food_network/enterprise_injection_data_spec.rb diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index e921167cf0..61d441fb2e 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -178,6 +178,10 @@ class Enterprise < ActiveRecord::Base count(distinct: true) end + def activated? + confirmed_at.present? && sells != 'unspecified' + end + def set_producer_property(property_name, property_value) transaction do property = Spree::Property.where(name: property_name).first_or_create!(presentation: property_name) diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 9862418b98..87516007c6 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -13,7 +13,7 @@ module OpenFoodNetwork end def relatives - @relatives ||= EnterpriseRelationship.relatives + @relatives ||= EnterpriseRelationship.relatives(true) end def supplied_taxons diff --git a/spec/lib/open_food_network/enterprise_injection_data_spec.rb b/spec/lib/open_food_network/enterprise_injection_data_spec.rb new file mode 100644 index 0000000000..cb94f2374a --- /dev/null +++ b/spec/lib/open_food_network/enterprise_injection_data_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +module OpenFoodNetwork + describe EnterpriseInjectionData do + describe "relatives" do + let!(:enterprise) { create(:distributor_enterprise) } + let!(:producer) { create(:supplier_enterprise) } + let!(:producer_inactive) { create(:supplier_enterprise, confirmed_at: nil) } + let!(:er_p) { create(:enterprise_relationship, parent: producer, child: enterprise) } + let!(:er_pi) { create(:enterprise_relationship, parent: producer_inactive, child: enterprise) } + + it "only loads activated relatives" do + subject.relatives[enterprise.id][:producers].should_not include producer_inactive.id + end + end + end +end From 3f4f8afacd8b8ca432861da0fa8f6b92e68953df Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 12:19:38 +1000 Subject: [PATCH 183/206] EnterpriseRelationship.relatives does not show duplicates --- app/models/enterprise_relationship.rb | 4 ++-- spec/models/enterprise_relationship_spec.rb | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index 49c26cb0f8..de06a0578e 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -33,8 +33,8 @@ class EnterpriseRelationship < ActiveRecord::Base relatives = {} relationships.each do |r| - relatives[r.parent_id] ||= {distributors: [], producers: []} - relatives[r.child_id] ||= {distributors: [], producers: []} + relatives[r.parent_id] ||= {distributors: Set.new, producers: Set.new} + relatives[r.child_id] ||= {distributors: Set.new, producers: Set.new} if !activated_only || r.child.activated? relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index 59e36c37e5..96f80a65a4 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -74,21 +74,27 @@ describe EnterpriseRelationship do let(:e1) { create(:supplier_enterprise) } let(:e2) { create(:supplier_enterprise, sells: 'any') } let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) } + let(:er_reverse) { create(:enterprise_relationship, parent: e2, child: e1) } it "categorises enterprises into distributors and producers" do EnterpriseRelationship.relatives.should == - {e1.id => {distributors: [e2.id], producers: [e2.id]}, - e2.id => {distributors: [], producers: [e1.id]}} + {e1.id => {distributors: Set.new([e2.id]), producers: Set.new([e2.id])}, + e2.id => {distributors: Set.new([]), producers: Set.new([e1.id])}} end it "finds inactive enterprises by default" do e1.update_attribute :confirmed_at, nil - EnterpriseRelationship.relatives[e2.id][:producers].should == [e1.id] + EnterpriseRelationship.relatives[e2.id][:producers].should == Set.new([e1.id]) end it "does not find inactive enterprises when requested" do e1.update_attribute :confirmed_at, nil EnterpriseRelationship.relatives(true)[e2.id][:producers].should be_empty end + + it "does not show duplicates" do + er_reverse + EnterpriseRelationship.relatives[e2.id][:producers].should == Set.new([e1.id]) + end end end From d478cc1f69a1ed7d4436ef5e323acaec5de5d39c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 14:03:44 +1000 Subject: [PATCH 184/206] Serialize taxons and relatives in expected format --- app/serializers/api/enterprise_serializer.rb | 16 ++++++++---- .../serializers/enterprise_serializer_spec.rb | 25 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index fc843a5138..44364c4dbc 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -3,7 +3,6 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer Api::IdSerializer def serializable_hash - cached_serializer_hash.merge uncached_serializer_hash end @@ -51,11 +50,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def taxons - options[:data].distributed_taxons[object.id] + ids_to_objs options[:data].distributed_taxons[object.id] end def supplied_taxons - options[:data].supplied_taxons[object.id] + ids_to_objs options[:data].supplied_taxons[object.id] end def pickup @@ -90,12 +89,12 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def producers relatives = options[:data].relatives[object.id] - relatives ? relatives[:producers] : [] + relatives ? ids_to_objs(relatives[:producers]) : [] end def hubs relatives = options[:data].relatives[object.id] - relatives ? relatives[:distributors] : [] + relatives ? ids_to_objs(relatives[:distributors]) : [] end # Map svg icons. @@ -135,4 +134,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer } icon_fonts[object.category] end + + + private + + def ids_to_objs(ids) + ids.andand.map { |id| {id: id} } + end end diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index 7c467d9c3d..f70852d973 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -1,20 +1,31 @@ #require 'spec_helper' describe Api::EnterpriseSerializer do + let(:serializer) { Api::EnterpriseSerializer.new enterprise, data: data } let(:enterprise) { create(:distributor_enterprise) } let(:taxon) { create(:taxon) } - let(:data_class) { Struct.new(:earliest_closing_times, :active_distributors, - :distributed_taxons, :supplied_taxons, - :shipping_method_services, :relatives) } - let(:data) { data_class.new({}, [], {}, {}, {}, {producers: [], distributors: []}) } + let(:data) { OpenStruct.new(earliest_closing_times: {}, + active_distributors: [], + distributed_taxons: {enterprise.id => [123]}, + supplied_taxons: {enterprise.id => [456]}, + shipping_method_services: {}, + relatives: {enterprise.id => {producers: [123], distributors: [456]}}) } it "serializes an enterprise" do - serializer = Api::EnterpriseSerializer.new enterprise, data: data serializer.to_json.should match enterprise.name end - it "will render urls" do - serializer = Api::EnterpriseSerializer.new enterprise, data: data + it "serializes taxons as ids only" do + serializer.serializable_hash[:taxons].should == [{id: 123}] + serializer.serializable_hash[:supplied_taxons].should == [{id: 456}] + end + + it "serializes producers and hubs as ids only" do + serializer.serializable_hash[:producers].should == [{id: 123}] + serializer.serializable_hash[:hubs].should == [{id: 456}] + end + + it "serializes icons" do serializer.to_json.should match "map_005-hub.svg" end end From 678b591c1845f18673476f3d605b6ffda5b84c4a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 29 May 2015 12:43:11 +1000 Subject: [PATCH 185/206] Explain how to disable delayed jobs to send emails again --- config/initializers/delayed_job.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/initializers/delayed_job.rb b/config/initializers/delayed_job.rb index 8fc9aa8ec7..80dc11d3aa 100644 --- a/config/initializers/delayed_job.rb +++ b/config/initializers/delayed_job.rb @@ -2,6 +2,10 @@ Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log')) Delayed::Worker.destroy_failed_jobs = false Delayed::Worker.max_run_time = 15.minutes +# Uncomment the next line if you want jobs to be executed straight away. +# For example you want emails to be opened in your browser while testing. +#Delayed::Worker.delay_jobs = false + # Notify bugsnag when a job fails # Code adapted from http://trevorturk.com/2011/01/25/notify-hoptoad-if-theres-an-exception-in-delayedjob/ class Delayed::Worker From bf935623dcfbd06c462b9aad5b5093b7d428106f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 29 May 2015 13:44:53 +1000 Subject: [PATCH 186/206] changing default mailer url in development from test.com to 0.0.0.0:3000 --- config/environments/development.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index efa229e33c..200484122a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -31,7 +31,7 @@ Openfoodnetwork::Application.configure do # Show emails using Letter Opener config.action_mailer.delivery_method = :letter_opener - config.action_mailer.default_url_options = { host: "test.com" } + config.action_mailer.default_url_options = { host: "0.0.0.0:3000" } end From fe27b1d446358231af4c85e5a1ecb9212878a9ea Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 29 May 2015 13:46:16 +1000 Subject: [PATCH 187/206] text changes to the registration wizard --- .../templates/registration/about.html.haml | 2 +- .../templates/registration/finished.html.haml | 2 +- .../registration/introduction.html.haml | 3 +- .../templates/registration/type.html.haml | 8 ++- .../confirmation_instructions.html.haml | 12 ++-- app/views/enterprise_mailer/welcome.html.haml | 71 +++++-------------- .../user_mailer/signup_confirmation.html.haml | 4 +- 7 files changed, 38 insertions(+), 64 deletions(-) diff --git a/app/assets/javascripts/templates/registration/about.html.haml b/app/assets/javascripts/templates/registration/about.html.haml index be9948b95d..9c57240dfe 100644 --- a/app/assets/javascripts/templates/registration/about.html.haml +++ b/app/assets/javascripts/templates/registration/about.html.haml @@ -14,7 +14,7 @@ .small-12.columns .alert-box.info{ "ofn-inline-alert" => true, ng: { show: "visible" } } %h6 Success! {{ enterprise.name }} added to the Open Food Network - %span If you exit the wizard at any stage, login and go to admin to edit or update your enterprise details. + %span If you exit this wizard at any stage, you need to click the confirmation link in the email you have received. This will take you to your admin interface where you can continue setting up your profile. %a.close{ ng: { click: "close()" } } × .small-12.large-8.columns diff --git a/app/assets/javascripts/templates/registration/finished.html.haml b/app/assets/javascripts/templates/registration/finished.html.haml index f647a2d8bb..46cca9daf5 100644 --- a/app/assets/javascripts/templates/registration/finished.html.haml +++ b/app/assets/javascripts/templates/registration/finished.html.haml @@ -18,7 +18,7 @@ %p We've sent a confirmation email to - %strong {{ enterprise.email }}. + %strong {{ enterprise.email }} if it hasn't been activated before. %br Please follow the instructions there to make your enterprise visible on the Open Food Network. %a.button.primary{ type: "button", href: "/" } Open Food Network home > diff --git a/app/assets/javascripts/templates/registration/introduction.html.haml b/app/assets/javascripts/templates/registration/introduction.html.haml index 60a8547b4a..48553de09a 100644 --- a/app/assets/javascripts/templates/registration/introduction.html.haml +++ b/app/assets/javascripts/templates/registration/introduction.html.haml @@ -5,7 +5,7 @@ %h4 %small %i.ofn-i_040-hub - Create your enterprise profile + You can now create a profile for your Producer or Hub .hide-for-large-up %hr %input.button.small.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } } @@ -38,6 +38,7 @@ %strong contact you on the Open Food Network. %p Use this space to tell the story of your enterprise, to help drive connections to your social and online presence. + %p It's also the first step towards trading on the Open Food Network, or opening an online store. .row.show-for-large-up .small-12.columns diff --git a/app/assets/javascripts/templates/registration/type.html.haml b/app/assets/javascripts/templates/registration/type.html.haml index 48d45cb66a..9593bfa89c 100644 --- a/app/assets/javascripts/templates/registration/type.html.haml +++ b/app/assets/javascripts/templates/registration/type.html.haml @@ -38,9 +38,13 @@ %i.ofn-i_013-help   %p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it. - / %p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc. + .panel.callout + .left + %i.ofn-i_013-help +   + %p If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other. .row.buttons .small-12.columns %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('contact')" } } - %input.button.primary.right{ type: "submit", value: "Continue" } + %input.button.primary.right{ type: "submit", value: "Create Profile" } diff --git a/app/views/enterprise_mailer/confirmation_instructions.html.haml b/app/views/enterprise_mailer/confirmation_instructions.html.haml index e957b70b1c..3fe7ba09e9 100644 --- a/app/views/enterprise_mailer/confirmation_instructions.html.haml +++ b/app/views/enterprise_mailer/confirmation_instructions.html.haml @@ -1,20 +1,22 @@ %h3 = "Hi, #{@resource.contact}!" %p.lead - = "Please confirm your email address for " - %strong - = "#{@resource.name}." + = "A profile for #{@resource.name} has been successfully created!" + To activate your Profile we need to confirm this email address. %p   %p.callout - Click the link below to confirm your email and to activate your enterprise. This link can be used only once: + Please click the link below to confirm your email and to continue setting up your profile. %br %strong = link_to 'Confirm this email address »', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %p   %p - = "We're so excited that you're joining the #{ Spree::Config[:site_name] }! Don't hestitate to get in touch if you have any questions." + After confirming your email you can access your administration account for this enterprise. + See the + = link_to 'User Guide', 'http://global.openfoodnetwork.org/platform/user-guide/' + = "to find out more about #{ Spree::Config[:site_name] }'s features and to start using your profile or online store." = render 'shared/mailers/signoff' diff --git a/app/views/enterprise_mailer/welcome.html.haml b/app/views/enterprise_mailer/welcome.html.haml index 5c69caae77..d24cc25904 100644 --- a/app/views/enterprise_mailer/welcome.html.haml +++ b/app/views/enterprise_mailer/welcome.html.haml @@ -1,67 +1,32 @@ %h3 = "Welcome, #{@enterprise.contact}!" %p.lead - Congratulations, + Thank you for confirming your email address. %strong - %strong= @enterprise.name + = @enterprise.name = "is now part of #{ Spree::Config.site_name }!" -/ Heading Panel + +%p + The User Guide with detailed support for setting up your Producer or Hub is here: + = link_to 'Open Food Network User Guide', 'http://global.openfoodnetwork.org/platform/user-guide/' + +%p + You can manage your account by logging into the + = link_to 'Admin Panel', spree.admin_url + or by clicking on the cog in the top right hand side of the homepage, and selecting Administration. + +%p + We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. + = link_to 'Join the community.', 'http://community.openfoodnetwork.org/' + %p Please find below all the details for viewing and editing your enterprise on %strong= "#{ Spree::Config.site_name }." We suggest keeping this email and information somewhere safe. Logging in with the account details below will allow complete access to your products and services. --#%p   - --# %p.callout --# %strong --# Your enterprise details --# %table{:width => "100%"} --# %tr --# %td{:align => "right"} --# %strong --# Shop URL --# %td   --# %td --# %a{:href => "#{ main_app.enterprise_shop_url(@enterprise) }", :target => "_blank"} --# = main_app.enterprise_shop_url(@enterprise) --# %tr --# %td   --# %tr --# %td{:align => "right"} --# %strong --# Email --# %td   --# %td --# %a{:href => "mailto:#{ @enterprise.email }", :target => "_blank"} --# = @enterprise.email - -%p   %p - Log into - %strong= "#{ Spree::Config.site_name } Admin" - in order to edit your enterprise details such as website and social media links, or to start adding products to your enterprise! - -%p.callout - %strong - OFN Admin -%table{ :width => "100%"} - %tr - %td{:align => "right"} - %strong - Admin - %td   - %td - %a{:href => "#{ spree.admin_url }", :target => "_blank"} - = spree.admin_url - -%p   -/ /Heading Panel -%p - We're so pleased to have you as a valued member of - %strong= "#{Spree::Config.site_name}!" - Don't hestitate to get in touch if you have any questions. + If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out! = render 'shared/mailers/signoff' -= render 'shared/mailers/social_and_contact' \ No newline at end of file += render 'shared/mailers/social_and_contact' diff --git a/app/views/spree/user_mailer/signup_confirmation.html.haml b/app/views/spree/user_mailer/signup_confirmation.html.haml index fb52f57119..c85b377fa4 100644 --- a/app/views/spree/user_mailer/signup_confirmation.html.haml +++ b/app/views/spree/user_mailer/signup_confirmation.html.haml @@ -21,7 +21,9 @@ %hr/ %p   %p.lead - Thanks for joining the network. We look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! + Thanks for joining the network. + If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! + If you are a producer or food enterprise, we are excited to have you as a part of the network. %p We welcome all your questions and feedback; you can use the %em From 50ae331d9459af558319558ea913b5a5562aaa5c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 16:03:16 +1000 Subject: [PATCH 188/206] ng-cloak mobile menu --- app/views/shared/menu/_mobile_menu.html.haml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/shared/menu/_mobile_menu.html.haml b/app/views/shared/menu/_mobile_menu.html.haml index 5ed57917fd..e5ba9af83b 100644 --- a/app/views/shared/menu/_mobile_menu.html.haml +++ b/app/views/shared/menu/_mobile_menu.html.haml @@ -2,7 +2,7 @@ %section.left %a.left-off-canvas-toggle.menu-icon %span - %section.right + %section.right{"ng-cloak" => true} .cart = render partial: "shared/menu/cart" %a{href: main_app.shop_path} @@ -11,34 +11,34 @@ %aside.left-off-canvas-menu.show-for-medium-down %ul.off-canvas-list %li.ofn-logo - %a{href: root_path} + %a{href: root_path} %img{src: "/assets/open-food-network-beta.png", srcset: "/assets/open-food-network-beta.svg", width: "110", height: "26"} - + - if current_page? root_path %li.li-menu %a{"ofn-scroll-to" => "hubs"} - %span.nav-primary + %span.nav-primary %i.ofn-i_040-hub Hubs - else %li.li-menu %a{href: root_path + "#/#hubs"} - %span.nav-primary + %span.nav-primary %i.ofn-i_040-hub Hubs %li.li-menu %a{href: main_app.map_path} - %span.nav-primary + %span.nav-primary %i.ofn-i_037-map Map %li.li-menu %a{href: main_app.producers_path} - %span.nav-primary + %span.nav-primary %i.ofn-i_036-producers Producers %li.li-menu %a{href: main_app.groups_path} - %span.nav-primary + %span.nav-primary %i.ofn-i_035-groups Groups From 5c3a59acabaf2a340e1fc084e221eeac5cba84ad Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 May 2015 16:39:41 +1000 Subject: [PATCH 189/206] ng-cloak order cycles selector, tabs and shopfront --- app/views/enterprises/shop.html.haml | 2 +- app/views/shop/products/_form.html.haml | 2 +- app/views/shopping_shared/_tabs.html.haml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/enterprises/shop.html.haml b/app/views/enterprises/shop.html.haml index 89ce1a30ac..08e25417cb 100644 --- a/app/views/enterprises/shop.html.haml +++ b/app/views/enterprises/shop.html.haml @@ -3,7 +3,7 @@ %shop.darkswarm - content_for :order_cycle_form do - %div{"ng-controller" => "OrderCycleChangeCtrl"} + %div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true} %closing{"ng-if" => "OrderCycle.selected()"} Next order closing %strong {{ OrderCycle.orders_close_at() | date_in_words }} diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index 0df4f022aa..bdaded0f0d 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -1,4 +1,4 @@ -%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null", +%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true, "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1"} // TODO: Needs an ng-show to slide content down diff --git a/app/views/shopping_shared/_tabs.html.haml b/app/views/shopping_shared/_tabs.html.haml index 3641e0cbd5..48c0a1d7b5 100644 --- a/app/views/shopping_shared/_tabs.html.haml +++ b/app/views/shopping_shared/_tabs.html.haml @@ -1,11 +1,11 @@ -#tabs{"ng-controller" => "TabsCtrl"} +#tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true} .row %tabset -# Build all tabs. - - for name, heading_cols in { about: ["About #{current_distributor.name}", 6], - producers: ["Producers",2], + - for name, heading_cols in { about: ["About #{current_distributor.name}", 6], + producers: ["Producers",2], contact: ["Contact",2], - groups: ["Groups",2]} + groups: ["Groups",2]} -# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl. - heading, cols = heading_cols %tab.columns{heading: heading, From 0d3cdb9c694b0346912acf0064b008b5b2cfa17a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 29 May 2015 16:51:55 +1000 Subject: [PATCH 190/206] Expand All button to show all variants in BPE --- .../javascripts/admin/bulk_product_update.js.coffee | 6 ++++++ .../admin/directives/toggle_variants.js.coffee | 4 +--- .../admin/services/display_properties.js.coffee | 8 +++----- .../admin/products/bulk_edit/_products_head.html.haml | 2 ++ .../admin/products/bulk_edit/_products_product.html.haml | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index c902328af4..c557c0c791 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -109,6 +109,12 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit" + $scope.toggleShowAllVariants = -> + showVariants = !DisplayProperties.showVariants 0 + $scope.filteredProducts.forEach (product) -> + DisplayProperties.setShowVariants product.id, showVariants + DisplayProperties.setShowVariants 0, showVariants + $scope.addVariant = (product) -> product.variants.push id: $scope.nextVariantId() diff --git a/app/assets/javascripts/admin/directives/toggle_variants.js.coffee b/app/assets/javascripts/admin/directives/toggle_variants.js.coffee index 410df8d7e9..f5b18ae5cb 100644 --- a/app/assets/javascripts/admin/directives/toggle_variants.js.coffee +++ b/app/assets/javascripts/admin/directives/toggle_variants.js.coffee @@ -1,10 +1,8 @@ angular.module("ofn.admin").directive "ofnToggleVariants", (DisplayProperties) -> link: (scope, element, attrs) -> if DisplayProperties.showVariants scope.product.id - element.removeClass "icon-chevron-right" element.addClass "icon-chevron-down" else - element.removeClass "icon-chevron-down" element.addClass "icon-chevron-right" element.on "click", -> @@ -16,4 +14,4 @@ angular.module("ofn.admin").directive "ofnToggleVariants", (DisplayProperties) - else DisplayProperties.setShowVariants scope.product.id, true element.removeClass "icon-chevron-right" - element.addClass "icon-chevron-down" \ No newline at end of file + element.addClass "icon-chevron-down" diff --git a/app/assets/javascripts/admin/services/display_properties.js.coffee b/app/assets/javascripts/admin/services/display_properties.js.coffee index 7288706032..3037c9f068 100644 --- a/app/assets/javascripts/admin/services/display_properties.js.coffee +++ b/app/assets/javascripts/admin/services/display_properties.js.coffee @@ -3,12 +3,10 @@ angular.module("ofn.admin").factory "DisplayProperties", -> displayProperties: {} showVariants: (product_id) -> - @initProduct product_id - @displayProperties[product_id].showVariants + @productProperties(product_id).showVariants setShowVariants: (product_id, showVariants) -> - @initProduct product_id - @displayProperties[product_id].showVariants = showVariants + @productProperties(product_id).showVariants = showVariants - initProduct: (product_id) -> + productProperties: (product_id) -> @displayProperties[product_id] ||= {showVariants: false} diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 6e22aef8ff..97b4f47c83 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -19,6 +19,8 @@ %thead %tr %th.left-actions + %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' } + Expand All %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer %th.sku{ 'ng-show' => 'columns.sku.visible' } SKU %th.name{ 'ng-show' => 'columns.name.visible' } Name diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index 376e88071a..6ac25ae286 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -1,6 +1,6 @@ %tr.product{ :id => "p_{{product.id}}" } %td.left-actions - %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' } + %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' } %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" } %td.producer{ 'ng-show' => 'columns.producer.visible' } %select.select2.fullwidth{ 'ng-model' => 'product.producer_id', :name => 'producer_id', 'ofn-track-product' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' } From 4b6222bbe2ccb20e0ebdb4852e145cf266b2bb3e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 3 Jun 2015 12:08:47 +1000 Subject: [PATCH 191/206] Updating spec using new button label --- spec/features/consumer/registration_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/consumer/registration_spec.rb b/spec/features/consumer/registration_spec.rb index 25a91ae160..27b748cce5 100644 --- a/spec/features/consumer/registration_spec.rb +++ b/spec/features/consumer/registration_spec.rb @@ -45,7 +45,7 @@ feature "Registration", js: true do # Choosing a type expect(page).to have_content 'Last step to add My Awesome Enterprise!' click_link 'producer-panel' - click_button 'Continue' + click_button 'Create Profile' # Enterprise should be created expect(page).to have_content 'Nice one!' From 8afffdae9ab7d205475ede289213524c5ed14ac1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:13:42 +1000 Subject: [PATCH 192/206] Fix error when product does not have a master variant --- app/assets/javascripts/darkswarm/services/products.js.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index a07ae1f466..4785adae85 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -32,8 +32,9 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro if product.variants product.variants = (Variants.register variant for variant in product.variants) variant.product = product for variant in product.variants - product.master.product = product - product.master = Variants.register product.master if product.master + if product.master + product.master.product = product + product.master = Variants.register product.master registerVariantsWithCart: -> for product in @products From 473322c7e6b14a4c24927bff36440364d9138206 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:25:28 +1000 Subject: [PATCH 193/206] CI: Add more robust merge-to-master script --- script/ci/merge_branch_to_master.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/ci/merge_branch_to_master.sh b/script/ci/merge_branch_to_master.sh index 3eaae17d56..fd9c602802 100755 --- a/script/ci/merge_branch_to_master.sh +++ b/script/ci/merge_branch_to_master.sh @@ -6,5 +6,9 @@ source ./script/ci/includes.sh echo "--- Verifying branch is based on current master" exit_unless_master_merged -echo "--- Pushing branch" -git push origin $BUILDKITE_COMMIT:master +echo "--- Merging and pushing branch" +git checkout master +git merge origin/master +git merge origin/$BUILDKITE_BRANCH +git push origin master +git checkout origin/$BUILDKITE_BRANCH From c6f6c11a43143d3c820be511b8c940937696c49a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:51:15 +1000 Subject: [PATCH 194/206] Add wait between clicks to fix race condition --- spec/features/admin/bulk_product_update_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 4c52ec2256..f5134f4e81 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -205,8 +205,9 @@ feature %q{ expect(page).to have_selector "a.edit-variant", count: 1 # When I remove two, they should be removed - page.all('a.delete-variant').first.click - page.all('a.delete-variant').first.click + page.all('a.delete-variant', visible: true).first.click + expect(page).to have_selector "tr.variant", count: 2 + page.all('a.delete-variant', visible: true).first.click expect(page).to have_selector "tr.variant", count: 1 # When I fill out variant details and hit update From b3878b126b3c73b6baa1cefd35feb06e6813ce7a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 12:53:46 +1000 Subject: [PATCH 195/206] Decouple generic injection spec from EnterpriseSerializer --- spec/helpers/injection_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/injection_helper_spec.rb b/spec/helpers/injection_helper_spec.rb index 96d9279ef5..362ca6479b 100644 --- a/spec/helpers/injection_helper_spec.rb +++ b/spec/helpers/injection_helper_spec.rb @@ -4,7 +4,7 @@ describe InjectionHelper do let!(:enterprise) { create(:distributor_enterprise, facebook: "roger") } it "will inject via AMS" do - helper.inject_json_ams("test", [enterprise], Api::EnterpriseSerializer).should match enterprise.name + helper.inject_json_ams("test", [enterprise], Api::IdSerializer).should match /#{enterprise.id}/ end it "injects enterprises" do From 36dc0d5ccd767446a63c9b4dd07bfb4f41a41c5b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 13:00:07 +1000 Subject: [PATCH 196/206] Do not run performance specs in CI --- script/ci/run_tests.sh | 2 +- spec/performance/injection_helper_spec.rb | 2 +- spec/performance/shop_controller_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/ci/run_tests.sh b/script/ci/run_tests.sh index 5539935a8b..f973c139ad 100755 --- a/script/ci/run_tests.sh +++ b/script/ci/run_tests.sh @@ -16,4 +16,4 @@ echo "--- Loading test database" bundle exec rake db:test:load echo "--- Running tests" -bundle exec rspec spec +bundle exec rspec --tag ~performance spec diff --git a/spec/performance/injection_helper_spec.rb b/spec/performance/injection_helper_spec.rb index 4ba22d738f..ef8d937ce1 100644 --- a/spec/performance/injection_helper_spec.rb +++ b/spec/performance/injection_helper_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe InjectionHelper, type: :helper do +describe InjectionHelper, type: :helper, performance: true do let(:oc) { create(:simple_order_cycle) } let(:relative_supplier) { create(:supplier_enterprise) } let(:relative_distributor) { create(:distributor_enterprise) } diff --git a/spec/performance/shop_controller_spec.rb b/spec/performance/shop_controller_spec.rb index 14e258d18d..984581a2ab 100644 --- a/spec/performance/shop_controller_spec.rb +++ b/spec/performance/shop_controller_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ShopController, type: :controller do +describe ShopController, type: :controller, performance: true do let(:d) { create(:distributor_enterprise) } let(:enterprise_fee) { create(:enterprise_fee) } let(:order_cycle) { create(:simple_order_cycle, distributors: [d], coordinator_fees: [enterprise_fee]) } From db47c01784df955cff00560f3c4b5454467025dd Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 1 May 2015 09:50:12 +1000 Subject: [PATCH 197/206] Initial config for parallel spec running --- .rspec_parallel | 2 ++ Gemfile | 1 + Gemfile.lock | 4 ++++ config/database.yml | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .rspec_parallel diff --git a/.rspec_parallel b/.rspec_parallel new file mode 100644 index 0000000000..54b33804ae --- /dev/null +++ b/.rspec_parallel @@ -0,0 +1,2 @@ +--format progress +--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log diff --git a/Gemfile b/Gemfile index b63961a3ef..3a698cf366 100644 --- a/Gemfile +++ b/Gemfile @@ -112,4 +112,5 @@ group :development do gem 'guard-rails' gem 'guard-zeus' gem 'guard-rspec' + gem 'parallel_tests' end diff --git a/Gemfile.lock b/Gemfile.lock index 1b62201120..09c5f866f3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -362,6 +362,9 @@ GEM activesupport (>= 3.0.0) cocaine (~> 0.5.3) mime-types + parallel (1.4.1) + parallel_tests (1.3.7) + parallel paypal-sdk-core (0.2.10) multi_json (~> 1.0) xml-simple @@ -575,6 +578,7 @@ DEPENDENCIES newrelic_rpm oj paperclip + parallel_tests pg poltergeist pry-debugger diff --git a/config/database.yml b/config/database.yml index d74ed6256a..7eef2396f9 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,7 +10,7 @@ development: test: adapter: postgresql encoding: unicode - database: open_food_network_test + database: open_food_network_test<%= ENV['TEST_ENV_NUMBER'] %> pool: 5 host: localhost username: ofn From 2b3689fd933f99349f5284c61fc51ff1e875fb34 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 3 Jun 2015 14:29:24 +1000 Subject: [PATCH 198/206] Run CI specs in parallel --- script/ci/run_tests.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/script/ci/run_tests.sh b/script/ci/run_tests.sh index 5539935a8b..9f9cdfa30d 100755 --- a/script/ci/run_tests.sh +++ b/script/ci/run_tests.sh @@ -13,7 +13,8 @@ echo "--- Bundling" bundle install echo "--- Loading test database" -bundle exec rake db:test:load +bundle exec rake db:drop db:create db:schema:load +bundle exec rake parallel:drop parallel:create parallel:load_schema echo "--- Running tests" -bundle exec rspec spec +bundle exec rake parallel:spec From 73029636055c609959feeca03d29a621eb1a1124 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 3 Jun 2015 15:11:32 +1000 Subject: [PATCH 199/206] inventory report: filter was broken because filter_to_order_cycle returned nil [skip ci] --- .../products_and_inventory_report.rb | 14 ++------------ .../products_and_inventory_report_spec.rb | 12 +----------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/lib/open_food_network/products_and_inventory_report.rb b/lib/open_food_network/products_and_inventory_report.rb index 163f9a4f50..2b797bd944 100644 --- a/lib/open_food_network/products_and_inventory_report.rb +++ b/lib/open_food_network/products_and_inventory_report.rb @@ -47,7 +47,7 @@ module OpenFoodNetwork end def variants - filter(child_variants) + filter(master_variants) + filter(child_variants) end def child_variants @@ -57,16 +57,6 @@ module OpenFoodNetwork .order("spree_products.name") end - def master_variants - Spree::Variant.where(:is_master => true) - .joins(:product) - .where("(select spree_variants.id from spree_variants as other_spree_variants - WHERE other_spree_variants.product_id = spree_variants.product_id - AND other_spree_variants.is_master = 'f' LIMIT 1) IS NULL") - .merge(visible_products) - .order("spree_products.name") - end - def filter(variants) # NOTE: Ordering matters. # filter_to_order_cycle and filter_to_distributor return Arrays not Arel @@ -107,7 +97,7 @@ module OpenFoodNetwork def filter_to_order_cycle(variants) if params[:order_cycle_id].to_i > 0 order_cycle = OrderCycle.find params[:order_cycle_id] - variants.select! { |v| order_cycle.variants.include? v } + variants.select { |v| order_cycle.variants.include? v } else variants end diff --git a/spec/lib/open_food_network/products_and_inventory_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_report_spec.rb index 368eb4d4d7..13796c10f6 100644 --- a/spec/lib/open_food_network/products_and_inventory_report_spec.rb +++ b/spec/lib/open_food_network/products_and_inventory_report_spec.rb @@ -54,10 +54,8 @@ module OpenFoodNetwork it "fetches variants for some params" do subject.should_receive(:child_variants).and_return ["children"] - subject.should_receive(:master_variants).and_return ["masters"] subject.should_receive(:filter).with(['children']).and_return ["filter_children"] - subject.should_receive(:filter).with(['masters']).and_return ["filter_masters"] - subject.variants.should == ["filter_children", "filter_masters"] + subject.variants.should == ["filter_children"] end end @@ -92,14 +90,6 @@ module OpenFoodNetwork end end - describe "fetching master variants" do - it "doesn't return master variants with siblings" do - product = create(:simple_product, supplier: supplier) - - subject.master_variants.should be_empty - end - end - describe "Filtering variants" do let(:variants) { Spree::Variant.scoped.joins(:product).where(is_master: false) } it "should return unfiltered variants sans-params" do From 09160c8ea67768afeca2c54a186586b29da4796e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 3 Jun 2015 15:23:55 +0800 Subject: [PATCH 200/206] Fixing customers controller spec --- .../controllers/customers_controller_spec.js.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index ced19c8b33..22777a6528 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -12,11 +12,11 @@ describe "CustomersCtrl", -> Customers = _Customers_ ctrl = $controller 'customersCtrl', {$scope: scope, Customers: Customers, shops: shops} - describe "initialise()", -> + describe "setting the shop on scope", -> beforeEach -> spyOn(Customers, "index").andReturn "list of customers" - scope.shop = {id: 1} - scope.initialise() + scope.$apply -> + scope.shop = {id: 1} it "calls Customers#index with the correct params", -> expect(Customers.index).toHaveBeenCalledWith({enterprise_id: 1}) From 7c9b4dbc3e395736f717622df80eb5442afe4e64 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 3 Jun 2015 15:33:04 +0800 Subject: [PATCH 201/206] Resolving unmerged section in bulk order management template --- .../admin/orders/bulk_management.html.haml | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index ded07435f6..71134e38a9 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -132,7 +132,6 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } -<<<<<<< HEAD %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } @@ -157,30 +156,4 @@ %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } %td.actions %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } -======= - %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } - %td.bulk - %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' } - %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} - %td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }} - %td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }} - %td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }} - %td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }} - %td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }} - %td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }} - %td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }} - %td.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.full_name }} - %td.quantity{ 'ng-show' => 'columns.quantity.visible' } - %input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" } - %td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }} - %td.unit_value{ 'ng-show' => 'columns.unit_value.visible' } - %input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-readonly' => "unitValueLessThanZero(line_item)", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "unit_value" } - %td.price{ 'ng-show' => 'columns.price.visible' } - %input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-model' => "line_item.price", 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "price" } - %td.actions - %a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" } - %td.actions - %a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" } ->>>>>>> master %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' } From dbd81e60a1903f4391c1385d657c68650f889912 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 3 Jun 2015 16:18:09 +0800 Subject: [PATCH 202/206] Fixing customer index feature spec, only checking shop when something has been selected --- .../customers/controllers/customers_controller.js.coffee | 5 +++-- spec/features/admin/customers_spec.rb | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index be526a4349..c475f1e4df 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -9,8 +9,9 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, Customers tags: { name: "Tags", visible: true } $scope.$watch "shop", -> - Customers.loaded = false - $scope.customers = Customers.index(enterprise_id: $scope.shop.id) + if $scope.shop? + Customers.loaded = false + $scope.customers = Customers.index(enterprise_id: $scope.shop.id) $scope.loaded = -> Customers.loaded diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index b6c84e10cd..c84f8f35ed 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -24,7 +24,6 @@ feature 'Customers' do expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] select2_select managed_distributor.name, from: "shop_id" - click_button "Go" # Loads the right customers expect(page).to have_selector "tr#c_#{customer1.id}" @@ -48,7 +47,6 @@ feature 'Customers' do it "allows updating of attributes", js: true do select2_select managed_distributor.name, from: "shop_id" - click_button "Go" within "tr#c_#{customer1.id}" do fill_in "code", with: "new-customer-code" From 359328a96e17d07a999169e185eb2c1b9cca5a17 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 13 May 2015 15:37:08 +1000 Subject: [PATCH 203/206] Producers and distributors lists include self where appropriate --- app/models/enterprise.rb | 8 ++++++-- spec/models/enterprise_spec.rb | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index f1e18f7cbe..4adfb05309 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -217,12 +217,16 @@ class Enterprise < ActiveRecord::Base ", self.id, self.id) end + def relatives_including_self + Enterprise.where(id: relatives.pluck(:id) | [id]) + end + def distributors - self.relatives.is_distributor + self.relatives_including_self.is_distributor end def suppliers - self.relatives.is_primary_producer + self.relatives_including_self.is_primary_producer end def website diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index df04324af2..388d80258e 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -121,14 +121,18 @@ describe Enterprise do e.relatives.should match_array [p, c] end + it "finds relatives_including_self" do + expect(e.relatives_including_self).to include e + end + it "scopes relatives to visible distributors" do - e.should_receive(:relatives).and_return(relatives = []) + e.should_receive(:relatives_including_self).and_return(relatives = []) relatives.should_receive(:is_distributor).and_return relatives e.distributors end it "scopes relatives to visible producers" do - e.should_receive(:relatives).and_return(relatives = []) + e.should_receive(:relatives_including_self).and_return(relatives = []) relatives.should_receive(:is_primary_producer).and_return relatives e.suppliers end From 3855ae133722477125ac3acbfd6d7f4ccbecb13f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 14 May 2015 13:04:16 +1000 Subject: [PATCH 204/206] Fixing bug on OC interface that prevented hubs from pulling their own products through --- .../order_cycle_permissions.rb | 8 ++++++-- .../order_cycle_permissions_spec.rb | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/open_food_network/order_cycle_permissions.rb b/lib/open_food_network/order_cycle_permissions.rb index 08fe7b2ee4..f728348630 100644 --- a/lib/open_food_network/order_cycle_permissions.rb +++ b/lib/open_food_network/order_cycle_permissions.rb @@ -128,6 +128,8 @@ module OpenFoodNetwork producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: Enterprise.is_primary_producer) permitted_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id IN (?)', producers) + hub_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id = (?)', hub) + # PLUS any variants that are already in an outgoing exchange of this hub, so things don't break # TODO: Remove this when all P-OC are sorted out active_variants = [] @@ -135,7 +137,7 @@ module OpenFoodNetwork active_variants = exchange.variants end - Spree::Variant.where(id: coordinator_variants | permitted_variants | active_variants) + Spree::Variant.where(id: coordinator_variants | hub_variants | permitted_variants | active_variants) else # Any variants produced by MY PRODUCERS that are in this order cycle, where my producer has granted P-OC to the hub producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: managed_participating_producers) @@ -165,6 +167,8 @@ module OpenFoodNetwork producers = related_enterprises_granting(:add_to_order_cycle, to: [hub], scope: Enterprise.is_primary_producer) permitted_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id IN (?)', producers) + hub_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id = (?)', hub) + # PLUS any variants that are already in an outgoing exchange of this hub, so things don't break # TODO: Remove this when all P-OC are sorted out active_variants = [] @@ -172,7 +176,7 @@ module OpenFoodNetwork active_variants = exchange.variants end - Spree::Variant.where(id: coordinator_variants | permitted_variants | active_variants) + Spree::Variant.where(id: coordinator_variants | hub_variants | permitted_variants | active_variants) else # Any of my managed producers in this order cycle granted P-OC by the hub granted_producers = related_enterprises_granted(:add_to_order_cycle, by: [hub], scope: managed_participating_producers) diff --git a/spec/lib/open_food_network/order_cycle_permissions_spec.rb b/spec/lib/open_food_network/order_cycle_permissions_spec.rb index d135a3500a..275d91d13d 100644 --- a/spec/lib/open_food_network/order_cycle_permissions_spec.rb +++ b/spec/lib/open_food_network/order_cycle_permissions_spec.rb @@ -545,6 +545,16 @@ module OpenFoodNetwork expect(visible).to_not include v2 end + context "where the hub produces products" do + # NOTE: No relationship to self required + let!(:v3) { create(:variant, product: create(:simple_product, supplier: hub)) } + + it "returns any variants produced by the hub" do + visible = permissions.visible_variants_for_outgoing_exchanges_to(hub) + expect(visible).to include v3 + end + end + # TODO: for backwards compatability, remove later context "when an exchange exists between the coordinator and the hub within this order cycle" do let!(:ex) { create(:exchange, order_cycle: oc, sender: coordinator, receiver: hub, incoming: false) } @@ -712,6 +722,16 @@ module OpenFoodNetwork expect(visible).to_not include v2 end + context "where the hub produces products" do + # NOTE: No relationship to self required + let!(:v3) { create(:variant, product: create(:simple_product, supplier: hub)) } + + it "returns any variants produced by the hub" do + visible = permissions.visible_variants_for_outgoing_exchanges_to(hub) + expect(visible).to include v3 + end + end + # TODO: for backwards compatability, remove later context "when an exchange exists between the coordinator and the hub within this order cycle" do let!(:ex) { create(:exchange, order_cycle: oc, sender: coordinator, receiver: hub, incoming: false) } From e93736b12386ac79bda101e756e730febee3ff40 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 3 Jun 2015 18:16:07 +0800 Subject: [PATCH 205/206] Ordering managed orders by id to fix spec --- app/controllers/spree/admin/orders_controller_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index f7d674048e..87c6f3329a 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -47,7 +47,7 @@ Spree::Admin::OrdersController.class_eval do def managed permissions = OpenFoodNetwork::Permissions.new(spree_current_user) - @orders = permissions.editable_orders.ransack(params[:q]).result.page(params[:page]).per(params[:per_page]) + @orders = permissions.editable_orders.order(:id).ransack(params[:q]).result.page(params[:page]).per(params[:per_page]) render json: @orders, each_serializer: Api::Admin::OrderSerializer end end From 552bbf221c0c799c07fe80e0bc5901c625c68dcd Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 5 Jun 2015 10:59:39 +1000 Subject: [PATCH 206/206] Delete obsolete paragraph in registration #595 --- app/views/enterprise_mailer/welcome.html.haml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/views/enterprise_mailer/welcome.html.haml b/app/views/enterprise_mailer/welcome.html.haml index d24cc25904..3cd9d14034 100644 --- a/app/views/enterprise_mailer/welcome.html.haml +++ b/app/views/enterprise_mailer/welcome.html.haml @@ -19,11 +19,6 @@ We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. = link_to 'Join the community.', 'http://community.openfoodnetwork.org/' -%p - Please find below all the details for viewing and editing your enterprise on - %strong= "#{ Spree::Config.site_name }." - We suggest keeping this email and information somewhere safe. Logging in with the account details below will allow complete access to your products and services. - %p If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!