Allow SIP checkout to pre-empt unfilled holds.
[koha.git] / C4 / SIP / ILS / Item.pm
1 #
2 # ILS::Item.pm
3
4 # A Class for hiding the ILS's concept of the item from the OpenSIP
5 # system
6 #
7
8 package ILS::Item;
9
10 use strict;
11 use warnings;
12
13 use Sys::Syslog qw(syslog);
14
15 use ILS::Transaction;
16
17 use C4::Debug;
18 use C4::Context;
19 use C4::Biblio;
20 use C4::Items;
21 use C4::Circulation;
22 use C4::Members;
23 use C4::Reserves;
24
25 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
26
27 BEGIN {
28         $VERSION = 2.10;
29         require Exporter;
30         @ISA = qw(Exporter);
31         @EXPORT_OK = qw();
32 }
33
34 =head2 EXAMPLE
35
36 our %item_db = (
37     '1565921879' => {
38         title => "Perl 5 desktop reference",
39         id => '1565921879',
40         sip_media_type => '001',
41         magnetic_media => 0,
42         hold_queue => [],
43     },
44     '0440242746' => {
45         title => "The deep blue alibi",
46         id => '0440242746',
47         sip_media_type => '001',
48         magnetic_media => 0,
49         hold_queue => [
50             {
51             itemnumber => '823',
52             priority => '1',
53             reservenotes => undef,
54             constrainttype => 'a',
55             reservedate => '2008-10-09',
56             found => undef,
57             rtimestamp => '2008-10-09 11:15:06',
58             biblionumber => '406',
59             borrowernumber => '756',
60             branchcode => 'CPL'
61             }
62         ],
63     },
64     '660' => {
65         title => "Harry Potter y el cáliz de fuego",
66         id => '660',
67         sip_media_type => '001',
68         magnetic_media => 0,
69         hold_queue => [],
70     },
71 );
72 =cut
73
74 sub priority_sort {
75     defined $a->{priority} or return -1;
76     defined $b->{priority} or return 1;
77     return $a->{priority} <=> $b->{priority};
78 }
79
80 sub new {
81         my ($class, $item_id) = @_;
82         my $type = ref($class) || $class;
83         my $self;
84     my $itemnumber = GetItemnumberFromBarcode($item_id);
85         my $item = GetBiblioFromItemNumber($itemnumber);
86         if (! $item) {
87                 syslog("LOG_DEBUG", "new ILS::Item('%s'): not found", $item_id);
88                 warn "new ILS::Item($item_id) : No item '$item_id'.";
89                 return undef;
90         }
91     $item->{itemnumber} = $itemnumber;
92     $item->{'id'} = $item->{'barcode'};
93         # check if its on issue and if so get the borrower
94         my $issue = GetItemIssue($item->{'itemnumber'});
95         my $borrower = GetMember($issue->{'borrowernumber'},'borrowernumber');
96         $item->{patron} = $borrower->{'cardnumber'};
97         my @reserves = (@{ GetReservesFromBiblionumber($item->{biblionumber}) });
98         $item->{hold_queue} = [ sort priority_sort @reserves ];
99         $item->{hold_shelf}    = [( grep {   defined $_->{found}  and $_->{found} eq 'W' } @{$item->{hold_queue}} )];
100         $item->{pending_queue} = [( grep {(! defined $_->{found}) or ($_->{found} ne 'W')} @{$item->{hold_queue}} )];
101         $self = $item;
102         bless $self, $type;
103
104     syslog("LOG_DEBUG", "new ILS::Item('%s'): found with title '%s'",
105            $item_id, $self->{title});
106
107     return $self;
108 }
109
110 sub magnetic {
111     my $self = shift;
112     return $self->{magnetic_media};
113 }
114 sub sip_media_type {
115     my $self = shift;
116     return $self->{sip_media_type};
117 }
118 sub sip_item_properties {
119     my $self = shift;
120     return $self->{sip_item_properties};
121 }
122
123 sub status_update {     # FIXME: this looks unimplemented
124     my ($self, $props) = @_;
125     my $status = new ILS::Transaction;
126     $self->{sip_item_properties} = $props;
127     $status->{ok} = 1;
128     return $status;
129 }
130     
131 sub id {
132     my $self = shift;
133     return $self->{id};
134 }
135 sub title_id {
136     my $self = shift;
137     return $self->{title};
138 }
139 sub permanent_location {
140     my $self = shift;
141     return $self->{permanent_location} || '';
142 }
143 sub current_location {
144     my $self = shift;
145     return $self->{current_location} || '';
146 }
147
148 sub sip_circulation_status {
149     my $self = shift;
150     if ($self->{patron}) {
151                 return '04';    # charged
152     } elsif (scalar @{$self->{hold_queue}}) {
153                 return '08';    # waiting on hold shelf
154     } else {
155                 return '03';    # available
156     }                   # FIXME: 01-13 enumerated in spec.
157 }
158
159 sub sip_security_marker {
160     return '02';        # FIXME? 00-other; 01-None; 02-Tattle-Tape Security Strip (3M); 03-Whisper Tape (3M)
161 }
162 sub sip_fee_type {
163     return '01';    # FIXME? 01-09 enumerated in spec.  We just use O1-other/unknown.
164 }
165
166 sub fee {
167     my $self = shift;
168     return $self->{fee} || 0;
169 }
170 sub fee_currency {
171     my $self = shift;
172     return $self->{currency} || 'USD';
173 }
174 sub owner {
175     my $self = shift;
176     return 'CPL';       # FIXME: UWOLS was hardcoded 
177 }
178 sub hold_queue {
179     my $self = shift;
180         (defined $self->{hold_queue}) or return [];
181     return $self->{hold_queue};
182 }
183 sub pending_queue {
184     my $self = shift;
185         (defined $self->{pending_queue}) or return [];
186     return $self->{pending_queue};
187 }
188 sub hold_shelf {
189     my $self = shift;
190         (defined $self->{hold_shelf}) or return [];
191     return $self->{hold_shelf};
192 }
193
194 sub hold_queue_position {
195         my ($self, $patron_id) = @_;
196         ($self->{hold_queue}) or return 0;
197         my $i = 0;
198         foreach (@{$self->{hold_queue}}) {
199                 $i++;
200                 $_->{patron_id} or next;
201                 if ($self->barcode_is_borrowernumber($patron_id, $_->{borrowernumber})) {
202                         return $i;  # maybe should return $_->{priority}
203                 }
204         }
205     return 0;
206 }
207
208 sub due_date {
209     my $self = shift;
210     return $self->{due_date} || 0;
211 }
212 sub recall_date {
213     my $self = shift;
214     return $self->{recall_date} || 0;
215 }
216 sub hold_pickup_date {
217     my $self = shift;
218     return $self->{hold_pickup_date} || 0;
219 }
220 sub screen_msg {
221     my $self = shift;
222     return $self->{screen_msg} || '';
223 }
224 sub print_line {
225         my $self = shift;
226         return $self->{print_line} || '';
227 }
228
229 # This is a partial check of "availability".  It is not supposed to check everything here.
230 # An item is available for a patron if it is:
231 # 1) checked out to the same patron 
232 #    AND no pending (i.e. non-W) hold queue
233 # OR
234 # 2) not checked out
235 #    AND (not on hold_shelf OR is on hold_shelf for patron)
236 #
237 # What this means is we are consciously allowing the patron to checkout (but not renew) an item that DOES
238 # have non-W holds on it, but has not been "picked" from the stacks.  That is to say, the
239 # patron has retrieved the item before the librarian.
240 #
241 # We don't check if the patron is at the front of the pending queue in the first case, because
242 # they should not be able to place a hold on an item they already have.
243
244 sub available {
245         my ($self, $for_patron) = @_;
246         my $count  = (defined $self->{pending_queue}) ? scalar @{$self->{pending_queue}} : 0;
247         my $count2 = (defined $self->{hold_shelf}   ) ? scalar @{$self->{hold_shelf}   } : 0;
248         $debug and print STDERR "availability check: pending_queue size $count, hold_shelf size $count2\n";
249     if (defined($self->{patron_id})) {
250                 ($self->{patron_id} eq $for_patron) or return 0;
251                 return ($count ? 0 : 1);
252         } else {        # not checked out
253         ($count2) and return $self->barcode_is_borrowernumber($for_patron, $self->{hold_shelf}[0]->{borrowernumber});
254         }
255         return 0;
256 }
257
258 sub _barcode_to_borrowernumber ($) {
259     my $known = shift;
260     (defined($known)) or return undef;
261     my $member = GetMember($known,'cardnumber') or return undef;
262     return $member->{borrowernumber};
263 }
264 sub barcode_is_borrowernumber ($$$) {    # because hold_queue only has borrowernumber...
265     my $self = shift;   # not really used
266     my $barcode = shift;
267     my $number  = shift or return undef;    # can't be zero
268     (defined($barcode)) or return undef;    # might be 0 or 000 or 000000
269     my $converted = _barcode_to_borrowernumber($barcode) or return undef;
270     return ($number eq $converted); # even though both *should* be numbers, eq is safer.
271 }
272 sub fill_reserve ($$) {
273     my $self = shift;
274     my $hold = shift or return undef;
275     foreach (qw(biblionumber borrowernumber reservedate)) {
276         $hold->{$_} or return undef;
277     }
278     return ModReserveFill($hold);
279 }
280 1;
281 __END__
282