Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 14a2c687

Von Kivitendo Admin vor fast 11 Jahren hinzugefügt

  • ID 14a2c687b4458d903ff212103ca24cf32c239dd1
  • Vorgänger ea8e8a62
  • Nachfolger 91aa1593

Verkaufsbericht verbessert: #2078 MwSt. inkl und Wechselkurs

Der Verkaufsbericht hat bisher bei Rechnungen, die mit MwSt. inklusive
gebucht worden sind, die Preise für die "Zeilensumme" falsch berechnet,
weil fxsellprice für den Fall "Steuer inklusive" im Gegensatz zu
sellprice die Steuer enthält, und diese nicht rausgerechnet wurde.

Stattdessen wird jetzt für Steuer inklusive und exklusive sellprice als
Grundlage genommen. Für den Fall Steuer inklusive kann es hier aber vor
Allem bei einer großen Artikelmenge (qty) noch zu Abweichungen in der
Zeilensumme kommen.

Außerdem wird jetzt der Wechselkurs mit berücksichtigt, was die Abfrage
allerdings langsamer macht. Meistens verkauft man zwar in der
Hauptwährung und Fremdwährung kommen eher im Einkauf vor, es kann aber
durchaus vorkommen.

Der Verkaufsbericht berücksichtigt jetzt auch den Preisfaktor bei
EK-Preis.

Schließlich wurden noch Kommentare übersetzt.

Unterschiede anzeigen:

SL/VK.pm
55 55
  # so we extract both versions in our query and later overwrite the description in article mode
56 56

  
57 57
  my $query =
58
    qq|SELECT ct.id as customerid, ct.name as customername,ct.customernumber,ct.country,ar.invnumber,ar.id,ar.transdate,p.partnumber,p.description as description, pg.partsgroup,i.parts_id,i.qty,i.price_factor,i.discount,i.description as invoice_description,i.lastcost,i.sellprice,i.fxsellprice,i.marge_total,i.marge_percent,i.unit,b.description as business,e.name as employee,e2.name as salesman, to_char(ar.transdate,'Month') as month, to_char(ar.transdate, 'YYYYMM') as nummonth, p.unit as parts_unit, p.weight | .
58
    qq|SELECT ct.id as customerid, ct.name as customername,ct.customernumber,ct.country,ar.invnumber,ar.id,ar.transdate,p.partnumber,p.description as description, pg.partsgroup,i.parts_id,i.qty,i.price_factor,i.discount,i.description as invoice_description,i.lastcost,i.sellprice,i.fxsellprice,i.marge_total,i.marge_percent,i.unit,b.description as business,e.name as employee,e2.name as salesman, to_char(ar.transdate,'Month') as month, to_char(ar.transdate, 'YYYYMM') as nummonth, p.unit as parts_unit, p.weight, ar.taxincluded | .
59
    qq|, COALESCE((SELECT e.buy FROM exchangerate e WHERE e.transdate = ar.transdate and ar.currency_id = e.currency_id),1) as exchangerate | .
59 60
    qq|FROM invoice i | .
60 61
    qq|JOIN ar on (i.trans_id = ar.id) | .
61 62
    qq|JOIN parts p on (i.parts_id = p.id) | .
bin/mozilla/vk.pl
110 110
  };
111 111

  
112 112
  if ( $form->{customer} =~ /--/ ) {
113
    # Felddaten kommen aus Dropdownbox
113
    # field data comes from dropdown box
114 114
    ($form->{customername}, $form->{customer_id}) = split(/--/, $form->{customer});
115 115
  } elsif ($form->{customer}) {
116
    # es wurde ein Wert im Freitextfeld übergeben, auf Eindeutigkeit überprüfen
117 116

  
118
    # check_name wird mit no_select => 1 ausgeführt, ist die Abfrage nicht eindeutig kommt ein Fehler
119
    # und die Abfrage muß erneut ausgeführt werden
117
    # a value was added in the input box, we only want to filter for one
118
    # customer, so check that a unique customer can be found
120 119

  
121
    # Ohne no_select kommt bei Auswahl des Kunden ein Aufruf von update der ins
122
    # Nichts führt, daher diese Zwischenlösung
120
    # check_name is executed with no_select => 1, if the result isn't unique
121
    # quit with an error message, the user has to enter a new name
122

  
123
    # Without no_select selecting a customer causes an update which doesn't
124
    # return anything, which is the reason for this workaround
123 125

  
124 126
    &check_name('customer', no_select => 1);
125 127

  
......
163 165
  push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
164 166

  
165 167

  
166
  # hidden variables für pdf/csv export übergeben
167
  # einmal mit l_ um zu bestimmen welche Spalten ausgegeben werden sollen
168
  # einmal optionen für die Überschrift (z.B. transdatefrom, partnumber, ...)
168
  # pass hidden variables for pdf/csv export
169
  # first with l_ to determine which columns to show
170
  # then with the options for headings (such as transdatefrom, partnumber, ...)
169 171
  my @hidden_variables  = (qw(l_headers_mainsort l_headers_subsort l_subtotal_mainsort l_subtotal_subsort l_total l_parts l_customername l_customernumber transdatefrom transdateto decimalplaces customer customername customer_id department partnumber partsgroup country business description project_id customernumber salesman employee salesman_id employee_id business_id partsgroup_id mainsort subsort),
170 172
      "$form->{db}number",
171 173
      map({ "cvar_$_->{name}" } @searchable_custom_variables),
172 174
      map { "l_$_" } @columns
173 175
      );
174 176
  my @hidden_nondefault = grep({ $form->{$_} } @hidden_variables);
175
  # Variablen werden dann als Hidden Variable mitgegeben, z.B.
177
  # variables are passed as hiddens, e.g.
176 178
  # <input type="hidden" name="report_generator_hidden_transdateto" value="21.05.2010">
177 179

  
178 180
  $href = build_std_url('action=invoice_transactions', grep { $form->{$_} } @hidden_variables);
......
222 224
  push @options, $locale->text('Description')             . " : $form->{description}"                                                       if $form->{description};
223 225
  push @options, $locale->text('Customer')                . " : $form->{customername}"                                                      if $form->{customer};
224 226
  push @options, $locale->text('Customer Number')         . " : $form->{customernumber}"                                                    if $form->{customernumber};
225
  # TODO: es wird nur id übergeben
227
  # TODO: only customer id is passed
226 228
  push @options, $locale->text('Department')              . " : " . (split /--/, $form->{department})[0]                                    if $form->{department};
227 229
  push @options, $locale->text('Invoice Number')          . " : $form->{invnumber}"                                                         if $form->{invnumber};
228 230
  push @options, $locale->text('Invoice Date')            . " : $form->{invdate}"                                                           if $form->{invdate};
......
304 306
    $basefactor = 1 unless $basefactor;
305 307

  
306 308
    $ar->{price_factor} = 1 unless $ar->{price_factor};
307
    # calculate individual sellprice
308
    # discount was already accounted for in db sellprice
309
    $ar->{sellprice}       = $ar->{sellprice}  / $ar->{price_factor} / $basefactor;
309

  
310
    # calculate individual sellprice, discount is already accounted for in column sellprice in db
311

  
312
    # The sellprice total can be calculated from sellprice or fxsellprice (the
313
    # value that was actually entered in the sellprice field and is always
314
    # stored seperately).  However, for fxsellprice this method only works when
315
    # the tax is not included, because otherwise fxsellprice includes the tax
316
    # and there is no simple way to extract the tax rate of the article from
317
    # the big query. 
318
    #
319
    # Using fxsellprice is potentially more accurate (certainly for tax
320
    # included), because we can use the same method as is used while the
321
    # invoice is generated.
322
    #
323
    # sellprice however has already been converted to the net value (but
324
    # rounded in the process, which leads to rounding errors when calculating
325
    # the linetotal from the rounded sellprice in the report.  These rounding
326
    # errors can quickly amount to several cents when qty is large)
327
    #
328
    # For calculating sellprice_total from fxsellprice, you would use:
329
    # sellprice_total_including_tax = qty * fxsellprice * (1-discount) /  price_factor * exchangerate
330
    # $ar->{sellprice_total_including_tax} =  $form->round_amount( $ar->{qty} * ( $ar->{fxsellprice} * ( 1 - $ar->{discount} ) ) / $ar->{price_factor}, $form->{"decimalplaces"});
331

  
332
    $ar->{sellprice}       = $ar->{sellprice}  / $ar->{price_factor} / $basefactor; 
333
    $ar->{sellprice_total} = $form->round_amount( $ar->{qty} * $ar->{sellprice} / $ar->{price_factor} , $form->{"decimalplaces"});
334

  
310 335
    $ar->{lastcost}        = $ar->{lastcost}   / $ar->{price_factor} / $basefactor;
311
    $ar->{sellprice_total} = $form->round_amount( $ar->{qty} * ( $ar->{fxsellprice} * ( 1 - $ar->{discount} ) ) / $ar->{price_factor}, $form->{"decimalplaces"});
312
    $ar->{lastcost_total}  = $form->round_amount( $ar->{qty} * $ar->{lastcost} * $basefactor, $form->{"decimalplaces"});
313
    # marge_percent wird neu berechnet, da Wert in invoice leer ist (Bug)
336
    $ar->{lastcost_total}  = $form->round_amount( $ar->{qty} * $ar->{lastcost} / $ar->{price_factor}, $form->{"decimalplaces"});
337

  
338
    # marge_percent is recalculated, because the value in invoice used to be empty
314 339
    $ar->{marge_percent} = $ar->{sellprice_total} ? (($ar->{sellprice_total}-$ar->{lastcost_total}) / $ar->{sellprice_total} * 100) : 0;
315
    # marge_total neu berechnen
340
    # also recalculate marge_total
316 341
    $ar->{marge_total} = $ar->{sellprice_total} ? $ar->{sellprice_total}-$ar->{lastcost_total}  : 0;
317
    $ar->{discount} *= 100;  # für Ausgabe formatieren, 10% stored as 0.1 in db
342
    $ar->{discount} *= 100;  # format discount value for output, 10% is stored as 0.1 in db
318 343

  
319
    #adapt qty to the chosen unit
344
    # adapt qty to the chosen unit
320 345
    $ar->{qty} *= $basefactor;
321 346

  
322
    #weight is the still the weight per part, but here we want the total weight
347
    # weight is the still the weight per part, but here we want the total weight
323 348
    $ar->{weight} *= $ar->{qty};
324 349

  
325
    # Anfangshauptüberschrift
326
    if ( $form->{l_headers_mainsort} eq "Y" && ( $idx == 0 or $ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx - 1]->{ $form->{'mainsort'} } )) {
350
    # Main header
351
    if ( $form->{l_headers_mainsort} eq "Y" && ( $idx == 1 or $ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx - 1]->{ $form->{'mainsort'} } )) {
327 352
      my $headerrow = {
328 353
        # use $emptyname for mainsort header if mainsort is empty
329 354
        data    => $ar->{$form->{'mainsort'}} || $locale->text('empty'),
......
339 364
#      $report->add_data($emptyheaderrow_set) if $form->{l_headers} eq "Y";
340 365
    };
341 366

  
342
    # subsort überschriften
367
    # subsort headers
343 368
    # special case: subsort headers only makes (aesthetical) sense if we show individual parts
344 369
    if ((   $idx == 0
345 370
         or $ar->{ $form->{'subsort'} }  ne $form->{AR}->[$idx - 1]->{ $form->{'subsort'} }
......
385 410
      $subtotals2{lastcost}  = 0;
386 411
    };
387 412

  
388
    # Ertrag prozentual in den Summen: (summe VK - summe Ertrag) / summe VK
413
    # marge percent as sums: (sum VK - sum Ertrag) / sum VK
389 414
    $subtotals1{marge_percent} = $subtotals1{sellprice_total} ? (($subtotals1{sellprice_total} - $subtotals1{lastcost_total}) / $subtotals1{sellprice_total}) * 100 : 0;
390 415
    $subtotals2{marge_percent} = $subtotals2{sellprice_total} ? (($subtotals2{sellprice_total} - $subtotals2{lastcost_total}) / $subtotals2{sellprice_total}) *100 : 0;
391 416

  
392
    # Ertrag prozentual:  (Summe VK betrag - Summe EK betrag) / Summe VK betrag
393
    # wird laufend bei jeder Position neu berechnet
417
    # total marge percent:  (sum VK betrag - sum EK betrag) / sum VK betrag
418
    # is recalculated after each position
394 419
    $totals{marge_percent}    = $totals{sellprice_total}    ? ( ($totals{sellprice_total} - $totals{lastcost_total}) / $totals{sellprice_total}   ) * 100 : 0;
395 420

  
396 421
    map { $ar->{$_} = $form->format_amount(\%myconfig, $ar->{$_}, 2) } qw(marge_percent qty);
397 422
    map { $ar->{$_} = $form->format_amount(\%myconfig, $ar->{$_}, 3) } qw(weight);
398 423
    map { $ar->{$_} = $form->format_amount(\%myconfig, $ar->{$_}, $form->{"decimalplaces"} )} qw(lastcost sellprice sellprice_total lastcost_total marge_total);
399 424

  
400
    # Einzelzeilen nur zeigen wenn l_parts gesetzt ist, nützlich, wenn man nur
401
    # Subtotals und Totals sehen möchte
425
    # only show individual lines when l_parts is set, this is useful, if you
426
    # only want to see subtotals and totals
427

  
402 428
    if ($form->{l_parts}) {
403 429
      my %row = (
404 430
        map { ($_ => { data => $ar->{$_}, align => $column_alignment{$_} }) } @columns
......
412 438
      $report->add_data(\%row);
413 439
    }
414 440

  
415
    # hier wird bei l_subtotal nicht differenziert zwischen mainsort und subsort
416
    # macht man l_subtotal_mainsort aus wird l_subtotal_subsort auch nicht ausgeführt
441
    # choosing l_subtotal doesn't make a distinction between mainsort and subsort
442
    # if l_subtotal_mainsort is not selected l_subtotal_subsort isn't run either
417 443
    if (   ($form->{l_subtotal_mainsort} eq 'Y')
418 444
        && ($form->{l_subtotal_subsort}  eq 'Y')
419 445
        && (($idx == (scalar @{ $form->{AR} } - 1))   # last element always has a subtotal
......
432 458
        && (($idx == (scalar @{ $form->{AR} } - 1))   # last element always has a subtotal
433 459
            || ($ar->{ $form->{'mainsort'} } ne $form->{AR}->[$idx + 1]->{ $form->{'mainsort'} })
434 460
            )) {   # if value that is sorted by changes, print subtotal
435
        # subtotal is overriden if mainsort and subsort are equal, don't print
461
        # subtotal is overridden if mainsort and subsort are equal, don't print
436 462
        # subtotal line even if it is selected
437 463
      $report->add_data(create_subtotal_row_invoice(\%subtotals1, \@columns, \%column_alignment, \@subtotal_columns, 'listsubtotal', $ar->{$form->{mainsort}}));
438 464
      $report->add_data({ %empty_row }) if $addemptylines; # insert empty row after mainsort

Auch abrufbar als: Unified diff