Skip to content

Commit 6d77ed5

Browse files
committed
More options for supplier emails
Previous behavior: The order email sent to the supplier will be sent as a copy (CC) to the user* and the supplier will be request to send any replies to that user*. *user: If sent by clicking on the "Send to supplier" button, the user who clicked the button; if sent automatically by end action, the user who created the order. New behavior: In Administration/Configuration, there's a new tab "Suppliers" where admins can set options for communication with suppliers. The default by migration is: The order email sent to the supplier will be sent as a copy (CC) to the associated users* and the supplier will be requested to send any replies to them. *associated users: both a) the user who created the order and b) (unless it was auto-sent) the user who clicked the button "Send to supplier." The copy of the email to the supplier can be changed to blind copy (BCC, default for new instances) or no copy at all. If a reply-to address is set, the supplier will be requested to send any replies to that address instead. If the "send reply copy" option below is checked, there will be multiple reply-to addresses: the specified address and the associated user(s) Old behavior: If not a single article has been ordered, the empty order will be sent to the supplier anyway (unless a minimum order quantity has been set and the respective end action been selected.) New behavior: Not to disrupt any workflows, this behavior remains the same if "Close the order and send it to the supplier" selected, but is pointed out now by the affix "(even if nothing has been ordered.)" There's a new option "Close the order and send it to the supplier unless nothing has been ordered." This checks if at least one article has been ordered (i.e. 1 box filled.) The behavior of "Close the order and send it to the supplier if the minimum quantity has been reached" is changed slightly: It also checks if at least one article has been ordered. This makes it a good general option that fulfills most use cases, so you don't have to memorize whether a minimum order quantity has been set for each supplier. TO DO: (help welcome!) - The "send reply copy" checkbox should only collapse if the email field above is filled. I didn't manage to solve this (yet) - I could only test the "unless nothing ordered" code indirectly, as the end actions didn't work in my local environment. The check worked in another method, but it should be tested if this exact code actually works (both for auto_close_and_send_min_quantity and auto_close_and_send_unless_empty.) - I tried to update the tests (since the min_order_quantity test didn't work anymore) and add more, but they don't work yet -- no email gets sent. I either made a mistake in the tests (didn't really grasp the "let" etc. logic yet) or the emailing function is broken for some reason.
1 parent 8b4c36e commit 6d77ed5

12 files changed

+149
-21
lines changed

.rubocop_todo.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Metrics/BlockNesting:
208208
# Offense count: 18
209209
# Configuration parameters: CountComments, CountAsOne.
210210
Metrics/ClassLength:
211-
Max: 294
211+
Max: 310
212212

213213
# Offense count: 51
214214
# Configuration parameters: AllowedMethods, AllowedPatterns.

app/controllers/admin/configs_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def update
2929

3030
# Set configuration tab names as `@tabs`
3131
def get_tabs
32-
@tabs = %w[foodcoop payment tasks messages layout language security others]
32+
@tabs = %w[foodcoop payment tasks messages suppliers layout language security others]
3333
# allow engines to modify this list
3434
engines = Rails::Engine.subclasses.map(&:instance).select { |e| e.respond_to?(:configuration) }
3535
engines.each { |e| e.configuration(@tabs, self) }

app/lib/foodsoft_config.rb

+6
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ module DistributionStrategy
5959
NO_AUTOMATIC_DISTRIBUTION = 'no_automatic_distribution'
6060
end
6161

62+
module MailOrderResultCopyToUser
63+
NO_COPY = 'no_copy'
64+
CC = 'cc'
65+
BCC = 'bcc'
66+
end
67+
6268
class << self
6369
# Load and initialize foodcoop configuration file.
6470
# @param filename [String] Override configuration file

app/mailers/mailer.rb

+18-3
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,25 @@ def order_result_supplier(user, order, options = {})
7979
@order = order
8080
@supplier = order.supplier
8181

82+
associated_users =
83+
if user == order.created_by
84+
format_user_address(user)
85+
else
86+
[format_user_address(user), format_user_address(order.created_by)].join(', ')
87+
end
88+
reply_to_users = associated_users unless FoodsoftConfig[:order_result_email_reply_to] && !FoodsoftConfig[:order_result_email_reply_copy_to_user]
89+
users_cc = associated_users if FoodsoftConfig[:mail_order_result_copy_to_user] == FoodsoftConfig::MailOrderResultCopyToUser::CC
90+
users_bcc = associated_users if FoodsoftConfig[:mail_order_result_copy_to_user] == FoodsoftConfig::MailOrderResultCopyToUser::BCC
91+
8292
add_order_result_attachments order, options
8393

8494
subject = I18n.t('mailer.order_result_supplier.subject', name: order.supplier.name)
8595
subject += " (#{I18n.t('activerecord.attributes.order.pickup')}: #{format_date(order.pickup)})" if order.pickup
8696

8797
mail to: order.supplier.email,
88-
cc: user,
89-
reply_to: user,
98+
cc: users_cc,
99+
bcc: users_bcc,
100+
reply_to: [FoodsoftConfig[:order_result_email_reply_to], reply_to_users].join(', '),
90101
subject: subject
91102
end
92103

@@ -119,7 +130,7 @@ def mail(args)
119130

120131
%i[bcc cc reply_to sender to].each do |k|
121132
user = args[k]
122-
args[k] = format_address(user.email, show_user(user)) if user.is_a? User
133+
args[k] = format_user_address(user) if user.is_a? User
123134
end
124135

125136
if contact_email = FoodsoftConfig[:contact][:email]
@@ -165,6 +176,10 @@ def additonal_welcome_text(user); end
165176

166177
private
167178

179+
def format_user_address(user)
180+
format_address(user.email, show_user(user))
181+
end
182+
168183
def format_address(email, name)
169184
address = Mail::Address.new email
170185
address.display_name = name

app/models/order.rb

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Order < ApplicationRecord
1414
belongs_to :updated_by, class_name: 'User', foreign_key: 'updated_by_user_id'
1515
belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id'
1616

17-
enum end_action: { no_end_action: 0, auto_close: 1, auto_close_and_send: 2, auto_close_and_send_min_quantity: 3 }
17+
enum end_action: { no_end_action: 0, auto_close: 1, auto_close_and_send: 2, auto_close_and_send_unless_empty: 4, auto_close_and_send_min_quantity: 3 }
1818
enum transport_distribution: { skip: 0, ordergroup: 1, price: 2, articles: 3 }
1919

2020
# Validations
@@ -316,7 +316,10 @@ def do_end_action!
316316
send_to_supplier!(created_by)
317317
elsif auto_close_and_send_min_quantity?
318318
finish!(created_by)
319-
send_to_supplier!(created_by) if sum >= supplier.min_order_quantity.to_r
319+
send_to_supplier!(created_by) if sum >= supplier.min_order_quantity.to_r && !order_articles.ordered.empty?
320+
elsif auto_close_and_send_unless_empty?
321+
finish!(created_by)
322+
send_to_supplier!(created_by) unless order_articles.ordered.empty?
320323
end
321324
end
322325

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
%fieldset
2+
%label
3+
%h4= t '.communication_with_suppliers'
4+
- mail_order_result_copy_to_user_options = FoodsoftConfig::MailOrderResultCopyToUser.constants.map { |c| FoodsoftConfig::MailOrderResultCopyToUser.const_get(c) }
5+
= config_input form, :mail_order_result_copy_to_user, as: :select, collection: mail_order_result_copy_to_user_options,
6+
include_blank: false, input_html: {class: 'input-xxlarge'}, value_method: ->(s){ s }, label_method: ->(s){ t("config.keys.mail_order_result_copy_to_user_options.#{s}") }
7+
= config_input form, :order_result_email_reply_to, as: :string, input_html: {class: 'input-xlarge', placeholder: "#{@cfg[:name]} <#{@cfg[:contact][:email]}>"}
8+
= config_input form, :order_result_email_reply_copy_to_user, as: :boolean

config/app_config.yml.SAMPLE

+13
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ default: &defaults
132132
# email address to be used as sender
133133
email_sender: [email protected]
134134

135+
136+
# Options for communication between suppliers, the foodcoop, and order-associated users:
137+
# Associated users are a) the user who created the order and b) (unless it was auto-sent) the user who clicked the button "Send to supplier."
138+
139+
# Mail order results only to the supplier (no_copy), as copy to associated user(s) (cc), or as blind copy to associated user(s) (bcc).
140+
mail_order_result_copy_to_user: bcc
141+
142+
# Enter an email address if you want to request your suppliers to send any replies to that address instead of the associated users':
143+
# order_result_email_reply_to: Foodcoop <[email protected]>
144+
# If you want replies to be sent to both the specified reply-to address and the associated users':
145+
# order_result_email_reply_copy_to_user: true
146+
147+
135148
# domain to be used for reply emails
136149
#reply_email_domain: reply.foodcoop.test
137150

config/locales/de.yml

+16-2
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ de:
118118
end_action: Endeaktion
119119
end_actions:
120120
auto_close: Bestellung beenden
121-
auto_close_and_send: Bestellung beenden und an Lieferantin schicken
122-
auto_close_and_send_min_quantity: Bestellung beenden und an Lieferantin schicken sofern die Mindestbestellmenge erreicht wurde
121+
auto_close_and_send: Bestellung beenden und an Lieferantin schicken (auch wenn nichts bestellt wurde)
122+
auto_close_and_send_min_quantity: Bestellung beenden und an Lieferantin schicken, sofern die Mindestbestellmenge erreicht wurde (und mind. 1 Artikel bestellt)
123+
auto_close_and_send_unless_empty: Bestellung beenden und an Lieferantin schicken, außer es wurde nichts bestellt
123124
no_end_action: Keine automatische Aktion
124125
ends: Endet am
125126
name: Lieferant
@@ -316,6 +317,8 @@ de:
316317
pdf_title: PDF-Dokumente
317318
tab_messages:
318319
emails_title: E-Mails versenden
320+
tab_suppliers:
321+
communication_with_suppliers: Kommunikation mit Lieferant:innen
319322
tab_payment:
320323
schedule_title: Bestellschema
321324
tab_security:
@@ -603,6 +606,9 @@ de:
603606
email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen.
604607
email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest.
605608
email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden.
609+
mail_order_result_copy_to_user: Wenn eine Bestellung an eine Lieferant:in gesendet wird, wird eine (Blind-)Kopie der E-Mail an die zugehörige Benutzer:innen gesendet. Diese sind a) die Benutzer:in, die die Bestellung eröffnet hat und b) (außer bei automatischer Aussendung) die Benutzer:in, die auf den "An Lieferantin schicken"-Button geklickt hat.
610+
order_result_email_reply_to: Gib eine E-Mail-Adresse ein, falls du die Lieferant:innen bitten möchtest, Antworten ggf. an jene Adresse zu schicken anstatt an die der zugehörigen Benutzer:innen (die die Bestellung erstellt bzw. auf den "An Lieferantin schicken"-Button geklickt haben)
611+
order_result_email_reply_copy_to_user: Wenn aktiviert, werden die Lieferant:innen gebeten Antworten ggf. sowohl an die angegebene Adresse, als auch an die Benutzer:in, die die Bestellung erstellt hat, als auch ggf. an die Benutzer:in, die auf den "An Lieferantin schicken"-Button geklickt hat, zu schicken.
606612
help_url: Link zur Dokumentationsseite
607613
homepage: Webseite der Foodcoop
608614
ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat.
@@ -660,6 +666,13 @@ de:
660666
email_from: Absenderadresse
661667
email_replyto: Antwortadresse
662668
email_sender: Senderadresse
669+
mail_order_result_copy_to_user: E-Mail mit Bestellergebnis ...
670+
mail_order_result_copy_to_user_options:
671+
no_copy: nur an Lieferant:in senden
672+
cc: als Kopie (CC) an zugehörige Benutzer:in(nen) senden
673+
bcc: als Blindkopie (BCC) an zugehörige Benutzer:in(nen) senden
674+
order_result_email_reply_to: Antwortadresse
675+
order_result_email_reply_copy_to_user: Antwort auch an zugehörige Benutzer:in(nen)
663676
help_url: URL Dokumentation
664677
homepage: Webseite
665678
ignore_browser_locale: Browsersprache ignorieren
@@ -701,6 +714,7 @@ de:
701714
layout: Layout
702715
list: Liste
703716
messages: Nachrichten
717+
suppliers: Lieferant:innen
704718
others: Sonstiges
705719
payment: Finanzen
706720
security: Sicherheit

config/locales/en.yml

+16-2
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ en:
118118
end_action: End action
119119
end_actions:
120120
auto_close: Close the order
121-
auto_close_and_send: Close the order and send it to the supplier
122-
auto_close_and_send_min_quantity: Close the order and send it to the supplier if the minimum quantity has been reached
121+
auto_close_and_send: Close the order and send it to the supplier (even if nothing has been ordered)
122+
auto_close_and_send_min_quantity: Close the order and send it to the supplier if the minimum quantity has been reached (and min. 1 article ordered)
123+
auto_close_and_send_unless_empty: Close the order and send it to the supplier unless nothing has been ordered
123124
no_end_action: No automatic action
124125
ends: Ends at
125126
name: Supplier
@@ -316,6 +317,8 @@ en:
316317
pdf_title: PDF documents
317318
tab_messages:
318319
emails_title: Sending email
320+
tab_suppliers:
321+
communication_with_suppliers: Communication with suppliers
319322
tab_payment:
320323
schedule_title: Ordering schedule
321324
tab_security:
@@ -603,6 +606,9 @@ en:
603606
email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address.
604607
email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above.
605608
email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain.
609+
mail_order_result_copy_to_user: When an order is sent to the supplier, a (blind) copy of the email will be sent to the associated users. Those are a) the user who created the order and b) (unless it was auto-sent) the user who clicked the button "Send to supplier."
610+
order_result_email_reply_to: Enter an email address if you want to request your suppliers to send any replies to that address instead of the associated users' (who created the order / clicked the "Send to supplier" button.)
611+
order_result_email_reply_copy_to_user: If enabled, your suppliers will be requested to send any replies both to the specified reply address, as to the user who created the order, as, if given, to the user who clicked the "Send to supplier" button.
606612
help_url: Documentation website.
607613
homepage: Website of your foodcoop.
608614
ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet.
@@ -660,6 +666,13 @@ en:
660666
email_from: From address
661667
email_replyto: Reply-to address
662668
email_sender: Sender address
669+
mail_order_result_copy_to_user: Mail order result ...
670+
mail_order_result_copy_to_user_options:
671+
no_copy: only to the supplier
672+
cc: as copy (CC) to associated user(s)
673+
bcc: as blind copy (BCC) to associated user(s)
674+
order_result_email_reply_to: Reply-to address
675+
order_result_email_reply_copy_to_user: Send reply copy to associated user(s)
663676
help_url: Documentation URL
664677
homepage: Homepage
665678
ignore_browser_locale: Ignore browser language
@@ -701,6 +714,7 @@ en:
701714
layout: Layout
702715
list: List
703716
messages: Messages
717+
suppliers: Suppliers
704718
others: Other
705719
payment: Finances
706720
security: Security
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class AddMailOrderResultCopyToUserSetting < ActiveRecord::Migration[7.0]
2+
def up
3+
FoodsoftConfig[:mail_order_result_copy_to_user] = FoodsoftConfig::MailOrderResultCopyToUser::CC
4+
end
5+
6+
def down
7+
FoodsoftConfig[:mail_order_result_copy_to_user] = nil
8+
FoodsoftConfig[:order_result_email_reply_to] = nil
9+
FoodsoftConfig[:order_result_email_reply_copy_to_user] = nil
10+
end
11+
end

db/schema.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[7.0].define(version: 2024_01_26_111615) do
13+
ActiveRecord::Schema[7.0].define(version: 2024_04_24_015646) do
1414
create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
1515
t.string "name", null: false
1616
t.text "body", size: :long

spec/models/order_spec.rb

+53-9
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,6 @@
7171
end
7272
end
7373

74-
it 'sends mail if min_order_quantity has been reached' do
75-
create(:user, groups: [create(:ordergroup)])
76-
create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago,
77-
end_action: :auto_close_and_send_min_quantity)
78-
79-
Order.finish_ended!
80-
expect(ActionMailer::Base.deliveries.count).to eq 1
81-
end
82-
8374
it 'needs a supplier' do
8475
expect(build(:order, supplier: nil)).to be_invalid
8576
end
@@ -163,6 +154,59 @@
163154
end
164155
end
165156

157+
describe 'with end_action auto_close_and_send' do
158+
let!(:order) { create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close_and_send) }
159+
160+
it 'sends mail even if nothing ordered' do
161+
Order.finish_ended!
162+
order.reload
163+
expect(ActionMailer::Base.deliveries.count).to eq 1
164+
end
165+
end
166+
167+
describe 'with end_action auto_close_and_send_min_quantity' do
168+
let!(:order) { create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close_and_send_min_quantity, article_count: 1) }
169+
let!(:oa) { order.order_articles.first }
170+
let!(:go) { create(:group_order, order: order) }
171+
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa, quantity: 0) }
172+
173+
it 'does not send mail if nothing ordered' do
174+
# TODO: call go.reload, oa.update_results! if that proves to be correct
175+
Order.finish_ended!
176+
order.reload
177+
expect(ActionMailer::Base.deliveries.count).to eq 0
178+
end
179+
180+
it 'sends mail if min_order_quantity has been reached' do # I think there isn't actually a min_order_quantity that is checked?!
181+
goa.update_quantities(1, 0)
182+
go.reload
183+
oa.update_results!
184+
Order.finish_ended!
185+
order.reload
186+
expect(ActionMailer::Base.deliveries.count).to eq 1
187+
end
188+
end
189+
190+
describe 'with end_action auto_close_and_send_unless_empty' do
191+
let!(:order) { create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close_and_send_unless_empty, article_count: 1) }
192+
let!(:oa) { order.order_articles.first }
193+
let!(:go) { create(:group_order, order: order) }
194+
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa, quantity: 0) }
195+
196+
it 'does not send mail if nothing ordered' do
197+
Order.finish_ended!
198+
order.reload
199+
expect(ActionMailer::Base.deliveries.count).to eq 0
200+
end
201+
202+
it 'sends mail if something ordered' do
203+
goa.update_quantities(1, 0)
204+
Order.finish_ended!
205+
order.reload
206+
expect(ActionMailer::Base.deliveries.count).to eq 1
207+
end
208+
end
209+
166210
describe 'balancing charges correct amounts' do
167211
let!(:transport) { rand(0.1..26.0).round(2) }
168212
let!(:order) { create(:order, article_count: 1) }

0 commit comments

Comments
 (0)