Skip to content

Commit

Permalink
multibooking with partial success
Browse files Browse the repository at this point in the history
  • Loading branch information
oetiker committed Jul 15, 2021
1 parent a964cbd commit 1d7b0eb
Show file tree
Hide file tree
Showing 17 changed files with 480 additions and 342 deletions.
13 changes: 12 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
0.5.3 2021-07-14 17:03:36 +0200 Tobias Oetiker <[email protected]>
0.5.5 2021-07-15 18:24:10 +0200 Tobias Oetiker <[email protected]>

- more german
- multibooking with partial success in bestEffort mode

0.5.4 2021-07-15 13:26:30 +0200 Tobias Oetiker <[email protected]>

- more translations
- fixed calculation of total daily reservation time
- fix biweekly booking

0.5.3 2021-07-14 17:03:36 +0200 Tobias Oetiker <[email protected]>
- set reply-to to Reply-To: [email protected]
- fixed validation rules for non privileged booker
- fixed equipment list in notification email
- fixed sorting order for booking list
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
0.5.3
0.5.5

2 changes: 1 addition & 1 deletion lib/Kuickres/GuiPlugin/Booking.pm
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ sub getTableData {
strftime('%H:%M',booking_start_ts+booking_duration_s,'unixepoch','localtime') AS booking_time",
\'booking_create_ts * 1000 AS booking_create_ts',
( $adm ? (
'booking_delete_ts * 1000 AS booking_delete_ts' ): ()
\'booking_delete_ts * 1000 AS booking_delete_ts' ): ()
),
],
$self->WHERE($args),
Expand Down
4 changes: 2 additions & 2 deletions lib/Kuickres/GuiPlugin/BookingForm.pm
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ has formCfg => sub {
) };
# $self->log->debug($@) if $@;
# $self->log->debug("T:",dumper($t));
$self->log->debug("F:",dumper($form));
#$self->log->debug("F:",dumper($form));
my @equipment;
my $eqHash = $self->getEqHash($form->{booking_cbuser},$form->{booking_room});

Expand Down Expand Up @@ -397,7 +397,7 @@ has actionCfg => sub {
action => 'showMessage',
title => trm("Booking Problem"),
html => true,
message => trm("Your booking overlaps with BookingIds: %1",join(", ",@$overlaps))
message => trm("Your booking overlaps with BookingIds: %1",join(", ", @{$overlaps->{desc_array}}))
}
}

Expand Down
67 changes: 46 additions & 21 deletions lib/Kuickres/GuiPlugin/MultiBookingForm.pm
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ user: [email protected]
mobile: mobile_number
SAMPLE_RULE
$self->db->select('district','*',undef,{order_by => 'district_id'})->hashes->map(sub {
$rule .= "# districtId: $_->{district_id} # $_->{district_name}\n"
$rule .= "#districtId: $_->{district_id} # $_->{district_name}\n"
});
$self->db->select('agegroup','*',undef,{order_by => 'agegroup_id'})->hashes->map(sub {
$rule .= "# agegroupId: $_->{agegroup_id} # $_->{agegroup_name}\n"
$rule .= "#agegroupId: $_->{agegroup_id} # $_->{agegroup_name}\n"
});
$rule .= <<'SAMPLE_RULE';
school: school_house
Expand All @@ -72,15 +72,13 @@ SAMPLE_RULE
}
$rule .= <<'SAMPLE_RULE';
interval: weekly
# interval: biweekly
#interval: biweekly
day:
# - mon
# - tue
# - wed
# - thu
# - fri
# - sat
# - sun
startTime: 15:00
endTime: 17:00
SAMPLE_RULE
Expand Down Expand Up @@ -239,8 +237,8 @@ has formCfg => sub ($self) {
return trm("Must be a number")
if $value !~ /^\d+$/;

return trm("Must be after the start")
if $value < $form->{mbooking_start_ts};
return trm("End Data must be after the Start Date")
if $value <= $form->{mbooking_start_ts};
return;
}
},
Expand Down Expand Up @@ -269,6 +267,7 @@ has actionCfg => sub {
my $handler = sub {
my $self = shift;
my $args = shift;
$self->log->debug(dumper \@_);
my %metaInfo;
my $db = $self->db;
my $tx = $db->begin;
Expand Down Expand Up @@ -306,12 +305,12 @@ has actionCfg => sub {
);

my $message;
my $good = "The following reservations have been created: <ul>".join("\n",map {"<li>$_</li>"} @$success)."</ul>";
my $bad = "The following reservations could not be created: <ul>";
my $good = "Die folgenden Reservationen konnte erzeugt werden: <ul>".join("\n",map {"<li>$_</li>"} @$success)."</ul>";
my $bad = "Die folgenden Reservationen konnten nicht erzeugt werden: <ul>";

for my $prob (@$problems) {
$bad .= "<li>$prob->{key}<ul>"
. join("\n",map {"<li>Overlapping $_</li>"} @{$prob->{overlaps}})
. join("\n",map {"<li>Überlappend $_</li>"} @{$prob->{overlaps}})
. join("\n",map {"<li>$_</li>"} @{$prob->{issues}})
."</ul></li>";
}
Expand All @@ -328,18 +327,18 @@ has actionCfg => sub {
return {
action => 'showMessage',
html => true,
message => "No reservations have been created. ".$bad."<div>If you set the <b>bestEffort</b> flag, the following reservations could be created: <ul>".join("\n",map {"<li>$_</li>"} @$success)."</ul>",
title => "Problems",
message => "Keine Reservationen wurden erzeugt. ".$bad."<div>Wenn Du das <b>bestEffort</b> Flag auf 'true' setzt, dann könnten folgende Reservationen erzeugt werden: <ul>".join("\n",map {"<li>$_</li>"} @$success)."</ul>",
title => "Probleme",
}
}
}
else {
return {
action => 'showMessage',
html => true,
title => "Problems",
title => "Probleme",
width => '600',
message => "No reservations have been created. ".$bad
message => "Keine Reservationen wurden erzeugt. ".$bad
};
}
my $rule = $args->{mbooking_rule};
Expand Down Expand Up @@ -481,7 +480,8 @@ sub multiBook ($self,$recId,$room,$start,$end,$rule) {
my @success;
while ($now->epoch < $end->epoch){
if ($dayFilter->{$now->day_of_week}) {
$self->log->debug("working on ".$now->strftime);
my @currentEq = @equipment;
#$self->log->debug("working on ".$now->strftime);
my @conflict;
my $start = $now+$startTime;
my $end = $now+$endTime;
Expand All @@ -493,9 +493,26 @@ sub multiBook ($self,$recId,$room,$start,$end,$rule) {
$start->epoch,
$end->epoch,
$room,
\@equipment
\@currentEq
);
#$self->log->debug("FULL ", dumper \@args);
my $overlaps = $self->getBookings(@args);
my @overlapEq;
if ($overlaps) {
@overlapEq = map { ($overlaps->{eq_hash}{$_} or exists $overlaps->{eq_hash}{0})
? ($overlaps->{eq_hash}{$_}) : () } @equipment;

@currentEq = grep {not $overlaps->{eq_hash}{$_} } @equipment;

if (@currentEq) {
#$self->log->debug("CLEAN " ,dumper \@args,\@currentEq,$overlaps);
if ($self->getBookings(@args)){
die mkerror(8474,trm("Internal error with eq removal"));
}
# we can book some equipment it seems
$overlaps = undef;
};
}
my $issues = $self->checkResourceAllocations(@args);

if (not $overlaps and not $issues) {
Expand All @@ -510,22 +527,30 @@ sub multiBook ($self,$recId,$room,$start,$end,$rule) {
booking_agegroup => $rule->{agegroupId},
booking_district => $rule->{districtId},
booking_school => $rule->{school},
booking_equipment_json => to_json(\@equipment),
booking_equipment_json => to_json(\@currentEq),
})->last_insert_id;
push @success, "$id - $key";
push @success, "$id - $key"
. (@overlapEq ? " ".trm("(Ohne: %1!)",join(', ',@overlapEq)) : '');
if(@overlapEq) {
push @problem, {
key => $key,
overlaps => [],
issues => [(@overlapEq ? trm("Ohne: %1!",join(', ',@overlapEq)) : '')],
};
}
}
else {
push @problem, {
key => $key,
overlaps => $overlaps,
overlaps => $overlaps->{desc_array},
issues => $issues,
};
}
}
$now = ($now+36*3600)->truncate(to => 'day');
if ($rule->{interval} eq 'biweekly'
and ($now-$week_start)->days > 6) {
$now = ($now + 6*24*3600 + 36*3600)->truncate(to => 'day');
and ($now-$week_start)->days >=7) {
$week_start = $now = ($now + 6*24*3600 + 36*3600)->truncate(to => 'day');
}

}
Expand Down
8 changes: 5 additions & 3 deletions lib/Kuickres/GuiPlugin/User.pm
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,11 @@ sub getTableData {
edit => {
enabled => true
},
delete => {
enabled => true,
},
$self->user->may('admin') ? (
delete => {
enabled => true,
},
) : ()
}
}
return $data;
Expand Down
13 changes: 6 additions & 7 deletions lib/Kuickres/GuiPlugin/UserForm.pm
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,13 @@ has actionCfg => sub {

$args->{cbuser_password} = hmac_sha1_sum($args->{cbuser_password});
my $db = $self->db;

$args->{cbuser_id} = $db->updateOrInsertData('cbuser',{
map { $_ => $args->{'cbuser_'.$_} } @fields
},$args->{cbuser_id} ? { id => int($args->{cbuser_id}) } : ());


my $adminId = $db->fetchValue('cbright',{key=>'admin'},'id');
if (@fields) {
$args->{cbuser_id} = $db->updateOrInsertData('cbuser',{
map { $_ => $args->{'cbuser_'.$_} } @fields
},$args->{cbuser_id} ? { id => int($args->{cbuser_id}) } : ());
}
if ($admin){
my $adminId = $db->fetchValue('cbright',{key=>'admin'},'id');
for (keys %$args){
next if not /^cbright_id_(\d+)$/;
my $right_id = $1;
Expand Down
47 changes: 32 additions & 15 deletions lib/Kuickres/Role/BookingHelper.pm
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ sub getEqHash ( $self, $user_id,$room_id ) {
$hash{ $rec->{equipment_id} } = true;
}
);
$self->log->debug('EQHASH',$user_id,$room_id,dumper \%hash);
#$self->log->debug('EQHASH',$user_id,$room_id,dumper \%hash);
return \%hash;
}

Expand All @@ -89,10 +89,14 @@ sub checkResourceAllocations ( $self, $user, $start,
# eq check
my $db = $self->db;

if ($start >= $end) {
push @issues, trm("Startzeit muss vor Endzeit sein");
}

#### check opening hours
if (not $self->isRoomOpen( $room, $start, $end ) ) {
push @issues, trm(
"Room not open for reservation %1 - %2",
"Raum ist nicht verfügbar für Reservationen von %1 - %2",
localtime($start)->strftime("%H:%M"),
localtime($end)->strftime("%H:%M"),
);
Expand All @@ -115,13 +119,13 @@ sub checkResourceAllocations ( $self, $user, $start,
sub ($rec) {
my $eqId = $rec->{equipment_id};
if (not $eqUserHash->{$eqId} and not $self->user->may('admin')) {
push @issues, trm( "User has no permission to book equipment %1", $rec->{equipment_name});
push @issues, trm( "Benutzer hat keine erlaubniss die Anlage %1 zu buchen.", $rec->{equipment_name});
}

if ($end < $rec->{equipment_start_ts}
or $start > $rec->{equipment_end_ts}) {
push @issues, trm(
"Equipment '%1' not available in the chosen periode",
"Anlage %1 ist im gewünschten Zeitraum nicht verfügbar.",
$rec->{equipment_name}
)
}
Expand All @@ -137,35 +141,45 @@ sub checkResourceAllocations ( $self, $user, $start,

if ($eqp > $rules->{maxEquipmentPointsPerBooking}) {
push @issues,
trm("More than %1 equipment points (%2) spent in a single reservation",
trm("Mehr als %1 Anlage Punkte (%2) in einer einzelnen Reservation",
$rules->{maxEquipmentPointsPerBooking},$eqp
);
};

if ($end > time + $rules->{futureBookingDays} * 24 * 3600) {
push @issues, trm("Booking more lies more than %1 days in the future",
push @issues, trm("Buchung liegt mehr als %1 Tage in der Zukunft",
$rules->{futureBookingDays}
);
}

my $duration = $end - $start;
my $day_start = localtime($start)->truncate(to=>'day');
my $day_end = ($day_start + 36*3600)->truncate(to=>'day');
#$self->log->debug("exclude:",$exclude);
#$self->log->debug("start:",$day_start->strftime);
#$self->log->debug("end:",$day_end->strftime);
my $bookings = $db->select(
'booking',
'*',
{
{ -and => [
booking_delete_ts => undef,
booking_cbuser => $user,
-bool => \["booking_start_ts > ?", {
type=> SQL_INTEGER, value => $day_start->epoch }],
-bool => \["booking_start_ts < ?", {
type=> SQL_INTEGER, value => $day_end->epoch }],
$exclude ? ( booking_id => { '!=' => $exclude } ) : (),
}
]}
)->hashes->map(
sub {
#$self->log->debug("MINE",dumper $_);
$duration += $_->{booking_duration_s};
}
);

if ($duration/3600 > $rules->{maxBookingHoursPerDay}) {
push @issues, trm(
"More than %1 hours (%2h) reserved in a single days",
"Mehr als %1 Stunden (%2h) reserviert in einem einzelnen Tag",
$rules->{maxBookingHoursPerDay},sprintf("%.1f",$duration/3600)
);
}
Expand Down Expand Up @@ -227,24 +241,27 @@ sub getBookings ( $self, $user, $start, $end, $room, $equipment,
]
}
)->hashes->map(sub ($rec){
my $key = 'ID:'.$rec->{booking_id} . ' '
my $desc = 'ID:'.$rec->{booking_id} . ' '
. localtime($rec->{booking_start_ts})->strftime("%H:%M")
. ' - '
. localtime($rec->{booking_start_ts}
+ $rec->{booking_duration_s})->strftime("%H:%M");
push @{$overlaps{ $key} } , $rec->{booking_equipment};
$overlaps{ $rec->{booking_id} }{desc} = $desc;
push @{$overlaps{ $rec->{booking_id} }{eq}} , $rec->{booking_equipment};
});
my @overlaps;
my %eqs;
for my $key (sort keys %overlaps) {
my @eqList;
$db->select('equipment', '*', {
equipment_id => { in => $overlaps{$key} } }
$db->select('equipment', '*', {
$overlaps{$key}{eq}[0] ne '0' ? (equipment_id => { in => $overlaps{$key}{eq} }):() }
)->hashes->map(sub ($rec) {
push @eqList, $rec->{equipment_name};
$eqs{$rec->{equipment_id}} = $rec->{equipment_name};
});
push @overlaps, $key. ((@eqList) ? ( ': ' . join(', ', @eqList)):'');
push @overlaps, $overlaps{$key}{desc}. ((@eqList) ? ( ': ' . join(', ', @eqList)):'');
}
return @overlaps ? \@overlaps : undef;
return @overlaps ? { desc_array => \@overlaps, eq_hash => \%eqs } : undef;
}

=head3 parseTime(date,from,to)
Expand Down
Loading

0 comments on commit 1d7b0eb

Please sign in to comment.