Bug 23012: Apply processing fee return policy when lost item found
[koha.git] / C4 / Acquisition.pm
1 package C4::Acquisition;
2
3 # Copyright 2000-2002 Katipo Communications
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
21 use Modern::Perl;
22 use Carp qw( carp croak );
23 use Text::CSV_XS;
24 use C4::Context;
25 use C4::Suggestions qw( GetSuggestion GetSuggestionFromBiblionumber ModSuggestion );
26 use C4::Biblio qw( GetMarcFromKohaField GetMarcStructure IsMarcStructureInternal );
27 use C4::Contract qw( GetContract );
28 use C4::Log qw( logaction );
29 use C4::Templates qw(gettemplate);
30 use Koha::DateUtils qw( dt_from_string );
31 use Koha::Acquisition::Baskets;
32 use Koha::Acquisition::Booksellers;
33 use Koha::Acquisition::Invoices;
34 use Koha::Acquisition::Orders;
35 use Koha::Biblios;
36 use Koha::Exceptions;
37 use Koha::Items;
38 use Koha::Number::Price;
39 use Koha::Libraries;
40 use Koha::CsvProfiles;
41 use Koha::Patrons;
42
43 use C4::Koha;
44
45 use MARC::Field;
46 use JSON qw( to_json );
47
48
49 our (@ISA, @EXPORT_OK);
50 BEGIN {
51     require Exporter;
52     @ISA       = qw(Exporter);
53     @EXPORT_OK = qw(
54       GetBasket NewBasket ReopenBasket ModBasket
55       GetBasketAsCSV GetBasketGroupAsCSV
56       GetBasketsByBookseller GetBasketsByBasketgroup
57       GetBasketsInfosByBookseller
58
59       GetBasketUsers ModBasketUsers
60       CanUserManageBasket
61
62       ModBasketHeader
63
64       ModBasketgroup NewBasketgroup DelBasketgroup GetBasketgroup CloseBasketgroup
65       GetBasketgroups ReOpenBasketgroup
66
67       ModOrder GetOrder GetOrders GetOrdersByBiblionumber
68       GetOrderFromItemnumber
69       SearchOrders GetHistory GetRecentAcqui
70       ModReceiveOrder CancelReceipt
71       TransferOrder
72       ModItemOrder
73
74       GetParcels
75
76       GetInvoices
77       GetInvoice
78       GetInvoiceDetails
79       AddInvoice
80       ModInvoice
81       CloseInvoice
82       ReopenInvoice
83       DelInvoice
84       MergeInvoices
85
86       AddClaim
87       GetBiblioCountByBasketno
88
89       GetOrderUsers
90       ModOrderUsers
91       NotifyOrderUsers
92
93       FillWithDefaultValues
94
95       get_rounded_price
96       get_rounding_sql
97     );
98 }
99
100
101
102
103
104 sub GetOrderFromItemnumber {
105     my ($itemnumber) = @_;
106     my $dbh          = C4::Context->dbh;
107     my $query        = qq|
108
109     SELECT  * from aqorders    LEFT JOIN aqorders_items
110     ON (     aqorders.ordernumber = aqorders_items.ordernumber   )
111     WHERE itemnumber = ?  |;
112
113     my $sth = $dbh->prepare($query);
114
115 #    $sth->trace(3);
116
117     $sth->execute($itemnumber);
118
119     my $order = $sth->fetchrow_hashref;
120     return ( $order  );
121
122 }
123
124 =head1 NAME
125
126 C4::Acquisition - Koha functions for dealing with orders and acquisitions
127
128 =head1 SYNOPSIS
129
130 use C4::Acquisition;
131
132 =head1 DESCRIPTION
133
134 The functions in this module deal with acquisitions, managing book
135 orders, basket and parcels.
136
137 =head1 FUNCTIONS
138
139 =head2 FUNCTIONS ABOUT BASKETS
140
141 =head3 GetBasket
142
143   $aqbasket = &GetBasket($basketnumber);
144
145 get all basket informations in aqbasket for a given basket
146
147 B<returns:> informations for a given basket returned as a hashref.
148
149 =cut
150
151 sub GetBasket {
152     my ($basketno) = @_;
153     my $dbh        = C4::Context->dbh;
154     my $query = "
155         SELECT  aqbasket.*,
156                 concat( b.firstname,' ',b.surname) AS authorisedbyname
157         FROM    aqbasket
158         LEFT JOIN borrowers b ON aqbasket.authorisedby=b.borrowernumber
159         WHERE basketno=?
160     ";
161     my $sth=$dbh->prepare($query);
162     $sth->execute($basketno);
163     my $basket = $sth->fetchrow_hashref;
164     return ( $basket );
165 }
166
167 #------------------------------------------------------------#
168
169 =head3 NewBasket
170
171   $basket = &NewBasket( $booksellerid, $authorizedby, $basketname,
172       $basketnote, $basketbooksellernote, $basketcontractnumber, $deliveryplace, $billingplace, $is_standing, $create_items );
173
174 Create a new basket in aqbasket table
175
176 =over
177
178 =item C<$booksellerid> is a foreign key in the aqbasket table
179
180 =item C<$authorizedby> is the username of who created the basket
181
182 =back
183
184 The other parameters are optional, see ModBasketHeader for more info on them.
185
186 =cut
187
188 sub NewBasket {
189     my ( $booksellerid, $authorisedby, $basketname, $basketnote,
190         $basketbooksellernote, $basketcontractnumber, $deliveryplace,
191         $billingplace, $is_standing, $create_items ) = @_;
192     my $dbh = C4::Context->dbh;
193     my $query =
194         'INSERT INTO aqbasket (creationdate,booksellerid,authorisedby) '
195       . 'VALUES  (now(),?,?)';
196     $dbh->do( $query, {}, $booksellerid, $authorisedby );
197
198     my $basket = $dbh->{mysql_insertid};
199     $basketname           ||= q{}; # default to empty strings
200     $basketnote           ||= q{};
201     $basketbooksellernote ||= q{};
202
203     # Log the basket creation
204     if (C4::Context->preference("AcquisitionLog")) {
205         my $created = Koha::Acquisition::Baskets->find( $basket );
206         logaction(
207             'ACQUISITIONS',
208             'ADD_BASKET',
209             $basket,
210             to_json($created->unblessed)
211         );
212     }
213
214     ModBasketHeader( $basket, $basketname, $basketnote, $basketbooksellernote,
215         $basketcontractnumber, $booksellerid, $deliveryplace, $billingplace, $is_standing, $create_items );
216
217     return $basket;
218 }
219
220 =head3 ReopenBasket
221
222   &ReopenBasket($basketno);
223
224 reopen a basket
225
226 =cut
227
228 sub ReopenBasket {
229     my ($basketno) = @_;
230     my $dbh        = C4::Context->dbh;
231     $dbh->do( q{UPDATE aqbasket SET closedate=NULL WHERE  basketno=?}, {}, $basketno );
232
233     $dbh->do( q{
234         UPDATE aqorders
235         SET orderstatus = 'new'
236         WHERE basketno = ?
237         AND orderstatus NOT IN ( 'complete', 'cancelled' )
238         }, {}, $basketno);
239
240     # Log the basket reopening
241     if (C4::Context->preference("AcquisitionLog")) {
242         my $reopened = Koha::Acquisition::Baskets->find( $basketno );
243         logaction(
244             'ACQUISITIONS',
245             'REOPEN_BASKET',
246             $basketno,
247             to_json($reopened->unblessed)
248         );
249     }
250     return;
251 }
252
253 #------------------------------------------------------------#
254
255 =head3 GetBasketAsCSV
256
257   &GetBasketAsCSV($basketno);
258
259 Export a basket as CSV
260
261 $cgi parameter is needed for column name translation
262
263 =cut
264
265 sub GetBasketAsCSV {
266     my ($basketno, $cgi, $csv_profile_id) = @_;
267     my $basket = GetBasket($basketno);
268     my @orders = GetOrders($basketno);
269     my $contract = GetContract({
270         contractnumber => $basket->{'contractnumber'}
271     });
272
273     my $template = C4::Templates::gettemplate("acqui/csv/basket.tt", "intranet", $cgi);
274     my @rows;
275     if ($csv_profile_id) {
276         my $csv_profile = Koha::CsvProfiles->find( $csv_profile_id );
277         Koha::Exceptions::ObjectNotFound->throw( 'There is no valid csv profile given') unless $csv_profile;
278
279         my $delimiter = $csv_profile->csv_separator;
280         $delimiter = "\t" if $delimiter eq "\\t";
281         my $csv = Text::CSV_XS->new({'quote_char'=>'"','escape_char'=>'"','sep_char'=>$delimiter,'binary'=>1});
282         my $csv_profile_content = $csv_profile->content;
283         my ( @headers, @fields );
284         while ( $csv_profile_content =~ /
285             ([^=\|]+) # header
286             =?
287             ([^\|]*) # fieldname (table.row or row)
288             \|? /gxms
289         ) {
290             my $header = $1;
291             my $field = ($2 eq '') ? $1 : $2;
292
293             $header =~ s/^\s+|\s+$//g; # Trim whitespaces
294             push @headers, $header;
295
296             $field =~ s/[^\.]*\.{1}//; # Remove the table name if exists.
297             $field =~ s/^\s+|\s+$//g; # Trim whitespaces
298             push @fields, $field;
299         }
300         for my $order (@orders) {
301             my @row;
302             my $biblio = Koha::Biblios->find( $order->{biblionumber} );
303             my $biblioitem = $biblio->biblioitem;
304             $order = { %$order, %{ $biblioitem->unblessed } };
305             if ($contract) {
306                 $order = {%$order, %$contract};
307             }
308             $order = {%$order, %$basket, %{ $biblio->unblessed }};
309             for my $field (@fields) {
310                 push @row, $order->{$field};
311             }
312             push @rows, \@row;
313         }
314         my $content = join( $delimiter, @headers ) . "\n";
315         for my $row ( @rows ) {
316             $csv->combine(@$row);
317             my $string = $csv->string;
318             $content .= $string . "\n";
319         }
320         return $content;
321     }
322     else {
323         foreach my $order (@orders) {
324             my $biblio = Koha::Biblios->find( $order->{biblionumber} );
325             my $biblioitem;
326             if ($biblio) {
327                 $biblioitem = $biblio->biblioitem;
328             }
329             my $row = {
330                 contractname    => $contract->{'contractname'},
331                 ordernumber     => $order->{'ordernumber'},
332                 entrydate       => $order->{'entrydate'},
333                 isbn            => $order->{'isbn'},
334                 author          => $biblio     ? $biblio->author              : q{},
335                 title           => $biblio     ? $biblio->title               : q{},
336                 publicationyear => $biblioitem ? $biblioitem->publicationyear : q{},
337                 publishercode   => $biblioitem ? $biblioitem->publishercode   : q{},
338                 collectiontitle => $biblioitem ? $biblioitem->collectiontitle : q{},
339                 notes           => $order->{'order_vendornote'},
340                 quantity        => $order->{'quantity'},
341                 rrp             => $order->{'rrp'},
342             };
343             for my $place ( qw( deliveryplace billingplace ) ) {
344                 if ( my $library = Koha::Libraries->find( $row->{deliveryplace} ) ) {
345                     $row->{$place} = $library->branchname
346                 }
347             }
348             foreach(qw(
349                 contractname author title publishercode collectiontitle notes
350                 deliveryplace billingplace
351             ) ) {
352                 # Double the quotes to not be interpreted as a field end
353                 $row->{$_} =~ s/"/""/g if $row->{$_};
354             }
355             push @rows, $row;
356          }
357
358         @rows = sort {
359             if(defined $a->{publishercode} and defined $b->{publishercode}) {
360                 $a->{publishercode} cmp $b->{publishercode};
361             }
362         } @rows;
363
364         $template->param(rows => \@rows);
365
366         return $template->output;
367     }
368 }
369
370
371 =head3 GetBasketGroupAsCSV
372
373   &GetBasketGroupAsCSV($basketgroupid);
374
375 Export a basket group as CSV
376
377 $cgi parameter is needed for column name translation
378
379 =cut
380
381 sub GetBasketGroupAsCSV {
382     my ($basketgroupid, $cgi) = @_;
383     my $baskets = GetBasketsByBasketgroup($basketgroupid);
384
385     my $template = C4::Templates::gettemplate('acqui/csv/basketgroup.tt', 'intranet', $cgi);
386
387     my @rows;
388     for my $basket (@$baskets) {
389         my @orders     = GetOrders( $basket->{basketno} );
390         my $contract   = GetContract({
391             contractnumber => $basket->{contractnumber}
392         });
393         my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
394         my $basketgroup = GetBasketgroup( $$basket{basketgroupid} );
395
396         foreach my $order (@orders) {
397             my $biblio = Koha::Biblios->find( $order->{biblionumber} );
398             my $biblioitem;
399             if ($biblio) {
400                 $biblioitem = $biblio->biblioitem;
401             }
402             my $row = {
403                 clientnumber       => $bookseller->accountnumber,
404                 basketname         => $basket->{basketname},
405                 ordernumber        => $order->{ordernumber},
406                 author             => $biblio     ? $biblio->author              : q{},
407                 title              => $biblio     ? $biblio->title               : q{},
408                 publishercode      => $biblioitem ? $biblioitem->publishercode   : q{},
409                 publicationyear    => $biblioitem ? $biblioitem->publicationyear : q{},
410                 collectiontitle    => $biblioitem ? $biblioitem->collectiontitle : q{},
411                 isbn               => $order->{isbn},
412                 quantity           => $order->{quantity},
413                 rrp_tax_included   => $order->{rrp_tax_included},
414                 rrp_tax_excluded   => $order->{rrp_tax_excluded},
415                 discount           => $bookseller->discount,
416                 ecost_tax_included => $order->{ecost_tax_included},
417                 ecost_tax_excluded => $order->{ecost_tax_excluded},
418                 notes              => $order->{order_vendornote},
419                 entrydate          => $order->{entrydate},
420                 booksellername     => $bookseller->name,
421                 bookselleraddress  => $bookseller->address1,
422                 booksellerpostal   => $bookseller->postal,
423                 contractnumber     => $contract->{contractnumber},
424                 contractname       => $contract->{contractname},
425             };
426             my $temp = {
427                 basketgroupdeliveryplace => $basketgroup->{deliveryplace},
428                 basketgroupbillingplace  => $basketgroup->{billingplace},
429                 basketdeliveryplace      => $basket->{deliveryplace},
430                 basketbillingplace       => $basket->{billingplace},
431             };
432             for my $place (qw( basketgroupdeliveryplace basketgroupbillingplace basketdeliveryplace basketbillingplace )) {
433                 if ( my $library = Koha::Libraries->find( $temp->{$place} ) ) {
434                     $row->{$place} = $library->branchname;
435                 }
436             }
437             foreach(qw(
438                 basketname author title publishercode collectiontitle notes
439                 booksellername bookselleraddress booksellerpostal contractname
440                 basketgroupdeliveryplace basketgroupbillingplace
441                 basketdeliveryplace basketbillingplace
442             ) ) {
443                 # Double the quotes to not be interpreted as a field end
444                 $row->{$_} =~ s/"/""/g if $row->{$_};
445             }
446             push @rows, $row;
447          }
448      }
449     $template->param(rows => \@rows);
450
451     return $template->output;
452
453 }
454
455 =head3 CloseBasketgroup
456
457   &CloseBasketgroup($basketgroupno);
458
459 close a basketgroup
460
461 =cut
462
463 sub CloseBasketgroup {
464     my ($basketgroupno) = @_;
465     my $dbh        = C4::Context->dbh;
466     my $sth = $dbh->prepare("
467         UPDATE aqbasketgroups
468         SET    closed=1
469         WHERE  id=?
470     ");
471     $sth->execute($basketgroupno);
472 }
473
474 #------------------------------------------------------------#
475
476 =head3 ReOpenBaskergroup($basketgroupno)
477
478   &ReOpenBaskergroup($basketgroupno);
479
480 reopen a basketgroup
481
482 =cut
483
484 sub ReOpenBasketgroup {
485     my ($basketgroupno) = @_;
486     my $dbh        = C4::Context->dbh;
487     my $sth = $dbh->prepare("
488         UPDATE aqbasketgroups
489         SET    closed=0
490         WHERE  id=?
491     ");
492     $sth->execute($basketgroupno);
493 }
494
495 #------------------------------------------------------------#
496
497 =head3 ModBasket
498
499   &ModBasket($basketinfo);
500
501 Modifies a basket, using a hashref $basketinfo for the relevant information, only $basketinfo->{'basketno'} is required.
502
503 =over
504
505 =item C<$basketno> is the primary key of the basket in the aqbasket table.
506
507 =back
508
509 =cut
510
511 sub ModBasket {
512     my $basketinfo = shift;
513     my $query = "UPDATE aqbasket SET ";
514     my @params;
515     foreach my $key (keys %$basketinfo){
516         if ($key ne 'basketno'){
517             $query .= "$key=?, ";
518             push(@params, $basketinfo->{$key} || undef );
519         }
520     }
521 # get rid of the "," at the end of $query
522     if (substr($query, length($query)-2) eq ', '){
523         chop($query);
524         chop($query);
525         $query .= ' ';
526     }
527     $query .= "WHERE basketno=?";
528     push(@params, $basketinfo->{'basketno'});
529     my $dbh = C4::Context->dbh;
530     my $sth = $dbh->prepare($query);
531     $sth->execute(@params);
532
533     # Log the basket update
534     if (C4::Context->preference("AcquisitionLog")) {
535         my $modified = Koha::Acquisition::Baskets->find(
536             $basketinfo->{basketno}
537         );
538         logaction(
539             'ACQUISITIONS',
540             'MODIFY_BASKET',
541             $basketinfo->{basketno},
542             to_json($modified->unblessed)
543         );
544     }
545
546     return;
547 }
548
549 #------------------------------------------------------------#
550
551 =head3 ModBasketHeader
552
553   &ModBasketHeader($basketno, $basketname, $note, $booksellernote, $contractnumber, $booksellerid);
554
555 Modifies a basket's header.
556
557 =over
558
559 =item C<$basketno> is the "basketno" field in the "aqbasket" table;
560
561 =item C<$basketname> is the "basketname" field in the "aqbasket" table;
562
563 =item C<$note> is the "note" field in the "aqbasket" table;
564
565 =item C<$booksellernote> is the "booksellernote" field in the "aqbasket" table;
566
567 =item C<$contractnumber> is the "contractnumber" (foreign) key in the "aqbasket" table.
568
569 =item C<$booksellerid> is the id (foreign) key in the "aqbooksellers" table for the vendor.
570
571 =item C<$deliveryplace> is the "deliveryplace" field in the aqbasket table.
572
573 =item C<$billingplace> is the "billingplace" field in the aqbasket table.
574
575 =item C<$is_standing> is the "is_standing" field in the aqbasket table.
576
577 =item C<$create_items> should be set to 'ordering', 'receiving' or 'cataloguing' (or undef, in which
578 case the AcqCreateItem syspref takes precedence).
579
580 =back
581
582 =cut
583
584 sub ModBasketHeader {
585     my ($basketno, $basketname, $note, $booksellernote, $contractnumber, $booksellerid, $deliveryplace, $billingplace, $is_standing, $create_items) = @_;
586
587     $is_standing ||= 0;
588     my $query = qq{
589         UPDATE aqbasket
590         SET basketname=?, note=?, booksellernote=?, booksellerid=?, deliveryplace=?, billingplace=?, is_standing=?, create_items=?
591         WHERE basketno=?
592     };
593
594     my $dbh = C4::Context->dbh;
595     my $sth = $dbh->prepare($query);
596     $sth->execute($basketname, $note, $booksellernote, $booksellerid, $deliveryplace, $billingplace, $is_standing, $create_items || undef, $basketno);
597
598     if ( $contractnumber ) {
599         my $query2 ="UPDATE aqbasket SET contractnumber=? WHERE basketno=?";
600         my $sth2 = $dbh->prepare($query2);
601         $sth2->execute($contractnumber,$basketno);
602     }
603
604     # Log the basket update
605     if (C4::Context->preference("AcquisitionLog")) {
606         my $modified = Koha::Acquisition::Baskets->find(
607             $basketno
608         );
609         logaction(
610             'ACQUISITIONS',
611             'MODIFY_BASKET_HEADER',
612             $basketno,
613             to_json($modified->unblessed)
614         );
615     }
616
617     return;
618 }
619
620 #------------------------------------------------------------#
621
622 =head3 GetBasketsByBookseller
623
624   @results = &GetBasketsByBookseller($booksellerid, $extra);
625
626 Returns a list of hashes of all the baskets that belong to bookseller 'booksellerid'.
627
628 =over
629
630 =item C<$booksellerid> is the 'id' field of the bookseller in the aqbooksellers table
631
632 =item C<$extra> is the extra sql parameters, can be
633
634  $extra->{groupby}: group baskets by column
635     ex. $extra->{groupby} = aqbasket.basketgroupid
636  $extra->{orderby}: order baskets by column
637  $extra->{limit}: limit number of results (can be helpful for pagination)
638
639 =back
640
641 =cut
642
643 sub GetBasketsByBookseller {
644     my ($booksellerid, $extra) = @_;
645     my $query = "SELECT * FROM aqbasket WHERE booksellerid=?";
646     if ($extra){
647         if ($extra->{groupby}) {
648             $query .= " GROUP by $extra->{groupby}";
649         }
650         if ($extra->{orderby}){
651             $query .= " ORDER by $extra->{orderby}";
652         }
653         if ($extra->{limit}){
654             $query .= " LIMIT $extra->{limit}";
655         }
656     }
657     my $dbh = C4::Context->dbh;
658     my $sth = $dbh->prepare($query);
659     $sth->execute($booksellerid);
660     return $sth->fetchall_arrayref({});
661 }
662
663 =head3 GetBasketsInfosByBookseller
664
665     my $baskets = GetBasketsInfosByBookseller($supplierid, $allbaskets);
666
667 The optional second parameter allbaskets is a boolean allowing you to
668 select all baskets from the supplier; by default only active baskets (open or 
669 closed but still something to receive) are returned.
670
671 Returns in a arrayref of hashref all about booksellers baskets, plus:
672     total_biblios: Number of distinct biblios in basket
673     total_items: Number of items in basket
674     expected_items: Number of non-received items in basket
675
676 =cut
677
678 sub GetBasketsInfosByBookseller {
679     my ($supplierid, $allbaskets) = @_;
680
681     return unless $supplierid;
682
683     my $dbh = C4::Context->dbh;
684     my $query = q{
685         SELECT aqbasket.basketno, aqbasket.basketname, aqbasket.note, aqbasket.booksellernote, aqbasket.contractnumber, aqbasket.creationdate, aqbasket.closedate, aqbasket.booksellerid, aqbasket.authorisedby, aqbasket.booksellerinvoicenumber, aqbasket.basketgroupid, aqbasket.deliveryplace, aqbasket.billingplace, aqbasket.branch, aqbasket.is_standing, aqbasket.create_items,
686           SUM(aqorders.quantity) AS total_items,
687           SUM(
688             IF ( aqorders.orderstatus = 'cancelled', aqorders.quantity, 0 )
689           ) AS total_items_cancelled,
690           COUNT(DISTINCT aqorders.biblionumber) AS total_biblios,
691           SUM(
692             IF(aqorders.datereceived IS NULL
693               AND aqorders.datecancellationprinted IS NULL
694             , aqorders.quantity
695             , 0)
696           ) AS expected_items,
697         SUM( aqorders.uncertainprice ) AS uncertainprices
698         FROM aqbasket
699           LEFT JOIN aqorders ON aqorders.basketno = aqbasket.basketno
700         WHERE booksellerid = ?};
701
702     $query.=" GROUP BY aqbasket.basketno, aqbasket.basketname, aqbasket.note, aqbasket.booksellernote, aqbasket.contractnumber, aqbasket.creationdate, aqbasket.closedate, aqbasket.booksellerid, aqbasket.authorisedby, aqbasket.booksellerinvoicenumber, aqbasket.basketgroupid, aqbasket.deliveryplace, aqbasket.billingplace, aqbasket.branch, aqbasket.is_standing, aqbasket.create_items";
703
704     unless ( $allbaskets ) {
705         # Don't show the basket if it's NOT CLOSED or is FULLY RECEIVED
706         $query.=" HAVING (closedate IS NULL OR (
707           SUM(
708             IF(aqorders.datereceived IS NULL
709               AND aqorders.datecancellationprinted IS NULL
710             , aqorders.quantity
711             , 0)
712             ) > 0))"
713     }
714
715     my $sth = $dbh->prepare($query);
716     $sth->execute($supplierid);
717     my $baskets = $sth->fetchall_arrayref({});
718
719     # Retrieve the number of biblios cancelled
720     my $cancelled_biblios = $dbh->selectall_hashref( q|
721         SELECT COUNT(DISTINCT(biblionumber)) AS total_biblios_cancelled, aqbasket.basketno
722         FROM aqbasket
723         LEFT JOIN aqorders ON aqorders.basketno = aqbasket.basketno
724         WHERE booksellerid = ?
725         AND aqorders.orderstatus = 'cancelled'
726         GROUP BY aqbasket.basketno
727     |, 'basketno', {}, $supplierid );
728     map {
729         $_->{total_biblios_cancelled} = $cancelled_biblios->{$_->{basketno}}{total_biblios_cancelled} || 0
730     } @$baskets;
731
732     return $baskets;
733 }
734
735 =head3 GetBasketUsers
736
737     $basketusers_ids = &GetBasketUsers($basketno);
738
739 Returns a list of all borrowernumbers that are in basket users list
740
741 =cut
742
743 sub GetBasketUsers {
744     my $basketno = shift;
745
746     return unless $basketno;
747
748     my $query = qq{
749         SELECT borrowernumber
750         FROM aqbasketusers
751         WHERE basketno = ?
752     };
753     my $dbh = C4::Context->dbh;
754     my $sth = $dbh->prepare($query);
755     $sth->execute($basketno);
756     my $results = $sth->fetchall_arrayref( {} );
757
758     my @borrowernumbers;
759     foreach (@$results) {
760         push @borrowernumbers, $_->{'borrowernumber'};
761     }
762
763     return @borrowernumbers;
764 }
765
766 =head3 ModBasketUsers
767
768     my @basketusers_ids = (1, 2, 3);
769     &ModBasketUsers($basketno, @basketusers_ids);
770
771 Delete all users from basket users list, and add users in C<@basketusers_ids>
772 to this users list.
773
774 =cut
775
776 sub ModBasketUsers {
777     my ($basketno, @basketusers_ids) = @_;
778
779     return unless $basketno;
780
781     my $dbh = C4::Context->dbh;
782     my $query = qq{
783         DELETE FROM aqbasketusers
784         WHERE basketno = ?
785     };
786     my $sth = $dbh->prepare($query);
787     $sth->execute($basketno);
788
789     $query = qq{
790         INSERT INTO aqbasketusers (basketno, borrowernumber)
791         VALUES (?, ?)
792     };
793     $sth = $dbh->prepare($query);
794     foreach my $basketuser_id (@basketusers_ids) {
795         $sth->execute($basketno, $basketuser_id);
796     }
797
798     # Log the basket update
799     if (C4::Context->preference("AcquisitionLog")) {
800         logaction(
801             'ACQUISITIONS',
802             'MODIFY_BASKET_USERS',
803             $basketno,
804             to_json({
805                 basketno    => $basketno,
806                 basketusers => @basketusers_ids
807             })
808         );
809     }
810
811     return;
812 }
813
814 =head3 CanUserManageBasket
815
816     my $bool = CanUserManageBasket($borrower, $basket[, $userflags]);
817     my $bool = CanUserManageBasket($borrowernumber, $basketno[, $userflags]);
818
819 Check if a borrower can manage a basket, according to system preference
820 AcqViewBaskets, user permissions and basket properties (creator, users list,
821 branch).
822
823 First parameter can be either a borrowernumber or a hashref as returned by
824 Koha::Patron->unblessed
825
826 Second parameter can be either a basketno or a hashref as returned by
827 C4::Acquisition::GetBasket.
828
829 The third parameter is optional. If given, it should be a hashref as returned
830 by C4::Auth::getuserflags. If not, getuserflags is called.
831
832 If user is authorised to manage basket, returns 1.
833 Otherwise returns 0.
834
835 =cut
836
837 sub CanUserManageBasket {
838     my ($borrower, $basket, $userflags) = @_;
839
840     if (!ref $borrower) {
841         # FIXME This needs to be replaced
842         # We should not accept both scalar and array
843         # Tests need to be updated
844         $borrower = Koha::Patrons->find( $borrower )->unblessed;
845     }
846     if (!ref $basket) {
847         $basket = GetBasket($basket);
848     }
849
850     return 0 unless ($basket and $borrower);
851
852     my $borrowernumber = $borrower->{borrowernumber};
853     my $basketno = $basket->{basketno};
854
855     my $AcqViewBaskets = C4::Context->preference('AcqViewBaskets');
856
857     if (!defined $userflags) {
858         my $dbh = C4::Context->dbh;
859         my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE borrowernumber = ?");
860         $sth->execute($borrowernumber);
861         my ($flags) = $sth->fetchrow_array;
862         $sth->finish;
863
864         $userflags = C4::Auth::getuserflags($flags, $borrower->{userid}, $dbh);
865     }
866
867     unless ($userflags->{superlibrarian}
868     || (ref $userflags->{acquisition} && $userflags->{acquisition}->{order_manage_all})
869     || (!ref $userflags->{acquisition} && $userflags->{acquisition}))
870     {
871         if (not exists $userflags->{acquisition}) {
872             return 0;
873         }
874
875         if ( (ref $userflags->{acquisition} && !$userflags->{acquisition}->{order_manage})
876         || (!ref $userflags->{acquisition} && !$userflags->{acquisition}) ) {
877             return 0;
878         }
879
880         if ($AcqViewBaskets eq 'user'
881         && $basket->{authorisedby} != $borrowernumber
882         && ! grep { $borrowernumber eq $_ } GetBasketUsers($basketno)) {
883              return 0;
884         }
885
886         if ($AcqViewBaskets eq 'branch' && defined $basket->{branch}
887         && $basket->{branch} ne $borrower->{branchcode}) {
888             return 0;
889         }
890     }
891
892     return 1;
893 }
894
895 #------------------------------------------------------------#
896
897 =head3 GetBasketsByBasketgroup
898
899   $baskets = &GetBasketsByBasketgroup($basketgroupid);
900
901 Returns a reference to all baskets that belong to basketgroup $basketgroupid.
902
903 =cut
904
905 sub GetBasketsByBasketgroup {
906     my $basketgroupid = shift;
907     my $query = qq{
908         SELECT *, aqbasket.booksellerid as booksellerid
909         FROM aqbasket
910         LEFT JOIN aqcontract USING(contractnumber) WHERE basketgroupid=?
911     };
912     my $dbh = C4::Context->dbh;
913     my $sth = $dbh->prepare($query);
914     $sth->execute($basketgroupid);
915     return $sth->fetchall_arrayref({});
916 }
917
918 #------------------------------------------------------------#
919
920 =head3 NewBasketgroup
921
922   $basketgroupid = NewBasketgroup(\%hashref);
923
924 Adds a basketgroup to the aqbasketgroups table, and add the initial baskets to it.
925
926 $hashref->{'booksellerid'} is the 'id' field of the bookseller in the aqbooksellers table,
927
928 $hashref->{'name'} is the 'name' field of the basketgroup in the aqbasketgroups table,
929
930 $hashref->{'basketlist'} is a list reference of the 'id's of the baskets that belong to this group,
931
932 $hashref->{'billingplace'} is the 'billingplace' field of the basketgroup in the aqbasketgroups table,
933
934 $hashref->{'deliveryplace'} is the 'deliveryplace' field of the basketgroup in the aqbasketgroups table,
935
936 $hashref->{'freedeliveryplace'} is the 'freedeliveryplace' field of the basketgroup in the aqbasketgroups table,
937
938 $hashref->{'deliverycomment'} is the 'deliverycomment' field of the basketgroup in the aqbasketgroups table,
939
940 $hashref->{'closed'} is the 'closed' field of the aqbasketgroups table, it is false if 0, true otherwise.
941
942 =cut
943
944 sub NewBasketgroup {
945     my $basketgroupinfo = shift;
946     die "booksellerid is required to create a basketgroup" unless $basketgroupinfo->{'booksellerid'};
947     my $query = "INSERT INTO aqbasketgroups (";
948     my @params;
949     foreach my $field (qw(name billingplace deliveryplace freedeliveryplace deliverycomment closed)) {
950         if ( defined $basketgroupinfo->{$field} ) {
951             $query .= "$field, ";
952             push(@params, $basketgroupinfo->{$field});
953         }
954     }
955     $query .= "booksellerid) VALUES (";
956     foreach (@params) {
957         $query .= "?, ";
958     }
959     $query .= "?)";
960     push(@params, $basketgroupinfo->{'booksellerid'});
961     my $dbh = C4::Context->dbh;
962     my $sth = $dbh->prepare($query);
963     $sth->execute(@params);
964     my $basketgroupid = $dbh->{'mysql_insertid'};
965     if( $basketgroupinfo->{'basketlist'} ) {
966         foreach my $basketno (@{$basketgroupinfo->{'basketlist'}}) {
967             my $query2 = "UPDATE aqbasket SET basketgroupid=? WHERE basketno=?";
968             my $sth2 = $dbh->prepare($query2);
969             $sth2->execute($basketgroupid, $basketno);
970         }
971     }
972     return $basketgroupid;
973 }
974
975 #------------------------------------------------------------#
976
977 =head3 ModBasketgroup
978
979   ModBasketgroup(\%hashref);
980
981 Modifies a basketgroup in the aqbasketgroups table, and add the baskets to it.
982
983 $hashref->{'id'} is the 'id' field of the basketgroup in the aqbasketgroup table, this parameter is mandatory,
984
985 $hashref->{'name'} is the 'name' field of the basketgroup in the aqbasketgroups table,
986
987 $hashref->{'basketlist'} is a list reference of the 'id's of the baskets that belong to this group,
988
989 $hashref->{'billingplace'} is the 'billingplace' field of the basketgroup in the aqbasketgroups table,
990
991 $hashref->{'deliveryplace'} is the 'deliveryplace' field of the basketgroup in the aqbasketgroups table,
992
993 $hashref->{'freedeliveryplace'} is the 'freedeliveryplace' field of the basketgroup in the aqbasketgroups table,
994
995 $hashref->{'deliverycomment'} is the 'deliverycomment' field of the basketgroup in the aqbasketgroups table,
996
997 $hashref->{'closed'} is the 'closed' field of the aqbasketgroups table, it is false if 0, true otherwise.
998
999 =cut
1000
1001 sub ModBasketgroup {
1002     my $basketgroupinfo = shift;
1003     die "basketgroup id is required to edit a basketgroup" unless $basketgroupinfo->{'id'};
1004     my $dbh = C4::Context->dbh;
1005     my $query = "UPDATE aqbasketgroups SET ";
1006     my @params;
1007     foreach my $field (qw(name billingplace deliveryplace freedeliveryplace deliverycomment closed)) {
1008         if ( defined $basketgroupinfo->{$field} ) {
1009             $query .= "$field=?, ";
1010             push(@params, $basketgroupinfo->{$field});
1011         }
1012     }
1013     chop($query);
1014     chop($query);
1015     $query .= " WHERE id=?";
1016     push(@params, $basketgroupinfo->{'id'});
1017     my $sth = $dbh->prepare($query);
1018     $sth->execute(@params);
1019
1020     $sth = $dbh->prepare('UPDATE aqbasket SET basketgroupid = NULL WHERE basketgroupid = ?');
1021     $sth->execute($basketgroupinfo->{'id'});
1022
1023     if($basketgroupinfo->{'basketlist'} && @{$basketgroupinfo->{'basketlist'}}){
1024         $sth = $dbh->prepare("UPDATE aqbasket SET basketgroupid=? WHERE basketno=?");
1025         foreach my $basketno (@{$basketgroupinfo->{'basketlist'}}) {
1026             $sth->execute($basketgroupinfo->{'id'}, $basketno);
1027         }
1028     }
1029     return;
1030 }
1031
1032 #------------------------------------------------------------#
1033
1034 =head3 DelBasketgroup
1035
1036   DelBasketgroup($basketgroupid);
1037
1038 Deletes a basketgroup in the aqbasketgroups table, and removes the reference to it from the baskets,
1039
1040 =over
1041
1042 =item C<$basketgroupid> is the 'id' field of the basket in the aqbasketgroup table
1043
1044 =back
1045
1046 =cut
1047
1048 sub DelBasketgroup {
1049     my $basketgroupid = shift;
1050     die "basketgroup id is required to edit a basketgroup" unless $basketgroupid;
1051     my $query = "DELETE FROM aqbasketgroups WHERE id=?";
1052     my $dbh = C4::Context->dbh;
1053     my $sth = $dbh->prepare($query);
1054     $sth->execute($basketgroupid);
1055     return;
1056 }
1057
1058 #------------------------------------------------------------#
1059
1060
1061 =head2 FUNCTIONS ABOUT ORDERS
1062
1063 =head3 GetBasketgroup
1064
1065   $basketgroup = &GetBasketgroup($basketgroupid);
1066
1067 Returns a reference to the hash containing all information about the basketgroup.
1068
1069 =cut
1070
1071 sub GetBasketgroup {
1072     my $basketgroupid = shift;
1073     die "basketgroup id is required to edit a basketgroup" unless $basketgroupid;
1074     my $dbh = C4::Context->dbh;
1075     my $result_set = $dbh->selectall_arrayref(
1076         'SELECT * FROM aqbasketgroups WHERE id=?',
1077         { Slice => {} },
1078         $basketgroupid
1079     );
1080     return $result_set->[0];    # id is unique
1081 }
1082
1083 #------------------------------------------------------------#
1084
1085 =head3 GetBasketgroups
1086
1087   $basketgroups = &GetBasketgroups($booksellerid);
1088
1089 Returns a reference to the array of all the basketgroups of bookseller $booksellerid.
1090
1091 =cut
1092
1093 sub GetBasketgroups {
1094     my $booksellerid = shift;
1095     die 'bookseller id is required to edit a basketgroup' unless $booksellerid;
1096     my $query = 'SELECT * FROM aqbasketgroups WHERE booksellerid=? ORDER BY id DESC';
1097     my $dbh = C4::Context->dbh;
1098     my $sth = $dbh->prepare($query);
1099     $sth->execute($booksellerid);
1100     return $sth->fetchall_arrayref({});
1101 }
1102
1103 #------------------------------------------------------------#
1104
1105 =head2 FUNCTIONS ABOUT ORDERS
1106
1107 =head3 GetOrders
1108
1109   @orders = &GetOrders( $basketno, { orderby => 'biblio.title', cancelled => 0|1 } );
1110
1111 Looks up the pending (non-cancelled) orders with the given basket
1112 number.
1113
1114 If cancelled is set, only cancelled orders will be returned.
1115
1116 =cut
1117
1118 sub GetOrders {
1119     my ( $basketno, $params ) = @_;
1120
1121     return () unless $basketno;
1122
1123     my $orderby = $params->{orderby};
1124     my $cancelled = $params->{cancelled} || 0;
1125
1126     my $dbh   = C4::Context->dbh;
1127     my $query = q|
1128         SELECT biblio.*,biblioitems.*,
1129                 aqorders.*,
1130                 aqbudgets.*,
1131         |;
1132     $query .= $cancelled
1133       ? q|
1134                 aqorders_transfers.ordernumber_to AS transferred_to,
1135                 aqorders_transfers.timestamp AS transferred_to_timestamp
1136     |
1137       : q|
1138                 aqorders_transfers.ordernumber_from AS transferred_from,
1139                 aqorders_transfers.timestamp AS transferred_from_timestamp
1140     |;
1141     $query .= q|
1142         FROM    aqorders
1143             LEFT JOIN aqbudgets        ON aqbudgets.budget_id = aqorders.budget_id
1144             LEFT JOIN biblio           ON biblio.biblionumber = aqorders.biblionumber
1145             LEFT JOIN biblioitems      ON biblioitems.biblionumber =biblio.biblionumber
1146     |;
1147     $query .= $cancelled
1148       ? q|
1149             LEFT JOIN aqorders_transfers ON aqorders_transfers.ordernumber_from = aqorders.ordernumber
1150     |
1151       : q|
1152             LEFT JOIN aqorders_transfers ON aqorders_transfers.ordernumber_to = aqorders.ordernumber
1153
1154     |;
1155     $query .= q|
1156         WHERE   basketno=?
1157     |;
1158
1159     if ($cancelled) {
1160         $orderby ||= q|biblioitems.publishercode, biblio.title|;
1161         $query .= q|
1162             AND datecancellationprinted IS NOT NULL
1163         |;
1164     }
1165     else {
1166         $orderby ||=
1167           q|aqorders.datecancellationprinted desc, aqorders.timestamp desc|;
1168         $query .= q|
1169             AND datecancellationprinted IS NULL
1170         |;
1171     }
1172
1173     $query .= " ORDER BY $orderby";
1174     my $orders =
1175       $dbh->selectall_arrayref( $query, { Slice => {} }, $basketno );
1176     return @{$orders};
1177
1178 }
1179
1180 #------------------------------------------------------------#
1181
1182 =head3 GetOrdersByBiblionumber
1183
1184   @orders = &GetOrdersByBiblionumber($biblionumber);
1185
1186 Looks up the orders with linked to a specific $biblionumber, including
1187 cancelled orders and received orders.
1188
1189 return :
1190 C<@orders> is an array of references-to-hash, whose keys are the
1191 fields from the aqorders, biblio, and biblioitems tables in the Koha database.
1192
1193 =cut
1194
1195 sub GetOrdersByBiblionumber {
1196     my $biblionumber = shift;
1197     return unless $biblionumber;
1198     my $dbh   = C4::Context->dbh;
1199     my $query  ="
1200         SELECT biblio.*,biblioitems.*,
1201                 aqorders.*,
1202                 aqbudgets.*
1203         FROM    aqorders
1204             LEFT JOIN aqbudgets        ON aqbudgets.budget_id = aqorders.budget_id
1205             LEFT JOIN biblio           ON biblio.biblionumber = aqorders.biblionumber
1206             LEFT JOIN biblioitems      ON biblioitems.biblionumber =biblio.biblionumber
1207         WHERE   aqorders.biblionumber=?
1208     ";
1209     my $result_set =
1210       $dbh->selectall_arrayref( $query, { Slice => {} }, $biblionumber );
1211     return @{$result_set};
1212
1213 }
1214
1215 #------------------------------------------------------------#
1216
1217 =head3 GetOrder
1218
1219   $order = &GetOrder($ordernumber);
1220
1221 Looks up an order by order number.
1222
1223 Returns a reference-to-hash describing the order. The keys of
1224 C<$order> are fields from the biblio, biblioitems, aqorders tables of the Koha database.
1225
1226 =cut
1227
1228 sub GetOrder {
1229     my ($ordernumber) = @_;
1230     return unless $ordernumber;
1231
1232     my $dbh      = C4::Context->dbh;
1233     my $query = qq{SELECT
1234                 aqorders.*,
1235                 biblio.title,
1236                 biblio.author,
1237                 aqbasket.basketname,
1238                 borrowers.branchcode,
1239                 biblioitems.publicationyear,
1240                 biblio.copyrightdate,
1241                 biblioitems.editionstatement,
1242                 biblioitems.isbn,
1243                 biblioitems.ean,
1244                 biblio.seriestitle,
1245                 biblioitems.publishercode,
1246                 aqorders.rrp              AS unitpricesupplier,
1247                 aqorders.ecost            AS unitpricelib,
1248                 aqbudgets.budget_name     AS budget,
1249                 aqbooksellers.name        AS supplier,
1250                 aqbooksellers.id          AS supplierid,
1251                 biblioitems.publishercode AS publisher,
1252                 ADDDATE(aqbasket.closedate, INTERVAL aqbooksellers.deliverytime DAY) AS calculateddeliverydate,
1253                 DATE(aqbasket.closedate)  AS orderdate,
1254                 aqorders.quantity - COALESCE(aqorders.quantityreceived,0)                 AS quantity_to_receive,
1255                 (aqorders.quantity - COALESCE(aqorders.quantityreceived,0)) * aqorders.rrp AS subtotal,
1256                 DATEDIFF(CURDATE( ),closedate) AS latesince
1257                 FROM aqorders LEFT JOIN biblio ON biblio.biblionumber = aqorders.biblionumber
1258                 LEFT JOIN biblioitems ON biblioitems.biblionumber = biblio.biblionumber
1259                 LEFT JOIN aqbudgets ON aqorders.budget_id = aqbudgets.budget_id,
1260                 aqbasket LEFT JOIN borrowers  ON aqbasket.authorisedby = borrowers.borrowernumber
1261                 LEFT JOIN aqbooksellers       ON aqbasket.booksellerid = aqbooksellers.id
1262                 WHERE aqorders.basketno = aqbasket.basketno
1263                     AND ordernumber=?};
1264     my $result_set =
1265       $dbh->selectall_arrayref( $query, { Slice => {} }, $ordernumber );
1266
1267     # result_set assumed to contain 1 match
1268     return $result_set->[0];
1269 }
1270
1271 =head3 ModOrder
1272
1273   &ModOrder(\%hashref);
1274
1275 Modifies an existing order. Updates the order with order number
1276 $hashref->{'ordernumber'} and biblionumber $hashref->{'biblionumber'}. All 
1277 other keys of the hash update the fields with the same name in the aqorders 
1278 table of the Koha database.
1279
1280 =cut
1281
1282 sub ModOrder {
1283     my $orderinfo = shift;
1284
1285     die "Ordernumber is required" if $orderinfo->{'ordernumber'} eq '';
1286
1287     my $dbh = C4::Context->dbh;
1288     my @params;
1289
1290     # update uncertainprice to an integer, just in case (under FF, checked boxes have the value "ON" by default)
1291     $orderinfo->{uncertainprice}=1 if $orderinfo->{uncertainprice};
1292
1293 #    delete($orderinfo->{'branchcode'});
1294     # the hash contains a lot of entries not in aqorders, so get the columns ...
1295     my $sth = $dbh->prepare("SELECT * FROM aqorders LIMIT 1;");
1296     $sth->execute;
1297     my $colnames = $sth->{NAME};
1298         #FIXME Be careful. If aqorders would have columns with diacritics,
1299         #you should need to decode what you get back from NAME.
1300         #See report 10110 and guided_reports.pl
1301     my $query = "UPDATE aqorders SET ";
1302
1303     foreach my $orderinfokey (grep(!/ordernumber/, keys %$orderinfo)){
1304         # ... and skip hash entries that are not in the aqorders table
1305         # FIXME : probably not the best way to do it (would be better to have a correct hash)
1306         next unless grep { $_ eq $orderinfokey } @$colnames;
1307             $query .= "$orderinfokey=?, ";
1308             push(@params, $orderinfo->{$orderinfokey});
1309     }
1310
1311     $query .= "timestamp=NOW()  WHERE  ordernumber=?";
1312     push(@params, $orderinfo->{'ordernumber'} );
1313     $sth = $dbh->prepare($query);
1314     $sth->execute(@params);
1315     return;
1316 }
1317
1318 #------------------------------------------------------------#
1319
1320 =head3 ModItemOrder
1321
1322     ModItemOrder($itemnumber, $ordernumber);
1323
1324 Modifies the ordernumber of an item in aqorders_items.
1325
1326 =cut
1327
1328 sub ModItemOrder {
1329     my ($itemnumber, $ordernumber) = @_;
1330
1331     return unless ($itemnumber and $ordernumber);
1332
1333     my $dbh = C4::Context->dbh;
1334     my $query = qq{
1335         UPDATE aqorders_items
1336         SET ordernumber = ?
1337         WHERE itemnumber = ?
1338     };
1339     my $sth = $dbh->prepare($query);
1340     return $sth->execute($ordernumber, $itemnumber);
1341 }
1342
1343 #------------------------------------------------------------#
1344
1345 =head3 ModReceiveOrder
1346
1347     my ( $date_received, $new_ordernumber ) = ModReceiveOrder(
1348         {
1349             biblionumber         => $biblionumber,
1350             order                => $order,
1351             quantityreceived     => $quantityreceived,
1352             user                 => $user,
1353             invoice              => $invoice,
1354             budget_id            => $budget_id,
1355             datereceived         => $datereceived,
1356             received_itemnumbers => \@received_itemnumbers,
1357         }
1358     );
1359
1360 Updates an order, to reflect the fact that it was received, at least
1361 in part.
1362
1363 If a partial order is received, splits the order into two.
1364
1365 Updates the order with biblionumber C<$biblionumber> and ordernumber
1366 C<$order->{ordernumber}>.
1367
1368 =cut
1369
1370
1371 sub ModReceiveOrder {
1372     my ($params)       = @_;
1373     my $biblionumber   = $params->{biblionumber};
1374     my $order          = { %{ $params->{order} } }; # Copy the order, we don't want to modify it
1375     my $invoice        = $params->{invoice};
1376     my $quantrec       = $params->{quantityreceived};
1377     my $user           = $params->{user};
1378     my $budget_id      = $params->{budget_id};
1379     my $datereceived   = $params->{datereceived};
1380     my $received_items = $params->{received_items};
1381
1382     my $dbh = C4::Context->dbh;
1383     $datereceived = $datereceived ? dt_from_string( $datereceived ) : dt_from_string;
1384     $datereceived = $datereceived->ymd;
1385
1386     my $suggestionid = GetSuggestionFromBiblionumber( $biblionumber );
1387     if ($suggestionid) {
1388         ModSuggestion( {suggestionid=>$suggestionid,
1389                         STATUS=>'AVAILABLE',
1390                         biblionumber=> $biblionumber}
1391                         );
1392     }
1393
1394     my $result_set = $dbh->selectrow_arrayref(
1395             q{SELECT aqbasket.is_standing
1396             FROM aqbasket
1397             WHERE basketno=?},{ Slice => {} }, $order->{basketno});
1398     my $is_standing = $result_set->[0];  # we assume we have a unique basket
1399
1400     my $new_ordernumber = $order->{ordernumber};
1401     if ( $is_standing || $order->{quantity} > $quantrec ) {
1402         # Split order line in two parts: the first is the original order line
1403         # without received items (the quantity is decreased),
1404         # the second part is a new order line with quantity=quantityrec
1405         # (entirely received)
1406         my $query = q|
1407             UPDATE aqorders
1408             SET quantity = ?,
1409                 orderstatus = 'partial'|;
1410         $query .= q| WHERE ordernumber = ?|;
1411         my $sth = $dbh->prepare($query);
1412
1413         $sth->execute(
1414             ( $is_standing ? 1 : ($order->{quantity} - $quantrec) ),
1415             $order->{ordernumber}
1416         );
1417
1418         if ( not $order->{subscriptionid} && defined $order->{order_internalnote} ) {
1419             $dbh->do(
1420                 q|UPDATE aqorders
1421                 SET order_internalnote = ?
1422                 WHERE ordernumber = ?|, {},
1423                 $order->{order_internalnote}, $order->{ordernumber}
1424             );
1425         }
1426
1427         # Recalculate tax_value
1428         $dbh->do(q|
1429             UPDATE aqorders
1430             SET
1431                 tax_value_on_ordering = quantity * | . get_rounding_sql(q|ecost_tax_excluded|) . q| * tax_rate_on_ordering,
1432                 tax_value_on_receiving = quantity * | . get_rounding_sql(q|unitprice_tax_excluded|) . q| * tax_rate_on_receiving
1433             WHERE ordernumber = ?
1434         |, undef, $order->{ordernumber});
1435
1436         delete $order->{ordernumber};
1437         $order->{budget_id} = ( $budget_id || $order->{budget_id} );
1438         $order->{quantity} = $quantrec;
1439         $order->{quantityreceived} = $quantrec;
1440         $order->{ecost_tax_excluded} //= 0;
1441         $order->{tax_rate_on_ordering} //= 0;
1442         $order->{unitprice_tax_excluded} //= 0;
1443         $order->{tax_rate_on_receiving} //= 0;
1444         $order->{tax_value_on_ordering} = $order->{quantity} * get_rounded_price($order->{ecost_tax_excluded}) * $order->{tax_rate_on_ordering};
1445         $order->{tax_value_on_receiving} = $order->{quantity} * get_rounded_price($order->{unitprice_tax_excluded}) * $order->{tax_rate_on_receiving};
1446         $order->{datereceived} = $datereceived;
1447         $order->{invoiceid} = $invoice->{invoiceid};
1448         $order->{orderstatus} = 'complete';
1449         $new_ordernumber = Koha::Acquisition::Order->new($order)->store->ordernumber; # TODO What if the store fails?
1450
1451         if ($received_items) {
1452             foreach my $itemnumber (@$received_items) {
1453                 ModItemOrder($itemnumber, $new_ordernumber);
1454             }
1455         }
1456     } else {
1457         my $query = q|
1458             UPDATE aqorders
1459             SET quantityreceived = ?,
1460                 datereceived = ?,
1461                 invoiceid = ?,
1462                 budget_id = ?,
1463                 orderstatus = 'complete'
1464         |;
1465
1466         $query .= q|
1467             , replacementprice = ?
1468         | if defined $order->{replacementprice};
1469
1470         $query .= q|
1471             , unitprice = ?, unitprice_tax_included = ?, unitprice_tax_excluded = ?
1472         | if defined $order->{unitprice};
1473
1474         $query .= q|
1475             ,tax_value_on_receiving = ?
1476         | if defined $order->{tax_value_on_receiving};
1477
1478         $query .= q|
1479             ,tax_rate_on_receiving = ?
1480         | if defined $order->{tax_rate_on_receiving};
1481
1482         $query .= q|
1483             , order_internalnote = ?
1484         | if defined $order->{order_internalnote};
1485
1486         $query .= q| where biblionumber=? and ordernumber=?|;
1487
1488         my $sth = $dbh->prepare( $query );
1489         my @params = ( $quantrec, $datereceived, $invoice->{invoiceid}, ( $budget_id ? $budget_id : $order->{budget_id} ) );
1490
1491         if ( defined $order->{replacementprice} ) {
1492             push @params, $order->{replacementprice};
1493         }
1494
1495         if ( defined $order->{unitprice} ) {
1496             push @params, $order->{unitprice}, $order->{unitprice_tax_included}, $order->{unitprice_tax_excluded};
1497         }
1498
1499         if ( defined $order->{tax_value_on_receiving} ) {
1500             push @params, $order->{tax_value_on_receiving};
1501         }
1502
1503         if ( defined $order->{tax_rate_on_receiving} ) {
1504             push @params, $order->{tax_rate_on_receiving};
1505         }
1506
1507         if ( defined $order->{order_internalnote} ) {
1508             push @params, $order->{order_internalnote};
1509         }
1510
1511         push @params, ( $biblionumber, $order->{ordernumber} );
1512
1513         $sth->execute( @params );
1514
1515         # All items have been received, sent a notification to users
1516         NotifyOrderUsers( $order->{ordernumber} );
1517
1518     }
1519     return ($datereceived, $new_ordernumber);
1520 }
1521
1522 =head3 CancelReceipt
1523
1524     my $parent_ordernumber = CancelReceipt($ordernumber);
1525
1526     Cancel an order line receipt and update the parent order line, as if no
1527     receipt was made.
1528     If items are created at receipt (AcqCreateItem = receiving) then delete
1529     these items.
1530
1531 =cut
1532
1533 sub CancelReceipt {
1534     my $ordernumber = shift;
1535
1536     return unless $ordernumber;
1537
1538     my $dbh = C4::Context->dbh;
1539     my $query = qq{
1540         SELECT datereceived, parent_ordernumber, quantity
1541         FROM aqorders
1542         WHERE ordernumber = ?
1543     };
1544     my $sth = $dbh->prepare($query);
1545     $sth->execute($ordernumber);
1546     my $order = $sth->fetchrow_hashref;
1547     unless($order) {
1548         warn "CancelReceipt: order $ordernumber does not exist";
1549         return;
1550     }
1551     unless($order->{'datereceived'}) {
1552         warn "CancelReceipt: order $ordernumber is not received";
1553         return;
1554     }
1555
1556     my $parent_ordernumber = $order->{'parent_ordernumber'};
1557
1558     my $order_obj = Koha::Acquisition::Orders->find( $ordernumber ); # FIXME rewrite all this subroutine using this object
1559     my @itemnumbers = $order_obj->items->get_column('itemnumber');
1560
1561     if($parent_ordernumber == $ordernumber || not $parent_ordernumber) {
1562         # The order line has no parent, just mark it as not received
1563         $query = qq{
1564             UPDATE aqorders
1565             SET quantityreceived = ?,
1566                 datereceived = ?,
1567                 invoiceid = ?,
1568                 orderstatus = 'ordered'
1569             WHERE ordernumber = ?
1570         };
1571         $sth = $dbh->prepare($query);
1572         $sth->execute(0, undef, undef, $ordernumber);
1573         _cancel_items_receipt( $order_obj );
1574     } else {
1575         # The order line has a parent, increase parent quantity and delete
1576         # the order line.
1577         unless ( $order_obj->basket->is_standing ) {
1578             $query = qq{
1579                 SELECT quantity, datereceived
1580                 FROM aqorders
1581                 WHERE ordernumber = ?
1582             };
1583             $sth = $dbh->prepare($query);
1584             $sth->execute($parent_ordernumber);
1585             my $parent_order = $sth->fetchrow_hashref;
1586             unless($parent_order) {
1587                 warn "Parent order $parent_ordernumber does not exist.";
1588                 return;
1589             }
1590             if($parent_order->{'datereceived'}) {
1591                 warn "CancelReceipt: parent order is received.".
1592                     " Can't cancel receipt.";
1593                 return;
1594             }
1595             $query = qq{
1596                 UPDATE aqorders
1597                 SET quantity = ?,
1598                     orderstatus = 'ordered'
1599                 WHERE ordernumber = ?
1600             };
1601             $sth = $dbh->prepare($query);
1602             my $rv = $sth->execute(
1603                 $order->{'quantity'} + $parent_order->{'quantity'},
1604                 $parent_ordernumber
1605             );
1606             unless($rv) {
1607                 warn "Cannot update parent order line, so do not cancel".
1608                     " receipt";
1609                 return;
1610             }
1611
1612             # Recalculate tax_value
1613             $dbh->do(q|
1614                 UPDATE aqorders
1615                 SET
1616                     tax_value_on_ordering = quantity * | . get_rounding_sql(q|ecost_tax_excluded|) . q| * tax_rate_on_ordering,
1617                     tax_value_on_receiving = quantity * | . get_rounding_sql(q|unitprice_tax_excluded|) . q| * tax_rate_on_receiving
1618                 WHERE ordernumber = ?
1619             |, undef, $parent_ordernumber);
1620         }
1621
1622         _cancel_items_receipt( $order_obj, $parent_ordernumber );
1623         # Delete order line
1624         $query = qq{
1625             DELETE FROM aqorders
1626             WHERE ordernumber = ?
1627         };
1628         $sth = $dbh->prepare($query);
1629         $sth->execute($ordernumber);
1630
1631     }
1632
1633     if( $order_obj->basket->effective_create_items eq 'ordering' ) {
1634         my @affects = split q{\|}, C4::Context->preference("AcqItemSetSubfieldsWhenReceiptIsCancelled");
1635         if ( @affects ) {
1636             for my $in ( @itemnumbers ) {
1637                 my $item = Koha::Items->find( $in ); # FIXME We do not need that, we already have Koha::Items from $order_obj->items
1638                 my $biblio = $item->biblio;
1639                 my ( $itemfield ) = GetMarcFromKohaField( 'items.itemnumber' );
1640                 my $item_marc = C4::Items::GetMarcItem( $biblio->biblionumber, $in );
1641                 for my $affect ( @affects ) {
1642                     my ( $sf, $v ) = split q{=}, $affect, 2;
1643                     foreach ( $item_marc->field($itemfield) ) {
1644                         $_->update( $sf => $v );
1645                     }
1646                 }
1647                 C4::Items::ModItemFromMarc( $item_marc, $biblio->biblionumber, $in );
1648             }
1649         }
1650     }
1651
1652     return $parent_ordernumber;
1653 }
1654
1655 sub _cancel_items_receipt {
1656     my ( $order, $parent_ordernumber ) = @_;
1657     $parent_ordernumber ||= $order->ordernumber;
1658
1659     my $items = $order->items;
1660     if ( $order->basket->effective_create_items eq 'receiving' ) {
1661         # Remove items that were created at receipt
1662         my $query = qq{
1663             DELETE FROM items, aqorders_items
1664             USING items, aqorders_items
1665             WHERE items.itemnumber = ? AND aqorders_items.itemnumber = ?
1666         };
1667         my $dbh = C4::Context->dbh;
1668         my $sth = $dbh->prepare($query);
1669         while ( my $item = $items->next ) {
1670             $sth->execute($item->itemnumber, $item->itemnumber);
1671         }
1672     } else {
1673         # Update items
1674         while ( my $item = $items->next ) {
1675             ModItemOrder($item->itemnumber, $parent_ordernumber);
1676         }
1677     }
1678 }
1679
1680 #------------------------------------------------------------#
1681
1682 =head3 SearchOrders
1683
1684 @results = &SearchOrders({
1685     ordernumber => $ordernumber,
1686     search => $search,
1687     ean => $ean,
1688     booksellerid => $booksellerid,
1689     basketno => $basketno,
1690     basketname => $basketname,
1691     basketgroupname => $basketgroupname,
1692     owner => $owner,
1693     pending => $pending
1694     ordered => $ordered
1695     biblionumber => $biblionumber,
1696     budget_id => $budget_id
1697 });
1698
1699 Searches for orders filtered by criteria.
1700
1701 C<$ordernumber> Finds matching orders or transferred orders by ordernumber.
1702 C<$search> Finds orders matching %$search% in title, author, or isbn.
1703 C<$owner> Finds order for the logged in user.
1704 C<$pending> Finds pending orders. Ignores completed and cancelled orders.
1705 C<$ordered> Finds orders to receive only (status 'ordered' or 'partial').
1706
1707
1708 C<@results> is an array of references-to-hash with the keys are fields
1709 from aqorders, biblio, biblioitems and aqbasket tables.
1710
1711 =cut
1712
1713 sub SearchOrders {
1714     my ( $params ) = @_;
1715     my $ordernumber = $params->{ordernumber};
1716     my $search = $params->{search};
1717     my $ean = $params->{ean};
1718     my $booksellerid = $params->{booksellerid};
1719     my $basketno = $params->{basketno};
1720     my $basketname = $params->{basketname};
1721     my $basketgroupname = $params->{basketgroupname};
1722     my $owner = $params->{owner};
1723     my $pending = $params->{pending};
1724     my $ordered = $params->{ordered};
1725     my $biblionumber = $params->{biblionumber};
1726     my $budget_id = $params->{budget_id};
1727
1728     my $dbh = C4::Context->dbh;
1729     my @args = ();
1730     my $query = q{
1731         SELECT aqbasket.basketno,
1732                borrowers.surname,
1733                borrowers.firstname,
1734                biblio.*,
1735                biblioitems.isbn,
1736                biblioitems.biblioitemnumber,
1737                biblioitems.publishercode,
1738                biblioitems.publicationyear,
1739                aqbasket.authorisedby,
1740                aqbasket.booksellerid,
1741                aqbasket.closedate,
1742                aqbasket.creationdate,
1743                aqbasket.basketname,
1744                aqbasketgroups.id as basketgroupid,
1745                aqbasketgroups.name as basketgroupname,
1746                aqorders.*
1747         FROM aqorders
1748             LEFT JOIN aqbasket ON aqorders.basketno = aqbasket.basketno
1749             LEFT JOIN aqbasketgroups ON aqbasket.basketgroupid = aqbasketgroups.id
1750             LEFT JOIN borrowers ON aqbasket.authorisedby=borrowers.borrowernumber
1751             LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber
1752             LEFT JOIN biblioitems ON biblioitems.biblionumber=biblio.biblionumber
1753     };
1754
1755     # If we search on ordernumber, we retrieve the transferred order if a transfer has been done.
1756     $query .= q{
1757             LEFT JOIN aqorders_transfers ON aqorders_transfers.ordernumber_to = aqorders.ordernumber
1758     } if $ordernumber;
1759
1760     $query .= q{
1761         WHERE (datecancellationprinted is NULL)
1762     };
1763
1764     if ( $pending or $ordered ) {
1765         $query .= q{
1766             AND (
1767                 ( aqbasket.is_standing AND aqorders.orderstatus IN ( "new", "ordered", "partial" ) )
1768                 OR (
1769                     ( quantity > quantityreceived OR quantityreceived is NULL )
1770         };
1771
1772         if ( $ordered ) {
1773             $query .= q{ AND aqorders.orderstatus IN ( "ordered", "partial" )};
1774         }
1775         $query .= q{
1776                 )
1777             )
1778         };
1779     }
1780
1781     my $userenv = C4::Context->userenv;
1782     if ( C4::Context->preference("IndependentBranches") ) {
1783         unless ( C4::Context->IsSuperLibrarian() ) {
1784             $query .= q{
1785                 AND (
1786                     borrowers.branchcode = ?
1787                     OR borrowers.branchcode  = ''
1788                 )
1789             };
1790             push @args, $userenv->{branch};
1791         }
1792     }
1793
1794     if ( $ordernumber ) {
1795         $query .= ' AND ( aqorders.ordernumber = ? OR aqorders_transfers.ordernumber_from = ? ) ';
1796         push @args, ( $ordernumber, $ordernumber );
1797     }
1798     if ( $biblionumber ) {
1799         $query .= 'AND aqorders.biblionumber = ?';
1800         push @args, $biblionumber;
1801     }
1802     if( $search ) {
1803         $query .= ' AND (biblio.title LIKE ? OR biblio.author LIKE ? OR biblioitems.isbn LIKE ?)';
1804         push @args, ("%$search%","%$search%","%$search%");
1805     }
1806     if ( $ean ) {
1807         $query .= ' AND biblioitems.ean = ?';
1808         push @args, $ean;
1809     }
1810     if ( $booksellerid ) {
1811         $query .= 'AND aqbasket.booksellerid = ?';
1812         push @args, $booksellerid;
1813     }
1814     if( $basketno ) {
1815         $query .= 'AND aqbasket.basketno = ?';
1816         push @args, $basketno;
1817     }
1818     if( $basketname ) {
1819         $query .= 'AND aqbasket.basketname LIKE ?';
1820         push @args, "%$basketname%";
1821     }
1822     if( $basketgroupname ) {
1823         $query .= ' AND aqbasketgroups.name LIKE ?';
1824         push @args, "%$basketgroupname%";
1825     }
1826
1827     if ( $owner ) {
1828         $query .= ' AND aqbasket.authorisedby=? ';
1829         push @args, $userenv->{'number'};
1830     }
1831
1832     if ( $budget_id ) {
1833         $query .= ' AND aqorders.budget_id = ?';
1834         push @args, $budget_id;
1835     }
1836
1837     $query .= ' ORDER BY aqbasket.basketno';
1838
1839     my $sth = $dbh->prepare($query);
1840     $sth->execute(@args);
1841     return $sth->fetchall_arrayref({});
1842 }
1843
1844 #------------------------------------------------------------#
1845
1846 =head3 TransferOrder
1847
1848     my $newordernumber = TransferOrder($ordernumber, $basketno);
1849
1850 Transfer an order line to a basket.
1851 Mark $ordernumber as cancelled with an internal note 'Cancelled and transferred
1852 to BOOKSELLER on DATE' and create new order with internal note
1853 'Transferred from BOOKSELLER on DATE'.
1854 Move all attached items to the new order.
1855 Received orders cannot be transferred.
1856 Return the ordernumber of created order.
1857
1858 =cut
1859
1860 sub TransferOrder {
1861     my ($ordernumber, $basketno) = @_;
1862
1863     return unless ($ordernumber and $basketno);
1864
1865     my $order = Koha::Acquisition::Orders->find( $ordernumber ) or return;
1866     return if $order->datereceived;
1867
1868     $order = $order->unblessed;
1869
1870     my $basket = GetBasket($basketno);
1871     return unless $basket;
1872
1873     my $dbh = C4::Context->dbh;
1874     my ($query, $sth, $rv);
1875
1876     $query = q{
1877         UPDATE aqorders
1878         SET datecancellationprinted = CAST(NOW() AS date), orderstatus = ?
1879         WHERE ordernumber = ?
1880     };
1881     $sth = $dbh->prepare($query);
1882     $rv = $sth->execute('cancelled', $ordernumber);
1883
1884     delete $order->{'ordernumber'};
1885     delete $order->{parent_ordernumber};
1886     $order->{'basketno'} = $basketno;
1887
1888     my $newordernumber = Koha::Acquisition::Order->new($order)->store->ordernumber;
1889
1890     $query = q{
1891         UPDATE aqorders_items
1892         SET ordernumber = ?
1893         WHERE ordernumber = ?
1894     };
1895     $sth = $dbh->prepare($query);
1896     $sth->execute($newordernumber, $ordernumber);
1897
1898     $query = q{
1899         INSERT INTO aqorders_transfers (ordernumber_from, ordernumber_to)
1900         VALUES (?, ?)
1901     };
1902     $sth = $dbh->prepare($query);
1903     $sth->execute($ordernumber, $newordernumber);
1904
1905     return $newordernumber;
1906 }
1907
1908 =head3 get_rounding_sql
1909
1910     $rounding_sql = get_rounding_sql($column_name);
1911
1912 returns the correct SQL routine based on OrderPriceRounding system preference.
1913
1914 =cut
1915
1916 sub get_rounding_sql {
1917     my ( $round_string ) = @_;
1918     my $rounding_pref = C4::Context->preference('OrderPriceRounding') // q{};
1919     if ( $rounding_pref eq "nearest_cent"  ) {
1920         return "CAST($round_string*100 AS SIGNED)/100";
1921     }
1922     return $round_string;
1923 }
1924
1925 =head3 get_rounded_price
1926
1927     $rounded_price = get_rounded_price( $price );
1928
1929 returns a price rounded as specified in OrderPriceRounding system preference.
1930
1931 =cut
1932
1933 sub get_rounded_price {
1934     my ( $price ) =  @_;
1935     my $rounding_pref = C4::Context->preference('OrderPriceRounding') // q{};
1936     if( $rounding_pref eq 'nearest_cent' ) {
1937         return Koha::Number::Price->new( $price )->round();
1938     }
1939     return $price;
1940 }
1941
1942
1943 =head2 FUNCTIONS ABOUT PARCELS
1944
1945 =head3 GetParcels
1946
1947   $results = &GetParcels($bookseller, $order, $code, $datefrom, $dateto);
1948
1949 get a lists of parcels.
1950
1951 * Input arg :
1952
1953 =over
1954
1955 =item $bookseller
1956 is the bookseller this function has to get parcels.
1957
1958 =item $order
1959 To know on what criteria the results list has to be ordered.
1960
1961 =item $code
1962 is the booksellerinvoicenumber.
1963
1964 =item $datefrom & $dateto
1965 to know on what date this function has to filter its search.
1966
1967 =back
1968
1969 * return:
1970 a pointer on a hash list containing parcel informations as such :
1971
1972 =over
1973
1974 =item Creation date
1975
1976 =item Last operation
1977
1978 =item Number of biblio
1979
1980 =item Number of items
1981
1982 =back
1983
1984 =cut
1985
1986 sub GetParcels {
1987     my ($bookseller,$order, $code, $datefrom, $dateto) = @_;
1988     my $dbh    = C4::Context->dbh;
1989     my @query_params = ();
1990     my $strsth ="
1991         SELECT  aqinvoices.invoicenumber,
1992                 datereceived,purchaseordernumber,
1993                 count(DISTINCT biblionumber) AS biblio,
1994                 sum(quantity) AS itemsexpected,
1995                 sum(quantityreceived) AS itemsreceived
1996         FROM   aqorders LEFT JOIN aqbasket ON aqbasket.basketno = aqorders.basketno
1997         LEFT JOIN aqinvoices ON aqorders.invoiceid = aqinvoices.invoiceid
1998         WHERE aqbasket.booksellerid = ? and datereceived IS NOT NULL
1999     ";
2000     push @query_params, $bookseller;
2001
2002     if ( defined $code ) {
2003         $strsth .= ' and aqinvoices.invoicenumber like ? ';
2004         # add a % to the end of the code to allow stemming.
2005         push @query_params, "$code%";
2006     }
2007
2008     if ( defined $datefrom ) {
2009         $strsth .= ' and datereceived >= ? ';
2010         push @query_params, $datefrom;
2011     }
2012
2013     if ( defined $dateto ) {
2014         $strsth .=  'and datereceived <= ? ';
2015         push @query_params, $dateto;
2016     }
2017
2018     $strsth .= "group by aqinvoices.invoicenumber,datereceived ";
2019
2020     # can't use a placeholder to place this column name.
2021     # but, we could probably be checking to make sure it is a column that will be fetched.
2022     $strsth .= "order by $order " if ($order);
2023
2024     my $sth = $dbh->prepare($strsth);
2025
2026     $sth->execute( @query_params );
2027     my $results = $sth->fetchall_arrayref({});
2028     return @{$results};
2029 }
2030
2031 #------------------------------------------------------------#
2032
2033 =head3 GetHistory
2034
2035   \@order_loop = GetHistory( %params );
2036
2037 Retreives some acquisition history information
2038
2039 params:  
2040   title
2041   author
2042   name
2043   isbn
2044   ean
2045   from_placed_on
2046   to_placed_on
2047   basket                  - search both basket name and number
2048   booksellerinvoicenumber 
2049   basketgroupname
2050   budget
2051   orderstatus (note that orderstatus '' will retrieve orders
2052                of any status except cancelled)
2053   is_standing
2054   managing_library
2055   biblionumber
2056   get_canceled_order (if set to a true value, cancelled orders will
2057                       be included)
2058
2059 returns:
2060     $order_loop is a list of hashrefs that each look like this:
2061             {
2062                 'author'           => 'Twain, Mark',
2063                 'basketno'         => '1',
2064                 'biblionumber'     => '215',
2065                 'count'            => 1,
2066                 'creationdate'     => 'MM/DD/YYYY',
2067                 'datereceived'     => undef,
2068                 'ecost'            => '1.00',
2069                 'id'               => '1',
2070                 'invoicenumber'    => undef,
2071                 'name'             => '',
2072                 'ordernumber'      => '1',
2073                 'quantity'         => 1,
2074                 'quantityreceived' => undef,
2075                 'title'            => 'The Adventures of Huckleberry Finn',
2076                 'managing_library' => 'CPL'
2077                 'is_standing'      => '1'
2078             }
2079
2080 =cut
2081
2082 sub GetHistory {
2083 # don't run the query if there are no parameters (list would be too long for sure !)
2084     croak "No search params" unless @_;
2085     my %params = @_;
2086     my $title = $params{title};
2087     my $author = $params{author};
2088     my $isbn   = $params{isbn};
2089     my $issn   = $params{issn};
2090     my $ean    = $params{ean};
2091     my $name = $params{name};
2092     my $internalnote = $params{internalnote};
2093     my $vendornote = $params{vendornote};
2094     my $from_placed_on = $params{from_placed_on};
2095     my $to_placed_on = $params{to_placed_on};
2096     my $basket = $params{basket};
2097     my $booksellerinvoicenumber = $params{booksellerinvoicenumber};
2098     my $basketgroupname = $params{basketgroupname};
2099     my $budget = $params{budget};
2100     my $orderstatus = $params{orderstatus};
2101     my $is_standing = $params{is_standing};
2102     my $biblionumber = $params{biblionumber};
2103     my $get_canceled_order = $params{get_canceled_order} || 0;
2104     my $ordernumber = $params{ordernumber};
2105     my $search_children_too = $params{search_children_too} || 0;
2106     my $created_by = $params{created_by} || [];
2107     my $managing_library = $params{managing_library};
2108     my $ordernumbers = $params{ordernumbers} || [];
2109     my $additional_fields = $params{additional_fields} // [];
2110
2111     my $total_qty         = 0;
2112     my $total_qtyreceived = 0;
2113     my $total_price       = 0;
2114
2115     #get variation of isbn
2116     my @isbn_params;
2117     my @isbns;
2118     if ($isbn){
2119         if ( C4::Context->preference("SearchWithISBNVariations") ){
2120             @isbns = C4::Koha::GetVariationsOfISBN( $isbn );
2121             foreach my $isb (@isbns){
2122                 push @isbn_params, '?';
2123             }
2124         }
2125         unless (@isbns){
2126             push @isbns, $isbn;
2127             push @isbn_params, '?';
2128         }
2129     }
2130
2131     #get variation of issn
2132     my @issn_params;
2133     my @issns;
2134     if ($issn){
2135         if ( C4::Context->preference("SearchWithISSNVariations") ){
2136             @issns = C4::Koha::GetVariationsOfISSN( $issn );
2137             push @issn_params, ('?') x @issns;
2138         }
2139         unless (@issns){
2140             push @issns, $issn;
2141             push @issn_params, '?';
2142         }
2143     }
2144
2145     my $dbh   = C4::Context->dbh;
2146     my $query ="
2147         SELECT
2148             COALESCE(biblio.title,     deletedbiblio.title)     AS title,
2149             COALESCE(biblio.author,    deletedbiblio.author)    AS author,
2150             COALESCE(biblioitems.isbn, deletedbiblioitems.isbn) AS isbn,
2151             COALESCE(biblioitems.ean,  deletedbiblioitems.ean)  AS ean,
2152             aqorders.basketno,
2153             aqbasket.basketname,
2154             aqbasket.basketgroupid,
2155             aqbasket.authorisedby,
2156             aqbasket.is_standing,
2157             concat( borrowers.firstname,' ',borrowers.surname) AS authorisedbyname,
2158             branch as managing_library,
2159             aqbasketgroups.name as groupname,
2160             aqbooksellers.name,
2161             aqbasket.creationdate,
2162             aqorders.datereceived,
2163             aqorders.quantity,
2164             aqorders.quantityreceived,
2165             aqorders.ecost,
2166             aqorders.ordernumber,
2167             aqorders.invoiceid,
2168             aqinvoices.invoicenumber,
2169             aqbooksellers.id as id,
2170             aqorders.biblionumber,
2171             aqorders.orderstatus,
2172             aqorders.parent_ordernumber,
2173             aqorders.order_internalnote,
2174             aqorders.order_vendornote,
2175             aqbudgets.budget_name
2176             ";
2177     $query .= ", aqbudgets.budget_id AS budget" if defined $budget;
2178     $query .= "
2179         FROM aqorders
2180         LEFT JOIN aqbasket ON aqorders.basketno=aqbasket.basketno
2181         LEFT JOIN aqbasketgroups ON aqbasket.basketgroupid=aqbasketgroups.id
2182         LEFT JOIN aqbooksellers ON aqbasket.booksellerid=aqbooksellers.id
2183         LEFT JOIN biblioitems ON biblioitems.biblionumber=aqorders.biblionumber
2184         LEFT JOIN biblio ON biblio.biblionumber=aqorders.biblionumber
2185         LEFT JOIN aqbudgets ON aqorders.budget_id=aqbudgets.budget_id
2186         LEFT JOIN aqinvoices ON aqorders.invoiceid = aqinvoices.invoiceid
2187         LEFT JOIN deletedbiblio ON deletedbiblio.biblionumber=aqorders.biblionumber
2188         LEFT JOIN deletedbiblioitems ON deletedbiblioitems.biblionumber=aqorders.biblionumber
2189         LEFT JOIN borrowers ON aqbasket.authorisedby=borrowers.borrowernumber
2190         ";
2191
2192     $query .= " WHERE 1 ";
2193
2194     unless ($get_canceled_order or (defined $orderstatus and $orderstatus eq 'cancelled')) {
2195         $query .= " AND datecancellationprinted IS NULL ";
2196     }
2197
2198     my @query_params  = ();
2199
2200     if ( $biblionumber ) {
2201         $query .= " AND biblio.biblionumber = ?";
2202         push @query_params, $biblionumber;
2203     }
2204
2205     if ( $title ) {
2206         $query .= " AND biblio.title LIKE ? ";
2207         $title =~ s/\s+/%/g;
2208         push @query_params, "%$title%";
2209     }
2210
2211     if ( $author ) {
2212         $query .= " AND biblio.author LIKE ? ";
2213         push @query_params, "%$author%";
2214     }
2215
2216     if ( @isbns ) {
2217         $query .= " AND ( biblioitems.isbn LIKE " . join (" OR biblioitems.isbn LIKE ", @isbn_params ) . ")";
2218         foreach my $isb (@isbns){
2219             push @query_params, "%$isb%";
2220         }
2221     }
2222
2223     if ( @issns ) {
2224         $query .= " AND ( biblioitems.issn LIKE " . join (" OR biblioitems.issn LIKE ", @issn_params ) . ")";
2225         foreach my $isn (@issns){
2226             push @query_params, "%$isn%";
2227         }
2228     }
2229
2230     if ( $ean ) {
2231         $query .= " AND biblioitems.ean = ? ";
2232         push @query_params, "$ean";
2233     }
2234     if ( $name ) {
2235         $query .= " AND aqbooksellers.name LIKE ? ";
2236         push @query_params, "%$name%";
2237     }
2238
2239     if ( $budget ) {
2240         $query .= " AND aqbudgets.budget_id = ? ";
2241         push @query_params, "$budget";
2242     }
2243
2244     if ( $from_placed_on ) {
2245         $query .= " AND creationdate >= ? ";
2246         push @query_params, $from_placed_on;
2247     }
2248
2249     if ( $to_placed_on ) {
2250         $query .= " AND creationdate <= ? ";
2251         push @query_params, $to_placed_on;
2252     }
2253
2254     if ( defined $orderstatus and $orderstatus ne '') {
2255         $query .= " AND aqorders.orderstatus = ? ";
2256         push @query_params, "$orderstatus";
2257     }
2258
2259     if ( $is_standing ) {
2260         $query .= " AND is_standing = ? ";
2261         push @query_params, $is_standing;
2262     }
2263
2264     if ($basket) {
2265         if ($basket =~ m/^\d+$/) {
2266             $query .= " AND aqorders.basketno = ? ";
2267             push @query_params, $basket;
2268         } else {
2269             $query .= " AND aqbasket.basketname LIKE ? ";
2270             push @query_params, "%$basket%";
2271         }
2272     }
2273
2274     if ( $internalnote ) {
2275         $query .= " AND aqorders.order_internalnote LIKE ? ";
2276         push @query_params, "%$internalnote%";
2277     }
2278
2279     if ( $vendornote ) {
2280         $query .= " AND aqorders.order_vendornote LIKE ?";
2281         push @query_params, "%$vendornote%";
2282     }
2283
2284     if ($booksellerinvoicenumber) {
2285         $query .= " AND aqinvoices.invoicenumber LIKE ? ";
2286         push @query_params, "%$booksellerinvoicenumber%";
2287     }
2288
2289     if ($basketgroupname) {
2290         $query .= " AND aqbasketgroups.name LIKE ? ";
2291         push @query_params, "%$basketgroupname%";
2292     }
2293
2294     if ($ordernumber) {
2295         $query .= " AND (aqorders.ordernumber = ? ";
2296         push @query_params, $ordernumber;
2297         if ($search_children_too) {
2298             $query .= " OR aqorders.parent_ordernumber = ? ";
2299             push @query_params, $ordernumber;
2300         }
2301         $query .= ") ";
2302     }
2303
2304     if ( @$created_by ) {
2305         $query .= ' AND aqbasket.authorisedby IN ( ' . join( ',', ('?') x @$created_by ) . ')';
2306         push @query_params, @$created_by;
2307     }
2308
2309     if ( $managing_library ) {
2310         $query .= " AND aqbasket.branch = ? ";
2311         push @query_params, $managing_library;
2312     }
2313
2314     if ( @$ordernumbers ) {
2315         $query .= ' AND (aqorders.ordernumber IN ( ' . join (',', ('?') x @$ordernumbers ) . '))';
2316         push @query_params, @$ordernumbers;
2317     }
2318     if ( @$additional_fields ) {
2319         my @baskets = Koha::Acquisition::Baskets->filter_by_additional_fields($additional_fields)->as_list;
2320
2321         return [] unless @baskets;
2322
2323         # No parameterization because record IDs come directly from DB
2324         $query .= ' AND aqbasket.basketno IN ( ' . join( ',', map { $_->basketno } @baskets ) . ' )';
2325     }
2326
2327     if ( C4::Context->preference("IndependentBranches") ) {
2328         unless ( C4::Context->IsSuperLibrarian() ) {
2329             $query .= " AND (borrowers.branchcode = ? OR borrowers.branchcode ='' ) ";
2330             push @query_params, C4::Context->userenv->{branch};
2331         }
2332     }
2333     $query .= " ORDER BY id";
2334
2335     return $dbh->selectall_arrayref( $query, { Slice => {} }, @query_params );
2336 }
2337
2338 =head2 GetRecentAcqui
2339
2340   $results = GetRecentAcqui($days);
2341
2342 C<$results> is a ref to a table which contains hashref
2343
2344 =cut
2345
2346 sub GetRecentAcqui {
2347     my $limit  = shift;
2348     my $dbh    = C4::Context->dbh;
2349     my $query = "
2350         SELECT *
2351         FROM   biblio
2352         ORDER BY timestamp DESC
2353         LIMIT  0,".$limit;
2354
2355     my $sth = $dbh->prepare($query);
2356     $sth->execute;
2357     my $results = $sth->fetchall_arrayref({});
2358     return $results;
2359 }
2360
2361 #------------------------------------------------------------#
2362
2363 =head3 AddClaim
2364
2365   &AddClaim($ordernumber);
2366
2367 Add a claim for an order
2368
2369 =cut
2370
2371 sub AddClaim {
2372     my ($ordernumber) = @_;
2373     my $dbh          = C4::Context->dbh;
2374     my $query        = "
2375         UPDATE aqorders SET
2376             claims_count = claims_count + 1,
2377             claimed_date = CURDATE()
2378         WHERE ordernumber = ?
2379         ";
2380     my $sth = $dbh->prepare($query);
2381     $sth->execute($ordernumber);
2382 }
2383
2384 =head3 GetInvoices
2385
2386     my @invoices = GetInvoices(
2387         invoicenumber => $invoicenumber,
2388         supplierid => $supplierid,
2389         suppliername => $suppliername,
2390         shipmentdatefrom => $shipmentdatefrom, # ISO format
2391         shipmentdateto => $shipmentdateto, # ISO format
2392         billingdatefrom => $billingdatefrom, # ISO format
2393         billingdateto => $billingdateto, # ISO format
2394         isbneanissn => $isbn_or_ean_or_issn,
2395         title => $title,
2396         author => $author,
2397         publisher => $publisher,
2398         publicationyear => $publicationyear,
2399         branchcode => $branchcode,
2400         order_by => $order_by
2401     );
2402
2403 Return a list of invoices that match all given criteria.
2404
2405 $order_by is "column_name (asc|desc)", where column_name is any of
2406 'invoicenumber', 'booksellerid', 'shipmentdate', 'billingdate', 'closedate',
2407 'shipmentcost', 'shipmentcost_budgetid'.
2408
2409 asc is the default if omitted
2410
2411 =cut
2412
2413 sub GetInvoices {
2414     my %args = @_;
2415
2416     my $additional_fields = $args{additional_fields} // [];
2417     my $matching_invoice_ids_for_additional_fields = [];
2418     if ( @$additional_fields ) {
2419         my @invoices = Koha::Acquisition::Invoices->filter_by_additional_fields($additional_fields)->as_list;
2420
2421         return () unless @invoices;
2422
2423         $matching_invoice_ids_for_additional_fields = [ map {
2424             $_->id
2425         } @invoices ];
2426     }
2427
2428     my @columns = qw(invoicenumber booksellerid shipmentdate billingdate
2429         closedate shipmentcost shipmentcost_budgetid);
2430
2431     my $dbh = C4::Context->dbh;
2432     my $query = qq{
2433         SELECT aqinvoices.invoiceid, aqinvoices.invoicenumber, aqinvoices.booksellerid, aqinvoices.shipmentdate, aqinvoices.billingdate, aqinvoices.closedate, aqinvoices.shipmentcost, aqinvoices.shipmentcost_budgetid, aqinvoices.message_id,
2434             aqbooksellers.name AS suppliername,
2435           COUNT(
2436             DISTINCT IF(
2437               aqorders.datereceived IS NOT NULL,
2438               aqorders.biblionumber,
2439               NULL
2440             )
2441           ) AS receivedbiblios,
2442           COUNT(
2443              DISTINCT IF(
2444               aqorders.subscriptionid IS NOT NULL,
2445               aqorders.subscriptionid,
2446               NULL
2447             )
2448           ) AS is_linked_to_subscriptions,
2449           SUM(aqorders.quantityreceived) AS receiveditems
2450         FROM aqinvoices
2451           LEFT JOIN aqbooksellers ON aqbooksellers.id = aqinvoices.booksellerid
2452           LEFT JOIN aqorders ON aqorders.invoiceid = aqinvoices.invoiceid
2453           LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno
2454           LEFT JOIN borrowers ON aqbasket.authorisedby=borrowers.borrowernumber
2455           LEFT JOIN biblio ON aqorders.biblionumber = biblio.biblionumber
2456           LEFT JOIN biblioitems ON biblio.biblionumber = biblioitems.biblionumber
2457           LEFT JOIN subscription ON biblio.biblionumber = subscription.biblionumber
2458     };
2459
2460     my @bind_args;
2461     my @bind_strs;
2462     if($args{supplierid}) {
2463         push @bind_strs, " aqinvoices.booksellerid = ? ";
2464         push @bind_args, $args{supplierid};
2465     }
2466     if($args{invoicenumber}) {
2467         push @bind_strs, " aqinvoices.invoicenumber LIKE ? ";
2468         push @bind_args, "%$args{invoicenumber}%";
2469     }
2470     if($args{suppliername}) {
2471         push @bind_strs, " aqbooksellers.name LIKE ? ";
2472         push @bind_args, "%$args{suppliername}%";
2473     }
2474     if($args{shipmentdatefrom}) {
2475         push @bind_strs, " aqinvoices.shipmentdate >= ? ";
2476         push @bind_args, $args{shipmentdatefrom};
2477     }
2478     if($args{shipmentdateto}) {
2479         push @bind_strs, " aqinvoices.shipmentdate <= ? ";
2480         push @bind_args, $args{shipmentdateto};
2481     }
2482     if($args{billingdatefrom}) {
2483         push @bind_strs, " aqinvoices.billingdate >= ? ";
2484         push @bind_args, $args{billingdatefrom};
2485     }
2486     if($args{billingdateto}) {
2487         push @bind_strs, " aqinvoices.billingdate <= ? ";
2488         push @bind_args, $args{billingdateto};
2489     }
2490     if($args{isbneanissn}) {
2491         push @bind_strs, " (biblioitems.isbn LIKE CONCAT('%', ?, '%') OR biblioitems.ean LIKE CONCAT('%', ?, '%') OR biblioitems.issn LIKE CONCAT('%', ?, '%') ) ";
2492         push @bind_args, $args{isbneanissn}, $args{isbneanissn}, $args{isbneanissn};
2493     }
2494     if($args{title}) {
2495         push @bind_strs, " biblio.title LIKE CONCAT('%', ?, '%') ";
2496         push @bind_args, $args{title};
2497     }
2498     if($args{author}) {
2499         push @bind_strs, " biblio.author LIKE CONCAT('%', ?, '%') ";
2500         push @bind_args, $args{author};
2501     }
2502     if($args{publisher}) {
2503         push @bind_strs, " biblioitems.publishercode LIKE CONCAT('%', ?, '%') ";
2504         push @bind_args, $args{publisher};
2505     }
2506     if($args{publicationyear}) {
2507         push @bind_strs, " ((biblioitems.publicationyear LIKE CONCAT('%', ?, '%')) OR (biblio.copyrightdate LIKE CONCAT('%', ?, '%'))) ";
2508         push @bind_args, $args{publicationyear}, $args{publicationyear};
2509     }
2510     if($args{branchcode}) {
2511         push @bind_strs, " borrowers.branchcode = ? ";
2512         push @bind_args, $args{branchcode};
2513     }
2514     if($args{message_id}) {
2515         push @bind_strs, " aqinvoices.message_id = ? ";
2516         push @bind_args, $args{message_id};
2517     }
2518
2519     $query .= " WHERE " . join(" AND ", @bind_strs) if @bind_strs;
2520
2521     # Handle additional fields filtering
2522     if ( @$additional_fields ) {
2523         my $operator = ' WHERE';
2524         if ( @bind_strs ) { # there's a WHERE already
2525             $operator = ' AND';
2526         }
2527         $query .= "$operator aqinvoices.invoiceid IN ("
2528             . join( ', ', @$matching_invoice_ids_for_additional_fields )
2529         . ')';
2530     }
2531
2532     $query .= " GROUP BY aqinvoices.invoiceid, aqinvoices.invoicenumber, aqinvoices.booksellerid, aqinvoices.shipmentdate, aqinvoices.billingdate, aqinvoices.closedate, aqinvoices.shipmentcost, aqinvoices.shipmentcost_budgetid, aqinvoices.message_id, aqbooksellers.name";
2533
2534     if($args{order_by}) {
2535         my ($column, $direction) = split / /, $args{order_by};
2536         if(grep  { $_ eq $column } @columns) {
2537             $direction ||= 'ASC';
2538             $query .= " ORDER BY $column $direction";
2539         }
2540     }
2541
2542     my $sth = $dbh->prepare($query);
2543     $sth->execute(@bind_args);
2544
2545     my $results = $sth->fetchall_arrayref({});
2546     return @$results;
2547 }
2548
2549 =head3 GetInvoice
2550
2551     my $invoice = GetInvoice($invoiceid);
2552
2553 Get informations about invoice with given $invoiceid
2554
2555 Return a hash filled with aqinvoices.* fields
2556
2557 =cut
2558
2559 sub GetInvoice {
2560     my ($invoiceid) = @_;
2561     my $invoice;
2562
2563     return unless $invoiceid;
2564
2565     my $dbh = C4::Context->dbh;
2566     my $query = qq{
2567         SELECT *
2568         FROM aqinvoices
2569         WHERE invoiceid = ?
2570     };
2571     my $sth = $dbh->prepare($query);
2572     $sth->execute($invoiceid);
2573
2574     $invoice = $sth->fetchrow_hashref;
2575     return $invoice;
2576 }
2577
2578 =head3 GetInvoiceDetails
2579
2580     my $invoice = GetInvoiceDetails($invoiceid)
2581
2582 Return informations about an invoice + the list of related order lines
2583
2584 Orders informations are in $invoice->{orders} (array ref)
2585
2586 =cut
2587
2588 sub GetInvoiceDetails {
2589     my ($invoiceid) = @_;
2590
2591     if ( !defined $invoiceid ) {
2592         carp 'GetInvoiceDetails called without an invoiceid';
2593         return;
2594     }
2595
2596     my $dbh = C4::Context->dbh;
2597     my $query = q{
2598         SELECT aqinvoices.*, aqbooksellers.name AS suppliername
2599         FROM aqinvoices
2600           LEFT JOIN aqbooksellers ON aqinvoices.booksellerid = aqbooksellers.id
2601         WHERE invoiceid = ?
2602     };
2603     my $sth = $dbh->prepare($query);
2604     $sth->execute($invoiceid);
2605
2606     my $invoice = $sth->fetchrow_hashref;
2607
2608     $query = q{
2609         SELECT aqorders.*,
2610                 biblio.*,
2611                 biblio.copyrightdate,
2612                 biblioitems.isbn,
2613                 biblioitems.publishercode,
2614                 biblioitems.publicationyear,
2615                 aqbasket.basketname,
2616                 aqbasketgroups.id AS basketgroupid,
2617                 aqbasketgroups.name AS basketgroupname
2618         FROM aqorders
2619           LEFT JOIN aqbasket ON aqorders.basketno = aqbasket.basketno
2620           LEFT JOIN aqbasketgroups ON aqbasket.basketgroupid = aqbasketgroups.id
2621           LEFT JOIN biblio ON aqorders.biblionumber = biblio.biblionumber
2622           LEFT JOIN biblioitems ON aqorders.biblionumber = biblioitems.biblionumber
2623         WHERE invoiceid = ?
2624     };
2625     $sth = $dbh->prepare($query);
2626     $sth->execute($invoiceid);
2627     $invoice->{orders} = $sth->fetchall_arrayref({});
2628     $invoice->{orders} ||= []; # force an empty arrayref if fetchall_arrayref fails
2629
2630     return $invoice;
2631 }
2632
2633 =head3 AddInvoice
2634
2635     my $invoiceid = AddInvoice(
2636         invoicenumber => $invoicenumber,
2637         booksellerid => $booksellerid,
2638         shipmentdate => $shipmentdate,
2639         billingdate => $billingdate,
2640         closedate => $closedate,
2641         shipmentcost => $shipmentcost,
2642         shipmentcost_budgetid => $shipmentcost_budgetid
2643     );
2644
2645 Create a new invoice and return its id or undef if it fails.
2646
2647 =cut
2648
2649 sub AddInvoice {
2650     my %invoice = @_;
2651
2652     return unless(%invoice and $invoice{invoicenumber});
2653
2654     my @columns = qw(invoicenumber booksellerid shipmentdate billingdate
2655         closedate shipmentcost shipmentcost_budgetid message_id);
2656
2657     my @set_strs;
2658     my @set_args;
2659     foreach my $key (keys %invoice) {
2660         if(0 < grep { $_ eq $key } @columns) {
2661             push @set_strs, "$key = ?";
2662             push @set_args, ($invoice{$key} || undef);
2663         }
2664     }
2665
2666     my $rv;
2667     if(@set_args > 0) {
2668         my $dbh = C4::Context->dbh;
2669         my $query = "INSERT INTO aqinvoices SET ";
2670         $query .= join (",", @set_strs);
2671         my $sth = $dbh->prepare($query);
2672         $rv = $sth->execute(@set_args);
2673         if($rv) {
2674             $rv = $dbh->last_insert_id(undef, undef, 'aqinvoices', undef);
2675         }
2676     }
2677     return $rv;
2678 }
2679
2680 =head3 ModInvoice
2681
2682     ModInvoice(
2683         invoiceid => $invoiceid,    # Mandatory
2684         invoicenumber => $invoicenumber,
2685         booksellerid => $booksellerid,
2686         shipmentdate => $shipmentdate,
2687         billingdate => $billingdate,
2688         closedate => $closedate,
2689         shipmentcost => $shipmentcost,
2690         shipmentcost_budgetid => $shipmentcost_budgetid
2691     );
2692
2693 Modify an invoice, invoiceid is mandatory.
2694
2695 Return undef if it fails.
2696
2697 =cut
2698
2699 sub ModInvoice {
2700     my %invoice = @_;
2701
2702     return unless(%invoice and $invoice{invoiceid});
2703
2704     my @columns = qw(invoicenumber booksellerid shipmentdate billingdate
2705         closedate shipmentcost shipmentcost_budgetid);
2706
2707     my @set_strs;
2708     my @set_args;
2709     foreach my $key (keys %invoice) {
2710         if(0 < grep { $_ eq $key } @columns) {
2711             push @set_strs, "$key = ?";
2712             push @set_args, ($invoice{$key} || undef);
2713         }
2714     }
2715
2716     my $dbh = C4::Context->dbh;
2717     my $query = "UPDATE aqinvoices SET ";
2718     $query .= join(",", @set_strs);
2719     $query .= " WHERE invoiceid = ?";
2720
2721     my $sth = $dbh->prepare($query);
2722     $sth->execute(@set_args, $invoice{invoiceid});
2723 }
2724
2725 =head3 CloseInvoice
2726
2727     CloseInvoice($invoiceid);
2728
2729 Close an invoice.
2730
2731 Equivalent to ModInvoice(invoiceid => $invoiceid, closedate => undef);
2732
2733 =cut
2734
2735 sub CloseInvoice {
2736     my ($invoiceid) = @_;
2737
2738     return unless $invoiceid;
2739
2740     my $dbh = C4::Context->dbh;
2741     my $query = qq{
2742         UPDATE aqinvoices
2743         SET closedate = CAST(NOW() AS DATE)
2744         WHERE invoiceid = ?
2745     };
2746     my $sth = $dbh->prepare($query);
2747     $sth->execute($invoiceid);
2748 }
2749
2750 =head3 ReopenInvoice
2751
2752     ReopenInvoice($invoiceid);
2753
2754 Reopen an invoice
2755
2756 Equivalent to ModInvoice(invoiceid => $invoiceid, closedate => $closedate );
2757
2758 =cut
2759
2760 sub ReopenInvoice {
2761     my ($invoiceid) = @_;
2762
2763     return unless $invoiceid;
2764
2765     my $dbh = C4::Context->dbh;
2766     my $query = qq{
2767         UPDATE aqinvoices
2768         SET closedate = NULL
2769         WHERE invoiceid = ?
2770     };
2771     my $sth = $dbh->prepare($query);
2772     $sth->execute($invoiceid);
2773 }
2774
2775 =head3 DelInvoice
2776
2777     DelInvoice($invoiceid);
2778
2779 Delete an invoice if there are no items attached to it.
2780
2781 =cut
2782
2783 sub DelInvoice {
2784     my ($invoiceid) = @_;
2785
2786     return unless $invoiceid;
2787
2788     my $dbh   = C4::Context->dbh;
2789     my $query = qq{
2790         SELECT COUNT(*)
2791         FROM aqorders
2792         WHERE invoiceid = ?
2793     };
2794     my $sth = $dbh->prepare($query);
2795     $sth->execute($invoiceid);
2796     my $res = $sth->fetchrow_arrayref;
2797     if ( $res && $res->[0] == 0 ) {
2798         $query = qq{
2799             DELETE FROM aqinvoices
2800             WHERE invoiceid = ?
2801         };
2802         my $sth = $dbh->prepare($query);
2803         return ( $sth->execute($invoiceid) > 0 );
2804     }
2805     return;
2806 }
2807
2808 =head3 MergeInvoices
2809
2810     MergeInvoices($invoiceid, \@sourceids);
2811
2812 Merge the invoices identified by the IDs in \@sourceids into
2813 the invoice identified by $invoiceid.
2814
2815 =cut
2816
2817 sub MergeInvoices {
2818     my ($invoiceid, $sourceids) = @_;
2819
2820     return unless $invoiceid;
2821     foreach my $sourceid (@$sourceids) {
2822         next if $sourceid == $invoiceid;
2823         my $source = GetInvoiceDetails($sourceid);
2824         foreach my $order (@{$source->{'orders'}}) {
2825             $order->{'invoiceid'} = $invoiceid;
2826             ModOrder($order);
2827         }
2828         DelInvoice($source->{'invoiceid'});
2829     }
2830     return;
2831 }
2832
2833 =head3 GetBiblioCountByBasketno
2834
2835 $biblio_count = &GetBiblioCountByBasketno($basketno);
2836
2837 Looks up the biblio's count that has basketno value $basketno
2838
2839 Returns a quantity
2840
2841 =cut
2842
2843 sub GetBiblioCountByBasketno {
2844     my ($basketno) = @_;
2845     my $dbh          = C4::Context->dbh;
2846     my $query        = "
2847         SELECT COUNT( DISTINCT( biblionumber ) )
2848         FROM   aqorders
2849         WHERE  basketno = ?
2850             AND datecancellationprinted IS NULL
2851         ";
2852
2853     my $sth = $dbh->prepare($query);
2854     $sth->execute($basketno);
2855     return $sth->fetchrow;
2856 }
2857
2858 =head3 GetOrderUsers
2859
2860     $order_users_ids = &GetOrderUsers($ordernumber);
2861
2862 Returns a list of all borrowernumbers that are in order users list
2863
2864 =cut
2865
2866 sub GetOrderUsers {
2867     my ($ordernumber) = @_;
2868
2869     return unless $ordernumber;
2870
2871     my $query = q|
2872         SELECT borrowernumber
2873         FROM aqorder_users
2874         WHERE ordernumber = ?
2875     |;
2876     my $dbh = C4::Context->dbh;
2877     my $sth = $dbh->prepare($query);
2878     $sth->execute($ordernumber);
2879     my $results = $sth->fetchall_arrayref( {} );
2880
2881     my @borrowernumbers;
2882     foreach (@$results) {
2883         push @borrowernumbers, $_->{'borrowernumber'};
2884     }
2885
2886     return @borrowernumbers;
2887 }
2888
2889 =head3 ModOrderUsers
2890
2891     my @order_users_ids = (1, 2, 3);
2892     &ModOrderUsers($ordernumber, @basketusers_ids);
2893
2894 Delete all users from order users list, and add users in C<@order_users_ids>
2895 to this users list.
2896
2897 =cut
2898
2899 sub ModOrderUsers {
2900     my ( $ordernumber, @order_users_ids ) = @_;
2901
2902     return unless $ordernumber;
2903
2904     my $dbh   = C4::Context->dbh;
2905     my $query = q|
2906         DELETE FROM aqorder_users
2907         WHERE ordernumber = ?
2908     |;
2909     my $sth = $dbh->prepare($query);
2910     $sth->execute($ordernumber);
2911
2912     $query = q|
2913         INSERT INTO aqorder_users (ordernumber, borrowernumber)
2914         VALUES (?, ?)
2915     |;
2916     $sth = $dbh->prepare($query);
2917     foreach my $order_user_id (@order_users_ids) {
2918         $sth->execute( $ordernumber, $order_user_id );
2919     }
2920 }
2921
2922 sub NotifyOrderUsers {
2923     my ($ordernumber) = @_;
2924
2925     my @borrowernumbers = GetOrderUsers($ordernumber);
2926     return unless @borrowernumbers;
2927
2928     my $order = GetOrder( $ordernumber );
2929     for my $borrowernumber (@borrowernumbers) {
2930         my $patron = Koha::Patrons->find( $borrowernumber );
2931         my $library = $patron->library->unblessed;
2932         my $biblio = Koha::Biblios->find( $order->{biblionumber} )->unblessed;
2933         my $letter = C4::Letters::GetPreparedLetter(
2934             module      => 'acquisition',
2935             letter_code => 'ACQ_NOTIF_ON_RECEIV',
2936             branchcode  => $library->{branchcode},
2937             lang        => $patron->lang,
2938             tables      => {
2939                 'branches'    => $library,
2940                 'borrowers'   => $patron->unblessed,
2941                 'biblio'      => $biblio,
2942                 'aqorders'    => $order,
2943             },
2944         );
2945         if ( $letter ) {
2946             C4::Letters::EnqueueLetter(
2947                 {
2948                     letter         => $letter,
2949                     borrowernumber => $borrowernumber,
2950                     LibraryName    => C4::Context->preference("LibraryName"),
2951                     message_transport_type => 'email',
2952                 }
2953             ) or warn "can't enqueue letter $letter";
2954         }
2955     }
2956 }
2957
2958 =head3 FillWithDefaultValues
2959
2960 FillWithDefaultValues( $marc_record, $params );
2961
2962 This will update the record with default value defined in the ACQ framework.
2963 For all existing fields, if a default value exists and there are no subfield, it will be created.
2964 If the field does not exist, it will be created too.
2965
2966 If the parameter only_mandatory => 1 is passed via $params, only the mandatory
2967 defaults are being applied to the record.
2968
2969 =cut
2970
2971 sub FillWithDefaultValues {
2972     my ( $record, $params ) = @_;
2973     my $mandatory = $params->{only_mandatory};
2974     my $tagslib = C4::Biblio::GetMarcStructure( 1, 'ACQ', { unsafe => 1 } );
2975     if ($tagslib) {
2976         my ($itemfield) =
2977           C4::Biblio::GetMarcFromKohaField( 'items.itemnumber' );
2978         for my $tag ( sort keys %$tagslib ) {
2979             next unless $tag;
2980             next if $tag == $itemfield;
2981             for my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2982                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2983                 next if $mandatory && !$tagslib->{$tag}{$subfield}{mandatory};
2984                 my $defaultvalue = $tagslib->{$tag}{$subfield}{defaultvalue};
2985                 if ( defined $defaultvalue and $defaultvalue ne '' ) {
2986                     my @fields = $record->field($tag);
2987                     if (@fields) {
2988                         for my $field (@fields) {
2989                             if ( $field->is_control_field ) {
2990                                 $field->update($defaultvalue) if not defined $field->data;
2991                             }
2992                             elsif ( not defined $field->subfield($subfield) ) {
2993                                 $field->add_subfields(
2994                                     $subfield => $defaultvalue );
2995                             }
2996                         }
2997                     }
2998                     else {
2999                         if ( $tag < 10 ) { # is_control_field
3000                             $record->insert_fields_ordered(
3001                                 MARC::Field->new(
3002                                     $tag, $defaultvalue
3003                                 )
3004                             );
3005                         }
3006                         else {
3007                             $record->insert_fields_ordered(
3008                                 MARC::Field->new(
3009                                     $tag, '', '', $subfield => $defaultvalue
3010                                 )
3011                             );
3012                         }
3013                     }
3014                 }
3015             }
3016         }
3017     }
3018 }
3019
3020 1;
3021 __END__
3022
3023 =head1 AUTHOR
3024
3025 Koha Development Team <http://koha-community.org/>
3026
3027 =cut