Bugzilla – Attachment 102926 Details for
Bug 15986
Add a script for sending hold waiting reminder notices
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 15986: Add a script for sending hold waiting reminder notices
Bug-15986-Add-a-script-for-sending-hold-waiting-re.patch (text/plain), 22.33 KB, created by
Nick Clemens (kidclamp)
on 2020-04-14 11:55:15 UTC
(
hide
)
Description:
Bug 15986: Add a script for sending hold waiting reminder notices
Filename:
MIME Type:
Creator:
Nick Clemens (kidclamp)
Created:
2020-04-14 11:55:15 UTC
Size:
22.33 KB
patch
obsolete
>From 260d981f8ad3d90745ea9376e5ff82cf987a53f5 Mon Sep 17 00:00:00 2001 >From: Nick Clemens <nick@bywatersolutions.com> >Date: Tue, 12 Jul 2016 13:16:45 -0400 >Subject: [PATCH] Bug 15986: Add a script for sending hold waiting reminder > notices > >This patch adds a script for sending holds reminder notice to patrons. > >We add a 'send_notice' routine to Koha::Patrons - this will either send using the patron's >email prefs, or allow forcing of a single method via the cron > >To test: > 1 - Create an email hold reminder notice for a single library (Koha module: Holds, code HOLDREMINDER, branch: CPL) > 2 - Set some waiting holds today for patrons at CPL, ensure those patrons have 'email' as the transport for hold filled notices > 3 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -n -li CPL > 4 - You should see the patrons here would have received emails > 5 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -li CPL > 6 - You should see the emails that were sent > 7 - Check the patron notices tab to confirm > 8 - Note a ptron with two holds waiting receives only one notice > 9 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -li CPL -days 3 >10 - No notices are sent >11 - Adjust the waiting date for the holds: > UPDATE reserves SET waitingdate=DATE_SUB(CURDATE(), INTERVAL 3 DAY) WHERE waitingdate = CURDATE(); >12 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -li CPL -days 3 >13 - Confirm the holds are now reminded >14 - Set yesterday as a holiday for CPL >15 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -n -li CPL -holidays -days 3 >16 - Notices should not be sent >17 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -n -li CPL -holidays -days 4 >18 - Notices should be sent again >19 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -n -holidays -days 4 >20 - Should get feedback that notice was not found for other libraries >21 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -n -holidays -days 4 -mtt sms >22 - Notice is not found >23 - Add the notice for sms >24 - perl misc/cronjobs/holds_reminder.pl -v -lettercode HOLDREMINDER -n -holidays -days 4 -mtt sms >25 - The notice should be sent >26 - Check patrons messaging tab to confirm >27 - prove -v t/db_dependent/Koha/Patrons.t > >Sponsored by: The Hotchkiss School (http://www.hotchkiss.org/) >--- > C4/Letters.pm | 2 +- > Koha/Patron.pm | 70 ++++++++++ > misc/cronjobs/holds_reminder.pl | 294 ++++++++++++++++++++++++++++++++++++++++ > t/db_dependent/Koha/Patrons.t | 95 ++++++++++++- > 4 files changed, 459 insertions(+), 2 deletions(-) > create mode 100755 misc/cronjobs/holds_reminder.pl > >diff --git a/C4/Letters.pm b/C4/Letters.pm >index 1036c461f3..cff68b2916 100644 >--- a/C4/Letters.pm >+++ b/C4/Letters.pm >@@ -47,7 +47,7 @@ BEGIN { > require Exporter; > @ISA = qw(Exporter); > @EXPORT = qw( >- &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes >+ &EnqueueLetter &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes > ); > } > >diff --git a/Koha/Patron.pm b/Koha/Patron.pm >index 9948f8c35c..ae3ef46214 100644 >--- a/Koha/Patron.pm >+++ b/Koha/Patron.pm >@@ -29,6 +29,7 @@ use C4::Context; > use C4::Log; > use Koha::Account; > use Koha::ArticleRequests; >+use C4::Letters qw( GetPreparedLetter EnqueueLetter ); > use Koha::AuthUtils; > use Koha::Checkouts; > use Koha::Club::Enrollments; >@@ -1694,6 +1695,75 @@ sub to_api_mapping { > }; > } > >+=head3 send_notice >+ >+ Koha::Patrons->send_notice({ letter_params => $letter_params, message_name => 'DUE'}); >+ Koha::Patrons->send_notice({ letter_params => $letter_params, message_transports => \@message_transports }); >+ Koha::Patrons->send_notice({ letter_params => $letter_params, message_transports => \@message_transports, test_mode => 1 }); >+ >+ Queue messages to a patron. Can pass a message that is part of the message_attributes >+ table or supply the transport to use. >+ >+ If passed a message name we retrieve the patrons preferences for transports >+ Otherwise we use the supplied transport. In the case of email or sms we fall back to print if >+ we have no address/number for sending >+ >+ $letter_params is a hashref of the values to be passed to GetPreparedLetter >+ >+ test_mode will only report which notices would be sent, but nothign will be queued >+ >+=cut >+ >+sub send_notice { >+ my ( $self, $params ) = @_; >+ my $letter_params = $params->{letter_params}; >+ my $test_mode = $params->{test_mode}; >+ >+ return unless $letter_params; >+ return unless exists $params->{message_name} xor $params->{message_transports}; # We only want one of these >+ >+ my $library = Koha::Libraries->find( $letter_params->{branchcode} )->unblessed; >+ my $admin_email_address = $library->{branchemail} || C4::Context->preference('KohaAdminEmailAddress'); >+ >+ my @message_transports; >+ my $letter_code; >+ $letter_code = $letter_params->{letter_code}; >+ if( $params->{message_name} ){ >+ my $messaging_prefs = C4::Members::Messaging::GetMessagingPreferences( { >+ borrowernumber => $letter_params->{borrowernumber}, >+ message_name => $params->{message_name} >+ } ); >+ @message_transports = ( keys %{ $messaging_prefs->{transports} } ); >+ $letter_code = $messaging_prefs->{transports}->{$message_transports[0]} unless $letter_code; >+ } else { >+ @message_transports = @{$params->{message_transports}}; >+ } >+ return unless defined $letter_code; >+ $letter_params->{letter_code} = $letter_code; >+ my $print_sent = 0; >+ my %return; >+ foreach my $mtt (@message_transports){ >+ next if ($mtt eq 'phone' and C4::Context->preference('TalkingTechItivaPhoneNotification') ); >+ # Phone notices are handled by TalkingTech_itiva_outbound.pl >+ if( ($mtt eq 'email' and not $self->notice_email_address) or ($mtt eq 'sms' and not $self->smsalertnumber) ){ >+ push @{$return{fallback}}, $mtt; >+ $mtt = 'print'; >+ } >+ next if $mtt eq 'print' && $print_sent; >+ $letter_params->{message_transport_type} = $mtt; >+ my $letter = C4::Letters::GetPreparedLetter( %$letter_params ); >+ C4::Letters::EnqueueLetter({ >+ letter => $letter, >+ borrowernumber => $self->borrowernumber, >+ from_address => $admin_email_address, >+ message_transport_type => $mtt >+ }) unless $test_mode; >+ push @{$return{sent}}, $mtt; >+ $print_sent = 1 if $mtt eq 'print'; >+ } >+ return \%return; >+} >+ > =head2 Internal methods > > =head3 _type >diff --git a/misc/cronjobs/holds_reminder.pl b/misc/cronjobs/holds_reminder.pl >new file mode 100755 >index 0000000000..c409e405b3 >--- /dev/null >+++ b/misc/cronjobs/holds_reminder.pl >@@ -0,0 +1,294 @@ >+#!/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; >+ >+BEGIN { >+ >+ # find Koha's Perl modules >+ # test carefully before changing this >+ use FindBin; >+ eval { require "$FindBin::Bin/../kohalib.pl" }; >+} >+ >+use Getopt::Long; >+use Pod::Usage; >+use Text::CSV_XS; >+use DateTime; >+use DateTime::Duration; >+ >+use C4::Context; >+use C4::Letters; >+use C4::Log; >+use Koha::DateUtils; >+use Koha::Calendar; >+use Koha::Libraries; >+use Koha::Script -cron; >+ >+=head1 NAME >+ >+holds_reminder.pl - prepare reminder messages to be sent to patrons with waiting holds >+ >+=head1 SYNOPSIS >+ >+holds_reminder.pl >+ [ -n ][ -library <branchcode> ][ -library <branchcode> ... ] >+ [ -days <number of days> ][ -csv [<filename>] ][ -itemscontent <field list> ] >+ [ -email <email_type> ... ] >+ >+ Options: >+ -help brief help message >+ -man full documentation >+ -v verbose >+ -n No email will be sent >+ -days <days> days waiting to deal with >+ -lettercode <lettercode> predefined notice to use >+ -library <branchname> only deal with holds from this library (repeatable : several libraries can be given) >+ -holidays use the calendar to not count holidays as waiting days >+ -mtt <message_transport_type> type of messages to send, default is to use patrons messaging preferences for Hold filled >+ populating this will force send even if patron has not chosen to receive hold notices >+ email and sms will fallback to print if borrower does not have an address/phone >+ -date Send notices as would have been sent on a specific date >+ >+=head1 OPTIONS >+ >+=over 8 >+ >+=item B<-help> >+ >+Print a brief help message and exits. >+ >+=item B<-man> >+ >+Prints the manual page and exits. >+ >+=item B<-v> >+ >+Verbose. Without this flag set, only fatal errors are reported. >+ >+=item B<-n> >+ >+Do not send any email (test-mode) . If verbose a list of notices that would have been sent to >+the patrons are printed to standard out. >+ >+=item B<-days> >+ >+Optional parameter, number of days an items has been 'waiting' on hold >+to send a message for. If not included a notice will be sent to all >+patrons with waiting holds. >+ >+=item B<-library> >+ >+select notices for one specific library. Use the value in the >+branches.branchcode table. This option can be repeated in order >+to select notices for a group of libraries. >+ >+=item B<-holidays> >+ >+This option determines whether library holidays are used when calculating how >+long an item has been waiting. If enabled the count will skip closed days. >+ >+=item B<-date> >+ >+use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD. >+ >+=item B<-mtt> >+ >+send a notices via a specific transport, this can be repeated to send various notices. >+If omitted the patron's messaging preferences for Hold notices will be used. >+If supplied the notice types will be force sent even if patron has not selected hold notices >+Email and SMS will fall back to print if there is no valid info in the patron's account >+ >+ >+=back >+ >+=head1 DESCRIPTION >+ >+This script is designed to alert patrons of waiting >+holds. >+ >+=head2 Configuration >+ >+This script sends reminders to patrons with waiting holds using a notice >+defined in the Tools->Notices & slips module within Koha. The lettercode >+is passed into this script and, along with other options, determine the content >+of the notices sent to patrons. >+ >+ >+=head1 USAGE EXAMPLES >+ >+C<holds_reminder.pl> - With no arguments the simple help is printed >+ >+C<holds_reminder.pl -lettercode CODE > In this most basic usage all >+libraries are processed individually, and notices are prepared for >+all patrons with waiting holds for whom we have email addresses. >+Messages for those patrons for whom we have no email >+address are sent in a single attachment to the library administrator's >+email address, or to the address in the KohaAdminEmailAddress system >+preference. >+ >+C<holds_reminder.pl -lettercode CODE -n -csv /tmp/holds_reminder.csv> - sends no email and >+populates F</tmp/holds_reminder.csv> with information about all waiting holds >+items. >+ >+C<holds_reminder.pl -lettercode CODE -library MAIN -days 14> - prepare notices of >+holds waiting for 2 weeks for the MAIN library. >+ >+C<holds_reminder.pl -library MAIN -days 14 -list-all> - prepare notices >+of holds waiting for 2 weeks for the MAIN library and include all the >+patron's waiting hold >+ >+=cut >+ >+# These variables are set by command line options. >+# They are initially set to default values. >+my $dbh = C4::Context->dbh(); >+my $help = 0; >+my $man = 0; >+my $verbose = 0; >+my $nomail = 0; >+my $days ; >+my $lettercode; >+my @branchcodes; # Branch(es) passed as parameter >+my $use_calendar = 0; >+my $date_input; >+my $opt_out = 0; >+my @mtts; >+ >+GetOptions( >+ 'help|?' => \$help, >+ 'man' => \$man, >+ 'v' => \$verbose, >+ 'n' => \$nomail, >+ 'days=s' => \$days, >+ 'lettercode=s' => \$lettercode, >+ 'library=s' => \@branchcodes, >+ 'date=s' => \$date_input, >+ 'holidays' => \$use_calendar, >+ 'mtt=s' => \@mtts >+); >+pod2usage(1) if $help; >+pod2usage( -verbose => 2 ) if $man; >+ >+if ( !$lettercode ) { >+ pod2usage({ >+ -exitval => 1, >+ -msg => qq{\nError: You must specify a lettercode to send reminders.\n}, >+ }); >+} >+ >+ >+cronlogaction(); >+ >+# Unless a delay is specified by the user we target all waiting holds >+unless (defined $days) { >+ $days=0; >+} >+ >+# Unless one ore more branchcodes are passed we use all the branches >+if (scalar @branchcodes > 0) { >+ my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch'; >+ $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n"; >+} >+else { >+ @branchcodes = Koha::Libraries->search()->get_column('branchcode'); >+} >+ >+# If provided we run the report as if it had run on a specified date >+my $date_to_run; >+if ( $date_input ){ >+ eval { >+ $date_to_run = dt_from_string( $date_input, 'iso' ); >+ }; >+ die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD." >+ if $@ or not $date_to_run; >+} >+else { >+ $date_to_run = dt_from_string(); >+} >+ >+# Loop through each branch >+foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP >+ # Check that this branch has the letter code specified or skip this branch >+ my $letter = C4::Letters::getletter( 'reserves', $lettercode , $branchcode ); >+ unless ($letter) { >+ $verbose and print qq|Message '$lettercode' content not found for $branchcode\n|; >+ next; >+ } >+ >+ # If respecting calendar get the correct waiting since date >+ my $waiting_date; >+ if( $use_calendar ){ >+ my $calendar = Koha::Calendar->new( branchcode => $branchcode ); >+ my $duration = DateTime::Duration->new( days => -$days ); >+ $waiting_date = $calendar->addDays($date_to_run,$duration); #Add negative of days >+ } else { >+ $waiting_date = $date_to_run->subtract( days => $days ); >+ } >+ >+ #Â Find all the holds waiting since this date for the current branch >+ my $dtf = Koha::Database->new->schema->storage->datetime_parser; >+ my $waiting_since = $dtf->format_date( $waiting_date ); >+ my $reserves = Koha::Holds->search({ >+ waitingdate => {'<=' => $waiting_since }, >+ branchcode => $branchcode, >+ }); >+ >+ $verbose and warn "No reserves found for $branchcode\n" unless $reserves->count; >+ next unless $reserves->count; >+ $verbose and warn $reserves->count . " reserves waiting since $waiting_since for $branchcode\n"; >+ >+ # We only want to send one notice per patron per branch - this variable will hold the completed borrowers >+ my %done; >+ >+ # If passed message transports we force use those, otherwise we will use the patrons preferences >+ # for the 'Hold_Filled' notice >+ my $sending_params = @mtts ? { message_transports => \@mtts } : { message_name => "Hold_Filled" }; >+ >+ >+ while ( my $reserve = $reserves->next ) { >+ >+ my $patron = $reserve->borrower; >+ # Skip if we already dealt with this borrower >+ next if ( $done{$patron->borrowernumber} ); >+ $verbose and print " borrower " . $patron->surname . ", " . $patron->firstname . " has holds triggering notice.\n"; >+ >+ # Setup the notice information >+ my $letter_params = { >+ module => 'reserves', >+ letter_code => $lettercode, >+ borrowernumber => $patron->borrowernumber, >+ branchcode => $branchcode, >+ tables => { >+ borrowers => $patron->borrowernumber, >+ branches => $reserve->branchcode, >+ reserves => $reserve->unblessed >+ }, >+ }; >+ $sending_params->{letter_params} = $letter_params; >+ $sending_params->{test_mode} = $nomail; >+ my $result_text = $nomail ? "would have been sent" : "was sent"; >+ # send_notice queues the notices, falling back to print for email or SMS, and ignores phone (they are handled by Itiva) >+ my $result = $patron->send_notice( $sending_params ); >+ $verbose and print " borrower " . $patron->surname . ", " . $patron->firstname . " $result_text notices via: @{$result->{sent}}\n" if defined $result->{sent}; >+ $verbose and print " borrower " . $patron->surname . ", " . $patron->firstname . " $result_text print fallback for: @{$result->{fallback}}\n" if defined $result->{fallback}; >+ # Mark this borrower as completed >+ $done{$patron->borrowernumber} = 1; >+ } >+ >+ >+} #END BRANCH LOOP >diff --git a/t/db_dependent/Koha/Patrons.t b/t/db_dependent/Koha/Patrons.t >index d4582fcbd9..6e81c695cc 100644 >--- a/t/db_dependent/Koha/Patrons.t >+++ b/t/db_dependent/Koha/Patrons.t >@@ -19,7 +19,7 @@ > > use Modern::Perl; > >-use Test::More tests => 40; >+use Test::More tests => 41; > use Test::Warn; > use Test::Exception; > use Test::MockModule; >@@ -44,6 +44,7 @@ use Koha::Patron::Relationship; > use Koha::Database; > use Koha::DateUtils; > use Koha::Virtualshelves; >+use Koha::Notice::Messages; > > use t::lib::TestBuilder; > use t::lib::Mocks; >@@ -2018,6 +2019,98 @@ subtest 'anonymize' => sub { > $patron2->discard_changes; # refresh > is( $patron2->firstname, undef, 'First name patron2 cleared' ); > }; >+ >+subtest 'send_notice' => sub { >+ plan tests => 11; >+ >+ my $dbh = C4::Context->dbh; >+ t::lib::Mocks::mock_preference( 'AutoEmailPrimaryAddress', 'email' ); >+ my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); >+ my $branch = $builder->build_object( { class => 'Koha::Libraries' } ); >+ my $letter_e = $builder->build_object( { >+ class => 'Koha::Notice::Templates', >+ value => { >+ branchcode => $branch->branchcode, >+ message_transport_type => 'email', >+ lang => 'default' >+ } >+ }); >+ my $letter_p = $builder->build_object( { >+ class => 'Koha::Notice::Templates', >+ value => { >+ code => $letter_e->code, >+ module => $letter_e->module, >+ branchcode => $branch->branchcode, >+ message_transport_type => 'print', >+ lang => 'default' >+ } >+ }); >+ my $letter_s = $builder->build_object( { >+ class => 'Koha::Notice::Templates', >+ value => { >+ code => $letter_e->code, >+ module => $letter_e->module, >+ branchcode => $branch->branchcode, >+ message_transport_type => 'sms', >+ lang => 'default' >+ } >+ }); >+ >+ my $letter_params = { >+ letter_code => $letter_e->code, >+ branchcode => $letter_e->branchcode, >+ module => $letter_e->module, >+ borrowernumber => $patron->borrowernumber, >+ tables => { >+ borrowers => $patron->borrowernumber, >+ } >+ }; >+ my @mtts = ('email'); >+ >+ is( $patron->send_notice(), undef, "Nothing is done if no params passed"); >+ is( $patron->send_notice({ letter_params => $letter_params }),undef, "Nothing done if only letter"); >+ is_deeply( >+ $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }), >+ {sent => ['email'] }, "Email sent" >+ ); >+ $patron->email("")->store; >+ is_deeply( >+ $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }), >+ {sent => ['print'],fallback => ['email']}, "Email fallsback to print if no email" >+ ); >+ push @mtts, 'sms'; >+ is_deeply( >+ $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }), >+ {sent => ['print','sms'],fallback => ['email']}, "Email fallsback to print if no email, sms sent" >+ ); >+ $patron->smsalertnumber("")->store; >+ my $counter = Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count; >+ is_deeply( >+ $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }), >+ {sent => ['print'],fallback => ['email','sms']}, "Email fallsback to print if no emai, sms fallsback to print if no sms, only one print sent" >+ ); >+ is( Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count, $counter+1,"Count of queued notices went up by one"); >+ >+ # Enable notification for Hold_Filled - Things are hardcoded here but should work with default data >+ $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ?)|, undef, $patron->borrowernumber, 4 ); >+ my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef); >+ $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' ); >+ >+ is( $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts, message_name => 'Hold_Filled' }),undef, "Nothing done if transports and name sent"); >+ >+ $patron->email(q|awesome@ismymiddle.name|)->store; >+ is_deeply( >+ $patron->send_notice({ letter_params => $letter_params, message_name => 'Hold_Filled' }), >+ {sent => ['email'] }, "Email sent when using borrower preferences" >+ ); >+ $counter = Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count; >+ is_deeply( >+ $patron->send_notice({ letter_params => $letter_params, message_name => 'Hold_Filled', test_mode => 1 }), >+ {sent => ['email'] }, "Report that email sent when using borrower preferences in test_mode" >+ ); >+ is( Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count, $counter,"Count of queued notices not increased in test mode"); >+}; >+ > $schema->storage->txn_rollback; > > subtest 'extended_attributes' => sub { >-- >2.11.0
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 15986
:
53323
|
53354
|
53594
|
55723
|
91767
|
91768
|
99919
|
102723
|
102926
|
106962
|
106963
|
106964
|
112999
|
113000
|
113001
|
118733
|
118734
|
118735
|
118736
|
118737
|
119277
|
119308
|
119327
|
119328
|
119329
|
119383
|
119384
|
119386
|
119624
|
119725
|
119748
|
120224