Bug 28960: Explicitly call get_column
[koha.git] / admin / smart-rules.pl
1 #!/usr/bin/perl
2 # Copyright 2000-2002 Katipo Communications
3 # copyright 2010 BibLibre
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21 use CGI qw ( -utf8 );
22 use C4::Context;
23 use C4::Output;
24 use C4::Auth;
25 use C4::Koha;
26 use C4::Debug;
27 use Koha::DateUtils;
28 use Koha::Database;
29 use Koha::IssuingRule;
30 use Koha::IssuingRules;
31 use Koha::Logger;
32 use Koha::RefundLostItemFeeRules;
33 use Koha::Libraries;
34 use Koha::CirculationRules;
35 use Koha::Patron::Categories;
36 use Koha::Caches;
37 use Koha::Patrons;
38
39 my $input = CGI->new;
40 my $dbh = C4::Context->dbh;
41
42 # my $flagsrequired;
43 # $flagsrequired->{circulation}=1;
44 my ($template, $loggedinuser, $cookie)
45     = get_template_and_user({template_name => "admin/smart-rules.tt",
46                             query => $input,
47                             type => "intranet",
48                             flagsrequired => {parameters => 'manage_circ_rules'},
49                             debug => 1,
50                             });
51
52 my $type=$input->param('type');
53
54 my $branch = $input->param('branch');
55 unless ( $branch ) {
56     if ( C4::Context->preference('DefaultToLoggedInLibraryCircRules') ) {
57         $branch = Koha::Libraries->search->count() == 1 ? undef : C4::Context::mybranch();
58     }
59     else {
60         $branch = C4::Context::only_my_library() ? ( C4::Context::mybranch() || '*' ) : '*';
61     }
62 }
63
64 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
65
66 my $can_edit_from_any_library = $logged_in_patron->has_permission( {parameters => 'manage_circ_rules_from_any_libraries' } );
67 $template->param( restricted_to_own_library => not $can_edit_from_any_library );
68 $branch = C4::Context::mybranch() unless $can_edit_from_any_library;
69
70 $branch = '*' if $branch eq 'NO_LIBRARY_SET';
71
72 my $op = $input->param('op') || q{};
73 my $language = C4::Languages::getlanguage();
74
75 my $cache = Koha::Caches->get_instance;
76 $cache->clear_from_cache( Koha::IssuingRules::GUESSED_ITEMTYPES_KEY );
77
78 if ($op eq 'delete') {
79     my $itemtype     = $input->param('itemtype');
80     my $categorycode = $input->param('categorycode');
81     $debug and warn "deleting $1 $2 $branch";
82
83     Koha::IssuingRules->find({
84         branchcode   => $branch,
85         categorycode => $categorycode,
86         itemtype     => $itemtype
87     })->delete;
88
89 }
90 elsif ($op eq 'delete-branch-cat') {
91     my $categorycode  = $input->param('categorycode');
92     if ($branch eq "*") {
93         if ($categorycode eq "*") {
94              Koha::CirculationRules->set_rules(
95                  {
96                      categorycode => undef,
97                      branchcode   => undef,
98                      itemtype     => undef,
99                      rules        => {
100                          patron_maxissueqty             => undef,
101                          patron_maxonsiteissueqty       => undef,
102                          holdallowed             => undef,
103                          hold_fulfillment_policy => undef,
104                          returnbranch            => undef,
105                          max_holds               => undef,
106                      }
107                  }
108              );
109          } else {
110              Koha::CirculationRules->set_rules(
111                  {
112                      categorycode => $categorycode,
113                      branchcode   => undef,
114                      itemtype     => undef,
115                      rules        => {
116                          max_holds         => undef,
117                          patron_maxissueqty       => undef,
118                          patron_maxonsiteissueqty => undef,
119                      }
120                  }
121              );
122         }
123     } elsif ($categorycode eq "*") {
124         Koha::CirculationRules->set_rules(
125             {
126                 branchcode   => $branch,
127                 categorycode => undef,
128                 rules        => {
129                     max_holds                => undef,
130                     patron_maxissueqty       => undef,
131                     patron_maxonsiteissueqty => undef,
132                 }
133             }
134         );
135         Koha::CirculationRules->set_rules(
136             {
137                 branchcode   => $branch,
138                 itemtype     => undef,
139                 rules        => {
140                     holdallowed             => undef,
141                     hold_fulfillment_policy => undef,
142                     returnbranch            => undef,
143                 }
144             }
145         );
146     } else {
147         Koha::CirculationRules->set_rules(
148             {
149                 categorycode => $categorycode,
150                 branchcode   => $branch,
151                 itemtype     => undef,
152                 rules        => {
153                     max_holds         => undef,
154                     patron_maxissueqty       => undef,
155                     patron_maxonsiteissueqty => undef,
156                 }
157             }
158         );
159     }
160 }
161 elsif ($op eq 'delete-branch-item') {
162     my $itemtype  = $input->param('itemtype');
163     if ($branch eq "*") {
164         if ($itemtype eq "*") {
165             Koha::CirculationRules->set_rules(
166                 {
167                     categorycode => undef,
168                     branchcode   => undef,
169                     itemtype     => undef,
170                     rules        => {
171                         patron_maxissueqty             => undef,
172                         patron_maxonsiteissueqty       => undef,
173                         holdallowed             => undef,
174                         hold_fulfillment_policy => undef,
175                         returnbranch            => undef,
176                     }
177                 }
178             );
179         } else {
180             Koha::CirculationRules->set_rules(
181                 {
182                     categorycode => undef,
183                     branchcode   => undef,
184                     itemtype     => $itemtype,
185                     rules        => {
186                         holdallowed             => undef,
187                         hold_fulfillment_policy => undef,
188                         returnbranch            => undef,
189                     }
190                 }
191             );
192         }
193     } elsif ($itemtype eq "*") {
194         Koha::CirculationRules->set_rules(
195             {
196                 categorycode => undef,
197                 branchcode   => $branch,
198                 itemtype     => undef,
199                 rules        => {
200                     maxissueqty             => undef,
201                     maxonsiteissueqty       => undef,
202                     holdallowed             => undef,
203                     hold_fulfillment_policy => undef,
204                     returnbranch            => undef,
205                 }
206             }
207         );
208     } else {
209         Koha::CirculationRules->set_rules(
210             {
211                 categorycode => undef,
212                 branchcode   => $branch,
213                 itemtype     => $itemtype,
214                 rules        => {
215                     holdallowed             => undef,
216                     hold_fulfillment_policy => undef,
217                     returnbranch            => undef,
218                 }
219             }
220         );
221     }
222 }
223 # save the values entered
224 elsif ($op eq 'add') {
225     my $br = $branch; # branch
226     my $bor  = $input->param('categorycode'); # borrower category
227     my $itemtype  = $input->param('itemtype');     # item type
228     my $fine = $input->param('fine');
229     my $finedays     = $input->param('finedays');
230     my $maxsuspensiondays = $input->param('maxsuspensiondays');
231     $maxsuspensiondays = undef if $maxsuspensiondays eq q||;
232     my $suspension_chargeperiod = $input->param('suspension_chargeperiod') || 1;
233     my $firstremind  = $input->param('firstremind');
234     my $chargeperiod = $input->param('chargeperiod');
235     my $chargeperiod_charge_at = $input->param('chargeperiod_charge_at');
236     my $maxissueqty  = $input->param('maxissueqty');
237     my $maxonsiteissueqty  = $input->param('maxonsiteissueqty');
238     my $renewalsallowed  = $input->param('renewalsallowed');
239     my $renewalperiod    = $input->param('renewalperiod');
240     my $norenewalbefore  = $input->param('norenewalbefore');
241     $norenewalbefore = undef if $norenewalbefore =~ /^\s*$/;
242     my $auto_renew = $input->param('auto_renew') eq 'yes' ? 1 : 0;
243     my $no_auto_renewal_after = $input->param('no_auto_renewal_after');
244     $no_auto_renewal_after = undef if $no_auto_renewal_after =~ /^\s*$/;
245     my $no_auto_renewal_after_hard_limit = $input->param('no_auto_renewal_after_hard_limit') || undef;
246     $no_auto_renewal_after_hard_limit = eval { dt_from_string( $input->param('no_auto_renewal_after_hard_limit') ) } if ( $no_auto_renewal_after_hard_limit );
247     $no_auto_renewal_after_hard_limit = output_pref( { dt => $no_auto_renewal_after_hard_limit, dateonly => 1, dateformat => 'iso' } ) if ( $no_auto_renewal_after_hard_limit );
248     my $reservesallowed  = $input->param('reservesallowed');
249     my $holds_per_record = $input->param('holds_per_record');
250     my $holds_per_day    = $input->param('holds_per_day');
251     $holds_per_day =~ s/\s//g;
252     $holds_per_day = undef if $holds_per_day !~ /^\d+/;
253     my $onshelfholds     = $input->param('onshelfholds') || 0;
254     $maxissueqty =~ s/\s//g;
255     $maxissueqty = '' if $maxissueqty !~ /^\d+/;
256     $maxonsiteissueqty =~ s/\s//g;
257     $maxonsiteissueqty = '' if $maxonsiteissueqty !~ /^\d+/;
258     my $issuelength  = $input->param('issuelength');
259     $issuelength = $issuelength eq q{} ? undef : $issuelength;
260     my $lengthunit  = $input->param('lengthunit');
261     my $hardduedate = $input->param('hardduedate') || undef;
262     $hardduedate = eval { dt_from_string( $input->param('hardduedate') ) } if ( $hardduedate );
263     $hardduedate = output_pref( { dt => $hardduedate, dateonly => 1, dateformat => 'iso' } ) if ( $hardduedate );
264     my $hardduedatecompare = $input->param('hardduedatecompare');
265     my $rentaldiscount = $input->param('rentaldiscount');
266     my $opacitemholds = $input->param('opacitemholds') || 0;
267     my $article_requests = $input->param('article_requests') || 'no';
268     my $overduefinescap = $input->param('overduefinescap') || undef;
269     my $cap_fine_to_replacement_price = $input->param('cap_fine_to_replacement_price') eq 'on';
270     my $note = $input->param('note');
271     $debug and warn "Adding $br, $bor, $itemtype, $fine, $maxissueqty, $maxonsiteissueqty, $cap_fine_to_replacement_price";
272
273     my $params = {
274         branchcode                    => $br,
275         categorycode                  => $bor,
276         itemtype                      => $itemtype,
277         fine                          => $fine,
278         finedays                      => $finedays,
279         maxsuspensiondays             => $maxsuspensiondays,
280         suspension_chargeperiod       => $suspension_chargeperiod,
281         firstremind                   => $firstremind,
282         chargeperiod                  => $chargeperiod,
283         chargeperiod_charge_at        => $chargeperiod_charge_at,
284         renewalsallowed               => $renewalsallowed,
285         renewalperiod                 => $renewalperiod,
286         norenewalbefore               => $norenewalbefore,
287         auto_renew                    => $auto_renew,
288         no_auto_renewal_after         => $no_auto_renewal_after,
289         no_auto_renewal_after_hard_limit => $no_auto_renewal_after_hard_limit,
290         reservesallowed               => $reservesallowed,
291         holds_per_record              => $holds_per_record,
292         holds_per_day                 => $holds_per_day,
293         issuelength                   => $issuelength,
294         lengthunit                    => $lengthunit,
295         hardduedate                   => $hardduedate,
296         hardduedatecompare            => $hardduedatecompare,
297         rentaldiscount                => $rentaldiscount,
298         onshelfholds                  => $onshelfholds,
299         opacitemholds                 => $opacitemholds,
300         overduefinescap               => $overduefinescap,
301         cap_fine_to_replacement_price => $cap_fine_to_replacement_price,
302         article_requests              => $article_requests,
303         note                          => $note,
304     };
305
306     my $issuingrule = Koha::IssuingRules->find({categorycode => $bor, itemtype => $itemtype, branchcode => $br});
307     if ($issuingrule) {
308         $issuingrule->set($params)->store();
309     } else {
310         Koha::IssuingRule->new()->set($params)->store();
311     }
312     Koha::CirculationRules->set_rules(
313         {
314             categorycode => $bor,
315             itemtype     => $itemtype,
316             branchcode   => $br,
317             rules        => {
318                 maxissueqty       => $maxissueqty,
319                 maxonsiteissueqty => $maxonsiteissueqty,
320             }
321         }
322     );
323
324 }
325 elsif ($op eq "set-branch-defaults") {
326     my $categorycode  = $input->param('categorycode');
327     my $patron_maxissueqty   = $input->param('patron_maxissueqty');
328     my $patron_maxonsiteissueqty = $input->param('patron_maxonsiteissueqty');
329     my $holdallowed   = $input->param('holdallowed');
330     my $hold_fulfillment_policy = $input->param('hold_fulfillment_policy');
331     my $returnbranch  = $input->param('returnbranch');
332     my $max_holds = $input->param('max_holds');
333     $patron_maxissueqty =~ s/\s//g;
334     $patron_maxissueqty = '' if $patron_maxissueqty !~ /^\d+/;
335     $patron_maxonsiteissueqty =~ s/\s//g;
336     $patron_maxonsiteissueqty = '' if $patron_maxonsiteissueqty !~ /^\d+/;
337     $holdallowed =~ s/\s//g;
338     $holdallowed = undef if $holdallowed !~ /^\d+/;
339     $max_holds =~ s/\s//g;
340     $max_holds = '' if $max_holds !~ /^\d+/;
341
342     if ($branch eq "*") {
343         Koha::CirculationRules->set_rules(
344             {
345                 categorycode => undef,
346                 itemtype     => undef,
347                 branchcode   => undef,
348                 rules        => {
349                     patron_maxissueqty       => $patron_maxissueqty,
350                     patron_maxonsiteissueqty => $patron_maxonsiteissueqty,
351                     holdallowed              => $holdallowed,
352                     hold_fulfillment_policy  => $hold_fulfillment_policy,
353                     returnbranch             => $returnbranch,
354                 }
355             }
356         );
357     } else {
358         Koha::CirculationRules->set_rules(
359             {
360                 categorycode => undef,
361                 itemtype     => undef,
362                 branchcode   => $branch,
363                 rules        => {
364                     patron_maxissueqty       => $patron_maxissueqty,
365                     patron_maxonsiteissueqty => $patron_maxonsiteissueqty,
366                     holdallowed              => $holdallowed,
367                     hold_fulfillment_policy  => $hold_fulfillment_policy,
368                     returnbranch             => $returnbranch,
369                 }
370             }
371         );
372     }
373     Koha::CirculationRules->set_rule(
374         {
375             branchcode   => $branch,
376             categorycode => undef,
377             itemtype     => undef,
378             rule_name    => 'max_holds',
379             rule_value   => $max_holds,
380         }
381     );
382 }
383 elsif ($op eq "add-branch-cat") {
384     my $categorycode  = $input->param('categorycode');
385     my $patron_maxissueqty   = $input->param('patron_maxissueqty');
386     my $patron_maxonsiteissueqty = $input->param('patron_maxonsiteissueqty');
387     my $max_holds = $input->param('max_holds');
388     $patron_maxissueqty =~ s/\s//g;
389     $patron_maxissueqty = '' if $patron_maxissueqty !~ /^\d+/;
390     $patron_maxonsiteissueqty =~ s/\s//g;
391     $patron_maxonsiteissueqty = '' if $patron_maxonsiteissueqty !~ /^\d+/;
392     $max_holds =~ s/\s//g;
393     $max_holds = undef if $max_holds !~ /^\d+/;
394
395     if ($branch eq "*") {
396         if ($categorycode eq "*") {
397             Koha::CirculationRules->set_rules(
398                 {
399                     categorycode => undef,
400                     itemtype     => undef,
401                     branchcode   => undef,
402                     rules        => {
403                         max_holds         => $max_holds,
404                         patron_maxissueqty       => $patron_maxissueqty,
405                         patron_maxonsiteissueqty => $patron_maxonsiteissueqty,
406                     }
407                 }
408             );
409         } else {
410             Koha::CirculationRules->set_rules(
411                 {
412                     branchcode   => undef,
413                     categorycode => $categorycode,
414                     itemtype     => undef,
415                     rules        => {
416                         max_holds         => $max_holds,
417                         patron_maxissueqty       => $patron_maxissueqty,
418                         patron_maxonsiteissueqty => $patron_maxonsiteissueqty,
419                     }
420                 }
421             );
422         }
423     } elsif ($categorycode eq "*") {
424         Koha::CirculationRules->set_rules(
425             {
426                 categorycode => undef,
427                 itemtype     => undef,
428                 branchcode   => $branch,
429                 rules        => {
430                     max_holds         => $max_holds,
431                     patron_maxissueqty       => $patron_maxissueqty,
432                     patron_maxonsiteissueqty => $patron_maxonsiteissueqty,
433                 }
434             }
435         );
436     } else {
437         Koha::CirculationRules->set_rules(
438             {
439                 categorycode => $categorycode,
440                 itemtype     => undef,
441                 branchcode   => $branch,
442                 rules        => {
443                     max_holds         => $max_holds,
444                     patron_maxissueqty       => $patron_maxissueqty,
445                     patron_maxonsiteissueqty => $patron_maxonsiteissueqty,
446                 }
447             }
448         );
449     }
450 }
451 elsif ($op eq "add-branch-item") {
452     my $itemtype                = $input->param('itemtype');
453     my $holdallowed             = $input->param('holdallowed');
454     my $hold_fulfillment_policy = $input->param('hold_fulfillment_policy');
455     my $returnbranch            = $input->param('returnbranch');
456
457     $holdallowed =~ s/\s//g;
458     $holdallowed = undef if $holdallowed !~ /^\d+/;
459
460     if ($branch eq "*") {
461         if ($itemtype eq "*") {
462             Koha::CirculationRules->set_rules(
463                 {
464                     categorycode => undef,
465                     itemtype     => undef,
466                     branchcode   => undef,
467                     rules        => {
468                         holdallowed             => $holdallowed,
469                         hold_fulfillment_policy => $hold_fulfillment_policy,
470                         returnbranch            => $returnbranch,
471                     }
472                 }
473             );
474         } else {
475             Koha::CirculationRules->set_rules(
476                 {
477                     categorycode => undef,
478                     itemtype     => $itemtype,
479                     branchcode   => undef,
480                     rules        => {
481                         holdallowed             => $holdallowed,
482                         hold_fulfillment_policy => $hold_fulfillment_policy,
483                         returnbranch            => $returnbranch,
484                     }
485                 }
486             );
487         }
488     } elsif ($itemtype eq "*") {
489             Koha::CirculationRules->set_rules(
490                 {
491                     categorycode => undef,
492                     itemtype     => undef,
493                     branchcode   => $branch,
494                     rules        => {
495                         holdallowed             => $holdallowed,
496                         hold_fulfillment_policy => $hold_fulfillment_policy,
497                         returnbranch            => $returnbranch,
498                     }
499                 }
500             );
501     } else {
502         Koha::CirculationRules->set_rules(
503             {
504                 categorycode => undef,
505                 itemtype     => $itemtype,
506                 branchcode   => $branch,
507                 rules        => {
508                     holdallowed             => $holdallowed,
509                     hold_fulfillment_policy => $hold_fulfillment_policy,
510                     returnbranch            => $returnbranch,
511                 }
512             }
513         );
514     }
515 }
516 elsif ( $op eq 'mod-refund-lost-item-fee-rule' ) {
517
518     my $refund = $input->param('refund');
519
520     if ( $refund eq '*' ) {
521         if ( $branch ne '*' ) {
522             # only do something for $refund eq '*' if branch-specific
523             Koha::CirculationRules->set_rules(
524                 {
525                     categorycode => undef,
526                     itemtype     => undef,
527                     branchcode   => $branch,
528                     rules        => {
529                         refund => undef
530                     }
531                 }
532             );
533         }
534     } else {
535         Koha::CirculationRules->set_rules(
536             {
537                 categorycode => undef,
538                 itemtype     => undef,
539                 branchcode   => $branch,
540                 rules        => {
541                     refund => $refund
542                 }
543             }
544         );
545     }
546 }
547
548 my $refundLostItemFeeRule = Koha::RefundLostItemFeeRules->find({ branchcode => ($branch eq '*') ? undef:$branch });
549
550 $template->param(
551     refundLostItemFeeRule => $refundLostItemFeeRule,
552     defaultRefundRule     => Koha::RefundLostItemFeeRules->_default_rule
553 );
554
555 my $patron_categories = Koha::Patron::Categories->search({}, { order_by => ['description'] });
556
557 my @row_loop;
558 my $itemtypes = Koha::ItemTypes->search_with_localization;
559
560 my $sth2 = $dbh->prepare("
561     SELECT  issuingrules.*,
562             itemtypes.description AS humanitemtype,
563             categories.description AS humancategorycode,
564             COALESCE( localization.translation, itemtypes.description ) AS translated_description
565     FROM issuingrules
566     LEFT JOIN itemtypes
567         ON (itemtypes.itemtype = issuingrules.itemtype)
568     LEFT JOIN categories
569         ON (categories.categorycode = issuingrules.categorycode)
570     LEFT JOIN localization ON issuingrules.itemtype = localization.code
571         AND localization.entity = 'itemtypes'
572         AND localization.lang = ?
573     WHERE issuingrules.branchcode = ?
574 ");
575 $sth2->execute($language, $branch);
576
577 while (my $row = $sth2->fetchrow_hashref) {
578     $row->{'current_branch'} ||= $row->{'branchcode'};
579     $row->{humanitemtype} ||= $row->{itemtype};
580     $row->{default_translated_description} = 1 if $row->{humanitemtype} eq '*';
581     $row->{'humancategorycode'} ||= $row->{'categorycode'};
582     $row->{'default_humancategorycode'} = 1 if $row->{'humancategorycode'} eq '*';
583     $row->{'fine'} = sprintf('%.2f', $row->{'fine'});
584     if ($row->{'hardduedate'} && $row->{'hardduedate'} ne '0000-00-00') {
585        my $harddue_dt = eval { dt_from_string( $row->{'hardduedate'} ) };
586        $row->{'hardduedate'} = eval { output_pref( { dt => $harddue_dt, dateonly => 1 } ) } if ( $harddue_dt );
587        $row->{'hardduedatebefore'} = 1 if ($row->{'hardduedatecompare'} == -1);
588        $row->{'hardduedateexact'} = 1 if ($row->{'hardduedatecompare'} ==  0);
589        $row->{'hardduedateafter'} = 1 if ($row->{'hardduedatecompare'} ==  1);
590     } else {
591        $row->{'hardduedate'} = 0;
592     }
593     if ($row->{no_auto_renewal_after_hard_limit}) {
594        my $dt = eval { dt_from_string( $row->{no_auto_renewal_after_hard_limit} ) };
595        $row->{no_auto_renewal_after_hard_limit} = eval { output_pref( { dt => $dt, dateonly => 1 } ) } if $dt;
596     }
597
598     push @row_loop, $row;
599 }
600
601 my @sorted_row_loop = sort by_category_and_itemtype @row_loop;
602
603 $template->param(show_branch_cat_rule_form => 1);
604
605 $template->param(
606     patron_categories => $patron_categories,
607                         itemtypeloop => $itemtypes,
608                         rules => \@sorted_row_loop,
609                         humanbranch => ($branch ne '*' ? $branch : ''),
610                         current_branch => $branch,
611                         definedbranch => scalar(@sorted_row_loop)>0
612                         );
613 output_html_with_http_headers $input, $cookie, $template->output;
614
615 exit 0;
616
617 # sort by patron category, then item type, putting
618 # default entries at the bottom
619 sub by_category_and_itemtype {
620     unless (by_category($a, $b)) {
621         return by_itemtype($a, $b);
622     }
623 }
624
625 sub by_category {
626     my ($a, $b) = @_;
627     if ($a->{'default_humancategorycode'}) {
628         return ($b->{'default_humancategorycode'} ? 0 : 1);
629     } elsif ($b->{'default_humancategorycode'}) {
630         return -1;
631     } else {
632         return $a->{'humancategorycode'} cmp $b->{'humancategorycode'};
633     }
634 }
635
636 sub by_itemtype {
637     my ($a, $b) = @_;
638     if ($a->{default_translated_description}) {
639         return ($b->{'default_translated_description'} ? 0 : 1);
640     } elsif ($b->{'default_translated_description'}) {
641         return -1;
642     } else {
643         return lc $a->{'translated_description'} cmp lc $b->{'translated_description'};
644     }
645 }