mirror of
https://github.com/derf/travelynx
synced 2024-12-14 23:12:31 +00:00
355 lines
9.6 KiB
Perl
355 lines
9.6 KiB
Perl
package Travelynx::Controller::Passengerrights;
|
|
|
|
# Copyright (C) 2020-2023 Birte Kristina 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;
|