Revision 51072516
Von Bernd Bleßmann vor etwa 7 Jahren hinzugefügt
SL/Controller/Inventory.pm | ||
---|---|---|
7 | 7 |
use parent qw(SL::Controller::Base); |
8 | 8 |
|
9 | 9 |
use SL::DB::Inventory; |
10 |
use SL::DB::Stocktaking; |
|
10 | 11 |
use SL::DB::Part; |
11 | 12 |
use SL::DB::Warehouse; |
12 | 13 |
use SL::DB::Unit; |
14 |
use SL::DB::Default; |
|
13 | 15 |
use SL::WH; |
14 | 16 |
use SL::ReportGenerator; |
15 | 17 |
use SL::Locale::String qw(t8); |
... | ... | |
17 | 19 |
use SL::DBUtils; |
18 | 20 |
use SL::Helper::Flash; |
19 | 21 |
use SL::Controller::Helper::ReportGenerator; |
22 |
use SL::Controller::Helper::GetModels; |
|
20 | 23 |
|
21 | 24 |
use English qw(-no_match_vars); |
22 | 25 |
|
23 | 26 |
use Rose::Object::MakeMethods::Generic ( |
24 |
'scalar --get_set_init' => [ qw(warehouses units) ], |
|
27 |
'scalar --get_set_init' => [ qw(warehouses units is_stocktaking stocktaking_models stocktaking_cutoff_date) ],
|
|
25 | 28 |
'scalar' => [ qw(warehouse bin unit part) ], |
26 | 29 |
); |
27 | 30 |
|
28 | 31 |
__PACKAGE__->run_before('_check_auth'); |
29 | 32 |
__PACKAGE__->run_before('_check_warehouses'); |
30 |
__PACKAGE__->run_before('load_part_from_form', only => [ qw(stock_in part_changed mini_stock stock) ]); |
|
31 |
__PACKAGE__->run_before('load_unit_from_form', only => [ qw(stock_in part_changed mini_stock stock) ]); |
|
32 |
__PACKAGE__->run_before('load_wh_from_form', only => [ qw(stock_in warehouse_changed stock) ]); |
|
33 |
__PACKAGE__->run_before('load_bin_from_form', only => [ qw(stock_in stock) ]); |
|
33 |
__PACKAGE__->run_before('load_part_from_form', only => [ qw(stock_in part_changed mini_stock stock stocktaking_part_changed save_stocktaking) ]);
|
|
34 |
__PACKAGE__->run_before('load_unit_from_form', only => [ qw(stock_in part_changed mini_stock stock stocktaking_part_changed save_stocktaking) ]);
|
|
35 |
__PACKAGE__->run_before('load_wh_from_form', only => [ qw(stock_in warehouse_changed stock stocktaking save_stocktaking) ]);
|
|
36 |
__PACKAGE__->run_before('load_bin_from_form', only => [ qw(stock_in stock stocktaking save_stocktaking) ]);
|
|
34 | 37 |
__PACKAGE__->run_before('set_target_from_part', only => [ qw(part_changed) ]); |
35 | 38 |
__PACKAGE__->run_before('mini_stock', only => [ qw(stock_in mini_stock) ]); |
36 |
__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_usage stock_in warehouse_changed part_changed) ]); |
|
39 |
__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_usage stock_in warehouse_changed part_changed stocktaking stocktaking_part_changed save_stocktaking) ]);
|
|
37 | 40 |
__PACKAGE__->run_before('set_layout'); |
38 | 41 |
|
39 | 42 |
sub action_stock_in { |
... | ... | |
483 | 486 |
->render; |
484 | 487 |
} |
485 | 488 |
|
489 |
sub action_stocktaking { |
|
490 |
my ($self) = @_; |
|
491 |
|
|
492 |
$::request->{layout}->use_javascript("${_}.js") for qw(kivi.Inventory); |
|
493 |
$::request->layout->focus('#part_id_name'); |
|
494 |
$self->setup_stock_stocktaking_action_bar; |
|
495 |
$self->render('inventory/stocktaking/form', title => t8('Stocktaking')); |
|
496 |
} |
|
497 |
|
|
498 |
sub action_save_stocktaking { |
|
499 |
my ($self) = @_; |
|
500 |
|
|
501 |
return $self->js->flash('error', t8('A target quantitiy has to be given'))->render() |
|
502 |
if $::form->{target_qty} eq ''; |
|
503 |
|
|
504 |
my $target_qty = $::form->parse_amount(\%::myconfig, $::form->{target_qty}); |
|
505 |
|
|
506 |
return $self->js->flash('error', t8('Error: A negative target quantity is not allowed.'))->render() |
|
507 |
if $target_qty < 0; |
|
508 |
|
|
509 |
my $stocked_qty = _get_stocked_qty($self->part, |
|
510 |
warehouse_id => $self->warehouse->id, |
|
511 |
bin_id => $self->bin->id, |
|
512 |
chargenumber => $::form->{chargenumber}, |
|
513 |
bestbefore => $::form->{bestbefore},); |
|
514 |
|
|
515 |
my $stocked_qty_in_form_units = $self->part->unit_obj->convert_to($stocked_qty, $self->unit); |
|
516 |
|
|
517 |
if (!$::form->{dont_check_already_counted}) { |
|
518 |
my $already_counted = _already_counted($self->part, |
|
519 |
warehouse_id => $self->warehouse->id, |
|
520 |
bin_id => $self->bin->id, |
|
521 |
cutoff_date => $::form->{cutoff_date_as_date}, |
|
522 |
chargenumber => $::form->{chargenumber}, |
|
523 |
bestbefore => $::form->{bestbefore}); |
|
524 |
if (scalar @$already_counted) { |
|
525 |
my $reply = $self->js->dialog->open({ |
|
526 |
html => $self->render('inventory/stocktaking/_already_counted_dialog', |
|
527 |
{ output => 0 }, |
|
528 |
already_counted => $already_counted, |
|
529 |
stocked_qty => $stocked_qty, |
|
530 |
stocked_qty_in_form_units => $stocked_qty_in_form_units), |
|
531 |
id => 'already_counted_dialog', |
|
532 |
dialog => { |
|
533 |
title => t8('Already counted'), |
|
534 |
}, |
|
535 |
})->render; |
|
536 |
|
|
537 |
return $reply; |
|
538 |
} |
|
539 |
} |
|
540 |
|
|
541 |
# - target_qty is in units given in form ($self->unit) |
|
542 |
# - WH->transfer expects qtys in given unit (here: unit from form (unit -> $self->unit)) |
|
543 |
# Therefore use stocked_qty in form units for calculation. |
|
544 |
my $qty = $target_qty - $stocked_qty_in_form_units; |
|
545 |
my $src_or_dst = $qty < 0? 'src' : 'dst'; |
|
546 |
$qty = abs($qty); |
|
547 |
|
|
548 |
my $transfer_error; |
|
549 |
# do stock |
|
550 |
$::form->throw_on_error(sub { |
|
551 |
eval { |
|
552 |
WH->transfer({ |
|
553 |
parts => $self->part, |
|
554 |
$src_or_dst.'_bin' => $self->bin, |
|
555 |
$src_or_dst.'_wh' => $self->warehouse, |
|
556 |
qty => $qty, |
|
557 |
unit => $self->unit, |
|
558 |
transfer_type => 'stocktaking', |
|
559 |
chargenumber => $::form->{chargenumber}, |
|
560 |
bestbefore => $::form->{bestbefore}, |
|
561 |
ean => $::form->{ean}, |
|
562 |
comment => $::form->{comment}, |
|
563 |
record_stocktaking => 1, |
|
564 |
stocktaking_qty => $target_qty, |
|
565 |
stocktaking_cutoff_date => $::form->{cutoff_date_as_date}, |
|
566 |
}); |
|
567 |
1; |
|
568 |
} or do { $transfer_error = $EVAL_ERROR->getMessage; } |
|
569 |
}); |
|
570 |
|
|
571 |
return $self->js->flash('error', $transfer_error)->render() |
|
572 |
if $transfer_error; |
|
573 |
|
|
574 |
flash_later('info', $::locale->text('Part successful counted')); |
|
575 |
$self->redirect_to(action => 'stocktaking', |
|
576 |
warehouse_id => $self->warehouse->id, |
|
577 |
bin_id => $self->bin->id, |
|
578 |
cutoff_date_as_date => $self->stocktaking_cutoff_date->to_kivitendo); |
|
579 |
} |
|
580 |
|
|
581 |
sub action_reload_stocktaking_history { |
|
582 |
my ($self) = @_; |
|
583 |
|
|
584 |
$::form->{filter}{'cutoff_date:date'} = $self->stocktaking_cutoff_date->to_kivitendo; |
|
585 |
$::form->{filter}{'employee_id'} = SL::DB::Manager::Employee->current->id; |
|
586 |
|
|
587 |
$self->prepare_stocktaking_report; |
|
588 |
$self->report_generator_list_objects(report => $self->{report}, objects => $self->stocktaking_models->get, layout => 0, header => 0); |
|
589 |
} |
|
590 |
|
|
591 |
sub action_stocktaking_part_changed { |
|
592 |
my ($self) = @_; |
|
593 |
|
|
594 |
$self->js |
|
595 |
->replaceWith('#unit_id', $self->build_unit_select) |
|
596 |
->focus('#target_qty') |
|
597 |
->render; |
|
598 |
} |
|
599 |
|
|
600 |
sub action_stocktaking_journal { |
|
601 |
my ($self) = @_; |
|
602 |
|
|
603 |
$self->prepare_stocktaking_report(full => 1); |
|
604 |
$self->report_generator_list_objects(report => $self->{report}, objects => $self->stocktaking_models->get); |
|
605 |
} |
|
486 | 606 |
#================================================================ |
487 | 607 |
|
488 | 608 |
sub _check_auth { |
... | ... | |
505 | 625 |
SL::DB::Manager::Unit->get_all; |
506 | 626 |
} |
507 | 627 |
|
628 |
sub init_is_stocktaking { |
|
629 |
return $_[0]->action_name =~ m{stocktaking}; |
|
630 |
} |
|
631 |
|
|
632 |
sub init_stocktaking_models { |
|
633 |
my ($self) = @_; |
|
634 |
|
|
635 |
SL::Controller::Helper::GetModels->new( |
|
636 |
controller => $self, |
|
637 |
model => 'Stocktaking', |
|
638 |
sorted => { |
|
639 |
_default => { |
|
640 |
by => 'itime', |
|
641 |
dir => 0, |
|
642 |
}, |
|
643 |
itime => t8('Insert Date'), |
|
644 |
qty => t8('Target Qty'), |
|
645 |
chargenumber => t8('Charge Number'), |
|
646 |
comment => t8('Comment'), |
|
647 |
employee => t8('Employee'), |
|
648 |
ean => t8('EAN'), |
|
649 |
partnumber => t8('Part Number'), |
|
650 |
part => t8('Part Description'), |
|
651 |
bin => t8('Bin'), |
|
652 |
cutoff_date => t8('Cutoff Date'), |
|
653 |
}, |
|
654 |
with_objects => ['employee', 'parts', 'warehouse', 'bin'], |
|
655 |
); |
|
656 |
} |
|
657 |
|
|
658 |
sub init_stocktaking_cutoff_date { |
|
659 |
my ($self) = @_; |
|
660 |
|
|
661 |
return DateTime->from_kivitendo($::form->{cutoff_date_as_date}) if $::form->{cutoff_date_as_date}; |
|
662 |
return SL::DB::Default->get->stocktaking_cutoff_date if SL::DB::Default->get->stocktaking_cutoff_date; |
|
663 |
|
|
664 |
# Default cutoff date is last day of current year, but if current month |
|
665 |
# is janurary, it is the last day of the last year. |
|
666 |
my $now = DateTime->now_local; |
|
667 |
my $cutoff = DateTime->new(year => $now->year, month => 12, day => 31); |
|
668 |
if ($now->month < 1) { |
|
669 |
$cutoff->substract(years => 1); |
|
670 |
} |
|
671 |
return $cutoff; |
|
672 |
} |
|
673 |
|
|
508 | 674 |
sub set_target_from_part { |
509 | 675 |
my ($self) = @_; |
510 | 676 |
|
... | ... | |
538 | 704 |
} |
539 | 705 |
|
540 | 706 |
sub load_wh_from_form { |
541 |
$_[0]->warehouse(SL::DB::Manager::Warehouse->find_by_or_create(id => $::form->{warehouse_id})); |
|
707 |
my $preselected; |
|
708 |
$preselected = SL::DB::Default->get->stocktaking_warehouse_id if $_[0]->is_stocktaking; |
|
709 |
|
|
710 |
$_[0]->warehouse(SL::DB::Manager::Warehouse->find_by_or_create(id => ($::form->{warehouse_id} || $preselected))); |
|
542 | 711 |
} |
543 | 712 |
|
544 | 713 |
sub load_bin_from_form { |
545 |
$_[0]->bin(SL::DB::Manager::Bin->find_by_or_create(id => $::form->{bin_id})); |
|
714 |
my $preselected; |
|
715 |
$preselected = SL::DB::Default->get->stocktaking_bin_id if $_[0]->is_stocktaking; |
|
716 |
|
|
717 |
$_[0]->bin(SL::DB::Manager::Bin->find_by_or_create(id => ($::form->{bin_id} || $preselected))); |
|
546 | 718 |
} |
547 | 719 |
|
548 | 720 |
sub set_layout { |
... | ... | |
619 | 791 |
$::form->show_generic_error($msg); |
620 | 792 |
} |
621 | 793 |
|
794 |
sub prepare_stocktaking_report { |
|
795 |
my ($self, %params) = @_; |
|
796 |
|
|
797 |
my $callback = $self->stocktaking_models->get_callback; |
|
798 |
|
|
799 |
my $report = SL::ReportGenerator->new(\%::myconfig, $::form); |
|
800 |
$self->{report} = $report; |
|
801 |
|
|
802 |
my @columns = qw(itime employee ean partnumber part qty unit bin chargenumber comment cutoff_date); |
|
803 |
my @sortable = qw(itime employee ean partnumber part qty bin chargenumber comment cutoff_date); |
|
804 |
|
|
805 |
my %column_defs = ( |
|
806 |
itime => { sub => sub { $_[0]->itime_as_timestamp }, |
|
807 |
text => t8('Insert Date'), }, |
|
808 |
employee => { sub => sub { $_[0]->employee->safe_name }, |
|
809 |
text => t8('Employee'), }, |
|
810 |
ean => { sub => sub { $_[0]->part->ean }, |
|
811 |
text => t8('EAN'), }, |
|
812 |
partnumber => { sub => sub { $_[0]->part->partnumber }, |
|
813 |
text => t8('Part Number'), }, |
|
814 |
part => { sub => sub { $_[0]->part->description }, |
|
815 |
text => t8('Part Description'), }, |
|
816 |
qty => { sub => sub { $_[0]->qty_as_number }, |
|
817 |
text => t8('Target Qty'), |
|
818 |
align => 'right', }, |
|
819 |
unit => { sub => sub { $_[0]->part->unit }, |
|
820 |
text => t8('Unit'), }, |
|
821 |
bin => { sub => sub { $_[0]->bin->full_description }, |
|
822 |
text => t8('Bin'), }, |
|
823 |
chargenumber => { text => t8('Charge Number'), }, |
|
824 |
comment => { text => t8('Comment'), }, |
|
825 |
cutoff_date => { sub => sub { $_[0]->cutoff_date_as_date }, |
|
826 |
text => t8('Cutoff Date'), }, |
|
827 |
); |
|
828 |
|
|
829 |
$report->set_options( |
|
830 |
std_column_visibility => 1, |
|
831 |
controller_class => 'Inventory', |
|
832 |
output_format => 'HTML', |
|
833 |
title => (!!$params{full})? $::locale->text('Stocktaking Journal') : $::locale->text('Stocktaking History'), |
|
834 |
allow_pdf_export => !!$params{full}, |
|
835 |
allow_csv_export => !!$params{full}, |
|
836 |
); |
|
837 |
$report->set_columns(%column_defs); |
|
838 |
$report->set_column_order(@columns); |
|
839 |
$report->set_export_options(qw(stocktaking_journal filter)); |
|
840 |
$report->set_options_from_form; |
|
841 |
$self->stocktaking_models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i; |
|
842 |
$self->stocktaking_models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable) if !!$params{full}; |
|
843 |
if (!!$params{full}) { |
|
844 |
$report->set_options( |
|
845 |
raw_top_info_text => $self->render('inventory/stocktaking/full_report_top', { output => 0 }), |
|
846 |
); |
|
847 |
} |
|
848 |
$report->set_options( |
|
849 |
raw_bottom_info_text => $self->render('inventory/stocktaking/report_bottom', { output => 0 }), |
|
850 |
); |
|
851 |
} |
|
852 |
|
|
853 |
sub _get_stocked_qty { |
|
854 |
my ($part, %params) = @_; |
|
855 |
|
|
856 |
my $bestbefore_filter = ''; |
|
857 |
my $bestbefore_val_cnt = 0; |
|
858 |
if ($::instance_conf->get_show_bestbefore) { |
|
859 |
$bestbefore_filter = ($params{bestbefore}) ? 'AND bestbefore = ?' : 'AND bestbefore IS NULL'; |
|
860 |
$bestbefore_val_cnt = ($params{bestbefore}) ? 1 : 0; |
|
861 |
} |
|
862 |
|
|
863 |
my $query = <<SQL; |
|
864 |
SELECT sum(qty) FROM inventory |
|
865 |
WHERE parts_id = ? AND warehouse_id = ? AND bin_id = ? AND chargenumber = ? $bestbefore_filter |
|
866 |
GROUP BY warehouse_id, bin_id, chargenumber |
|
867 |
SQL |
|
868 |
|
|
869 |
my @values = ($part->id, |
|
870 |
$params{warehouse_id}, |
|
871 |
$params{bin_id}, |
|
872 |
$params{chargenumber}); |
|
873 |
push @values, $params{bestbefore} if $bestbefore_val_cnt; |
|
874 |
|
|
875 |
my ($stocked_qty) = selectrow_query($::form, $::form->get_standard_dbh, $query, @values); |
|
876 |
|
|
877 |
return 1*($stocked_qty || 0); |
|
878 |
} |
|
879 |
|
|
880 |
sub _already_counted { |
|
881 |
my ($part, %params) = @_; |
|
882 |
|
|
883 |
my %bestbefore_filter; |
|
884 |
if ($::instance_conf->get_show_bestbefore) { |
|
885 |
%bestbefore_filter = (bestbefore => $params{bestbefore}); |
|
886 |
} |
|
887 |
|
|
888 |
SL::DB::Manager::Stocktaking->get_all(query => [and => [parts_id => $part->id, |
|
889 |
warehouse_id => $params{warehouse_id}, |
|
890 |
bin_id => $params{bin_id}, |
|
891 |
cutoff_date => $params{cutoff_date}, |
|
892 |
chargenumber => $params{chargenumber}, |
|
893 |
%bestbefore_filter]], |
|
894 |
sort_by => ['itime DESC']); |
|
895 |
} |
|
896 |
|
|
622 | 897 |
sub setup_stock_in_action_bar { |
623 | 898 |
my ($self, %params) = @_; |
624 | 899 |
|
... | ... | |
648 | 923 |
} |
649 | 924 |
} |
650 | 925 |
|
926 |
sub setup_stock_stocktaking_action_bar { |
|
927 |
my ($self, %params) = @_; |
|
928 |
|
|
929 |
for my $bar ($::request->layout->get('actionbar')) { |
|
930 |
$bar->add( |
|
931 |
action => [ |
|
932 |
t8('Save'), |
|
933 |
call => [ 'kivi.Inventory.save_stocktaking' ], |
|
934 |
accesskey => 'enter', |
|
935 |
], |
|
936 |
); |
|
937 |
} |
|
938 |
} |
|
939 |
|
|
651 | 940 |
1; |
652 | 941 |
__END__ |
653 | 942 |
|
... | ... | |
655 | 944 |
|
656 | 945 |
=head1 NAME |
657 | 946 |
|
658 |
SL::Controller::Inventory - Report Controller for inventory
|
|
947 |
SL::Controller::Inventory - Controller for inventory |
|
659 | 948 |
|
660 | 949 |
=head1 DESCRIPTION |
661 | 950 |
|
662 |
This controller makes three reports about inventory in warehouses/stocks |
|
951 |
This controller handles stock in, stocktaking and reports about inventory |
|
952 |
in warehouses/stocks |
|
663 | 953 |
|
664 | 954 |
- warehouse content |
665 | 955 |
|
... | ... | |
667 | 957 |
|
668 | 958 |
- warehouse withdrawal |
669 | 959 |
|
960 |
- stocktaking |
|
961 |
|
|
962 |
=head2 Stocktaking |
|
963 |
|
|
964 |
Stocktaking allows to document the counted quantities of parts during |
|
965 |
stocktaking for a certain cutoff date. Differences between counted and stocked |
|
966 |
quantities are corrected in the stock. The transfer type 'stocktacking' is set |
|
967 |
here. |
|
968 |
|
|
969 |
After picking a part, the mini stock for this part is displayed. At the bottom |
|
970 |
of the form a history of already counted parts for the current employee and the |
|
971 |
choosen cutoff date is shown. |
|
972 |
|
|
973 |
Warehouse, bin and cutoff date canbe preselected in the client configuration. |
|
974 |
|
|
975 |
If a part was already counted for this cutoff date, warehouse and bin, a warning |
|
976 |
is displayed, allowing the user to choose to add the counted quantity to the |
|
977 |
stocked one or to take his counted quantity as the new stocked quantity. |
|
978 |
|
|
979 |
There is also a journal of stocktakings. |
|
980 |
|
|
981 |
Templates are located under C<templates/webpages/inventory/stocktaking>. |
|
982 |
JavaScript functions can be found in C<js/kivi.Inventory.js>. |
|
983 |
|
|
670 | 984 |
=head1 FUNCTIONS |
671 | 985 |
|
672 | 986 |
=over 4 |
... | ... | |
682 | 996 |
|
683 | 997 |
The manual pagination is implemented like the pagination in SL::Controller::CsvImport. |
684 | 998 |
|
999 |
=item C<action_stocktaking> |
|
1000 |
|
|
1001 |
This action renders the input form for stocktaking. |
|
1002 |
|
|
1003 |
=item C<action_save_stocktaking> |
|
1004 |
|
|
1005 |
This action saves the stocktaking values and corrects the stock after checking |
|
1006 |
if the part is already counted for this warehouse, bin and cutoff date. |
|
1007 |
For saving SL::WH->transfer is called. |
|
1008 |
|
|
1009 |
=item C<action_reload_stocktaking_history> |
|
1010 |
|
|
1011 |
This action is responsible for displaying the stocktaking history at the bottom |
|
1012 |
of the form. It uses the stocktaking journal with fixed filters for cutoff date |
|
1013 |
and the current employee. The history is displayed via javascript. |
|
1014 |
|
|
1015 |
=item C<action_stocktaking_part_changed> |
|
1016 |
|
|
1017 |
This action is called after the user selected or changed the part. |
|
1018 |
|
|
1019 |
=item C<is_stocktaking> |
|
1020 |
|
|
1021 |
This is a method to check if actions are called from stocktaking form. |
|
1022 |
|
|
685 | 1023 |
=back |
686 | 1024 |
|
687 | 1025 |
=head1 SPECIAL CASES |
... | ... | |
698 | 1036 |
|
699 | 1037 |
=head1 AUTHOR |
700 | 1038 |
|
701 |
only for C<action_stock_usage> and C<action_usage>: |
|
1039 |
=over 4 |
|
1040 |
|
|
1041 |
=item only for C<action_stock_usage> and C<action_usage>: |
|
702 | 1042 |
|
703 | 1043 |
Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt> |
704 | 1044 |
|
1045 |
=item for stocktaking: |
|
1046 |
|
|
1047 |
Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt> |
|
1048 |
|
|
1049 |
=back |
|
705 | 1050 |
|
706 | 1051 |
=cut |
SL/DB/Manager/Stocktaking.pm | ||
---|---|---|
1 |
# This file has been auto-generated only because it didn't exist. |
|
2 |
# Feel free to modify it at will; it will not be overwritten automatically. |
|
3 |
|
|
4 | 1 |
package SL::DB::Manager::Stocktaking; |
5 | 2 |
|
6 | 3 |
use strict; |
7 | 4 |
|
8 | 5 |
use parent qw(SL::DB::Helper::Manager); |
9 | 6 |
|
7 |
use SL::DB::Helper::Filtered; |
|
8 |
use SL::DB::Helper::Paginated; |
|
9 |
use SL::DB::Helper::Sorted; |
|
10 |
|
|
10 | 11 |
sub object_class { 'SL::DB::Stocktaking' } |
11 | 12 |
|
12 | 13 |
__PACKAGE__->make_manager_methods; |
13 | 14 |
|
15 |
sub _sort_spec { |
|
16 |
return ( |
|
17 |
default => [ 'itime', 1 ], |
|
18 |
columns => { |
|
19 |
SIMPLE => 'ALL', |
|
20 |
comment => 'lower(comment)', |
|
21 |
chargenumber => 'lower(chargenumber)', |
|
22 |
employee => 'lower(employee.name)', |
|
23 |
ean => 'lower(parts.ean)', |
|
24 |
partnumber => 'lower(parts.partnumber)', |
|
25 |
part => 'lower(parts.description)', |
|
26 |
bin => ['lower(warehouse.description)', 'lower(bin.description)'], |
|
27 |
}); |
|
28 |
} |
|
29 |
|
|
30 |
sub default_objects_per_page { |
|
31 |
20; |
|
32 |
} |
|
33 |
|
|
14 | 34 |
1; |
SL/DB/Stocktaking.pm | ||
---|---|---|
10 | 10 |
|
11 | 11 |
__PACKAGE__->meta->initialize; |
12 | 12 |
|
13 |
# part accessor is badly named |
|
14 |
sub part { |
|
15 |
goto &parts; |
|
16 |
} |
|
17 |
|
|
13 | 18 |
1; |
js/kivi.Inventory.js | ||
---|---|---|
1 |
namespace('kivi.Inventory', function(ns) { |
|
2 |
ns.reload_bin_selection = function() { |
|
3 |
$.post("controller.pl", { action: 'Inventory/warehouse_changed', |
|
4 |
warehouse_id: function(){ return $('#warehouse_id').val() } }, |
|
5 |
kivi.eval_json_result); |
|
6 |
}; |
|
7 |
|
|
8 |
ns.save_stocktaking = function(dont_check_already_counted) { |
|
9 |
var data = $('#stocktaking_form').serializeArray(); |
|
10 |
data.push({ name: 'action', value: 'Inventory/save_stocktaking' }); |
|
11 |
data.push({ name: 'dont_check_already_counted', value: dont_check_already_counted }); |
|
12 |
|
|
13 |
$.post("controller.pl", data, kivi.eval_json_result); |
|
14 |
}; |
|
15 |
|
|
16 |
ns.stocktaking_part_changed = function() { |
|
17 |
var data = $('#stocktaking_form').serializeArray(); |
|
18 |
data.push({ name: 'action', value: 'Inventory/stocktaking_part_changed' }); |
|
19 |
$.post("controller.pl", data, kivi.eval_json_result); |
|
20 |
$.post("controller.pl", { action: 'Inventory/mini_stock', |
|
21 |
part_id: function(){ return $('#part_id').val() } }, |
|
22 |
kivi.eval_json_result); |
|
23 |
}; |
|
24 |
|
|
25 |
ns.reload_stocktaking_history = function(target, source) { |
|
26 |
var data = $('#stocktaking_form').serializeArray(); |
|
27 |
$.ajax({ |
|
28 |
url: source, |
|
29 |
data: data, |
|
30 |
success: function (rsp) { |
|
31 |
$(target).html(rsp); |
|
32 |
$(target).find('a.paginate-link').click(function(event){ |
|
33 |
event.preventDefault(); |
|
34 |
kivi.Inventory.reload_stocktaking_history(target, event.target + '')}); |
|
35 |
} |
|
36 |
}); |
|
37 |
}; |
|
38 |
|
|
39 |
ns.stocktaking_correct_counted = function() { |
|
40 |
kivi.Inventory.close_already_counted_dialog(); |
|
41 |
kivi.Inventory.save_stocktaking(1); |
|
42 |
}; |
|
43 |
|
|
44 |
ns.stocktaking_add_counted = function(qty_to_add_to) { |
|
45 |
resulting_qty = kivi.parse_amount($('#target_qty').val()) + 1.0*qty_to_add_to; |
|
46 |
$('#target_qty').val(kivi.format_amount(resulting_qty, -2)); |
|
47 |
kivi.Inventory.close_already_counted_dialog(); |
|
48 |
kivi.Inventory.save_stocktaking(1); |
|
49 |
}; |
|
50 |
|
|
51 |
ns.close_already_counted_dialog = function() { |
|
52 |
$('#already_counted_dialog').dialog("close"); |
|
53 |
}; |
|
54 |
|
|
55 |
}); |
|
56 |
|
|
57 |
$(function(){ |
|
58 |
$('#part_id').change(kivi.Inventory.stocktaking_part_changed); |
|
59 |
$('#warehouse_id').change(kivi.Inventory.reload_bin_selection); |
|
60 |
$('#cutoff_date_as_date').change(function() {kivi.Inventory.reload_stocktaking_history('#stocktaking_history', 'controller.pl?action=Inventory/reload_stocktaking_history');}); |
|
61 |
|
|
62 |
kivi.Inventory.reload_stocktaking_history('#stocktaking_history', 'controller.pl?action=Inventory/reload_stocktaking_history'); |
|
63 |
}); |
menus/user/00-erp.yaml | ||
---|---|---|
543 | 543 |
params: |
544 | 544 |
action: transfer_warehouse_selection |
545 | 545 |
trans_type: removal |
546 |
- parent: warehouse |
|
547 |
id: warehouse_stocktaking |
|
548 |
name: Stocktaking |
|
549 |
order: 450 |
|
550 |
access: warehouse_management |
|
551 |
params: |
|
552 |
action: Inventory/stocktaking |
|
546 | 553 |
- parent: warehouse |
547 | 554 |
id: warehouse_reports |
548 | 555 |
name: Reports |
... | ... | |
571 | 578 |
access: warehouse_contents | warehouse_management |
572 | 579 |
params: |
573 | 580 |
action: Inventory/stock_usage |
581 |
- parent: warehouse_reports |
|
582 |
id: warehouse_stocktaking_journal |
|
583 |
name: Stocktaking Journal |
|
584 |
order: 400 |
|
585 |
access: warehouse_contents | warehouse_management |
|
586 |
params: |
|
587 |
action: Inventory/stocktaking_journal |
|
574 | 588 |
- id: general_ledger |
575 | 589 |
name: General Ledger |
576 | 590 |
icon: gl |
templates/webpages/inventory/stocktaking/_already_counted_dialog.html | ||
---|---|---|
1 |
[%- USE T8 %][%- USE HTML %][%- USE L %][%- USE LxERP %] |
|
2 |
|
|
3 |
<form method="post" id="already_counted_form" method="POST"> |
|
4 |
|
|
5 |
[% 'This part was already counted for this bin:' | $T8 %]<br> |
|
6 |
[% SELF.part.displayable_name %] / [% SELF.part.ean %]<br> |
|
7 |
[% already_counted.first.bin.full_description %], [% 'Stocked Qty' | $T8 %]: [%- LxERP.format_amount(stocked_qty, -2) -%] [%- SELF.part.unit -%] |
|
8 |
[%- IF SELF.part.unit != SELF.unit.name -%] |
|
9 |
([%- LxERP.format_amount(stocked_qty_in_form_units, -2) -%] [%- SELF.unit.name -%])<br> |
|
10 |
[%- END -%] |
|
11 |
<br> |
|
12 |
<br> |
|
13 |
<table> |
|
14 |
<tr class='listheading'> |
|
15 |
<th>[% 'Insert Date' | $T8 %]</th> |
|
16 |
<th>[% 'Employee' | $T8 %]</th> |
|
17 |
<th>[% 'Bin' | $T8 %]</th> |
|
18 |
<th>[% 'Target Qty' | $T8 %]</th> |
|
19 |
</tr> |
|
20 |
[% FOREACH ac = already_counted %] |
|
21 |
<tr class='listrow'> |
|
22 |
<td>[%- ac.itime_as_timestamp -%]</td> |
|
23 |
<td>[%- ac.employee.safe_name -%]</td> |
|
24 |
<td>[%- ac.bin.full_description -%]</td> |
|
25 |
<td class="numeric">[%- ac.qty_as_number -%] [%- ac.part.unit -%]</td> |
|
26 |
</tr> |
|
27 |
[% END %] |
|
28 |
</table> |
|
29 |
|
|
30 |
<p> |
|
31 |
[% 'Please choose the action to be processed for your target quantity:' | $T8 %]<br> |
|
32 |
[% 'Correct counted' | $T8 %]: [% 'The stock will be changed to your target quantity.' | $T8 %]<br> |
|
33 |
[% 'Add counted' | $T8 %]: [% 'Your target quantity will be added to the stocked quantity.' | $T8 %]<br> |
|
34 |
</p> |
|
35 |
|
|
36 |
<br> |
|
37 |
[% L.hidden_tag('action', 'Inventory/dispatch') %] |
|
38 |
[% L.button_tag('kivi.Inventory.stocktaking_correct_counted()', LxERP.t8("Correct counted")) %] |
|
39 |
[% L.button_tag('kivi.Inventory.stocktaking_add_counted(' _ stocked_qty_in_form_units _ ')', LxERP.t8("Add counted")) %] |
|
40 |
<a href="#" onclick="kivi.Inventory.close_already_counted_dialog();">[%- LxERP.t8("Cancel") %]</a> |
|
41 |
|
|
42 |
</form> |
templates/webpages/inventory/stocktaking/_filter.html | ||
---|---|---|
1 |
[%- USE T8 %] |
|
2 |
[%- USE HTML %] |
|
3 |
[%- USE L %] |
|
4 |
[%- USE LxERP %] |
|
5 |
|
|
6 |
<table id="filter_table"> |
|
7 |
<tr> |
|
8 |
<th align="right">[% 'Cutoff Date' | $T8 %]</th> |
|
9 |
<td>[% L.date_tag('filter.cutoff_date:date', filter.cutoff_date_date) %]</td> |
|
10 |
</tr> |
|
11 |
<tr> |
|
12 |
<th align="right">[% 'Comment' | $T8 %]</th> |
|
13 |
<td>[% L.input_tag('filter.comment:substr::ilike', filter.comment_substr__ilike, size=60) %]</td> |
|
14 |
</tr> |
|
15 |
</table> |
templates/webpages/inventory/stocktaking/form.html | ||
---|---|---|
1 |
[%- USE T8 %] |
|
2 |
[%- USE L %] |
|
3 |
[%- USE P %] |
|
4 |
[%- USE HTML %] |
|
5 |
[%- USE LxERP %] |
|
6 |
|
|
7 |
<h1>[% title | html %]</h1> |
|
8 |
|
|
9 |
[%- INCLUDE 'common/flash.html' %] |
|
10 |
|
|
11 |
<form method="post" action="controller.pl" id="stocktaking_form"> |
|
12 |
|
|
13 |
<p> |
|
14 |
<label for="part_id">[% "Article" | $T8 %]</label> |
|
15 |
[% P.part.picker("part_id", "") %] |
|
16 |
</p> |
|
17 |
|
|
18 |
<p> |
|
19 |
<div id="stock"></div> |
|
20 |
</p> |
|
21 |
|
|
22 |
<table id="stocktaking_settings_table"> |
|
23 |
<tr> |
|
24 |
<th align="right" nowrap>[% 'Destination warehouse' | $T8 %]</th> |
|
25 |
<td>[% L.select_tag('warehouse_id', SELF.warehouses, default=SELF.warehouse.id, title_key='description') %] |
|
26 |
[% IF SELF.warehouse.id %] |
|
27 |
[% L.select_tag('bin_id', SELF.warehouse.bins, default=SELF.bin.id, title_key='description') %] |
|
28 |
[%- ELSE %] |
|
29 |
<span id='bin_id'></span> |
|
30 |
[% END %] |
|
31 |
</td> |
|
32 |
</tr> |
|
33 |
|
|
34 |
<tr> |
|
35 |
<th align="right" nowrap>[% 'Charge number' | $T8 %]</th> |
|
36 |
<td>[% L.input_tag('chargenumber', "", size=30) %]</td> |
|
37 |
</tr> |
|
38 |
|
|
39 |
[% IF INSTANCE_CONF.get_show_bestbefore %] |
|
40 |
<tr> |
|
41 |
<th align="right" nowrap>[% 'Best Before' | $T8 %]</th> |
|
42 |
<td>[% L.date_tag('bestbefore', "") %]</td> |
|
43 |
</tr> |
|
44 |
[%- END %] |
|
45 |
|
|
46 |
<tr> |
|
47 |
<th align="right" nowrap>[% 'Target Qty' | $T8 %]</th> |
|
48 |
<td> |
|
49 |
[% L.input_tag('target_qty', '', size=10, class='numeric') %] |
|
50 |
[%- IF SELF.part.unit %] |
|
51 |
[% L.select_tag('unit_id', SELF.part.available_units, title_key='name', default=SELF.unit.id) %] |
|
52 |
[%- ELSE %] |
|
53 |
[% L.select_tag('unit_id', SELF.units, title_key='name') %] |
|
54 |
[%- END %] |
|
55 |
</td> |
|
56 |
</tr> |
|
57 |
|
|
58 |
<tr> |
|
59 |
<th align="right" nowrap>[% 'Cutoff Date' | $T8 %]</th> |
|
60 |
<td> |
|
61 |
[% L.date_tag('cutoff_date_as_date', SELF.stocktaking_cutoff_date) %] |
|
62 |
</td> |
|
63 |
</tr> |
|
64 |
|
|
65 |
<tr> |
|
66 |
<th align="right" nowrap>[% 'Optional comment' | $T8 %]</th> |
|
67 |
<td> |
|
68 |
[% L.input_tag('comment', SELF.stocktaking_comment, size=40) %] |
|
69 |
</td> |
|
70 |
</tr> |
|
71 |
</table> |
|
72 |
|
|
73 |
</form> |
|
74 |
|
|
75 |
<p> |
|
76 |
<div id="stocktaking_history"> |
|
77 |
[%- LxERP.t8("Loading...") %] |
|
78 |
</div> |
|
79 |
</p> |
templates/webpages/inventory/stocktaking/full_report_top.html | ||
---|---|---|
1 |
[%- USE L %] |
|
2 |
[%- USE T8 %] |
|
3 |
[%- USE LxERP %] |
|
4 |
[%- USE HTML %] |
|
5 |
<form action='controller.pl' method='post'> |
|
6 |
<div class='filter_toggle'> |
|
7 |
<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Show Filter' | $T8 %]</a> |
|
8 |
[% SELF.filter_summary | html %] |
|
9 |
</div> |
|
10 |
<div class='filter_toggle' style='display:none'> |
|
11 |
<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a> |
|
12 |
[%- PROCESS 'inventory/stocktaking/_filter.html' filter=SELF.stocktaking_models.filtered.laundered %] |
|
13 |
|
|
14 |
[% L.hidden_tag('action', 'Inventory/dispatch') %] |
|
15 |
[% L.hidden_tag('sort_by', FORM.sort_by) %] |
|
16 |
[% L.hidden_tag('sort_dir', FORM.sort_dir) %] |
|
17 |
[% L.hidden_tag('page', FORM.page) %] |
|
18 |
[% L.input_tag('action_stocktaking_journal', LxERP.t8('Continue'), type = 'submit', class='submit')%] |
|
19 |
|
|
20 |
|
|
21 |
<a href='#' onClick='javascript:$("#filter_table input").val("");$("#filter_table input[type=checkbox]").prop("checked", 0);'>[% 'Reset' | $T8 %]</a> |
|
22 |
|
|
23 |
</div> |
|
24 |
|
|
25 |
</form> |
|
26 |
<hr> |
templates/webpages/inventory/stocktaking/report_bottom.html | ||
---|---|---|
1 |
[% USE L %] |
|
2 |
[%- L.paginate_controls(models=SELF.stocktaking_models) %] |
Auch abrufbar als: Unified diff
Inventur: Eingabemaske und Journal im Inventory-Controller