b5c0931af78cc85469b32ce9f87b6fd5b1172fb1
[kivitendo-erp.git] / bin / mozilla / vk.pl
1 #=====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger Accounting
9 # Copyright (c) 2001
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
20 #
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #======================================================================
29 #
30 # Sales report
31 #
32 #======================================================================
33
34 use POSIX qw(strftime);
35 use List::Util qw(sum first);
36
37 use SL::VK;
38 use SL::IS;
39 use SL::ReportGenerator;
40 use Data::Dumper;
41
42 require "bin/mozilla/arap.pl";
43 require "bin/mozilla/common.pl";
44 require "bin/mozilla/drafts.pl";
45 require "bin/mozilla/reportgenerator.pl";
46
47 use strict;
48
49
50 sub search_invoice {
51   $main::lxdebug->enter_sub();
52   $main::auth->assert('general_ledger | invoice_edit');
53
54   my $form     = $main::form;
55   my %myconfig = %main::myconfig;
56   my $locale   = $main::locale;
57
58   my ($customer, $department);
59
60   # setup customer selection
61   $form->all_vc(\%myconfig, "customer", "AR");
62
63   $form->{title}    = $locale->text('Sales Report');
64   $form->{jsscript} = 1;
65
66   $form->get_lists("projects"     => { "key" => "ALL_PROJECTS", "all" => 1 },
67                    "departments"  => "ALL_DEPARTMENTS",
68                    "customers"    => "ALL_VC");
69
70   $form->{vc_keys}   = sub { "$_[0]->{name}--$_[0]->{id}" };
71
72   $form->header;
73   print $form->parse_html_template('vk/search_invoice', { %myconfig });
74
75   $main::lxdebug->leave_sub();
76 }
77
78 sub invoice_transactions {
79   $main::lxdebug->enter_sub();
80
81   $main::auth->assert('general_ledger | invoice_edit');
82
83   my $form     = $main::form;
84   my %myconfig = %main::myconfig;
85   my $locale   = $main::locale;
86
87   my ($callback, $href, @columns);
88
89   if ( $form->{customer} =~ /--/ ) {
90     # Felddaten kommen aus Dropdownbox
91     ($form->{customername}, $form->{customer_id}) = split(/--/, $form->{customer});
92   } elsif ($form->{customer}) {
93     # es wurde ein Wert im Freitextfeld übergeben, auf Eindeutigkeit überprüfen
94
95     # check_name wird mit no_select => 1 ausgeführt, ist die Abfrage nicht eindeutig kommt ein Fehler
96     # und die Abfrage muß erneut ausgeführt werden
97
98     # Ohne no_select kommt bei Auswahl des Kunden ein Aufruf von update der ins
99     # Nichts führt, daher diese Zwischenlösung
100
101     &check_name('customer', no_select => 1);
102
103     # $form->{customer_id} wurde schon von check_name gesetzt
104     $form->{customername} = $form->{customer};
105   };
106   # ist $form->{customer} leer passiert hier nichts weiter
107
108   # decimalplaces überprüfen oder auf Default 2 setzen
109   $form->{decimalplaces} = 2 unless $form->{decimalplaces} > 0 && $form->{decimalplaces} < 6;
110
111 #  report_generator_set_default_sort('transdate', 1);
112
113   VK->invoice_transactions(\%myconfig, \%$form);
114
115   # anhand von radio button die Sortierreihenfolge festlegen
116   if ($form->{sortby} eq 'artikelsort') {
117     $form->{'mainsort'} = 'parts_id';
118     $form->{'subsort'}  = 'name';
119   } else {
120     $form->{'mainsort'} = 'name';
121     $form->{'subsort'}  = 'parts_id';
122   };
123
124   $form->{title} = $locale->text('Sales Report');
125
126   @columns =
127     qw(description invnumber transdate customernumber partnumber transdate qty unit sellprice sellprice_total discount lastcost lastcost_total marge_total marge_percent);
128
129   # hidden variables für pdf/csv export übergeben
130   # einmal mit l_ um zu bestimmen welche Spalten ausgegeben werden sollen
131   # einmal optionen für die Überschrift (z.B. transdatefrom, partnumber, ...)
132   my @hidden_variables  = (qw(l_headers l_subtotal l_total l_customernumber transdatefrom transdateto decimalplaces customer customername customer_id department partnumber description project_id customernumber), "$form->{db}number", map { "l_$_" } @columns);
133   my @hidden_nondefault = grep({ $form->{$_} } @hidden_variables);
134   # Variablen werden dann als Hidden Variable mitgegeben, z.B.
135   # <input type="hidden" name="report_generator_hidden_transdateto" value="21.05.2010">
136
137   $href = build_std_url('action=invoice_transactions', grep { $form->{$_} } @hidden_variables);
138   # href = vk.pl?action=invoice_transactions&l_headers=Y&l_subtotal=Y&l_total=Y&transdatefrom=04.03.2010 ...
139
140   my %column_defs = (
141     'description'             => { 'text' => $locale->text('Description'), },
142     'partnumber'              => { 'text' => $locale->text('Part Number'), },
143     'invnumber'               => { 'text' => $locale->text('Invoice Number'), },
144     'transdate'               => { 'text' => $locale->text('Invoice Date'), },
145     'qty'                     => { 'text' => $locale->text('Quantity'), },
146     'unit'                    => { 'text' => $locale->text('Unit'), },
147     'sellprice'               => { 'text' => $locale->text('Sales price'), },
148     'sellprice_total'         => { 'text' => $locale->text('Sales net amount'), },
149     'lastcost_total'          => { 'text' => $locale->text('Purchase net amount'), },
150     'discount'                => { 'text' => $locale->text('Discount'), },
151     'lastcost'                => { 'text' => $locale->text('Purchase price'), },
152     'marge_total'             => { 'text' => $locale->text('Sales margin'), },
153     'marge_percent'           => { 'text' => $locale->text('Sales margin %'), },
154     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
155   );
156
157   my %column_alignment = map { $_ => 'right' } qw(lastcost sellprice sellprice_total lastcost_total unit discount marge_total marge_percent qty);
158
159   $form->{"l_type"} = "Y";
160   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
161
162
163   my @options;
164   if ($form->{description}) {
165     push @options, $locale->text('Description') . " : $form->{description}";
166   }
167   if ($form->{customer}) {
168     push @options, $locale->text('Customer') . " : $form->{customername}";
169   }
170   if ($form->{customernumber}) {
171     push @options, $locale->text('Customer Number') . " : $form->{customernumber}";
172   }
173   if ($form->{department}) {
174     my ($department) = split /--/, $form->{department};
175     push @options, $locale->text('Department') . " : $department";
176   }
177   if ($form->{invnumber}) {
178     push @options, $locale->text('Invoice Number') . " : $form->{invnumber}";
179   }
180   if ($form->{invdate}) {
181     push @options, $locale->text('Invoice Date') . " : $form->{invdate}";
182   }
183   if ($form->{partnumber}) {
184     push @options, $locale->text('Part Number') . " : $form->{partnumber}";
185   }
186   if ($form->{ordnumber}) {
187     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
188   }
189   if ($form->{notes}) {
190     push @options, $locale->text('Notes') . " : $form->{notes}";
191   }
192   if ($form->{transaction_description}) {
193     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
194   }
195   if ($form->{transdatefrom}) {
196     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1);
197   }
198   if ($form->{transdateto}) {
199     push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1);
200   }
201
202   my $report = SL::ReportGenerator->new(\%myconfig, $form);
203
204   $report->set_options('top_info_text'        => join("\n", @options),
205                        'output_format'        => 'HTML',
206                        'title'                => $form->{title},
207                        'attachment_basename'  => $locale->text('Sales Report') . strftime('_%Y%m%d', localtime time),
208     );
209   $report->set_options_from_form();
210   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
211
212   $report->set_columns(%column_defs);
213   $report->set_column_order(@columns);
214
215   $report->set_export_options('invoice_transactions', @hidden_variables, qw(mainsort sortdir));
216
217   $report->set_sort_indicator($form->{mainsort}, $form->{sortdir});
218
219   # add sort and escape callback, this one we use for the add sub
220   $form->{callback} = $href .= "&sort=$form->{mainsort}";
221
222   # escape callback for href
223   $callback = $form->escape($href);
224
225   my @subtotal_columns = qw(qty sellprice sellprice_total lastcost lastcost_total marge_total marge_percent discount);
226   # Gesamtsumme:
227   # Summe von sellprice_total, lastcost_total und marge_total
228   # Durchschnitt von marge_percent
229   my @total_columns = qw(sellprice_total lastcost_total marge_total marge_percent );
230
231   my %totals    = map { $_ => 0 } @total_columns;
232   my %subtotals1 = map { $_ => 0 } @subtotal_columns;
233   my %subtotals2 = map { $_ => 0 } @subtotal_columns;
234
235   my $idx = 0;
236
237   foreach my $ar (@{ $form->{AR} }) {
238
239     $ar->{price_factor} = 1 unless $ar->{price_factor};
240     # calculate individual sellprice
241     # discount was already accounted for in db sellprice
242     $ar->{sellprice} = $ar->{sellprice} / $ar->{price_factor};
243     $ar->{lastcost} = $ar->{lastcost} / $ar->{price_factor};
244     $ar->{sellprice_total} = $ar->{qty} * ( $ar->{fxsellprice} * ( 1 - $ar->{discount} ) ) ;
245     $ar->{lastcost_total}  = $ar->{qty} * $ar->{lastcost};
246     # marge_percent wird neu berechnet, da Wert in invoice leer ist (Bug)
247     $ar->{marge_percent} = $ar->{sellprice_total} ? (($ar->{sellprice_total}-$ar->{lastcost_total}) / $ar->{sellprice_total}) : 0;
248     # marge_total neu berechnen
249     $ar->{marge_total} = $ar->{sellprice_total} ? $ar->{sellprice_total}-$ar->{lastcost_total}  : 0;
250     $ar->{discount} *= 100;  # für Ausgabe formatieren, 10% stored as 0.1 in db
251
252     # Anfangshauptüberschrift
253     if ( $form->{l_headers} eq "Y" && ( $idx == 0 or $ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx - 1]->{ $form->{'mainsort'} } )) {
254       my $name;
255       my $headerrow;
256       if ( $form->{mainsort} eq 'parts_id' ) {
257         $headerrow->{description}->{data} = "$ar->{description}";
258       } else {
259         $headerrow->{description}->{data} = "$ar->{name}";
260       };
261       $headerrow->{description}->{class} = "listmainsortheader";
262       my $headerrow_set = [ $headerrow ];
263       $report->add_data($headerrow_set);
264
265       # add empty row after main header
266 #      my $emptyheaderrow->{description}->{data} = "";
267 #      $emptyheaderrow->{description}->{class} = "listmainsortheader";
268 #      my $emptyheaderrow_set = [ $emptyheaderrow ];
269 #      $report->add_data($emptyheaderrow_set) if $form->{l_headers} eq "Y";
270     };
271
272     # subsort überschriften
273     if ( $idx == 0
274       or $ar->{ $form->{'subsort'} }  ne $form->{AR}->[$idx - 1]->{ $form->{'subsort'} }
275       or $ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx - 1]->{ $form->{'mainsort'} }
276     ) {
277       my $headerrow;
278       my $name;
279       if ( $form->{subsort} eq 'parts_id' ) {
280         $name = 'description';
281         $headerrow->{description}->{data} = "$ar->{$name}";
282       } else {
283         $name = 'name';
284         $headerrow->{description}->{data} = "$ar->{$name}";
285       };
286       $headerrow->{description}->{class} = "listsubsortheader";
287       my $headerrow_set = [ $headerrow ];
288       $report->add_data($headerrow_set) if $form->{l_headers} eq "Y";
289     };
290
291     map { $subtotals1{$_} += $ar->{$_};
292           $subtotals2{$_} += $ar->{$_};
293         } @subtotal_columns;
294
295     map { $totals{$_}    += $ar->{$_} } @total_columns;
296
297     if ( $subtotals1{qty} != 0 ) {
298       # calculate averages for subtotals1 and subtotals2
299       # credited positions reduce both total and qty and thus don't influence average prices
300       $subtotals1{sellprice} = $subtotals1{sellprice_total} / $subtotals1{qty};
301       $subtotals1{lastcost} = $subtotals1{lastcost_total} / $subtotals1{qty};
302     } else {
303       # qty is zero, so we have a special case where each position in subtotal
304       # group has a corresponding credit note so that the total qty is zero in
305       # this case we also want the total amounts to be zero, so overwrite them,
306       # rather than leaving the last value in sellprice/lastcost
307
308       $subtotals1{sellprice} = 0;
309       $subtotals1{lastcost} = 0;
310     };
311
312     if ( $subtotals2{qty} != 0 ) {
313       $subtotals2{sellprice} = $subtotals2{sellprice_total} / $subtotals2{qty};
314       $subtotals2{lastcost} = $subtotals2{lastcost_total} / $subtotals2{qty};
315     } else {
316       $subtotals2{sellprice} = 0;
317       $subtotals2{lastcost} = 0;
318     };
319
320     # Ertrag prozentual in den Summen: (summe VK - summe Ertrag) / summe VK
321     $subtotals1{marge_percent} = $subtotals1{sellprice_total} ? (($subtotals1{sellprice_total} - $subtotals1{lastcost_total}) / $subtotals1{sellprice_total}) : 0;
322     $subtotals2{marge_percent} = $subtotals2{sellprice_total} ? (($subtotals2{sellprice_total} - $subtotals2{lastcost_total}) / $subtotals2{sellprice_total}) : 0;
323
324     # Ertrag prozentual:  (Summe VK betrag - Summe EK betrag) / Summe VK betrag
325     # wird laufend bei jeder Position neu berechnet
326     $totals{marge_percent}    = $totals{sellprice_total}    ? ( ($totals{sellprice_total} - $totals{lastcost_total}) / $totals{sellprice_total}   ) : 0;
327
328     map { $ar->{$_} = $form->format_amount(\%myconfig, $ar->{$_}, 2) } qw(marge_total marge_percent);
329     map { $ar->{$_} = $form->format_amount(\%myconfig, $ar->{$_}, $form->{"decimalplaces"} )} qw(lastcost sellprice sellprice_total lastcost_total);
330
331     my $row = { };
332
333     foreach my $column (@columns) {
334       $row->{$column} = {
335         'data'  => $ar->{$column},
336         'align' => $column_alignment{$column},
337       };
338     }
339
340    $row->{description}->{class} = 'listsortdescription';
341
342     $row->{invnumber}->{link} = build_std_url("script=is.pl", 'action=edit')
343       . "&id=" . E($ar->{id}) . "&callback=${callback}";
344
345     my $row_set = [ $row ];
346
347     if (($form->{l_subtotal} eq 'Y')
348         && (($idx == (scalar @{ $form->{AR} } - 1))   # last element always has a subtotal
349           || ($ar->{ $form->{'subsort'} } ne $form->{AR}->[$idx + 1]->{ $form->{'subsort'}   })
350           || ($ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx + 1]->{ $form->{'mainsort'} })
351           )) {   # if value that is sorted by changes, print subtotal
352       my $name;
353       if ( $form->{subsort} eq 'parts_id' ) {
354         $name = 'description';
355       } else {
356         $name = 'name';
357       };
358
359       if ($form->{l_subtotal} eq 'Y') {
360         push @{ $row_set }, create_subtotal_row_invoice(\%subtotals2, \@columns, \%column_alignment, \@subtotal_columns, 'listsubsortsubtotal', $ar->{$name}) ;
361         push @{ $row_set }, insert_empty_row();
362       };
363     }
364
365     # if mainsort has changed, add mainsort subtotal and empty row
366     if (($form->{l_subtotal} eq 'Y')
367         && (($idx == (scalar @{ $form->{AR} } - 1))   # last element always has a subtotal
368             || ($ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx + 1]->{ $form->{'mainsort'} })
369             )) {   # if value that is sorted by changes, print subtotal
370       my $name;
371       if ( $form->{mainsort} eq 'parts_id' ) {
372         $name = 'description';
373       } else {
374         $name = 'name';
375       };
376       if ($form->{l_subtotal} eq 'Y' ) {
377         push @{ $row_set }, create_subtotal_row_invoice(\%subtotals1, \@columns, \%column_alignment, \@subtotal_columns, 'listmainsortsubtotal', $ar->{$name});
378         push @{ $row_set }, insert_empty_row();
379       };
380     }
381
382     $report->add_data($row_set);
383
384     $idx++;
385   }
386   if ( $form->{l_total} eq "Y" ) {
387     $report->add_separator();
388     $report->add_data(create_subtotal_row_invoice(\%totals, \@columns, \%column_alignment, \@total_columns, 'listtotal'))
389   };
390
391   $report->generate_with_headers();
392   $main::lxdebug->leave_sub();
393 }
394
395
396 sub insert_empty_row {
397     my $dummyrow;
398     $dummyrow->{description}->{data} = "";
399     my $dummyrowset = [ $dummyrow ];
400     return $dummyrow;
401 };
402
403
404
405 sub create_subtotal_row_invoice {
406   $main::lxdebug->enter_sub();
407
408   my ($totals, $columns, $column_alignment, $subtotal_columns, $class, $name) = @_;
409
410   my $form     = $main::form;
411   my %myconfig = %main::myconfig;
412
413   my $row = { map { $_ => { 'data' => '', 'class' => $class, 'align' => $column_alignment->{$_}, } } @{ $columns } };
414
415   $row->{description}->{data} = "Summe " . $name;
416
417   map { $row->{$_}->{data} = $form->format_amount(\%myconfig, $totals->{$_}, 2) } qw(marge_total marge_percent);
418   map { $row->{$_}->{data} = $form->format_amount(\%myconfig, $totals->{$_}, 0) } qw(qty);
419   map { $row->{$_}->{data} = $form->format_amount(\%myconfig, $totals->{$_}, $form->{decimalplaces}) } qw(lastcost sellprice sellprice_total lastcost_total);
420
421
422   map { $totals->{$_} = 0 } @{ $subtotal_columns };
423
424   $main::lxdebug->leave_sub();
425
426   return $row;
427 }
428
429 1;
430