From 34035b33f21af21316df798b19f2a758aa86b3a8 Mon Sep 17 00:00:00 2001
From: "Martin Helmling martin.helmling@octosoft.eu"
Date: Mon, 19 Sep 2016 17:54:40 +0200
Subject: [PATCH] Verbrauchsbericht: Lager->Berichte->Lagerentnahme
MIME-Version: 1.0
Content-Type: text/plain; charset=utf8
Content-Transfer-Encoding: 8bit
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
---
SL/Controller/Inventory.pm | 360 +++++++++++++++++-
bin/mozilla/wh.pl | 69 +++-
doc/changelog | 3 +
locale/de/all | 20 +
menus/user/12-warehouse-usage.yaml | 15 +
.../webpages/inventory/report_bottom.html | 1 +
.../webpages/inventory/warehouse_usage.html | 126 ++++++
.../report_generator/pdf_export_options.html | 11 +-
8 files changed, 585 insertions(+), 20 deletions(-)
create mode 100644 menus/user/12-warehouse-usage.yaml
create mode 100644 templates/webpages/inventory/report_bottom.html
create mode 100644 templates/webpages/inventory/warehouse_usage.html
diff --git a/SL/Controller/Inventory.pm b/SL/Controller/Inventory.pm
index 6392ecc0c..454b012e2 100644
--- a/SL/Controller/Inventory.pm
+++ b/SL/Controller/Inventory.pm
@@ -2,6 +2,7 @@ package SL::Controller::Inventory;
use strict;
use warnings;
+use POSIX qw(strftime);
use parent qw(SL::Controller::Base);
@@ -10,10 +11,12 @@ use SL::DB::Part;
use SL::DB::Warehouse;
use SL::DB::Unit;
use SL::WH;
+use SL::ReportGenerator;
use SL::Locale::String qw(t8);
use SL::Presenter;
use SL::DBUtils;
use SL::Helper::Flash;
+use SL::Controller::Helper::ReportGenerator;
use English qw(-no_match_vars);
@@ -30,7 +33,7 @@ __PACKAGE__->run_before('load_wh_from_form', only => [ qw(stock_in warehouse
__PACKAGE__->run_before('load_bin_from_form', only => [ qw(stock_in stock) ]);
__PACKAGE__->run_before('set_target_from_part', only => [ qw(part_changed) ]);
__PACKAGE__->run_before('mini_stock', only => [ qw(stock_in mini_stock) ]);
-__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_in warehouse_changed part_changed) ]);
+__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_usage stock_in warehouse_changed part_changed) ]);
__PACKAGE__->run_before('set_layout');
sub action_stock_in {
@@ -44,6 +47,349 @@ sub action_stock_in {
$self->render('inventory/warehouse_selection_stock', title => $::form->{title}, TRANSFER_TYPES => $transfer_types );
}
+sub action_stock_usage {
+ my ($self) = @_;
+
+ $::form->{title} = t8('UsageE');
+
+ $::form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
+ 'bins' => 'BINS', });
+ $::request->layout->use_javascript("${_}.js") for qw(kivi.PartsWarehouse);
+
+ $self->render('inventory/warehouse_usage',
+ title => $::form->{title},
+ year => DateTime->today->year,
+ # PARTSCLASSIFICATIONS => SL::DB:Manager::PartsClassification->get_all_classifications_by_name() ,
+ WAREHOUSES => $::form->{WAREHOUSES},
+ WAREHOUSE_FILTER => 1,
+ warehouse_id => 0,
+ bin_id => 0
+ );
+
+}
+
+sub getnumcolumns {
+ my ($self) = @_;
+ return qw(stock incorrection found insum back outcorrection disposed
+ missing shipped used outsum consumed averconsumed);
+}
+
+sub action_usage {
+ my ($self) = @_;
+
+ $main::lxdebug->enter_sub();
+
+ my $form = $main::form;
+ my %myconfig = %main::myconfig;
+ my $locale = $main::locale;
+
+ $form->{title} = t8('UsageE');
+ $form->{report_generator_output_format} = 'HTML' if !$form->{report_generator_output_format};
+
+ my $report = SL::ReportGenerator->new(\%myconfig, $form);
+
+ my @columns = qw(partnumber partdescription);
+
+ push @columns , qw(ptype unit) if $form->{report_generator_output_format} eq 'HTML';
+
+ my @numcolumns = qw(stock incorrection found insum back outcorrection disposed
+ missing shipped used outsum consumed averconsumed);
+
+ push @columns , $self->getnumcolumns();
+
+ my @hidden_variables = qw(reporttype year duetyp fromdate todate
+ warehouse_id bin_id partnumber description bestbefore chargenumber partstypes_id);
+ my %column_defs = (
+ 'partnumber' => { 'text' => $locale->text('Part Number'), },
+ # 'partclass' => { 'text' => $locale->text('Part Classification'), },
+ 'partdescription' => { 'text' => $locale->text('Part_br_Description'), },
+ 'unit' => { 'text' => $locale->text('Unit'), },
+ 'stock' => { 'text' => $locale->text('stock_br'), },
+ 'incorrection' => { 'text' => $locale->text('correction_br'), },
+ 'found' => { 'text' => $locale->text('found_br'), },
+ 'insum' => { 'text' => $locale->text('sum'), },
+ 'back' => { 'text' => $locale->text('back_br'), },
+ 'outcorrection' => { 'text' => $locale->text('correction_br'), },
+ 'disposed' => { 'text' => $locale->text('disposed_br'), },
+ 'missing' => { 'text' => $locale->text('missing_br'), },
+ 'shipped' => { 'text' => $locale->text('shipped_br'), },
+ 'used' => { 'text' => $locale->text('used_br'), },
+ 'outsum' => { 'text' => $locale->text('sum'), },
+ 'consumed' => { 'text' => $locale->text('consumed'), },
+ 'averconsumed' => { 'text' => $locale->text('averconsumed_br'), },
+ );
+
+
+ map { $column_defs{$_}->{visible} = 1 } @columns;
+ #map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
+ map { $column_defs{$_}->{align} = 'right' } @numcolumns;
+
+ my @custom_headers = ();
+ # Zeile 1:
+ push @custom_headers, [
+ { 'text' => $locale->text('Part'),
+ 'colspan' => ($form->{report_generator_output_format} eq 'HTML'?4:2), 'align' => 'center'},
+ { 'text' => $locale->text('Into bin'), 'colspan' => 4, 'align' => 'center'},
+ { 'text' => $locale->text('From bin'), 'colspan' => 7, 'align' => 'center'},
+ { 'text' => $locale->text('UsageWithout'), 'colspan' => 2, 'align' => 'center'},
+ ];
+
+ # Zeile 2:
+ my @line_2 = ();
+ map { push @line_2 , $column_defs{$_} } @columns;
+ push @custom_headers, [ @line_2 ];
+
+ $report->set_custom_headers(@custom_headers);
+ $report->set_columns( %column_defs );
+ $report->set_column_order(@columns);
+
+ $report->set_export_options('usage', @hidden_variables );
+
+ $report->set_sort_indicator($form->{sort}, $form->{order});
+ $report->set_options('output_format' => 'HTML',
+ 'controller_class' => 'Inventory',
+ 'title' => $form->{title},
+# 'html_template' => 'inventory/usage_report',
+ 'attachment_basename' => strftime($locale->text('warehouse_usage_list') . '_%Y%m%d', localtime time));
+ $report->set_options_from_form;
+
+ my %searchparams ;
+# form vars
+# reporttype = custom
+# year = 2014
+# duetyp = 7
+
+ my $start = DateTime->now_local;
+ my $end = DateTime->now_local;
+ my $actualepoch = $end->epoch();
+ my $days = 365;
+ my $mdays=30;
+ $searchparams{reporttype} = $form->{reporttype};
+ if ($form->{reporttype} eq "custom") {
+ my $smon = 1;
+ my $emon = 12;
+ my $sday = 1;
+ my $eday = 31;
+ #forgotten the year --> thisyear
+ if ($form->{year} !~ m/^\d\d\d\d$/) {
+ $locale->date(\%myconfig, $form->current_date(\%myconfig), 0) =~
+ /(\d\d\d\d)/;
+ $form->{year} = $1;
+ }
+ my $leapday = ($form->{year} % 4 == 0) ? 1:0;
+ #yearly report
+ if ($form->{duetyp} eq "13") {
+ $days += $leapday;
+ }
+
+ #Quater reports
+ if ($form->{duetyp} eq "A") {
+ $emon = 3;
+ $days = 90 + $leapday;
+ }
+ if ($form->{duetyp} eq "B") {
+ $smon = 4;
+ $emon = 6;
+ $eday = 30;
+ $days = 91;
+ }
+ if ($form->{duetyp} eq "C") {
+ $smon = 7;
+ $emon = 9;
+ $eday = 30;
+ $days = 92;
+ }
+ if ($form->{duetyp} eq "D") {
+ $smon = 10;
+ $days = 92;
+ }
+ #Monthly reports
+ if ($form->{duetyp} eq "1" || $form->{duetyp} eq "3" || $form->{duetyp} eq "5" ||
+ $form->{duetyp} eq "7" || $form->{duetyp} eq "8" || $form->{duetyp} eq "10" ||
+ $form->{duetyp} eq "12") {
+ $smon = $emon = $form->{duetyp}*1;
+ $mdays=$days = 31;
+ }
+ if ($form->{duetyp} eq "2" || $form->{duetyp} eq "4" || $form->{duetyp} eq "6" ||
+ $form->{duetyp} eq "9" || $form->{duetyp} eq "11" ) {
+ $smon = $emon = $form->{duetyp}*1;
+ $eday = 30;
+ if ($form->{duetyp} eq "2" ) {
+ #this works from 1901 to 2099, 1900 and 2100 fail.
+ $eday = ($form->{year} % 4 == 0) ? 29 : 28;
+ }
+ $mdays=$days = $eday;
+ }
+ $searchparams{year} = $form->{year};
+ $searchparams{duetyp} = $form->{duetyp};
+ $start->set_month($smon);
+ $start->set_day($sday);
+ $start->set_year($form->{year}*1);
+ $end->set_month($emon);
+ $end->set_day($eday);
+ $end->set_year($form->{year}*1);
+ } else {
+ $searchparams{fromdate} = $form->{fromdate};
+ $searchparams{todate} = $form->{todate};
+# reporttype = free
+# fromdate = 01.01.2014
+# todate = 31.05.2014
+ my ($yy, $mm, $dd) = $locale->parse_date(\%myconfig,$form->{fromdate});
+ $start->set_year($yy);
+ $start->set_month($mm);
+ $start->set_day($dd);
+ ($yy, $mm, $dd) = $locale->parse_date(\%myconfig,$form->{todate});
+ $end->set_year($yy);
+ $end->set_month($mm);
+ $end->set_day($dd);
+ my $dur = $start->delta_md($end);
+ $days = $dur->delta_months()*30 + $dur->delta_days() ;
+ }
+ $start->set_second(0);
+ $start->set_minute(0);
+ $start->set_hour(0);
+ $end->set_second(59);
+ $end->set_minute(59);
+ $end->set_hour(23);
+ if ( $end->epoch() > $actualepoch ) {
+ $end = DateTime->now_local;
+ my $dur = $start->delta_md($end);
+ $days = $dur->delta_months()*30 + $dur->delta_days() ;
+ }
+ if ( $start->epoch() > $end->epoch() ) { $start = $end;$days = 1;}
+ $days = $mdays if $days < $mdays;
+ #$main::lxdebug->message(LXDebug->DEBUG2(), "start=".$start->epoch());
+ #$main::lxdebug->message(LXDebug->DEBUG2(), " end=".$end->epoch());
+ #$main::lxdebug->message(LXDebug->DEBUG2(), " days=".$days);
+ my @andfilter = (shippingdate => { ge => $start }, shippingdate => { le => $end } );
+ if ( $form->{warehouse_id} ) {
+ push @andfilter , ( warehouse_id => $form->{warehouse_id});
+ $searchparams{warehouse_id} = $form->{warehouse_id};
+ if ( $form->{bin_id} ) {
+ push @andfilter , ( bin_id => $form->{bin_id});
+ $searchparams{bin_id} = $form->{bin_id};
+ }
+ }
+ # alias class t2 entspricht parts
+ if ( $form->{partnumber} ) {
+ push @andfilter , ( 't2.partnumber' => { ilike => '%'. $form->{partnumber} .'%' });
+ $searchparams{partnumber} = $form->{partnumber};
+ }
+ if ( $form->{description} ) {
+ push @andfilter , ( 't2.description' => { ilike => '%'. $form->{description} .'%' });
+ $searchparams{description} = $form->{description};
+ }
+ if ( $form->{bestbefore} ) {
+ push @andfilter , ( bestbefore => { eq => $form->{bestbefore} });
+ $searchparams{bestbefore} = $form->{bestbefore};
+ }
+ if ( $form->{chargenumber} ) {
+ push @andfilter , ( chargenumber => { ilike => '%'.$form->{chargenumber}.'%' });
+ $searchparams{chargenumber} = $form->{chargenumber};
+ }
+ if ( $form->{partstypes_id} ) {
+ push @andfilter , ( 't2.partstypes_id' => $form->{partstypes_id} );
+ $searchparams{partstypes_id} = $form->{partstypes_id};
+ }
+
+ my @filter = (and => [ @andfilter ] );
+
+ my $objs = SL::DB::Manager::Inventory->get_all(with_objects => ['parts'], where => [ @filter ] , sort_by => 'parts.partnumber ASC');
+ #my $objs = SL::DB::Inventory->_get_manager_class->get_all(...);
+
+ # manual paginating, yuck
+ my $page = $::form->{page} || 1;
+ my $pages = {};
+ $pages->{per_page} = $::form->{per_page} || 20;
+ my $first_nr = ($page - 1) * $pages->{per_page};
+ my $last_nr = $first_nr + $pages->{per_page};
+
+ my $last_partid = 0;
+ my $last_row = { };
+ my $row_ind = 0;
+ my $allrows = 0;
+ $allrows = 1 if $form->{report_generator_output_format} ne 'HTML' ;
+ #$main::lxdebug->message(LXDebug->DEBUG2(), "first_nr=".$first_nr." last_nr=".$last_nr);
+ foreach my $entry (@{ $objs } ) {
+ if ( $entry->parts_id != $last_partid ) {
+ if ( $last_partid > 0 ) {
+ if ( $allrows || ($row_ind >= $first_nr && $row_ind < $last_nr )) {
+ $self->make_row_result($last_row,$days,$last_partid);
+ $report->add_data($last_row);
+ }
+ $row_ind++ ;
+ }
+ $last_partid = $entry->parts_id;
+ $last_row = { };
+ $last_row->{partnumber}->{data} = $entry->part->partnumber;
+ $last_row->{partdescription}->{data} = $entry->part->description;
+ $last_row->{unit}->{data} = $entry->part->unit;
+ $last_row->{stock}->{data} = 0;
+ $last_row->{incorrection}->{data} = 0;
+ $last_row->{found}->{data} = 0;
+ $last_row->{back}->{data} = 0;
+ $last_row->{outcorrection}->{data} = 0;
+ $last_row->{disposed}->{data} = 0;
+ $last_row->{missing}->{data} = 0;
+ $last_row->{shipped}->{data} = 0;
+ $last_row->{used}->{data} = 0;
+ $last_row->{insum}->{data} = 0;
+ $last_row->{outsum}->{data} = 0;
+ $last_row->{consumed}->{data} = 0;
+ $last_row->{averconsumed}->{data} = 0;
+ }
+ if ( !$allrows && $row_ind >= $last_nr ) {
+ next;
+ }
+ my $prefix='';
+ if ( $entry->trans_type->description eq 'correction' ) {
+ $prefix = $entry->trans_type->direction;
+ }
+ $last_row->{$prefix.$entry->trans_type->description}->{data} +=
+ ( $entry->trans_type->direction eq 'out' ? -$entry->qty : $entry->qty );
+ }
+ if ( $last_partid > 0 && ( $allrows || ($row_ind >= $first_nr && $row_ind < $last_nr ))) {
+ $self->make_row_result($last_row,$days,$last_partid);
+ $report->add_data($last_row);
+ $row_ind++ ;
+ }
+ my $num_rows = @{ $report->{data} } ;
+ #$main::lxdebug->message(LXDebug->DEBUG2(), "count=".$row_ind." rows=".$num_rows);
+
+ if ( ! $allrows ) {
+ $pages->{max} = SL::DB::Helper::Paginated::ceil($row_ind, $pages->{per_page}) || 1;
+ $pages->{page} = $page < 1 ? 1: $page > $pages->{max} ? $pages->{max}: $page;
+ $pages->{common} = [ grep { $_->{visible} } @{ SL::DB::Helper::Paginated::make_common_pages($pages->{page}, $pages->{max}) } ];
+ $self->{pages} = $pages;
+ $searchparams{action} = "usage";
+ $self->{base_url} = $self->url_for(\%searchparams );
+ #$main::lxdebug->message(LXDebug->DEBUG2(), "page=".$pages->{page}." url=".$self->{base_url});
+
+ $report->set_options('raw_bottom_info_text' => $self->render('inventory/report_bottom', { output => 0 }) );
+ }
+ $report->generate_with_headers();
+
+ $main::lxdebug->leave_sub();
+
+}
+
+sub make_row_result {
+ my ($self,$row,$days,$partid) = @_;
+ my $form = $main::form;
+ my $myconfig = \%main::myconfig;
+
+ $row->{insum}->{data} = $row->{stock}->{data} + $row->{incorrection}->{data} + $row->{found}->{data};
+ $row->{outsum}->{data} = $row->{back}->{data} + $row->{outcorrection}->{data} + $row->{disposed}->{data} +
+ $row->{missing}->{data} + $row->{shipped}->{data} + $row->{used}->{data};
+ $row->{consumed}->{data} = $row->{outsum}->{data} -
+ $row->{outcorrection}->{data} - $row->{incorrection}->{data};
+ $row->{averconsumed}->{data} = $row->{consumed}->{data}*30/$days ;
+ map { $row->{$_}->{data} = $form->format_amount($myconfig,$row->{$_}->{data},2); } $self->getnumcolumns();
+# $row->{partclass}->{data} = '';
+ $row->{partnumber}->{link} = 'ic.pl?action=edit&id='.$partid;
+# $row->{partdescription}->{link} = 'ic.pl?action=edit&id='.$partid;
+}
+
sub action_stock {
my ($self) = @_;
@@ -153,6 +499,10 @@ sub init_warehouses {
SL::DB::Manager::Warehouse->get_all(query => [ or => [ invalid => 0, invalid => undef ]]);
}
+#sub init_bins {
+# SL::DB::Manager::Bin->get_all();
+#}
+
sub init_units {
SL::DB::Manager::Unit->get_all;
}
@@ -175,6 +525,14 @@ sub sanitize_target {
$self->warehouse($self->warehouses->[0]) if !$self->warehouse || !$self->warehouse->id;
$self->bin ($self->warehouse->bins->[0]) if !$self->bin || !$self->bin->id;
+# foreach my $warehouse ( $self->warehouses ) {
+# $warehouse->{BINS} = [];
+# foreach my $bin ( $self->bins ) {
+# if ( $bin->warehouse_id == $warehouse->id ) {
+# push @{ $warehouse->{BINS} }, $bin;
+# }
+# }
+# }
}
sub load_part_from_form {
diff --git a/bin/mozilla/wh.pl b/bin/mozilla/wh.pl
index 5ffa38289..fb359085e 100644
--- a/bin/mozilla/wh.pl
+++ b/bin/mozilla/wh.pl
@@ -674,6 +674,8 @@ sub generate_journal {
$form->{title} = $locale->text("WHJournal");
$form->{sort} ||= 'date';
+ $form->{report_generator_output_format} = 'HTML' if !$form->{report_generator_output_format};
+
my %filter;
my @columns = qw(trans_id date warehouse_from bin_from warehouse_to bin_to partnumber partdescription chargenumber bestbefore trans_type comment qty employee oe_id projectnumber);
@@ -709,13 +711,16 @@ sub generate_journal {
'chargenumber' => { 'text' => $locale->text('Charge Number'), },
'bestbefore' => { 'text' => $locale->text('Best Before'), },
'qty' => { 'text' => $locale->text('Qty'), },
+ 'unit' => { 'text' => $locale->text('Part Unit'), },
+ 'partunit' => { 'text' => $locale->text('Unit'), },
'employee' => { 'text' => $locale->text('Employee'), },
'projectnumber' => { 'text' => $locale->text('Project Number'), },
'oe_id' => { 'text' => $locale->text('Document'), },
);
my $href = build_std_url('action=generate_journal', grep { $form->{$_} } @hidden_variables);
- map { $column_defs{$_}->{link} = $href . "&sort=${_}&order=" . Q($_ eq $form->{sort} ? 1 - $form->{order} : $form->{order}) } @columns;
+ my $page = $::form->{page} || 1;
+ map { $column_defs{$_}->{link} = $href ."&page=".$page. "&sort=${_}&order=" . Q($_ eq $form->{sort} ? 1 - $form->{order} : $form->{order}) } @columns;
my %column_alignment = map { $_ => 'right' } qw(qty);
@@ -747,6 +752,16 @@ sub generate_journal {
'purchase_invoice' => { script => 'ir', title => $locale->text('Purchase Invoice') },
);
+ my $allrows = 0;
+ $allrows = 1 if $form->{report_generator_output_format} ne 'HTML' ;
+
+ # manual paginating
+ my $pages = {};
+ $pages->{per_page} = $::form->{per_page} || 15;
+ my $first_nr = ($page - 1) * $pages->{per_page};
+ my $last_nr = $first_nr + $pages->{per_page};
+ my $idx = 0;
+
foreach my $entry (@contents) {
$entry->{qty} = $form->format_amount_units('amount' => $entry->{qty},
'part_unit' => $entry->{partunit},
@@ -774,9 +789,20 @@ sub generate_journal {
}
}
- $report->add_data($row);
+ if ( $allrows || ($idx >= $first_nr && $idx < $last_nr )) {
+ $report->add_data($row);
+ }
+ $idx++;
}
+ if ( ! $allrows ) {
+ $pages->{max} = SL::DB::Helper::Paginated::ceil($idx, $pages->{per_page}) || 1;
+ $pages->{page} = $page < 1 ? 1: $page > $pages->{max} ? $pages->{max}: $page;
+ $pages->{common} = [ grep { $_->{visible} } @{ SL::DB::Helper::Paginated::make_common_pages($pages->{page}, $pages->{max}) } ];
+
+ $report->set_options('raw_bottom_info_text' => $form->parse_html_template('common/paginate',
+ { 'pages' => $pages , 'base_url' => $href}) );
+ }
$report->generate_with_headers();
$main::lxdebug->leave_sub();
@@ -822,13 +848,14 @@ sub generate_report {
$form->{title} = $locale->text("Report about warehouse contents");
$form->{sort} ||= 'partnumber';
+ $form->{sort} ||= 'partunit';
my $sort_col = $form->{sort};
my %filter;
my @columns = qw(warehousedescription bindescription partnumber partdescription chargenumber bestbefore qty stock_value);
# filter stuff
- map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id partnumber description chargenumber bestbefore date include_invalid_warehouses);
+ map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id partstypes_id partnumber description chargenumber bestbefore date include_invalid_warehouses);
# show filter stuff also in report
my @options;
@@ -862,10 +889,11 @@ sub generate_report {
$form->{subtotal} = '' if (!first { $_ eq $sort_col } qw(partnumber partdescription));
+ $form->{report_generator_output_format} = 'HTML' if !$form->{report_generator_output_format};
my $report = SL::ReportGenerator->new(\%myconfig, $form);
my @hidden_variables = map { "l_${_}" } @columns;
- push @hidden_variables, qw(warehouse_id bin_id partnumber description chargenumber bestbefore qty_op qty qty_unit l_warehousedescription l_bindescription);
+ push @hidden_variables, qw(warehouse_id bin_id partnumber partstypes_id description chargenumber bestbefore qty_op qty qty_unit partunit l_warehousedescription l_bindescription);
push @hidden_variables, qw(include_empty_bins subtotal include_invalid_warehouses date);
my %column_defs = (
@@ -876,11 +904,13 @@ sub generate_report {
'chargenumber' => { 'text' => $locale->text('Charge Number'), },
'bestbefore' => { 'text' => $locale->text('Best Before'), },
'qty' => { 'text' => $locale->text('Qty'), },
+ 'partunit' => { 'text' => $locale->text('Unit'), },
'stock_value' => { 'text' => $locale->text('Stock value'), },
);
my $href = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
- map { $column_defs{$_}->{link} = $href . "&sort=${_}&order=" . Q($_ eq $sort_col ? 1 - $form->{order} : $form->{order}) } @columns;
+ my $page = $::form->{page} || 1;
+ map { $column_defs{$_}->{link} = $href . "&page=".$page."&sort=${_}&order=" . Q($_ eq $sort_col ? 1 - $form->{order} : $form->{order}) } @columns;
my %column_alignment = map { $_ => 'right' } qw(qty stock_value);
@@ -910,13 +940,22 @@ sub generate_report {
my $total_stock_value = 0;
+ my $allrows = 0;
+ $allrows = 1 if $form->{report_generator_output_format} ne 'HTML' ;
+
+ # manual paginating
+ my $pages = {};
+ $pages->{per_page} = $::form->{per_page} || 20;
+ my $first_nr = ($page - 1) * $pages->{per_page};
+ my $last_nr = $first_nr + $pages->{per_page};
+
foreach my $entry (@contents) {
map { $subtotals{$_} += $entry->{$_} } @subtotals_columns;
$total_stock_value += $entry->{stock_value} * 1;
-
- $entry->{qty} = $form->format_amount_units('amount' => $entry->{qty},
- 'part_unit' => $entry->{partunit},
- 'conv_units' => 'convertible');
+ $entry->{qty} = $form->format_amount(\%myconfig, $entry->{qty});
+# $entry->{qty} = $form->format_amount_units('amount' => $entry->{qty},
+# 'part_unit' => $entry->{partunit},
+# 'conv_units' => 'convertible');
$entry->{stock_value} = $form->format_amount(\%myconfig, $entry->{stock_value} * 1, 2);
my $row_set = [ { map { $_ => { 'data' => $entry->{$_}, 'align' => $column_alignment{$_} } } @columns } ];
@@ -926,9 +965,10 @@ sub generate_report {
|| ($entry->{$sort_col} ne $contents[$idx + 1]->{$sort_col}))) {
my $row = { map { $_ => { 'data' => '', 'class' => 'listsubtotal', 'align' => $column_alignment{$_}, } } @columns };
- $row->{qty}->{data} = $form->format_amount_units('amount' => $subtotals{qty} * 1,
- 'part_unit' => $entry->{partunit},
- 'conv_units' => 'convertible');
+ $row->{qty}->{data} = $form->format_amount(\%myconfig, $subtotals{qty});
+# $row->{qty}->{data} = $form->format_amount_units('amount' => $subtotals{qty} * 1,
+# 'part_unit' => $entry->{partunit},
+# 'conv_units' => 'convertible');
$row->{stock_value}->{data} = $form->format_amount(\%myconfig, $subtotals{stock_value} * 1, 2);
%subtotals = map { $_ => 0 } @subtotals_columns;
@@ -936,8 +976,9 @@ sub generate_report {
push @{ $row_set }, $row;
}
- $report->add_data($row_set);
-
+ if ( $allrows || ($idx >= $first_nr && $idx < $last_nr )) {
+ $report->add_data($row_set);
+ }
$idx++;
}
diff --git a/doc/changelog b/doc/changelog
index fa0caef4a..57d85e45e 100644
--- a/doc/changelog
+++ b/doc/changelog
@@ -6,6 +6,9 @@
kleinere neue Features und Detailverbesserungen:
+ - Weiterer Bericht in der Rubrik Lager: Lagerentnahme
+ Gibt eine Statistik über Lagerbewegungen, pro Monat/Quartal/Jahr.
+
- Für UStVA Voranmeldung über Elster gibt es die Anbindung über Geierlein (Installation/Config siehe Commit)
- CSV-Import von Artikel hat nun für existierende Artikel folgende Optionen:
diff --git a/locale/de/all b/locale/de/all
index 4b05b81a7..be38d44cd 100755
--- a/locale/de/all
+++ b/locale/de/all
@@ -1330,6 +1330,7 @@ $self->{texts} = {
'Fristsetzung' => 'Fristsetzung',
'From' => 'Von',
'From Date' => 'Von',
+ 'From bin' => 'Ausgelagert',
'From this version on a new feature is available.' => 'Ab dieser Version ist ein neues Feature verfügbar.',
'From this version on it is necessary to name a default value.' => 'Ab dieser Version benötigt kivitendo eine Standardwährung.',
'From this version on the partnumber of services, articles and assemblies have to be unique.' => 'Ab dieser Version müssen Artikelnummern eindeutig vergeben werden.',
@@ -1494,6 +1495,7 @@ $self->{texts} = {
'Internal Phone List' => 'Interne Telefonliste',
'Internal comment' => 'Interne Bemerkungen',
'Internet' => 'Internet',
+ 'Into bin' => 'Eingelagert',
'Intra-Community supply' => 'Gelangensbestätigung',
'Introduction of clients' => 'Einführung von Mandanten',
'Inv. Duedate' => 'Rg. Fälligkeit',
@@ -1985,12 +1987,15 @@ $self->{texts} = {
'Part' => 'Ware',
'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => 'Bei Artikel "#1" ist eine Chargenummer oder ein Mindesthaltbarkeitsdatum vergeben. Deshalb kann dieser Artikel nicht automatisch ausgelagert werden.',
'Part (database ID)' => 'Artikel (Datenbank-ID)',
+ 'Part Classification' => '',
'Part Description' => 'Artikelbeschreibung',
'Part Description missing!' => 'Artikelbezeichnung fehlt!',
'Part Notes' => 'Bemerkungen',
'Part Number' => 'Artikelnummer',
'Part Number missing!' => 'Artikelnummer fehlt!',
+ 'Part Unit' => '',
'Part picker' => 'Artikelauswahl',
+ 'Part_br_Description' => 'Beschreibung',
'Partial invoices' => 'Teilrechnungen',
'Partnumber' => 'Artikelnummer',
'Partnumber must not be set to empty!' => 'Die Artikelnummer darf nicht auf leer geändert werden.',
@@ -3281,6 +3286,8 @@ $self->{texts} = {
'Updating the client fields in the database "#1" on host "#2:#3" failed.' => 'Die Aktualisierung der Mandantenfelder in der Datenbank "#1" auf Host "#2:#3" schlug fehl.',
'Uploaded at' => 'Hochgeladen um',
'Uploaded on #1, size #2 kB' => 'Am #1 hochgeladen, GröÃe #2 kB',
+ 'UsageE' => 'Lagerentnahme',
+ 'UsageWithout' => 'Entnommen (ohne Korr.)',
'Use As New' => 'Als neu verwenden',
'Use Balance Sheet' => 'Bilanz verwenden',
'Use Datevautomatik' => 'Datev-Automatik verwenden',
@@ -3355,6 +3362,7 @@ $self->{texts} = {
'View/edit all employees sales documents' => 'Bearbeiten/ansehen der Verkaufsdokumente aller Mitarbeiter',
'Von Konto: ' => 'von Konto: ',
'WHJournal' => 'Lagerbuchungen',
+ 'WHUsage' => 'Lagerentnahme',
'Warehouse' => 'Lager',
'Warehouse (database ID)' => 'Lager (Datenbank-ID)',
'Warehouse (name)' => 'Lager (Name)',
@@ -3472,7 +3480,9 @@ $self->{texts} = {
'assembly' => 'Erzeugnis',
'assembly_list' => 'erzeugnisliste',
'averaged values, in invoice mode only useful when filtered by a part' => 'gemittelte Werte, im Rechnungsmodus nur sinnvoll wenn nach Artikel gefiltert wird',
+ 'averconsumed_br' => 'Ã mtl.',
'back' => 'zurück',
+ 'back_br' => 'Zurk.',
'balance' => 'Betriebsvermögensvergleich/Bilanzierung',
'bank_collection_payment_list_#1' => 'bankeinzugszahlungsliste_#1',
'bank_transfer_payment_list_#1' => 'ueberweisungszahlungsliste_#1',
@@ -3495,9 +3505,11 @@ $self->{texts} = {
'config/kivitendo.conf: Key "authentication/ldap" is missing.' => 'config/kivitendo.conf: Der Schlüssel "authentication/ldap" fehlt.',
'config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".' => 'config/kivitendo.conf: Fehlende Parameter in "authentication/database". Benötigte Parameter sind "host", "db" und "user".',
'config/kivitendo.conf: Missing parameters in "authentication/ldap". Required parameters are "host", "attribute" and "base_dn".' => 'config/kivitendo.conf: Fehlende Parameter in "authentication/ldap". Benötigt werden "host", "attribute" und "base_dn".',
+ 'consumed' => 'Im Zeitraum',
'contact_list' => 'ansprechperson_liste',
'continue' => 'weiter',
'correction' => 'Korrektur',
+ 'correction_br' => 'Korr.',
'cp_greeting to cp_gender migration' => 'Datenumwandlung von Titel nach Geschlecht (cp_greeting to cp_gender)',
'customer' => 'Kunde',
'customer_list' => 'kundenliste',
@@ -3511,6 +3523,7 @@ $self->{texts} = {
'difference_as_skonto' => 'Differenz als Skonto',
'direct debit' => 'Lastschrifteinzug',
'disposed' => 'Entsorgung',
+ 'disposed_br' => 'Entsgt.',
'do not include' => 'Nicht aufnehmen',
'done' => 'erledigt',
'dunning_list' => 'mahnungsliste',
@@ -3533,6 +3546,7 @@ $self->{texts} = {
'for all' => 'für alle',
'for date' => 'zum Stichtag',
'found' => 'Gefunden',
+ 'found_br' => 'Gef.',
'from (time)' => 'von',
'general_ledger_list' => 'buchungsjournal',
'generate cb/ob transactions for selected charts' => 'Buchungen erstellen',
@@ -3572,6 +3586,7 @@ $self->{texts} = {
'male' => 'männlich',
'mark as paid' => 'als bezahlt markieren',
'missing' => 'Fehlbestand',
+ 'missing_br' => 'Fehl.',
'month' => 'Monatliche Abgabe',
'monthly' => 'monatlich',
'never' => 'niemals',
@@ -3650,11 +3665,14 @@ $self->{texts} = {
'service' => 'Dienstleistung',
'service_list' => 'dienstleistungsliste',
'shipped' => 'verschickt',
+ 'shipped_br' => 'Verschk.',
'singular first char' => 'S',
'sort items' => 'Positionen sortieren',
'stock' => 'Einlagerung',
+ 'stock_br' => 'Eingel.',
'submit' => 'abschicken',
'succeeded' => 'erfolgreich',
+ 'sum' => 'Summe',
'tax_chartaccno' => 'Automatikkonto',
'tax_percent' => 'Prozentsatz',
'tax_rate' => 'Prozent',
@@ -3682,6 +3700,7 @@ $self->{texts} = {
'use program settings' => 'benutze Programmeinstellungen',
'use user config' => 'Verwende Benutzereinstellung',
'used' => 'Verbraucht',
+ 'used_br' => 'Verbr.',
'valid from' => 'Gültig ab',
'vendor' => 'Lieferant',
'vendor_invoice_list' => 'kreditorenbuchungsliste',
@@ -3689,6 +3708,7 @@ $self->{texts} = {
'waiting for job to be started' => 'warte darauf, dass der Job gestartet wird',
'warehouse_journal_list' => 'lagerbuchungsliste',
'warehouse_report_list' => 'lagerbestandsliste',
+ 'warehouse_usage_list' => 'Lagerentnahmeliste',
'with amount' => 'mit Betrag',
'with skonto acc. to pt' => 'mit Skonto nach ZB',
'with_skonto_pt' => 'mit Skonto nach ZB',
diff --git a/menus/user/12-warehouse-usage.yaml b/menus/user/12-warehouse-usage.yaml
new file mode 100644
index 000000000..06b3c4c70
--- /dev/null
+++ b/menus/user/12-warehouse-usage.yaml
@@ -0,0 +1,15 @@
+#
+# opendynamic feature
+#
+---
+#
+# Warenverbrauchsbericht
+#
+- parent: warehouse_reports
+ id: warehouse_reports_whusage
+ name: WHUsage
+ icon: warehouse_usage
+ order: 300
+ access: warehouse_contents | warehouse_management
+ params:
+ action: Inventory/stock_usage
diff --git a/templates/webpages/inventory/report_bottom.html b/templates/webpages/inventory/report_bottom.html
new file mode 100644
index 000000000..c85ec7381
--- /dev/null
+++ b/templates/webpages/inventory/report_bottom.html
@@ -0,0 +1 @@
+[%- PROCESS 'common/paginate.html' pages=SELF.pages, base_url = SELF.base_url %]
diff --git a/templates/webpages/inventory/warehouse_usage.html b/templates/webpages/inventory/warehouse_usage.html
new file mode 100644
index 000000000..9151556e7
--- /dev/null
+++ b/templates/webpages/inventory/warehouse_usage.html
@@ -0,0 +1,126 @@
+[%- USE T8 %]
+[%- USE L %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- WAREHOUSE_FILTER = 1 %]
+[%- PROCESS 'common/select_warehouse_bin.html' %]
+
+[% title | html %]
+
+[%- INCLUDE 'common/flash.html' %]
+
+
+
+
+
+
+
+
diff --git a/templates/webpages/report_generator/pdf_export_options.html b/templates/webpages/report_generator/pdf_export_options.html
index 59a98b7b8..444c0b600 100644
--- a/templates/webpages/report_generator/pdf_export_options.html
+++ b/templates/webpages/report_generator/pdf_export_options.html
@@ -1,7 +1,8 @@
[%- USE T8 %]
[%- USE HTML %][%- USE LxERP %]
- [%- SET default_margin = LxERP.format_amount(1.5) %]
+ [%- SET default_ymargin = LxERP.format_amount(1.5) %]
+ [%- SET default_xmargin = LxERP.format_amount(0.8) %]
[% HTML.escape(title) %]
@@ -73,22 +74,22 @@
[% 'Top' | $T8 %] |
- cm |
+ cm |
[% 'Left' | $T8 %] |
- cm |
+ cm |
[% 'Bottom' | $T8 %] |
- cm |
+ cm |
[% 'Right' | $T8 %] |
- cm |
+ cm |
--
2.20.1