1 package Koha::Acquisition::Order;
3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use C4::Biblio qw(DelBiblio);
25 use Koha::Acquisition::Baskets;
26 use Koha::Acquisition::Funds;
27 use Koha::Acquisition::Invoices;
28 use Koha::Acquisition::Order::Claims;
30 use Koha::DateUtils qw( dt_from_string output_pref );
31 use Koha::Exceptions::Object;
35 use Koha::Subscriptions;
37 use base qw(Koha::Object);
41 Koha::Acquisition::Order Object class
49 Overloaded I<new> method for backwards compatibility.
54 my ( $self, $params ) = @_;
56 my $schema = Koha::Database->new->schema;
57 my @columns = $schema->source('Aqorder')->columns;
60 { map { exists $params->{$_} ? ( $_ => $params->{$_} ) : () } @columns };
61 return $self->SUPER::new($values);
66 Overloaded I<store> method for backwards compatibility.
73 my $schema = Koha::Database->new->schema;
74 # Override quantity for standing orders
75 $self->quantity(1) if ( $self->basketno && $schema->resultset('Aqbasket')->find( $self->basketno )->is_standing );
77 # if these parameters are missing, we can't continue
78 for my $key (qw( basketno quantity biblionumber budget_id )) {
79 croak "Cannot insert order: Mandatory parameter $key is missing"
83 if (not defined $self->{created_by}) {
84 my $userenv = C4::Context->userenv;
86 $self->created_by($userenv->{number});
90 $self->quantityreceived(0) unless $self->quantityreceived;
91 $self->entrydate(dt_from_string) unless $self->entrydate;
93 $self->ordernumber(undef) unless $self->ordernumber;
94 $self = $self->SUPER::store( $self );
96 unless ( $self->parent_ordernumber ) {
97 $self->set( { parent_ordernumber => $self->ordernumber } );
98 $self = $self->SUPER::store( $self );
107 { [ reason => $reason,
108 delete_biblio => $delete_biblio ]
112 This method marks an order as cancelled, optionally using the I<reason> parameter.
113 As the order is cancelled, the (eventual) items linked to it are removed.
114 If I<delete_biblio> is passed, it will try to remove the linked biblio.
116 If either the items or biblio removal fails, an error message is added to the object
117 so the caller can take appropriate actions.
122 my ($self, $params) = @_;
124 my $delete_biblio = $params->{delete_biblio};
125 my $reason = $params->{reason};
127 # Delete the related items
128 my $items = $self->items;
129 while ( my $item = $items->next ) {
130 my $safe_to_delete = $item->safe_to_delete;
131 if ( $safe_to_delete eq '1' ) {
137 message => 'error_delitem',
138 payload => { item => $item, reason => $safe_to_delete }
144 my $biblio = $self->biblio;
145 if ( $biblio and $delete_biblio ) {
148 $biblio->active_orders->search(
149 { ordernumber => { '!=' => $self->ordernumber } }
151 and $biblio->subscriptions->count == 0
152 and $biblio->items->count == 0
156 my $error = DelBiblio( $biblio->id );
159 message => 'error_delbiblio',
160 payload => { biblio => $biblio, reason => $error }
168 if ( $biblio->active_orders->search(
169 { ordernumber => { '!=' => $self->ordernumber } }
171 $message = 'error_delbiblio_active_orders';
173 elsif ( $biblio->subscriptions->count > 0 ) {
174 $message = 'error_delbiblio_subscriptions';
176 else { # $biblio->items->count > 0
177 $message = 'error_delbiblio_items';
183 payload => { biblio => $biblio }
189 # Update order status
192 cancellationreason => $reason,
193 datecancellationprinted => \'NOW()',
194 orderstatus => 'cancelled',
203 $order->add_item( $itemnumber );
205 Link an item to this order.
210 my ( $self, $itemnumber ) = @_;
212 my $schema = Koha::Database->new->schema;
213 my $rs = $schema->resultset('AqordersItem');
214 $rs->create({ ordernumber => $self->ordernumber, itemnumber => $itemnumber });
219 my $basket = $order->basket;
221 Returns the I<Koha::Acquisition::Basket> object for the basket associated
228 my $basket_rs = $self->_result->basket;
229 return Koha::Acquisition::Basket->_new_from_dbic( $basket_rs );
234 my $fund = $order->fund;
236 Returns the I<Koha::Acquisition::Fund> object for the fund (aqbudgets)
237 associated to the order.
243 my $fund_rs = $self->_result->fund;
244 return Koha::Acquisition::Fund->_new_from_dbic( $fund_rs );
249 my $invoice = $order->invoice;
251 Returns the I<Koha::Acquisition::Invoice> object for the invoice associated
254 It returns B<undef> if no linked invoice is found.
260 my $invoice_rs = $self->_result->invoice;
261 return unless $invoice_rs;
262 return Koha::Acquisition::Invoice->_new_from_dbic( $invoice_rs );
267 my $subscription = $order->subscription
269 Returns the I<Koha::Subscription> object for the subscription associated
272 It returns B<undef> if no linked subscription is found.
278 my $subscription_rs = $self->_result->subscription;
279 return unless $subscription_rs;
280 return Koha::Subscription->_new_from_dbic( $subscription_rs );
283 =head3 current_item_level_holds
285 my $holds = $order->current_item_level_holds;
287 Returns the current item-level holds associated to the order. It returns a I<Koha::Holds>
292 sub current_item_level_holds {
295 my $items_rs = $self->_result->aqorders_items;
296 my @item_numbers = $items_rs->get_column('itemnumber')->all;
297 my $biblio = $self->biblio;
299 unless ( $biblio and @item_numbers ) {
300 return Koha::Holds->new->empty;
303 return $biblio->current_holds->search(
306 -in => \@item_numbers
314 my $items = $order->items
316 Returns the items associated to the order.
322 # aqorders_items is not a join table
323 # There is no FK on items (may have been deleted)
324 my $items_rs = $self->_result->aqorders_items;
325 my @itemnumbers = $items_rs->get_column( 'itemnumber' )->all;
326 return Koha::Items->search({ itemnumber => \@itemnumbers });
331 my $biblio = $order->biblio
333 Returns the bibliographic record associated to the order
339 my $biblio_rs= $self->_result->biblio;
340 return unless $biblio_rs;
341 return Koha::Biblio->_new_from_dbic( $biblio_rs );
346 my $claims = $order->claims
348 Return the claims history for this order
354 my $claims_rs = $self->_result->aqorders_claims;
355 return Koha::Acquisition::Order::Claims->_new_from_dbic( $claims_rs );
360 my $claim = $order->claim
362 Do claim for this order
368 my $claim_rs = $self->_result->create_related('aqorders_claims', {});
369 return Koha::Acquisition::Order::Claim->_new_from_dbic($claim_rs);
374 my $nb_of_claims = $order->claims_count;
376 This is the equivalent of $order->claims->count. Keeping it for retrocompatibilty.
382 return $self->claims->count;
387 my $last_claim_date = $order->claimed_date;
389 This is the equivalent of $order->claims->last->claimed_on. Keeping it for retrocompatibilty.
395 my $last_claim = $self->claims->last;
396 return unless $last_claim;
397 return $last_claim->claimed_on;
402 my $duplicated_order = $order->duplicate_to($basket, [$default_values]);
404 Duplicate an existing order and attach it to a basket. $default_values can be specified as a hashref
405 that contain default values for the different order's attributes.
406 Items will be duplicated as well but barcodes will be set to null.
411 my ( $self, $basket, $default_values ) = @_;
413 $default_values //= {};
414 Koha::Database->schema->txn_do(
416 my $order_info = $self->unblessed;
417 undef $order_info->{ordernumber};
424 datecancellationprinted
433 undef $order_info->{$field};
435 $order_info->{placed_on} = dt_from_string;
436 $order_info->{entrydate} = dt_from_string;
437 $order_info->{orderstatus} = 'new';
438 $order_info->{quantityreceived} = 0;
439 while ( my ( $field, $value ) = each %$default_values ) {
440 $order_info->{$field} = $value;
443 my $userenv = C4::Context->userenv;
444 $order_info->{created_by} = $userenv->{number};
445 $order_info->{basketno} = $basket->basketno;
447 $new_order = Koha::Acquisition::Order->new($order_info)->store;
449 if ( ! $self->subscriptionid && $self->basket->effective_create_items eq 'ordering') { # Do copy items if not a subscription order AND if items are created on ordering
450 my $items = $self->items;
451 while ( my ($item) = $items->next ) {
452 my $item_info = $item->unblessed;
453 undef $item_info->{itemnumber};
454 undef $item_info->{barcode};
455 my $new_item = Koha::Item->new($item_info)->store;
456 $new_order->add_item( $new_item->itemnumber );
464 =head3 to_api_mapping
466 This method returns the mapping for representing a Koha::Acquisition::Order object
473 basketno => 'basket_id',
474 biblionumber => 'biblio_id',
475 budget_id => 'fund_id',
476 budgetdate => undef, # unused
477 cancellationreason => 'cancellation_reason',
478 claimed_date => 'last_claim_date',
479 datecancellationprinted => 'cancellation_date',
480 datereceived => 'date_received',
481 discount => 'discount_rate',
482 entrydate => 'entry_date',
483 freight => 'shipping_cost',
484 invoiceid => 'invoice_id',
485 line_item_id => undef, # EDIFACT related
486 listprice => 'list_price',
487 order_internalnote => 'internal_note',
488 order_vendornote => 'vendor_note',
489 ordernumber => 'order_id',
490 orderstatus => 'status',
491 parent_ordernumber => 'parent_order_id',
492 purchaseordernumber => undef, # obsolete
493 quantityreceived => 'quantity_received',
494 replacementprice => 'replacement_price',
495 sort1 => 'statistics_1',
496 sort1_authcat => 'statistics_1_authcat',
497 sort2 => 'statistics_2',
498 sort2_authcat => 'statistics_2_authcat',
499 subscriptionid => 'subscription_id',
500 suppliers_reference_number => undef, # EDIFACT related
501 suppliers_reference_qualifier => undef, # EDIFACT related
502 suppliers_report => undef, # EDIFACT related
503 tax_rate_bak => undef, # unused
504 tax_value_bak => undef, # unused
505 uncertainprice => 'uncertain_price',
506 unitprice => 'unit_price',
507 unitprice_tax_excluded => 'unit_price_tax_excluded',
508 unitprice_tax_included => 'unit_price_tax_included'
512 =head2 Internal methods