View | Details | Raw Unified | Return to bug 11897
Collapse All | Expand All

(-)a/Koha/Stockrotationitem.pm (-117 / +85 lines)
Lines 84-142 sub stage { Link Here
84
    return Koha::Stockrotationstage->_new_from_dbic( $rs );
84
    return Koha::Stockrotationstage->_new_from_dbic( $rs );
85
}
85
}
86
86
87
sub needs_initiating {
88
    my ( $self ) = @_;
89
    my $completed = $self->itemnumber->_result->branchtransfers->search;
90
    if ( !$completed->count ) {
91
        return 1;
92
    } else {
93
        my $stage_branch = $self->stage->branchcode_id;
94
        my $home_branch = $self->itemnumber->homebranch;
95
        return !( $stage_branch eq $home_branch );
96
    }
97
}
98
99
=head3 needs_repatriating
87
=head3 needs_repatriating
100
88
101
  my $status = $item->needs_repatriating;
89
  1|0 = $item->needs_repatriating;
102
90
103
Return 1 if this item is currently not at the library it should be at
91
Return 1 if this item is currently not at the library it should be at
104
according to our stockrotation plan.
92
according to our stockrotation plan.
105
93
106
=cut
94
=cut
107
95
108
# Check following things:
109
#
110
# Homebranch == stockrotationstage.branch
111
#   If not, check if Homebranch == stockrotationstage.stageafter.branch
112
#     If so, presumably in transit :=> 0
113
#     Else, :=> 1
114
#
115
# Holdingbranch == Homebranch
116
#   If not, :=> 1
117
118
sub needs_repatriating {
96
sub needs_repatriating {
119
    my ( $self ) = @_;
97
    my ( $self ) = @_;
120
    my ( $item, $stage ) = ( $self->itemnumber, $self->stage );
98
    my ( $item, $stage ) = ( $self->itemnumber, $self->stage );
121
    if ( $item->homebranch ne $stage->branchcode_id ) {
99
    if ( $self->itemnumber->get_transfer ) {
122
        # We're either in transit to new branch or we got corrupted.
100
        return 0;               # We're in transit.
123
        my $next_stage = $stage->next_sibling;
101
    } elsif ( $item->holdingbranch ne $stage->branchcode_id
124
        ( $next_stage && $item->homebranch eq $next_stage->branchcode_id )
102
                  || $item->homebranch ne $stage->branchcode_id ) {
125
            ? return 0          # We seem to be in transit.
103
        return 1;               # We're not where we should be.
126
            : return 1;         # We got corrupted, gotta be repatriated!
127
    }
128
    # Check if we are where we should be…
129
    if ( $item->holdingbranch eq $item->homebranch
130
             || $self->itemnumber->get_transfer ) {
131
        return 0;               # Yup, all good
132
    } else {
104
    } else {
133
        return 1;               # Nope, gotta be repatriated
105
        return 0;               # We're at home.
134
    }
106
    }
135
}
107
}
136
108
137
=head3 needs_advancing
109
=head3 needs_advancing
138
110
139
  my $status = $item->needs_advancing;
111
  1|0 = $item->needs_advancing;
140
112
141
Return 1 if this item is ready to be moved on to the next stage in its rota.
113
Return 1 if this item is ready to be moved on to the next stage in its rota.
142
114
Lines 144-202 Return 1 if this item is ready to be moved on to the next stage in its rota. Link Here
144
116
145
sub needs_advancing {
117
sub needs_advancing {
146
    my ( $self ) = @_;
118
    my ( $self ) = @_;
147
    my $intransfers = $self->itemnumber->get_transfer;
119
    return 0 if $self->itemnumber->get_transfer; # intransfer: don't advance.
148
    # Check whether we are in transfer
120
    return 1 if $self->fresh;                    # Just on rota: advance.
149
    if ( !$intransfers ) {
121
    my $completed = $self->itemnumber->_result->branchtransfers->search(
150
        my $completed = $self->itemnumber->_result->branchtransfers->search(
122
        { 'comments'    => "StockrotationAdvance" },
151
            {},
123
        { order_by => { -desc => 'datearrived' } }
152
            {
124
    );
153
                order_by => { -desc => 'datearrived' }
125
    # Do maths on whether we need to be moved on.
154
            }
126
    if ( $completed->count ) {
127
        my $arrival = dt_from_string(
128
            $completed->next->datearrived, 'iso'
155
        );
129
        );
156
        # Do maths on whether we need to be moved on.
130
        my $duration = DateTime::Duration
157
        if ( $completed->count ) {
131
            ->new( days => $self->stage->duration );
158
            my $arrival = dt_from_string(
132
        if ( $arrival + $duration le DateTime->now ) {
159
                $completed->next->datearrived, 'iso'
133
            return 1;
160
            );
161
            my $duration = DateTime::Duration
162
                ->new( days => $self->stage->duration );
163
            if ( $arrival + $duration le DateTime->now ) {
164
                return 1;
165
            } else {
166
                return 0;
167
            }
168
        } else {
134
        } else {
169
            die "We have no historical branch transfer; this should not have happened!";
135
            return 0;
170
        }
136
        }
171
    }
172
    # Currently in transfer, do not advance.
173
    return 0;
174
}
175
176
sub initiate {
177
    my ( $self ) = @_;
178
    my $stage_branch = $self->stage->branchcode_id;
179
    my $home_branch = $self->itemnumber->homebranch;
180
    my $holding_branch = $self->itemnumber->holdingbranch;
181
    if ( !( ( $stage_branch eq $home_branch )
182
             && ( $holding_branch eq $home_branch ) ) ) {
183
        return $self->repatriate("StockrotationInit");
184
    } else {
137
    } else {
185
        $self->itemnumber->homebranch($self->stage->branchcode_id)->store;
138
        die "We have no historical branch transfer; this should not have happened!";
186
        return Koha::Item::Transfer->new({
187
            'itemnumber'  => $self->itemnumber_id,
188
            'frombranch'  => $self->stage->branchcode_id,
189
            'tobranch'    => $self->stage->branchcode_id,
190
            'datesent'    => DateTime->now,
191
            'datearrived' => DateTime->now,
192
            'comments'    => "StockrotationInit"
193
        })->store;
194
    }
139
    }
195
}
140
}
196
141
197
=head3 repatriate
142
=head3 repatriate
198
143
199
  $sritem = $sritem->repatriate
144
  1|0 = $sritem->repatriate
200
145
201
Put this item into branch transfer with 'StockrotationCorrection' comment, so
146
Put this item into branch transfer with 'StockrotationCorrection' comment, so
202
that it may return to it's stage.branch to continue its rota as normal.
147
that it may return to it's stage.branch to continue its rota as normal.
Lines 219-225 sub repatriate { Link Here
219
164
220
=head3 advance
165
=head3 advance
221
166
222
  $sritem = $sritem->advance
167
  1|0 = $sritem->advance;
223
168
224
Put this item into branch transfer with 'StockrotationAdvance' comment, to
169
Put this item into branch transfer with 'StockrotationAdvance' comment, to
225
transfer it to the next stage in its rota.
170
transfer it to the next stage in its rota.
Lines 235-301 advance the item as per usual. Link Here
235
180
236
sub advance {
181
sub advance {
237
    my ( $self ) = @_;
182
    my ( $self ) = @_;
238
183
    my $item = $self->itemnumber;
239
    my $transfer = Koha::Item::Transfer->new({
184
    my $transfer = Koha::Item::Transfer->new({
240
        'itemnumber' => $self->itemnumber_id,
185
        'itemnumber' => $self->itemnumber_id,
241
        'frombranch' => $self->itemnumber->holdingbranch,
186
        'frombranch' => $item->holdingbranch,
242
        'datesent'   => DateTime->now,
187
        'datesent'   => DateTime->now,
243
        'comments'   => "StockrotationAdvance"
188
        'comments'   => "StockrotationAdvance"
244
    });
189
    });
245
190
246
    if ( $self->indemand ) {
191
    if ( $self->indemand && !$self->fresh ) {
247
        $self->indemand(0)->store;  # De-activate indemand
192
        $self->indemand(0)->store;  # De-activate indemand
248
        $transfer->tobranch($self->stage->branchcode_id);
193
        $transfer->tobranch($self->stage->branchcode_id);
249
        $transfer->datearrived(DateTime->now);
194
        $transfer->datearrived(DateTime->now);
250
    } else {
195
    } else {
251
        # Find and update our stage.
196
        # Find and update our stage.
252
        my $current_stage = $self->stage;
197
        my $stage = $self->stage;
253
        my $new_stage;
198
        my $new_stage;
254
        # Last stage in rota?
199
        if ( $self->fresh ) {   # Just added to rota
255
        if ( !$current_stage->last_sibling ) {
200
            $new_stage = $self->stage->first_sibling || $self->stage;
256
            # Special rules: Cyclical rota?
201
            $transfer->tobranch($new_stage->branchcode_id);
257
            if ( $current_stage->rota->cyclical ) {
202
            $transfer->datearrived(DateTime->now) # Already at first branch
203
                if $item->holdingbranch eq $new_stage->branchcode_id;
204
            $self->fresh(0)->store;         # Reset fresh
205
        } elsif ( !$stage->last_sibling ) { # Last stage
206
            if ( $stage->rota->cyclical ) { # Cyclical rota?
258
                # Revert to first stage.
207
                # Revert to first stage.
259
                my $xstg = $current_stage->first_sibling;
208
                $new_stage = $stage->first_sibling || $stage;
260
                # Is this the first (as well as last) stage?
209
                $transfer->tobranch($new_stage->branchcode_id);
261
                if ( $xstg ) {
210
                $transfer->datearrived(DateTime->now);
262
                    # ... No, so return to first stage.
263
                    $new_stage = $xstg;
264
                } else {
265
                    # ... Yes, so we create a dummy transfer (for statistics)
266
                    my $transfer_stored = Koha::Item::Transfer->new({
267
                        'itemnumber'  => $self->itemnumber_id,
268
                        'frombranch'  => $self->itemnumber->holdingbranch,
269
                        'tobranch'    => $self->itemnumber->holdingbranch,
270
                        'datesent'    => DateTime->now,
271
                        'datearrived' => DateTime->now,
272
                        'comments'    => "StockrotationAdvance"
273
                    })->store;
274
                    # and return.
275
                    return 1;
276
                }
277
                $new_stage = ( $xstg ) ? $xstg : $current_stage;
278
            } else {
211
            } else {
279
                # Stockrotationitem no longer needs to exist.
212
                $self->delete;  # Stockrotationitem is done.
280
                $self->delete;
281
                return 1;
213
                return 1;
282
            }
214
            }
283
        } else {
215
        } else {
284
            # Just advance.
216
            # Just advance.
285
            $new_stage = $self->stage->next_sibling;
217
            $new_stage = $self->stage->next_sibling;
286
        }
218
        }
287
219
        $self->stage_id($new_stage->stage_id)->store;        # Set new stage
288
        # Update our stage.
220
        $item->homebranch($new_stage->branchcode_id)->store; # Update homebranch
289
        $self->stage_id($new_stage->stage_id)->store;
221
        $transfer->tobranch($new_stage->branchcode_id);      # Send to new branch
290
        # Update our homebranch.
291
        $self->itemnumber->homebranch($self->stage->branchcode_id)->store;
292
        #Update the transfer
293
        $transfer->tobranch($new_stage->branchcode_id);
294
    }
222
    }
295
223
296
    return $transfer->store;
224
    return $transfer->store;
297
}
225
}
298
226
227
=head3 investigate
228
229
  my $report = $item->investigate;
230
231
Return the base set of information, namely this individual item's report, for
232
generating stockrotation reports about this stockrotationitem.
233
234
=cut
235
236
sub investigate {
237
    my ( $self ) = @_;
238
    my $item_report = {
239
        title      => $self->itemnumber->_result->biblioitem
240
            ->biblionumber->title,
241
        author     => $self->itemnumber->_result->biblioitem
242
            ->biblionumber->author,
243
        callnumber => $self->itemnumber->itemcallnumber,
244
        location   => $self->itemnumber->location,
245
        onloan     => $self->itemnumber->onloan,
246
        barcode    => $self->itemnumber->barcode,
247
        itemnumber => $self->itemnumber_id,
248
        branch => $self->itemnumber->_result->holdingbranch,
249
        object => $self,
250
    };
251
    my $reason;
252
    if ( $self->fresh ) {
253
        $reason = 'initiation';
254
    } elsif ( $self->needs_repatriating ) {
255
        $reason = 'repatriation';
256
    } elsif ( $self->needs_advancing ) {
257
        $reason = 'advancement';
258
        $reason = 'in-demand' if $self->indemand;
259
    } else {
260
        $reason = 'not-ready';
261
    }
262
    $item_report->{reason} = $reason;
263
264
    return $item_report;
265
}
266
299
1;
267
1;
300
268
301
=head1 AUTHOR
269
=head1 AUTHOR
(-)a/Koha/Stockrotationitems.pm (+59 lines)
Lines 55-60 sub object_class { Link Here
55
    return 'Koha::Stockrotationitem';
55
    return 'Koha::Stockrotationitem';
56
}
56
}
57
57
58
=head3 investigate
59
60
  my $report = $items->investigate;
61
62
Return a stockrotation report about this set of stockrotationitems.
63
64
In this part of the overall investigation process we split individual item
65
reports into appropriate action segments of our items report and increment
66
some counters.
67
68
The report generated here will be used on the stage level to slot our item
69
reports into appropriate sections of the branched report.
70
71
For details of intent and context of this procedure, please see
72
Koha::Stockrotationrota->investigate.
73
74
=cut
75
76
sub investigate {
77
    my ( $self ) = @_;
78
79
    my $items_report = {
80
        items => [],
81
        log => [],
82
        initiable_items => [],
83
        repatriable_items => [],
84
        advanceable_items => [],
85
        indemand_items => [],
86
        actionable => 0,
87
        stationary => 0,
88
    };
89
    while ( my $item = $self->next ) {
90
        my $report = $item->investigate;
91
        if ( $report->{reason} eq 'initiation' ) {
92
            $items_report->{initiable}++;
93
            $items_report->{actionable}++;
94
            push @{$items_report->{items}}, $report;
95
            push @{$items_report->{initiable_items}}, $report;
96
        } elsif ( $report->{reason} eq 'repatriation' ) {
97
            $items_report->{repatriable}++;
98
            $items_report->{actionable}++;
99
            push @{$items_report->{items}}, $report;
100
            push @{$items_report->{repatriable_items}}, $report;
101
        } elsif ( $report->{reason} eq 'advancement' ) {
102
            $items_report->{actionable}++;
103
            push @{$items_report->{items}}, $report;
104
            push @{$items_report->{advanceable_items}}, $report;
105
        } elsif ( $report->{reason} eq 'in-demand' ) {
106
            $items_report->{actionable}++;
107
            push @{$items_report->{items}}, $report;
108
            push @{$items_report->{indemand_items}}, $report;
109
        } else {
110
            $items_report->{stationary}++;
111
            push @{$items_report->{log}}, $report;
112
        }
113
    }
114
115
    return $items_report;
116
}
58
117
59
1;
118
1;
60
119
(-)a/Koha/Stockrotationrota.pm (-6 / +53 lines)
Lines 72-86 a rota, move it from that rota to this rota. Link Here
72
sub add_item {
72
sub add_item {
73
    my ( $self, $itemnumber ) = @_;
73
    my ( $self, $itemnumber ) = @_;
74
    my $sritem = Koha::Stockrotationitems->find($itemnumber);
74
    my $sritem = Koha::Stockrotationitems->find($itemnumber);
75
    my $stages = $self->stockrotationstages;
76
    die "Rota is missing stages." unless ( $stages->count > 0 );
77
    if ($sritem) {
75
    if ($sritem) {
78
        $sritem->stage_id($stages->next->stage_id)->store;
76
        $sritem->stage_id($self->first_stage->stage_id)
77
            ->indemand(0)->fresh(1)->store;
79
    } else {
78
    } else {
80
        $sritem = Koha::Stockrotationitem->new({
79
        $sritem = Koha::Stockrotationitem->new({
81
            itemnumber_id => $itemnumber,
80
            itemnumber_id => $itemnumber,
82
            stage_id      => $stages->next->stage_id,
81
            stage_id      => $self->first_stage->stage_id,
83
            indemand      => 0,
82
            indemand      => 0,
83
            fresh         => 1,
84
        })->store;
84
        })->store;
85
    }
85
    }
86
    return $self;
86
    return $self;
Lines 102-110 sub first_stage { Link Here
102
    return ( $stage ) ? $stage : $guess;
102
    return ( $stage ) ? $stage : $guess;
103
}
103
}
104
104
105
=head3 items
105
=head3 stockrotationitems
106
106
107
  my $items = $rota->items;
107
  my $items = $rota->stockrotationitems;
108
108
109
Return all items associated with this rota via its stages.
109
Return all items associated with this rota via its stages.
110
110
Lines 118-123 sub stockrotationitems { Link Here
118
    return $rs;
118
    return $rs;
119
}
119
}
120
120
121
=head3 investigate
122
123
  my $report = $rota->investigate($report_so_far);
124
125
Aim here is to return $report augmented with content for this rota.  We
126
delegate to $stage->investigate.
127
128
The report will include some basic information and 2 primary reports:
129
130
- per rota report in 'rotas'. This report is mainly used by admins to do check
131
  & compare results.
132
133
- branched report in 'branched'.  This is the workhorse: emails to libraries
134
  are compiled from these reports, and they will have the actionable work.
135
136
Both reports are generated in stage based investigations; the rota report is
137
then glued into place at this stage.
138
139
=cut
140
141
sub investigate {
142
    my ( $self, $report ) = @_;
143
    my $count = $self->stockrotationitems->count;
144
    $report->{sum_items} += $count;
145
146
    if ( $self->active ) {
147
        $report->{rotas_active}++;
148
        # stockrotationstages->investigate augments $report with the stage's
149
        # content.  This is how 'branched' slowly accumulates all items.
150
        $report = $self->stockrotationstages->investigate($report);
151
        # Add our rota report to the full report.
152
        push @{$report->{rotas}}, {
153
            name  => $self->title,
154
            id    => $self->rota_id,
155
            items => $report->{tmp_items},
156
            log   => $report->{tmp_log},
157
        };
158
        delete $report->{tmp_items};
159
        delete $report->{tmp_log};
160
    } else {                    # Rota is not active.
161
        $report->{rotas_inactive}++;
162
        $report->{items_inactive} += $count;
163
    }
164
165
    return $report;
166
}
167
121
=head3 _type
168
=head3 _type
122
169
123
=cut
170
=cut
(-)a/Koha/Stockrotationrotas.pm (+37 lines)
Lines 43-48 Standard Koha::Objects definitions, and additional methods. Link Here
43
43
44
=cut
44
=cut
45
45
46
=head3 investigate
47
48
  my $report = $rotas->investigate;
49
50
Return a report detailing the current status and required actions for all
51
relevant items spread over rotas.
52
53
See Koha::Stockrotationrota->investigate for details.
54
55
=cut
56
57
sub investigate {
58
    my ( $self ) = @_;
59
60
    my $report = {
61
        actionable     => 0,
62
        advanceable    => 0,
63
        initiable      => 0,
64
        items_inactive => 0,
65
        repatriable    => 0,
66
        rotas_active   => 0,
67
        rotas_inactive => 0,
68
        stationary     => 0,
69
        sum_items      => 0,
70
        sum_rotas      => $self->count,
71
        branched       => {},
72
        rotas          => [],
73
        items          => [],
74
    };
75
76
    while ( my $rota = $self->next ) {
77
        $report = $rota->investigate($report)
78
    }
79
80
    return $report;
81
}
82
46
=head3 _type
83
=head3 _type
47
84
48
=cut
85
=cut
(-)a/Koha/Stockrotationstage.pm (-1 / +119 lines)
Lines 21-27 use Modern::Perl; Link Here
21
21
22
use Koha::Database;
22
use Koha::Database;
23
use Koha::Library;
23
use Koha::Library;
24
use Koha::Stockrotationitems;
25
use Koha::Stockrotationrota;
24
use Koha::Stockrotationrota;
26
25
27
use base qw(Koha::Object);
26
use base qw(Koha::Object);
Lines 286-291 sub delete { Link Here
286
    return $self->_result->delete;
285
    return $self->_result->delete;
287
}
286
}
288
287
288
=head3 investigate
289
290
  my $report = $stage->investigate($report_so_far);
291
292
Return a stage based report.  This report will mutate and augment the report
293
that is passed to it.  It slots item reports into the branched and temporary
294
rota sections of the report.  It also increments a number of counters.
295
296
For details of intent and context of this procedure, please see
297
Koha::Stockrotationrota->investigate.
298
299
=cut
300
301
sub investigate {
302
    my ( $self, $report ) = @_;
303
    my $new_stage = $self->next_sibling;
304
    my $duration = $self->duration;
305
    # Generate stage items report
306
    my $items_report = $self->stockrotationitems->investigate;
307
308
    # Merge into general report
309
310
    ## Branched indexes
311
    ### The branched indexes work as follows:
312
    ### - They contain information about the relevant branch
313
    ### - They contain an index of actionable items for that branch
314
    ### - They contain an index of non-actionable items for that branch
315
316
    ### Items are assigned to a particular branched index as follows:
317
    ### - 'advanceable' : assigned to branch of the current stage
318
    ###   (this should also be the current holding branch)
319
    ### - 'log' items are always assigned to branch of current stage.
320
    ### - 'indemand' : assigned to branch of current stage
321
    ###   (this should also be the current holding branch)
322
    ### - 'initiable' : assigned to the current holding branch of item
323
    ### - 'repatriable' : assigned to the current holding branch of item
324
325
    ### 'Advanceable', 'log', 'indemand':
326
327
    # Set up our stage branch info.
328
    my $stagebranch = $self->_result->branchcode;
329
    my $stagebranchcode = $stagebranch->branchcode;
330
331
    # Initiate our stage branch index if it does not yet exist.
332
    if ( !$report->{branched}->{$stagebranchcode} ) {
333
        $report->{branched}->{$stagebranchcode} = {
334
            code  => $stagebranchcode,
335
            name  => $stagebranch->branchname,
336
            email => $stagebranch->branchemail,
337
            phone => $stagebranch->branchphone,
338
            items => [],
339
            log => [],
340
        };
341
    }
342
343
    push @{$report->{branched}->{$stagebranchcode}->{items}},
344
        @{$items_report->{advanceable_items}};
345
    push @{$report->{branched}->{$stagebranchcode}->{log}},
346
        @{$items_report->{log}};
347
    push @{$report->{branched}->{$stagebranchcode}->{items}},
348
        @{$items_report->{indemand_items}};
349
350
    ### 'Initiable' & 'Repatriable'
351
    foreach my $ireport (@{$items_report->{initiable_items}}) {
352
        my $branch = $ireport->{branch};
353
        my $branchcode = $branch->branchcode;
354
        if ( !$report->{branched}->{$branchcode} ) {
355
            $report->{branched}->{$branchcode} = {
356
                code  => $branchcode,
357
                name  => $branch->branchname,
358
                email => $branch->branchemail,
359
                phone => $branch->branchphone,
360
                items => [],
361
                log => [],
362
            };
363
        }
364
        push @{$report->{branched}->{$branchcode}->{items}}, $ireport;
365
    }
366
367
    foreach my $ireport (@{$items_report->{repatriable_items}}) {
368
        my $branch = $ireport->{branch};
369
        my $branchcode = $branch->branchcode;
370
        if ( !$report->{branched}->{$branchcode} ) {
371
            $report->{branched}->{$branchcode} = {
372
                code  => $branchcode,
373
                name  => $branch->branchname,
374
                email => $branch->branchemail,
375
                phone => $branch->branchphone,
376
                items => [],
377
                log => [],
378
            };
379
        }
380
        push @{$report->{branched}->{$branchcode}->{items}}, $ireport;
381
    }
382
383
    ## Per rota indexes
384
    ### Per rota indexes are item reports pushed into the index for the
385
    ### current rota.  We don't know where that index is yet as we don't know
386
    ### about the current rota.  To resolve this we assign our items and log
387
    ### to tmp indexes.  They will be merged into the proper rota index at the
388
    ### rota level.
389
    push @{$report->{tmp_items}}, @{$items_report->{items}};
390
    push @{$report->{tmp_log}}, @{$items_report->{log}};
391
392
    ## Collection of items
393
    ### Finally we just add our collection of items to the full item index.
394
    push @{$report->{items}}, @{$items_report->{items}};
395
396
    ## Assemble counters
397
    $report->{actionable} += $items_report->{actionable};
398
    $report->{indemand} += scalar @{$items_report->{indemand_items}};
399
    $report->{advanceable} += scalar @{$items_report->{advanceable_items}};
400
    $report->{initiable} += scalar @{$items_report->{initiable_items}};
401
    $report->{repatriable} += scalar @{$items_report->{repatriable_items}};
402
    $report->{stationary} += scalar @{$items_report->{log}};
403
404
    return $report;
405
}
406
289
1;
407
1;
290
408
291
=head1 AUTHOR
409
=head1 AUTHOR
(-)a/Koha/Stockrotationstages.pm (+22 lines)
Lines 43-48 Standard Koha::Objects definitions, and additional methods. Link Here
43
43
44
=cut
44
=cut
45
45
46
=head3 investigate
47
48
  my $report = $stages->investigate($rota_so_far);
49
50
Return a report detailing the current status and required actions for all
51
relevant items spread over the set of stages.
52
53
For details of intent and context of this procedure, please see
54
Koha::Stockrotationrota->investigate.
55
56
=cut
57
58
sub investigate {
59
    my ( $self, $report ) = @_;
60
61
    while ( my $stage = $self->next ) {
62
        $report = $stage->investigate($report);
63
    }
64
65
    return $report;
66
}
67
46
=head3 _type
68
=head3 _type
47
69
48
=cut
70
=cut
(-)a/misc/cronjobs/stockrotation.pl (-159 / +21 lines)
Lines 117-262 $send_email++ if ( $send_all ); # if we send all, then we must want emails. Link Here
117
117
118
=head2 Helpers
118
=head2 Helpers
119
119
120
=head3 investigate
121
122
  my $report = investigate($rotas);
123
124
Return a datastructure which contains a wealth of information about the
125
current state of the stockrotation submodule, and which can be used by
126
`report_full`, `report_email` and `execute` to either emit reports or perform
127
database updates.
128
129
$ROTAS should be an iterable set of Koha::Stockrotationrotas.
130
131
No data in the database is manipulated by this procedure.
132
133
=cut
134
135
sub investigate {
136
    my ( $rotas ) = @_;
137
    # Initiate return variables.
138
    my $all_relevant_items = [];
139
    my $all_relevant_rotas = [];
140
    my $branched = {};
141
    my $rotas_active = 0;
142
    my $rotas_inactive = 0;
143
    my $sum_items = 0;
144
    my $items_inactive = 0;
145
    my $stationary = 0;
146
    my $advanceable = 0;
147
    my $expulseable = 0;
148
    my $repatriable = 0;
149
    my $initiable = 0;
150
    my $actionable = 0;
151
152
    # Each 'rota' entry in `rotas` will contain an `items` section with items
153
    # to be moved, and a `logs` section, with items that will not be moved,
154
    # with a reason for not moving it.
155
    while ( my $rota = $rotas->next ) {
156
        if ( $rota->active ) {
157
            $rotas_active++;
158
            my $items = [];
159
            my $log = [];
160
            my $stages = $rota->stockrotationstages;
161
            while ( my $stage = $stages->next) {
162
                my $old_stage = $stage;
163
                my $new_stage = $stage->next_sibling;
164
                my $duration = $stage->duration;
165
                my $branch = $old_stage->_result->branchcode;
166
                my $branchcode = $branch->branchcode;
167
                $branched->{$branchcode} = {
168
                    code  => $branchcode,
169
                    name  => $branch->branchname,
170
                    email => $branch->branchemail,
171
                    phone => $branch->branchphone,
172
                    items => [],
173
                };
174
                my $stage_items = $stage->stockrotationitems;
175
                while ( my $item = $stage_items->next ) {
176
                    $sum_items++;
177
                    # Generate item details
178
                    my $item_report = {
179
                        title      => $item->itemnumber->_result->biblioitem
180
                            ->biblionumber->title,
181
                        author     => $item->itemnumber->_result->biblioitem
182
                            ->biblionumber->author,
183
                        callnumber => $item->itemnumber->itemcallnumber,
184
                        location   => $item->itemnumber->location,
185
                        onloan     => $item->itemnumber->onloan,
186
                        barcode    => $item->itemnumber->barcode,
187
                        itemnumber => $item->itemnumber_id,
188
                        old_stage  => $old_stage,
189
                        new_stage  => $new_stage,
190
                        object     => $item,
191
                    };
192
193
                    # Test whether we need to perform an action on item.
194
                    my $flag = 0;
195
                    if ( $item->needs_initiating ) {
196
                        $flag++;
197
                        $initiable++;
198
                        $actionable++;
199
                        $item_report->{reason} = 'initiation';
200
                    } elsif ( $item->needs_repatriating ) {
201
                        $flag++;
202
                        $repatriable++;
203
                        $actionable++;
204
                        $item_report->{reason} = 'repatriation';
205
                    } elsif ( $item->needs_advancing ) {
206
                        $flag++;
207
                        $actionable++;
208
                        if ( !$new_stage ) {
209
                            $expulseable++;
210
                        } else {
211
                            $advanceable++;
212
                        }
213
                        $item_report->{reason} =
214
                            ( $item->indemand ) ? 'in-demand' : 'advancement';
215
                    }
216
217
                    if ( $flag ) { # Item will be moved.
218
                        push @{$items}, $item_report;
219
                        push @{$all_relevant_items}, $item_report;
220
                        push @{$branched->{$branchcode}->{items}}, $item_report;
221
                    } else {       # Item not ready to be moved.
222
                        $stationary++;
223
                        $item_report->{reason} = 'not-ready';
224
                        push @{$log}, $item_report;
225
                    }
226
                }
227
            }
228
            # Add our rota report to the full report.
229
            push @{$all_relevant_rotas}, {
230
                name  => $rota->title,
231
                id    => $rota->rota_id,
232
                items => $items,
233
                log   => $log,
234
            };
235
        } else {                # Rota is not active.
236
            $rotas_inactive++;
237
            $sum_items += $rota->stockrotationitems->count;
238
            $items_inactive += $rota->stockrotationitems->count;
239
        }
240
    }
241
242
    return {
243
        sum_rotas          => $rotas->count,
244
        rotas_inactive     => $rotas_inactive,
245
        rotas_active       => $rotas_active,
246
        sum_items          => $sum_items,
247
        items_inactive     => $items_inactive,
248
        stationary         => $stationary,
249
        actionable         => $actionable,
250
        initiable          => $initiable,
251
        repatriable        => $repatriable,
252
        advanceable        => $advanceable,
253
        expulseable        => $expulseable,
254
        rotas              => $all_relevant_rotas,
255
        items              => $all_relevant_items,
256
        branched           => $branched,
257
    }
258
}
259
260
=head3 execute
120
=head3 execute
261
121
262
  undef = execute($report);
122
  undef = execute($report);
Lines 280-290 sub execute { Link Here
280
    # Carry out db updates
140
    # Carry out db updates
281
    foreach my $item (@{$data->{items}}) {
141
    foreach my $item (@{$data->{items}}) {
282
        my $reason = $item->{reason};
142
        my $reason = $item->{reason};
283
        if ( $reason eq 'initiation' ) {
143
        if ( $reason eq 'repatriation' ) {
284
            $item->{object}->initiate;
285
        } elsif ( $reason eq 'repatriation' ) {
286
            $item->{object}->repatriate;
144
            $item->{object}->repatriate;
287
        } elsif ( $reason eq 'in-demand' || $reason eq 'advancement' ) {
145
        } elsif ( grep { $reason eq $_ }
146
                      qw/in-demand advancement initiation/ ) {
288
            $item->{object}->advance;
147
            $item->{object}->advance;
289
        }
148
        }
290
    }
149
    }
Lines 325-336 STOCKROTATION REPORT Link Here
325
  Total items to be initiated:   %5u
184
  Total items to be initiated:   %5u
326
  Total items to be repatriated: %5u
185
  Total items to be repatriated: %5u
327
  Total items to be advanced:    %5u
186
  Total items to be advanced:    %5u
328
  Total items to be expulsed:    %5u\n\n",
187
  Total items in demand:         %5u\n\n",
329
      $data->{sum_rotas}, $data->{rotas_inactive}, $data->{rotas_active},
188
      $data->{sum_rotas}, $data->{rotas_inactive}, $data->{rotas_active},
330
      $data->{sum_items}, $data->{items_inactive},
189
      $data->{sum_items}, $data->{items_inactive}, $data->{stationary},
331
      $data->{stationary}, $data->{actionable},
190
      $data->{actionable}, $data->{initiable}, $data->{repatriable},
332
      $data->{initiable}, $data->{repatriable},
191
      $data->{advanceable}, $data->{indemand};
333
      $data->{advanceable}, $data->{expulseable};
334
192
335
    if (@{$data->{rotas}}) {    # Per Rota details
193
    if (@{$data->{rotas}}) {    # Per Rota details
336
        $body .= sprintf "ROTAS DETAIL\n------------\n\n";
194
        $body .= sprintf "ROTAS DETAIL\n------------\n\n";
Lines 441-447 sub _report_per_branch { Link Here
441
    } else {
299
    } else {
442
        $out .= sprintf "No items to be processed for this branch.\n\n";
300
        $out .= sprintf "No items to be processed for this branch.\n\n";
443
    }
301
    }
444
    $out .= join("", map { _print_item($_) } @{$per_branch->{items}});
302
    $out .= join("", map {
303
        _print_item($_) unless $_->{reason} eq 'in-demand'
304
    } @{$per_branch->{items}});
445
    return {
305
    return {
446
        body          => $out,                 # The body of the report
306
        body          => $out,                 # The body of the report
447
        email_address => $per_branch->{email}, # The branch email address
307
        email_address => $per_branch->{email}, # The branch email address
Lines 465-481 No data in the database is manipulated by this procedure. Link Here
465
sub _print_item {
325
sub _print_item {
466
    my ( $item ) = @_;
326
    my ( $item ) = @_;
467
    return sprintf "
327
    return sprintf "
468
    Title:      %s
328
    Title:           %s
469
    Author:     %s
329
    Author:          %s
470
    Callnumber: %s
330
    Callnumber:      %s
471
    Location:   %s
331
    Location:        %s
472
    Barcode:    %s
332
    Barcode:         %s
473
    On loan?:   %s
333
    On loan?:        %s
474
    Status:     %s\n\n",
334
    Status:          %s
335
    Current Library: %s [%s]\n\n",
475
          $item->{title} || "N/A", $item->{author} || "N/A",
336
          $item->{title} || "N/A", $item->{author} || "N/A",
476
          $item->{callnumber} || "N/A", $item->{location} || "N/A",
337
          $item->{callnumber} || "N/A", $item->{location} || "N/A",
477
          $item->{barcode} || "N/A", $item->{onloan} ?  'Yes' : 'No',
338
          $item->{barcode} || "N/A", $item->{onloan} ?  'Yes' : 'No',
478
          $item->{reason} || "N/A";
339
          $item->{reason} || "N/A", $item->{branch}->branchname,
340
          $item->{branch}->branchcode;
479
}
341
}
480
342
481
=head3 emit
343
=head3 emit
Lines 557-563 sub emit { Link Here
557
419
558
# Compile Stockrotation Report data
420
# Compile Stockrotation Report data
559
my $rotas = Koha::Stockrotationrotas->search;
421
my $rotas = Koha::Stockrotationrotas->search;
560
my $data = investigate($rotas);
422
my $data = $rotas->investigate;
561
423
562
# Perform db updates if requested
424
# Perform db updates if requested
563
execute($data) if ( $execute );
425
execute($data) if ( $execute );
(-)a/t/db_dependent/Stockrotationitems.t (-7 / +147 lines)
Lines 20-30 Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use DateTime;
22
use DateTime;
23
use DateTime::Duration;
23
use Koha::Database;
24
use Koha::Database;
24
use Koha::Item::Transfer;
25
use Koha::Item::Transfer;
25
use t::lib::TestBuilder;
26
use t::lib::TestBuilder;
26
27
27
use Test::More tests => 7;
28
use Test::More tests => 8;
28
29
29
my $schema = Koha::Database->new->schema;
30
my $schema = Koha::Database->new->schema;
30
31
Lines 146-157 subtest "Tests for repatriate." => sub { Link Here
146
};
147
};
147
148
148
subtest "Tests for needs_advancing." => sub {
149
subtest "Tests for needs_advancing." => sub {
149
    plan tests => 3;
150
    plan tests => 6;
150
    $schema->storage->txn_begin;
151
    $schema->storage->txn_begin;
151
152
152
    # Setup a pristine stockrotation context.
153
    # Test behaviour of item freshly added to rota.
153
    my $sritem = $builder->build({ source => 'Stockrotationitem' });
154
    my $sritem = $builder->build({
155
        source => 'Stockrotationitem',
156
        value  => { 'fresh' => 1, },
157
    });
154
    my $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
158
    my $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
159
    is($dbitem->needs_advancing, 1, "An item that is fresh will always need advancing.");
160
161
    # Setup a pristine stockrotation context.
162
    $sritem = $builder->build({
163
        source => 'Stockrotationitem',
164
        value => { 'fresh' => 0,}
165
    });
166
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
155
    $dbitem->itemnumber->homebranch($dbitem->stage->branchcode_id);
167
    $dbitem->itemnumber->homebranch($dbitem->stage->branchcode_id);
156
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id);
168
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id);
157
    $dbitem->stage->position(1);
169
    $dbitem->stage->position(1);
Lines 163-177 subtest "Tests for needs_advancing." => sub { Link Here
163
        'tobranch'    => $dbitem->stage->branchcode_id,
175
        'tobranch'    => $dbitem->stage->branchcode_id,
164
        'datesent'    => DateTime->now,
176
        'datesent'    => DateTime->now,
165
        'datearrived' => undef,
177
        'datearrived' => undef,
166
        'comments'    => "Test item",
178
        'comments'    => "StockrotationAdvance",
167
    })->store;
179
    })->store;
168
180
169
    # Test item will not be advanced if in transit.
181
    # Test item will not be advanced if in transit.
170
    is($dbitem->needs_advancing, 0, "Not ready to advance: in transfer.");
182
    is($dbitem->needs_advancing, 0, "Not ready to advance: in transfer.");
183
    # Test item will not be advanced if in transit even if fresh.
184
    $dbitem->fresh(1)->store;
185
    is($dbitem->needs_advancing, 0, "Not ready to advance: in transfer (fresh).");
186
    $dbitem->fresh(0)->store;
171
187
172
    # Test item will not be advanced if it has not spent enough time.
188
    # Test item will not be advanced if it has not spent enough time.
173
    $dbtransfer->datearrived(DateTime->now)->store;
189
    $dbtransfer->datearrived(DateTime->now)->store;
174
    is($dbitem->needs_advancing, 0, "Not ready to advance: Not spent enough time.");
190
    is($dbitem->needs_advancing, 0, "Not ready to advance: Not spent enough time.");
191
    # Test item will be advanced if it has not spent enough time, but is fresh.
192
    $dbitem->fresh(1)->store;
193
    is($dbitem->needs_advancing, 1, "Advance: Not spent enough time, but fresh.");
194
    $dbitem->fresh(0)->store;
175
195
176
    # Test item will be advanced if it has spent enough time.
196
    # Test item will be advanced if it has spent enough time.
177
    $dbtransfer->datesent(      # Item was sent 100 days ago...
197
    $dbtransfer->datesent(      # Item was sent 100 days ago...
Lines 186-195 subtest "Tests for needs_advancing." => sub { Link Here
186
};
206
};
187
207
188
subtest "Tests for advance." => sub {
208
subtest "Tests for advance." => sub {
189
    plan tests => 12;
209
    plan tests => 15;
190
    $schema->storage->txn_begin;
210
    $schema->storage->txn_begin;
191
211
192
    my $sritem = $builder->build({ source => 'Stockrotationitem' });
212
    my $sritem = $builder->build({
213
        source => 'Stockrotationitem',
214
        value => { 'fresh' => 1 }
215
    });
193
    my $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
216
    my $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
194
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id);
217
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id);
195
    my $dbstage = $dbitem->stage;
218
    my $dbstage = $dbitem->stage;
Lines 200-205 subtest "Tests for advance." => sub { Link Here
200
    # Sanity check
223
    # Sanity check
201
    is($dbitem->stage->stage_id, $dbstage->stage_id, "Stage sanity check.");
224
    is($dbitem->stage->stage_id, $dbstage->stage_id, "Stage sanity check.");
202
225
226
    # Test if an item is fresh, always move to first stage.
227
    is($dbitem->fresh, 1, "Fresh is correct.");
228
    $dbitem->advance;
229
    is($dbitem->stage->stage_id, $dbstage->stage_id, "Stage is first stage after fresh advance.");
230
    is($dbitem->fresh, 0, "Fresh reset after advance.");
231
203
    # Test cases of single stage
232
    # Test cases of single stage
204
    $dbstage->rota->cyclical(1)->store;         # Set Rota to cyclical.
233
    $dbstage->rota->cyclical(1)->store;         # Set Rota to cyclical.
205
    ok($dbitem->advance, "Single stage cyclical advance done.");
234
    ok($dbitem->advance, "Single stage cyclical advance done.");
Lines 250-253 subtest "Tests for advance." => sub { Link Here
250
    $schema->storage->txn_rollback;
279
    $schema->storage->txn_rollback;
251
};
280
};
252
281
282
subtest "Tests for investigate (singular)." => sub {
283
    plan tests => 7;
284
    $schema->storage->txn_begin;
285
286
    # Test brand new item's investigation ['initiation']
287
    my $sritem = $builder->build({ source => 'Stockrotationitem', value => { fresh => 1 } });
288
    my $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
289
    is($dbitem->investigate->{reason}, 'initiation', "fresh item initiates.");
290
291
    # Test brand new item at stagebranch ['initiation']
292
    $sritem = $builder->build({ source => 'Stockrotationitem', value => { fresh => 1 } });
293
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
294
    $dbitem->itemnumber->homebranch($dbitem->stage->branchcode_id)->store;
295
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id)->store;
296
    is($dbitem->investigate->{reason}, 'initiation', "fresh item at stagebranch initiates.");
297
298
    # Test item not at stagebranch with branchtransfer history ['repatriation']
299
    $sritem = $builder->build({
300
        source => 'Stockrotationitem',
301
        value => { 'fresh'       => 0,}
302
    });
303
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
304
    my $dbtransfer = Koha::Item::Transfer->new({
305
        'itemnumber'  => $dbitem->itemnumber_id,
306
        'frombranch'  => $dbitem->itemnumber->homebranch,
307
        'tobranch'    => $dbitem->itemnumber->homebranch,
308
        'datesent'    => DateTime->now,
309
        'datearrived' => DateTime->now,
310
        'comments'    => "StockrotationAdvance",
311
    })->store;
312
    is($dbitem->investigate->{reason}, 'repatriation', "older item repatriates.");
313
314
    # Test item at stagebranch with branchtransfer history ['not-ready']
315
    $sritem = $builder->build({
316
        source => 'Stockrotationitem',
317
        value => { 'fresh'       => 0,}
318
    });
319
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
320
    $dbtransfer = Koha::Item::Transfer->new({
321
        'itemnumber'  => $dbitem->itemnumber_id,
322
        'frombranch'  => $dbitem->itemnumber->homebranch,
323
        'tobranch'    => $dbitem->stage->branchcode_id,
324
        'datesent'    => DateTime->now,
325
        'datearrived' => DateTime->now,
326
        'comments'    => "StockrotationAdvance",
327
    })->store;
328
    $dbitem->itemnumber->homebranch($dbitem->stage->branchcode_id)->store;
329
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id)->store;
330
    is($dbitem->investigate->{reason}, 'not-ready', "older item at stagebranch not-ready.");
331
332
    # Test item due for advancement ['advancement']
333
    $sritem = $builder->build({ source => 'Stockrotationitem', value => { fresh => 0 } });
334
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
335
    $dbitem->indemand(0)->store;
336
    $dbitem->stage->duration(50)->store;
337
    my $sent_duration =  DateTime::Duration->new( days => 55);
338
    my $arrived_duration =  DateTime::Duration->new( days => 52);
339
    $dbtransfer = Koha::Item::Transfer->new({
340
        'itemnumber'  => $dbitem->itemnumber_id,
341
        'frombranch'  => $dbitem->itemnumber->homebranch,
342
        'tobranch'    => $dbitem->stage->branchcode_id,
343
        'datesent'    => DateTime->now - $sent_duration,
344
        'datearrived' => DateTime->now - $arrived_duration,
345
        'comments'    => "StockrotationAdvance",
346
    })->store;
347
    $dbitem->itemnumber->homebranch($dbitem->stage->branchcode_id)->store;
348
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id)->store;
349
    is($dbitem->investigate->{reason}, 'advancement',
350
       "Item ready for advancement.");
351
352
    # Test item due for advancement but in-demand ['in-demand']
353
    $sritem = $builder->build({ source => 'Stockrotationitem', value => { fresh => 0 } });
354
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
355
    $dbitem->indemand(1)->store;
356
    $dbitem->stage->duration(50)->store;
357
    $sent_duration =  DateTime::Duration->new( days => 55);
358
    $arrived_duration =  DateTime::Duration->new( days => 52);
359
    $dbtransfer = Koha::Item::Transfer->new({
360
        'itemnumber'  => $dbitem->itemnumber_id,
361
        'frombranch'  => $dbitem->itemnumber->homebranch,
362
        'tobranch'    => $dbitem->stage->branchcode_id,
363
        'datesent'    => DateTime->now - $sent_duration,
364
        'datearrived' => DateTime->now - $arrived_duration,
365
        'comments'    => "StockrotationAdvance",
366
    })->store;
367
    $dbitem->itemnumber->homebranch($dbitem->stage->branchcode_id)->store;
368
    $dbitem->itemnumber->holdingbranch($dbitem->stage->branchcode_id)->store;
369
    is($dbitem->investigate->{reason}, 'in-demand',
370
       "Item advances, but in-demand.");
371
372
    # Test item ready for advancement, but at wrong library ['repatriation']
373
    $sritem = $builder->build({ source => 'Stockrotationitem', value => { fresh => 0 } });
374
    $dbitem = Koha::Stockrotationitems->find($sritem->{itemnumber_id});
375
    $dbitem->indemand(0)->store;
376
    $dbitem->stage->duration(50)->store;
377
    $sent_duration =  DateTime::Duration->new( days => 55);
378
    $arrived_duration =  DateTime::Duration->new( days => 52);
379
    $dbtransfer = Koha::Item::Transfer->new({
380
        'itemnumber'  => $dbitem->itemnumber_id,
381
        'frombranch'  => $dbitem->itemnumber->homebranch,
382
        'tobranch'    => $dbitem->stage->branchcode_id,
383
        'datesent'    => DateTime->now - $sent_duration,
384
        'datearrived' => DateTime->now - $arrived_duration,
385
        'comments'    => "StockrotationAdvance",
386
    })->store;
387
    is($dbitem->investigate->{reason}, 'repatriation',
388
       "Item advances, but not at stage branch.");
389
390
    $schema->storage->txn_rollback;
391
};
392
253
1;
393
1;
(-)a/t/db_dependent/Stockrotationrotas.t (-1 / +1 lines)
Lines 72-78 subtest 'Basic object tests' => sub { Link Here
72
72
73
    is(
73
    is(
74
        Koha::Stockrotationitems->find($item->{itemnumber})->stage_id,
74
        Koha::Stockrotationitems->find($item->{itemnumber})->stage_id,
75
        $srrota->stockrotationstages->next->stage_id,
75
        $srrota->first_stage->stage_id,
76
        "Adding an item results in a new sritem item being assigned to the first stage."
76
        "Adding an item results in a new sritem item being assigned to the first stage."
77
    );
77
    );
78
78
(-)a/t/db_dependent/Stockrotationstages.t (-2 / +182 lines)
Lines 22-28 use Modern::Perl; Link Here
22
use Koha::Database;
22
use Koha::Database;
23
use t::lib::TestBuilder;
23
use t::lib::TestBuilder;
24
24
25
use Test::More tests => 5;
25
use Test::More tests => 6;
26
26
27
my $schema = Koha::Database->new->schema;
27
my $schema = Koha::Database->new->schema;
28
28
Lines 193-196 subtest 'Relationship to stockrotationitems' => sub { Link Here
193
    $schema->storage->txn_rollback;
193
    $schema->storage->txn_rollback;
194
};
194
};
195
195
196
197
subtest 'Tests for investigate (singular)' => sub {
198
199
    plan tests => 3;
200
201
    # In this subtest series we will primarily be testing whether items end up
202
    # in the correct 'branched' section of the stage-report.  We don't care
203
    # for item reasons here, as they are tested in Stockrotationitems.
204
205
    # We will run tests on first on an empty report (the base-case) and then
206
    # on a populated report.
207
208
    # We will need:
209
    # - Libraries which will hold the Items
210
    # - Rota Which containing the related stages
211
    #   + Stages on which we run investigate
212
    #     * Items on the stages
213
214
    $schema->storage->txn_begin;
215
216
    # Libraries
217
    my $library1 = $builder->build({ source => 'Branch' });
218
    my $library2 = $builder->build({ source => 'Branch' });
219
    my $library3 = $builder->build({ source => 'Branch' });
220
221
    my $stage1lib = $builder->build({ source => 'Branch' });
222
    my $stage2lib = $builder->build({ source => 'Branch' });
223
    my $stage3lib = $builder->build({ source => 'Branch' });
224
    my $stage4lib = $builder->build({ source => 'Branch' });
225
226
    my $libraries = [ $library1, $library2, $library3, $stage1lib, $stage2lib,
227
                      $stage3lib, $stage4lib ];
228
229
    # Rota
230
    my $rota = $builder->build({
231
        source => 'Stockrotationrota',
232
        value  => { cyclical => 0 },
233
    });
234
235
    # Stages
236
    my $stage1 = $builder->build({
237
        source => 'Stockrotationstage',
238
        value  => {
239
            rota_id => $rota->{rota_id},
240
            branchcode_id => $stage1lib->{branchcode},
241
            duration => 10,
242
            position => 1,
243
        },
244
    });
245
    my $stage2 = $builder->build({
246
        source => 'Stockrotationstage',
247
        value  => {
248
            rota_id => $rota->{rota_id},
249
            branchcode_id => $stage2lib->{branchcode},
250
            duration => 20,
251
            position => 2,
252
        },
253
    });
254
    my $stage3 = $builder->build({
255
        source => 'Stockrotationstage',
256
        value  => {
257
            rota_id => $rota->{rota_id},
258
            branchcode_id => $stage3lib->{branchcode},
259
            duration => 10,
260
            position => 3,
261
        },
262
    });
263
    my $stage4 = $builder->build({
264
        source => 'Stockrotationstage',
265
        value  => {
266
            rota_id => $rota->{rota_id},
267
            branchcode_id => $stage4lib->{branchcode},
268
            duration => 20,
269
            position => 4,
270
        },
271
    });
272
273
    # Test on an empty report.
274
    my $spec =  {
275
        $library1->{branchcode} => 1,
276
        $library2->{branchcode} => 1,
277
        $library3->{branchcode} => 1,
278
        $stage1lib->{branchcode} => 2,
279
        $stage2lib->{branchcode} => 1,
280
        $stage3lib->{branchcode} => 3,
281
        $stage4lib->{branchcode} => 4
282
    };
283
    while ( my ( $code, $count ) = each %{$spec} ) {
284
        my $cnt = 0;
285
        while ( $cnt < $count ) {
286
            my $item = $builder->build({
287
                source => 'Stockrotationitem',
288
                value  => {
289
                    stage_id => $stage1->{stage_id},
290
                    indemand => 0,
291
                    fresh    => 1,
292
                }
293
            });
294
            my $dbitem = Koha::Stockrotationitems->find($item);
295
            $dbitem->itemnumber->homebranch($code)
296
                ->holdingbranch($code)->store;
297
            $cnt++;
298
        }
299
    }
300
    my $report = Koha::Stockrotationstages
301
        ->find($stage1->{stage_id})->investigate;
302
    my $results = [];
303
    foreach my $lib ( @{$libraries} ) {
304
        my $items = $report->{branched}->{$lib->{branchcode}}->{items} || [];
305
        push @{$results},
306
            scalar @{$items};
307
    }
308
309
    # Items assigned to stag1lib -> log, hence $results[4] = 0;
310
    is_deeply( $results, [ 1, 1, 1, 2, 1, 3, 4 ], "Empty report test 1.");
311
312
    # Now we test by adding the next stage's items to the same report.
313
    $spec =  {
314
        $library1->{branchcode} => 3,
315
        $library2->{branchcode} => 2,
316
        $library3->{branchcode} => 1,
317
        $stage1lib->{branchcode} => 4,
318
        $stage2lib->{branchcode} => 2,
319
        $stage3lib->{branchcode} => 0,
320
        $stage4lib->{branchcode} => 3
321
    };
322
    while ( my ( $code, $count ) = each %{$spec} ) {
323
        my $cnt = 0;
324
        while ( $cnt < $count ) {
325
            my $item = $builder->build({
326
                source => 'Stockrotationitem',
327
                value  => {
328
                    stage_id => $stage2->{stage_id},
329
                    indemand => 0,
330
                    fresh => 1,
331
                }
332
            });
333
            my $dbitem = Koha::Stockrotationitems->find($item);
334
            $dbitem->itemnumber->homebranch($code)
335
                ->holdingbranch($code)->store;
336
            $cnt++;
337
        }
338
    }
339
340
    $report = Koha::Stockrotationstages
341
        ->find($stage2->{stage_id})->investigate($report);
342
    $results = [];
343
    foreach my $lib ( @{$libraries} ) {
344
        my $items = $report->{branched}->{$lib->{branchcode}}->{items} || [];
345
        push @{$results},
346
            scalar @{$items};
347
    }
348
    is_deeply( $results, [ 4, 3, 2, 6, 3, 3, 7 ], "full report test.");
349
350
    # Carry out db updates
351
    foreach my $item (@{$report->{items}}) {
352
        my $reason = $item->{reason};
353
        if ( $reason eq 'repatriation' ) {
354
            $item->{object}->repatriate;
355
        } elsif ( grep { $reason eq $_ }
356
                      qw/in-demand advancement initiation/ ) {
357
            $item->{object}->advance;
358
        }
359
    }
360
361
    $report = Koha::Stockrotationstages
362
        ->find($stage1->{stage_id})->investigate;
363
    $results = [];
364
    foreach my $lib ( @{$libraries} ) {
365
        my $items = $report->{branched}->{$lib->{branchcode}}->{items} || [];
366
        push @{$results},
367
            scalar @{$items};
368
    }
369
    # All items have been 'initiated', which means they are either happily in
370
    # transit or happily at the library they are supposed to be.  Either way
371
    # they will register as 'not-ready' in the stock rotation report.
372
    is_deeply( $results, [ 0, 0, 0, 0, 0, 0, 0 ], "All items now in logs.");
373
374
    $schema->storage->txn_rollback;
375
};
376
196
1;
377
1;
197
- 

Return to bug 11897