Inventory: Fehlermeldung gefixt
[kivitendo-erp.git] / SL / Helper / Inventory.pm
1 package SL::Helper::Inventory;
2
3 use strict;
4 use Carp;
5 use DateTime;
6 use Exporter qw(import);
7 use List::Util qw(min sum);
8 use List::UtilsBy qw(sort_by);
9 use List::MoreUtils qw(any);
10 use POSIX qw(ceil);
11
12 use SL::Locale::String qw(t8);
13 use SL::MoreCommon qw(listify);
14 use SL::DBUtils qw(selectall_hashref_query selectrow_query);
15 use SL::DB::TransferType;
16 use SL::Helper::Number qw(_format_number _round_number);
17 use SL::Helper::Inventory::Allocation;
18 use SL::X;
19
20 our @EXPORT_OK = qw(get_stock get_onhand allocate allocate_for_assembly produce_assembly check_constraints);
21 our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
22
23 sub _get_stock_onhand {
24   my (%params) = @_;
25
26   my $onhand_mode = !!$params{onhand};
27
28   my @selects = (
29     'SUM(qty) AS qty',
30     'MIN(EXTRACT(epoch FROM inventory.itime)) AS itime',
31   );
32   my @values;
33   my @where;
34   my @groups;
35
36   if ($params{part}) {
37     my @ids = map { ref $_ ? $_->id : $_ } listify($params{part});
38     push @where, sprintf "parts_id IN (%s)", join ', ', ("?") x @ids;
39     push @values, @ids;
40   }
41
42   if ($params{bin}) {
43     my @ids = map { ref $_ ? $_->id : $_ } listify($params{bin});
44     push @where, sprintf "bin_id IN (%s)", join ', ', ("?") x @ids;
45     push @values, @ids;
46   }
47
48   if ($params{warehouse}) {
49     my @ids = map { ref $_ ? $_->id : $_ } listify($params{warehouse});
50     push @where, sprintf "warehouse.id IN (%s)", join ', ', ("?") x @ids;
51     push @values, @ids;
52   }
53
54   if ($params{chargenumber}) {
55     my @ids = listify($params{chargenumber});
56     push @where, sprintf "chargenumber IN (%s)", join ', ', ("?") x @ids;
57     push @values, @ids;
58   }
59
60   if ($params{date}) {
61     Carp::croak("not DateTime ".$params{date}) unless ref($params{date}) eq 'DateTime';
62     push @where, sprintf "shippingdate <= ?";
63     push @values, $params{date};
64   }
65
66   if (!$params{bestbefore} && $onhand_mode && default_show_bestbefore()) {
67     $params{bestbefore} = DateTime->now_local;
68   }
69
70   if ($params{bestbefore}) {
71     Carp::croak("not DateTime ".$params{date}) unless ref($params{bestbefore}) eq 'DateTime';
72     push @where, sprintf "(bestbefore IS NULL OR bestbefore >= ?)";
73     push @values, $params{bestbefore};
74   }
75
76   # by
77   my %allowed_by = (
78     part          => [ qw(parts_id) ],
79     bin           => [ qw(bin_id inventory.warehouse_id)],
80     warehouse     => [ qw(inventory.warehouse_id) ],
81     chargenumber  => [ qw(chargenumber) ],
82     bestbefore    => [ qw(bestbefore) ],
83     for_allocate  => [ qw(parts_id bin_id inventory.warehouse_id chargenumber bestbefore) ],
84   );
85
86   if ($params{by}) {
87     for (listify($params{by})) {
88       my $selects = $allowed_by{$_} or Carp::croak("unknown option for by: $_");
89       push @selects, @$selects;
90       push @groups,  @$selects;
91     }
92   }
93
94   my $select   = join ',', @selects;
95   my $where    = @where  ? 'WHERE ' . join ' AND ', @where : '';
96   my $group_by = @groups ? 'GROUP BY ' . join ', ', @groups : '';
97
98   my $query = <<"";
99     SELECT $select FROM inventory
100     LEFT JOIN bin ON bin_id = bin.id
101     LEFT JOIN warehouse ON bin.warehouse_id = warehouse.id
102     $where
103     $group_by
104
105   if ($onhand_mode) {
106     $query .= ' HAVING SUM(qty) > 0';
107   }
108
109   my $results = selectall_hashref_query($::form, SL::DB->client->dbh, $query, @values);
110
111   my %with_objects = (
112     part         => 'SL::DB::Manager::Part',
113     bin          => 'SL::DB::Manager::Bin',
114     warehouse    => 'SL::DB::Manager::Warehouse',
115   );
116
117   my %slots = (
118     part      =>  'parts_id',
119     bin       =>  'bin_id',
120     warehouse =>  'warehouse_id',
121   );
122
123   if ($params{by} && $params{with_objects}) {
124     for my $with_object (listify($params{with_objects})) {
125       Carp::croak("unknown with_object $with_object") if !exists $with_objects{$with_object};
126
127       my $manager = $with_objects{$with_object};
128       my $slot = $slots{$with_object};
129       next if !(my @ids = map { $_->{$slot} } @$results);
130       my $objects = $manager->get_all(query => [ id => \@ids ]);
131       my %objects_by_id = map { $_->id => $_ } @$objects;
132
133       $_->{$with_object} = $objects_by_id{$_->{$slot}} for @$results;
134     }
135   }
136
137   if ($params{by}) {
138     return $results;
139   } else {
140     return $results->[0]{qty};
141   }
142 }
143
144 sub get_stock {
145   _get_stock_onhand(@_, onhand => 0);
146 }
147
148 sub get_onhand {
149   _get_stock_onhand(@_, onhand => 1);
150 }
151
152 sub allocate {
153   my (%params) = @_;
154
155   croak('allocate needs a part') unless $params{part};
156   croak('allocate needs a qty')  unless $params{qty};
157
158   my $part = $params{part};
159   my $qty  = $params{qty};
160
161   return () if $qty <= 0;
162
163   my $results = get_stock(part => $part, by => 'for_allocate');
164   my %bin_whitelist = map { (ref $_ ? $_->id : $_) => 1 } grep defined, listify($params{bin});
165   my %wh_whitelist  = map { (ref $_ ? $_->id : $_) => 1 } grep defined, listify($params{warehouse});
166   my %chargenumbers = map { (ref $_ ? $_->id : $_) => 1 } grep defined, listify($params{chargenumber});
167
168   # sort results so that chargenumbers are matched first, then wanted bins, then wanted warehouses
169   my @sorted_results = sort {
170        exists $chargenumbers{$b->{chargenumber}}  <=> exists $chargenumbers{$a->{chargenumber}} # then prefer wanted chargenumbers
171     || exists $bin_whitelist{$b->{bin_id}}        <=> exists $bin_whitelist{$a->{bin_id}}       # then prefer wanted bins
172     || exists $wh_whitelist{$b->{warehouse_id}}   <=> exists $wh_whitelist{$a->{warehouse_id}}  # then prefer wanted bins
173     || $a->{itime}                                <=> $b->{itime}                               # and finally prefer earlier charges
174   } @$results;
175   my @allocations;
176   my $rest_qty = $qty;
177
178   for my $chunk (@sorted_results) {
179     my $qty = min($chunk->{qty}, $rest_qty);
180
181     # since allocate operates on stock, this also ensures that no negative stock results are used
182     if ($qty > 0) {
183       push @allocations, SL::Helper::Inventory::Allocation->new(
184         parts_id          => $chunk->{parts_id},
185         qty               => $qty,
186         comment           => $params{comment},
187         bin_id            => $chunk->{bin_id},
188         warehouse_id      => $chunk->{warehouse_id},
189         chargenumber      => $chunk->{chargenumber},
190         bestbefore        => $chunk->{bestbefore},
191         for_object_id     => undef,
192       );
193       $rest_qty -=  _round_number($qty, 5);
194     }
195     $rest_qty = _round_number($rest_qty, 5);
196     last if $rest_qty == 0;
197   }
198   if ($rest_qty > 0) {
199     die SL::X::Inventory::Allocation->new(
200       error => 'not enough to allocate',
201       msg => t8("can not allocate #1 units of #2, missing #3 units", _format_number($qty), $part->displayable_name, _format_number($rest_qty)),
202     );
203   } else {
204     if ($params{constraints}) {
205       check_constraints($params{constraints},\@allocations);
206     }
207     return @allocations;
208   }
209 }
210
211 sub allocate_for_assembly {
212   my (%params) = @_;
213
214   my $part = $params{part} or Carp::croak('allocate needs a part');
215   my $qty  = $params{qty}  or Carp::croak('allocate needs a qty');
216
217   Carp::croak('not an assembly') unless $part->is_assembly;
218
219   my %parts_to_allocate;
220
221   for my $assembly ($part->assemblies) {
222     $parts_to_allocate{ $assembly->part->id } //= 0;
223     $parts_to_allocate{ $assembly->part->id } += $assembly->qty * $qty;
224   }
225
226   my @allocations;
227
228   for my $part_id (keys %parts_to_allocate) {
229     my $part = SL::DB::Part->load_cached($part_id);
230     push @allocations, allocate(%params, part => $part, qty => $parts_to_allocate{$part_id});
231   }
232
233   @allocations;
234 }
235
236 sub check_constraints {
237   my ($constraints, $allocations) = @_;
238   if ('CODE' eq ref $constraints) {
239     if (!$constraints->(@$allocations)) {
240       die SL::X::Inventory::Allocation->new(
241         error => 'allocation constraints failure',
242         msg => t8("Allocations didn't pass constraints"),
243       );
244     }
245   } else {
246     croak 'constraints needs to be a hashref' unless 'HASH' eq ref $constraints;
247
248     my %supported_constraints = (
249       bin_id       => 'bin_id',
250       warehouse_id => 'warehouse_id',
251       chargenumber => 'chargenumber',
252     );
253
254     for (keys %$constraints ) {
255       croak "unsupported constraint '$_'" unless $supported_constraints{$_};
256       next unless defined $constraints->{$_};
257
258       my %whitelist = map { (ref $_ ? $_->id : $_) => 1 } listify($constraints->{$_});
259       my $accessor = $supported_constraints{$_};
260
261       if (any { !$whitelist{$_->$accessor} } @$allocations) {
262         my %error_constraints = (
263           bin_id         => t8('Bins'),
264           warehouse_id   => t8('Warehouses'),
265           chargenumber   => t8('Chargenumbers'),
266         );
267         my @allocs = grep { $whitelist{$_->$accessor} } @$allocations;
268         my $needed = sum map { $_->qty } grep { !$whitelist{$_->$accessor} } @$allocations;
269         my $err    = t8("Cannot allocate parts.");
270         $err      .= ' '.t8('part \'#\'1 in bin \'#2\' only with qty #3 (need additional #4) and chargenumber \'#5\'.',
271               SL::DB::Part->load_cached($_->parts_id)->description,
272               SL::DB::Bin->load_cached($_->bin_id)->full_description,
273               _format_number($_->qty), _format_number($needed), $_->chargenumber ? $_->chargenumber : '--') for @allocs;
274         die SL::X::Inventory::Allocation->new(
275           error => 'allocation constraints failure',
276           msg   => $err,
277         );
278       }
279     }
280   }
281 }
282
283 sub produce_assembly {
284   my (%params) = @_;
285
286   my $part = $params{part} or Carp::croak('produce_assembly needs a part');
287   my $qty  = $params{qty}  or Carp::croak('produce_assembly needs a qty');
288
289   my $allocations = $params{allocations};
290   if ($params{auto_allocate}) {
291     Carp::croak("produce_assembly: can't have both allocations and auto_allocate") if $params{allocations};
292     $allocations = [ allocate_for_assembly(part => $part, qty => $qty) ];
293   } else {
294     Carp::croak("produce_assembly: need allocations or auto_allocate to produce something") if !$params{allocations};
295     $allocations = $params{allocations};
296   }
297
298   my $bin          = $params{bin} or Carp::croak("need target bin");
299   my $chargenumber = $params{chargenumber};
300   my $bestbefore   = $params{bestbefore};
301   my $for_object_id = $params{for_object_id};
302   my $comment      = $params{comment} // '';
303
304   my $invoice               = $params{invoice};
305   my $project               = $params{project};
306
307   my $shippingdate = $params{shippingsdate} // DateTime->now_local;
308
309   my $trans_id              = $params{trans_id};
310   ($trans_id) = selectrow_query($::form, SL::DB->client->dbh, qq|SELECT nextval('id')| ) unless $trans_id;
311
312   my $trans_type_out = SL::DB::Manager::TransferType->find_by(direction => 'out', description => 'used');
313   my $trans_type_in  = SL::DB::Manager::TransferType->find_by(direction => 'in', description => 'assembled');
314
315   # check whether allocations are sane
316   if (!$params{no_check_allocations} && !$params{auto_allocate}) {
317     my %allocations_by_part = map { $_->parts_id  => $_->qty } @$allocations;
318     for my $assembly ($part->assemblies) {
319       $allocations_by_part{ $assembly->parts_id } -= $assembly->qty * $qty;
320     }
321
322     die "allocations are insufficient for production" if any { $_ < 0 } values %allocations_by_part;
323   }
324
325   my @transfers;
326   for my $allocation (@$allocations) {
327     my $oe_id = delete $allocation->{for_object_id};
328     push @transfers, $allocation->transfer_object(
329       trans_id     => $trans_id,
330       qty          => -$allocation->qty,
331       trans_type   => $trans_type_out,
332       shippingdate => $shippingdate,
333       employee     => SL::DB::Manager::Employee->current,
334     );
335   }
336
337   push @transfers, SL::DB::Inventory->new(
338     trans_id          => $trans_id,
339     trans_type        => $trans_type_in,
340     part              => $part,
341     qty               => $qty,
342     bin               => $bin,
343     warehouse         => $bin->warehouse_id,
344     chargenumber      => $chargenumber,
345     bestbefore        => $bestbefore,
346     shippingdate      => $shippingdate,
347     project           => $project,
348     invoice           => $invoice,
349     comment           => $comment,
350     employee          => SL::DB::Manager::Employee->current,
351     oe_id             => $for_object_id,
352   );
353
354   SL::DB->client->with_transaction(sub {
355     $_->save for @transfers;
356     1;
357   }) or do {
358     die SL::DB->client->error;
359   };
360
361   @transfers;
362 }
363
364 sub default_show_bestbefore {
365   $::instance_conf->get_show_bestbefore
366 }
367
368 1;
369
370 =encoding utf-8
371
372 =head1 NAME
373
374 SL::WH - Warehouse and Inventory API
375
376 =head1 SYNOPSIS
377
378   # See description for an intro to the concepts used here.
379
380   use SL::Helper::Inventory qw(:ALL);
381
382   # stock, get "what's there" for a part with various conditions:
383   my $qty = get_stock(part => $part);                              # how much is on stock?
384   my $qty = get_stock(part => $part, date => $date);               # how much was on stock at a specific time?
385   my $qty = get_stock(part => $part, bin => $bin);                 # how much is on stock in a specific bin?
386   my $qty = get_stock(part => $part, warehouse => $warehouse);     # how much is on stock in a specific warehouse?
387   my $qty = get_stock(part => $part, chargenumber => $chargenumber); # how much is on stock of a specific chargenumber?
388
389   # onhand, get "what's available" for a part with various conditions:
390   my $qty = get_onhand(part => $part);                              # how much is available?
391   my $qty = get_onhand(part => $part, date => $date);               # how much was available at a specific time?
392   my $qty = get_onhand(part => $part, bin => $bin);                 # how much is available in a specific bin?
393   my $qty = get_onhand(part => $part, warehouse => $warehouse);     # how much is available in a specific warehouse?
394   my $qty = get_onhand(part => $part, chargenumber => $chargenumber); # how much is availbale of a specific chargenumber?
395
396   # onhand batch mode:
397   my $data = get_onhand(
398     warehouse    => $warehouse,
399     by           => [ qw(bin part chargenumber) ],
400     with_objects => [ qw(bin part) ],
401   );
402
403   # allocate:
404   my @allocations = allocate(
405     part         => $part,          # part_id works too
406     qty          => $qty,           # must be positive
407     chargenumber => $chargenumber,  # optional, may be arrayref. if provided these charges will be used first
408     bestbefore   => $datetime,      # optional, defaults to today. items with bestbefore prior to that date wont be used
409     bin          => $bin,           # optional, may be arrayref. if provided
410   );
411
412   # shortcut to allocate all that is needed for producing an assembly, will use chargenumbers as appropriate
413   my @allocations = allocate_for_assembly(
414     part         => $assembly,      # part_id works too
415     qty          => $qty,           # must be positive
416   );
417
418   # create allocation manually, bypassing checks. all of these need to be passed, even undefs
419   my $allocation = SL::Helper::Inventory::Allocation->new(
420     part_id           => $part->id,
421     qty               => 15,
422     bin_id            => $bin_obj->id,
423     warehouse_id      => $bin_obj->warehouse_id,
424     chargenumber      => '1823772365',
425     bestbefore        => undef,
426     for_object_id     => $order->id,
427   );
428
429   # produce_assembly:
430   produce_assembly(
431     part         => $part,           # target assembly
432     qty          => $qty,            # qty
433     allocations  => \@allocations,   # allocations to use. alternatively use "auto_allocate => 1,"
434
435     # where to put it
436     bin          => $bin,           # needed unless a global standard target is configured
437     chargenumber => $chargenumber,  # optional
438     bestbefore   => $datetime,      # optional
439     comment      => $comment,       # optional
440   );
441
442 =head1 DESCRIPTION
443
444 New functions for the warehouse and inventory api.
445
446 The WH api currently has three large shortcomings: It is very hard to just get
447 the current stock for an item, it's extremely complicated to use it to produce
448 assemblies while ensuring that no stock ends up negative, and it's very hard to
449 use it to get an overview over the actual contents of the inventory.
450
451 The first problem has spawned several dozen small functions in the program that
452 try to implement that, and those usually miss some details. They may ignore
453 bestbefore times, comments, ignore negative quantities etc.
454
455 To get this cleaned up a bit this code introduces two concepts: stock and onhand.
456
457 =over 4
458
459 =item * Stock is defined as the actual contents of the inventory, everything that is
460 there.
461
462 =item * Onhand is what is available, which means things that are stocked,
463 not expired and not in any other way reserved for other uses.
464
465 =back
466
467 The two new functions C<get_stock> and C<get_onhand> encapsulate these principles and
468 allow simple access with some optional filters for chargenumbers or warehouses.
469 Both of them have a batch mode that can be used to get these information to
470 supplement simple reports.
471
472 To address the safe assembly creation a new function has been added.
473 C<allocate> will try to find the requested quantity of a part in the inventory
474 and will return allocations of it which can then be used to create the
475 assembly. Allocation will happen with the C<onhand> semantics defined above,
476 meaning that by default no expired goods will be used. The caller can supply
477 hints of what shold be used and in those cases chargenumbers will be used up as
478 much as possible first. C<allocate> will always try to fulfil the request even
479 beyond those. Should the required amount not be stocked, allocate will throw an
480 exception.
481
482 C<produce_assembly> has been rewritten to only accept parameters about the
483 target of the production, and requires allocations to complete the request. The
484 allocations can be supplied manually, or can be generated automatically.
485 C<produce_assembly> will check whether enough allocations are given to create
486 the assembly, but will not check whether the allocations are backed. If the
487 allocations are not sufficient or if the auto-allocation fails an exception
488 is returned. If you need to produce something that is not in the inventory, you
489 can bypass those checks by creating the allocations yourself (see
490 L</"ALLOCATION DATA STRUCTURE">).
491
492 Note: this is only intended to cover the scenarios described above. For other cases:
493
494 =over 4
495
496 =item *
497
498 If you need actual inventory objects because of record links or something like
499 that load them directly. And strongly consider redesigning that, because it's
500 really fragile.
501
502 =item *
503
504 You need weight or accounting information you're on your own. The inventory api
505 only concerns itself with the raw quantities.
506
507 =item *
508
509 If you need the first stock date of parts, or anything related to a specific
510 transfer type or direction, this is not covered yet.
511
512 =back
513
514 =head1 FUNCTIONS
515
516 =over 4
517
518 =item * get_stock PARAMS
519
520 Returns for single parts how much actually exists in the inventory.
521
522 Options:
523
524 =over 4
525
526 =item * part
527
528 The part. Must be present without C<by>. May be arrayref with C<by>. Can be object or id.
529
530 =item * bin
531
532 If given, will only return stock on these bins. Optional. May be array, May be object or id.
533
534 =item * warehouse
535
536 If given, will only return stock on these warehouses. Optional. May be array, May be object or id.
537
538 =item * date
539
540 If given, will return stock as it were on this timestamp. Optional. Must be L<DateTime> object.
541
542 =item * chargenumber
543
544 If given, will only show stock with this chargenumber. Optional. May be array.
545
546 =item * by
547
548 See L</"STOCK/ONHAND REPORT MODE">
549
550 =item * with_objects
551
552 See L</"STOCK/ONHAND REPORT MODE">
553
554 =back
555
556 Will return a single qty normally, see L</"STOCK/ONHAND REPORT MODE"> for batch
557 mode when C<by> is given.
558
559 =item * get_onhand PARAMS
560
561 Returns for single parts how much is available in the inventory. That excludes
562 stock with expired bestbefore.
563
564 It takes the same options as L</get_stock>.
565
566 =over 4
567
568 =item * bestbefore
569
570 If given, will only return stock with a bestbefore at or after the given date.
571 Optional. Must be L<DateTime> object.
572
573 =back
574
575 =item * allocate PARAMS
576
577 Accepted parameters:
578
579 =over 4
580
581 =item * part
582
583 =item * qty
584
585 =item * bin
586
587 Bin object. Optional.
588
589 =item * warehouse
590
591 Warehouse object. Optional.
592
593 =item * chargenumber
594
595 Optional.
596
597 =item * bestbefore
598
599 Datetime. Optional.
600
601 =back
602
603 Tries to allocate the required quantity using what is currently onhand. If
604 given any of C<bin>, C<warehouse>, C<chargenumber>
605
606 =item * allocate_for_assembly PARAMS
607
608 Shortcut to allocate everything for an assembly. Takes the same arguments. Will
609 compute the required amount for each assembly part and allocate all of them.
610
611 =item * produce_assembly
612
613
614 =back
615
616 =head1 STOCK/ONHAND REPORT MODE
617
618 If the special option C<by> is given with an arrayref, the result will instead
619 be an arrayref of partitioned stocks by those fields. Valid partitions are:
620
621 =over 4
622
623 =item * part
624
625 If this is given, part is optional in the parameters
626
627 =item * bin
628
629 =item * warehouse
630
631 =item * chargenumber
632
633 =item * bestbefore
634
635 =back
636
637 Note: If you want to use the returned data to create allocations you I<need> to
638 enable all of these. To make this easier a special shortcut exists
639
640 In this mode, C<with_objects> can be used to load C<warehouse>, C<bin>,
641 C<parts>  objects in one go, just like with Rose. They
642 need to be present in C<by> before that though.
643
644 =head1 ALLOCATION ALGORITHM
645
646 When calling allocate, the current onhand (== available stock) of the item will
647 be used to decide which bins/chargenumbers/bestbefore can be used.
648
649 In general allocate will try to make the request happen, and will use the
650 provided charges up first, and then tap everything else. If you need to only
651 I<exactly> use the provided charges, you'll need to craft the allocations
652 yourself. See L</"ALLOCATION DATA STRUCTURE"> for that.
653
654 If C<chargenumber> is given, those will be used up next.
655
656 After that normal quantities will be used.
657
658 These are tiebreakers and expected to rarely matter in reality. If you need
659 finegrained control over which allocation is used, you may want to get the
660 onhands yourself and select the appropriate ones.
661
662 Only quantities with C<bestbefore> unset or after the given date will be
663 considered. If more than one charge is eligible, the earlier C<bestbefore>
664 will be used.
665
666 Allocations do NOT have an internal memory and can't react to other allocations
667 of the same part earlier. Never double allocate the same part within a
668 transaction.
669
670 =head1 ALLOCATION DATA STRUCTURE
671
672 Allocations are instances of the helper class C<SL::Helper::Inventory::Allocation>. They require
673 each of the following attributes to be set at creation time:
674
675 =over 4
676
677 =item * parts_id
678
679 =item * qty
680
681 =item * bin_id
682
683 =item * warehouse_id
684
685 =item * chargenumber
686
687 =item * bestbefore
688
689 =item * for_object_id
690
691 If set the allocations will be marked as allocated for the given object.
692 If these allocations are later used to produce an assembly, the resulting
693 consuming transactions will be marked as belonging to the given object.
694 The object may be an order, productionorder or other objects
695
696 =back
697
698 C<chargenumber>, C<bestbefore> and C<for_object_id> and C<comment> may be
699 C<undef> (but must still be present at creation time). Instances are considered
700 immutable.
701
702 Allocations also provide the method C<transfer_object> which will create a new
703 C<SL::DB::Inventory> bject with all the playload.
704
705 =head1 CONSTRAINTS
706
707   # whitelist constraints
708   ->allocate(
709     ...
710     constraints => {
711       bin_id       => \@allowed_bins,
712       chargenumber => \@allowed_chargenumbers,
713     }
714   );
715
716   # custom constraints
717   ->allocate(
718     constraints => sub {
719       # only allow chargenumbers with specific format
720       all { $_->chargenumber =~ /^ C \d{8} - \a{d2} $/x } @_
721
722       &&
723       # and must all have a bestbefore date
724       all { $_->bestbefore } @_;
725     }
726   )
727
728 C<allocation> is "best effort" in nature. It will take the C<bin>,
729 C<chargenumber> etc hints from the parameters, but will try it's bvest to
730 fulfil the request anyway and only bail out if it is absolutely not possible.
731
732 Sometimes you need to restrict allocations though. For this you can pass
733 additional constraints to C<allocate>. A constraint serves as a whitelist.
734 Every allocation must fulfil every constraint by having that attribute be one
735 of the given values.
736
737 In case even that is not enough, you may supply a custom check by passing a
738 function that will be given the allocation objects.
739
740 Note that both whitelists and constraints do not influence the order of
741 allocations, which is done purely from the initial parameters. They only serve
742 to reject allocations made in good faith which do fulfil required assertions.
743
744 =head1 ERROR HANDLING
745
746 C<allocate> and C<produce_assembly> will throw exceptions if the request can
747 not be completed. The usual reason will be insufficient onhand to allocate, or
748 insufficient allocations to process the request.
749
750 =head1 KNOWN PROBLEMS
751
752   * It's not currently possible to identify allocations between requests, for
753     example for presenting the user possible allocations and then actually using
754     them on the next request.
755   * It's not currently possible to give C<allocate> prior constraints.
756     Currently all constraints are treated as hints (and will be preferred) but
757     the internal ordering of the hints is fixed and more complex preferentials
758     are not supported.
759   * bestbefore handling is untested
760   * interaction with config option "transfer_default_ignore_onhand" is
761     currently undefined (and implicitly ignores it)
762
763 =head1 TODO
764
765   * define and describe error classes
766   * define wrapper classes for stock/onhand batch mode return values
767   * handle extra arguments in produce: shippingdate, project
768   * document no_ check
769   * tests
770
771 =head1 BUGS
772
773 None yet :)
774
775 =head1 AUTHOR
776
777 Sven Schöling E<lt>sven.schoeling@googlemail.comE<gt>
778
779 =cut