Bug 23463: Fix CancelReceipt.t
[koha.git] / Koha / Item.pm
1 package Koha::Item;
2
3 # Copyright ByWater Solutions 2014
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Carp;
23 use List::MoreUtils qw(any);
24 use Data::Dumper;
25 use Try::Tiny;
26
27 use Koha::Database;
28 use Koha::DateUtils qw( dt_from_string );
29
30 use C4::Context;
31 use C4::Circulation;
32 use C4::Reserves;
33 use C4::Biblio qw( ModZebra ); # FIXME This is terrible, we should move the indexation code outside of C4::Biblio
34 use C4::ClassSource; # FIXME We would like to avoid that
35 use C4::Log qw( logaction );
36
37 use Koha::Checkouts;
38 use Koha::CirculationRules;
39 use Koha::Item::Transfer::Limits;
40 use Koha::Item::Transfers;
41 use Koha::Patrons;
42 use Koha::Plugins;
43 use Koha::Libraries;
44 use Koha::StockRotationItem;
45 use Koha::StockRotationRotas;
46
47 use base qw(Koha::Object);
48
49 =head1 NAME
50
51 Koha::Item - Koha Item object class
52
53 =head1 API
54
55 =head2 Class methods
56
57 =cut
58
59 =head3 store
60
61 =cut
62
63 sub store {
64     my ($self, $params) = @_;
65
66     my $log_action = $params->{log_action} // 1;
67
68     # We do not want to oblige callers to pass this value
69     # Dev conveniences vs performance?
70     unless ( $self->biblioitemnumber ) {
71         $self->biblioitemnumber( $self->biblio->biblioitem->biblioitemnumber );
72     }
73
74     # See related changes from C4::Items::AddItem
75     unless ( $self->itype ) {
76         $self->itype($self->biblio->biblioitem->itemtype);
77     }
78
79     if ( $self->itemcallnumber ) { # This could be improved, we should recalculate it only if changed
80         my $cn_sort = GetClassSort($self->cn_source, $self->itemcallnumber, "");
81         $self->cn_sort($cn_sort);
82     }
83
84     my $today = dt_from_string;
85     unless ( $self->in_storage ) { #AddItem
86         unless ( $self->permanent_location ) {
87             $self->permanent_location($self->location);
88         }
89         unless ( $self->replacementpricedate ) {
90             $self->replacementpricedate($today);
91         }
92         unless ( $self->datelastseen ) {
93             $self->datelastseen($today);
94         }
95
96         unless ( $self->dateaccessioned ) {
97             $self->dateaccessioned($today);
98         }
99
100         C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" );
101
102         logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
103           if $log_action && C4::Context->preference("CataloguingLog");
104
105         $self->_after_item_action_hooks({ action => 'create' });
106
107     } else { # ModItem
108
109         { # Update *_on  fields if needed
110           # Why not for AddItem as well?
111             my @fields = qw( itemlost withdrawn damaged );
112
113             # Only retrieve the item if we need to set an "on" date field
114             if ( $self->itemlost || $self->withdrawn || $self->damaged ) {
115                 my $pre_mod_item = $self->get_from_storage;
116                 for my $field (@fields) {
117                     if (    $self->$field
118                         and not $pre_mod_item->$field )
119                     {
120                         my $field_on = "${field}_on";
121                         $self->$field_on(
122                           DateTime::Format::MySQL->format_datetime( dt_from_string() )
123                         );
124                     }
125                 }
126             }
127
128             # If the field is defined but empty, we are removing and,
129             # and thus need to clear out the 'on' field as well
130             for my $field (@fields) {
131                 if ( defined( $self->$field ) && !$self->$field ) {
132                     my $field_on = "${field}_on";
133                     $self->$field_on(undef);
134                 }
135             }
136         }
137
138         my %updated_columns = $self->_result->get_dirty_columns;
139         if (    defined $self->location
140             and $self->location ne 'CART'
141             and $self->location ne 'PROC'
142             and not exists $updated_columns{permanent_location} )
143         {
144             $self->permanent_location( $self->location );
145         }
146
147         $self->timestamp(undef) if $self->timestamp; # Maybe move this to Koha::Object->store?
148
149         C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" );
150
151         $self->_after_item_action_hooks({ action => 'modify' });
152
153         logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper($self->unblessed) )
154           if $log_action && C4::Context->preference("CataloguingLog");
155     }
156
157     unless ( $self->dateaccessioned ) {
158         $self->dateaccessioned($today);
159     }
160
161     return $self->SUPER::store;
162 }
163
164 =head3 delete
165
166 =cut
167
168 sub delete {
169     my ( $self ) = @_;
170
171     # FIXME check the item has no current issues
172     # i.e. raise the appropriate exception
173
174     C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" );
175
176     $self->_after_item_action_hooks({ action => 'delete' });
177
178     logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
179       if C4::Context->preference("CataloguingLog");
180
181     return $self->SUPER::delete;
182 }
183
184 =head3 safe_delete
185
186 =cut
187
188 sub safe_delete {
189     my ($self) = @_;
190
191     my $safe_to_delete = $self->safe_to_delete;
192     return $safe_to_delete unless $safe_to_delete eq '1';
193
194     $self->move_to_deleted;
195
196     return $self->delete;
197 }
198
199 =head3 safe_to_delete
200
201 returns 1 if the item is safe to delete,
202
203 "book_on_loan" if the item is checked out,
204
205 "not_same_branch" if the item is blocked by independent branches,
206
207 "book_reserved" if the there are holds aganst the item, or
208
209 "linked_analytics" if the item has linked analytic records.
210
211 =cut
212
213 sub safe_to_delete {
214     my ($self) = @_;
215
216     return "book_on_loan" if $self->checkout;
217
218     return "not_same_branch"
219       if defined C4::Context->userenv
220       and !C4::Context->IsSuperLibrarian()
221       and C4::Context->preference("IndependentBranches")
222       and ( C4::Context->userenv->{branch} ne $self->homebranch );
223
224     # check it doesn't have a waiting reserve
225     return "book_reserved"
226       if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
227
228     return "linked_analytics"
229       if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
230
231     return 1;
232 }
233
234 =head3 move_to_deleted
235
236 my $is_moved = $item->move_to_deleted;
237
238 Move an item to the deleteditems table.
239 This can be done before deleting an item, to make sure the data are not completely deleted.
240
241 =cut
242
243 sub move_to_deleted {
244     my ($self) = @_;
245     my $item_infos = $self->unblessed;
246     delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
247     return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
248 }
249
250
251 =head3 effective_itemtype
252
253 Returns the itemtype for the item based on whether item level itemtypes are set or not.
254
255 =cut
256
257 sub effective_itemtype {
258     my ( $self ) = @_;
259
260     return $self->_result()->effective_itemtype();
261 }
262
263 =head3 home_branch
264
265 =cut
266
267 sub home_branch {
268     my ($self) = @_;
269
270     $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
271
272     return $self->{_home_branch};
273 }
274
275 =head3 holding_branch
276
277 =cut
278
279 sub holding_branch {
280     my ($self) = @_;
281
282     $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
283
284     return $self->{_holding_branch};
285 }
286
287 =head3 biblio
288
289 my $biblio = $item->biblio;
290
291 Return the bibliographic record of this item
292
293 =cut
294
295 sub biblio {
296     my ( $self ) = @_;
297     my $biblio_rs = $self->_result->biblio;
298     return Koha::Biblio->_new_from_dbic( $biblio_rs );
299 }
300
301 =head3 biblioitem
302
303 my $biblioitem = $item->biblioitem;
304
305 Return the biblioitem record of this item
306
307 =cut
308
309 sub biblioitem {
310     my ( $self ) = @_;
311     my $biblioitem_rs = $self->_result->biblioitem;
312     return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
313 }
314
315 =head3 checkout
316
317 my $checkout = $item->checkout;
318
319 Return the checkout for this item
320
321 =cut
322
323 sub checkout {
324     my ( $self ) = @_;
325     my $checkout_rs = $self->_result->issue;
326     return unless $checkout_rs;
327     return Koha::Checkout->_new_from_dbic( $checkout_rs );
328 }
329
330 =head3 holds
331
332 my $holds = $item->holds();
333 my $holds = $item->holds($params);
334 my $holds = $item->holds({ found => 'W'});
335
336 Return holds attached to an item, optionally accept a hashref of params to pass to search
337
338 =cut
339
340 sub holds {
341     my ( $self,$params ) = @_;
342     my $holds_rs = $self->_result->reserves->search($params);
343     return Koha::Holds->_new_from_dbic( $holds_rs );
344 }
345
346 =head3 get_transfer
347
348 my $transfer = $item->get_transfer;
349
350 Return the transfer if the item is in transit or undef
351
352 =cut
353
354 sub get_transfer {
355     my ( $self ) = @_;
356     my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
357     return unless $transfer_rs;
358     return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
359 }
360
361 =head3 last_returned_by
362
363 Gets and sets the last borrower to return an item.
364
365 Accepts and returns Koha::Patron objects
366
367 $item->last_returned_by( $borrowernumber );
368
369 $last_returned_by = $item->last_returned_by();
370
371 =cut
372
373 sub last_returned_by {
374     my ( $self, $borrower ) = @_;
375
376     my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
377
378     if ($borrower) {
379         return $items_last_returned_by_rs->update_or_create(
380             { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
381     }
382     else {
383         unless ( $self->{_last_returned_by} ) {
384             my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
385             if ($result) {
386                 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
387             }
388         }
389
390         return $self->{_last_returned_by};
391     }
392 }
393
394 =head3 can_article_request
395
396 my $bool = $item->can_article_request( $borrower )
397
398 Returns true if item can be specifically requested
399
400 $borrower must be a Koha::Patron object
401
402 =cut
403
404 sub can_article_request {
405     my ( $self, $borrower ) = @_;
406
407     my $rule = $self->article_request_type($borrower);
408
409     return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
410     return q{};
411 }
412
413 =head3 hidden_in_opac
414
415 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
416
417 Returns true if item fields match the hidding criteria defined in $rules.
418 Returns false otherwise.
419
420 Takes HASHref that can have the following parameters:
421     OPTIONAL PARAMETERS:
422     $rules : { <field> => [ value_1, ... ], ... }
423
424 Note: $rules inherits its structure from the parsed YAML from reading
425 the I<OpacHiddenItems> system preference.
426
427 =cut
428
429 sub hidden_in_opac {
430     my ( $self, $params ) = @_;
431
432     my $rules = $params->{rules} // {};
433
434     return 1
435         if C4::Context->preference('hidelostitems') and
436            $self->itemlost > 0;
437
438     my $hidden_in_opac = 0;
439
440     foreach my $field ( keys %{$rules} ) {
441
442         if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
443             $hidden_in_opac = 1;
444             last;
445         }
446     }
447
448     return $hidden_in_opac;
449 }
450
451 =head3 can_be_transferred
452
453 $item->can_be_transferred({ to => $to_library, from => $from_library })
454 Checks if an item can be transferred to given library.
455
456 This feature is controlled by two system preferences:
457 UseBranchTransferLimits to enable / disable the feature
458 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
459                          for setting the limitations
460
461 Takes HASHref that can have the following parameters:
462     MANDATORY PARAMETERS:
463     $to   : Koha::Library
464     OPTIONAL PARAMETERS:
465     $from : Koha::Library  # if not given, item holdingbranch
466                            # will be used instead
467
468 Returns 1 if item can be transferred to $to_library, otherwise 0.
469
470 To find out whether at least one item of a Koha::Biblio can be transferred, please
471 see Koha::Biblio->can_be_transferred() instead of using this method for
472 multiple items of the same biblio.
473
474 =cut
475
476 sub can_be_transferred {
477     my ($self, $params) = @_;
478
479     my $to   = $params->{to};
480     my $from = $params->{from};
481
482     $to   = $to->branchcode;
483     $from = defined $from ? $from->branchcode : $self->holdingbranch;
484
485     return 1 if $from eq $to; # Transfer to current branch is allowed
486     return 1 unless C4::Context->preference('UseBranchTransferLimits');
487
488     my $limittype = C4::Context->preference('BranchTransferLimitsType');
489     return Koha::Item::Transfer::Limits->search({
490         toBranch => $to,
491         fromBranch => $from,
492         $limittype => $limittype eq 'itemtype'
493                         ? $self->effective_itemtype : $self->ccode
494     })->count ? 0 : 1;
495 }
496
497 =head3 pickup_locations
498
499 @pickup_locations = $item->pickup_locations( {patron => $patron } )
500
501 Returns possible pickup locations for this item, according to patron's home library (if patron is defined and holds are allowed only from hold groups)
502 and if item can be transferred to each pickup location.
503
504 =cut
505
506 sub pickup_locations {
507     my ($self, $params) = @_;
508
509     my $patron = $params->{patron};
510
511     my $circ_control_branch =
512       C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
513     my $branchitemrule =
514       C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
515
516     my @libs;
517     if(defined $patron) {
518         return @libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
519         return @libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
520     }
521
522     if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
523         @libs  = $self->home_branch->get_hold_libraries;
524         push @libs, $self->home_branch unless scalar(@libs) > 0;
525     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
526         my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
527         @libs  = $plib->get_hold_libraries;
528         push @libs, $self->home_branch unless scalar(@libs) > 0;
529     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
530         push @libs, $self->home_branch;
531     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
532         push @libs, $self->holding_branch;
533     } else {
534         @libs = Koha::Libraries->search({
535             pickup_location => 1
536         }, {
537             order_by => ['branchname']
538         })->as_list;
539     }
540
541     my @pickup_locations;
542     foreach my $library (@libs) {
543         if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
544             push @pickup_locations, $library;
545         }
546     }
547
548     return wantarray ? @pickup_locations : \@pickup_locations;
549 }
550
551 =head3 article_request_type
552
553 my $type = $item->article_request_type( $borrower )
554
555 returns 'yes', 'no', 'bib_only', or 'item_only'
556
557 $borrower must be a Koha::Patron object
558
559 =cut
560
561 sub article_request_type {
562     my ( $self, $borrower ) = @_;
563
564     my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
565     my $branchcode =
566         $branch_control eq 'homebranch'    ? $self->homebranch
567       : $branch_control eq 'holdingbranch' ? $self->holdingbranch
568       :                                      undef;
569     my $borrowertype = $borrower->categorycode;
570     my $itemtype = $self->effective_itemtype();
571     my $rule = Koha::CirculationRules->get_effective_rule(
572         {
573             rule_name    => 'article_requests',
574             categorycode => $borrowertype,
575             itemtype     => $itemtype,
576             branchcode   => $branchcode
577         }
578     );
579
580     return q{} unless $rule;
581     return $rule->rule_value || q{}
582 }
583
584 =head3 current_holds
585
586 =cut
587
588 sub current_holds {
589     my ( $self ) = @_;
590     my $attributes = { order_by => 'priority' };
591     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
592     my $params = {
593         itemnumber => $self->itemnumber,
594         suspend => 0,
595         -or => [
596             reservedate => { '<=' => $dtf->format_date(dt_from_string) },
597             waitingdate => { '!=' => undef },
598         ],
599     };
600     my $hold_rs = $self->_result->reserves->search( $params, $attributes );
601     return Koha::Holds->_new_from_dbic($hold_rs);
602 }
603
604 =head3 holds
605
606 =cut
607
608 sub holds {
609     my ( $self ) = @_;
610     my $hold_rs = $self->_result->reserves->search;
611     return Koha::Holds->_new_from_dbic($hold_rs);
612 }
613
614 =head3 stockrotationitem
615
616   my $sritem = Koha::Item->stockrotationitem;
617
618 Returns the stock rotation item associated with the current item.
619
620 =cut
621
622 sub stockrotationitem {
623     my ( $self ) = @_;
624     my $rs = $self->_result->stockrotationitem;
625     return 0 if !$rs;
626     return Koha::StockRotationItem->_new_from_dbic( $rs );
627 }
628
629 =head3 add_to_rota
630
631   my $item = $item->add_to_rota($rota_id);
632
633 Add this item to the rota identified by $ROTA_ID, which means associating it
634 with the first stage of that rota.  Should this item already be associated
635 with a rota, then we will move it to the new rota.
636
637 =cut
638
639 sub add_to_rota {
640     my ( $self, $rota_id ) = @_;
641     Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
642     return $self;
643 }
644
645 =head3 has_pending_hold
646
647   my $is_pending_hold = $item->has_pending_hold();
648
649 This method checks the tmp_holdsqueue to see if this item has been selected for a hold, but not filled yet and returns true or false
650
651 =cut
652
653 sub has_pending_hold {
654     my ( $self ) = @_;
655     my $pending_hold = $self->_result->tmp_holdsqueues;
656     return $pending_hold->count ? 1: 0;
657 }
658
659 =head3 as_marc_field
660
661     my $mss   = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
662     my $field = $item->as_marc_field({ [ mss => $mss ] });
663
664 This method returns a MARC::Field object representing the Koha::Item object
665 with the current mappings configuration.
666
667 =cut
668
669 sub as_marc_field {
670     my ( $self, $params ) = @_;
671
672     my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
673     my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
674
675     my @subfields;
676
677     my @columns = $self->_result->result_source->columns;
678
679     foreach my $item_field ( @columns ) {
680         my $mapping = $mss->{ "items.$item_field"}[0];
681         my $tagfield    = $mapping->{tagfield};
682         my $tagsubfield = $mapping->{tagsubfield};
683         next if !$tagfield; # TODO: Should we raise an exception instead?
684                             # Feels like safe fallback is better
685
686         push @subfields, $tagsubfield => $self->$item_field;
687     }
688
689     my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
690     push( @subfields, @{$unlinked_item_subfields} )
691         if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
692
693     my $field;
694
695     $field = MARC::Field->new(
696         "$item_tag", ' ', ' ', @subfields
697     ) if @subfields;
698
699     return $field;
700 }
701
702 =head3 to_api_mapping
703
704 This method returns the mapping for representing a Koha::Item object
705 on the API.
706
707 =cut
708
709 sub to_api_mapping {
710     return {
711         itemnumber               => 'item_id',
712         biblionumber             => 'biblio_id',
713         biblioitemnumber         => undef,
714         barcode                  => 'external_id',
715         dateaccessioned          => 'acquisition_date',
716         booksellerid             => 'acquisition_source',
717         homebranch               => 'home_library_id',
718         price                    => 'purchase_price',
719         replacementprice         => 'replacement_price',
720         replacementpricedate     => 'replacement_price_date',
721         datelastborrowed         => 'last_checkout_date',
722         datelastseen             => 'last_seen_date',
723         stack                    => undef,
724         notforloan               => 'not_for_loan_status',
725         damaged                  => 'damaged_status',
726         damaged_on               => 'damaged_date',
727         itemlost                 => 'lost_status',
728         itemlost_on              => 'lost_date',
729         withdrawn                => 'withdrawn',
730         withdrawn_on             => 'withdrawn_date',
731         itemcallnumber           => 'callnumber',
732         coded_location_qualifier => 'coded_location_qualifier',
733         issues                   => 'checkouts_count',
734         renewals                 => 'renewals_count',
735         reserves                 => 'holds_count',
736         restricted               => 'restricted_status',
737         itemnotes                => 'public_notes',
738         itemnotes_nonpublic      => 'internal_notes',
739         holdingbranch            => 'holding_library_id',
740         paidfor                  => undef,
741         timestamp                => 'timestamp',
742         location                 => 'location',
743         permanent_location       => 'permanent_location',
744         onloan                   => 'checked_out_date',
745         cn_source                => 'call_number_source',
746         cn_sort                  => 'call_number_sort',
747         ccode                    => 'collection_code',
748         materials                => 'materials_notes',
749         uri                      => 'uri',
750         itype                    => 'item_type',
751         more_subfields_xml       => 'extended_subfields',
752         enumchron                => 'serial_issue_number',
753         copynumber               => 'copy_number',
754         stocknumber              => 'inventory_number',
755         new_status               => 'new_status'
756     };
757 }
758
759 =head2 Internal methods
760
761 =head3 _after_item_action_hooks
762
763 Helper method that takes care of calling all plugin hooks
764
765 =cut
766
767 sub _after_item_action_hooks {
768     my ( $self, $params ) = @_;
769
770     my $action = $params->{action};
771
772     if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
773
774         my @plugins = Koha::Plugins->new->GetPlugins({
775             method => 'after_item_action',
776         });
777
778         if (@plugins) {
779
780             foreach my $plugin ( @plugins ) {
781                 try {
782                     $plugin->after_item_action({ action => $action, item => $self, item_id => $self->itemnumber });
783                 }
784                 catch {
785                     warn "$_";
786                 };
787             }
788         }
789     }
790 }
791
792 =head3 _type
793
794 =cut
795
796 sub _type {
797     return 'Item';
798 }
799
800 =head1 AUTHOR
801
802 Kyle M Hall <kyle@bywatersolutions.com>
803
804 =cut
805
806 1;