Bugzilla – Attachment 137282 Details for
Bug 30650
Add a curbside pickup module
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 30650: Admin page
Bug-30650-Admin-page.patch (text/plain), 19.05 KB, created by
Jonathan Druart
on 2022-07-07 13:01:12 UTC
(
hide
)
Description:
Bug 30650: Admin page
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2022-07-07 13:01:12 UTC
Size:
19.05 KB
patch
obsolete
>From d90da4f0d36d3508136fc8138717809adeeb0c69 Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Date: Fri, 29 Apr 2022 12:04:03 +0200 >Subject: [PATCH] Bug 30650: Admin page > >This patch adds a new page in the administration area to manage curbside >pickups. > >Test plan: >1. Enable the new CurbsidePickup syspref >2. Go to Administration > Curbside pickups >=> If the logged-in user has the manage_curbside_pickups permission you >should see the administration view >3. Enable the feature for some libraries and configure it. > a. You must define a "pickup interval" and a "maximum patrons per interval" >values. > b. If "Patron-scheduled pickup" is enabled, patrons will be able to >schedule curbside pickups from the OPAC > c. If "Enable for waiting holds only" is enabled, only patron with waiting >holds will be allowed to schedule curbside pickups > d. In the bottom part of the form you can define the slots available for >curbside pickups. > e. Save > >Sponsored-by: Association KohaLa - https://koha-fr.org/ > >Signed-off-by: Koha Team University Lyon 3 <koha@univ-lyon3.fr> >--- > admin/curbside_pickup.pl | 95 ++++++ > .../prog/en/modules/admin/curbside_pickup.tt | 316 ++++++++++++++++++ > 2 files changed, 411 insertions(+) > create mode 100755 admin/curbside_pickup.pl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/curbside_pickup.tt > >diff --git a/admin/curbside_pickup.pl b/admin/curbside_pickup.pl >new file mode 100755 >index 00000000000..e90c3bfc15c >--- /dev/null >+++ b/admin/curbside_pickup.pl >@@ -0,0 +1,95 @@ >+#! /usr/bin/perl >+ >+# 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::Context; >+use C4::Auth qw( get_template_and_user ); >+use C4::Output qw( output_html_with_http_headers ); >+ >+use Koha::CurbsidePickupPolicies; >+use Koha::Libraries; >+ >+my $input = CGI->new; >+my $op = $input->param('op') || 'list'; >+my @messages; >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => "admin/curbside_pickup.tt", >+ query => $input, >+ type => "intranet", >+ flagsrequired => { parameters => 'manage_curbside_pickups' }, >+ } >+); >+ >+my $libraries = Koha::Libraries->search( {}, { order_by => ['branchname'] } ); >+if ( $op eq 'save' ) { >+ foreach my $library ( $libraries->as_list ) { >+ my $branchcode = $library->branchcode; >+ >+ my $params = { >+ >+ branchcode => $branchcode, >+ enabled => scalar $input->param("enable-$branchcode") || 0, >+ pickup_interval => scalar $input->param("interval-$branchcode"), >+ patrons_per_interval => scalar $input->param("max-per-interval-$branchcode"), >+ patron_scheduled_pickup => scalar $input->param("patron-scheduled-$branchcode") || 0, >+ }; >+ >+ for my $day ( >+ qw( sunday monday tuesday wednesday thursday friday saturday )) >+ { >+ for my $start_end (qw( start end )) { >+ for my $hour_min (qw( hour minute )) { >+ >+ my $value = $input->param( >+ "pickup-$start_end-$hour_min-$day-$branchcode"); >+ $value = undef if $value eq q{}; >+ >+ my $key = $day . '_' . $start_end . '_' . $hour_min; >+ >+ $params->{$key} = $value; >+ } >+ } >+ } >+ >+ my $CurbsidePickupPolicy = >+ Koha::CurbsidePickupPolicies->find( { branchcode => $branchcode } ); >+ $CurbsidePickupPolicy->delete if $CurbsidePickupPolicy; >+ >+ Koha::CurbsidePickupPolicy->new($params)->store(); >+ } >+ $op = 'list'; >+} >+ >+if ( $op eq 'list' ) { >+ $template->param( >+ policies => { >+ map { $_->branchcode => $_ } >+ Koha::CurbsidePickupPolicies->search->as_list >+ }, >+ libraries => $libraries, >+ ); >+} >+ >+$template->param( >+ messages => \@messages, >+ op => $op, >+); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/curbside_pickup.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/curbside_pickup.tt >new file mode 100644 >index 00000000000..de171d88d72 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/curbside_pickup.tt >@@ -0,0 +1,316 @@ >+[% USE raw %] >+[% USE Asset %] >+[% SET footerjs = 1 %] >+[% USE TablesSettings %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title> >+ Curbside pickup › Administration › Koha >+</title> >+[% INCLUDE 'doc-head-close.inc' %] >+<style> >+ .pickup-slot { >+ border: solid; >+ padding: 1em; >+ } >+</style> >+</head> >+ >+<body id="admin_curbside_pickup" class="admin"> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'cities-admin-search.inc' %] >+ >+[% USE format_minutes = format('%02d') %] >+[% MACRO minutes_format BLOCK %][% IF m != "" %][% format_minutes(m) | html %][% END %][% END %] >+<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/admin/admin-home.pl">Administration</a> >+ </li> >+ >+ <li> >+ <a href="#" aria-current="page"> >+ Curbside pickup >+ </a> >+ </li> >+ </ol> >+</nav> >+ >+<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 %] >+ <span>[% m.code | html %]</span> >+ [% END %] >+ </div> >+[% END %] >+ >+[% IF op == 'list' %] >+ >+ <h1>Cubside pickup configuration</h1> >+ >+ <form method="post" class="form"> >+ <div id="curbside_pickup_tabs" class="toptabs"> >+ <ul class="nav nav-tabs" role="tablist"> >+ [% FOREACH l IN libraries %] >+ [% IF loop.first %] >+ <li role="presentation" class="active"> >+ [% ELSE %] >+ <li role="presentation"> >+ [% END %] >+ <a href="#conf-[% l.branchcode | uri %]" aria-controls="conf-[% l.branchcode | html %]" role="tab" data-toggle="tab">[% l.branchname | html %]</a> >+ </li> >+ [% END %] >+ </ul> >+ >+ <div class="tab-content"> >+ [% FOREACH l IN libraries %] >+ [% SET branchcode = l.branchcode %] >+ [% IF loop.first %] >+ <div id="conf-[% l.branchcode | html %]" role="tabpanel" class="tab-pane active"> >+ [% ELSE %] >+ <div id="conf-[% l.branchcode | html %]" role="tabpanel" class="tab-pane"> >+ [% END %] >+ <fieldset class="rows" style="float: none;"> >+ <ol> >+ <li> >+ <label for="enable-[% l.branchcode | html %]">Enable: </label> >+ [% IF policies.$branchcode.enabled %] >+ <input name="enable-[% l.branchcode | html %]" id="enable-[% l.branchcode | html %]" value="1" type="checkbox" checked> >+ [% ELSE %] >+ <input name="enable-[% l.branchcode | html %]" id="enable-[% l.branchcode | html %]" value="1" type="checkbox"> >+ [% END %] >+ <span class="hint">Enable curbside pickup.<span> >+ </li> >+ >+ <li> >+ <label for="interval-[% l.branchcode | html %]">Pickup interval: </label> >+ <input name="interval-[% l.branchcode | html %]" id="interval-[% l.branchcode | html %]" value="[% policies.$branchcode.pickup_interval | html %]" type="text"> >+ <span class="hint">Number of minutes each curbside pickup interaction will take.</span> >+ </li> >+ >+ <li> >+ <label for="max-per-interval-[% l.branchcode | html %]">Maximum patrons per interval: </label> >+ <input name="max-per-interval-[% l.branchcode | html %]" id="max-per-interval-[% l.branchcode | html %]" value="[% policies.$branchcode.patrons_per_interval | html %]" type="text"> >+ <span class="hint">Maximum number of simultaneus pickups per interval.</span> >+ </li> >+ >+ <li> >+ <label for="patron-scheduled-[% l.branchcode | html %]">Patron-scheduled pickup: </label> >+ [% IF policies.$branchcode.patron_scheduled_pickup %] >+ <input name="patron-scheduled-[% l.branchcode | html %]" id="patron-scheduled-[% l.branchcode | html %]" value="1" type="checkbox" checked> >+ [% ELSE %] >+ <input name="patron-scheduled-[% l.branchcode | html %]" id="patron-scheduled-[% l.branchcode | html %]" value="1" type="checkbox"> >+ [% END %] >+ <span class="hint">Enable patrons to schedule their own curbside pickups.</span> >+ </li> >+ </ol> >+ </fieldset> >+ >+ <fieldset class="rows" style="float: none;"> >+ <legend>Curbside pickup hours</legend> >+ >+ <em>Times should be in 24-hour format ( 00:00 to 23:59 ).</em> >+ >+ <ol class="pickup_hours"> >+ [% BLOCK pickup_hours_day %] >+ [% SET day_start_hour = d _ '_start_hour' %] >+ [% SET day_start_minute = d _ '_start_minute' %] >+ [% SET day_end_hour = d _ '_end_hour' %] >+ [% SET day_end_minute = d _ '_end_minute' %] >+ [% SET p = policies.$branchcode %] >+ [% IF p.$day_start_hour && p.$day_start_minute && p.$day_end_hour && p.$day_end_minute %] >+ <li> >+ <span class="slot"> >+ <input type="hidden" class="pickup-slot" name="pickup-slot-[% l.branchcode | html %]" value="[% d | html %]-[% p.$day_start_hour %]:[% p.$day_start_minute %]-[% p.$day_end_hour %]:[% p.$day_end_minute %]" /> >+ </span> >+ </li> >+ [% END %] >+ [% END %] >+ [% PROCESS pickup_hours_day d => 'sunday' %] >+ [% PROCESS pickup_hours_day d => 'monday' %] >+ [% PROCESS pickup_hours_day d => 'tuesday' %] >+ [% PROCESS pickup_hours_day d => 'wednesday' %] >+ [% PROCESS pickup_hours_day d => 'thursday' %] >+ [% PROCESS pickup_hours_day d => 'friday' %] >+ [% PROCESS pickup_hours_day d => 'saturday' %] >+ </ol> >+ >+ <ol> >+ <li> >+ <label>New slot:</label> >+ <div> >+ <select id="day-[% l.branchcode | html %]"> >+ <option value="0">Sunday</option> >+ <option value="1">Monday</option> >+ <option value="2">Tuesday</option> >+ <option value="3">Wednesday</option> >+ <option value="4">Thursday</option> >+ <option value="5">Friday</option> >+ <option value="6">Saturday</option> >+ <span> >+ From <input type="text" size="5" class="noEnterSubmit" id="new-start-[% l.branchcode | html %]" placeholder="00:00" /> >+ to <input type="text" size="5" class="noEnterSubmit" id="new-end-[% l.branchcode | html %]" placeholder="23:55" /> >+ </span> >+ </select> >+ <input type="button" class="add-new-slot" data-branchcode="[% l.branchcode | html %]" value="Add" /> >+ </div> >+ </li> >+ >+ </ol> >+ </fieldset> >+ </div> >+ [% END %] >+ </div> >+ </div> >+ >+ <input type="hidden" name="op" value="save" /> >+ >+ <fieldset class="action"> >+ <input type="submit" value="Save configuration" /> >+ </fieldset> >+ </form> >+[% END %] >+ >+ </main> >+ </div> <!-- /.col-sm-10.col-sm-push-2 --> >+ >+ <div class="col-sm-2 col-sm-pull-10"> >+ <aside> >+ [% INCLUDE 'admin-menu.inc' %] >+ </aside> >+ </div> <!-- /.col-sm-2.col-sm-pull-10 --> >+ </div> <!-- /.row --> >+ >+[% MACRO jsinclude BLOCK %] >+ [% Asset.js("js/admin-menu.js") | $raw %] >+ <script> >+ >+ let opening_slots = {}; >+ let slots; >+ [% FOR l IN libraries %] >+ [% SET branchcode = l.branchcode %] >+ slots = []; >+ [% FOR p IN policies.$branchcode.opening_slots %] >+ slots.push('%s-%s-%s'.format("[% p.day | html %]", format_hhmm("[% p.start_hour | html %]:[% p.start_minute | html %]"), format_hhmm("[% p.end_hour | html %]:[% p.end_minute | html %]"))); >+ [% END %] >+ opening_slots["[% l.branchcode | html %]"] = slots; >+ [% END %] >+ >+ function format_hhmm(hhmm){ >+ let hh, mm; >+ [ hh, mm ] = hhmm.split(':'); >+ return String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0'); >+ } >+ function format_slot(slot){ >+ let start = slot[0]; >+ let end = slot[1]; >+ return format_hhmm(start) + _(" to ") + format_hhmm(end); >+ } >+ function refresh_pickup_hours(branchcode) { >+ let slots = {}; >+ $("#conf-"+branchcode).find("input.pickup-slot").each(function(){ >+ let splitted = $(this).val().split("-"); >+ let day = splitted[0]; >+ let start = splitted[1]; >+ let end = splitted[2]; >+ >+ if(!slots[day]) slots[day] = []; >+ >+ slots[day].push([start, end]); >+ }); >+ >+ let new_slot_node = $("<span class='slot'><input type='hidden' class='pickup-slot' name='pickup-slot-"+branchcode+"' value='"+day+"-"+start+"-"+end+"'/></span>"); >+ $("#conf-"+branchcode).find(".pickup_hours").append(new_slot_node); >+ >+ $("#conf-"+branchcode).find(".pickup_hours li").remove(); >+ >+ Object.keys(slots).forEach(function(day){ >+ let li_node = $('<li><label>'+get_day_lib(day)+'<label></li>'); >+ slots[day].forEach(function(slot) { >+ li_node.append('<span>'+format_slot(slot)+'</span>'); >+ }); >+ li_node.appendTo($("#conf-"+branchcode).find(".pickup_hours")); >+ }); >+ } >+ function get_day_lib(day){ >+ let lib; >+ switch(day){ >+ case 'sunday': >+ lib = _("Sunday"); >+ break; >+ case 'monday': >+ lib = _("Monday"); >+ break; >+ case 'tuesday': >+ lib = _("Tuesday"); >+ break; >+ case 'wednesday': >+ lib = _("Wednesday"); >+ break; >+ case 'thursday': >+ lib = _("Thursday"); >+ break; >+ case 'friday': >+ lib = _("Friday"); >+ break; >+ case 'saturday': >+ lib = _("Saturday"); >+ break; >+ } >+ return lib; >+ } >+ >+ let opening_slots = {}; >+ [% FOR l IN libraries %] >+ opening_slots["[% l.branchcode | html %]"] = []; >+ [% END %] >+ $(document).ready(function(){ >+ [% FOR l IN libraries %] >+ refresh_pickup_hours("[% l.branchcode | html %]"); >+ [% END %] >+ >+ $(".add-new-slot").on("click", function(){ >+ let branchcode = $(this).data('branchcode'); >+ let day = $("#day-" + branchcode).val(); >+ let start = $("#new-start-" + branchcode).val(); >+ let end = $("#new-end-" + branchcode).val(); >+ // FIXME confirm selection >+ >+ let start_hour, start_minute, end_hour, end_minute; >+ [ start_hour, start_minute ] = start.split(":"); >+ [ end_hour, end_minute ] = end.split(":"); >+ if ( start_hour === undefined >+ || start_minute === undefined >+ || end_hour === undefined >+ || end_minute === undefined >+ || isNaN(parseInt(start_hour)) >+ || isNaN(parseInt(end_hour)) >+ || isNaN(parseInt(start_minute)) >+ || isNaN(parseInt(end_minute)) >+ || start_hour > 23 || start_minute > 59 >+ || end_hour > 23 || end_minute > 59 >+ ) { >+ $("#invalid_slot_warning").show(); >+ return; >+ } else { >+ $("#invalid_slot_warning").hide(); >+ } >+ >+ let new_slot = day + '-' + start + '-' + end; >+ if ( opening_slots[branchcode].indexOf(new_slot) < 0 ) >+ opening_slots[branchcode].push(new_slot); >+ >+ refresh_pickup_hours(branchcode); >+ }) >+ }); >+ </script> >+[% END %] >+[% INCLUDE 'intranet-bottom.inc' %] >-- >2.25.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 30650
:
136584
|
136585
|
136586
|
136587
|
136588
|
136589
|
136590
|
136591
|
136592
|
136593
|
136594
|
136595
|
136596
|
136597
|
136598
|
136599
|
136600
|
136601
|
136602
|
136603
|
136604
|
136605
|
136606
|
136607
|
136608
|
136609
|
136610
|
137095
|
137100
|
137199
|
137211
|
137212
|
137277
|
137278
|
137279
|
137280
|
137281
|
137282
|
137283
|
137284
|
137285
|
137286
|
137287
|
137288
|
137289
|
137290
|
137291
|
137292
|
137293
|
137294
|
137295
|
137296
|
137297
|
137298
|
137299
|
137300
|
137301
|
137302
|
137303
|
137304
|
137305
|
137306
|
137307
|
138261
|
138262
|
138263
|
138264
|
138265
|
138266
|
138267
|
138268
|
138269
|
138270
|
138271
|
138272
|
138273
|
138274
|
138275
|
138276
|
138277
|
138278
|
138279
|
138280
|
138281
|
138282
|
138283
|
138284
|
138285
|
138286
|
138287
|
138288
|
138289
|
138290
|
138291
|
138292
|
138319
|
138320
|
138321
|
138322
|
138323
|
138324
|
138325
|
138330
|
138331
|
138332
|
138333
|
138334
|
138335
|
138336
|
138337
|
138338
|
138339
|
138340
|
138341
|
138342
|
138343
|
138344
|
138345
|
138346
|
138347
|
138348
|
138349
|
138350
|
138351
|
138352
|
138353
|
138354
|
138355
|
138356
|
138357
|
138358
|
138359
|
138360
|
138361
|
138362
|
138363
|
138364
|
138365
|
138366
|
138367
|
138368
|
138369
|
138375
|
138401
|
138610