Bug 17981: Add a preview mode for notice templates
authorJonathan Druart <jonathan.druart@bugs.koha-community.org>
Tue, 24 Jan 2017 12:31:30 +0000 (13:31 +0100)
committerJonathan Druart <jonathan.druart@bugs.koha-community.org>
Thu, 12 Apr 2018 13:51:50 +0000 (10:51 -0300)
This patch is a first step to provide a preview mode for notice
templates.

CHECKIN, CHECKOUT and HOLD_SLIP are supported so far.
Maybe more, but I have not tested yet and the interface will not allow
you to generate the preview.

The idea is to provide an idea of how will render the messages generated
from a notice template.

A new "Preview" button is added close to each textarea on the editing
notice templates view.
For each notice template code (letter_code), we will need some input
data to produce the preview.

For instance, for CHECKIN we need an barcode. From the barcode we
can guess all the other data.
For CHECKOUT we will need the borrowernumber and the barcode.
Note that the way to enter the data for the preview is not really
user-friendly, for CHECKOUT you will have to fill
'borrowernumber|barcode', but the placeholder will help you to know how
and what to fill.
In the modal window, you will see 4 blocks:
1/ the content of the letter (with the placeholds << >>)
2/ the generated message (with the data filled)
3/ if the letter contained historical syntax markers, the screen will
try to generate a notice template using the TT syntax
4/ the generated message from this TT syntax
=> You will be able to compare the 2 generated messages.

What is the goal of this first patchset:
- Show this first POC and get feedback from other developpers
- Add a way to easily visualise the differences between the 2 syntaxes
- Confort users with the TT syntax and the migration step from the
historical syntax.

I'd like to get opinions before going further.

The possibilities:
- Mock data to get fully working generated messages for any notice
templates. For instance, for CHECKIN and CHECKOUT, the item is not
checked in/out yet. So we cannot access the issue's information.
(I have no idea how to do that)
- Browse the data to get the ones we want to use for the preview (big).

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Josef Moravec <josef.moravec@gmail.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
C4/Letters.pm
koha-tmpl/intranet-tmpl/prog/en/modules/tools/letter.tt
koha-tmpl/intranet-tmpl/prog/en/modules/tools/preview_letter.tt [new file with mode: 0644]
svc/letters/preview [new file with mode: 0644]
tools/letter.pl

index 79eae47..e6cec80 100644 (file)
@@ -884,7 +884,7 @@ sub _parseletter {
     my $values = $values_in ? { %$values_in } : {};
 
     if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){
-        $values->{'dateexpiry'} = format_sqldatetime( $values->{'dateexpiry'} );
+        $values->{'dateexpiry'} = output_pref({ str => $values->{dateexpiry}, dateonly => 1 });
     }
 
     if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
index d9405d9..f2e3ef2 100644 (file)
 
 <div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; [% IF ( add_form or copy_form) %][% IF ( modify ) %]<a href="/cgi-bin/koha/tools/letter.pl">Notices &amp; Slips</a> &rsaquo; Modify notice[% ELSE %] <a href="/cgi-bin/koha/tools/letter.pl">Notices &amp; Slips</a> &rsaquo; Add notice[% END %][% ELSE %][% IF ( add_validate or copy_validate) %] <a href="/cgi-bin/koha/tools/letter.pl">Notices &amp; Slips</a> &rsaquo; Notice added[% ELSE %][% IF ( delete_confirm ) %] <a href="/cgi-bin/koha/tools/letter.pl">Notices &amp; Slips</a> &rsaquo; Confirm deletion[% ELSE %]Notices &amp; Slips[% END %][% END %][% END %]</div>
 
+<div id="preview_template" class="modal in" tabindex="-1" role="dialog" aria-labelledby="preview_template_label" aria-hidden="true">
+    <div class="modal-dialog modal-lg">
+    <div class="modal-content">
+    <div class="modal-header">
+        <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
+        <h3 id="preview_template_label">Preview notice template</h3>
+    </div>
+    <div class="modal-body">
+        <div id="loading"> <img src="[% interface %]/[% theme %]/img/spinner-small.gif" alt="" /> Loading </div>
+    </div>
+    <div class="modal-footer">
+        <!-- TODO <a href="#" class="btn btn-default" id="preview_template_button" role="button" data-toggle="modal">Convert using the Template Toolkit syntax</a>-->
+        <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
+    </div>
+    </div>
+    </div>
+</div>
+
 [% IF add_form or copy_form %]
     <div class="main container-fluid">
         <div class="row">
@@ -24,7 +42,6 @@
                 <div class="yui-b">
 [% END %]
 
-
 [% IF ( no_op_set ) %]
     <h1>Notices and Slips</h1>
     <form method="get" action="/cgi-bin/koha/tools/letter.pl" id="selectlibrary">
               <input type="text" id="name" name="name" size="60" value="[% letter_name %]" required="required" />
               <span class="required">Required</span>
             </li>
+            [% IF code and preview_is_available%]
+                <li>
+                    <label for="name">Data for preview:</label>
+                    [% SWITCH code %]
+                    [% CASE 'CHECKIN' %]
+                        <input type="text" id="data_preview" name="data_preview" value="" placeholder="barcode" />
+                    [% CASE 'CHECKOUT' %]
+                        <input type="text" id="data_preview" name="data_preview" value="" placeholder="barcode|borrowernumber" />
+                    [% CASE 'HOLD_SLIP' %]
+                        <input type="text" id="data_preview" name="data_preview" value="" placeholder="biblionumber|borrowernumber" />
+                    [% CASE %]
+                        Not supported yet.
+                    [% END %]
+                    </li>
+            [% END %]
         </ol>
     </fieldset>
         [% IF Koha.Preference('TranslateNotices') %]
                     </tr>
                   </table>
                 </li>
+                [% IF preview_is_available %]
+                    <li>
+                        <a href="/cgi-bin/koha/svc/letters/preview" class="preview_template btn btn-default btn-xs" title="Preview this notice template" data-mtt="[% letter.message_transport_type %]" data-lang="[% lang %]"><i class="fa fa-eye"></i> Preview</a>
+                    </li>
+                [% END %]
               </ol>
             </fieldset>
             [% END %]
                 $("#submit_form").click();
             });
 
+            $("body").on("click", ".preview_template", function(e){
+                e.preventDefault();
+                var mtt = $(this).data("mtt");
+                var lang = $(this).data("lang");
+
+                var code = $("#code").val();
+                var content = $("#content_"+mtt+"_"+lang).val();
+                var title = $("#title_"+mtt+"_"+lang).val();
+
+                var is_html = $("#is_html_"+mtt+"_"+lang).val();
+                var page = $(this).attr("href");
+                var data_preview = $("#data_preview").val();
+                page += '?code='+encodeURIComponent(code);
+                page += '&title='+encodeURIComponent(title);
+                page += '&content='+encodeURIComponent(content);
+                page += '&data_preview='+encodeURIComponent(data_preview);
+                page += '&is_html='+encodeURIComponent(is_html);
+                $("#preview_template .modal-body").load(page + " div");
+                $('#preview_template').modal('show');
+                $("#preview_template_button").attr("href", "/cgi-bin/koha/svc/letters/convert?module="+module+"&code="+code+"&mtt="+mtt+"&lang="+lang);
+            });
+            $("#preview_template").on("hidden", function(){
+                $("#preview_template_label").html("");
+                $("#preview_template .modal-body").html("<div id=\"loading\"><img src=\"[% interface %]/[% theme %]/img/spinner-small.gif\" alt=\"\" /> "+_("Loading")+"</div>");
+            });
+            $("body").on("click", "#convert_template", function(e){
+                e.preventDefault();
+                var mtt = $(this).data("mtt");
+                var lang = $(this).data("lang");
+
+                var code = $("#code").val();
+                var content = $("#content_"+mtt+"_"+lang).val();
+                var title = $("#title_"+mtt+"_"+lang).val();
+
+                var is_html = $("#is_html_"+mtt+"_"+lang).val();
+                var page = $(this).attr("href");
+                var data_preview = $("#data_preview").val();
+                page += '?code='+encodeURIComponent(code);
+                page += '&title='+encodeURIComponent(title);
+                page += '&content='+encodeURIComponent(content);
+                page += '&data_preview='+encodeURIComponent(data_preview);
+                page += '&is_html='+encodeURIComponent(is_html);
+                $("#preview_template .modal-body").load(page + " div");
+                $('#preview_template').modal('show');
+                $("#preview_template_button").attr("href", "/cgi-bin/koha/svc/letters/convert?module="+module+"&code="+code+"&branchcode="+branchcode+"&mtt="+mtt+"&lang="+lang);
+            });
+            $("#convert_template").on("hidden", function(){
+                $("#convert_template_label").html("");
+                $("#convert_template .modal-body").html("<div id=\"loading\"><img src=\"[% interface %]/[% theme %]/img/spinner-small.gif\" alt=\"\" /> "+_("Loading")+"</div>");
+            });
+
         });
         [% IF add_form or copy_form %]
             function cancel(f) {
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/preview_letter.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/preview_letter.tt
new file mode 100644 (file)
index 0000000..f7aea70
--- /dev/null
@@ -0,0 +1,52 @@
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; Preview notice template</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    </head>
+    <body id="preview_letter" class="catalog">
+        <div id="main">
+            [% FOR m IN messages %]
+                [%# FIXME The message block does not appear at the top of the modal! %]
+                <div class="dialog [% m.type %]">
+                    [% SWITCH m.code %]
+                    [% CASE 'no_data_for_preview' %]You did not specify data for preview.
+                    [% CASE 'preview_not_available' %]Preview is not available for letters '[% m.letter_code %]'.
+                    [% CASE 'not_checked_in_yet' %]Do not forget that the issue has not been checked in yet.
+                    [% CASE 'not_checked_out_yet' %]Do not forget that the issue has not been checked out yet.
+                    [% CASE %][% m.code %]
+                    [% END %]
+                </div>
+            [% END %]
+            [% IF rendered_message %]
+                <fieldset class="brief">
+                    <legend>Original version</legend>
+                    <pre>[% original_content | html %]</pre>
+                </fieldset>
+
+                <fieldset class="brief">
+                    <legend>Rendered message:</legend>
+                    <pre>[% rendered_message.content | html %]</pre>
+                </fieldset>
+            [% END %]
+
+            [% IF tt_content %]
+                <fieldset class="brief">
+                    <legend>Converted version</legend>
+                    <pre>[% tt_content | html %]</pre>
+                </fieldset>
+
+                <fieldset class="brief">
+                    <legend>Rendered message:</legend>
+                    <pre>[% rendered_tt_message.content | html %]</pre>
+                </fieldset>
+            [% END %]
+        </div>
+
+        [% IF tt_content %]
+            [% IF messages_are_similar %]
+                <div class="dialog message">The generated notices are exactly the same!</div>
+            [% ELSE %]
+                <div class="dialog alert">The generated notices are different!</div>
+            [% END %]
+        [% END %]
+    </body>
+</html>
diff --git a/svc/letters/preview b/svc/letters/preview
new file mode 100644 (file)
index 0000000..d517398
--- /dev/null
@@ -0,0 +1,248 @@
+#!/usr/bin/perl
+
+# Copyright 2016 Koha Development Team
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+use CGI qw( -utf8 );
+use C4::Auth;
+use C4::Context;
+use C4::Output;
+use C4::Circulation;
+use C4::Letters;
+use Koha::Checkouts;
+use Koha::Items;
+use Koha::Patrons;
+
+my $input = new CGI;
+
+my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
+    {
+        template_name   => "tools/preview_letter.tt",
+        query           => $input,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { tools => 'edit_notices' },
+        debug           => 1,
+    }
+);
+
+my @messages;
+my $code         = $input->param('code');
+my $content      = $input->param('content');
+my $title        = $input->param('title');
+my $is_html      = $input->param('is_html');
+my $data_preview = $input->param('data_preview');
+
+unless ( $data_preview ) {
+    $template->param( messages => [{ code => 'no_data_for_preview', type => 'error' }]);
+    output_html_with_http_headers $input, $cookie, $template->output;
+    exit;
+}
+
+my $fake_letter = { content => $content, title => $title, is_html => $is_html };
+
+my ( $tt_content, $fake_tt_letter );
+if ( $content =~ m/[^\n]*<<.*>>[^\n]*/so ) {
+    $tt_content = $content;
+
+    my $table_mapping = {
+        biblio                 => 'biblio',
+        borrowers              => 'borrower',
+        branches               => 'branch',
+        items                  => 'item',
+        opac_news              => 'news',
+        aqorders               => 'orders',
+        reserves               => 'hold',
+        serial                 => 'serial',
+        subscription           => 'subscription',
+        suggestions            => 'suggestion',
+        issues                 => 'checkout',
+        old_issues             => 'old_checkout',
+        overdues               => 'overdue',
+        borrower_modifications => 'patron_modification',
+    };
+
+    # Today
+    $tt_content =~ s#<<today>>#[% today| \$KohaDates with_hours => 1 %]#sg;
+
+
+    for my $date_field ( qw(
+            borrowers.dateofbirth
+            borrowers.dateenrolled
+            borrowers.dateexpiry
+            borrowers.debarred
+            items.dateaccessioned
+            items.datelastborrowed
+            items.datelastseen
+            items.onloan
+            serials.planneddate
+            serials.publisheddate
+            serials.claimdate
+            reserves.reservedate
+            reserves.waitingdate
+            reserves.expirationdate
+            suggestions.suggesteddate
+            suggestions.manageddate
+            suggestions.accepteddate
+            suggestions.rejecteddate
+            aqorders.entrydate
+            aqorders.datereceived
+            aqorders.datecancellationprinted
+            aqorders.budgetdate
+            aqorders.claimed_date
+    ) ) {
+        my ( $table, $field ) = split '\.', $date_field;
+        my $new_field =
+          exists $table_mapping->{$table}
+          ? $table_mapping->{$table} . ".$field"
+          : "$table.$field";
+        $tt_content =~ s#<<$table\.$field>>#[% $new_field | \$KohaDates %]#sg;
+        $tt_content =~ s#<<$table\.$field\s*|\s*dateonly>>#[% $new_field | \$KohaDates %]#sg;
+    }
+
+    for my $datetime_field ( qw(
+            items.itemlost_on
+            items.withdrawn_on
+            issues.date_due
+            issues.returndate
+            issues.lastreneweddate
+            issues.issuedate
+            reserves.suspend_until
+    ) ) {
+        my ( $table, $field ) = split '\.', $datetime_field;
+        my $new_field =
+          exists $table_mapping->{$table}
+          ? $table_mapping->{$table} . ".$field"
+          : "$table.$field";
+        $tt_content =~ s#<<$table\.$field>>#[% $new_field | \$KohaDates with_hours => 1 %]#sg;
+        $tt_content =~ s#<<$table\.$field\s*|\s*dateonly>>#[% $new_field | \$KohaDates %]#sg;
+    }
+
+
+
+    while ( my ( $key, $value ) = each %$table_mapping ) {
+        $tt_content =~ s|<<$key\.|<<$value.|sg;
+    }
+
+    $tt_content =~ s|<<|[% |sg;
+    $tt_content =~ s|>>| %]|sg;
+    $fake_tt_letter =
+      { content => $tt_content, title => $title, is_html => $is_html };
+}
+
+my ( $rendered_message, $rendered_tt_message ) = (q||) x 2;
+my $messages_are_similar;
+my $letter_params = {};
+if ( $code eq 'CHECKIN' ) {
+    my $item = Koha::Items->find( { barcode => $data_preview } );
+    my $checkout = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );
+    if ($checkout) {
+        my $patron = Koha::Patrons->find( $checkout->borrowernumber );
+        my $branchcode =
+          C4::Circulation::_GetCircControlBranch( $item->unblessed,
+            $patron->unblessed );
+        $letter_params = {
+            tables => {
+                issues      => $item->itemnumber,
+                items       => $item->itemnumber,
+                biblio      => $item->biblionumber,
+                biblioitems => $item->biblionumber,
+                issues      => $patron->borrowernumber,
+                branches    => $branchcode,
+            }
+        };
+        push @messages, { code => 'not_checked_in_yet', type => 'message' };
+    }
+    else {
+        warn "No checkout";
+    }
+}
+elsif ( $code eq 'CHECKOUT' ) {
+    my ( $barcode, $borrowernumber ) = split '\|', $data_preview;
+    my $item = Koha::Items->find( { barcode => $barcode } );
+    my $patron = Koha::Patrons->find( $borrowernumber );
+    if ($item and $patron) {
+        my $branchcode =
+          C4::Circulation::_GetCircControlBranch( $item->unblessed,
+            $patron->unblessed );
+        $letter_params = {
+            tables => {
+                issues      => $item->itemnumber,
+                items       => $item->itemnumber,
+                biblio      => $item->biblionumber,
+                biblioitems => $item->biblionumber,
+                issues      => $patron->borrowernumber,
+                branches    => $branchcode,
+            }
+        };
+        push @messages, { code => 'not_checked_out_yet', type => 'message' };
+    }
+    else {
+        warn "No item or no patron";
+    }
+}
+elsif ( $code eq 'HOLD_SLIP' ) {
+    my ( $biblionumber, $borrowernumber ) = split '\|', $data_preview;
+    my $hold = Koha::Holds->find( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } );
+    if ($hold) {
+        $letter_params = {
+            tables => {
+                reserves    => $hold->unblessed,
+                branches    => $hold->branchcode,
+                borrowers   => $hold->borrowernumber,
+                biblio      => $hold->biblionumber,
+                biblioitems => $hold->biblionumber,
+                items       => $hold->itemnumber,
+            }
+        };
+    }
+    else {
+        warn "No hold placed by this patron on this bibliographic record.";
+    }
+}
+else {
+    warn "Preview for letter code $code is not available";
+    push @messages, { type => 'alert', code => 'preview_not_available', letter_code => $code, };
+}
+
+if ( %$letter_params ) {
+    # FIXME Be case here GetPreparedLetter modify $fake_letter
+    $rendered_message = C4::Letters::GetPreparedLetter(
+        letter => $fake_letter,
+        %$letter_params,
+    );
+    if ($tt_content) {
+        $rendered_tt_message = C4::Letters::GetPreparedLetter(
+            letter => $fake_tt_letter,
+            %$letter_params,
+        );
+    }
+    $messages_are_similar =
+      $rendered_message->{content} eq $rendered_tt_message->{content};
+}
+
+$template->param(
+    original_content     => $content,
+    rendered_message     => $rendered_message,
+    tt_content           => $tt_content,
+    rendered_tt_message  => $rendered_tt_message,
+    messages_are_similar => $messages_are_similar,
+    messages             => \@messages,
+);
+
+output_html_with_http_headers $input, $cookie, $template->output;
index ef29c73..92615a5 100755 (executable)
@@ -267,10 +267,14 @@ sub add_form {
         }
     }
 
+    my $preview_is_available = grep {/^$code$/} qw(
+        CHECKIN CHECKOUT HOLD_SLIP
+    );
     $template->param(
         module     => $module,
         SQLfieldnames => $field_selection,
         branchcode => $branchcode,
+        preview_is_available => $preview_is_available,
     );
     return;
 }