Bug 5260 - Add option to send an order by e-mail to the acquisition module
authorKatrin Fischer <Katrin.Fischer.83@web.de>
Mon, 15 Jun 2015 22:44:44 +0000 (00:44 +0200)
committerKyle M Hall <kyle@bywatersolutions.com>
Fri, 28 Oct 2016 11:52:25 +0000 (11:52 +0000)
With this patch it will be possible to send order information
to the vendor by e-mail. For now this feature can be triggered
manually with a button before closing the basket.
The order e-mail is based on the acquisition claim feature, but
uses a new notice template.

Test plan:

1) Vendors
A new checkbox "Contact when ordering?" was added to the vendor
page.
- Add a vendor and/or edit an existing vendor
- Verify the new option is saved correctly
- Verify the new option displays on the vendor summary page
  after saving

2) Notices
The feature works with a new notice template: ACQORDER
It works with the same formatting/fields etc. as the acq claim
notice.
- Add a new notice template ACQORDER in module
  'Claim/order aquisition'
- Make sure to use fields from the various offered tables
  in your notice
- Verify it is saved correctly

3) Basket
- Turn on LetterLog system preference
- Create multiple order lines
- Click the 'Send order' button in the toolbar
- Verify error or success message
- Verify you received the e-mail
- Verify there is a new entry with about the sent
  notice in your action_logs table

4) Regression testing...
- Verify order claims still work
- Verify serial claims still work
- Verify new serial issue notices still work
...
(I can provide additional test plans if needed)

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@biblibre.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>

C4/Bookseller/Contact.pm
C4/Letters.pm
acqui/basket.pl
acqui/lateorders.pl
acqui/updatesupplier.pl
installer/data/mysql/atomicupdate/bug_5260_acq_email_orders.sql [new file with mode: 0644]
installer/data/mysql/kohastructure.sql
koha-tmpl/intranet-tmpl/prog/en/modules/acqui/basket.tt
koha-tmpl/intranet-tmpl/prog/en/modules/acqui/supplier.tt
koha-tmpl/intranet-tmpl/prog/en/modules/tools/letter.tt
tools/letter.pl

index 8816897..a91ecda 100644 (file)
@@ -63,6 +63,10 @@ Contact's e-mail address.
 
 Notes about contact.
 
+=item orderacquisition
+
+Whether the contact should receive acquisitions orders.
+
 =item claimacquisition
 
 Whether the contact should receive acquisitions claims.
@@ -92,7 +96,7 @@ use C4::Context;
 
 use base qw(Class::Accessor);
 
-__PACKAGE__->mk_accessors(qw(id name position phone altphone fax email notes claimacquisition claimissues acqprimary serialsprimary bookseller));
+__PACKAGE__->mk_accessors(qw(id name position phone altphone fax email notes orderacquisition claimacquisition claimissues acqprimary serialsprimary bookseller));
 
 =head1 METHODS
 
@@ -168,14 +172,15 @@ sub save {
         $self->phone, $self->altphone,
         $self->fax,   $self->email,
         $self->notes, $self->acqprimary ? 1 : 0,
-        $self->serialsprimary ? 1 : 0, $self->claimacquisition ? 1 : 0,
+        $self->serialsprimary ? 1 : 0,
+        $self->orderacquisition ? 1 : 0, $self->claimacquisition ? 1 : 0,
         $self->claimissues ? 1 : 0, $self->bookseller
     );
     if ($self->id) {
-        $query = 'UPDATE aqcontacts SET name = ?, position = ?, phone = ?, altphone = ?, fax = ?, email = ?, notes = ?, acqprimary = ?, serialsprimary = ?, claimacquisition = ?, claimissues = ?, booksellerid = ? WHERE id = ?;';
+        $query = 'UPDATE aqcontacts SET name = ?, position = ?, phone = ?, altphone = ?, fax = ?, email = ?, notes = ?, acqprimary = ?, serialsprimary = ?, orderacquisition = ?, claimacquisition = ?, claimissues = ?, booksellerid = ? WHERE id = ?;';
         push @params, $self->id;
     } else {
-        $query = 'INSERT INTO aqcontacts (name, position, phone, altphone, fax, email, notes, acqprimary, serialsprimary, claimacquisition, claimissues, booksellerid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);';
+        $query = 'INSERT INTO aqcontacts (name, position, phone, altphone, fax, email, notes, acqprimary, serialsprimary, orderacquisition, claimacquisition, claimissues, booksellerid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);';
     }
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare($query);
index 9f17169..6ff7713 100644 (file)
@@ -17,8 +17,7 @@ package C4::Letters;
 # You should have received a copy of the GNU General Public License
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-use strict;
-use warnings;
+use Modern::Perl;
 
 use MIME::Lite;
 use Mail::Sendmail;
@@ -364,12 +363,20 @@ sub findrelatedto {
 
 =head2 SendAlerts
 
-    parameters :
-    - $type : the type of alert
-    - $externalid : the id of the "object" to query
-    - $letter_code : the letter to send.
+    my $err = &SendAlerts($type, $externalid, $letter_code);
 
-    send an alert to all borrowers having put an alert on a given subject.
+Parameters:
+ - $type : the type of alert
+ - $externalid : the id of the "object" to query
+ - $letter_code : the notice template to use
+
+C<&SendAlerts> sends an email notice directly to a patron or a vendor.
+Currently it supports ($type):
+- claim serial issues (claimissues)
+- claim acquisition orders (claimacquisition)
+- send acquisition orders to the vendor (orderacquisition)
+- notify patrons about newly received serial issues (issue)
+- notify patrons when their account is created (members)
 
     Returns undef or { error => 'message } on failure.
     Returns true on success.
@@ -449,20 +456,36 @@ sub SendAlerts {
             }
         }
     }
-    elsif ( $type eq 'claimacquisition' or $type eq 'claimissues' ) {
+    elsif ( $type eq 'claimacquisition' or $type eq 'claimissues' or $type eq 'orderacquisition' ) {
 
         # prepare the letter...
-        # search the biblionumber
-        my $strsth =  $type eq 'claimacquisition'
-            ? qq{
+        my $strsth;
+        my $sthorders;
+        my $dataorders;
+        my $action;
+        if ( $type eq 'claimacquisition') {
+            $strsth = qq{
             SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*
             FROM aqorders
             LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno
             LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber
             LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber
             WHERE aqorders.ordernumber IN (
+            };
+
+            if (!@$externalid){
+                carp "No order selected";
+                return { error => "no_order_selected" };
             }
-            : qq{
+            $strsth .= join( ",", @$externalid ) . ")";
+            $action = "ACQUISITION CLAIM";
+            $sthorders = $dbh->prepare($strsth);
+            $sthorders->execute;
+            $dataorders = $sthorders->fetchall_arrayref( {} );
+        }
+
+        if ($type eq 'claimissues') {
+            $strsth = qq{
             SELECT serial.*,subscription.*, biblio.*, aqbooksellers.*,
             aqbooksellers.id AS booksellerid
             FROM serial
@@ -472,21 +495,46 @@ sub SendAlerts {
             WHERE serial.serialid IN (
             };
 
-        if (!@$externalid){
-            carp "No Order selected";
-            return { error => "no_order_selected" };
+            if (!@$externalid){
+                carp "No Order selected";
+                return { error => "no_order_selected" };
+            }
+
+            $strsth .= join( ",", @$externalid ) . ")";
+            $action = "CLAIM ISSUE";
+            $sthorders = $dbh->prepare($strsth);
+            $sthorders->execute;
+            $dataorders = $sthorders->fetchall_arrayref( {} );
         }
 
-        $strsth .= join( ",", @$externalid ) . ")";
-        my $sthorders = $dbh->prepare($strsth);
-        $sthorders->execute;
-        my $dataorders = $sthorders->fetchall_arrayref( {} );
+        if ( $type eq 'orderacquisition') {
+            $strsth = qq{
+            SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*
+            FROM aqorders
+            LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno
+            LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber
+            LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber
+            WHERE aqbasket.basketno = ?
+            AND orderstatus IN ('new','ordered')
+            };
+
+            if (!$externalid){
+                carp "No basketnumber given";
+                return { error => "no_basketno" };
+            }
+            $action = "ACQUISITION ORDER";
+            $sthorders = $dbh->prepare($strsth);
+            $sthorders->execute($externalid);
+            $dataorders = $sthorders->fetchall_arrayref( {} );
+        }
 
         my $sthbookseller =
           $dbh->prepare("select * from aqbooksellers where id=?");
         $sthbookseller->execute( $dataorders->[0]->{booksellerid} );
         my $databookseller = $sthbookseller->fetchrow_hashref;
-        my $addressee =  $type eq 'claimacquisition' ? 'acqprimary' : 'serialsprimary';
+
+        my $addressee =  $type eq 'claimacquisition' || $type eq 'orderacquisition' ? 'acqprimary' : 'serialsprimary';
+
         my $sthcontact =
           $dbh->prepare("SELECT * FROM aqcontacts WHERE booksellerid=? AND $type=1 ORDER BY $addressee DESC");
         $sthcontact->execute( $dataorders->[0]->{booksellerid} );
@@ -537,12 +585,14 @@ sub SendAlerts {
                                 : 'text/plain; charset="utf-8"',
         );
 
-        $mail{'Reply-to'} = C4::Context->preference('ReplytoDefault')
-          if C4::Context->preference('ReplytoDefault');
-        $mail{'Sender'} = C4::Context->preference('ReturnpathDefault')
-          if C4::Context->preference('ReturnpathDefault');
-        $mail{'Bcc'} = $userenv->{emailaddress}
-          if C4::Context->preference("ClaimsBccCopy");
+        if ($type eq 'claimacquisition' || $type eq 'claimissues' ) {
+            $mail{'Reply-to'} = C4::Context->preference('ReplytoDefault')
+              if C4::Context->preference('ReplytoDefault');
+            $mail{'Sender'} = C4::Context->preference('ReturnpathDefault')
+              if C4::Context->preference('ReturnpathDefault');
+            $mail{'Bcc'} = $userenv->{emailaddress}
+              if C4::Context->preference("ClaimsBccCopy");
+        }
 
         unless ( sendmail(%mail) ) {
             carp $Mail::Sendmail::error;
@@ -551,7 +601,7 @@ sub SendAlerts {
 
         logaction(
             "ACQUISITION",
-            $type eq 'claimissues' ? "CLAIM ISSUE" : "ACQUISITION CLAIM",
+            $action,
             undef,
             "To="
                 . join( ',', @email )
index ecc8f59..b8caba5 100755 (executable)
@@ -20,8 +20,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-use strict;
-use warnings;
+use Modern::Perl;
 use C4::Auth;
 use C4::Koha;
 use C4::Output;
@@ -35,6 +34,7 @@ use C4::Members qw/GetMember/;  #needed for permissions checking for changing ba
 use C4::Items;
 use C4::Suggestions;
 use Koha::Libraries;
+use C4::Letters qw/SendAlerts/;
 use Date::Calc qw/Add_Delta_Days/;
 use Koha::Database;
 use Koha::EDI qw( create_edi_order get_edifact_ean );
@@ -116,6 +116,10 @@ if (!defined $op) {
 my $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
 
+$template->param( email_ok => 1 ) if defined $query->param('email_ok');
+$template->param( email_error => $query->param('email_error') ) if defined $query->param('email_error');
+
+
 if ( $op eq 'delete_confirm' ) {
     my $basketno = $query->param('basketno');
     my $delbiblio = $query->param('delbiblio');
@@ -165,6 +169,25 @@ if ( $op eq 'delete_confirm' ) {
     );
     print GetBasketAsCSV($query->param('basketno'), $query);
     exit;
+} elsif ($op eq 'email') {
+    my $redirect_url = '/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'};
+    my $err;
+
+    eval {
+        $err = SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
+    };
+    if ( $@ ) {
+    $redirect_url .= '&email_error='.$@;
+    } elsif ( ref $err and exists $err->{error} and $err->{error} eq "no_email" ) {
+        $redirect_url .= '&email_error=no_email';
+    } elsif ( ref $err and exists $err->{error} and $err->{error} eq "no_basketno" ) {
+        $redirect_url .= '&email_error=no_basketno';
+    } else {
+        $redirect_url .= '&email_ok=1';
+    }
+
+    print $query->redirect($redirect_url)
+
 } elsif ($op eq 'close') {
     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
     if ($confirm) {
index d372064..fe1acc3 100755 (executable)
@@ -102,10 +102,10 @@ if ( $delay and not $delay =~ /^\d{1,3}$/ ) {
 }
 
 if ($op and $op eq "send_alert"){
-    my @ordernums = $input->multi_param("ordernumber");# FIXME: Fallback values?
+    my @ordernums = $input->multi_param("ordernumber");
     my $err;
     eval {
-        $err = SendAlerts( 'claimacquisition', \@ordernums, $input->param("letter_code") );    # FIXME: Fallback value?
+        $err = SendAlerts( 'claimacquisition', \@ordernums, $input->param("letter_code") );
         if ( not ref $err or not exists $err->{error} ) {
             AddClaim ( $_ ) for @ordernums;
         }
index c064f76..962217c 100755 (executable)
@@ -97,14 +97,14 @@ $data{'active'}=$input->param('status');
 my @contacts;
 my %contact_info;
 
-foreach (qw(id name position phone altphone fax email notes claimacquisition claimissues acqprimary serialsprimary)) {
+foreach (qw(id name position phone altphone fax email notes orderacquisition claimacquisition claimissues acqprimary serialsprimary)) {
     $contact_info{$_} = [ $input->param('contact_' . $_) ];
 }
 
 for my $cnt (0..scalar(@{$contact_info{'id'}})) {
     my %contact;
     my $real_contact;
-    foreach (qw(id name position phone altphone fax email notes claimacquisition claimissues acqprimary serialsprimary)) {
+    foreach (qw(id name position phone altphone fax email notes orderacquisition claimacquisition claimissues acqprimary serialsprimary)) {
         $contact{$_} = $contact_info{$_}->[$cnt];
         $real_contact = 1 if $contact{$_};
     }
diff --git a/installer/data/mysql/atomicupdate/bug_5260_acq_email_orders.sql b/installer/data/mysql/atomicupdate/bug_5260_acq_email_orders.sql
new file mode 100644 (file)
index 0000000..5d6aafe
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE `aqcontacts` ADD `orderacquisition` BOOLEAN NOT NULL DEFAULT 0 AFTER `notes`;
index 6b6fe57..7a14838 100644 (file)
@@ -2914,6 +2914,7 @@ CREATE TABLE aqcontacts (
   fax varchar(100) default NULL,  -- contact's fax number
   email varchar(100) default NULL, -- contact's email address
   notes mediumtext, -- notes related to the contact
+  orderacquisition BOOLEAN NOT NULL DEFAULT 0, -- should this contact receive acquisition orders
   claimacquisition BOOLEAN NOT NULL DEFAULT 0, -- should this contact receive acquisitions claims
   claimissues BOOLEAN NOT NULL DEFAULT 0, -- should this contact receive serial claims
   acqprimary BOOLEAN NOT NULL DEFAULT 0, -- is this the primary contact for acquisitions messages
index d43a959..f94fbf4 100644 (file)
                         [% IF ediaccount %]
                         <div class="btn-group"><a href="/cgi-bin/koha/acqui/edi_ean.pl?op=ediorder&amp;basketno=[% basketno %]&amp;booksellerid=[% booksellerid %]" class="btn btn-small" id="ediorderbutton"><i class="fa fa-download"></i> Create EDIFACT order</a></div>
                         [% END %]
+                        [% IF ( active && books_loop ) %]
+                        <div class="btn-group"><a href="[% script_name %]?op=email&amp;basketno=[% basketno %]" class="btn btn-small" id="emailvendorbutton"><i class="fa fa-envelope"></i> E-mail order</a></div>
+                        [% END %]
                 </div>
 <!-- Modal for confirm deletion box-->
                 <div class="modal hide" id="deleteBasketModal" tabindex="-1" role="dialog" aria-labelledby="delbasketModalLabel" aria-hidden="true">
             <META HTTP-EQUIV=Refresh CONTENT="0; url=booksellers.pl">
             [% END %]
         [% ELSE %]
+
+        [% IF email_error %]
+            <div class="dialog alert">
+            [% IF ( email_error == "no_email" ) %]
+                This vendor has no contact selected for sending orders to or is missing an e-mail address.
+            [% ELSIF ( email_error == "no_basketno" ) %]
+                No basket given.
+            [% ELSE %]
+                ERROR! - [% email_error %]
+            [% END %]
+            </div>
+        [% END %]
+        [% IF ( email_ok ) %]
+            <div class="dialog message">Order e-mail was sent to the vendor.</div>
+        [% END %]
         <h1>[% UNLESS ( basketno ) %]New [% END %]Basket [% basketname|html %] ([% basketno %]) for <a href="supplier.pl?booksellerid=[% booksellerid %]">[% name|html %]</a></h1>
         [% IF ( basketno ) %]
             <div id="acqui_basket_summary" class="yui-g">
                         <th>No.</th>
                         <th class="anti-the">Order</th>
                         <th class="gste">RRP tax exc.</th>
-                        <th class="gste">ecost tax exc.</th>
+                        <th class="gste">Ecost tax exc.</th>
                         <th class="gsti">RRP tax inc.</th>
                         <th class="gsti">ecost tax inc.</th>
                         <th>Qty.</th>
index 02e143a..531cc83 100644 (file)
                 <input type="checkbox" id="contact_serialsprimary[% contact.id %]" class="contact_serialsprimary"></input>
             [% END %]
             <input type="hidden" class="contact_serialsprimary_hidden" name="contact_serialsprimary" value="[% contact.serialsprimary %]"></input>
+        <li><label for="contact_orderacquisition[% contact.id %]">Contact when ordering?</label>
+            [% IF contact.orderacquisition %]
+                <input type="checkbox" id="contact_orderacquisition[% contact.id %]" class="contact_orderacquisition" checked="checked"></input>
+            [% ELSE %]
+                <input type="checkbox" id="contact_orderacquisition[% contact.id %]" class="contact_orderacquisition"></input>
+            [% END %]
+            <input type="hidden" class="contact_orderacquisition_hidden" name="contact_orderacquisition" value="[% contact.orderacquisition %]"></input>
         <li><label for="contact_claimacquisition[% contact.id %]">Contact about late orders?</label>
             [% IF contact.claimacquisition %]
                 <input type="checkbox" id="contact_claimacquisition[% contact.id %]" class="contact_claimacquisition" checked="checked"></input>
@@ -67,6 +74,9 @@
     [% IF ( contact.serialsprimary ) %]
         <p><span class="label">Primary serials contact</span></p>
     [% END %]
+    [% IF ( contact.orderacquisition ) %]
+        <p><span class="label">Receives orders</span></p>
+    [% END %]
     [% IF ( contact.claimacquisition ) %]
         <p><span class="label">Receives claims for late orders</span></p>
     [% END %]
@@ -101,7 +111,7 @@ function add_contact() {
     });
     $(new_contact).insertBefore(this);
     if ($('.supplier-contact').length === 2) { // First contact
-        $.each(['.contact_acqprimary', '.contact_serialsprimary', '.contact_claimacquisition', '.contact_claimissues'], function (idx, checkbox) {
+        $.each(['.contact_acqprimary', '.contact_serialsprimary', '.contact_orderacquisition', '.contact_claimacquisition', '.contact_claimissues'], function (idx, checkbox) {
             $(checkbox, new_contact).click();
         });
     }
@@ -142,6 +152,9 @@ function delete_contact(ev) {
         }
         $(this).next('.contact_serialsprimary_hidden').val($(this).is(':checked') ? '1' : '0');
     });
+    $('body').on('click', '.contact_orderacquisition', null, function () {
+        $(this).next('.contact_orderacquisition_hidden').val($(this).is(':checked') ? '1' : '0');
+    });
     $('body').on('click', '.contact_claimacquisition', null, function () {
         $(this).next('.contact_claimacquisition_hidden').val($(this).is(':checked') ? '1' : '0');
     });
index 18094b4..35ca75d 100644 (file)
@@ -319,6 +319,11 @@ $(document).ready(function() {
                                     [% ELSE %]
                                       <option value="circulation">Circulation</option>
                                     [% END %]
+                                    [% IF ( module == "orderacquisition" ) %]
+                                      <option value="orderacquisition" selected="selected">Order acquisition</option>
+                                    [% ELSE %]
+                                      <option value="orderacquisition">Order acquisition</option>
+                                    [% END %]
                                     [% IF ( module == "claimacquisition" ) %]
                                       <option value="claimacquisition" selected="selected">Claim acquisition</option>
                                     [% ELSE %]
index 441a05f..351e87c 100755 (executable)
@@ -198,8 +198,8 @@ sub add_form {
     elsif ( $module eq 'acquisition' ) {
         push @{$field_selection}, add_fields('aqbooksellers', 'aqorders', 'biblio', 'items');
     }
-    elsif ($module eq 'claimacquisition') {
-        push @{$field_selection}, add_fields('aqbooksellers', 'aqorders', 'biblio', 'biblioitems');
+    elsif ($module eq 'claimacquisition' || $module eq 'orderacquisition') {
+        push @{$field_selection}, add_fields('aqbooksellers', 'aqbasket', 'aqorders', 'biblio', 'biblioitems');
     }
     elsif ($module eq 'claimissues') {
         push @{$field_selection}, add_fields('aqbooksellers', 'serial', 'subscription');