travelynx/lib/Travelynx/Controller/Passengerrights.pm
2023-02-19 09:35:38 +01:00

355 lines
9.6 KiB
Perl

package Travelynx::Controller::Passengerrights;
# Copyright (C) 2020-2023 Daniel Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
use Mojo::Base 'Mojolicious::Controller';
use DateTime;
use CAM::PDF;
# Internal Helpers
sub mark_if_missed_connection {
my ( $self, $journey, $next_journey ) = @_;
my $possible_delay
= ( $next_journey->{rt_departure}->epoch
- $journey->{sched_arrival}->epoch )
/ 60;
my $wait_time
= ( $next_journey->{rt_departure}->epoch - $journey->{rt_arrival}->epoch )
/ 60;
# Assumption: $next_journey is a missed connection (i.e., if $journey had
# been on time it would have been an earlier train)
# * the wait time between arrival and departure is less than 70 minutes
# (up to 60 minutes to wait for the next train in an hourly connection
# + up to 10 minutes transfer time between platforms)
# * the delay between scheduled arrival at the interchange station and
# real departure of $next_journey (which is hopefully the same as the
# total delay at the destination of $next_journey) is more than 120
# minutes (-> 50% fare reduction) or it is more than 60 minutes and the
# single-journey delay is less than 60 minutes (-> 25% fare reduction)
# This ensures that $next_journey is only referenced if the missed
# connection makes a difference from a passenger rights point of view --
# if $journey itself is already 60 .. 119 minutes delayed and the
# delay with the connection to $next_journey is also 60 .. 119 minutes,
# including it is not worth the effort. Similarly, if $journey is already
# ≥120 minutes delayed, looking for connections and more delay is
# pointless.
if (
$wait_time < 70
and ( $possible_delay >= 120
or ( $journey->{delay} < 60 and $possible_delay >= 60 ) )
)
{
$journey->{connection_missed} = 1;
$journey->{connection} = $next_journey;
$journey->{possible_delay} = $possible_delay;
$journey->{wait_time} = $wait_time;
return 1;
}
return 0;
}
sub mark_substitute_connection {
my ( $self, $journey ) = @_;
my @substitute_candidates = reverse $self->journeys->get(
uid => $self->current_user->{id},
after => $journey->{sched_departure}->clone->subtract( hours => 1 ),
before => $journey->{sched_departure}->clone->add( hours => 12 ),
with_datetime => 1,
);
my ( $first_substitute, $last_substitute );
for my $substitute_candidate (@substitute_candidates) {
if ( not $first_substitute
and $substitute_candidate->{from_name} eq $journey->{from_name} )
{
$first_substitute = $substitute_candidate;
}
if ( not $last_substitute
and $substitute_candidate->{to_name} eq $journey->{to_name} )
{
$last_substitute = $substitute_candidate;
last;
}
}
if ( $first_substitute and $last_substitute ) {
$journey->{has_substitute} = 1;
$journey->{from_substitute} = $first_substitute;
$journey->{to_substitute} = $last_substitute;
$journey->{substitute_delay}
= ( $last_substitute->{rt_arr_ts} - $journey->{sched_arr_ts} ) / 60;
}
}
# Controllers
sub list_candidates {
my ($self) = @_;
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
my $range_start = $now->clone->subtract( months => 6 );
my @journeys = $self->journeys->get(
uid => $self->current_user->{id},
after => $range_start,
before => $now,
with_datetime => 1,
);
@journeys = grep { $_->{sched_arrival}->epoch and $_->{rt_arrival}->epoch }
@journeys;
for my $i ( 0 .. $#journeys ) {
my $journey = $journeys[$i];
$journey->{delay}
= ( $journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch )
/ 60;
if ( $journey->{delay} < 3 or $journey->{delay} >= 120 ) {
next;
}
if ( $i > 0 ) {
$self->mark_if_missed_connection( $journey, $journeys[ $i - 1 ] );
}
}
@journeys = grep { $_->{delay} >= 60 or $_->{connection_missed} } @journeys;
my @cancelled = $self->journeys->get(
uid => $self->current_user->{id},
after => $range_start,
before => $now,
cancelled => 1,
with_datetime => 1,
);
for my $journey (@cancelled) {
if ( not $journey->{sched_arrival}->epoch ) {
next;
}
$journey->{cancelled} = 1;
$self->mark_substitute_connection($journey);
if ( $journey->{has_substitute}
and $journey->{to_substitute}->{rt_arr_ts}
- $journey->{sched_arr_ts} >= 3600 )
{
push( @journeys, $journey );
}
}
@journeys
= sort { $b->{sched_departure}->epoch <=> $a->{sched_departure}->epoch }
@journeys;
$self->respond_to(
json => { json => [@journeys] },
any => {
template => 'passengerrights',
journeys => [@journeys]
}
);
}
sub generate {
my ($self) = @_;
my $journey_id = $self->param('id');
my $uid = $self->current_user->{id};
if ( not($journey_id) ) {
$self->render(
'journey',
error => 'notfound',
journey => {}
);
return;
}
my $journey = $self->journeys->get_single(
uid => $uid,
journey_id => $journey_id,
verbose => 1,
with_datetime => 1,
);
if ( not $journey ) {
$self->render(
'journey',
error => 'notfound',
journey => {}
);
return;
}
$journey->{delay}
= ( $journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch )
/ 60;
if ( $journey->{cancelled} ) {
$self->mark_substitute_connection($journey);
}
elsif ( $journey->{delay} < 120 ) {
my @connections = $self->journeys->get(
uid => $uid,
after => $journey->{rt_arrival},
before => $journey->{rt_arrival}->clone->add( hours => 2 ),
with_datetime => 1,
);
if (@connections) {
$self->mark_if_missed_connection( $journey, $connections[-1] );
}
}
my $pdf = CAM::PDF->new('public/static/pdf/fahrgastrechteformular.pdf');
# from station
$pdf->fillFormFields( 'S1F4', $journey->{from_name} );
if ( $journey->{connection} ) {
# to station
$pdf->fillFormFields( 'S1F7', $journey->{connection}{to_name} );
# missed connection in:
$pdf->fillFormFields( 'S1F22', $journey->{to_name} );
# last change in:
$pdf->fillFormFields( 'S1F24', $journey->{to_name} );
}
else {
# to station
$pdf->fillFormFields( 'S1F7', $journey->{to_name} );
}
if ( $journey->{has_substitute} ) {
# arived with: TRAIN NO
$pdf->fillFormFields( 'S1F13', $journey->{to_substitute}{type} );
$pdf->fillFormFields( 'S1F14', $journey->{to_substitute}{no} );
# arrival YYMMDD
$pdf->fillFormFields( 'S1F10',
$journey->{to_substitute}{rt_arrival}->strftime('%d') );
$pdf->fillFormFields( 'S1F11',
$journey->{to_substitute}{rt_arrival}->strftime('%m') );
$pdf->fillFormFields( 'S1F12',
$journey->{to_substitute}{rt_arrival}->strftime('%y') );
# arrival HHMM
$pdf->fillFormFields( 'S1F15',
$journey->{to_substitute}{rt_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F16',
$journey->{to_substitute}{rt_arrival}->strftime('%M') );
if ( $journey->{from_substitute} != $journey->{to_substitute} ) {
# last change in:
$pdf->fillFormFields( 'S1F24',
$journey->{to_substitute}{from_name} );
}
}
elsif ( not $journey->{cancelled} ) {
# arived with: TRAIN NO
if ( $journey->{connection} ) {
$pdf->fillFormFields( 'S1F13', $journey->{connection}{type} );
$pdf->fillFormFields( 'S1F14', $journey->{connection}{no} );
}
else {
$pdf->fillFormFields( 'S1F13', $journey->{type} );
$pdf->fillFormFields( 'S1F14', $journey->{no} );
}
}
# first delayed train: TRAIN NO
$pdf->fillFormFields( 'S1F17', $journey->{type} );
$pdf->fillFormFields( 'S1F18', $journey->{no} );
if ( $journey->{sched_departure}->epoch ) {
# journey YYMMDD
$pdf->fillFormFields( 'S1F1',
$journey->{sched_departure}->strftime('%d') );
$pdf->fillFormFields( 'S1F2',
$journey->{sched_departure}->strftime('%m') );
$pdf->fillFormFields( 'S1F3',
$journey->{sched_departure}->strftime('%y') );
# sched departure HHMM
$pdf->fillFormFields( 'S1F5',
$journey->{sched_departure}->strftime('%H') );
$pdf->fillFormFields( 'S1F6',
$journey->{sched_departure}->strftime('%M') );
# first delayed train: sched departure HHMM
$pdf->fillFormFields( 'S1F19',
$journey->{sched_departure}->strftime('%H') );
$pdf->fillFormFields( 'S1F20',
$journey->{sched_departure}->strftime('%M') );
}
if ( $journey->{sched_arrival}->epoch ) {
# sched arrival HHMM
if ( $journey->{connection} ) {
# TODO (needs plan data for non-journey trains)
}
else {
$pdf->fillFormFields( 'S1F8',
$journey->{sched_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F9',
$journey->{sched_arrival}->strftime('%M') );
}
}
if ( $journey->{rt_arrival}->epoch and not $journey->{cancelled} ) {
if ( $journey->{connection} ) {
# arrival YYMMDD
$pdf->fillFormFields( 'S1F10',
$journey->{connection}{rt_arrival}->strftime('%d') );
$pdf->fillFormFields( 'S1F11',
$journey->{connection}{rt_arrival}->strftime('%m') );
$pdf->fillFormFields( 'S1F12',
$journey->{connection}{rt_arrival}->strftime('%y') );
# arrival HHMM
$pdf->fillFormFields( 'S1F15',
$journey->{connection}{rt_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F16',
$journey->{connection}{rt_arrival}->strftime('%M') );
}
else {
# arrival YYMMDD
$pdf->fillFormFields( 'S1F10',
$journey->{rt_arrival}->strftime('%d') );
$pdf->fillFormFields( 'S1F11',
$journey->{rt_arrival}->strftime('%m') );
$pdf->fillFormFields( 'S1F12',
$journey->{rt_arrival}->strftime('%y') );
# arrival HHMM
$pdf->fillFormFields( 'S1F15',
$journey->{rt_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F16',
$journey->{rt_arrival}->strftime('%M') );
}
}
$self->res->headers->content_type('application/pdf');
$self->res->body( $pdf->toPDF() );
$self->rendered(200);
}
1;