header;
print qq|$info |;
- ::end_of_request();
- }
-
- if ($self->{"DEBUG"}) {
- $additional_params->{"DEBUG"} = $self->{"DEBUG"};
- }
-
- if ($additional_params->{"DEBUG"}) {
- $additional_params->{"DEBUG"} =
- "DEBUG INFORMATION: " . $additional_params->{"DEBUG"} . " ";
- }
-
- if (%main::myconfig) {
- $::myconfig{jsc_dateformat} = apply {
- s/d+/\%d/gi;
- s/m+/\%m/gi;
- s/y+/\%Y/gi;
- } $::myconfig{"dateformat"};
- $additional_params->{"myconfig"} ||= \%::myconfig;
- map { $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys %::myconfig;
+ $::dispatcher->end_request;
}
+ $additional_params->{AUTH} = $::auth;
$additional_params->{INSTANCE_CONF} = $::instance_conf;
-
- if (my $debug_options = $::lx_office_conf{debug}{options}) {
- map { $additional_params->{'DEBUG_' . uc($_)} = $debug_options->{$_} } keys %$debug_options;
- }
-
- if ($main::auth && $main::auth->{RIGHTS} && $main::auth->{RIGHTS}->{$self->{login}}) {
- while (my ($key, $value) = each %{ $main::auth->{RIGHTS}->{$self->{login}} }) {
- $additional_params->{"AUTH_RIGHTS_" . uc($key)} = $value;
- }
- }
+ $additional_params->{LOCALE} = $::locale;
+ $additional_params->{LXCONFIG} = \%::lx_office_conf;
+ $additional_params->{LXDEBUG} = $::lxdebug;
+ $additional_params->{MYCONFIG} = \%::myconfig;
$main::lxdebug->leave_sub();
@@ -701,7 +685,7 @@ sub show_generic_error {
SL::ClientJS->new
->error($error)
->render(SL::Controller::Base->new);
- ::end_of_request();
+ $::dispatcher->end_request;
}
my $add_params = {
@@ -732,7 +716,7 @@ sub show_generic_error {
$main::lxdebug->leave_sub();
- ::end_of_request();
+ $::dispatcher->end_request;
}
sub show_generic_information {
@@ -752,7 +736,7 @@ sub show_generic_information {
$main::lxdebug->leave_sub();
- ::end_of_request();
+ $::dispatcher->end_request;
}
sub _store_redirect_info_in_session {
@@ -778,7 +762,7 @@ sub redirect {
print $::form->redirect_header($self->{callback});
}
- ::end_of_request();
+ $::dispatcher->end_request;
$main::lxdebug->leave_sub();
}
@@ -804,6 +788,7 @@ sub format_amount {
my $force_places = defined $places && $places >= 0;
$amount = $self->round_amount($amount, abs $places) if $force_places;
+ $neg = 0 if $amount == 0; # don't show negative zero
$amount = sprintf "%.*f", ($force_places ? $places : 10), abs $amount; # 6 is default for %fa
# before the sprintf amount was a number, afterwards it's a string. because of the dynamic nature of perl
@@ -820,7 +805,7 @@ sub format_amount {
if ($places || $p[1]) {
$amount .= $d[0]
. ( $p[1] || '' )
- . (0 x (abs($places || 0) - length ($p[1]||''))); # pad the fraction
+ . (0 x max(abs($places || 0) - length ($p[1]||''), 0)); # pad the fraction
}
$amount = do {
@@ -944,34 +929,54 @@ sub parse_amount {
# Make sure no code wich is not a math expression ends up in eval().
return 0 unless $amount =~ /^ [\s \d \( \) \- \+ \* \/ \. ]* $/x;
+
+ # Prevent numbers from being parsed as octals;
+ $amount =~ s{ (?get_precision || 0.01;
+ return $self->round_amount( $self->round_amount($amount / $precision, 0) * $precision, $places);
+ }
+
+ # We use Perl's knowledge of string representation for
+ # rounding. First, convert the floating point number to a string
+ # with a high number of places. Then split the string on the decimal
+ # sign and use integer calculation for rounding the decimal places
+ # part. If an overflow occurs then apply that overflow to the part
+ # before the decimal sign as well using integer arithmetic again.
+
+ my $int_amount = int(abs $amount);
+ my $str_places = max(min(10, 16 - length("$int_amount") - $places), $places);
+ my $amount_str = sprintf '%.*f', $places + $str_places, abs($amount);
+
+ return $amount unless $amount_str =~ m{^(\d+)\.(\d+)$};
- # Round amounts to eight places before rounding to the requested
- # number of places. This gets rid of errors due to internal floating
- # point representation.
- $amount = $self->round_amount($amount, 8) if $places < 8;
+ my ($pre, $post) = ($1, $2);
+ my $decimals = '1' . substr($post, 0, $places);
- # Remember the amount's sign but calculate in positive values only.
- my $sign = $amount <=> 0;
- $amount = abs $amount;
+ my $propagation_limit = $Config{i32size} == 4 ? 7 : 18;
+ my $add_for_rounding = substr($post, $places, 1) >= 5 ? 1 : 0;
- # Shift the amount left by $places+1 decimal places and truncate it
- # to integer. Then to the integer equivalent of rounding to the next
- # multiple of 10: first add half of it (5). Then truncate it back to
- # the lower multiple of 10 by subtracting $amount modulo 10.
- my $shift = 10 ** ($places + 1);
- $amount = int($amount * $shift) + 5;
- $amount -= $amount % 10;
+ if ($places > $propagation_limit) {
+ $decimals = Math::BigInt->new($decimals)->badd($add_for_rounding);
+ $pre = Math::BigInt->new($decimals)->badd(1) if substr($decimals, 0, 1) eq '2';
- # Lastly shift the amount back right by $places+1 decimal places and
- # restore its sign. Then we're done.
- $amount = ($amount / $shift) * $sign;
+ } else {
+ $decimals += $add_for_rounding;
+ $pre += 1 if substr($decimals, 0, 1) eq '2';
+ }
+
+ $amount = ("${pre}." . substr($decimals, 1)) * ($amount <=> 0);
return $amount;
}
@@ -1111,7 +1116,7 @@ sub parse_template {
if ($self->{media} eq 'email') {
- my $mail = new Mailer;
+ my $mail = Mailer->new;
map { $mail->{$_} = $self->{$_} }
qw(cc bcc subject message version format);
@@ -1129,7 +1134,7 @@ sub parse_template {
$full_signature =~ s/\n/ \n/g;
$mail->{message} .= $full_signature;
- open(IN, "<", $self->{tmpfile})
+ open(IN, "<:encoding(UTF-8)", $self->{tmpfile})
or $self->error($self->cleanup . "$self->{tmpfile} : $!");
$mail->{message} .= $_ while ;
close(IN);
@@ -1227,6 +1232,8 @@ sub get_formname_translation {
sales_delivery_order => $main::locale->text('Delivery Order'),
purchase_delivery_order => $main::locale->text('Delivery Order'),
dunning => $main::locale->text('Dunning'),
+ letter => $main::locale->text('Letter'),
+ ic_supply => $main::locale->text('Intra-Community supply'),
);
$main::lxdebug->leave_sub();
@@ -1241,8 +1248,13 @@ sub get_number_prefix_for_type {
(first { $self->{type} eq $_ } qw(invoice credit_note)) ? 'inv'
: ($self->{type} =~ /_quotation$/) ? 'quo'
: ($self->{type} =~ /_delivery_order$/) ? 'do'
+ : ($self->{type} =~ /letter/) ? 'letter'
: 'ord';
+ # better default like this?
+ # : ($self->{type} =~ /(sales|purcharse)_order/ : 'ord';
+ # : 'prefix_undefined';
+
$main::lxdebug->leave_sub();
return $prefix;
}
@@ -1278,6 +1290,9 @@ sub generate_attachment_filename {
} elsif ($attachment_filename && $self->{"${prefix}number"}) {
$attachment_filename .= "_" . $self->{"${prefix}number"} . $self->get_extension_for_format();
+ } elsif ($attachment_filename) {
+ $attachment_filename .= $self->get_extension_for_format();
+
} else {
$attachment_filename = "";
}
@@ -1318,7 +1333,7 @@ sub cleanup {
push @err, $::locale->text('The application "#1" was not found on the system.', $application || 'pdflatex') . ' ' . $::locale->text('Please contact your administrator.');
} elsif (-f "$self->{tmpfile}.err") {
- open(FH, "$self->{tmpfile}.err");
+ open(FH, "<:encoding(UTF-8)", "$self->{tmpfile}.err");
@err = ;
close(FH);
}
@@ -1682,36 +1697,20 @@ sub get_default_currency {
}
sub set_payment_options {
- $main::lxdebug->enter_sub();
+ my ($self, $myconfig, $transdate, $type) = @_;
- my ($self, $myconfig, $transdate) = @_;
+ my $terms = $self->{payment_id} ? SL::DB::PaymentTerm->new(id => $self->{payment_id})->load : undef;
+ return if !$terms;
- return $main::lxdebug->leave_sub() unless ($self->{payment_id});
-
- my $dbh = $self->get_standard_dbh($myconfig);
+ my $is_invoice = $type =~ m{invoice}i;
- my $query =
- qq|SELECT p.terms_netto, p.terms_skonto, p.percent_skonto, p.description_long , p.description | .
- qq|FROM payment_terms p | .
- qq|WHERE p.id = ?|;
+ $transdate ||= $self->{invdate} || $self->{transdate};
+ my $due_date = $self->{duedate} || $self->{reqdate};
- ($self->{terms_netto}, $self->{terms_skonto}, $self->{percent_skonto},
- $self->{payment_terms}, $self->{payment_description}) =
- selectrow_query($self, $dbh, $query, $self->{payment_id});
-
- if ($transdate eq "") {
- if ($self->{invdate}) {
- $transdate = $self->{invdate};
- } else {
- $transdate = $self->{transdate};
- }
- }
-
- $query =
- qq|SELECT ?::date + ?::integer AS netto_date, ?::date + ?::integer AS skonto_date | .
- qq|FROM payment_terms|;
- ($self->{netto_date}, $self->{skonto_date}) =
- selectrow_query($self, $dbh, $query, $transdate, $self->{terms_netto}, $transdate, $self->{terms_skonto});
+ $self->{$_} = $terms->$_ for qw(terms_netto terms_skonto percent_skonto);
+ $self->{payment_description} = $terms->description;
+ $self->{netto_date} = $terms->calc_date(reference_date => $transdate, due_date => $due_date, terms => 'net')->to_kivitendo;
+ $self->{skonto_date} = $terms->calc_date(reference_date => $transdate, due_date => $due_date, terms => 'discount')->to_kivitendo;
my ($invtotal, $total);
my (%amounts, %formatted_amounts);
@@ -1741,38 +1740,26 @@ sub set_payment_options {
}
if ($self->{"language_id"}) {
- $query =
- qq|SELECT t.translation, l.output_numberformat, l.output_dateformat, l.output_longdates | .
- qq|FROM generic_translations t | .
- qq|LEFT JOIN language l ON t.language_id = l.id | .
- qq|WHERE (t.language_id = ?)
- AND (t.translation_id = ?)
- AND (t.translation_type = 'SL::DB::PaymentTerm/description_long')|;
- my ($description_long, $output_numberformat, $output_dateformat,
- $output_longdates) =
- selectrow_query($self, $dbh, $query,
- $self->{"language_id"}, $self->{"payment_id"});
-
- $self->{payment_terms} = $description_long if ($description_long);
-
- if ($output_dateformat) {
+ my $language = SL::DB::Language->new(id => $self->{language_id})->load;
+
+ $self->{payment_terms} = $type =~ m{invoice}i ? $terms->translated_attribute('description_long_invoice', $language->id) : undef;
+ $self->{payment_terms} ||= $terms->translated_attribute('description_long', $language->id);
+
+ if ($language->output_dateformat) {
foreach my $key (qw(netto_date skonto_date)) {
- $self->{$key} =
- $main::locale->reformat_date($myconfig, $self->{$key},
- $output_dateformat,
- $output_longdates);
+ $self->{$key} = $::locale->reformat_date($myconfig, $self->{$key}, $language->output_dateformat, $language->output_longdates);
}
}
- if ($output_numberformat &&
- ($output_numberformat ne $myconfig->{"numberformat"})) {
- my $saved_numberformat = $myconfig->{"numberformat"};
- $myconfig->{"numberformat"} = $output_numberformat;
- map { $formatted_amounts{$_} = $self->format_amount($myconfig, $amounts{$_}) } keys %amounts;
- $myconfig->{"numberformat"} = $saved_numberformat;
+ if ($language->output_numberformat && ($language->output_numberformat ne $myconfig->{numberformat})) {
+ local $myconfig->{numberformat};
+ $myconfig->{"numberformat"} = $language->output_numberformat;
+ $formatted_amounts{$_} = $self->format_amount($myconfig, $amounts{$_}) for keys %amounts;
}
}
+ $self->{payment_terms} = $self->{payment_terms} || ($is_invoice ? $terms->description_long_invoice : undef) || $terms->description_long;
+
$self->{payment_terms} =~ s/<%netto_date%>/$self->{netto_date}/g;
$self->{payment_terms} =~ s/<%skonto_date%>/$self->{skonto_date}/g;
$self->{payment_terms} =~ s/<%currency%>/$self->{currency}/g;
@@ -1780,13 +1767,15 @@ sub set_payment_options {
$self->{payment_terms} =~ s/<%account_number%>/$self->{account_number}/g;
$self->{payment_terms} =~ s/<%bank%>/$self->{bank}/g;
$self->{payment_terms} =~ s/<%bank_code%>/$self->{bank_code}/g;
+ $self->{payment_terms} =~ s/<\%bic\%>/$self->{bic}/g;
+ $self->{payment_terms} =~ s/<\%iban\%>/$self->{iban}/g;
+ $self->{payment_terms} =~ s/<\%mandate_date_of_signature\%>/$self->{mandate_date_of_signature}/g;
+ $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};
- $main::lxdebug->leave_sub();
-
}
sub get_template_language {
@@ -1837,20 +1826,25 @@ sub get_shipto {
my $query = qq|SELECT * FROM shipto WHERE shipto_id = ?|;
my $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{shipto_id});
map({ $self->{$_} = $ref->{$_} } keys(%$ref));
+
+ my $cvars = CVar->get_custom_variables(
+ dbh => $dbh,
+ module => 'ShipTo',
+ trans_id => $self->{shipto_id},
+ );
+ $self->{"shiptocvar_$_->{name}"} = $_->{value} for @{ $cvars };
}
$main::lxdebug->leave_sub();
}
sub add_shipto {
- $main::lxdebug->enter_sub();
-
my ($self, $dbh, $id, $module) = @_;
my $shipto;
my @values;
- foreach my $item (qw(name department_1 department_2 street zipcode city country
+ foreach my $item (qw(name department_1 department_2 street zipcode city country gln
contact cp_gender phone fax email)) {
if ($self->{"shipto$item"}) {
$shipto = 1 if ($self->{$item} ne $self->{"shipto$item"});
@@ -1858,52 +1852,68 @@ sub add_shipto {
push(@values, $self->{"shipto${item}"});
}
- if ($shipto) {
- if ($self->{shipto_id}) {
- my $query = qq|UPDATE shipto set
- shiptoname = ?,
- shiptodepartment_1 = ?,
- shiptodepartment_2 = ?,
- shiptostreet = ?,
- shiptozipcode = ?,
- shiptocity = ?,
- shiptocountry = ?,
- shiptocontact = ?,
- shiptocp_gender = ?,
- shiptophone = ?,
- shiptofax = ?,
- shiptoemail = ?
- WHERE shipto_id = ?|;
- do_query($self, $dbh, $query, @values, $self->{shipto_id});
- } else {
- my $query = qq|SELECT * FROM shipto
- WHERE shiptoname = ? AND
- shiptodepartment_1 = ? AND
- shiptodepartment_2 = ? AND
- shiptostreet = ? AND
- shiptozipcode = ? AND
- shiptocity = ? AND
- shiptocountry = ? AND
- shiptocontact = ? AND
- shiptocp_gender = ? AND
- shiptophone = ? AND
- shiptofax = ? AND
- shiptoemail = ? AND
- module = ? AND
- trans_id = ?|;
- my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id);
- if(!$insert_check){
- $query =
- qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2,
- shiptostreet, shiptozipcode, shiptocity, shiptocountry,
- shiptocontact, shiptocp_gender, shiptophone, shiptofax, shiptoemail, module)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
- do_query($self, $dbh, $query, $id, @values, $module);
- }
+ return if !$shipto;
+
+ my $shipto_id = $self->{shipto_id};
+
+ if ($self->{shipto_id}) {
+ my $query = qq|UPDATE shipto set
+ shiptoname = ?,
+ shiptodepartment_1 = ?,
+ shiptodepartment_2 = ?,
+ shiptostreet = ?,
+ shiptozipcode = ?,
+ shiptocity = ?,
+ shiptocountry = ?,
+ shiptogln = ?,
+ shiptocontact = ?,
+ shiptocp_gender = ?,
+ shiptophone = ?,
+ shiptofax = ?,
+ shiptoemail = ?
+ WHERE shipto_id = ?|;
+ do_query($self, $dbh, $query, @values, $self->{shipto_id});
+ } else {
+ my $query = qq|SELECT * FROM shipto
+ WHERE shiptoname = ? AND
+ shiptodepartment_1 = ? AND
+ shiptodepartment_2 = ? AND
+ shiptostreet = ? AND
+ shiptozipcode = ? AND
+ shiptocity = ? AND
+ shiptocountry = ? AND
+ shiptogln = ? AND
+ shiptocontact = ? AND
+ shiptocp_gender = ? AND
+ shiptophone = ? AND
+ shiptofax = ? AND
+ shiptoemail = ? AND
+ module = ? AND
+ trans_id = ?|;
+ my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id);
+ if(!$insert_check){
+ 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)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
+ do_query($self, $dbh, $insert_query, $id, @values, $module);
+
+ $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id);
}
+
+ $shipto_id = $insert_check->{shipto_id};
}
- $main::lxdebug->leave_sub();
+ return unless $shipto_id;
+
+ CVar->save_custom_variables(
+ dbh => $dbh,
+ module => 'ShipTo',
+ trans_id => $shipto_id,
+ variables => $self,
+ name_prefix => 'shipto',
+ );
}
sub get_employee {
@@ -1959,23 +1969,6 @@ sub get_employee_data {
$main::lxdebug->leave_sub();
}
-sub get_duedate {
- $main::lxdebug->enter_sub();
-
- my ($self, $myconfig, $reference_date) = @_;
-
- my $terms = $self->{payment_id} ? SL::DB::PaymentTerm->new(id => $self->{payment_id}) ->load
- : $self->{customer_id} ? SL::DB::Customer ->new(id => $self->{customer_id})->load->payment
- : $self->{vendor_id} ? SL::DB::Vendor ->new(id => $self->{vendor_id}) ->load->payment
- : $self->{invdate} ? undef # no payment terms, therefore invdate == duedate
- : croak("Missing field in \$::form: payment_id, customer_id, vendor_id or invdate");
- my $duedate = $terms ? $terms->calc_date(reference_date => $reference_date)->to_kivitendo : undef;
-
- $main::lxdebug->leave_sub();
-
- return $duedate;
-}
-
sub _get_contacts {
$main::lxdebug->enter_sub();
@@ -2146,7 +2139,7 @@ sub _get_taxzones {
$key = "all_taxzones" unless ($key);
my $tzfilter = "";
- $tzfilter = "WHERE obsolete is FALSE" if $key eq 'ALL_ACTIVE_TAXZONES';
+ $tzfilter = "WHERE obsolete is FALSE" if $key eq 'ALL_ACTIVE_TAXZONES';
my $query = qq|SELECT * FROM tax_zones $tzfilter ORDER BY sortkey|;
@@ -2379,7 +2372,7 @@ sub get_lists {
if ($params{contacts} || $params{shipto}) {
$vc = 'customer' if $self->{"vc"} eq "customer";
$vc = 'vendor' if $self->{"vc"} eq "vendor";
- die "invalid use of get_lists, need 'vc'";
+ die "invalid use of get_lists, need 'vc'" unless $vc;
$vc_id = $self->{"${vc}_id"};
}
@@ -2422,7 +2415,7 @@ sub get_lists {
}
if ($params{"salesmen"}) {
- $self->_get_employees($dbh, "all_salesmen", $params{"salesmen"});
+ $self->_get_employees($dbh, $params{"salesmen"});
}
if ($params{"business_types"}) {
@@ -2494,10 +2487,10 @@ sub get_name {
my $where;
if ($self->{customernumber} ne "") {
$where = qq|(vc.customernumber ILIKE ?)|;
- push(@values, '%' . $self->{customernumber} . '%');
+ push(@values, like($self->{customernumber}));
} else {
$where = qq|(vc.name ILIKE ?)|;
- push(@values, '%' . $self->{$table} . '%');
+ push(@values, like($self->{$table}));
}
$query =
@@ -2514,7 +2507,7 @@ sub get_name {
JOIN $table vc ON (a.${table}_id = vc.id)
WHERE NOT (a.amount = a.paid) AND (vc.name ILIKE ?)
ORDER BY vc.name~;
- push(@values, '%' . $self->{$table} . '%');
+ push(@values, like($self->{$table}));
}
$self->{name_list} = selectall_hashref_query($self, $dbh, $query, @values);
@@ -2597,6 +2590,38 @@ sub all_vc {
$main::lxdebug->leave_sub();
}
+sub new_lastmtime {
+ my ($self, $table, $option) = @_;
+
+ return unless $self->{id};
+ croak ("wrong call, no valid table defined") unless $table =~ /^(oe|ar|ap|delivery_orders|parts)$/;
+
+ my $query = "SELECT mtime, itime FROM " . $table . " WHERE id = ?";
+ my $ref = selectfirst_hashref_query($self, $self->get_standard_dbh, $query, $self->{id});
+ $ref->{mtime} ||= $ref->{itime};
+ $self->{lastmtime} = $ref->{mtime};
+ $main::lxdebug->message(LXDebug->DEBUG2(),"new lastmtime=".$self->{lastmtime});
+}
+
+sub mtime_ischanged {
+ my ($self, $table, $option) = @_;
+
+ return unless $self->{id};
+ croak ("wrong call, no valid table defined") unless $table =~ /^(oe|ar|ap|delivery_orders|parts)$/;
+
+ my $query = "SELECT mtime, itime FROM " . $table . " WHERE id = ?";
+ my $ref = selectfirst_hashref_query($self, $self->get_standard_dbh, $query, $self->{id});
+ $ref->{mtime} ||= $ref->{itime};
+
+ if ($self->{lastmtime} && $self->{lastmtime} ne $ref->{mtime} ) {
+ $self->error(($option eq 'mail') ?
+ t8("The document has been changed by another user. No mail was sent. Please reopen it in another window and copy the changes to the new window") :
+ t8("The document has been changed by another user. Please reopen it in another window and copy the changes to the new window")
+ );
+ $::dispatcher->end_request;
+ }
+}
+
sub language_payment {
$main::lxdebug->enter_sub();
@@ -2707,7 +2732,7 @@ sub create_links {
$sth = $dbh->prepare($query);
- do_statement($self, $sth, $query, '%' . $module . '%');
+ do_statement($self, $sth, $query, like($module));
$self->{accounts} = "";
while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
@@ -2748,6 +2773,7 @@ sub create_links {
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.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}
@@ -2764,7 +2790,8 @@ sub create_links {
foreach my $key (keys %$ref) {
$self->{$key} = $ref->{$key};
}
-
+ $self->{mtime} ||= $self->{itime};
+ $self->{lastmtime} = $self->{mtime};
my $transdate = "current_date";
if ($self->{transdate}) {
$transdate = $dbh->quote($self->{transdate});
@@ -2780,7 +2807,7 @@ sub create_links {
ORDER BY c.accno|;
$sth = $dbh->prepare($query);
- do_statement($self, $sth, $query, "%$module%");
+ do_statement($self, $sth, $query, like($module));
$self->{accounts} = "";
while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
@@ -2847,7 +2874,9 @@ sub create_links {
d.closedto, d.revtrans,
(SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency,
(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
FROM defaults d|;
$ref = selectfirst_hashref_query($self, $dbh, $query);
map { $self->{$_} = $ref->{$_} } keys %$ref;
@@ -2860,7 +2889,9 @@ sub create_links {
current_date AS transdate, d.closedto, d.revtrans,
(SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency,
(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
FROM defaults d|;
$ref = selectfirst_hashref_query($self, $dbh, $query);
map { $self->{$_} = $ref->{$_} } keys %$ref;
@@ -2898,7 +2929,6 @@ sub lastname_used {
"d.description" => "department",
"ct.name" => $table,
"cu.name" => "currency",
- "current_date + ct.terms" => "duedate",
);
if ($self->{type} =~ /delivery_order/) {
@@ -2968,22 +2998,6 @@ sub current_date {
return $thisdate;
}
-sub like {
- $main::lxdebug->enter_sub();
-
- my ($self, $string) = @_;
-
- if ($string !~ /%/) {
- $string = "%$string%";
- }
-
- $string =~ s/\'/\'\'/g;
-
- $main::lxdebug->leave_sub();
-
- return $string;
-}
-
sub redo_rows {
$main::lxdebug->enter_sub();
@@ -3352,10 +3366,17 @@ 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.
- if ($self->{shipto_id}) {
- my $shipto = SL::DB::Shipto->new(shipto_id => $self->{shipto_id})->load;
+ # 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} : '';
@@ -3439,6 +3460,82 @@ sub prepare_for_printing {
return $self;
}
+sub calculate_arap {
+ my ($self,$buysell,$taxincluded,$exchangerate,$roundplaces) = @_;
+
+ # this function is used to calculate netamount, total_tax and amount for AP and
+ # AR transactions (Kreditoren-/Debitorenbuchungen) by going over all lines
+ # (1..$rowcount)
+ # Thus it needs a fully prepared $form to work on.
+ # calculate_arap assumes $form->{amount_$i} entries still need to be parsed
+
+ # The calculated total values are all rounded (default is to 2 places) and
+ # returned as parameters rather than directly modifying form. The aim is to
+ # make the calculation of AP and AR behave identically. There is a test-case
+ # for this function in t/form/arap.t
+
+ # While calculating the totals $form->{amount_$i} and $form->{tax_$i} are
+ # modified and formatted and receive the correct sign for writing straight to
+ # acc_trans, depending on whether they are ar or ap.
+
+ # check parameters
+ die "taxincluded needed in Form->calculate_arap" unless defined $taxincluded;
+ die "exchangerate needed in Form->calculate_arap" unless defined $exchangerate;
+ die 'illegal buysell parameter, has to be \"buy\" or \"sell\" in Form->calculate_arap\n' unless $buysell =~ /^(buy|sell)$/;
+ $roundplaces = 2 unless $roundplaces;
+
+ my $sign = 1; # adjust final results for writing amount to acc_trans
+ $sign = -1 if $buysell eq 'buy';
+
+ my ($netamount,$total_tax,$amount);
+
+ my $tax;
+
+ # parse and round amounts, setting correct sign for writing to acc_trans
+ for my $i (1 .. $self->{rowcount}) {
+ $self->{"amount_$i"} = $self->round_amount($self->parse_amount(\%::myconfig, $self->{"amount_$i"}) * $exchangerate * $sign, $roundplaces);
+
+ $amount += $self->{"amount_$i"} * $sign;
+ }
+
+ for my $i (1 .. $self->{rowcount}) {
+ next unless $self->{"amount_$i"};
+ ($self->{"tax_id_$i"}) = split /--/, $self->{"taxchart_$i"};
+ my $tax_id = $self->{"tax_id_$i"};
+
+ my $selected_tax = SL::DB::Manager::Tax->find_by(id => "$tax_id");
+
+ if ( $selected_tax ) {
+
+ if ( $buysell eq 'sell' ) {
+ $self->{AR_amounts}{"tax_$i"} = $selected_tax->chart->accno if defined $selected_tax->chart;
+ } else {
+ $self->{AP_amounts}{"tax_$i"} = $selected_tax->chart->accno if defined $selected_tax->chart;
+ };
+
+ $self->{"taxkey_$i"} = $selected_tax->taxkey;
+ $self->{"taxrate_$i"} = $selected_tax->rate;
+ };
+
+ ($self->{"amount_$i"}, $self->{"tax_$i"}) = $self->calculate_tax($self->{"amount_$i"},$self->{"taxrate_$i"},$taxincluded,$roundplaces);
+
+ $netamount += $self->{"amount_$i"};
+ $total_tax += $self->{"tax_$i"};
+
+ }
+ $amount = $netamount + $total_tax;
+
+ # due to $sign amount_$i und tax_$i already have the right sign for acc_trans
+ # but reverse sign of totals for writing amounts to ar
+ if ( $buysell eq 'buy' ) {
+ $netamount *= -1;
+ $amount *= -1;
+ $total_tax *= -1;
+ };
+
+ return($netamount,$total_tax,$amount);
+}
+
sub format_dates {
my ($self, $dateformat, $longformat, @indices) = @_;
@@ -3551,6 +3648,40 @@ sub layout {
return $layout;
}
+sub calculate_tax {
+ # this function calculates the net amount and tax for the lines in ar, ap and
+ # gl and is used for update as well as post. When used with update the return
+ # value of amount isn't needed
+
+ # calculate_tax should always work with positive values, or rather as the user inputs them
+ # calculate_tax uses db/perl numberformat, i.e. parsed numbers
+ # convert to negative numbers (when necessary) only when writing to acc_trans
+ # the amount from $form for ap/ar/gl is currently always rounded to 2 decimals before it reaches here
+ # for post_transaction amount already contains exchangerate and correct sign and is rounded
+ # calculate_tax doesn't (need to) know anything about exchangerate
+
+ my ($self,$amount,$taxrate,$taxincluded,$roundplaces) = @_;
+
+ $roundplaces //= 2;
+ $taxincluded //= 0;
+
+ my $tax;
+
+ if ($taxincluded) {
+ # calculate tax (unrounded), subtract from amount, round amount and round tax
+ $tax = $amount - ($amount / ($taxrate + 1)); # equivalent to: taxrate * amount / (taxrate + 1)
+ $amount = $self->round_amount($amount - $tax, $roundplaces);
+ $tax = $self->round_amount($tax, $roundplaces);
+ } else {
+ $tax = $amount * $taxrate;
+ $tax = $self->round_amount($tax, $roundplaces);
+ }
+
+ $tax = 0 unless $tax;
+
+ return ($amount,$tax);
+};
+
1;
__END__
@@ -3622,6 +3753,17 @@ Used to override the default favicon.
A html page title will be generated from this
+=item mtime_ischanged
+
+Tries to avoid concurrent write operations to records by checking the database mtime with a fetched one.
+
+Can be used / called with any table, that has itime and mtime attributes.
+Valid C names are: oe, ar, ap, delivery_orders, parts.
+Can be called wit C mail to generate a different error message.
+
+Returns undef if no save operation has been done yet ($self->{id} not present).
+Returns undef if no concurrent write process is detected otherwise a error message.
+
=back
=cut