Revision 34035b33
Von Martin Helmling martin.helmling@octosoft.eu vor etwa 8 Jahren hinzugefügt
SL/Controller/Inventory.pm | ||
---|---|---|
2 | 2 |
|
3 | 3 |
use strict; |
4 | 4 |
use warnings; |
5 |
use POSIX qw(strftime); |
|
5 | 6 |
|
6 | 7 |
use parent qw(SL::Controller::Base); |
7 | 8 |
|
... | ... | |
10 | 11 |
use SL::DB::Warehouse; |
11 | 12 |
use SL::DB::Unit; |
12 | 13 |
use SL::WH; |
14 |
use SL::ReportGenerator; |
|
13 | 15 |
use SL::Locale::String qw(t8); |
14 | 16 |
use SL::Presenter; |
15 | 17 |
use SL::DBUtils; |
16 | 18 |
use SL::Helper::Flash; |
19 |
use SL::Controller::Helper::ReportGenerator; |
|
17 | 20 |
|
18 | 21 |
use English qw(-no_match_vars); |
19 | 22 |
|
... | ... | |
30 | 33 |
__PACKAGE__->run_before('load_bin_from_form', only => [ qw(stock_in stock) ]); |
31 | 34 |
__PACKAGE__->run_before('set_target_from_part', only => [ qw(part_changed) ]); |
32 | 35 |
__PACKAGE__->run_before('mini_stock', only => [ qw(stock_in mini_stock) ]); |
33 |
__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_in warehouse_changed part_changed) ]); |
|
36 |
__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_usage stock_in warehouse_changed part_changed) ]);
|
|
34 | 37 |
__PACKAGE__->run_before('set_layout'); |
35 | 38 |
|
36 | 39 |
sub action_stock_in { |
... | ... | |
44 | 47 |
$self->render('inventory/warehouse_selection_stock', title => $::form->{title}, TRANSFER_TYPES => $transfer_types ); |
45 | 48 |
} |
46 | 49 |
|
50 |
sub action_stock_usage { |
|
51 |
my ($self) = @_; |
|
52 |
|
|
53 |
$::form->{title} = t8('UsageE'); |
|
54 |
|
|
55 |
$::form->get_lists('warehouses' => { 'key' => 'WAREHOUSES', |
|
56 |
'bins' => 'BINS', }); |
|
57 |
$::request->layout->use_javascript("${_}.js") for qw(kivi.PartsWarehouse); |
|
58 |
|
|
59 |
$self->render('inventory/warehouse_usage', |
|
60 |
title => $::form->{title}, |
|
61 |
year => DateTime->today->year, |
|
62 |
# PARTSCLASSIFICATIONS => SL::DB:Manager::PartsClassification->get_all_classifications_by_name() , |
|
63 |
WAREHOUSES => $::form->{WAREHOUSES}, |
|
64 |
WAREHOUSE_FILTER => 1, |
|
65 |
warehouse_id => 0, |
|
66 |
bin_id => 0 |
|
67 |
); |
|
68 |
|
|
69 |
} |
|
70 |
|
|
71 |
sub getnumcolumns { |
|
72 |
my ($self) = @_; |
|
73 |
return qw(stock incorrection found insum back outcorrection disposed |
|
74 |
missing shipped used outsum consumed averconsumed); |
|
75 |
} |
|
76 |
|
|
77 |
sub action_usage { |
|
78 |
my ($self) = @_; |
|
79 |
|
|
80 |
$main::lxdebug->enter_sub(); |
|
81 |
|
|
82 |
my $form = $main::form; |
|
83 |
my %myconfig = %main::myconfig; |
|
84 |
my $locale = $main::locale; |
|
85 |
|
|
86 |
$form->{title} = t8('UsageE'); |
|
87 |
$form->{report_generator_output_format} = 'HTML' if !$form->{report_generator_output_format}; |
|
88 |
|
|
89 |
my $report = SL::ReportGenerator->new(\%myconfig, $form); |
|
90 |
|
|
91 |
my @columns = qw(partnumber partdescription); |
|
92 |
|
|
93 |
push @columns , qw(ptype unit) if $form->{report_generator_output_format} eq 'HTML'; |
|
94 |
|
|
95 |
my @numcolumns = qw(stock incorrection found insum back outcorrection disposed |
|
96 |
missing shipped used outsum consumed averconsumed); |
|
97 |
|
|
98 |
push @columns , $self->getnumcolumns(); |
|
99 |
|
|
100 |
my @hidden_variables = qw(reporttype year duetyp fromdate todate |
|
101 |
warehouse_id bin_id partnumber description bestbefore chargenumber partstypes_id); |
|
102 |
my %column_defs = ( |
|
103 |
'partnumber' => { 'text' => $locale->text('Part Number'), }, |
|
104 |
# 'partclass' => { 'text' => $locale->text('Part Classification'), }, |
|
105 |
'partdescription' => { 'text' => $locale->text('Part_br_Description'), }, |
|
106 |
'unit' => { 'text' => $locale->text('Unit'), }, |
|
107 |
'stock' => { 'text' => $locale->text('stock_br'), }, |
|
108 |
'incorrection' => { 'text' => $locale->text('correction_br'), }, |
|
109 |
'found' => { 'text' => $locale->text('found_br'), }, |
|
110 |
'insum' => { 'text' => $locale->text('sum'), }, |
|
111 |
'back' => { 'text' => $locale->text('back_br'), }, |
|
112 |
'outcorrection' => { 'text' => $locale->text('correction_br'), }, |
|
113 |
'disposed' => { 'text' => $locale->text('disposed_br'), }, |
|
114 |
'missing' => { 'text' => $locale->text('missing_br'), }, |
|
115 |
'shipped' => { 'text' => $locale->text('shipped_br'), }, |
|
116 |
'used' => { 'text' => $locale->text('used_br'), }, |
|
117 |
'outsum' => { 'text' => $locale->text('sum'), }, |
|
118 |
'consumed' => { 'text' => $locale->text('consumed'), }, |
|
119 |
'averconsumed' => { 'text' => $locale->text('averconsumed_br'), }, |
|
120 |
); |
|
121 |
|
|
122 |
|
|
123 |
map { $column_defs{$_}->{visible} = 1 } @columns; |
|
124 |
#map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns; |
|
125 |
map { $column_defs{$_}->{align} = 'right' } @numcolumns; |
|
126 |
|
|
127 |
my @custom_headers = (); |
|
128 |
# Zeile 1: |
|
129 |
push @custom_headers, [ |
|
130 |
{ 'text' => $locale->text('Part'), |
|
131 |
'colspan' => ($form->{report_generator_output_format} eq 'HTML'?4:2), 'align' => 'center'}, |
|
132 |
{ 'text' => $locale->text('Into bin'), 'colspan' => 4, 'align' => 'center'}, |
|
133 |
{ 'text' => $locale->text('From bin'), 'colspan' => 7, 'align' => 'center'}, |
|
134 |
{ 'text' => $locale->text('UsageWithout'), 'colspan' => 2, 'align' => 'center'}, |
|
135 |
]; |
|
136 |
|
|
137 |
# Zeile 2: |
|
138 |
my @line_2 = (); |
|
139 |
map { push @line_2 , $column_defs{$_} } @columns; |
|
140 |
push @custom_headers, [ @line_2 ]; |
|
141 |
|
|
142 |
$report->set_custom_headers(@custom_headers); |
|
143 |
$report->set_columns( %column_defs ); |
|
144 |
$report->set_column_order(@columns); |
|
145 |
|
|
146 |
$report->set_export_options('usage', @hidden_variables ); |
|
147 |
|
|
148 |
$report->set_sort_indicator($form->{sort}, $form->{order}); |
|
149 |
$report->set_options('output_format' => 'HTML', |
|
150 |
'controller_class' => 'Inventory', |
|
151 |
'title' => $form->{title}, |
|
152 |
# 'html_template' => 'inventory/usage_report', |
|
153 |
'attachment_basename' => strftime($locale->text('warehouse_usage_list') . '_%Y%m%d', localtime time)); |
|
154 |
$report->set_options_from_form; |
|
155 |
|
|
156 |
my %searchparams ; |
|
157 |
# form vars |
|
158 |
# reporttype = custom |
|
159 |
# year = 2014 |
|
160 |
# duetyp = 7 |
|
161 |
|
|
162 |
my $start = DateTime->now_local; |
|
163 |
my $end = DateTime->now_local; |
|
164 |
my $actualepoch = $end->epoch(); |
|
165 |
my $days = 365; |
|
166 |
my $mdays=30; |
|
167 |
$searchparams{reporttype} = $form->{reporttype}; |
|
168 |
if ($form->{reporttype} eq "custom") { |
|
169 |
my $smon = 1; |
|
170 |
my $emon = 12; |
|
171 |
my $sday = 1; |
|
172 |
my $eday = 31; |
|
173 |
#forgotten the year --> thisyear |
|
174 |
if ($form->{year} !~ m/^\d\d\d\d$/) { |
|
175 |
$locale->date(\%myconfig, $form->current_date(\%myconfig), 0) =~ |
|
176 |
/(\d\d\d\d)/; |
|
177 |
$form->{year} = $1; |
|
178 |
} |
|
179 |
my $leapday = ($form->{year} % 4 == 0) ? 1:0; |
|
180 |
#yearly report |
|
181 |
if ($form->{duetyp} eq "13") { |
|
182 |
$days += $leapday; |
|
183 |
} |
|
184 |
|
|
185 |
#Quater reports |
|
186 |
if ($form->{duetyp} eq "A") { |
|
187 |
$emon = 3; |
|
188 |
$days = 90 + $leapday; |
|
189 |
} |
|
190 |
if ($form->{duetyp} eq "B") { |
|
191 |
$smon = 4; |
|
192 |
$emon = 6; |
|
193 |
$eday = 30; |
|
194 |
$days = 91; |
|
195 |
} |
|
196 |
if ($form->{duetyp} eq "C") { |
|
197 |
$smon = 7; |
|
198 |
$emon = 9; |
|
199 |
$eday = 30; |
|
200 |
$days = 92; |
|
201 |
} |
|
202 |
if ($form->{duetyp} eq "D") { |
|
203 |
$smon = 10; |
|
204 |
$days = 92; |
|
205 |
} |
|
206 |
#Monthly reports |
|
207 |
if ($form->{duetyp} eq "1" || $form->{duetyp} eq "3" || $form->{duetyp} eq "5" || |
|
208 |
$form->{duetyp} eq "7" || $form->{duetyp} eq "8" || $form->{duetyp} eq "10" || |
|
209 |
$form->{duetyp} eq "12") { |
|
210 |
$smon = $emon = $form->{duetyp}*1; |
|
211 |
$mdays=$days = 31; |
|
212 |
} |
|
213 |
if ($form->{duetyp} eq "2" || $form->{duetyp} eq "4" || $form->{duetyp} eq "6" || |
|
214 |
$form->{duetyp} eq "9" || $form->{duetyp} eq "11" ) { |
|
215 |
$smon = $emon = $form->{duetyp}*1; |
|
216 |
$eday = 30; |
|
217 |
if ($form->{duetyp} eq "2" ) { |
|
218 |
#this works from 1901 to 2099, 1900 and 2100 fail. |
|
219 |
$eday = ($form->{year} % 4 == 0) ? 29 : 28; |
|
220 |
} |
|
221 |
$mdays=$days = $eday; |
|
222 |
} |
|
223 |
$searchparams{year} = $form->{year}; |
|
224 |
$searchparams{duetyp} = $form->{duetyp}; |
|
225 |
$start->set_month($smon); |
|
226 |
$start->set_day($sday); |
|
227 |
$start->set_year($form->{year}*1); |
|
228 |
$end->set_month($emon); |
|
229 |
$end->set_day($eday); |
|
230 |
$end->set_year($form->{year}*1); |
|
231 |
} else { |
|
232 |
$searchparams{fromdate} = $form->{fromdate}; |
|
233 |
$searchparams{todate} = $form->{todate}; |
|
234 |
# reporttype = free |
|
235 |
# fromdate = 01.01.2014 |
|
236 |
# todate = 31.05.2014 |
|
237 |
my ($yy, $mm, $dd) = $locale->parse_date(\%myconfig,$form->{fromdate}); |
|
238 |
$start->set_year($yy); |
|
239 |
$start->set_month($mm); |
|
240 |
$start->set_day($dd); |
|
241 |
($yy, $mm, $dd) = $locale->parse_date(\%myconfig,$form->{todate}); |
|
242 |
$end->set_year($yy); |
|
243 |
$end->set_month($mm); |
|
244 |
$end->set_day($dd); |
|
245 |
my $dur = $start->delta_md($end); |
|
246 |
$days = $dur->delta_months()*30 + $dur->delta_days() ; |
|
247 |
} |
|
248 |
$start->set_second(0); |
|
249 |
$start->set_minute(0); |
|
250 |
$start->set_hour(0); |
|
251 |
$end->set_second(59); |
|
252 |
$end->set_minute(59); |
|
253 |
$end->set_hour(23); |
|
254 |
if ( $end->epoch() > $actualepoch ) { |
|
255 |
$end = DateTime->now_local; |
|
256 |
my $dur = $start->delta_md($end); |
|
257 |
$days = $dur->delta_months()*30 + $dur->delta_days() ; |
|
258 |
} |
|
259 |
if ( $start->epoch() > $end->epoch() ) { $start = $end;$days = 1;} |
|
260 |
$days = $mdays if $days < $mdays; |
|
261 |
#$main::lxdebug->message(LXDebug->DEBUG2(), "start=".$start->epoch()); |
|
262 |
#$main::lxdebug->message(LXDebug->DEBUG2(), " end=".$end->epoch()); |
|
263 |
#$main::lxdebug->message(LXDebug->DEBUG2(), " days=".$days); |
|
264 |
my @andfilter = (shippingdate => { ge => $start }, shippingdate => { le => $end } ); |
|
265 |
if ( $form->{warehouse_id} ) { |
|
266 |
push @andfilter , ( warehouse_id => $form->{warehouse_id}); |
|
267 |
$searchparams{warehouse_id} = $form->{warehouse_id}; |
|
268 |
if ( $form->{bin_id} ) { |
|
269 |
push @andfilter , ( bin_id => $form->{bin_id}); |
|
270 |
$searchparams{bin_id} = $form->{bin_id}; |
|
271 |
} |
|
272 |
} |
|
273 |
# alias class t2 entspricht parts |
|
274 |
if ( $form->{partnumber} ) { |
|
275 |
push @andfilter , ( 't2.partnumber' => { ilike => '%'. $form->{partnumber} .'%' }); |
|
276 |
$searchparams{partnumber} = $form->{partnumber}; |
|
277 |
} |
|
278 |
if ( $form->{description} ) { |
|
279 |
push @andfilter , ( 't2.description' => { ilike => '%'. $form->{description} .'%' }); |
|
280 |
$searchparams{description} = $form->{description}; |
|
281 |
} |
|
282 |
if ( $form->{bestbefore} ) { |
|
283 |
push @andfilter , ( bestbefore => { eq => $form->{bestbefore} }); |
|
284 |
$searchparams{bestbefore} = $form->{bestbefore}; |
|
285 |
} |
|
286 |
if ( $form->{chargenumber} ) { |
|
287 |
push @andfilter , ( chargenumber => { ilike => '%'.$form->{chargenumber}.'%' }); |
|
288 |
$searchparams{chargenumber} = $form->{chargenumber}; |
|
289 |
} |
|
290 |
if ( $form->{partstypes_id} ) { |
|
291 |
push @andfilter , ( 't2.partstypes_id' => $form->{partstypes_id} ); |
|
292 |
$searchparams{partstypes_id} = $form->{partstypes_id}; |
|
293 |
} |
|
294 |
|
|
295 |
my @filter = (and => [ @andfilter ] ); |
|
296 |
|
|
297 |
my $objs = SL::DB::Manager::Inventory->get_all(with_objects => ['parts'], where => [ @filter ] , sort_by => 'parts.partnumber ASC'); |
|
298 |
#my $objs = SL::DB::Inventory->_get_manager_class->get_all(...); |
|
299 |
|
|
300 |
# manual paginating, yuck |
|
301 |
my $page = $::form->{page} || 1; |
|
302 |
my $pages = {}; |
|
303 |
$pages->{per_page} = $::form->{per_page} || 20; |
|
304 |
my $first_nr = ($page - 1) * $pages->{per_page}; |
|
305 |
my $last_nr = $first_nr + $pages->{per_page}; |
|
306 |
|
|
307 |
my $last_partid = 0; |
|
308 |
my $last_row = { }; |
|
309 |
my $row_ind = 0; |
|
310 |
my $allrows = 0; |
|
311 |
$allrows = 1 if $form->{report_generator_output_format} ne 'HTML' ; |
|
312 |
#$main::lxdebug->message(LXDebug->DEBUG2(), "first_nr=".$first_nr." last_nr=".$last_nr); |
|
313 |
foreach my $entry (@{ $objs } ) { |
|
314 |
if ( $entry->parts_id != $last_partid ) { |
|
315 |
if ( $last_partid > 0 ) { |
|
316 |
if ( $allrows || ($row_ind >= $first_nr && $row_ind < $last_nr )) { |
|
317 |
$self->make_row_result($last_row,$days,$last_partid); |
|
318 |
$report->add_data($last_row); |
|
319 |
} |
|
320 |
$row_ind++ ; |
|
321 |
} |
|
322 |
$last_partid = $entry->parts_id; |
|
323 |
$last_row = { }; |
|
324 |
$last_row->{partnumber}->{data} = $entry->part->partnumber; |
|
325 |
$last_row->{partdescription}->{data} = $entry->part->description; |
|
326 |
$last_row->{unit}->{data} = $entry->part->unit; |
|
327 |
$last_row->{stock}->{data} = 0; |
|
328 |
$last_row->{incorrection}->{data} = 0; |
|
329 |
$last_row->{found}->{data} = 0; |
|
330 |
$last_row->{back}->{data} = 0; |
|
331 |
$last_row->{outcorrection}->{data} = 0; |
|
332 |
$last_row->{disposed}->{data} = 0; |
|
333 |
$last_row->{missing}->{data} = 0; |
|
334 |
$last_row->{shipped}->{data} = 0; |
|
335 |
$last_row->{used}->{data} = 0; |
|
336 |
$last_row->{insum}->{data} = 0; |
|
337 |
$last_row->{outsum}->{data} = 0; |
|
338 |
$last_row->{consumed}->{data} = 0; |
|
339 |
$last_row->{averconsumed}->{data} = 0; |
|
340 |
} |
|
341 |
if ( !$allrows && $row_ind >= $last_nr ) { |
|
342 |
next; |
|
343 |
} |
|
344 |
my $prefix=''; |
|
345 |
if ( $entry->trans_type->description eq 'correction' ) { |
|
346 |
$prefix = $entry->trans_type->direction; |
|
347 |
} |
|
348 |
$last_row->{$prefix.$entry->trans_type->description}->{data} += |
|
349 |
( $entry->trans_type->direction eq 'out' ? -$entry->qty : $entry->qty ); |
|
350 |
} |
|
351 |
if ( $last_partid > 0 && ( $allrows || ($row_ind >= $first_nr && $row_ind < $last_nr ))) { |
|
352 |
$self->make_row_result($last_row,$days,$last_partid); |
|
353 |
$report->add_data($last_row); |
|
354 |
$row_ind++ ; |
|
355 |
} |
|
356 |
my $num_rows = @{ $report->{data} } ; |
|
357 |
#$main::lxdebug->message(LXDebug->DEBUG2(), "count=".$row_ind." rows=".$num_rows); |
|
358 |
|
|
359 |
if ( ! $allrows ) { |
|
360 |
$pages->{max} = SL::DB::Helper::Paginated::ceil($row_ind, $pages->{per_page}) || 1; |
|
361 |
$pages->{page} = $page < 1 ? 1: $page > $pages->{max} ? $pages->{max}: $page; |
|
362 |
$pages->{common} = [ grep { $_->{visible} } @{ SL::DB::Helper::Paginated::make_common_pages($pages->{page}, $pages->{max}) } ]; |
|
363 |
$self->{pages} = $pages; |
|
364 |
$searchparams{action} = "usage"; |
|
365 |
$self->{base_url} = $self->url_for(\%searchparams ); |
|
366 |
#$main::lxdebug->message(LXDebug->DEBUG2(), "page=".$pages->{page}." url=".$self->{base_url}); |
|
367 |
|
|
368 |
$report->set_options('raw_bottom_info_text' => $self->render('inventory/report_bottom', { output => 0 }) ); |
|
369 |
} |
|
370 |
$report->generate_with_headers(); |
|
371 |
|
|
372 |
$main::lxdebug->leave_sub(); |
|
373 |
|
|
374 |
} |
|
375 |
|
|
376 |
sub make_row_result { |
|
377 |
my ($self,$row,$days,$partid) = @_; |
|
378 |
my $form = $main::form; |
|
379 |
my $myconfig = \%main::myconfig; |
|
380 |
|
|
381 |
$row->{insum}->{data} = $row->{stock}->{data} + $row->{incorrection}->{data} + $row->{found}->{data}; |
|
382 |
$row->{outsum}->{data} = $row->{back}->{data} + $row->{outcorrection}->{data} + $row->{disposed}->{data} + |
|
383 |
$row->{missing}->{data} + $row->{shipped}->{data} + $row->{used}->{data}; |
|
384 |
$row->{consumed}->{data} = $row->{outsum}->{data} - |
|
385 |
$row->{outcorrection}->{data} - $row->{incorrection}->{data}; |
|
386 |
$row->{averconsumed}->{data} = $row->{consumed}->{data}*30/$days ; |
|
387 |
map { $row->{$_}->{data} = $form->format_amount($myconfig,$row->{$_}->{data},2); } $self->getnumcolumns(); |
|
388 |
# $row->{partclass}->{data} = ''; |
|
389 |
$row->{partnumber}->{link} = 'ic.pl?action=edit&id='.$partid; |
|
390 |
# $row->{partdescription}->{link} = 'ic.pl?action=edit&id='.$partid; |
|
391 |
} |
|
392 |
|
|
47 | 393 |
sub action_stock { |
48 | 394 |
my ($self) = @_; |
49 | 395 |
|
... | ... | |
153 | 499 |
SL::DB::Manager::Warehouse->get_all(query => [ or => [ invalid => 0, invalid => undef ]]); |
154 | 500 |
} |
155 | 501 |
|
502 |
#sub init_bins { |
|
503 |
# SL::DB::Manager::Bin->get_all(); |
|
504 |
#} |
|
505 |
|
|
156 | 506 |
sub init_units { |
157 | 507 |
SL::DB::Manager::Unit->get_all; |
158 | 508 |
} |
... | ... | |
175 | 525 |
|
176 | 526 |
$self->warehouse($self->warehouses->[0]) if !$self->warehouse || !$self->warehouse->id; |
177 | 527 |
$self->bin ($self->warehouse->bins->[0]) if !$self->bin || !$self->bin->id; |
528 |
# foreach my $warehouse ( $self->warehouses ) { |
|
529 |
# $warehouse->{BINS} = []; |
|
530 |
# foreach my $bin ( $self->bins ) { |
|
531 |
# if ( $bin->warehouse_id == $warehouse->id ) { |
|
532 |
# push @{ $warehouse->{BINS} }, $bin; |
|
533 |
# } |
|
534 |
# } |
|
535 |
# } |
|
178 | 536 |
} |
179 | 537 |
|
180 | 538 |
sub load_part_from_form { |
Auch abrufbar als: Unified diff
Verbrauchsbericht: Lager->Berichte->Lagerentnahme
Ein Bericht über jährliche/quartalsweise/monatliche Lagerentnahme
Anforderungen waren:
1. Zahlen im Bericht einheitlich Formatieren (mit Tausender-Trennpunkt)
2. Spaltenüberschrift:
- "Lagerverbrauch" (Letzte zwei Spalten)
> "Entnommen (ohne Korrekturen)"
"Verbrauch monatlich" (vorletzte Spalte)> "Im Zeitraum"
"Ø Verbr." (letzte Spalte -> "Ø mtl."3. In der Suchmaske unter "Vorgewählte Zeiträume" sollte das aktuelle Jahr vorgegeben sein.
4. Filter "Freier Zeitraum" funktioniert nicht: "Es wurden keine Daten gefunden."
Bei der Berechnung des "Ø mtl." wird Folgendes beachtet:
Falls ein Zeitraum angegeben ist mit einem Enddatum in der Zukunft (z.B. auch bei "Jährlich" oder "Quartal")
muss der Bezugszeitraum mit dem aktuellen Datum (heute) begrenzt werden, ansonsten wird der Durchschnitt falsch berechnet!
Bei Zeitraum kleiner einem Monat, wird Durchschnitt des Entnommenen über einen Monat berechnet
Bei PDF/CSV Export müssen alle Werte berechnet werden
Wegen PDF-Erzeugung ausserhalb Papier X-Rand default reduziert auf 0,8 cm