From 58893050b93731a3cfa1ad3df2df58c4d81953d4 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Tue, 11 Apr 2023 23:01:53 +0200
Subject: [PATCH] Bug 33105: Add vendor issues
Content-Type: text/plain; charset=utf-8

This patch is the main patch of this patch set, it contains the
controller acqui/vendor_issues.pl, its corresponding template, and some
links to this script.

It adds:
* A new DB table aqbookseller_issues linked with the aqbooksellers table
* A new subpermission acquisition.issue_manage
* A new authorised value category VENDOR_ISSUE_TYPE and two examples
MAINTENANCE and OUTAGE
* A new controller couple acqui/vendor_issues.[pl,tt]

Test plan:
0. Apply the patches, run updatedatabase and restart_all
1. Go to the acquisition module, create a new vendor or use an existing
one
2. Create a couple of issues for this vendor
3. Edit/Delete and search for those issues

This is the basics for tracking issues with vendors.
Suggestions welcome, on follow-up bug reports.

Signed-off-by: Jonathan Field <jonathan.fieeld@ptfs-europe.com>
---
 acqui/vendor_issues.pl                        | 113 +++++++
 .../prog/en/includes/permissions.inc          |   5 +
 .../prog/en/includes/vendor-menu.inc          |   1 +
 .../prog/en/modules/acqui/vendor_issues.tt    | 282 ++++++++++++++++++
 .../en/modules/admin/authorised_values.tt     |   2 +
 5 files changed, 403 insertions(+)
 create mode 100755 acqui/vendor_issues.pl
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/acqui/vendor_issues.tt

diff --git a/acqui/vendor_issues.pl b/acqui/vendor_issues.pl
new file mode 100755
index 0000000000..561769c28e
--- /dev/null
+++ b/acqui/vendor_issues.pl
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+
+# Copyright 2023 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 Try::Tiny;
+use C4::Context;
+use C4::Auth qw( get_template_and_user );
+use C4::Output qw( output_html_with_http_headers );
+
+use Koha::Acquisition::Booksellers;
+
+my $input        = CGI->new;
+my $booksellerid = $input->param('booksellerid');
+my $issue_id     = $input->param('issue_id');
+my $op           = $input->param('op') || 'list';
+my @messages;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name => "acqui/vendor_issues.tt",
+        query         => $input,
+        type          => "intranet",
+        flagsrequired => { acquisition => 'issues_manage' },
+    }
+);
+
+my $issue;
+if ( $issue_id ) {
+    $issue = Koha::Acquisition::Bookseller::Issues->find($issue_id);
+    $booksellerid = $issue->vendor_id;
+}
+my $vendor = Koha::Acquisition::Booksellers->find($booksellerid);
+
+if ( $op eq 'add_form' || $op eq 'show' ) {
+    $template->param( issue => $issue );
+} elsif ( $op eq 'add_validate' ) {
+    my $type       = $input->param('type');
+    my $started_on = $input->param('started_on');
+    my $ended_on   = $input->param('ended_on');
+    my $notes      = $input->param('notes');
+
+    if ($issue_id) {
+        try {
+            $issue->set(
+                {
+                    type       => $type,
+                    started_on => $started_on,
+                    ended_on   => $ended_on,
+                    notes      => $notes
+                }
+            )->store;
+            push @messages, { type => 'message', code => 'success_on_update' };
+        } catch {
+            push @messages, { type => 'error', code => 'error_on_update' };
+        };
+    } else {
+        try {
+            Koha::Acquisition::Bookseller::Issue->new(
+                {
+                    vendor_id  => $booksellerid,
+                    type       => $type,
+                    started_on => $started_on,
+                    ended_on   => $ended_on,
+                    notes      => $notes,
+                }
+            )->store;
+            push @messages, { type => 'message', code => 'success_on_insert' };
+        } catch {
+            push @messages, { type => 'error', code => 'error_on_insert' };
+        };
+    }
+    $op = 'list';
+} elsif ( $op eq 'delete_confirm' ) {
+    $template->param( issue => $issue );
+} elsif ( $op eq 'delete_confirmed' ) {
+    try {
+        $issue->delete;
+        push @messages, { type => 'message', code => 'success_on_delete' };
+    } catch {
+        push @messages, { type => 'error', code => 'error_on_delete' };
+    };
+    $op = 'list';
+}
+
+if ( $op eq 'list' ) {
+    $template->param( issues_count => $vendor->issues->search->count );
+}
+
+$template->param(
+    messages     => \@messages,
+    op           => $op,
+    vendor       => $vendor,
+    booksellerid => $vendor->id,    # Used by vendor-menu.inc
+);
+
+output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc
index 9466b94cf7..f718ee8260 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc
@@ -454,6 +454,11 @@
             Manage all baskets and order lines, regardless of restrictions on them
         </span>
         <span class="permissioncode">([% name | html %])</span>
+    [%- CASE 'issue_manage' -%]
+        <span class="sub_permission issues_manage_subpermission">
+            Manage issues
+        </span>
+        <span class="permissioncode">([% name | html %])</span>
     [%- CASE 'order_receive' -%]
         <span class="sub_permission order_receive_subpermission">
             Receive orders and manage shipments
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/vendor-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/vendor-menu.inc
index 3fc669e840..82415521c0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/vendor-menu.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/vendor-menu.inc
@@ -4,6 +4,7 @@
         [% IF ( CAN_user_acquisition_order_manage ) %]<li><a href="/cgi-bin/koha/acqui/booksellers.pl?booksellerid=[% booksellerid | uri %]">Baskets</a></li>[% END %]
         [% IF ( CAN_user_acquisition_group_manage ) %]<li><a href="/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=[% booksellerid | uri %]">Basket groups</a></li>[% END %]
         [% IF ( CAN_user_acquisition_contracts_manage ) %]<li><a href="/cgi-bin/koha/admin/aqcontract.pl?booksellerid=[% booksellerid | uri %]">Contracts</a></li>[% END %]
+        [% IF ( CAN_user_acquisition_issue_manage ) %]<li><a href="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% booksellerid | uri %]">Issues</a></li>[% END %]
         <li><a href="/cgi-bin/koha/acqui/invoices.pl?supplierid=[% booksellerid | uri %]&amp;op=do_search">Invoices</a></li>
         [% IF ( CAN_user_acquisition_order_manage ) %][% IF ( basketno ) %]
         <li><a href="/cgi-bin/koha/acqui/uncertainprice.pl?booksellerid=[% booksellerid | uri %]&amp;basketno=[% basketno | uri %]&amp;owner=1">Uncertain prices</a></li>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/vendor_issues.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/vendor_issues.tt
new file mode 100644
index 0000000000..4a6a672875
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/vendor_issues.tt
@@ -0,0 +1,282 @@
+[% USE raw %]
+[% USE Asset %]
+[% USE AuthorisedValues %]
+[% USE KohaDates %]
+[% SET footerjs = 1 %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>
+    [% IF op =='add_form' %]
+        [% IF issue.issue_id %]
+            Modify vendor issue
+        [% ELSE %]
+            New vendor issue
+        [% END %] &rsaquo; [% ELSE %]
+        [% IF op == 'delete_confirm' %]
+            Confirm deletion of vendor issue &rsaquo; [% END %]
+    [% END %]
+    Vendor issue &rsaquo; Acquisition &rsaquo; Koha
+</title>
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+
+<body id="acq_supplier_issues" class="acq">
+[% WRAPPER 'header.inc' %]
+    [% INCLUDE 'acquisitions-search.inc' %]
+[% END %]
+
+[% WRAPPER 'sub-header.inc' %]
+<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
+    <ol>
+        <li>
+            <a href="/cgi-bin/koha/mainpage.pl">Home</a>
+        </li>
+        <li>
+            <a href="/cgi-bin/koha/acqui/acqui-home.pl">Acquisitions</a>
+        </li>
+        <li>
+            <a href="/cgi-bin/koha/acqui/supplier.pl?booksellerid=[% vendor.id | uri %]">[% vendor.name | html %]</a>
+        </li>
+        [% IF op == 'list' %]
+            <li>
+                <a href="#" aria-current="page">Issues</a>
+            </li>
+        [% ELSE %]
+            <li>
+                <a href="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% vendor.id | uri %]">Issues</a>
+            </li>
+            <li>
+                <a href="#" aria-current="page">
+                    [% IF issue %]
+                        Issue #[% issue.issue_id| html %]
+                    [% ELSE %]
+                        New issue
+                    [% END %]
+                </a>
+            </li>
+        [% END %]
+    </ol>
+</nav>
+[% END %]
+
+<div class="main container-fluid">
+    <div class="row">
+        <div class="col-sm-10 col-sm-push-2">
+            <main>
+
+[% FOR m IN messages %]
+    <div class="dialog [% m.type | html %]">
+        [% SWITCH m.code %]
+        [% CASE 'error_on_update' %]
+            <span>An error occurred when updating this issue.</span>
+        [% CASE 'error_on_insert' %]
+            <span>An error occurred when adding this issue</span>
+        [% CASE 'error_on_delete' %]
+            <span>An error occurred when deleting this issue. Check the logs.</span>
+        [% CASE 'success_on_update' %]
+            <span>Issue updated successfully.</span>
+        [% CASE 'success_on_insert' %]
+            <span>Issue created successfully.</span>
+        [% CASE 'success_on_delete' %]
+            <span>Issue deleted successfully.</span>
+        [% CASE %]
+            <span>[% m.code | html %]</span>
+        [% END %]
+    </div>
+[% END %]
+
+[% IF op == 'add_form' %]
+    [% IF issue %]
+        <h1>Modify a vendor issue</h1>
+    [% ELSE %]
+        <h1>New vendor issue</h1>
+    [% END %]
+
+    <form action="/cgi-bin/koha/acqui/vendor_issues.pl" name="Aform" method="post" class="validated">
+        <input type="hidden" name="op" value="add_validate" />
+        <input type="hidden" name="booksellerid" value="[% vendor.id | html %]" />
+        <input type="hidden" name="issue_id" value="[% issue.issue_id| html %]" />
+
+        <fieldset class="rows">
+            <ol>
+                [% IF issue %]
+                    <li><span class="label">Issue ID: </span>[% issue.issue_id | html %]</li>
+                [% END %]
+                <li>
+                    <label for="issue_type">Type: </label>
+                    [% PROCESS 'av-build-dropbox.inc' name="type", category="VENDOR_ISSUE_TYPE", default=issue.type, empty=1, size = 20 %]
+                </li>
+                <li>
+                    <label for="started_on">Started on: </label>
+                    <input type="text" size="10" id="from" name="started_on" value="[% issue.started_on| html %]" class="flatpickr" data-date_to="to" />
+                    <div class="hint">[% INCLUDE 'date-format.inc' %]</div>
+                </li>
+                <li>
+                    <label for="ended_on">Ended on: </label>
+                    <input type="text" size="10" id="to" name="ended_on" value="[% issue.ended_on | html %]" class="flatpickr" />
+                    <div class="hint">[% INCLUDE 'date-format.inc' %]</div>
+                </li>
+                <li>
+                    <label for="notes">Notes: </label>
+                    <textarea name="notes" id="notes" rows="3" cols="50">[% issue.notes | html %]</textarea>
+                </li>
+            </ol>
+        </fieldset>
+
+        <fieldset class="action">
+            <input type="submit" class="btn btn-primary" value="Submit" />
+            <a class="cancel" href="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% vendor.id | uri %]">Cancel</a>
+        </fieldset>
+    </form>
+[% END %]
+
+[% IF op == 'delete_confirm' %]
+    <div class="dialog alert">
+        <h1>Delete issue #[% issue.issue_id | html %]?<h1>
+        <form action="/cgi-bin/koha/acqui/vendor_issues.pl" method="post">
+            <input type="hidden" name="op" value="delete_confirmed" />
+            <input type="hidden" name="issue_id" value="[% issue.issue_id | html %]" />
+            <button type="submit" class="approve"><i class="fa fa-fw fa-check"></i> Yes, delete</button>
+        </form>
+        <form action="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% vendor.id | uri %]" method="get">
+            <button type="submit" class="deny"><i class="fa fa-fw fa-remove"></i> No, do not delete</button>
+        </form>
+    </div>
+[% END %]
+
+[% IF op == 'show' %]
+    <h1>Vendor issue #[% issue.issue_id | html %]</h1>
+
+        <fieldset class="rows">
+            <ol>
+                [% IF issue %]
+                    <li><span class="label">Issue ID: </span>[% issue.issue_id | html %]</li>
+                [% END %]
+                <li>
+                    <label for="issue_type">Type: </label>
+                    [% AuthorisedValues.GetByCode( 'VENDOR_ISSUE_TYPE', issue.type, 0 ) | html %]
+                </li>
+                <li>
+                    <label for="started_on">Started on: </label>
+                    [% issue.started_on | $KohaDates %]
+                </li>
+                <li>
+                    <label for="ended_on">Ended on: </label>
+                    [% issue.ended_on | $KohaDates %]
+                </li>
+                <li>
+                    <label for="notes">Notes: </label>
+                    [% issue.notes | html %]
+                </li>
+            </ol>
+        </fieldset>
+
+        <fieldset class="action">
+            <a href="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% vendor.id | uri %]">Back</a>
+        </fieldset>
+    </form>
+[% END %]
+[% IF op == 'list' %]
+
+    <div id="toolbar" class="btn-toolbar">
+        <a class="btn btn-default" id="new_issue" href="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% vendor.id | uri %]&op=add_form"><i class="fa fa-plus"></i> New issue</a>
+    </div>
+
+    <h1>Vendor issues</h1>
+
+    [% IF issues_count > 0 %]
+        <div class="page-section">
+            <div class="table_vendor_issues_table_controls"></div>
+            <table id="vendor_issues">
+                <thead>
+                    <tr>
+                        <th>Issue ID</th>
+                        <th>Type</th>
+                        <th>Started on</th>
+                        <th>Ended on</th>
+                        <th data-class-name="actions noExport">Actions</th>
+                    </tr>
+                </thead>
+            </table>
+        </div><!-- /.page-section -->
+    [% ELSE %]
+        <div class="dialog message">
+            There are no issues defined. <a href="/cgi-bin/koha/acqui/vendor_issues.pl?booksellerid=[% vendor.id | uri %]&op=add_form">Create a new issue</a>.
+        </div>
+    [% END %]
+[% END %]
+
+            </main>
+        </div> <!-- /.col-sm-10.col-sm-push-2 -->
+
+        <div class="col-sm-2 col-sm-pull-10">
+            <aside>
+                [% INCLUDE 'vendor-menu.inc' %]
+            </aside>
+        </div> <!-- /.col-sm-2.col-sm-pull-10 -->
+     </div> <!-- /.row -->
+
+[% MACRO jsinclude BLOCK %]
+    [% Asset.js("js/acquisitions-menu.js") | $raw %]
+    [% INCLUDE 'calendar.inc' %]
+    [% INCLUDE 'datatables.inc' %]
+    <script>
+
+        $(document).ready(function() {
+            var issues_table_url = '/api/v1/acquisitions/vendors/[% vendor.id | uri %]/issues?';
+
+            var issues_table = $("#vendor_issues").kohaTable({
+                ajax: {
+                    url: issues_table_url
+                },
+                embed: ["+strings"],
+                order: [[ 0, "desc" ]],
+                columns: [
+                    {
+                        data: "issue_id",
+                        searchable: true,
+                        orderable: true,
+                        render: function(data, type, row, meta) {
+                            return '<a href="/cgi-bin/koha/acqui/vendor_issues.pl?op=show&amp;issue_id=' + encodeURIComponent(row.issue_id) +'">' + escape_str(row.issue_id) + '</a>';
+                        },
+                    },
+                    {
+                        data: "type",
+                        searchable: true,
+                        orderable: true,
+                        render: function(data, type, row, meta) {
+                            return row._strings.type ? escape_str(row._strings.type.str) : "";
+                        }
+                    },
+                    {
+                        data: "started_on",
+                        searchable: true,
+                        orderable: true,
+                        render: function(data, type, row, meta) {
+                            return $date(row.started_on);
+                        }
+                    },
+                    {
+                        data: "ended_on",
+                        searchable: true,
+                        orderable: true,
+                        render: function(data, type, row, meta) {
+                            return $date(row.ended_on);
+                        }
+                    },
+                    {
+                        data: function( row, type, val, meta ) {
+
+                            var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/acqui/vendor_issues.pl?op=add_form&amp;issue_id='+ encodeURIComponent(row.issue_id) +'"><i class="fa fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>'+"\n";
+                            result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/acqui/vendor_issues.pl?op=delete_confirm&amp;issue_id='+ encodeURIComponent(row.issue_id) +'"><i class="fa fa-trash" aria-hidden="true"></i> '+_("Delete")+'</a>';
+                            return result;
+                        },
+                        searchable: false,
+                        orderable: false
+                    }
+                ]
+            }, null, 1);
+
+        });
+    </script>
+[% END %]
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt
index 1a5265ff3b..9609fc38bc 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt
@@ -497,6 +497,8 @@
             <p>Values that can be entered to fill in the 'Vendor type' field in the acquisitions module, that can be used for statistical purposes</p>
         [% CASE 'VENDOR_INTERFACE_TYPE' %]
             <p>Values that can be entered to fill in the 'Vendor interface type' field in the acquisitions module</p>
+        [% CASE 'VENDOR_ISSUE_TYPE' %]
+            <p>Values that can be entered to fill in the 'Vendor issue type' field in the acquisitions module</p>
         [% CASE 'WITHDRAWN' %]
             <p>Description of a withdrawn item (appears when adding or editing an item)</p>
         [% CASE 'YES_NO' %]
-- 
2.30.2