Revision 756f5042
Von Bernd Bleßmann vor 24 Tagen hinzugefügt
SL/Controller/StockCountingReconciliation.pm | ||
---|---|---|
4 | 4 |
use parent qw(SL::Controller::Base); |
5 | 5 |
|
6 | 6 |
use English qw(-no_match_vars); |
7 |
use List::Util qw(sum0); |
|
7 |
use List::Util qw(sum sum0);
|
|
8 | 8 |
use POSIX qw(strftime); |
9 | 9 |
|
10 | 10 |
use SL::Controller::Helper::GetModels; |
11 | 11 |
use SL::Controller::Helper::ReportGenerator; |
12 | 12 |
use SL::DB::Employee; |
13 |
use SL::DB::Inventory; |
|
13 | 14 |
use SL::DB::StockCounting; |
14 | 15 |
use SL::DB::StockCountingItem; |
15 | 16 |
use SL::Helper::Flash qw(flash_later); |
... | ... | |
18 | 19 |
use SL::ReportGenerator; |
19 | 20 |
use SL::WH; |
20 | 21 |
|
22 |
|
|
23 |
use Data::Dumper; |
|
24 |
|
|
21 | 25 |
use Rose::Object::MakeMethods::Generic( |
22 | 26 |
#scalar => [ qw() ], |
23 | 27 |
'scalar --get_set_init' => [ qw(countings models) ], |
... | ... | |
74 | 78 |
$objects = \@grouped_objects; |
75 | 79 |
|
76 | 80 |
$self->get_stocked($objects); |
81 |
$self->get_inbetweens($objects); |
|
77 | 82 |
|
78 | 83 |
$self->setup_list_action_bar; |
79 | 84 |
$self->report_generator_list_objects(report => $self->{report}, objects => $objects); |
... | ... | |
82 | 87 |
sub action_reconcile { |
83 | 88 |
my ($self) = @_; |
84 | 89 |
|
85 |
my $counting_id = $::form->{filter}->{counting_id}; |
|
86 |
my $counting_items = SL::DB::Manager::StockCountingItem->get_all(query => [counting_id => $counting_id]); |
|
87 |
|
|
88 |
my $counted_qty = sum0 map { $_->qty } @$counting_items; |
|
89 |
my $stocked_qty = $counting_items->[0]->part->get_stock(bin_id => $counting_items->[0]->bin_id); |
|
90 |
|
|
91 |
my $comment = t8('correction from stock counting (counting "#1")', $counting_items->[0]->counting->name); |
|
90 |
my $counting = SL::DB::StockCounting->new(id => $::form->{counting_id})->load; |
|
91 |
# todo: sanity checks |
|
92 |
# return if $counting->is_reconciliated; |
|
93 |
# return if scalar(@{$counting->items}) == 0; |
|
94 |
|
|
95 |
my $counting_items = $counting->items; |
|
96 |
|
|
97 |
$self->get_stocked($counting_items); |
|
98 |
$self->get_inbetweens($counting_items); |
|
99 |
|
|
100 |
my $counted_by_part_and_bin; |
|
101 |
my $stocked_by_part_and_bin; |
|
102 |
my $inbetweens_by_part_and_bin; |
|
103 |
my $item_ids_by_part_and_bin; |
|
104 |
foreach my $item (@$counting_items) { |
|
105 |
$counted_by_part_and_bin ->{$item->part_id}->{$item->bin_id} += $item->qty; |
|
106 |
$stocked_by_part_and_bin ->{$item->part_id}->{$item->bin_id} ||= $item->{stocked}; |
|
107 |
$inbetweens_by_part_and_bin->{$item->part_id}->{$item->bin_id} ||= $item->{inbetweens}; |
|
108 |
push @{$item_ids_by_part_and_bin->{$item->part_id}->{$item->bin_id}}, $item->id; |
|
109 |
} |
|
92 | 110 |
|
93 |
my $transfer_qty = $counted_qty - $stocked_qty; |
|
94 |
my $src_or_dst = $transfer_qty < 0? 'src' : 'dst'; |
|
95 |
$transfer_qty = abs($transfer_qty); |
|
111 |
my $comment = t8('correction from stock counting (counting "#1")', $counting->name); |
|
96 | 112 |
|
97 | 113 |
my $transfer_error; |
98 |
# do stock |
|
99 | 114 |
$::form->throw_on_error(sub { |
100 | 115 |
eval { |
101 |
WH->transfer({ |
|
102 |
parts => $counting_items->[0]->part, |
|
103 |
$src_or_dst.'_bin' => $counting_items->[0]->bin, |
|
104 |
$src_or_dst.'_wh' => $counting_items->[0]->bin->warehouse, |
|
105 |
qty => $transfer_qty, |
|
106 |
unit => $counting_items->[0]->part->unit, |
|
107 |
transfer_type => 'correction', |
|
108 |
comment => $comment, |
|
109 |
}); |
|
116 |
SL::DB->client->with_transaction(sub { |
|
117 |
foreach my $part_id (keys %$counted_by_part_and_bin) { |
|
118 |
foreach my $bin_id (keys %{$counted_by_part_and_bin->{$part_id}}) { |
|
119 |
my $counted_qty = $counted_by_part_and_bin ->{$part_id}->{$bin_id}; |
|
120 |
my $stocked_qty = $stocked_by_part_and_bin ->{$part_id}->{$bin_id}; |
|
121 |
my $inbetween_qty = $inbetweens_by_part_and_bin->{$part_id}->{$bin_id}; |
|
122 |
|
|
123 |
my $transfer_qty = $counted_qty - $stocked_qty + $inbetween_qty; |
|
124 |
|
|
125 |
my $src_or_dst = $transfer_qty < 0? 'src' : 'dst'; |
|
126 |
$transfer_qty = abs($transfer_qty); |
|
127 |
|
|
128 |
# Do stock. |
|
129 |
# todo: run in transaction and record the inventory id in the counting items |
|
130 |
my %transfer_params = ( |
|
131 |
parts_id => $part_id, |
|
132 |
$src_or_dst.'_bin_id' => $bin_id, |
|
133 |
qty => $transfer_qty, |
|
134 |
transfer_type => 'correction', |
|
135 |
comment => $comment, |
|
136 |
); |
|
137 |
|
|
138 |
my @trans_ids = WH->transfer(\%transfer_params); |
|
139 |
|
|
140 |
if (scalar(@trans_ids) != 1) { |
|
141 |
die "Program logic error: no error, but no transfer" if scalar(@trans_ids) == 0; |
|
142 |
die "Program logic error: too many transfers" if scalar(@trans_ids) > 1; |
|
143 |
} |
|
144 |
|
|
145 |
# Get inventory entries via trans_ids- |
|
146 |
my $inventories = SL::DB::Manager::Inventory->get_all(where => [trans_id => $trans_ids[0]]); |
|
147 |
if (scalar(@$inventories) != 1) { |
|
148 |
die "Program logic error: no error, but no inventory entry" if scalar(@$inventories) == 0; |
|
149 |
die "Program logic error: too many inventory entries" if scalar(@$inventories) > 1; |
|
150 |
} |
|
151 |
|
|
152 |
|
|
153 |
SL::DB::Manager::StockCountingItem->update_all(set => {correction_inventory_id => $inventories->[0]->id}, |
|
154 |
where => [id => $item_ids_by_part_and_bin->{$part_id}->{$bin_id}]); |
|
155 |
} |
|
156 |
} |
|
157 |
|
|
158 |
1; |
|
159 |
}) or do { die SL::DB->client->error; }; # end of with_transaction |
|
160 |
|
|
110 | 161 |
1; |
111 |
} or do { $transfer_error = ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->error : $EVAL_ERROR; } |
|
112 |
}); |
|
162 |
} or do { $transfer_error = ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->error : $EVAL_ERROR; }; # end of eval |
|
163 |
|
|
164 |
}); # end of throw_on_error |
|
113 | 165 |
|
114 | 166 |
if ($transfer_error) { |
115 | 167 |
flash_later('error', $transfer_error); |
... | ... | |
146 | 198 |
my $report = SL::ReportGenerator->new(\%::myconfig, $::form); |
147 | 199 |
$self->{report} = $report; |
148 | 200 |
|
149 |
my @columns = qw(ids counting part bin qty stocked);
|
|
201 |
my @columns = qw(counting part bin qty stocked inbetweens);
|
|
150 | 202 |
|
151 | 203 |
my %column_defs = ( |
152 | 204 |
counting => { text => t8('Stock Counting'), sub => sub { $_[0]->counting->name }, }, |
... | ... | |
156 | 208 |
bin => { text => t8('Bin'), sub => sub { $_[0]->bin->full_description } }, |
157 | 209 |
employee => { text => t8('Employee'), sub => sub { $_[0]->employee ? $_[0]->employee->safe_name : '---'} }, |
158 | 210 |
stocked => { text => t8('Stocked Qty'), sub => sub { _format_total($_[0]->{stocked}) }, align => 'right'}, |
211 |
inbetweens => { text => t8('Inbetweens Qty'), sub => sub { _format_total($_[0]->{inbetweens}) }, align => 'right'}, |
|
159 | 212 |
); |
160 | 213 |
|
161 | 214 |
# remove columns from defs which are not in @columns |
... | ... | |
187 | 240 |
|
188 | 241 |
$report->set_options( |
189 | 242 |
raw_top_info_text => $self->render('stock_counting_reconciliation/report_top', { output => 0 }), |
190 |
raw_bottom_info_text => $self->render('stock_counting_reconciliation/report_bottom', { output => 0 }, models => $self->models), |
|
243 |
raw_bottom_info_text => $self->render('stock_counting_reconciliation/report_bottom', { output => 0 }, models => $self->models, counting_id => $::form->{filter}->{counting_id}),
|
|
191 | 244 |
attachment_basename => t8('stock_countings') . strftime('_%Y%m%d', localtime time), |
192 | 245 |
); |
193 | 246 |
} |
... | ... | |
218 | 271 |
$_->{stocked} = $_->part->get_stock(bin_id => $_->bin_id) for @$objects; |
219 | 272 |
} |
220 | 273 |
|
274 |
sub get_inbetweens { |
|
275 |
my ($self, $objects) = @_; |
|
276 |
|
|
277 |
# Get changes in stock while a counting was active. |
|
278 |
# (i.e. from start of counting till now). |
|
279 |
# Use itime, because shippingdate has no time component. |
|
280 |
# Ignore stock counting corrections. |
|
281 |
# Todo: warn if itime::date != shippingdate? |
|
282 |
|
|
283 |
my $correction_inventory_ids = SL::DB::Manager::StockCountingItem->get_all(where => ['!correction_inventory_id' => undef], |
|
284 |
select => ['correction_inventory_id'], |
|
285 |
distcint => 1); |
|
286 |
foreach my $object (@$objects) { |
|
287 |
my $start = $object->counting->start_time_of_counting; |
|
288 |
my $inbetweens = SL::DB::Manager::Inventory->get_all(where => [itime => { ge => $start }, |
|
289 |
parts_id => $object->part_id, |
|
290 |
bin_id => $object->bin_id, |
|
291 |
'!id' => [map { $_->correction_inventory_id } @$correction_inventory_ids]], |
|
292 |
select => ['qty']); |
|
293 |
$object->{inbetweens} = sum map { $_->qty } @$inbetweens; |
|
294 |
} |
|
295 |
} |
|
296 |
|
|
221 | 297 |
sub setup_list_action_bar { |
222 | 298 |
my ($self) = @_; |
223 | 299 |
|
Auch abrufbar als: Unified diff
Zwischeninventur-Abgleich: Bewegungen zwischen Zählung und Abgleich berücksichtigen