Bug 20517: (follow-up) Add explanation to syspref and fix QA issues
[koha.git] / C4 / SIP / ILS / Transaction / Checkin.pm
1
2 # An object to handle checkin status
3 #
4
5 package C4::SIP::ILS::Transaction::Checkin;
6
7 use warnings;
8 use strict;
9
10 # use POSIX qw(strftime);
11
12 use C4::SIP::ILS::Transaction;
13
14 use C4::Circulation qw( AddReturn LostItem );
15 use C4::Items qw( ModItemTransfer );
16 use C4::Reserves qw( ModReserve ModReserveAffect );
17 use Koha::DateUtils qw( dt_from_string );
18 use Koha::Items;
19
20 use parent qw(C4::SIP::ILS::Transaction);
21
22 my %fields = (
23     magnetic => 0,
24     sort_bin => undef,
25     collection_code  => undef,
26     # 3M extensions:
27     call_number      => undef,
28     destination_loc  => undef,
29     alert_type       => undef,  # 00,01,02,03,04 or 99
30     hold_patron_id   => undef,
31     hold_patron_name => "",
32     hold             => undef,
33 );
34
35 sub new {
36     my $class = shift;
37     my $self = $class->SUPER::new();                # start with an ILS::Transaction object
38
39     foreach (keys %fields) {
40         $self->{_permitted}->{$_} = $fields{$_};    # overlaying _permitted
41     }
42
43     @{$self}{keys %fields} = values %fields;        # copying defaults into object
44     return bless $self, $class;
45 }
46
47 sub do_checkin {
48     my $self = shift;
49     my $branch = shift;
50     my $return_date = shift;
51     my $account = shift;
52
53     my $checked_in_ok     = $account->{checked_in_ok};
54     my $cv_triggers_alert = $account->{cv_triggers_alert};
55     my $holds_block_checkin  = $account->{holds_block_checkin};
56
57     if (!$branch) {
58         $branch = 'SIP2';
59     }
60     my $barcode = $self->{item}->id;
61
62     if ( $return_date ) {
63         $return_date =   substr( $return_date, 0, 4 )
64                        . '-'
65                        . substr( $return_date, 4, 2 )
66                        . '-'
67                        . substr( $return_date, 6, 2 )
68                        . q{ }
69                        . substr( $return_date, 12, 2 )
70                        . ':'
71                        . substr( $return_date, 14, 2 )
72                        . ':'
73                        . substr( $return_date, 16, 2 );
74         $return_date = dt_from_string($return_date);
75     }
76
77     my ( $return, $messages, $issue, $borrower );
78
79     my $item = Koha::Items->find( { barcode => $barcode } );
80
81     my $human_required = 0;
82     if (   C4::Context->preference("CircConfirmItemParts")
83         && defined($item)
84         && $item->materials )
85     {
86         $human_required                   = 1;
87         $messages->{additional_materials} = 1;
88     }
89
90     my $checkin_blocked_by_holds = $holds_block_checkin && $item->biblio->holds->count;
91
92     ( $return, $messages, $issue, $borrower ) =
93       AddReturn( $barcode, $branch, undef, $return_date )
94       unless $human_required || $checkin_blocked_by_holds;
95
96     if ( $checked_in_ok ) {
97         delete $messages->{ItemLocationUpdated};
98         delete $messages->{NotIssued};
99         delete $messages->{LocalUse};
100         $return = 1 unless keys %$messages;
101     }
102
103     # biblionumber, biblioitemnumber, itemnumber
104     # borrowernumber, reservedate, branchcode
105     # cancellationdate, found, reservenotes, priority, timestamp
106     if ($messages->{additional_materials}) {
107         $self->alert_type('99');
108     }
109     if( $messages->{DataCorrupted} ) {
110         $self->alert_type('98');
111     }
112     if ($messages->{BadBarcode}) {
113         $self->alert_type('99');
114     }
115     if ($messages->{withdrawn}) {
116         $self->alert_type('99');
117     }
118     if ($messages->{WasLost}) {
119         $self->alert_type('99') if C4::Context->preference("BlockReturnOfLostItems");
120     }
121     if ($messages->{Wrongbranch}) {
122         $self->{item}->destination_loc($messages->{Wrongbranch}->{Rightbranch});
123         $self->alert_type('04');            # send to other branch
124     }
125     if ($messages->{WrongTransfer}) {
126         $self->{item}->destination_loc($messages->{WrongTransfer});
127         $self->alert_type('04');            # send to other branch
128     }
129     if ($messages->{NeedsTransfer}) {
130         $self->{item}->destination_loc($messages->{NeedsTransfer});
131         $self->alert_type('04');            # send to other branch
132     }
133     if ($messages->{WasTransfered}) { # set into transit so tell unit
134         $self->{item}->destination_loc($item->homebranch);
135         $self->alert_type('04');            # send to other branch
136     }
137     if ($messages->{ResFound} || $checkin_blocked_by_holds ) {
138         if ($checkin_blocked_by_holds) {
139             $self->alert_type('99');
140             $return = 0;
141         } elsif ($branch eq $messages->{ResFound}->{branchcode}) {
142             $self->hold($messages->{ResFound});
143             $self->alert_type('01');
144             ModReserveAffect( $messages->{ResFound}->{itemnumber},
145                 $messages->{ResFound}->{borrowernumber}, 0, $messages->{ResFound}->{reserve_id});
146
147         } else {
148             $self->hold($messages->{ResFound});
149             $self->alert_type('02');
150             ModReserveAffect( $item->itemnumber,
151                 $messages->{ResFound}->{borrowernumber}, 1, $messages->{ResFound}->{reserve_id});
152             ModItemTransfer( $item->itemnumber,
153                 $branch,
154                 $messages->{ResFound}->{branchcode},
155                 'Reserve',
156             );
157
158         }
159         $self->{item}->hold_patron_id( $messages->{ResFound}->{borrowernumber} );
160         $self->{item}->destination_loc( $messages->{ResFound}->{branchcode} );
161     }
162     # ignoring messages: NotIssued, WasTransfered
163
164     if ($cv_triggers_alert) {
165         $self->alert( defined $self->alert_type ); # Overwrites existing alert value, should set to 0 if there is no alert type
166     }
167     else {
168         $self->alert( !$return || defined $self->alert_type );
169     }
170
171     # Set sort bin based on info in the item associated with the issue, and the
172     # mapping from SIP2SortBinMapping
173     $self->sort_bin( _get_sort_bin( $item, $branch ) );
174
175     $self->ok($return);
176
177     return { messages => $messages };
178 }
179
180 sub resensitize {
181         my $self = shift;
182         unless ($self->{item}) {
183                 warn "resensitize(): no item found in object to resensitize";
184                 return;
185         }
186         return !$self->{item}->magnetic_media;
187 }
188
189 sub patron_id {
190         my $self = shift;
191         unless ($self->{patron}) {
192                 warn "patron_id(): no patron found in object";
193                 return;
194         }
195         return $self->{patron}->id;
196 }
197
198 =head1 _get_sort_bin
199
200 Takes a Koha::Item object and the return branch branchcode as arguments.
201
202 Uses the contents of the SIP2SortBinMapping syspref to determine the sort_bin
203 value that should be returned for an item checked in via SIP2.
204
205 The mapping should be:
206
207  <branchcode>:<item field>:<comparator>:<item field value>:<sort bin number>
208
209 For example:
210
211  CPL:itype:eq:BOOK:1
212  CPL:location:eq:OFFICE:2
213  CPL:classmark:<:339.6:3
214
215 This will give:
216
217 =over 4
218
219 =item * sort_bin = "1" for items at the CPL branch with an itemtype of BOOK
220
221 =item * sort_bin = "2" for items at the CPL branch with a location of OFFICE
222
223 =item * sort_bin = "3" for items at the CPL branch with a classmark less than 339.6
224
225 =back
226
227 Returns the ID of the appropriate sort_bin, if there is one, or undef.
228
229 =cut
230
231 sub _get_sort_bin {
232
233     # We should get an item represented as a hashref here
234     my ( $item, $branch ) = @_;
235     return unless $item;
236
237     # Get the mapping and split on newlines
238     my $raw_map = C4::Context->preference('SIP2SortBinMapping');
239     return unless $raw_map;
240     my @lines = split /\r\n/, $raw_map;
241
242     # Iterate over the mapping. The first hit wins.
243     my $rule = 0;
244     foreach my $line (@lines) {
245
246         # Split the line into fields
247         my ( $branchcode, $item_property, $comparator, $value, $sort_bin ) =
248           split /:/, $line;
249         if ( $value =~ s/^\$// ) {
250             $value = $item->$value;
251         }
252         # Check the fields against values in the item
253         if ( $branch eq $branchcode ) {
254             my $property = $item->$item_property;
255             if ( ( $comparator eq 'eq' || $comparator eq '=' ) && ( $property eq $value ) ) {
256                 return $sort_bin;
257             }
258             if ( ( $comparator eq 'ne' || $comparator eq '!=' ) && ( $property ne $value ) ) {
259                 return $sort_bin;
260             }
261             if ( ( $comparator eq '<' ) && ( $property < $value ) ) {
262                 return $sort_bin;
263             }
264             if ( ( $comparator eq '>' ) && ( $property > $value ) ) {
265                 return $sort_bin;
266             }
267             if ( ( $comparator eq '<=' ) && ( $property <= $value ) ) {
268                 return $sort_bin;
269             }
270             if ( ( $comparator eq '>=' ) && ( $property >= $value ) ) {
271                 return $sort_bin;
272             }
273         }
274     }
275
276     # Return undef if no hits were found
277     return;
278 }
279
280 1;