X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FIS.pm;h=fdc70ab3a5faa5fd2e0c58335b0a4e4d263e50f2;hb=HEAD;hp=10892dec1091047f5c2d1c71a0a6ee73d5df9c52;hpb=12727b136690f21b83182f05db72d7aeaa93ea5d;p=kivitendo-erp.git diff --git a/SL/IS.pm b/SL/IS.pm index 10892dec1..fdc70ab3a 100644 --- a/SL/IS.pm +++ b/SL/IS.pm @@ -25,7 +25,8 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1335, USA. #====================================================================== # # Inventory invoicing module @@ -34,7 +35,8 @@ package IS; -use List::Util qw(max); +use List::Util qw(max sum0); +use List::MoreUtils qw(any); use Carp; use SL::AM; @@ -50,13 +52,20 @@ use SL::MoreCommon; use SL::IC; use SL::IO; use SL::TransNumber; +use SL::DB::Chart; use SL::DB::Default; +use SL::DB::Draft; use SL::DB::Tax; use SL::DB::TaxZone; use SL::TransNumber; +use SL::DB; +use SL::Presenter::Part qw(type_abbreviation classification_abbreviation); use Data::Dumper; use strict; +use constant PCLASS_OK => 0; +use constant PCLASS_NOTFORSALE => 1; +use constant PCLASS_NOTFORPURCHASE => 2; sub invoice_details { $main::lxdebug->enter_sub(); @@ -148,6 +157,7 @@ sub invoice_details { # so that they can be sorted in later my %prepared_template_arrays = IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form); my @prepared_arrays = keys %prepared_template_arrays; + my @separate_totals = qw(non_separate_subtotal); my $ic_cvar_configs = CVar->get_configs(module => 'IC'); my $project_cvar_configs = CVar->get_configs(module => 'Projects'); @@ -163,11 +173,17 @@ sub invoice_details { push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs }; push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs }; - my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber); + my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber tax_id); my @payment_arrays = qw(payment paymentaccount paymentdate paymentsource paymentmemo); - map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @payment_arrays, @prepared_arrays); + my @invoices_for_advance_payment_arrays = qw(iap_invnumber iap_transdate + iap_amount iap_amount_nofmt + iap_taxamount iap_taxamount_nofmt + iap_open_amount iap_open_amount_nofmt + iap_netamount); + + map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @payment_arrays, @prepared_arrays, @invoices_for_advance_payment_arrays); my $totalweight = 0; foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) { @@ -331,6 +347,17 @@ sub invoice_details { push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : ''; push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} }, $form->{"discount_$i"}; + if ( $prepared_template_arrays{separate}[$i - 1] ) { + my $pabbr = $prepared_template_arrays{separate}[$i - 1]; + if ( ! $form->{"separate_${pabbr}_subtotal"} ) { + push @separate_totals , "separate_${pabbr}_subtotal"; + $form->{"separate_${pabbr}_subtotal"} = 0; + } + $form->{"separate_${pabbr}_subtotal"} += $linetotal; + } else { + $form->{non_separate_subtotal} += $linetotal; + } + $form->{total} += $linetotal; $form->{nodiscount_total} += $nodiscount_linetotal; $form->{discount_total} += $discount; @@ -416,16 +443,16 @@ sub invoice_details { } my $tax_rate = $taxrate * 100; push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|); - if ($form->{"assembly_$i"}) { + if ($form->{"part_type_$i"} eq 'assembly') { $sameitem = ""; # get parts and push them onto the stack my $sortorder = ""; if ($form->{groupitems}) { $sortorder = - qq|ORDER BY pg.partsgroup, a.oid|; + qq|ORDER BY pg.partsgroup, a.position|; } else { - $sortorder = qq|ORDER BY a.oid|; + $sortorder = qq|ORDER BY a.position|; } my $query = @@ -486,10 +513,25 @@ sub invoice_details { push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} }, $form->format_amount($myconfig, $form->{"${item}_rate"} * 100)); push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} }, $form->{"${item}_rate"} * 100); push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} }, $form->{"${item}_taxnumber"}); + push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} }, $form->{"${item}_tax_id"}); + + # taxnumber (= accno) is used for grouping the amounts of the various taxes and as a prefix in form + + # This code used to assume that at most one tax entry can point to the same + # chart_id, even though chart_id does not have a unique constraint! + + # This chart_id was then looked up via its accno, which is the key that is + # used to group the different taxes by for a record + + # As we now also store the tax_id we can use that to look up the tax + # instead, this is only done here to get the (translated) taxdescription. + + if ( $form->{"${item}_tax_id"} ) { + my $tax_obj = SL::DB::Manager::Tax->find_by(id => $form->{"${item}_tax_id"}) or die "Can't find tax with id " . $form->{"${item}_tax_id"}; + my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : ''; + push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%}); + } - my $tax_obj = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"}); - my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : ''; - push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%}); } for my $i (1 .. $form->{paidaccounts}) { @@ -519,35 +561,66 @@ sub invoice_details { $form->{nodiscount} = $form->format_amount($myconfig, $nodiscount, 2); $form->{yesdiscount} = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2); - $form->{invtotal} = ($form->{taxincluded}) ? $form->{total} : $form->{total} + $tax; - $form->{total} = $form->format_amount($myconfig, $form->{invtotal} - $form->{paid}, 2); + my $grossamount = ($form->{taxincluded}) ? $form->{total} : $form->{total} + $tax; + $form->{invtotal} = $form->round_amount($grossamount, 2, 1); + $form->{rounding} = $form->round_amount( + $form->{invtotal} - $form->round_amount($grossamount, 2), + 2 + ); + + $form->{rounding_nofmt} = $form->{rounding}; + $form->{total_nofmt} = $form->{total}; + $form->{invtotal_nofmt} = $form->{invtotal}; + $form->{paid_nofmt} = $form->{paid}; + $form->{rounding} = $form->format_amount($myconfig, $form->{rounding}, 2); + $form->{total} = $form->format_amount($myconfig, $form->{invtotal} - $form->{paid}, 2); $form->{invtotal} = $form->format_amount($myconfig, $form->{invtotal}, 2); $form->{paid} = $form->format_amount($myconfig, $form->{paid}, 2); - $form->set_payment_options($myconfig, $form->{invdate}); + $form->set_payment_options($myconfig, $form->{invdate}, 'sales_invoice'); + $form->{department} = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id}; $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef); $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id}; - $form->{department} = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id}; $form->{username} = $myconfig->{name}; + $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) for @separate_totals; + + my $id_for_iap = $form->{convert_from_oe_ids} || $form->{convert_from_ar_ids} || $form->{id}; + my $from_order = !!$form->{convert_from_oe_ids}; + foreach my $invoice_for_advance_payment (@{$self->_get_invoices_for_advance_payment($id_for_iap, $from_order)}) { + # Collect VAT of invoices for advance payment. + # Set sellprices to fxsellprices for items, because + # the PriceTaxCalculator sets fxsellprice from sellprice before calculating. + $_->sellprice($_->fxsellprice) for @{$invoice_for_advance_payment->items}; + my %pat = $invoice_for_advance_payment->calculate_prices_and_taxes; + my $taxamount = sum0 values %{ $pat{taxes_by_tax_id} }; + + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_$_"} }, $invoice_for_advance_payment->$_) for qw(invnumber transdate); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_amount_nofmt"} }, $invoice_for_advance_payment->amount); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_amount"} }, $invoice_for_advance_payment->amount_as_number); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_netamount"} }, $invoice_for_advance_payment->netamount_as_number); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_taxamount_nofmt"} }, $taxamount); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_taxamount"} }, $form->format_amount($myconfig, $taxamount, 2)); + + my $open_amount = $form->round_amount($invoice_for_advance_payment->open_amount, 2); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_open_amount_nofmt"} }, $open_amount); + push(@{ $form->{TEMPLATE_ARRAYS}->{"iap_open_amount"} }, $form->format_amount($myconfig, $open_amount, 2)); + + $form->{iap_amount_nofmt} += $invoice_for_advance_payment->amount; + $form->{iap_taxamount_nofmt} += $taxamount; + $form->{iap_open_amount_nofmt} += $open_amount; + $form->{iap_existing} = 1; + } + $form->{iap_amount} = $form->format_amount($myconfig, $form->{iap_amount_nofmt}, 2); + $form->{iap_taxamount} = $form->format_amount($myconfig, $form->{iap_taxamount_nofmt}, 2); + $form->{iap_open_amount} = $form->format_amount($myconfig, $form->{iap_open_amount_nofmt}, 2); - $main::lxdebug->leave_sub(); -} - -sub project_description { - $main::lxdebug->enter_sub(); - - my ($self, $dbh, $id) = @_; - my $form = \%main::form; - - my $query = qq|SELECT description FROM project WHERE id = ?|; - my ($description) = selectrow_query($form, $dbh, $query, conv_i($id)); + $form->{iap_final_amount_nofmt} = $form->{invtotal_nofmt} - $form->{iap_amount_nofmt}; + $form->{iap_final_amount} = $form->format_amount($myconfig, $form->{iap_final_amount_nofmt}, 2); $main::lxdebug->leave_sub(); - - return $_; } sub customer_details { @@ -600,8 +673,8 @@ sub customer_details { $ref->{cp_gender} = $contact->cp_gender; } } - # remove id and taxincluded before copy back - delete @$ref{qw(id taxincluded)}; + # remove id,notes (double of customernotes) and taxincluded before copy back + delete @$ref{qw(id taxincluded notes)}; @wanted_vars = grep({ $_ } @wanted_vars); if (scalar(@wanted_vars) > 0) { @@ -639,6 +712,13 @@ sub customer_details { 'trans_id' => $form->{customer_id}); map { $form->{"vc_cvar_$_->{name}"} = $_->{value} } @{ $custom_variables }; + if ($form->{cp_id}) { + $custom_variables = CVar->get_custom_variables(dbh => $dbh, + module => 'Contacts', + trans_id => $form->{cp_id}); + $form->{"cp_cvar_$_->{name}"} = $_->{value} for @{ $custom_variables }; + } + $form->{cp_greeting} = GenericTranslations->get('dbh' => $dbh, 'translation_type' => 'greetings::' . ($form->{cp_gender} eq 'f' ? 'female' : 'male'), 'language_id' => $language_id, @@ -649,12 +729,20 @@ sub customer_details { } sub post_invoice { + my ($self, $myconfig, $form, $provided_dbh, %params) = @_; $main::lxdebug->enter_sub(); - my ($self, $myconfig, $form, $provided_dbh, $payments_only) = @_; + my $rc = SL::DB->client->with_transaction(\&_post_invoice, $self, $myconfig, $form, $provided_dbh, %params); + + $::lxdebug->leave_sub; + return $rc; +} + +sub _post_invoice { + my ($self, $myconfig, $form, $provided_dbh, %params) = @_; - # connect to database, turn off autocommit - my $dbh = $provided_dbh ? $provided_dbh : $form->get_standard_dbh; + my $payments_only = $params{payments_only}; + my $dbh = $provided_dbh || SL::DB->client->dbh; my $restricter = SL::HTML::Restrict->create; my ($query, $sth, $null, $project_id, @values); @@ -672,6 +760,8 @@ sub post_invoice { my $all_units = AM->retrieve_units($myconfig, $form); + my $already_booked = !!$form->{id}; + if (!$payments_only) { if ($form->{storno}) { _delete_transfers($dbh, $form, $form->{storno_id}); @@ -826,9 +916,9 @@ sub post_invoice { next if $payments_only; - if ($form->{"inventory_accno_$i"} || $form->{"assembly_$i"}) { + if ($form->{"inventory_accno_$i"} || $form->{"part_type_$i"} eq 'assembly') { - if ($form->{"assembly_$i"}) { + if ($form->{"part_type_$i"} eq 'assembly') { # record assembly item as allocated &process_assembly($dbh, $myconfig, $form, $position, $form->{"id_$i"}, $baseqty); @@ -968,9 +1058,21 @@ SQL } } - $form->{amount}{ $form->{id} }{ $form->{AR} } = $netamount + $tax; - $form->{paid} = - $form->round_amount($form->{paid} * $form->{exchangerate} + $diff, 2); + # Invoice Summary includes Rounding + my $grossamount = $netamount + $tax; + my $rounding = $form->round_amount( + $form->round_amount($grossamount, 2, 1) - $form->round_amount($grossamount, 2), + 2 + ); + my $rnd_accno = $rounding == 0 ? 0 + : $rounding > 0 ? $form->{rndgain_accno} + : $form->{rndloss_accno} + ; + $form->{amount}{ $form->{id} }{ $form->{AR} } = $form->round_amount($grossamount, 2, 1); + $form->{paid} = $form->round_amount( + $form->{paid} * $form->{exchangerate} + $diff, + 2 + ); # reverse AR $form->{amount}{ $form->{id} }{ $form->{AR} } *= -1; @@ -983,7 +1085,106 @@ SQL $project_id = conv_i($form->{"globalproject_id"}); # entsprechend auch beim Bestimmen des Steuerschlüssels in Taxkey.pm berücksichtigen - my $taxdate = $form->{deliverydate} ? $form->{deliverydate} : $form->{invdate}; + my $taxdate = $form->{tax_point} ||$form->{deliverydate} || $form->{invdate}; + + # Sanity checks for invoices for advance payment and final invoices + my $advance_payment_clearing_chart; + if (any { $_ eq $form->{type} } qw(invoice_for_advance_payment final_invoice)) { + $advance_payment_clearing_chart = SL::DB::Chart->new(id => $::instance_conf->get_advance_payment_clearing_chart_id)->load; + die "No Clearing Chart for Advance Payment" unless ref $advance_payment_clearing_chart eq 'SL::DB::Chart'; + + my @current_taxaccounts = (split(/ /, $form->{taxaccounts})); + die 'Wrong call: Cannot post invoice for advance payment or final invoice with more than one tax' if (scalar @current_taxaccounts > 1); + + my @trans_ids = keys %{ $form->{amount} }; + if (scalar @trans_ids > 1) { + require Data::Dumper; + die "Invalid state for advance payment more than one trans_id " . Dumper($form->{amount}); + } + } + + my $iap_amounts; + if ($form->{type} eq 'final_invoice') { + my $id_for_iap = $form->{convert_from_oe_ids} || $form->{convert_from_ar_ids} || $form->{id}; + my $from_order = !!$form->{convert_from_oe_ids}; + my $invoices_for_advance_payment = $self->_get_invoices_for_advance_payment($id_for_iap, $from_order); + if (scalar @$invoices_for_advance_payment > 0) { + # reverse booking for invoices for advance payment + foreach my $invoice_for_advance_payment (@$invoices_for_advance_payment) { + # delete ? + # --> is implemented below (bookings are marked in memo field) + # + # TODO: helper table acc_trans_advance_payment + # trans_id for final invoice connects to acc_trans_id here + # my $booking = SL::DB::AccTrans->new( ...) + # --> helper table not nessessary because of mark in memo field + # + # TODO: If final_invoice change (delete storno) delete all connectin acc_trans entries, if + # period is not closed + # --> no problem because gldate of reverse booking is date of final invoice + # if deletion of final invoice is allowed, reverting bookings in invoices + # for advance payment are allowed, too. + # $booking->id, $self->id in helper table + if (!$already_booked) { + # move all netamount to correct transfer chart (19% or 7%) + my %inv_calc = $invoice_for_advance_payment->calculate_prices_and_taxes(); + my @trans_ids = keys %{ $inv_calc{amounts} }; + die "Invalid state for advance payment invoice,more than one trans_id" if (scalar @trans_ids > 1); + my $entry = delete $inv_calc{amounts}{$trans_ids[0]}; + my $tax; + if ($entry->{tax_id}) { + $tax = SL::DB::Manager::Tax->find_by(id => $entry->{tax_id}); # || die "Can't find tax with id " . $entry->{tax_id}; + } + # no tax, no prob + if ($tax and $tax->rate != 0) { + my $transfer_chart = $tax->taxkey == 2 ? SL::DB::Chart->new(id => $::instance_conf->get_advance_payment_taxable_7_id)->load + : $tax->taxkey == 3 ? SL::DB::Chart->new(id => $::instance_conf->get_advance_payment_taxable_19_id)->load + : undef; + die "No Transfer Chart for Advance Payment" unless ref $transfer_chart eq 'SL::DB::Chart'; + $form->{amount}->{$invoice_for_advance_payment->id}->{$transfer_chart->accno} = -1 * $invoice_for_advance_payment->netamount; + $form->{memo} ->{$invoice_for_advance_payment->id}->{$transfer_chart->accno} = 'reverse booking by final invoice'; + # AR + $form->{amount}->{$invoice_for_advance_payment->id}->{$form->{AR}} = $invoice_for_advance_payment->netamount; + $form->{memo} ->{$invoice_for_advance_payment->id}->{$form->{AR}} = 'reverse booking by final invoice'; + } + } + + # VAT for invoices for advance payment is booked on payment of these. So do not book this VAT for final invoice. + # And book the amount of the invoices for advance payment with taxkey 0 (see below). + # Collect amounts and VAT of invoices for advance payment. + + # Set sellprices to fxsellprices for items, because + # the PriceTaxCalculator sets fxsellprice from sellprice before calculating. + $_->sellprice($_->fxsellprice) for @{$invoice_for_advance_payment->items}; + my %pat = $invoice_for_advance_payment->calculate_prices_and_taxes; + + foreach my $tax_chart_id (keys %{ $pat{taxes_by_chart_id} }) { + my $tax_accno = SL::DB::Chart->load_cached($tax_chart_id)->accno; + $form->{amount}{ $form->{id} }{$tax_accno} -= $pat{taxes_by_chart_id}->{$tax_chart_id}; + $form->{amount}{ $form->{id} }{$form->{AR}} += $pat{taxes_by_chart_id}->{$tax_chart_id}; + } + + foreach my $amount_chart_id (keys %{ $pat{amounts} }) { + my $amount_accno = SL::DB::Chart->load_cached($amount_chart_id)->accno; + $iap_amounts->{$amount_accno} += $pat{amounts}->{$amount_chart_id}->{amount}; + $form->{amount}{ $form->{id} }{$amount_accno} -= $pat{amounts}->{$amount_chart_id}->{amount}; + } + } + } + } + + if ($form->{type} eq 'invoice_for_advance_payment') { + # get gross and move to clearing chart - delete everything else + # 1. gross + my $gross = $form->{amount}{ $form->{id} }{$form->{AR}}; + # 2. destroy + undef $form->{amount}{ $form->{id} }; + # 3. rebuild + $form->{amount}{ $form->{id} }{$form->{AR}} = $gross; + $form->{amount}{ $form->{id} }{$advance_payment_clearing_chart->accno} = $gross * -1; + # 4. no cogs, hopefully not commonly used at all + undef $form->{amount_cogs}; + } foreach my $trans_id (keys %{ $form->{amount_cogs} }) { foreach my $accno (keys %{ $form->{amount_cogs}{$trans_id} }) { @@ -1022,7 +1223,7 @@ SQL if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) { $query = - qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link) + qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link, memo) VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT tax_id FROM taxkeys @@ -1039,8 +1240,9 @@ SQL AND startdate <= ? ORDER BY startdate DESC LIMIT 1), ?, - (SELECT link FROM chart WHERE accno = ?))|; - @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno); + (SELECT link FROM chart WHERE accno = ?), + ?)|; + @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno, $form->{memo}{$trans_id}{$accno}); do_query($form, $dbh, $query, @values); $form->{amount}{$trans_id}{$accno} = 0; } @@ -1051,7 +1253,7 @@ SQL if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) { $query = - qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link) + qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link, memo) VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT tax_id FROM taxkeys @@ -1068,11 +1270,31 @@ SQL AND startdate <= ? ORDER BY startdate DESC LIMIT 1), ?, - (SELECT link FROM chart WHERE accno = ?))|; - @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno); + (SELECT link FROM chart WHERE accno = ?), + ?)|; + @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno,$form->{memo}{$trans_id}{$accno}); do_query($form, $dbh, $query, @values); } } + if (!$payments_only && ($rnd_accno != 0)) { + $query = + qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link) + VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|; + @values = (conv_i($trans_id), $rnd_accno, $rounding, conv_date($form->{invdate}), conv_i($project_id), $rnd_accno); + do_query($form, $dbh, $query, @values); + $rnd_accno = 0; + } + } + + # Book the amount of the invoices for advance payment with taxkey 0 (see below). + if ($form->{type} eq 'final_invoice' && $iap_amounts) { + foreach my $accno (keys %$iap_amounts) { + $query = + qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link) + VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|; + @values = (conv_i($form->{id}), $accno, $iap_amounts->{$accno}, conv_date($form->{invdate}), conv_i($project_id), $accno); + do_query($form, $dbh, $query, @values); + } } # deduct payment differences from diff @@ -1084,6 +1306,8 @@ SQL } } + my %already_cleared = %{ $params{already_cleared} // {} }; + # record payments and offsetting AR if (!$form->{storno}) { for my $i (1 .. $form->{paidaccounts}) { @@ -1113,9 +1337,16 @@ SQL # record AR $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate} + $diff, 2); + my $new_cleared = !$form->{"acc_trans_id_$i"} ? 'f' + : !$already_cleared{$form->{"acc_trans_id_$i"}} ? 'f' + : $already_cleared{$form->{"acc_trans_id_$i"}}->{amount} != $form->{"paid_$i"} * -1 ? 'f' + : $already_cleared{$form->{"acc_trans_id_$i"}}->{accno} != $accno ? 'f' + : $already_cleared{$form->{"acc_trans_id_$i"}}->{cleared} ? 't' + : 'f'; + if ($form->{amount}{ $form->{id} }{ $form->{AR} } != 0) { $query = - qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link) + qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, cleared, chart_link) VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT tax_id FROM taxkeys @@ -1131,9 +1362,9 @@ SQL WHERE accno = ?) AND startdate <= ? ORDER BY startdate DESC LIMIT 1), - ?, + ?, ?, (SELECT link FROM chart WHERE accno = ?))|; - @values = (conv_i($form->{"id"}), $form->{AR}, $amount, $form->{"datepaid_$i"}, $form->{AR}, conv_date($taxdate), $form->{AR}, conv_date($taxdate), $project_id, $form->{AR}); + @values = (conv_i($form->{"id"}), $form->{AR}, $amount, $form->{"datepaid_$i"}, $form->{AR}, conv_date($taxdate), $form->{AR}, conv_date($taxdate), $project_id, $new_cleared, $form->{AR}); do_query($form, $dbh, $query, @values); } @@ -1142,7 +1373,7 @@ SQL my $gldate = (conv_date($form->{"gldate_$i"}))? conv_date($form->{"gldate_$i"}) : conv_date($form->current_date($myconfig)); $query = - qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo, tax_id, taxkey, project_id, chart_link) + qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo, tax_id, taxkey, project_id, cleared, chart_link) VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, ?, ?, ?, (SELECT tax_id FROM taxkeys @@ -1158,10 +1389,10 @@ SQL WHERE accno = ?) AND startdate <= ? ORDER BY startdate DESC LIMIT 1), - ?, + ?, ?, (SELECT link FROM chart WHERE accno = ?))|; @values = (conv_i($form->{"id"}), $accno, $form->{"paid_$i"}, $form->{"datepaid_$i"}, - $gldate, $form->{"source_$i"}, $form->{"memo_$i"}, $accno, conv_date($taxdate), $accno, conv_date($taxdate), $project_id, $accno); + $gldate, $form->{"source_$i"}, $form->{"memo_$i"}, $accno, conv_date($taxdate), $accno, conv_date($taxdate), $project_id, $new_cleared, $accno); do_query($form, $dbh, $query, @values); # exchangerate difference @@ -1231,45 +1462,42 @@ SQL $form->new_lastmtime('ar'); - $dbh->commit if !$provided_dbh; - - $main::lxdebug->leave_sub(); return; } - $amount = $netamount + $tax; + $amount = $form->round_amount( $netamount + $tax, 2, 1); # save AR record #erweiterung fuer lieferscheinnummer (donumber) 12.02.09 jb $query = qq|UPDATE ar set invnumber = ?, ordnumber = ?, quonumber = ?, cusordnumber = ?, - transdate = ?, orddate = ?, quodate = ?, customer_id = ?, + transdate = ?, orddate = ?, quodate = ?, tax_point = ?, customer_id = ?, amount = ?, netamount = ?, paid = ?, duedate = ?, deliverydate = ?, invoice = ?, shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name = ?), department_id = ?, payment_id = ?, taxincluded = ?, - type = ?, language_id = ?, taxzone_id = ?, shipto_id = ?, + type = ?, language_id = ?, taxzone_id = ?, shipto_id = ?, billing_address_id = ?, employee_id = ?, salesman_id = ?, storno_id = ?, storno = ?, cp_id = ?, marge_total = ?, marge_percent = ?, globalproject_id = ?, delivery_customer_id = ?, transaction_description = ?, delivery_vendor_id = ?, - donumber = ?, invnumber_for_credit_note = ?, direct_debit = ?, + donumber = ?, invnumber_for_credit_note = ?, direct_debit = ?, qrbill_without_amount = ?, delivery_term_id = ? WHERE id = ?|; @values = ( $form->{"invnumber"}, $form->{"ordnumber"}, $form->{"quonumber"}, $form->{"cusordnumber"}, - conv_date($form->{"invdate"}), conv_date($form->{"orddate"}), conv_date($form->{"quodate"}), conv_i($form->{"customer_id"}), + conv_date($form->{"invdate"}), conv_date($form->{"orddate"}), conv_date($form->{"quodate"}), conv_date($form->{tax_point}), conv_i($form->{"customer_id"}), $amount, $netamount, $form->{"paid"}, conv_date($form->{"duedate"}), conv_date($form->{"deliverydate"}), '1', $form->{"shippingpoint"}, $form->{"shipvia"}, $restricter->process($form->{"notes"}), $form->{"intnotes"}, $form->{"currency"}, conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}), $form->{"taxincluded"} ? 't' : 'f', - $form->{"type"}, conv_i($form->{"language_id"}), conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}), + $form->{"type"}, conv_i($form->{"language_id"}), conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}), conv_i($form->{billing_address_id}), conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}), conv_i($form->{storno_id}), $form->{"storno"} ? 't' : 'f', conv_i($form->{"cp_id"}), 1 * $form->{marge_total} , 1 * $form->{marge_percent}, conv_i($form->{"globalproject_id"}), conv_i($form->{"delivery_customer_id"}), $form->{transaction_description}, conv_i($form->{"delivery_vendor_id"}), - $form->{"donumber"}, $form->{"invnumber_for_credit_note"}, $form->{direct_debit} ? 't' : 'f', + $form->{"donumber"}, $form->{"invnumber_for_credit_note"}, $form->{direct_debit} ? 't' : 'f', $form->{qrbill_without_amount} ? 't' : 'f', conv_i($form->{delivery_term_id}), conv_i($form->{"id"})); do_query($form, $dbh, $query, @values); @@ -1278,7 +1506,7 @@ SQL if ($form->{storno}) { $query = qq!UPDATE ar SET - paid = paid + amount, + paid = amount, storno = 't', intnotes = ? || intnotes WHERE id = ?!; @@ -1286,10 +1514,10 @@ SQL do_query($form, $dbh, qq|UPDATE ar SET paid = amount WHERE id = ?|, conv_i($form->{"id"})); } - $form->new_lastmtime('ar'); - - $form->{name} = $form->{customer}; - $form->{name} =~ s/--\Q$form->{customer_id}\E//; + # maybe we are in a larger transaction and the current + # object is not yet persistent in the db, therefore we + # need the current dbh to get the not yet committed mtime + $form->new_lastmtime('ar', $provided_dbh); # add shipto if (!$form->{shipto_id}) { @@ -1354,34 +1582,87 @@ SQL do_query($form, $dbh, $query, @orphaned_ids); } + if ($form->{draft_id}) { + SL::DB::Manager::Draft->delete_all(where => [ id => delete($form->{draft_id}) ]); + } + # safety check datev export if ($::instance_conf->get_datev_check_on_sales_invoice) { - my $transdate = $::form->{invdate} ? DateTime->from_lxoffice($::form->{invdate}) : undef; - $transdate ||= DateTime->today; my $datev = SL::DATEV->new( - exporttype => DATEV_ET_BUCHUNGEN, - format => DATEV_FORMAT_KNE, dbh => $dbh, trans_id => $form->{id}, ); - $datev->export; + $datev->generate_datev_data; if ($datev->errors) { - $dbh->rollback; die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors; } } - my $rc = 1; - $dbh->commit if !$provided_dbh; + # update shop status + my $invoice = SL::DB::Invoice->new( id => $form->{id} )->load; + my @linked_shop_orders = $invoice->linked_records( + from => 'ShopOrder', + via => ['DeliveryOrder','Order',], + ); + #do update + my $shop_order = $linked_shop_orders[0][0]; + if ( $shop_order ) { + require SL::Shop; + my $shop_config = SL::DB::Manager::Shop->get_first( query => [ id => $shop_order->shop_id ] ); + my $shop = SL::Shop->new( config => $shop_config ); + $shop->connector->set_orderstatus($shop_order->shop_trans_id, "completed"); + } - $main::lxdebug->leave_sub(); + return 1; +} - return $rc; +sub _get_invoices_for_advance_payment { + my ($self, $id, $id_is_from_order) = @_; + + return [] if !$id; + + # Search all related invoices for advance payment. + # Case 1: + # (order) -> invoice for adv. payment 1 -> invoice for adv. payment 2 -> invoice for adv. payment 3 -> final invoice + # + # Case 2: + # order -> invoice for adv. payment 1 + # | |`-> invoice for adv. payment 2 + # | `--> invoice for adv. payment 3 + # `----> final invoice + # + # The id is currently that from the last invoice for adv. payment (3 in this example), + # that from the final invoice or that from the order. + + my $invoice_obj; + my $order_obj; + my $links; + + if (!$id_is_from_order) { + $invoice_obj = SL::DB::Invoice->load_cached($id*1); + $links = $invoice_obj->linked_records(direction => 'from', from => ['Order']); + $order_obj = $links->[0]; + } else { + $order_obj = SL::DB::Order->load_cached($id*1); + } + + if ($order_obj) { + $links = $order_obj ->linked_records(direction => 'to', to => ['Invoice']); + } else { + $links = $invoice_obj->linked_records(direction => 'from', from => ['Invoice'], recursive => 1); + } + + my @related_invoices = grep {'SL::DB::Invoice' eq ref $_ && "invoice_for_advance_payment" eq $_->type} @$links; + + push @related_invoices, $invoice_obj if !$order_obj && "invoice_for_advance_payment" eq $invoice_obj->type; + + return \@related_invoices; } + sub transfer_out { $::lxdebug->enter_sub; @@ -1389,7 +1670,7 @@ sub transfer_out { my (@errors, @transfers); - # do nothing, if transfer default is not requeseted at all + # do nothing, if transfer default is not requested at all if (!$::instance_conf->get_transfer_default) { $::lxdebug->leave_sub; return \@errors; @@ -1399,26 +1680,56 @@ sub transfer_out { foreach my $i (1 .. $form->{rowcount}) { next if !$form->{"id_$i"}; - my ($err, $wh_id, $bin_id) = _determine_wh_and_bin($dbh, $::instance_conf, - $form->{"id_$i"}, - $form->{"qty_$i"}, - $form->{"unit_$i"}); - if (!@{ $err } && $wh_id && $bin_id) { - push @transfers, { - 'parts_id' => $form->{"id_$i"}, - 'qty' => $form->{"qty_$i"}, - 'unit' => $form->{"unit_$i"}, - 'transfer_type' => 'shipped', - 'src_warehouse_id' => $wh_id, - 'src_bin_id' => $bin_id, - 'project_id' => $form->{"project_id_$i"}, - 'invoice_id' => $form->{"invoice_id_$i"}, - 'comment' => $::locale->text("Default transfer invoice"), - }; - } + my ($err, $qty, $wh_id, $bin_id, $chargenumber); + + if ($::instance_conf->get_sales_serial_eq_charge && $form->{"serialnumber_$i"}) { + my @serials = split(" ", $form->{"serialnumber_$i"}); + if (scalar @serials != $form->{"qty_$i"}) { + push @errors, $::locale->text("Cannot transfer #1 qty with #2 serial number(s)", $form->{"qty_$i"}, scalar @serials); + last; + } + foreach my $serial (@serials) { + ($qty, $wh_id, $bin_id, $chargenumber) = WH->get_wh_and_bin_for_charge(chargenumber => $serial); + if (!$qty) { + push @errors, $::locale->text("Not enough in stock for the serial number #1", $serial); + last; + } + push @transfers, { + 'parts_id' => $form->{"id_$i"}, + 'qty' => 1, + 'unit' => $form->{"unit_$i"}, + 'transfer_type' => 'shipped', + 'src_warehouse_id' => $wh_id, + 'src_bin_id' => $bin_id, + 'chargenumber' => $chargenumber, + 'project_id' => $form->{"project_id_$i"}, + 'invoice_id' => $form->{"invoice_id_$i"}, + 'comment' => $::locale->text("Default transfer invoice with charge number"), + }; + } + $err = []; # error handling uses @errors direct + } else { + ($err, $wh_id, $bin_id) = _determine_wh_and_bin($dbh, $::instance_conf, + $form->{"id_$i"}, + $form->{"qty_$i"}, + $form->{"unit_$i"}); + if (!@{ $err } && $wh_id && $bin_id) { + push @transfers, { + 'parts_id' => $form->{"id_$i"}, + 'qty' => $form->{"qty_$i"}, + 'unit' => $form->{"unit_$i"}, + 'transfer_type' => 'shipped', + 'src_warehouse_id' => $wh_id, + 'src_bin_id' => $bin_id, + 'project_id' => $form->{"project_id_$i"}, + 'invoice_id' => $form->{"invoice_id_$i"}, + 'comment' => $::locale->text("Default transfer invoice"), + }; + } + } push @errors, @{ $err }; - } + } # end form rowcount if (!@errors) { WH->transfer(@transfers); @@ -1463,7 +1774,7 @@ sub _determine_wh_and_bin { parts_id => $part->id, bin_id => $bin_id); if ($error == 1) { - push @errors, $::locale->text('Part "#1" has chargenumber or best before date set. So it cannot be transfered automaticaly.', + push @errors, $::locale->text('Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.', $part->description); } my $form_unit_obj = SL::DB::Unit->new(name => $unit)->load; @@ -1551,17 +1862,33 @@ sub _delete_payments { } sub post_payment { + my ($self, $myconfig, $form, $locale) = @_; $main::lxdebug->enter_sub(); + my $rc = SL::DB->client->with_transaction(\&_post_payment, $self, $myconfig, $form, $locale); + + $::lxdebug->leave_sub; + return $rc; +} + +sub _post_payment { my ($self, $myconfig, $form, $locale) = @_; - # connect to database, turn off autocommit - my $dbh = $form->get_standard_dbh; + my $dbh = SL::DB->client->dbh; my (%payments, $old_form, $row, $item, $query, %keep_vars); $old_form = save_form(); + $query = <{id}); + # Delete all entries in acc_trans from prior payments. if (SL::DB::Default->get->payments_changeable != 0) { $self->_delete_payments($form, $dbh); @@ -1608,15 +1935,11 @@ sub post_payment { ($form->{AR}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id})); # Post the new payments. - $self->post_invoice($myconfig, $form, $dbh, 1); + $self->post_invoice($myconfig, $form, $dbh, payments_only => 1, already_cleared => \%already_cleared); restore_form($old_form); - my $rc = $dbh->commit(); - - $main::lxdebug->leave_sub(); - - return $rc; + return 1; } sub process_assembly { @@ -1625,8 +1948,7 @@ sub process_assembly { my ($dbh, $myconfig, $form, $position, $id, $totalqty) = @_; my $query = - qq|SELECT a.parts_id, a.qty, p.assembly, p.partnumber, p.description, p.unit, - p.inventory_accno_id, p.income_accno_id, p.expense_accno_id + qq|SELECT a.parts_id, a.qty, p.part_type, p.partnumber, p.description, p.unit FROM assembly a JOIN parts p ON (a.parts_id = p.id) WHERE (a.id = ?)|; @@ -1766,7 +2088,7 @@ sub reverse_invoice { # reverse inventory items my $query = - qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, p.inventory_accno_id + qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.part_type FROM invoice i JOIN parts p ON (i.parts_id = p.id) WHERE i.trans_id = ?|; @@ -1814,12 +2136,19 @@ sub reverse_invoice { } sub delete_invoice { + my ($self, $myconfig, $form) = @_; $main::lxdebug->enter_sub(); + my $rc = SL::DB->client->with_transaction(\&_delete_invoice, $self, $myconfig, $form); + + $::lxdebug->leave_sub; + return $rc; +} + +sub _delete_invoice { my ($self, $myconfig, $form) = @_; - # connect to database - my $dbh = $form->get_standard_dbh; + my $dbh = SL::DB->client->dbh; &reverse_invoice($dbh, $form); _delete_transfers($dbh, $form, $form->{id}); @@ -1840,6 +2169,18 @@ sub delete_invoice { do_query($form, $dbh, qq|UPDATE ar SET storno = 'f', paid = 0 WHERE id = ?|, $invoice_id); } + # if we delete a final invoice, the reverse bookings for the clearing account in the invoice for advance payment + # must be deleted as well + my $invoices_for_advance_payment = $self->_get_invoices_for_advance_payment($form->{id}); + + # Todo: allow only if invoice for advance payment is not paid. + # die if any { $_->paid } for @$invoices_for_advance_payment; + my @trans_ids_to_consider = map { $_->id } @$invoices_for_advance_payment; + if (scalar @trans_ids_to_consider) { + my $query = sprintf 'DELETE FROM acc_trans WHERE memo LIKE ? AND trans_id IN (%s)', join ', ', ("?") x scalar @trans_ids_to_consider; + do_query($form, $dbh, $query, 'reverse booking by final invoice', @trans_ids_to_consider); + } + # delete spool files my @spoolfiles = selectall_array_query($form, $dbh, qq|SELECT spoolfile FROM status WHERE trans_id = ?|, @values); @@ -1852,25 +2193,26 @@ sub delete_invoice { map { do_query($form, $dbh, $_, @values) } @queries; - my $rc = $dbh->commit; + my $spool = $::lx_office_conf{paths}->{spool}; + map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles; - if ($rc) { - my $spool = $::lx_office_conf{paths}->{spool}; - map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles; - } - - $main::lxdebug->leave_sub(); - - return $rc; + return 1; } sub retrieve_invoice { + my ($self, $myconfig, $form) = @_; $main::lxdebug->enter_sub(); + my $rc = SL::DB->client->with_transaction(\&_retrieve_invoice, $self, $myconfig, $form); + + $::lxdebug->leave_sub; + return $rc; +} + +sub _retrieve_invoice { my ($self, $myconfig, $form) = @_; - # connect to database - my $dbh = $form->get_standard_dbh; + my $dbh = SL::DB->client->dbh; my ($sth, $ref, $query); @@ -1882,7 +2224,9 @@ sub retrieve_invoice { (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id) AS income_accno, (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id) AS expense_accno, (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, - (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno + (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno, + (SELECT c.accno FROM chart c WHERE d.rndgain_accno_id = c.id) AS rndgain_accno, + (SELECT c.accno FROM chart c WHERE d.rndloss_accno_id = c.id) AS rndloss_accno ${query_transdate} FROM defaults d|; @@ -1899,14 +2243,15 @@ sub retrieve_invoice { qq|SELECT a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber, a.orddate, a.quodate, a.globalproject_id, - a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.storno_id, a.gldate, + a.transdate AS invdate, a.deliverydate, a.tax_point, a.paid, a.storno, a.storno_id, a.gldate, a.shippingpoint, a.shipvia, a.notes, a.intnotes, a.taxzone_id, a.duedate, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.shipto_id, a.cp_id, + a.billing_address_id, a.employee_id, a.salesman_id, a.payment_id, a.mtime, a.itime, a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type, a.transaction_description, a.donumber, a.invnumber_for_credit_note, - a.marge_total, a.marge_percent, a.direct_debit, a.delivery_term_id, + a.marge_total, a.marge_percent, a.direct_debit, a.qrbill_without_amount, a.delivery_term_id, dc.dunning_description, e.name AS employee FROM ar a @@ -1928,8 +2273,7 @@ sub retrieve_invoice { # get shipto $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|; $ref = selectfirst_hashref_query($form, $dbh, $query, $id); - delete($ref->{id}); - map { $form->{$_} = $ref->{$_} } keys %$ref; + $form->{$_} = $ref->{$_} for grep { m{^shipto(?!_id$)} } keys %$ref; # get printed, emailed $query = qq|SELECT printed, emailed, spoolfile, formname FROM status WHERE trans_id = ?|; @@ -1943,7 +2287,8 @@ sub retrieve_invoice { $sth->finish; map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued); - my $transdate = $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) + my $transdate = $form->{tax_point} ? $dbh->quote($form->{tax_point}) + : $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) : $form->{invdate} ? $dbh->quote($form->{invdate}) : "current_date"; @@ -1962,7 +2307,8 @@ sub retrieve_invoice { i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate, i.project_id, i.serialnumber, i.pricegroup_id, i.ordnumber, i.donumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost, i.price_factor_id, i.price_factor, i.marge_price_factor, i.active_price_source, i.active_discount_source, - p.partnumber, p.assembly, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice, + p.partnumber, p.part_type, p.notes AS partnotes, p.formel, p.listprice, + p.classification_id, pr.projectnumber, pg.partsgroup, prg.pricegroup FROM invoice i @@ -2001,7 +2347,8 @@ sub retrieve_invoice { # get tax rates and description my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno}; $query = - qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t + qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber + FROM tax t LEFT JOIN chart c ON (c.id = t.chart_id) WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk @@ -2023,7 +2370,8 @@ sub retrieve_invoice { if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) { $form->{"$ptr->{accno}_rate"} = $ptr->{rate}; $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription}; - $form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber}; + $form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber}; # don't use this anymore + $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id}; $form->{taxaccounts} .= "$ptr->{accno} "; } @@ -2055,11 +2403,7 @@ sub retrieve_invoice { Common::webdav_folder($form); } - my $rc = $dbh->commit; - - $main::lxdebug->leave_sub(); - - return $rc; + return 1; } sub get_customer { @@ -2079,23 +2423,31 @@ sub get_customer { my $payment_id; # get customer + my $where = ''; + if ($cid) { + $where .= 'AND c.id = ?'; + push @values, $cid; + } $query = qq|SELECT c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.email, c.cc, c.bcc, c.language_id, c.payment_id, c.delivery_term_id, c.street, c.zipcode, c.city, c.country, - c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, cu.name AS curr, + c.notes AS intnotes, c.pricegroup_id as customer_pricegroup_id, c.taxzone_id, c.salesman_id, cu.name AS curr, c.taxincluded_checked, c.direct_debit, + (SELECT aba.id + FROM additional_billing_addresses aba + WHERE aba.default_address + LIMIT 1) AS default_billing_address_id, b.discount AS tradediscount, b.description AS business FROM customer c LEFT JOIN business b ON (b.id = c.business_id) LEFT JOIN currencies cu ON (c.currency_id=cu.id) - WHERE c.id = ?|; - push @values, $cid; + WHERE 1 = 1 $where|; $ref = selectfirst_hashref_query($form, $dbh, $query, @values); delete $ref->{salesman_id} if !$ref->{salesman_id}; - delete $ref->{payment_id} if $form->{payment_id}; + delete $ref->{payment_id} if !$ref->{payment_id}; map { $form->{$_} = $ref->{$_} } keys %$ref; @@ -2150,46 +2502,6 @@ sub get_customer { } $sth->finish; - # setup last accounts used for this customer - if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) { - $query = - qq|SELECT c.id, c.accno, c.description, c.link, c.category - FROM chart c - JOIN acc_trans ac ON (ac.chart_id = c.id) - JOIN ar a ON (a.id = ac.trans_id) - WHERE a.customer_id = ? - AND NOT (c.link LIKE '%_tax%' OR c.link LIKE '%_paid%') - AND a.id IN (SELECT max(a2.id) FROM ar a2 WHERE a2.customer_id = ?)|; - $sth = prepare_execute_query($form, $dbh, $query, $cid, $cid); - - my $i = 0; - while ($ref = $sth->fetchrow_hashref('NAME_lc')) { - if ($ref->{category} eq 'I') { - $i++; - $form->{"AR_amount_$i"} = "$ref->{accno}--$ref->{description}"; - - if ($form->{initial_transdate}) { - my $tax_query = - qq|SELECT tk.tax_id, t.rate - FROM taxkeys tk - LEFT JOIN tax t ON tk.tax_id = t.id - WHERE (tk.chart_id = ?) AND (startdate <= date(?)) - ORDER BY tk.startdate DESC - LIMIT 1|; - my ($tax_id, $rate) = - selectrow_query($form, $dbh, $tax_query, $ref->{id}, - $form->{initial_transdate}); - $form->{"taxchart_$i"} = "${tax_id}--${rate}"; - } - } - if ($ref->{category} eq 'A') { - $form->{ARselected} = $form->{AR_1} = $ref->{accno}; - } - } - $sth->finish; - $form->{rowcount} = $i if ($i && !$form->{type}); - } - $main::lxdebug->leave_sub(); } @@ -2262,8 +2574,9 @@ sub retrieve_item { my $query = qq|SELECT p.id, p.partnumber, p.description, p.sellprice, - p.listprice, p.inventory_accno_id, p.lastcost, + p.listprice, p.part_type, p.lastcost, p.ean, p.notes, + p.classification_id, c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, @@ -2277,13 +2590,13 @@ sub retrieve_item { c3.new_chart_id AS expense_new_chart, date($transdate) - c3.valid_from AS expense_valid, - p.unit, p.assembly, p.onhand, + p.unit, p.part_type, p.onhand, p.notes AS partnotes, p.notes AS longdescription, p.not_discountable, p.formel, p.payment_id AS part_payment_id, p.price_factor_id, p.weight, pfac.factor AS price_factor, - + pt.used_for_sale AS used_for_sale, pg.partsgroup FROM parts p @@ -2300,6 +2613,7 @@ sub retrieve_item { FROM taxzone_charts tc WHERE tc.buchungsgruppen_id = p.buchungsgruppen_id and tc.taxzone_id = ${taxzone_id}) = c3.id) LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id) + LEFT JOIN part_classifications pt ON (pt.id = p.classification_id) LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id) WHERE $where|; my $sth = prepare_execute_query($form, $dbh, $query, @values); @@ -2317,6 +2631,7 @@ sub retrieve_item { LIMIT 1| ] ); map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries; + my $has_wrong_pclass = PCLASS_OK; while (my $ref = $sth->fetchrow_hashref('NAME_lc')) { if ($mm_by_id{$ref->{id}}) { @@ -2328,6 +2643,12 @@ sub retrieve_item { push @{ $ref->{matches} ||= [] }, $::locale->text('EAN') . ': ' . $ref->{ean}; } + $ref->{type_and_classific} = type_abbreviation($ref->{part_type}) . + classification_abbreviation($ref->{classification_id}); + if (! $ref->{used_for_sale} ) { + $has_wrong_pclass = PCLASS_NOTFORSALE ; + next; + } # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das # Buchungskonto also aus dem Ergebnis rausgenommen werden. @@ -2356,7 +2677,7 @@ sub retrieve_item { # get tax rates and description my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno}; $query = - qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber + qq|SELECT c.accno, t.taxdescription, t.id as tax_id, t.rate, c.accno as taxnumber FROM tax t LEFT JOIN chart c ON (c.id = t.chart_id) WHERE t.id in @@ -2385,6 +2706,7 @@ sub retrieve_item { $form->{"$ptr->{accno}_rate"} = $ptr->{rate}; $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription}; $form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber}; + $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id}; $form->{taxaccounts} .= "$ptr->{accno} "; } @@ -2405,21 +2727,22 @@ sub retrieve_item { } $ref->{onhand} *= 1; - push @{ $form->{item_list} }, $ref; } $sth->finish; $_->[1]->finish for @translation_queries; + $form->{is_wrong_pclass} = $has_wrong_pclass; + $form->{NOTFORSALE} = PCLASS_NOTFORSALE; + $form->{NOTFORPURCHASE} = PCLASS_NOTFORPURCHASE; foreach my $item (@{ $form->{item_list} }) { my $custom_variables = CVar->get_custom_variables(module => 'IC', trans_id => $item->{id}, dbh => $dbh, ); - + $form->{is_wrong_pclass} = PCLASS_OK; # one correct type map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables }; } - $main::lxdebug->leave_sub(); }