Koha/acqui/duplicate_orders.pl
Jonathan Druart d0040dff9f
Bug 23079: Handle invalid timezones when adding/subtracting durations
On Nov 3rd 2019, Brazil will skip from 00:00 to 1:00 (http://www.currenttimeonline.com/dst/dst.do?tz=America/Sao_Paulo), DateTime consider it as an invalid date.
It is a problem when we are playing with dates without the time part (always 00:00).
When we instantiate a DateTime (from dt_from_string) we are already handling this issue, and use the floating timezone (since bug 12669).

The problem remains when we generate a DateTime then add or subtract a duration, which will result in an invalid date:

DateTime->new(year => 2019, month => 12, day => 3, time_zone => 'America/Sao_Paulo')->subtract(days => 30);

=> Nov 3rd 2019, kaboom.

We should replace all the problematic occurrences of dt_from_string->subtract (or ->add)
with dt_from_string(undef, undef, 'floating'), to use the floating timezone and avoid the error.

Actually there are not many of them, I have found only 3 that could
produce real problems.

The other occurrences are:
- in tests => Not a big deal (for now)
- called on a datetime, so it will explode if called at midnight
00:00:00 (and nobody should work at that time).

Test plan:
0/ Define the timezone to 'America/Sao_Paulo' (in your koha-conf.xml file), restart_all
1/ Set a patron's expiry date to Dec 3rd 2019, and
NotifyBorrowerDeparture to 30 (default value)
2/ See the checkouts page for this user
=> Without this patch you get "Invalid local time for date in time zone:
America/Sao_Paulo"
=> With this patch apply the page displays correctly

QA will review the 2 other occurrences.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
2019-09-06 13:17:47 +01:00

177 lines
6.3 KiB
Perl
Executable file

#!/usr/bin/perl
# Copyright 2018 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::Output;
use C4::Acquisition qw(GetHistory);
use C4::Budgets qw(GetBudgetPeriods GetBudgetHierarchy CanUserUseBudget);
use Koha::Acquisition::Baskets;
use Koha::Acquisition::Currencies;
use Koha::Acquisition::Orders;
use Koha::DateUtils qw(dt_from_string output_pref);
my $input = new CGI;
my $basketno = $input->param('basketno');
my $op = $input->param('op') || 'search'; # search, select, batch_edit
my ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
{
template_name => "acqui/duplicate_orders.tt",
query => $input,
type => "intranet",
authnotrequired => 0,
flagsrequired => { acquisition => 'order_manage' },
}
);
my $basket = Koha::Acquisition::Baskets->find($basketno);
output_and_exit( $input, $cookie, $template, 'unknown_basket' )
unless $basket;
my $vendor = $basket->bookseller;
my $patron = Koha::Patrons->find($loggedinuser)->unblessed;
my $filters = {
basket => scalar $input->param('basket'),
title => scalar $input->param('title'),
author => scalar $input->param('author'),
isbn => scalar $input->param('isbn'),
name => scalar $input->param('name'),
ean => scalar $input->param('ean'),
basketgroupname => scalar $input->param('basketgroupname'),
booksellerinvoicenumber => scalar $input->param('booksellerinvoicenumber'),
budget => scalar $input->param('budget'),
orderstatus => scalar $input->param('orderstatus'),
ordernumber => scalar $input->param('ordernumber'),
search_children_too => scalar $input->param('search_children_too'),
created_by => scalar $input->multi_param('created_by'),
};
my $from_placed_on =
eval { dt_from_string( scalar $input->param('from') ) } || dt_from_string;
my $to_placed_on =
eval { dt_from_string( scalar $input->param('to') ) } || dt_from_string;
unless ( $input->param('from') ) {
# Fill the form with year-1
$from_placed_on->set_time_zone('floating')->subtract( years => 1 );
}
$filters->{from_placed_on} =
output_pref( { dt => $from_placed_on, dateformat => 'iso', dateonly => 1 } ),
$filters->{to_placed_on} =
output_pref( { dt => $to_placed_on, dateformat => 'iso', dateonly => 1 } ),
my ( @result_order_loop, @selected_order_loop );
my @ordernumbers = split ',', scalar $input->param('ordernumbers') || '';
if ( $op eq 'select' ) {
@result_order_loop = map {
my $order = $_;
( grep { /^$order->{ordernumber}$/ } @ordernumbers ) ? () : $order
} @{ C4::Acquisition::GetHistory(%$filters) };
@selected_order_loop =
scalar @ordernumbers
? @{ C4::Acquisition::GetHistory( ordernumbers => \@ordernumbers ) }
: ();
}
elsif ( $op eq 'batch_edit' ) {
@ordernumbers = $input->multi_param('ordernumber');
# build budget list
my $budget_loop = [];
my $budgets_hierarchy = GetBudgetHierarchy;
foreach my $r ( @{$budgets_hierarchy} ) {
next
unless ( C4::Budgets::CanUserUseBudget( $patron, $r, $userflags ) );
if ( !defined $r->{budget_amount} || $r->{budget_amount} == 0 ) {
next;
}
push @{$budget_loop},
{
b_id => $r->{budget_id},
b_txt => $r->{budget_name},
b_code => $r->{budget_code},
b_sort1_authcat => $r->{'sort1_authcat'},
b_sort2_authcat => $r->{'sort2_authcat'},
b_active => $r->{budget_period_active},
};
}
@{$budget_loop} =
sort { uc( $a->{b_txt} ) cmp uc( $b->{b_txt} ) } @{$budget_loop};
my @currencies = Koha::Acquisition::Currencies->search;
$template->param(
currencies => \@currencies,
budget_loop => $budget_loop,
);
}
elsif ( $op eq 'do_duplicate' ) {
my @fields_to_copy = $input->multi_param('copy_existing_value');
my $default_values;
for my $field (
qw(currency budget_id order_internalnote order_vendornote sort1 sort2 ))
{
next if grep { /^$field$/ } @fields_to_copy;
$default_values->{$field} = $input->param("all_$field");
}
@ordernumbers = $input->multi_param('ordernumber');
my @new_ordernumbers;
for my $ordernumber (@ordernumbers) {
my $original_order = Koha::Acquisition::Orders->find($ordernumber);
next unless $original_order;
my $new_order =
$original_order->duplicate_to( $basket, $default_values );
push @new_ordernumbers, $new_order->ordernumber;
}
my $new_orders =
C4::Acquisition::GetHistory( ordernumbers => \@new_ordernumbers );
$template->param( new_orders => $new_orders );
$op = 'duplication_done';
}
my $budgetperiods = C4::Budgets::GetBudgetPeriods;
my $bp_loop = $budgetperiods;
for my $bp ( @{$budgetperiods} ) {
my $hierarchy = C4::Budgets::GetBudgetHierarchy( $$bp{budget_period_id} );
for my $budget ( @{$hierarchy} ) {
$$budget{budget_display_name} =
sprintf( "%s", ">" x $$budget{depth} . $$budget{budget_name} );
}
$$bp{hierarchy} = $hierarchy;
}
$template->param(
basket => $basket,
vendor => $vendor,
filters => $filters,
result_order_loop => \@result_order_loop,
selected_order_loop => \@selected_order_loop,
bp_loop => $bp_loop,
ordernumbers => \@ordernumbers,
op => $op,
);
output_html_with_http_headers $input, $cookie, $template->output;