' . $::locale->quote_special_chars('HTML', $body) . '
'; + + my $translation_type = $params{translation_type} // "preset_text_$self->{formname}"; + my $main_body = GenericTranslations->get(translation_type => $translation_type, language_id => $self->{language_id}); + $main_body = GenericTranslations->get(translation_type => $params{fallback_translation_type}, language_id => $self->{language_id}) if !$main_body && $params{fallback_translation_type}; + $body .= $main_body; $body = $main::locale->unquote_special_chars('HTML', $body); @@ -1542,22 +1438,31 @@ sub update_balance { sub update_exchangerate { $main::lxdebug->enter_sub(); - my ($self, $dbh, $curr, $transdate, $buy, $sell) = @_; - my ($query); - # some sanity check for currency - if ($curr eq '') { - $main::lxdebug->leave_sub(); - return; - } - $query = qq|SELECT name AS curr FROM currencies WHERE id=(SELECT currency_id FROM defaults)|; - - my ($defaultcurrency) = selectrow_query($self, $dbh, $query); - - if ($curr eq $defaultcurrency) { + validate_pos(@_, + { isa => 'Form'}, + { isa => 'DBI::db'}, + { type => SCALAR, callbacks => { is_fx_currency => sub { shift ne $_[1]->[0]->{defaultcurrency} } } }, # should be ISO three letter codes for currency identification (ISO 4217) + { type => SCALAR, callbacks => { is_valid_kivi_date => sub { shift =~ m/\d+\d+\d+/ } } }, # we have three numers + { type => SCALAR, callbacks => { is_null_or_ar_int => sub { $_[0] == 0 + || $_[0] > 0 + && $_[1]->[0]->{script} =~ m/cp\.pl|ar\.pl|is\.pl/ } } }, # value buy fxrate + { type => SCALAR, callbacks => { is_null_or_ap_int => sub { $_[0] == 0 + || $_[0] > 0 + && $_[1]->[0]->{script} =~ m/cp\.pl|ap\.pl|ir\.pl/ } } }, # value sell fxrate + { type => SCALAR, callbacks => { is_current_form_id => sub { $_[0] == $_[1]->[0]->{id} } }, optional => 1 }, + { type => SCALAR, callbacks => { is_valid_fx_table => sub { shift =~ m/(ar|ap|bank_transactions)/ } }, optional => 1 } + ); + + my ($self, $dbh, $curr, $transdate, $buy, $sell, $id, $record_table) = @_; + + # record has a exchange rate and should be updated + if ($record_table && $id) { + do_query($self, $dbh, qq|UPDATE $record_table SET exchangerate = ? WHERE id = ?|, $buy || $sell, $id); $main::lxdebug->leave_sub(); return; } + my ($query); $query = qq|SELECT e.currency_id FROM exchangerate e WHERE e.currency_id = (SELECT cu.id FROM currencies cu WHERE cu.name=?) AND e.transdate = ? FOR UPDATE|; @@ -1583,6 +1488,7 @@ sub update_exchangerate { } if ($sth->fetchrow_array) { + # die "this never happens never"; # except for credit or debit bookings $query = qq|UPDATE exchangerate SET $set WHERE currency_id = (SELECT id FROM currencies WHERE name = ?) @@ -1598,80 +1504,34 @@ sub update_exchangerate { $main::lxdebug->leave_sub(); } -sub save_exchangerate { - $main::lxdebug->enter_sub(); - - my ($self, $myconfig, $currency, $transdate, $rate, $fld) = @_; - - SL::DB->client->with_transaction(sub { - my $dbh = SL::DB->client->dbh; - - my ($buy, $sell); - - $buy = $rate if $fld eq 'buy'; - $sell = $rate if $fld eq 'sell'; - - - $self->update_exchangerate($dbh, $currency, $transdate, $buy, $sell); - 1; - }) or do { die SL::DB->client->error }; - - $main::lxdebug->leave_sub(); -} - -sub get_exchangerate { - $main::lxdebug->enter_sub(); - - my ($self, $dbh, $curr, $transdate, $fld) = @_; - my ($query); - - unless ($transdate && $curr) { - $main::lxdebug->leave_sub(); - return 1; - } - - $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|; - - my ($defaultcurrency) = selectrow_query($self, $dbh, $query); - - if ($curr eq $defaultcurrency) { - $main::lxdebug->leave_sub(); - return 1; - } - - $query = qq|SELECT e.$fld FROM exchangerate e - WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|; - my ($exchangerate) = selectrow_query($self, $dbh, $query, $curr, $transdate); - - - - $main::lxdebug->leave_sub(); - - return $exchangerate; -} - sub check_exchangerate { $main::lxdebug->enter_sub(); - my ($self, $myconfig, $currency, $transdate, $fld) = @_; + validate_pos(@_, + { isa => 'Form'}, + { type => HASHREF, callbacks => { has_yy_in_dateformat => sub { $_[0]->{dateformat} =~ m/yy/ } } }, + { type => SCALAR, callbacks => { is_fx_currency => sub { shift ne $_[1]->[0]->{defaultcurrency} } } }, # should be ISO three letter codes for currency identification (ISO 4217) + { type => SCALAR | HASHREF, callbacks => { is_valid_kivi_date => sub { shift =~ m/\d+.\d+.\d+/ } } }, # we have three numbers. Either DateTime or form scalar + { type => SCALAR, callbacks => { is_buy_or_sell_rate => sub { shift =~ m/^(buy|sell)$/ } } }, + { type => SCALAR | UNDEF, callbacks => { is_current_form_id => sub { $_[0] == $_[1]->[0]->{id} } }, optional => 1 }, + { type => SCALAR, callbacks => { is_valid_fx_table => sub { shift =~ m/^(ar|ap)$/ } }, optional => 1 } + ); + my ($self, $myconfig, $currency, $transdate, $fld, $id, $record_table) = @_; - if ($fld !~/^buy|sell$/) { - $self->error('Fatal: check_exchangerate called with invalid buy/sell argument'); - } - - unless ($transdate) { - $main::lxdebug->leave_sub(); - return ""; - } + my $dbh = $self->get_standard_dbh($myconfig); - my ($defaultcurrency) = $self->get_default_currency($myconfig); + # callers wants a check if record has a exchange rate and should be fetched instead + if ($record_table && $id) { + my ($record_exchange_rate) = selectrow_query($self, $dbh, qq|SELECT exchangerate FROM $record_table WHERE id = ?|, $id); + if ($record_exchange_rate && $record_exchange_rate > 0) { - if ($currency eq $defaultcurrency) { - $main::lxdebug->leave_sub(); - return 1; + $main::lxdebug->leave_sub(); + # second param indicates record exchange rate + return ($record_exchange_rate, 1); + } } - my $dbh = $self->get_standard_dbh($myconfig); + # fetch default from exchangerate table my $query = qq|SELECT e.$fld FROM exchangerate e WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|; @@ -1788,9 +1648,11 @@ sub set_payment_options { $self->{payment_terms} =~ s/<\%mandator_id\%>/$self->{mandator_id}/g; map { $self->{payment_terms} =~ s/<%${_}%>/$formatted_amounts{$_}/g; } keys %formatted_amounts; - - $self->{skonto_in_percent} = $formatted_amounts{skonto_in_percent}; - + # put amounts in form for print template + foreach (keys %formatted_amounts) { + next if $_ =~ m/(^total$|^invtotal$)/; + $self->{$_} = $formatted_amounts{$_}; + } } sub get_template_language { @@ -1860,7 +1722,7 @@ sub add_shipto { my @values; foreach my $item (qw(name department_1 department_2 street zipcode city country gln - contact cp_gender phone fax email)) { + contact phone fax email)) { if ($self->{"shipto$item"}) { $shipto = 1 if ($self->{$item} ne $self->{"shipto$item"}); } @@ -1869,6 +1731,12 @@ sub add_shipto { return if !$shipto; + # shiptocp_gender only makes sense, if any other shipto attribute is set. + # Because shiptocp_gender is set to 'm' by default in forms + # it must not be considered above to decide if shiptos has to be added or + # updated, but must be inserted or updated as well in case. + push(@values, $self->{shiptocp_gender}); + my $shipto_id = $self->{shipto_id}; if ($self->{shipto_id}) { @@ -1882,10 +1750,10 @@ sub add_shipto { shiptocountry = ?, shiptogln = ?, shiptocontact = ?, - shiptocp_gender = ?, shiptophone = ?, shiptofax = ?, - shiptoemail = ? + shiptoemail = ?, + shiptocp_gender = ? WHERE shipto_id = ?|; do_query($self, $dbh, $query, @values, $self->{shipto_id}); } else { @@ -1899,10 +1767,10 @@ sub add_shipto { shiptocountry = ? AND shiptogln = ? AND shiptocontact = ? AND - shiptocp_gender = ? AND shiptophone = ? AND shiptofax = ? AND shiptoemail = ? AND + shiptocp_gender = ? AND module = ? AND trans_id = ?|; my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id); @@ -1910,7 +1778,7 @@ sub add_shipto { my $insert_query = qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2, shiptostreet, shiptozipcode, shiptocity, shiptocountry, shiptogln, - shiptocontact, shiptocp_gender, shiptophone, shiptofax, shiptoemail, module) + shiptocontact, shiptophone, shiptofax, shiptoemail, shiptocp_gender, module) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|; do_query($self, $dbh, $insert_query, $id, @values, $module); @@ -2058,26 +1926,6 @@ sub _get_projects { $main::lxdebug->leave_sub(); } -sub _get_shipto { - $main::lxdebug->enter_sub(); - - my ($self, $dbh, $vc_id, $key) = @_; - - $key = "all_shipto" unless ($key); - - if ($vc_id) { - # get shipping addresses - my $query = qq|SELECT * FROM shipto WHERE trans_id = ?|; - - $self->{$key} = selectall_hashref_query($self, $dbh, $query, $vc_id); - - } else { - $self->{$key} = []; - } - - $main::lxdebug->leave_sub(); -} - sub _get_printers { $main::lxdebug->enter_sub(); @@ -2117,36 +1965,6 @@ sub _get_charts { $main::lxdebug->leave_sub(); } -sub _get_taxcharts { - $main::lxdebug->enter_sub(); - - my ($self, $dbh, $params) = @_; - - my $key = "all_taxcharts"; - my @where; - - if (ref $params eq 'HASH') { - $key = $params->{key} if ($params->{key}); - if ($params->{module} eq 'AR') { - push @where, 'chart_categories ~ \'[ACILQ]\''; - - } elsif ($params->{module} eq 'AP') { - push @where, 'chart_categories ~ \'[ACELQ]\''; - } - - } elsif ($params) { - $key = $params; - } - - my $where = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : ''; - - my $query = qq|SELECT * FROM tax $where ORDER BY taxkey, rate|; - - $self->{$key} = selectall_hashref_query($self, $dbh, $query); - - $main::lxdebug->leave_sub(); -} - sub _get_taxzones { $main::lxdebug->enter_sub(); @@ -2360,31 +2178,19 @@ sub _get_simple { $main::lxdebug->leave_sub(); } -#sub _get_groups { -# $main::lxdebug->enter_sub(); -# -# my ($self, $dbh, $key) = @_; -# -# $key ||= "all_groups"; -# -# my $groups = $main::auth->read_groups(); -# -# $self->{$key} = selectall_hashref_query($self, $dbh, $query); -# -# $main::lxdebug->leave_sub(); -#} - sub get_lists { $main::lxdebug->enter_sub(); my $self = shift; my %params = @_; + croak "get_lists: shipto is no longer supported" if $params{shipto}; + my $dbh = $self->get_standard_dbh(\%main::myconfig); my ($sth, $query, $ref); my ($vc, $vc_id); - if ($params{contacts} || $params{shipto}) { + if ($params{contacts}) { $vc = 'customer' if $self->{"vc"} eq "customer"; $vc = 'vendor' if $self->{"vc"} eq "vendor"; die "invalid use of get_lists, need 'vc'" unless $vc; @@ -2395,10 +2201,6 @@ sub get_lists { $self->_get_contacts($dbh, $vc_id, $params{"contacts"}); } - if ($params{"shipto"}) { - $self->_get_shipto($dbh, $vc_id, $params{"shipto"}); - } - if ($params{"projects"} || $params{"all_projects"}) { $self->_get_projects($dbh, $params{"all_projects"} ? $params{"all_projects"} : $params{"projects"}, @@ -2417,10 +2219,6 @@ sub get_lists { $self->_get_charts($dbh, $params{"charts"}); } - if ($params{"taxcharts"}) { - $self->_get_taxcharts($dbh, $params{"taxcharts"}); - } - if ($params{"taxzones"}) { $self->_get_taxzones($dbh, $params{"taxzones"}); } @@ -2473,10 +2271,6 @@ sub get_lists { $self->_get_warehouses($dbh, $params{warehouses}); } -# if ($params{groups}) { -# $self->_get_groups($dbh, $params{groups}); -# } - if ($params{partsgroup}) { $self->get_partsgroup(\%main::myconfig, { all => 1, target => $params{partsgroup} }); } @@ -2710,12 +2504,12 @@ sub create_links { if ($self->{id}) { $query = qq|SELECT - a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid, - a.duedate, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes, + a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid, a.deliverydate, + a.duedate, a.tax_point, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes, a.mtime, a.itime, a.intnotes, a.department_id, a.amount AS oldinvtotal, a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type, - a.globalproject_id, ${extra_columns} + a.globalproject_id, a.transaction_description, ${extra_columns} c.name AS $table, d.description AS department, e.name AS employee @@ -2776,26 +2570,40 @@ sub create_links { c.accno, c.description, a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey, a.chart_id, p.projectnumber, - t.rate, t.id + t.rate, t.id, + a.fx_transaction FROM acc_trans a LEFT JOIN chart c ON (c.id = a.chart_id) LEFT JOIN project p ON (p.id = a.project_id) LEFT JOIN tax t ON (t.id= a.tax_id) WHERE a.trans_id = ? - AND a.fx_transaction = '0' ORDER BY a.acc_trans_id, a.transdate|; $sth = $dbh->prepare($query); do_statement($self, $sth, $query, $self->{id}); # get exchangerate for currency - $self->{exchangerate} = - $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld); + ($self->{exchangerate}, $self->{record_forex}) = $self->check_exchangerate($myconfig, $self->{currency}, $self->{transdate}, $fld, + $self->{id}, $arap); + my $index = 0; + my @fx_transaction_entries; # store amounts in {acc_trans}{$key} for multiple accounts while (my $ref = $sth->fetchrow_hashref("NAME_lc")) { + # skip fx_transaction entries and add them for post processing + if ($ref->{fx_transaction}) { + die "first entry in a record transaction should not be fx_transaction" unless @fx_transaction_entries; + push @{ $fx_transaction_entries[-1] }, $ref; + next; + } else { + push @fx_transaction_entries, [ $ref ]; + } + + + # credit and debit bookings calc fx rate for positions + # also used as exchangerate_$i for payments - exchangerate here can come from frontend or from bank transactions $ref->{exchangerate} = - $self->get_exchangerate($dbh, $self->{currency}, $ref->{transdate}, $fld); + $self->check_exchangerate($myconfig, $self->{currency}, $ref->{transdate}, $fld); if (!($xkeyref{ $ref->{accno} } =~ /tax/)) { $index++; } @@ -2807,6 +2615,36 @@ sub create_links { push @{ $self->{acc_trans}{ $xkeyref{ $ref->{accno} } } }, $ref; } + # post process fx_transactions. + # old bin/mozilla code first posts the intended foreign currency amount and then the correction for exchange flagged as fx_transaction + # for example: when posting 20 USD on a system in EUR with an exchangerate of 1.1, the resulting acc_trans will say: + # +20 no fx (intended: 20 USD) + # +2 fx (but it's actually 22 EUR) + # + # for payments this is followed by the fxgain/loss. when paying the above invoice with 20 USD at 1.3 exchange: + # -20 no fx (intended: 20 USD) + # -6 fx (but it's actually 26 EUR) + # +4 fx (but 4 of them go to fxgain) + # + # bin/mozilla/ controllers will display the intended amount as is, but would have to guess at the actual book value + # without the extra fields + # + # bank transactions however will convert directly into internal currency, so a foreign currency invoice might end up + # having non-fxtransactions. to make sure that these are roundtrip safe, flag the fx-transaction payments as fx and give the + # intendended internal amount + # + # this still operates on the cached entries of form->{acc_trans} + for my $fx_block (@fx_transaction_entries) { + my ($ref, @fx_entries) = @$fx_block; + for my $fx_ref (@fx_entries) { + if ($fx_ref->{chart_id} == $ref->{chart_id}) { + $ref->{defaultcurrency_paid} //= $ref->{amount}; + $ref->{defaultcurrency_paid} += $fx_ref->{amount}; + $ref->{fx_transaction} = 1; + } + } + } + $sth->finish; #check das: $query = @@ -2836,21 +2674,11 @@ sub create_links { $ref = selectfirst_hashref_query($self, $dbh, $query); map { $self->{$_} = $ref->{$_} } keys %$ref; - if ($self->{"$self->{vc}_id"}) { - - # only setup currency - ($self->{currency}) = $self->{defaultcurrency} if !$self->{currency}; - - } else { - - $self->lastname_used($dbh, $myconfig, $table, $module); - - # get exchangerate for currency - $self->{exchangerate} = - $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld); - - } - + # failsafe, set currency if caller has not yet assigned one + $self->lastname_used($dbh, $myconfig, $table, $module) unless ($self->{"$self->{vc}_id"}); + $self->{currency} = $self->{defaultcurrency} unless $self->{currency}; + $self->{exchangerate} = + $self->check_exchangerate($myconfig, $self->{currency}, $self->{transdate}, $fld); } $main::lxdebug->leave_sub(); @@ -2912,19 +2740,48 @@ sub lastname_used { } sub get_variable_content_types { - my %html_variables = ( - longdescription => 'html', - partnotes => 'html', - notes => 'html', - orignotes => 'html', - notes1 => 'html', - notes2 => 'html', - notes3 => 'html', - notes4 => 'html', - header_text => 'html', - footer_text => 'html', + my ($self) = @_; + + my %html_variables = ( + longdescription => 'html', + partnotes => 'html', + notes => 'html', + orignotes => 'html', + notes1 => 'html', + notes2 => 'html', + notes3 => 'html', + notes4 => 'html', + header_text => 'html', + footer_text => 'html', ); - return \%html_variables; + + return { + %html_variables, + $self->get_variable_content_types_for_cvars, + }; +} + +sub get_variable_content_types_for_cvars { + my ($self) = @_; + my $html_configs = SL::DB::Manager::CustomVariableConfig->get_all(where => [ type => 'htmlfield' ]); + my %types; + + if (@{ $html_configs }) { + my %prefix_by_module = ( + Contacts => 'cp_cvar_', + CT => 'vc_cvar_', + IC => 'ic_cvar_', + Projects => 'project_cvar_', + ShipTo => 'shiptocvar_', + ); + + foreach my $cfg (@{ $html_configs }) { + my $prefix = $prefix_by_module{$cfg->module}; + $types{$prefix . $cfg->name} = 'html' if $prefix; + } + } + + return %types; } sub current_date { @@ -3102,20 +2959,28 @@ sub save_status { # $main::locale->text('ELSE') # $main::locale->text('SAVED FOR DUNNING') # $main::locale->text('DUNNING STARTED') +# $main::locale->text('PREVIEWED') # $main::locale->text('PRINTED') # $main::locale->text('MAILED') # $main::locale->text('SCREENED') # $main::locale->text('CANCELED') # $main::locale->text('IMPORT') +# $main::locale->text('UNDO TRANSFER') # $main::locale->text('UNIMPORT') # $main::locale->text('invoice') +# $main::locale->text('invoice_for_advance_payment') +# $main::locale->text('final_invoice') # $main::locale->text('proforma') +# $main::locale->text('storno_invoice') +# $main::locale->text('sales_order_intake') # $main::locale->text('sales_order') # $main::locale->text('pick_list') # $main::locale->text('purchase_order') +# $main::locale->text('purchase_order_confirmation') # $main::locale->text('bin_list') # $main::locale->text('sales_quotation') # $main::locale->text('request_quotation') +# $main::locale->text('purchase_quotation_intake') sub save_history { $main::lxdebug->enter_sub(); @@ -3153,7 +3018,7 @@ sub get_history { qq|SELECT h.employee_id, h.itime::timestamp(0) AS itime, h.addition, h.what_done, emp.name, h.snumbers, h.trans_id AS id | . qq|FROM history_erp h | . qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | . - qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | . + qq|WHERE (trans_id = | . $dbh->quote($trans_id) . qq|) $restriction | . $order; my $sth = $dbh->prepare($query) || $self->dberror($query); @@ -3326,19 +3191,6 @@ sub prepare_for_printing { $self->{"employee_${_}"} = $defaults->$_ for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber); } - # Load shipping address from database. If shipto_id is set then it's - # one from the customer's/vendor's master data. Otherwise look an a - # customized address linking back to the current record. - my $shipto_module = $self->{type} =~ /_delivery_order$/ ? 'DO' - : $self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/ ? 'OE' - : 'AR'; - my $shipto = $self->{shipto_id} ? SL::DB::Shipto->new(shipto_id => $self->{shipto_id})->load - : SL::DB::Manager::Shipto->get_first(where => [ module => $shipto_module, trans_id => $self->{id} ]); - if ($shipto) { - $self->{$_} = $shipto->$_ for grep { m{^shipto} } map { $_->name } @{ $shipto->meta->columns }; - $self->{"shiptocvar_" . $_->config->name} = $_->value_as_text for @{ $shipto->cvars_by_config }; - } - my $language = $self->{language} ? '_' . $self->{language} : ''; my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates); @@ -3359,12 +3211,16 @@ sub prepare_for_printing { if ($self->{type} =~ /_delivery_order$/) { DO->order_details(\%::myconfig, $self); - } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/) { + } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order|purchase_quotation_intake/) { OE->order_details(\%::myconfig, $self); + } elsif ($self->{type} =~ /reclamation/) { + # skip reclamation here, legacy template arrays are added in the reclamation controller } else { IS->invoice_details(\%::myconfig, $self, $::locale); } + $self->set_addition_billing_address_print_variables; + # Chose extension & set source file name my $extension = 'html'; if ($self->{format} eq 'postscript') { @@ -3387,7 +3243,7 @@ sub prepare_for_printing { # Format dates. $self->format_dates($output_dateformat, $output_longdates, - qw(invdate orddate quodate pldate duedate reqdate transdate shippingdate deliverydate validitydate paymentdate datepaid + qw(invdate orddate quodate pldate duedate reqdate transdate tax_point shippingdate deliverydate validitydate paymentdate datepaid transdate_oe deliverydate_oe employee_startdate employee_enddate), grep({ /^(?:datepaid|transdate_oe|reqdate|deliverydate|deliverydate_oe|transdate)_\d+$/ } keys(%{$self}))); @@ -3407,6 +3263,14 @@ sub prepare_for_printing { $self->reformat_numbers($output_numberformat, $precision, @{ $field_list }); } + # Translate units + if (($self->{language} // '') ne '') { + my $template_arrays = $self->{TEMPLATE_ARRAYS} || $self; + for my $idx (0..scalar(@{ $template_arrays->{unit} }) - 1) { + $template_arrays->{unit}->[$idx] = AM->translate_units($self, $self->{language}, $template_arrays->{unit}->[$idx], $template_arrays->{qty}->[$idx]) + } + } + $self->{template_meta} = { formname => $self->{formname}, language => SL::DB::Manager::Language->find_by_or_create(id => $self->{language_id} || undef), @@ -3417,6 +3281,43 @@ sub prepare_for_printing { today => DateTime->today, }; + if ($defaults->print_interpolate_variables_in_positions) { + $self->substitute_placeholders_in_template_arrays({ field => 'description', type => 'text' }, { field => 'longdescription', type => 'html' }); + } + + return $self; +} + +sub set_addition_billing_address_print_variables { + my ($self) = @_; + + return if !$self->{billing_address_id}; + + my $address = SL::DB::Manager::AdditionalBillingAddress->find_by(id => $self->{billing_address_id}); + return if !$address; + + $self->{"billing_address_${_}"} = $address->$_ for map { $_->name } @{ $address->meta->columns }; +} + +sub substitute_placeholders_in_template_arrays { + my ($self, @fields) = @_; + + foreach my $spec (@fields) { + $spec = { field => $spec, type => 'text' } if !ref($spec); + my $field = $spec->{field}; + + next unless exists $self->{TEMPLATE_ARRAYS} && exists $self->{TEMPLATE_ARRAYS}->{$field}; + + my $tag_start = $spec->{type} eq 'html' ? '<%' : '<%'; + my $tag_end = $spec->{type} eq 'html' ? '%>' : '%>'; + my $formatter = $spec->{type} eq 'html' ? sub { $::locale->quote_special_chars('html', $_[0] // '') } : sub { $_[0] }; + + $self->{TEMPLATE_ARRAYS}->{$field} = [ + apply { s{${tag_start}(.+?)${tag_end}}{ $formatter->($self->{$1}) }eg } + @{ $self->{TEMPLATE_ARRAYS}->{$field} } + ]; + } + return $self; } @@ -3464,9 +3365,7 @@ sub calculate_arap { my $tax_id = $self->{"tax_id_$i"}; my $selected_tax = SL::DB::Manager::Tax->find_by(id => "$tax_id"); - - if ( $selected_tax ) { - + if ( $selected_tax && !$selected_tax->reverse_charge_chart_id) { if ( $buysell eq 'sell' ) { $self->{AR_amounts}{"tax_$i"} = $selected_tax->chart->accno if defined $selected_tax->chart; } else { @@ -3477,6 +3376,8 @@ sub calculate_arap { $self->{"taxrate_$i"} = $selected_tax->rate; }; + $self->{"taxkey_$i"} = $selected_tax->taxkey if ($selected_tax && $selected_tax->reverse_charge_chart_id); + ($self->{"amount_$i"}, $self->{"tax_$i"}) = $self->calculate_tax($self->{"amount_$i"},$self->{"taxrate_$i"},$taxincluded,$roundplaces); $netamount += $self->{"amount_$i"}; @@ -3571,19 +3472,11 @@ sub reformat_numbers { } sub create_email_signature { - my $client_signature = $::instance_conf->get_signature; my $user_signature = $::myconfig{signature}; - my $signature = ''; - if ( $client_signature or $user_signature ) { - $signature = "\n\n-- \n"; - $signature .= $user_signature . "\n" if $user_signature; - $signature .= $client_signature . "\n" if $client_signature; - }; - return $signature; - -}; + return join '', grep { $_ } ($user_signature, $client_signature); +} sub calculate_tax { # this function calculates the net amount and tax for the lines in ar, ap and @@ -3701,6 +3594,36 @@ Can be called wit C