diff --git a/app/assets/javascripts/admin/standing_orders/controllers/address_controller.js.coffee b/app/assets/javascripts/admin/standing_orders/controllers/address_controller.js.coffee new file mode 100644 index 0000000000..08151bbe11 --- /dev/null +++ b/app/assets/javascripts/admin/standing_orders/controllers/address_controller.js.coffee @@ -0,0 +1,25 @@ +angular.module("admin.standingOrders").controller "AddressController", ($scope, $filter, StatusMessage, availableCountries) -> + $scope.submitted = false + $scope.countries = availableCountries + $scope.statesFor = (country_id) -> + return [] unless country_id + $filter('filter')(availableCountries, {id: country_id})[0].states + $scope.billStates = $scope.statesFor($scope.standingOrder.bill_address.country_id) + $scope.shipStates = $scope.statesFor($scope.standingOrder.ship_address.country_id) + + $scope.next = -> + $scope.submitted = true + if $scope.standing_order_address_form.$valid + StatusMessage.clear() + $scope.setView('products') + else + StatusMessage.display 'failure', t('admin.standing_orders.details.invalid_error') + + + $scope.back = -> $scope.setView('details') + + $scope.$watch 'standingOrder.bill_address.country_id', (newValue, oldValue) -> + $scope.billStates = $scope.statesFor(newValue) if newValue? + + $scope.$watch 'standingOrder.ship_address.country_id', (newValue, oldValue) -> + $scope.shipStates = $scope.statesFor(newValue) if newValue? diff --git a/app/assets/javascripts/admin/standing_orders/controllers/details_controller.js.coffee b/app/assets/javascripts/admin/standing_orders/controllers/details_controller.js.coffee index ce9f673e41..d48335b626 100644 --- a/app/assets/javascripts/admin/standing_orders/controllers/details_controller.js.coffee +++ b/app/assets/javascripts/admin/standing_orders/controllers/details_controller.js.coffee @@ -5,6 +5,6 @@ angular.module("admin.standingOrders").controller "DetailsController", ($scope, $scope.submitted = true if $scope.standing_order_details_form.$valid StatusMessage.clear() - $scope.setView('products') + $scope.setView('address') else StatusMessage.display 'failure', t('admin.standing_orders.details.invalid_error') diff --git a/app/assets/javascripts/admin/standing_orders/controllers/products_controller.js.coffee b/app/assets/javascripts/admin/standing_orders/controllers/products_controller.js.coffee index 05287a01c8..eea942f4b7 100644 --- a/app/assets/javascripts/admin/standing_orders/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/admin/standing_orders/controllers/products_controller.js.coffee @@ -6,4 +6,4 @@ angular.module("admin.standingOrders").controller "ProductsController", ($scope, else StatusMessage.display 'failure', 'Please add at least one product' - $scope.back = -> $scope.setView('details') + $scope.back = -> $scope.setView('address') diff --git a/app/controllers/admin/standing_orders_controller.rb b/app/controllers/admin/standing_orders_controller.rb index 7c3b30f9ba..64ca07fd50 100644 --- a/app/controllers/admin/standing_orders_controller.rb +++ b/app/controllers/admin/standing_orders_controller.rb @@ -4,7 +4,7 @@ module Admin class StandingOrdersController < ResourceController before_filter :load_shop, only: [:new] before_filter :load_shops, only: [:index] - before_filter :wrap_sli_attrs, only: [:create] + before_filter :wrap_nested_attrs, only: [:create] respond_to :json respond_override create: { json: { @@ -25,6 +25,8 @@ module Admin def new @standing_order.shop = @shop + @standing_order.bill_address = Spree::Address.new + @standing_order.ship_address = Spree::Address.new @customers = Customer.of(@shop) @schedules = Schedule.with_coordinator(@shop) @payment_methods = Spree::PaymentMethod.for_distributor(@shop) @@ -63,13 +65,19 @@ module Admin end # Wrap :standing_line_items_attributes in :standing_order root - def wrap_sli_attrs + def wrap_nested_attrs if params[:standing_line_items].is_a? Array attributes = params[:standing_line_items].map do |sli| sli.slice(*StandingLineItem.attribute_names) end params[:standing_order][:standing_line_items_attributes] = attributes end + if bill_address_attrs = params[:bill_address] + params[:standing_order][:bill_address_attributes] = bill_address_attrs.slice(*Spree::Address.attribute_names) + end + if ship_address_attrs = params[:ship_address] + params[:standing_order][:ship_address_attributes] = ship_address_attrs.slice(*Spree::Address.attribute_names) + end end # Overriding Spree method to load data from params here so that diff --git a/app/serializers/api/admin/standing_order_serializer.rb b/app/serializers/api/admin/standing_order_serializer.rb index c0a66ce005..0ec722baaf 100644 --- a/app/serializers/api/admin/standing_order_serializer.rb +++ b/app/serializers/api/admin/standing_order_serializer.rb @@ -2,6 +2,8 @@ class Api::Admin::StandingOrderSerializer < ActiveModel::Serializer attributes :id, :shop_id, :customer_id, :schedule_id, :payment_method_id, :shipping_method_id, :begins_at, :ends_at has_many :standing_line_items, serializer: Api::Admin::StandingLineItemSerializer + has_one :bill_address, serializer: Api::AddressSerializer + has_one :ship_address, serializer: Api::AddressSerializer def begins_at object.begins_at.andand.strftime('%F') diff --git a/app/views/admin/standing_orders/_address.html.haml b/app/views/admin/standing_orders/_address.html.haml new file mode 100644 index 0000000000..2b8d121c5b --- /dev/null +++ b/app/views/admin/standing_orders/_address.html.haml @@ -0,0 +1,91 @@ +.row + .seven.columns.alpha + %fieldset.no-border-bottom + %legend{ align: 'center'}= t(:bill_address) + .field + %label{ for: 'bill_address_firstname'}= t(:first_name) + %input.fullwidth#bill_address_firstname{ name: 'bill_address_firstname', type: 'text', required: true, ng: { model: "standingOrder.bill_address.firstname" } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_firstname.$error.required' } }= t(:error_required) + .error{ ng: { repeat: "error in errors['bill_address.firstname']", show: 'standing_order_address_form.bill_address_firstname.$pristine' } } {{ error }} + .field + %label{ for: 'bill_address_lastname'}= t(:last_name) + %input.fullwidth#bill_address_lastname{ name: 'bill_address_lastname', type: 'text', required: true, ng: { model: "standingOrder.bill_address.lastname" } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_lastname.$error.required' } }= t(:error_required) + .error{ ng: { repeat: "error in errors['bill_address.lastname']", show: 'standing_order_address_form.bill_address_lastname.$pristine' } } {{ error }} + .field + %label{ for: 'bill_address_address1'}= t(:address) + %input.fullwidth#bill_address_address1{ name: 'bill_address_address1', type: 'text', required: true, ng: { model: "standingOrder.bill_address.address1" } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_address1.$error.required' } }= t(:error_required) + .error{ ng: { repeat: "error in errors['bill_address.address1']", show: 'standing_order_address_form.bill_address_address1.$pristine' } } {{ error }} + .field + %label{ for: 'bill_address_city'}= t(:suburb) + %input.fullwidth#bill_address_city{ name: 'bill_address_city', type: 'text', required: true, ng: { model: "standingOrder.bill_address.city" } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_city.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.bill_address.city', show: 'standing_order_address_form.bill_address_city.$pristine' } } {{ error }} + .field + %label{ for: "bill_address_zipcode"}= t(:postcode) + %input.fullwidth#bill_address_zipcode{ name: 'bill_address_zipcode', type: 'text', required: true, ng: { model: "standingOrder.bill_address.zipcode" } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_zipcode.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.bill_address.zipcode', show: 'standing_order_address_form.bill_address_zipcode.$pristine' } } {{ error }} + .field + %label{ for: "bill_address_phone"}= t(:phone) + %input.fullwidth#bill_address_phone{ name: 'bill_address_phone', type: 'text', required: true, ng: { model: "standingOrder.bill_address.phone" } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_phone.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.bill_address.phone', show: 'standing_order_address_form.bill_address_phone.$pristine' } } {{ error }} + .field + %label{ for: "bill_address_country_id"}= t(:country) + %input.ofn-select2.fullwidth#bill_address_country_id{ name: 'bill_address_country_id', type: 'number', data: 'countries', required: true, placeholder: t('admin.choose'), ng: { model: 'standingOrder.bill_address.country_id' } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_country_id.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.bill_address.country', show: 'standing_order_address_form.bill_address_country_id.$pristine' } } {{ error }} + .field + %label{ for: "bill_address_state_id"}= t(:state) + %input.ofn-select2.fullwidth#bill_address_state_id{ name: 'bill_address_state_id', type: 'number', data: 'billStates', required: true, placeholder: t('admin.choose'), ng: { model: 'standingOrder.bill_address.state_id' } } + .error{ ng: { show: 'submitted && standing_order_address_form.bill_address_state_id.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.bill_address.state', show: 'standing_order_address_form.bill_address_state_id.$pristine' } } {{ error }} + + + .two.columns +   + .seven.columns.omega + %fieldset.no-border-bottom + %legend{ align: 'center'}= t(:ship_address) + .field + %label{ for: 'ship_address_firstname'}= t(:first_name) + %input.fullwidth#ship_address_firstname{ name: 'ship_address_firstname', type: 'text', required: true, ng: { model: "standingOrder.ship_address.firstname" } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_firstname.$error.required' } }= t(:error_required) + .error{ ng: { repeat: "error in errors['ship_address.firstname']", show: 'standing_order_address_form.ship_address_firstname.$pristine' } } {{ error }} + .field + %label{ for: 'ship_address_lastname'}= t(:last_name) + %input.fullwidth#ship_address_lastname{ name: 'ship_address_lastname', type: 'text', required: true, ng: { model: "standingOrder.ship_address.lastname" } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_lastname.$error.required' } }= t(:error_required) + .error{ ng: { repeat: "error in errors['ship_address.lastname']", show: 'standing_order_address_form.ship_address_lastname.$pristine' } } {{ error }} + .field + %label{ for: 'ship_address_address1'}= t(:address) + %input.fullwidth#ship_address_address1{ name: 'ship_address_address1', type: 'text', required: true, ng: { model: "standingOrder.ship_address.address1" } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_address1.$error.required' } }= t(:error_required) + .error{ ng: { repeat: "error in errors['ship_address.address1']", show: 'standing_order_address_form.ship_address_address1.$pristine' } } {{ error }} + .field + %label{ for: 'ship_address_city'}= t(:suburb) + %input.fullwidth#ship_address_city{ name: 'ship_address_city', type: 'text', required: true, ng: { model: "standingOrder.ship_address.city" } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_city.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.ship_address.city', show: 'standing_order_address_form.ship_address_city.$pristine' } } {{ error }} + .field + %label{ for: "ship_address_zipcode"}= t(:potcode) + %input.fullwidth#ship_address_zipcode{ name: 'ship_address_zipcode', type: 'text', required: true, ng: { model: "standingOrder.ship_address.zipcode" } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_zipcode.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.ship_address.zipcode', show: 'standing_order_address_form.ship_address_zipcode.$pristine' } } {{ error }} + .field + %label{ for: "ship_address_phone"}= t(:phone) + %input.fullwidth#ship_address_phone{ name: 'ship_address_phone', type: 'text', required: true, ng: { model: "standingOrder.ship_address.phone" } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_phone.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.ship_address.phone', show: 'standing_order_address_form.ship_address_phone.$pristine' } } {{ error }} + .field + %label{ for: "ship_address_country_id"}= t(:country) + %input.ofn-select2.fullwidth#ship_address_country_id{ name: 'ship_address_country_id', type: 'number', data: 'countries', required: true, placeholder: t('admin.choose'), ng: { model: 'standingOrder.ship_address.country_id' } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_country_id.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.ship_address.country', show: 'standing_order_address_form.ship_address_country_id.$pristine' } } {{ error }} + .field + %label{ for: "ship_address_state_id"}= t(:state) + %input.ofn-select2.fullwidth#ship_address_state_id{ name: 'ship_address_state_id', type: 'number', data: 'shipStates', required: true, placeholder: t('admin.choose'), ng: { model: 'standingOrder.ship_address.state_id' } } + .error{ ng: { show: 'submitted && standing_order_address_form.ship_address_state_id.$error.required' } }= t(:error_required) + .error{ ng: { repeat: 'error in errors.ship_address.state', show: 'standing_order_address_form.ship_address_state_id.$pristine' } } {{ error }} diff --git a/app/views/admin/standing_orders/_data.html.haml b/app/views/admin/standing_orders/_data.html.haml index ebd0d75cbf..6507e94b65 100644 --- a/app/views/admin/standing_orders/_data.html.haml +++ b/app/views/admin/standing_orders/_data.html.haml @@ -4,3 +4,4 @@ = admin_inject_json_ams_array "admin.standingOrders", "schedules", @schedules, Api::Admin::IdNameSerializer if @schedules = admin_inject_json_ams_array "admin.standingOrders", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer if @payment_methods = admin_inject_json_ams_array "admin.standingOrders", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer if @shipping_methods += admin_inject_available_countries(module: "admin.standingOrders") diff --git a/app/views/admin/standing_orders/_form.html.haml b/app/views/admin/standing_orders/_form.html.haml index 6381388a52..0de558a79b 100644 --- a/app/views/admin/standing_orders/_form.html.haml +++ b/app/views/admin/standing_orders/_form.html.haml @@ -8,6 +8,13 @@ %input.red{ type: "button", value: t(:next), ng: { click: 'next()' } } = render 'details' + .address{ ng: { show: "['address','review'].indexOf(view) >= 0" } } + %ng-form{ name: 'standing_order_address_form', ng: { controller: 'AddressController' } } + %save-bar{ dirty: "standing_order_address_form.$dirty", persist: 'true', ng: { hide: "view == 'review'" } } + %input{ type: "button", value: t(:back), ng: { click: "back()" } } + %input.red{ type: "button", value: t(:next), ng: { click: 'next()' } } + = render 'address' + .products{ ng: { show: "['products','review'].indexOf(view) >= 0" } } %ng-form{ name: 'standing_order_products_form', ng: { controller: 'ProductsController' } } %save-bar{ dirty: "standing_order_products_form.$dirty", persist: 'true', ng: { hide: "view == 'review'" } } diff --git a/app/views/admin/standing_orders/_wizard_progress.html.haml b/app/views/admin/standing_orders/_wizard_progress.html.haml index aa5c5b2e99..d8f381247b 100644 --- a/app/views/admin/standing_orders/_wizard_progress.html.haml +++ b/app/views/admin/standing_orders/_wizard_progress.html.haml @@ -1,3 +1,3 @@ %ul.wizard-progress - %li{ ng: { repeat: "step in ['details','products','review']", class: '{current: view==step}' } } + %li{ ng: { repeat: "step in ['details','address','products','review']", class: '{current: view==step}' } } {{ stepTitleFor(step) }} diff --git a/config/locales/en.yml b/config/locales/en.yml index cace4f8b46..81abddfe4c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -129,6 +129,8 @@ en: say_yes: "Yes" then: then ongoing: Ongoing + bill_address: Billing Address + ship_address: Shipping Address sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" required_fields: Required fields are denoted with an asterisk select_continue: Select and Continue @@ -798,8 +800,9 @@ en: create: Create Standing Order steps: details: 1. Basic Details - products: 2. Add Products - review: 3. Review & Save + address: 2. Address + products: 3. Add Products + review: 4. Review & Save details: details: Details invalid_error: Oops! Please fill in all of the required fields... diff --git a/spec/controllers/admin/standing_orders_controller_spec.rb b/spec/controllers/admin/standing_orders_controller_spec.rb index adfc2fc632..6befaa8d6c 100644 --- a/spec/controllers/admin/standing_orders_controller_spec.rb +++ b/spec/controllers/admin/standing_orders_controller_spec.rb @@ -159,6 +159,7 @@ describe Admin::StandingOrdersController, type: :controller do end context 'when I submit valid and complete params' do + let!(:address) { create(:address) } before do params[:standing_order].merge!({ schedule_id: schedule.id, @@ -168,6 +169,10 @@ describe Admin::StandingOrdersController, type: :controller do begins_at: 2.days.ago, ends_at: 3.months.from_now }) + params.merge!({ + bill_address: address.attributes.except('id'), + ship_address: address.attributes.except('id') + }) end it 'creates a standing order' do @@ -177,6 +182,8 @@ describe Admin::StandingOrdersController, type: :controller do expect(standing_order.customer).to eq customer expect(standing_order.payment_method).to eq payment_method expect(standing_order.shipping_method).to eq shipping_method + expect(standing_order.bill_address.firstname).to eq address.firstname + expect(standing_order.ship_address.firstname).to eq address.firstname end context 'with standing_line_items params' do diff --git a/spec/features/admin/standing_orders_spec.rb b/spec/features/admin/standing_orders_spec.rb index 68bef7f7f1..6b8d48cd90 100644 --- a/spec/features/admin/standing_orders_spec.rb +++ b/spec/features/admin/standing_orders_spec.rb @@ -85,6 +85,23 @@ feature 'Standing Orders' do expect(page).to have_content 'Oops! Please fill in all of the required fields...' fill_in 'begins_at', with: Date.today.strftime('%F') + click_button('Next') + expect(page).to have_content 'BILLING ADDRESS' + click_button('Next') + expect(page).to have_content 'can\'t be blank', count: 16 + + # Setting the shipping and billing addresses + [:bill_address, :ship_address].each do |type| + fill_in "#{type}_firstname", with: 'Freda' + fill_in "#{type}_lastname", with: 'Figapple' + fill_in "#{type}_address1", with: '7 Tempany Lane' + fill_in "#{type}_city", with: 'Natte Yallock' + fill_in "#{type}_zipcode", with: '3465' + fill_in "#{type}_phone", with: '0400 123 456' + select2_select "Australia", from: "#{type}_country_id" + select2_select "Victoria", from: "#{type}_state_id" + end + click_button('Next') expect(page).to have_content 'NAME OR SKU' click_button('Next') @@ -122,6 +139,8 @@ feature 'Standing Orders' do expect(standing_order.schedule).to eq schedule expect(standing_order.payment_method).to eq payment_method expect(standing_order.shipping_method).to eq shipping_method + expect(standing_order.bill_address.firstname).to eq 'Freda' + expect(standing_order.ship_address.firstname).to eq 'Freda' # Standing Line Items are created expect(standing_order.standing_line_items.count).to eq 1