Merge branch 'producer-emails' of https://github.com/folklabs/openfoodnetwork into folklabs-producer-emails

Conflicts:
	Gemfile.lock
	app/controllers/admin/order_cycles_controller.rb
	app/views/admin/order_cycles/edit.html.haml
	app/views/admin/order_cycles/show.rep
	db/schema.rb
	spec/controllers/admin/order_cycles_controller_spec.rb
This commit is contained in:
Rohan Mitchell
2015-04-29 14:42:17 +10:00
17 changed files with 216 additions and 10 deletions

View File

@@ -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'

View File

@@ -573,6 +573,7 @@ DEPENDENCIES
letter_opener
momentjs-rails
newrelic_rpm
nokogiri
oj
paperclip
pg

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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'}

View File

@@ -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

View File

@@ -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}

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,6 @@
class AddReceivalTimeToExchange < ActiveRecord::Migration
def change
add_column :exchanges, :receival_time, :string
add_column :exchanges, :receival_instructions, :string
end
end

View File

@@ -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"

View File

@@ -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

View File

@@ -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) }

View File

@@ -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

View File

@@ -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