diff --git a/Gemfile b/Gemfile index b63961a3ef..ac1697e3f9 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,8 @@ gem 'rails', '3.2.21' gem 'rails-i18n', '~> 3.0.0' gem 'i18n', '~> 0.6.11' +gem 'nokogiri' + gem 'pg' gem 'spree', :github => 'openfoodfoundation/spree', :branch => '1-3-stable' gem 'spree_i18n', :github => 'spree/spree_i18n', :branch => '1-3-stable' @@ -49,7 +51,6 @@ 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 'foreigner' gem 'immigrant' diff --git a/Gemfile.lock b/Gemfile.lock index 17dbfdf8c5..76fddfd060 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -573,6 +573,7 @@ DEPENDENCIES letter_opener momentjs-rails newrelic_rpm + nokogiri oj paperclip pg diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index fe1157a7f3..526b9d5976 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -53,7 +53,6 @@ module Admin respond_to do |format| if @order_cycle.update_attributes(params[:order_cycle]) OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, spree_current_user).go! - flash[:notice] = 'Your order cycle has been updated.' format.html { redirect_to admin_order_cycles_path } format.json { render :json => {:success => true} } @@ -79,6 +78,14 @@ module Admin redirect_to main_app.admin_order_cycles_path, :notice => "Your order cycle #{@order_cycle.name} has been cloned." end + # Send notifications to all producers who are part of the order cycle + def notify_producers + @order_cycle = OrderCycle.find params[:id] + Delayed::Job.enqueue OrderCycleNotificationJob.new(@order_cycle) + + redirect_to main_app.admin_order_cycles_path, :notice => 'Emails to be sent to producers have been queued for sending.' + end + protected def collection(show_more=false) diff --git a/app/jobs/order_cycle_notification_job.rb b/app/jobs/order_cycle_notification_job.rb new file mode 100644 index 0000000000..ea8b09f1fe --- /dev/null +++ b/app/jobs/order_cycle_notification_job.rb @@ -0,0 +1,7 @@ + +OrderCycleNotificationJob = Struct.new(:order_cycle) do + def perform + order_cycle.suppliers.each { |supplier| ProducerMailer.order_cycle_report(supplier, order_cycle).deliver } + end +end + diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb new file mode 100644 index 0000000000..8cf4efacb1 --- /dev/null +++ b/app/mailers/producer_mailer.rb @@ -0,0 +1,36 @@ + +class ProducerMailer < Spree::BaseMailer + + def order_cycle_report(producer, order_cycle) + @producer = producer + @coordinator = order_cycle.coordinator + @order_cycle = order_cycle + + subject = "[#{Spree::Config[:site_name]}] Order cycle report" + + # if @order_cycle.distributors.any? + # first_producer = @order_cycle.distributors.first + # @distribution_date = @order_cycle.pickup_time_for first_producer + # subject += " for #{@distribution_date}" if @distribution_date.present? + # end + + @line_items = Spree::LineItem. + joins(:order => :order_cycle, :variant => :product). + where('order_cycles.id = ?', order_cycle). + where('spree_products.supplier_id = ?', producer) + + # Arrange the items in a hash to group quantities + @line_items = @line_items.inject({}) do |lis, li| + lis[li.variant] ||= {line_item: li, quantity: 0} + lis[li.variant][:quantity] += li.quantity + lis + end + + mail(to: @producer.email, + from: from_address, + subject: subject, + reply_to: @coordinator.email, + cc: @coordinator.email) + end + +end diff --git a/app/views/admin/order_cycles/_exchange_form.html.haml b/app/views/admin/order_cycles/_exchange_form.html.haml index 7f0fb983f9..ad9ace7001 100644 --- a/app/views/admin/order_cycles/_exchange_form.html.haml +++ b/app/views/admin/order_cycles/_exchange_form.html.haml @@ -7,6 +7,11 @@ - else {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }} selected +- if type == 'supplier' + %td.receival-details + = text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_time', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_time', 'placeholder' => 'Receive at (ie. Date / Time)', 'ng-model' => 'exchange.receival_time' + %br/ + = text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions' - if type == 'distributor' %td.collection-details = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index a5f7ec1518..fe3746bee6 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -9,6 +9,7 @@ %tr %th Supplier %th Products + %th Receival details %th Fees %th.actions %tbody{'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'} diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 2f17ecf85b..d09b0e0ee0 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -1,7 +1,12 @@ += content_for :page_actions do + %li + = button_to "Notify producers", main_app.notify_producers_admin_order_cycle_path, :id => 'admin_notify_producers' + + %h1 Edit Order Cycle -- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' +- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.order_cycles', 'ng-controller' => ng_controller, 'ng-submit' => 'submit($event)'} do |f| - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml new file mode 100644 index 0000000000..43723aafa3 --- /dev/null +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -0,0 +1,28 @@ +Dear #{@producer.name}, +\ +We now have all the consumer orders for the food drop on #{@distribution_date}. +Please deliver to #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} during the regular delivery time. If this is not convenient then please call #{@coordinator.phone}. + +Note: If you have to arrange a different delivery day and time, it is requested that you do not come on site during drop off/pick up times. + +\ +Orders summary +================ +\ +Here is a summary of the orders for your products: +\ +- @line_items.each_pair do |variant, data| + #{variant.sku} #{raw(variant.product.supplier.name)} #{raw(variant.product.name)} #{raw(variant.options_text)} (QTY: #{data[:quantity]}) @ #{data[:line_item].single_money} = #{data[:line_item].display_amount} + +\ +Detailed orders breakdown +=========================== + + +Please confirm that you have got this email. + +Please send me an invoice for this amount so we can send you payment. + +If you need to phone on the day please call #{@coordinator.phone}. +\ +Thanks and best wishes - #{@coordinator.name} diff --git a/config/application.rb b/config/application.rb index 2d9dcd4982..311aab16cb 100644 --- a/config/application.rb +++ b/config/application.rb @@ -47,7 +47,10 @@ module Openfoodnetwork # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/app/presenters) + config.autoload_paths += %W( + #{config.root}/app/presenters + #{config.root}/app/jobs + ) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. diff --git a/config/routes.rb b/config/routes.rb index 4621ee4a35..d4a16998b6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,6 +44,8 @@ Openfoodnetwork::Application.routes.draw do resources :order_cycles do post :bulk_update, on: :collection, as: :bulk_update get :clone, on: :member + + post 'notify_producers', on: :member end resources :enterprises do diff --git a/db/migrate/20141229094516_add_receival_time_to_exchange.rb b/db/migrate/20141229094516_add_receival_time_to_exchange.rb new file mode 100644 index 0000000000..06933ed051 --- /dev/null +++ b/db/migrate/20141229094516_add_receival_time_to_exchange.rb @@ -0,0 +1,6 @@ +class AddReceivalTimeToExchange < ActiveRecord::Migration + def change + add_column :exchanges, :receival_time, :string + add_column :exchanges, :receival_instructions, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index cb8b503583..d6422ef2ac 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -353,6 +353,8 @@ ActiveRecord::Schema.define(:version => 20150424025907) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.boolean "incoming", :default => false, :null => false + t.string "receival_time" + t.string "receival_instructions" end add_index "exchanges", ["order_cycle_id"], :name => "index_exchanges_on_order_cycle_id" diff --git a/lib/open_food_network/order_cycle_form_applicator.rb b/lib/open_food_network/order_cycle_form_applicator.rb index 69ba284cd8..95afa6713a 100644 --- a/lib/open_food_network/order_cycle_form_applicator.rb +++ b/lib/open_food_network/order_cycle_form_applicator.rb @@ -21,10 +21,14 @@ module OpenFoodNetwork if exchange_exists?(exchange[:enterprise_id], @order_cycle.coordinator_id, true) update_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id, true, - {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids}) + {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids, + receival_time: exchange[:receival_time], + receival_instructions: exchange[:receival_instructions]}) else add_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id, true, - {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids}) + {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids, + receival_time: exchange[:receival_time], + receival_instructions: exchange[:receival_instructions],}) end end @@ -35,12 +39,16 @@ module OpenFoodNetwork if exchange_exists?(@order_cycle.coordinator_id, exchange[:enterprise_id], false) update_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], false, - {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids, - pickup_time: exchange[:pickup_time], pickup_instructions: exchange[:pickup_instructions]}) + {variant_ids: variant_ids, + enterprise_fee_ids: enterprise_fee_ids, + pickup_time: exchange[:pickup_time], + pickup_instructions: exchange[:pickup_instructions]}) else add_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], false, - {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids, - pickup_time: exchange[:pickup_time], pickup_instructions: exchange[:pickup_instructions]}) + {variant_ids: variant_ids, + enterprise_fee_ids: enterprise_fee_ids, + pickup_time: exchange[:pickup_time], + pickup_instructions: exchange[:pickup_instructions]}) end end diff --git a/spec/controllers/admin/order_cycles_controller_spec.rb b/spec/controllers/admin/order_cycles_controller_spec.rb index 9e996f0da7..234ee0fe5c 100644 --- a/spec/controllers/admin/order_cycles_controller_spec.rb +++ b/spec/controllers/admin/order_cycles_controller_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' module Admin describe OrderCyclesController do include AuthenticationWorkflow + let!(:distributor_owner) { create_enterprise_user enterprise_limit: 2 } before do @@ -95,6 +96,32 @@ module Admin end end + + describe "notifying producers" do + let(:user) { create_enterprise_user } + let(:admin_user) do + user = create(:user) + user.spree_roles << Spree::Role.find_or_create_by_name!('admin') + user + end + let(:order_cycle) { create(:simple_order_cycle) } + + before do + controller.stub spree_current_user: admin_user + spree_post :notify_producers, {id: order_cycle.id} + end + + it "enqueues a job" do + expect(Delayed::Job).to receive(:enqueue).once + end + + it "redirects back to the order cycles path with a success message" do + expect(response).to redirect_to admin_order_cycles_path + flash[:notice].should == 'Emails to be sent to producers have been queued for sending.' + end + end + + describe "destroy" do let!(:distributor) { create(:distributor_enterprise, owner: distributor_owner) } diff --git a/spec/jobs/order_cycle_notification_job_spec.rb b/spec/jobs/order_cycle_notification_job_spec.rb new file mode 100644 index 0000000000..3c90295955 --- /dev/null +++ b/spec/jobs/order_cycle_notification_job_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe OrderCycleNotificationJob do + let(:order_cycle) { create(:order_cycle) } + + it 'sends a mail to each supplier' do + mail = double() + allow(mail).to receive(:deliver) + expect(ProducerMailer).to receive(:order_cycle_report).twice.and_return(mail) + job = OrderCycleNotificationJob.new(order_cycle) + job.perform + end +end diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb new file mode 100644 index 0000000000..19a10f4336 --- /dev/null +++ b/spec/mailers/producer_mailer_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe ProducerMailer do + let(:s1) { create(:supplier_enterprise, address: create(:address)) } + let(:s2) { create(:supplier_enterprise, address: create(:address)) } + let(:d1) { create(:distributor_enterprise, address: create(:address)) } + let(:d2) { create(:distributor_enterprise, address: create(:address)) } + let(:p1) { create(:product, price: 12.34, supplier: s1) } + let(:p2) { create(:product, price: 23.45, supplier: s2) } + let(:order_cycle) { create(:simple_order_cycle) } + let!(:order) do + order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'complete') + order.line_items << create(:line_item, variant: p1.master) + order.line_items << create(:line_item, variant: p1.master) + order.line_items << create(:line_item, variant: p2.master) + order.finalize! + order.save + order + end + + before do + ActionMailer::Base.deliveries.clear + end + + after do + ActionMailer::Base.deliveries.clear + end + + it "should send an email when an order cycle is closed" do + ProducerMailer.order_cycle_report(s1, order_cycle).deliver + puts ActionMailer::Base.deliveries + ActionMailer::Base.deliveries.count.should == 1 + end + + it "sets a reply-to of the enterprise email" do + ProducerMailer.order_cycle_report(s1, order_cycle).deliver + ActionMailer::Base.deliveries.last.reply_to.should == [s1.email] + end + + it "cc's the enterprise" do + ProducerMailer.order_cycle_report(s1, order_cycle).deliver + ActionMailer::Base.deliveries.last.cc.should == [s1.email] + end + + it "contains an aggregated list of produce" do + ProducerMailer.order_cycle_report(s1, order_cycle).deliver + email_body = ActionMailer::Base.deliveries.last.body + email_body.to_s.each_line do |line| + if line.include? p1.name + line.include?('QTY: 2').should == true + end + end + end +end